diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1aa430f2..f48af9667 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,7 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: thing: - stable @@ -109,6 +110,8 @@ jobs: rust: stable os: ubuntu-latest check_only: true + custom_env: + CXXFLAGS: -msse2 - thing: x86_64-android target: x86_64-linux-android rust: stable @@ -139,6 +142,8 @@ jobs: rust: stable os: ubuntu-latest apt_packages: gcc-multilib g++-multilib + custom_env: + CXXFLAGS: -msse2 - thing: arm-linux target: arm-unknown-linux-gnueabi rust: stable @@ -185,6 +190,8 @@ jobs: target: i686-pc-windows-msvc rust: stable-x86_64-msvc os: windows-latest + custom_env: + CXXFLAGS: -msse2 # CI's Windows doesn't have required root certs extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring - thing: x86_64-msvc @@ -282,6 +289,7 @@ jobs: name: Cross build from macOS to Linux runs-on: macos-latest strategy: + fail-fast: false matrix: include: - target: x86_64-unknown-linux-gnu @@ -303,6 +311,8 @@ jobs: shell: bash - name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV + - name: Set CXXFLAGS + run: echo "CXXFLAGS=-D__STDC_FORMAT_MACROS" >> $GITHUB_ENV - name: Build for ${{ matrix.target }} run: cargo build --target ${{ matrix.target }} --all-targets diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index ce49709d2..6def757f8 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -20,6 +20,7 @@ include = [ "/LICENSE-MIT", "/cmake/*.cmake", "/deps/boringssl/**/*.[chS]", + "/deps/boringssl/**/*.inc", "/deps/boringssl/**/*.asm", "/deps/boringssl/**/*.pl", "/deps/boringssl/**/*.go", diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 25aaabf40..49125aa30 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -9,7 +9,9 @@ pub(crate) struct Config { pub(crate) host: String, pub(crate) target: String, pub(crate) target_arch: String, + pub(crate) target_env: String, pub(crate) target_os: String, + pub(crate) unix: bool, pub(crate) features: Features, pub(crate) env: Env, } @@ -45,7 +47,9 @@ impl Config { let host = env::var("HOST").unwrap(); let target = env::var("TARGET").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let unix = env::var("CARGO_CFG_UNIX").is_ok(); let features = Features::from_env(); let env = Env::from_env(&host, &target, features.is_fips_like()); @@ -62,7 +66,9 @@ impl Config { host, target, target_arch, + target_env, target_os, + unix, features, env, }; diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 41789cee3..bc2792c55 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -1,5 +1,4 @@ use fslock::LockFile; -use std::env; use std::ffi::OsString; use std::fs; use std::io; @@ -199,11 +198,19 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { let src_path = get_boringssl_source_path(config); let mut boringssl_cmake = cmake::Config::new(src_path); - if config.host == config.target { + if config.env.cmake_toolchain_file.is_some() { return boringssl_cmake; } - if config.env.cmake_toolchain_file.is_some() { + if config.target_os == "windows" { + // Explicitly use the non-debug CRT. + // This is required now because newest BoringSSL requires CMake 3.22 which + // uses the new logic with CMAKE_MSVC_RUNTIME_LIBRARY introduced in CMake 3.15. + // https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2969758499 + boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL"); + } + + if config.host == config.target { return boringssl_cmake; } @@ -254,8 +261,8 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file); // 21 is the minimum level tested. You can give higher value. - boringssl_cmake.define("ANDROID_NATIVE_API_LEVEL", "21"); - boringssl_cmake.define("ANDROID_STL", "c++_shared"); + boringssl_cmake.define("CMAKE_SYSTEM_VERSION", "21"); + boringssl_cmake.define("CMAKE_ANDROID_STL_TYPE", "c++_shared"); } "macos" => { @@ -553,14 +560,11 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { return cpp_lib.clone().into_string().ok(); } - // TODO(rmehra): figure out how to do this for windows - if env::var_os("CARGO_CFG_UNIX").is_some() { - match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() { - "macos" | "ios" | "freebsd" => Some("c++".into()), - _ => Some("stdc++".into()), - } - } else { - None + match &*config.target_os { + "macos" | "ios" | "freebsd" | "android" => Some("c++".into()), + _ if config.unix || config.target_env == "gnu" => Some("stdc++".into()), + // TODO(rmehra): figure out how to do this for windows + _ => None, } } diff --git a/boring-sys/deps/boringssl b/boring-sys/deps/boringssl index 478b28ab1..91a66a59b 160000 --- a/boring-sys/deps/boringssl +++ b/boring-sys/deps/boringssl @@ -1 +1 @@ -Subproject commit 478b28ab12f2001a03261624261fd041f5439706 +Subproject commit 91a66a59b6c1435120ff83e245d7719411294386 diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 5c55eb8c4..f0aa1ea26 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -1,4746 +1,706 @@ -From 969fc4fb866c94b6585c323d6e27571e5286f845 Mon Sep 17 00:00:00 2001 -From: Bas Westerbaan -Date: Thu, 2 Oct 2025 13:07:05 +0200 +From cb5689e091f515fc8a42ceaff08d702333e505ed Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Wed, 3 Dec 2025 11:10:16 +0100 Subject: [PATCH] Add additional post-quantum key agreements -BoringSSL upstream has supported the temporary post-quantum -key agreement X25519Kyber768Draft00 (0x6399) for a while. -At the time of writing X25519Kyber768Draft00 is widely deployed by browsers. - -Recent BoringSSL adds support for X25519MLKEM768 (0x11ec), -which will be the long term post-quantum key agreement of choice, -and many browsers are expected to switch to it before the end of 2024. - -This patch adds: - -1. Support for X25519MLKEM768 under the codepoint 0x11ec. The version - of BoringSSL we patch against did not support it yet. Like recent - upstream, enable by default. - -2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily - need for compliance reasons. (Note that this is not the codepoint - allocated for that exchange in the IANA table.) - Enables by default and in FIPS mode. - -3. Support for X25519Kyber768Draft00 under the old codepoint 0xfe31. - -4. Support for X25519Kyber512Draft00 under the codepoint 0xfe30. This - key agreement should only be used for testing: to see if the smaller - keyshare makes a difference. - -The patch also replaces Google's implementation of Kyber, by the -portable reference implementation, so as to support Kyber512. - -Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 ---- - crypto/CMakeLists.txt | 3 +- - crypto/kyber/internal.h | 60 - - crypto/kyber/kyber.c | 3013 +++++++++++++++++++++++++++--------- - crypto/kyber/kyber512.c | 5 + - crypto/kyber/kyber768.c | 4 + - crypto/kyber/kyber_test.cc | 184 --- - crypto/obj/obj_dat.h | 17 +- - crypto/obj/obj_mac.num | 4 + - crypto/obj/objects.txt | 6 +- - include/openssl/kyber.h | 203 ++- - include/openssl/nid.h | 12 + - include/openssl/ssl.h | 4 + - sources.cmake | 2 - - ssl/extensions.cc | 6 + - ssl/ssl_key_share.cc | 525 ++++++- - ssl/ssl_lib.cc | 2 +- - ssl/ssl_test.cc | 29 +- - tool/speed.cc | 162 +- - 18 files changed, 3083 insertions(+), 1158 deletions(-) - delete mode 100644 crypto/kyber/internal.h - create mode 100644 crypto/kyber/kyber512.c - create mode 100644 crypto/kyber/kyber768.c - delete mode 100644 crypto/kyber/kyber_test.cc - -diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt -index a594b9e9d..ed468237f 100644 ---- a/crypto/CMakeLists.txt -+++ b/crypto/CMakeLists.txt -@@ -176,7 +176,8 @@ add_library( - hpke/hpke.c - hrss/hrss.c - keccak/keccak.c -- kyber/kyber.c -+ kyber/kyber512.c -+ kyber/kyber768.c - lhash/lhash.c - mem.c - obj/obj.c -diff --git a/crypto/kyber/internal.h b/crypto/kyber/internal.h -deleted file mode 100644 -index b11211726..000000000 ---- a/crypto/kyber/internal.h -+++ /dev/null -@@ -1,60 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#ifndef OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H --#define OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -- --#include --#include -- --#if defined(__cplusplus) --extern "C" { --#endif -- -- --// KYBER_ENCAP_ENTROPY is the number of bytes of uniformly random entropy --// necessary to encapsulate a secret. The entropy will be leaked to the --// decapsulating party. --#define KYBER_ENCAP_ENTROPY 32 -- --// KYBER_GENERATE_KEY_ENTROPY is the number of bytes of uniformly random entropy --// necessary to generate a key. --#define KYBER_GENERATE_KEY_ENTROPY 64 -- --// KYBER_generate_key_external_entropy is a deterministic function to create a --// pair of Kyber768 keys, using the supplied entropy. The entropy needs to be --// uniformly random generated. This function is should only be used for tests, --// regular callers should use the non-deterministic |KYBER_generate_key| --// directly. --OPENSSL_EXPORT void KYBER_generate_key_external_entropy( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key, -- const uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]); -- --// KYBER_encap_external_entropy is a deterministic function to encapsulate --// |out_shared_secret_len| bytes of |out_shared_secret| to |ciphertext|, using --// |KYBER_ENCAP_ENTROPY| bytes of |entropy| for randomization. The --// decapsulating side will be able to recover |entropy| in full. This --// function is should only be used for tests, regular callers should use the --// non-deterministic |KYBER_encap| directly. --OPENSSL_EXPORT void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]); -- --#if defined(__cplusplus) --} --#endif -- --#endif // OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -diff --git a/crypto/kyber/kyber.c b/crypto/kyber/kyber.c -index d3ea02090..74d092907 100644 ---- a/crypto/kyber/kyber.c -+++ b/crypto/kyber/kyber.c -@@ -1,835 +1,2426 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+// Taken from round 3 public domain reference implementation -+// -+// https://github.com/pq-crystals/kyber -+// 8e00ec73035147d18b27d06048dff322f8de1f29 -+// -+// with some small modifications: -+// -+// - Merged into one file. -+// - Removed 90s version. -+// - Seeds are passed as paramters. -+// - Changed the API to be more BoringSSL-like -+// - Mitigated timing sidechannels (Kyberslash 1 and 2). -+// (Note that these do not affect ephemeral usage as in TLS.) -+// -+// TODO -+// -+// - Optimizations -+// -+// The majority of Kyber's time is spent in keccak: generating the matrix -+// A, hashing the public key, et cetera. This can be sped up dramatically -+// by using a multiway keccak implementation such as f1600x4 on AVX2. -+// -+// Also the NTT and other operations can be sped up with SIMD. This is -+// more complex and the gains are more modest. See the avx2 reference -+// implementation or https://github.com/cloudflare/circl/tree/main/pke/kyber -+// -+// - Option to keep A stored in private key. - --#include -+#ifndef KYBER_K -+#error "Don't compile this file direcly" -+#endif - --#include --#include -+#include -+#include - --#include --#include -+#include -+#include -+#include - - #include "../internal.h" --#include "../keccak/internal.h" --#include "./internal.h" -- -- --// See --// https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf -- --#define DEGREE 256 --#define RANK 3 -- --static const size_t kBarrettMultiplier = 5039; --static const unsigned kBarrettShift = 24; --static const uint16_t kPrime = 3329; --static const int kLog2Prime = 12; --static const uint16_t kHalfPrime = (/*kPrime=*/3329 - 1) / 2; --static const int kDU = 10; --static const int kDV = 4; --// kInverseDegree is 128^-1 mod 3329; 128 because kPrime does not have a 512th --// root of unity. --static const uint16_t kInverseDegree = 3303; --static const size_t kEncodedVectorSize = -- (/*kLog2Prime=*/12 * DEGREE / 8) * RANK; --static const size_t kCompressedVectorSize = /*kDU=*/10 * RANK * DEGREE / 8; -- --typedef struct scalar { -- // On every function entry and exit, 0 <= c < kPrime. -- uint16_t c[DEGREE]; --} scalar; -- --typedef struct vector { -- scalar v[RANK]; --} vector; -- --typedef struct matrix { -- scalar v[RANK][RANK]; --} matrix; -- --// This bit of Python will be referenced in some of the following comments: --// --// p = 3329 --// --// def bitreverse(i): --// ret = 0 --// for n in range(7): --// bit = i & 1 --// ret <<= 1 --// ret |= bit --// i >>= 1 --// return ret -- --// kNTTRoots = [pow(17, bitreverse(i), p) for i in range(128)] --static const uint16_t kNTTRoots[128] = { -- 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, -- 2786, 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, -- 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, -- 1197, 2304, 2277, 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, -- 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, -- 2474, 3110, 1227, 910, 17, 2761, 583, 2649, 1637, 723, 2288, 1100, -- 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, 1703, 1651, 2789, 1789, -- 1847, 952, 1461, 2687, 939, 2308, 2437, 2388, 733, 2337, 268, 641, -- 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, -- 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, -- 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, --}; - --// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] --static const uint16_t kInverseNTTRoots[128] = { -- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, -- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, -- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, -- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, -- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, -- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, -- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, -- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, -- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, -- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, -- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, --}; -+#if (KYBER_K == 2) -+#define KYBER_NAMESPACE(s) KYBER512_##s -+#elif (KYBER_K == 3) -+#define KYBER_NAMESPACE(s) KYBER768_##s -+#elif (KYBER_K == 4) -+#define KYBER_NAMESPACE(s) KYBER1024_##s -+#else -+#error "KYBER_K must be in {2,3,4}" -+#endif - --// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] --static const uint16_t kModRoots[128] = { -- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, -- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, -- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, -- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, -- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, -- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, -- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, -- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, -- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, -- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, -- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, --}; -+#define public_key KYBER_NAMESPACE(public_key) -+#define private_key KYBER_NAMESPACE(private_key) - --// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. --static uint16_t reduce_once(uint16_t x) { -- assert(x < 2 * kPrime); -- const uint16_t subtracted = x - kPrime; -- uint16_t mask = 0u - (subtracted >> 15); -- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber -- // overall and Clang still produces constant-time code using `csel`. On other -- // platforms & compilers on godbolt that we care about, this code also -- // produces constant-time output. -- return (mask & x) | (~mask & subtracted); --} -- --// constant time reduce x mod kPrime using Barrett reduction. x must be less --// than kPrime + 2×kPrime². --static uint16_t reduce(uint32_t x) { -- assert(x < kPrime + 2u * kPrime * kPrime); -- uint64_t product = (uint64_t)x * kBarrettMultiplier; -- uint32_t quotient = (uint32_t)(product >> kBarrettShift); -- uint32_t remainder = x - quotient * kPrime; -- return reduce_once(remainder); --} -- --static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --// In place number theoretic transform of a given scalar. --// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this --// transform leaves off the last iteration of the usual FFT code, with the 128 --// relevant roots of unity being stored in |kNTTRoots|. This means the output --// should be seen as 128 elements in GF(3329^2), with the coefficients of the --// elements being consecutive entries in |s->c|. --static void scalar_ntt(scalar *s) { -- int offset = DEGREE; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int step = 1; step < DEGREE / 2; step <<= 1) { -- offset >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- const uint32_t step_root = kNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = reduce(step_root * s->c[j + offset]); -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce_once(even - odd + kPrime); -- } -- k += 2 * offset; -+#define generate_key KYBER_NAMESPACE(generate_key) -+#define encap KYBER_NAMESPACE(encap) -+#define decap KYBER_NAMESPACE(decap) -+#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) -+#define parse_public_key KYBER_NAMESPACE(parse_public_key) -+ -+ -+// -+// params.h -+// -+#define KYBER_N 256 -+#define KYBER_Q 3329 -+ -+#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ -+#define KYBER_SSBYTES 32 /* size in bytes of shared key */ -+ -+#define KYBER_POLYBYTES 384 -+#define KYBER_POLYVECBYTES (KYBER_K * KYBER_POLYBYTES) -+ -+#if KYBER_K == 2 -+#define KYBER_ETA1 3 -+#define KYBER_POLYCOMPRESSEDBYTES 128 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) -+#elif KYBER_K == 3 -+#define KYBER_ETA1 2 -+#define KYBER_POLYCOMPRESSEDBYTES 128 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) -+#elif KYBER_K == 4 -+#define KYBER_ETA1 2 -+#define KYBER_POLYCOMPRESSEDBYTES 160 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 352) -+#endif -+ -+#define KYBER_ETA2 2 -+ -+#define KYBER_INDCPA_MSGBYTES (KYBER_SYMBYTES) -+#define KYBER_INDCPA_PUBLICKEYBYTES (KYBER_POLYVECBYTES + KYBER_SYMBYTES) -+#define KYBER_INDCPA_SECRETKEYBYTES (KYBER_POLYVECBYTES) -+#define KYBER_INDCPA_BYTES (KYBER_POLYVECCOMPRESSEDBYTES + KYBER_POLYCOMPRESSEDBYTES) -+ -+#define KYBER_PUBLICKEYBYTES (KYBER_INDCPA_PUBLICKEYBYTES) -+/* 32 bytes of additional space to save H(pk) */ -+#define KYBER_SECRETKEYBYTES (KYBER_INDCPA_SECRETKEYBYTES + KYBER_INDCPA_PUBLICKEYBYTES + 2*KYBER_SYMBYTES) -+#define KYBER_CIPHERTEXTBYTES (KYBER_INDCPA_BYTES) -+ -+// -+// verify.h -+// -+static int verify(const uint8_t *a, const uint8_t *b, size_t len); -+static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b); -+ -+// -+// reduce.h -+// -+#define MONT -1044 // 2^16 mod q -+#define QINV -3327 // q^-1 mod 2^16 -+ -+static int16_t montgomery_reduce(int32_t a); -+static int16_t barrett_reduce(int16_t a); -+ -+// -+// ntt.h -+// -+static void ntt(int16_t poly[256]); -+static void invntt(int16_t poly[256]); -+static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta); -+ -+// -+// poly.h -+// -+ -+/* -+ * Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial -+ * coeffs[0] + X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1] -+ */ -+typedef struct{ -+ int16_t coeffs[KYBER_N]; -+} poly; -+ -+static void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a); -+static void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]); -+ -+static void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a); -+static void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]); -+ -+static void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]); -+static void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *r); -+ -+static void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); -+static void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); -+ -+static void poly_ntt(poly *r); -+static void poly_invntt_tomont(poly *r); -+static void poly_basemul_montgomery(poly *r, const poly *a, const poly *b); -+static void poly_tomont(poly *r); -+ -+static void poly_reduce(poly *r); -+ -+static void poly_add(poly *r, const poly *a, const poly *b); -+static void poly_sub(poly *r, const poly *a, const poly *b); -+ -+// -+// cbd.h -+// -+static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]); -+static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]); -+ -+// -+// polyvec.h -+// -+ -+typedef struct{ -+ poly vec[KYBER_K]; -+} polyvec; -+ -+static void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a); -+static void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]); -+ -+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a); -+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]); -+ -+static void polyvec_ntt(polyvec *r); -+static void polyvec_invntt_tomont(polyvec *r); -+ -+static void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b); -+ -+static void polyvec_reduce(polyvec *r); -+ -+static void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b); -+ -+// -+// indcpa.h -+// -+ -+static void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed); -+static void indcpa_keypair(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], -+ const uint8_t seed[KYBER_SYMBYTES]); -+ -+static int indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ const uint8_t coins[KYBER_SYMBYTES]); -+ -+static void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]); -+ -+// -+// fips202.h -+// -+ -+#define SHAKE128_RATE 168 -+#define SHAKE256_RATE 136 -+#define SHA3_256_RATE 136 -+#define SHA3_512_RATE 72 -+ -+typedef struct { -+ uint64_t s[25]; -+ unsigned int pos; -+} keccak_state; -+ -+static void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); -+ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state); -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake256_finalize(keccak_state *state); -+static void shake256_init(keccak_state *state); -+ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen); -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen); -+ -+// -+// symmetric.h -+// -+ -+typedef keccak_state xof_state; -+ -+static void kyber_shake128_absorb(keccak_state *s, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y); -+ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce); -+ -+#define XOF_BLOCKBYTES SHAKE128_RATE -+ -+#define hash_h(OUT, IN, INBYTES) sha3_256(OUT, IN, INBYTES) -+#define hash_g(OUT, IN, INBYTES) sha3_512(OUT, IN, INBYTES) -+#define xof_absorb(STATE, SEED, X, Y) kyber_shake128_absorb(STATE, SEED, X, Y) -+#define xof_squeezeblocks(OUT, OUTBLOCKS, STATE) shake128_squeezeblocks(OUT, OUTBLOCKS, STATE) -+#define prf(OUT, OUTBYTES, KEY, NONCE) kyber_shake256_prf(OUT, OUTBYTES, KEY, NONCE) -+#define kdf(OUT, IN, INBYTES) shake256(OUT, KYBER_SSBYTES, IN, INBYTES) -+ -+ -+// -+// verify.c -+// -+ -+/************************************************* -+* Name: verify -+* -+* Description: Compare two arrays for equality in constant time. -+* -+* Arguments: const uint8_t *a: pointer to first byte array -+* const uint8_t *b: pointer to second byte array -+* size_t len: length of the byte arrays -+* -+* Returns 0 if the byte arrays are equal, 1 otherwise -+**************************************************/ -+static int verify(const uint8_t *a, const uint8_t *b, size_t len) -+{ -+ size_t i; -+ uint8_t r = 0; -+ -+ for(i=0;i> 63; -+} -+ -+/************************************************* -+* Name: cmov -+* -+* Description: Copy len bytes from x to r if b is 1; -+* don't modify x if b is 0. Requires b to be in {0,1}; -+* assumes two's complement representation of negative integers. -+* Runs in constant time. -+* -+* Arguments: uint8_t *r: pointer to output byte array -+* const uint8_t *x: pointer to input byte array -+* size_t len: Amount of bytes to be copied -+* uint8_t b: Condition bit; has to be in {0,1} -+**************************************************/ -+static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b) -+{ -+ size_t i; -+ -+ b = -b; -+ for(i=0;i> 16; -+ return t; -+} -+ -+/************************************************* -+* Name: barrett_reduce -+* -+* Description: Barrett reduction; given a 16-bit integer a, computes -+* centered representative congruent to a mod q in {-(q-1)/2,...,(q-1)/2} -+* -+* Arguments: - int16_t a: input integer to be reduced -+* -+* Returns: integer in {-(q-1)/2,...,(q-1)/2} congruent to a modulo q. -+**************************************************/ -+static int16_t barrett_reduce(int16_t a) { -+ int16_t t; -+ const int16_t v = ((1<<26) + KYBER_Q/2)/KYBER_Q; -+ -+ t = ((int32_t)v*a + (1<<25)) >> 26; -+ t *= KYBER_Q; -+ return a - t; -+} -+ -+// -+// cbd.c -+// -+ -+/************************************************* -+* Name: load32_littleendian -+* -+* Description: load 4 bytes into a 32-bit integer -+* in little-endian order -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns 32-bit unsigned integer loaded from x -+**************************************************/ -+static uint32_t load32_littleendian(const uint8_t x[4]) -+{ -+ uint32_t r; -+ r = (uint32_t)x[0]; -+ r |= (uint32_t)x[1] << 8; -+ r |= (uint32_t)x[2] << 16; -+ r |= (uint32_t)x[3] << 24; -+ return r; -+} -+ -+/************************************************* -+* Name: load24_littleendian -+* -+* Description: load 3 bytes into a 32-bit integer -+* in little-endian order. -+* This function is only needed for Kyber-512 -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns 32-bit unsigned integer loaded from x (most significant byte is zero) -+**************************************************/ -+#if KYBER_ETA1 == 3 -+static uint32_t load24_littleendian(const uint8_t x[3]) -+{ -+ uint32_t r; -+ r = (uint32_t)x[0]; -+ r |= (uint32_t)x[1] << 8; -+ r |= (uint32_t)x[2] << 16; -+ return r; -+} -+#endif -+ -+ -+/************************************************* -+* Name: cbd2 -+* -+* Description: Given an array of uniformly random bytes, compute -+* polynomial with coefficients distributed according to -+* a centered binomial distribution with parameter eta=2 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *buf: pointer to input byte array -+**************************************************/ -+static void cbd2(poly *r, const uint8_t buf[2*KYBER_N/4]) -+{ -+ unsigned int i,j; -+ uint32_t t,d; -+ int16_t a,b; -+ -+ for(i=0;i>1) & 0x55555555; -+ -+ for(j=0;j<8;j++) { -+ a = (d >> (4*j+0)) & 0x3; -+ b = (d >> (4*j+2)) & 0x3; -+ r->coeffs[8*i+j] = a - b; -+ } -+ } -+} -+ -+/************************************************* -+* Name: cbd3 -+* -+* Description: Given an array of uniformly random bytes, compute -+* polynomial with coefficients distributed according to -+* a centered binomial distribution with parameter eta=3. -+* This function is only needed for Kyber-512 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *buf: pointer to input byte array -+**************************************************/ -+#if KYBER_ETA1 == 3 -+static void cbd3(poly *r, const uint8_t buf[3*KYBER_N/4]) -+{ -+ unsigned int i,j; -+ uint32_t t,d; -+ int16_t a,b; -+ -+ for(i=0;i>1) & 0x00249249; -+ d += (t>>2) & 0x00249249; -+ -+ for(j=0;j<4;j++) { -+ a = (d >> (6*j+0)) & 0x7; -+ b = (d >> (6*j+3)) & 0x7; -+ r->coeffs[4*i+j] = a - b; - } - } - } -+#endif -+ -+static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) -+{ -+#if KYBER_ETA1 == 2 -+ cbd2(r, buf); -+#elif KYBER_ETA1 == 3 -+ cbd3(r, buf); -+#else -+#error "This implementation requires eta1 in {2,3}" -+#endif -+} - --static void vector_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_ntt(&a->v[i]); -+static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) -+{ -+#if KYBER_ETA2 == 2 -+ cbd2(r, buf); -+#else -+#error "This implementation requires eta2 = 2" -+#endif -+} -+ -+// -+// ntt.c -+// -+ -+/* Code to generate zetas and zetas_inv used in the number-theoretic transform: -+ -+#define KYBER_ROOT_OF_UNITY 17 -+ -+static const uint8_t tree[128] = { -+ 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, -+ 4, 68, 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124, -+ 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90, 58, 122, -+ 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110, 30, 94, 62, 126, -+ 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41, 105, 25, 89, 57, 121, -+ 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, -+ 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, -+ 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 -+}; -+ -+void init_ntt() { -+ unsigned int i; -+ int16_t tmp[128]; -+ -+ tmp[0] = MONT; -+ for(i=1;i<128;i++) -+ tmp[i] = fqmul(tmp[i-1],MONT*KYBER_ROOT_OF_UNITY % KYBER_Q); -+ -+ for(i=0;i<128;i++) { -+ zetas[i] = tmp[tree[i]]; -+ if(zetas[i] > KYBER_Q/2) -+ zetas[i] -= KYBER_Q; -+ if(zetas[i] < -KYBER_Q/2) -+ zetas[i] += KYBER_Q; - } - } -+*/ -+ -+static const int16_t zetas[128] = { -+ -1044, -758, -359, -1517, 1493, 1422, 287, 202, -+ -171, 622, 1577, 182, 962, -1202, -1474, 1468, -+ 573, -1325, 264, 383, -829, 1458, -1602, -130, -+ -681, 1017, 732, 608, -1542, 411, -205, -1571, -+ 1223, 652, -552, 1015, -1293, 1491, -282, -1544, -+ 516, -8, -320, -666, -1618, -1162, 126, 1469, -+ -853, -90, -271, 830, 107, -1421, -247, -951, -+ -398, 961, -1508, -725, 448, -1065, 677, -1275, -+ -1103, 430, 555, 843, -1251, 871, 1550, 105, -+ 422, 587, 177, -235, -291, -460, 1574, 1653, -+ -246, 778, 1159, -147, -777, 1483, -602, 1119, -+ -1590, 644, -872, 349, 418, 329, -156, -75, -+ 817, 1097, 603, 610, 1322, -1285, -1465, 384, -+ -1215, -136, 1218, -1335, -874, 220, -1187, -1659, -+ -1185, -1530, -1278, 794, -1510, -854, -870, 478, -+ -108, -308, 996, 991, 958, -1460, 1522, 1628 -+}; -+ -+/************************************************* -+* Name: fqmul -+* -+* Description: Multiplication followed by Montgomery reduction -+* -+* Arguments: - int16_t a: first factor -+* - int16_t b: second factor -+* -+* Returns 16-bit integer congruent to a*b*R^{-1} mod q -+**************************************************/ -+static int16_t fqmul(int16_t a, int16_t b) { -+ return montgomery_reduce((int32_t)a*b); -+} - --// In place inverse number theoretic transform of a given scalar, with pairs of --// entries of s->v being interpreted as elements of GF(3329^2). Just as with the --// number theoretic transform, this leaves off the first step of the normal iFFT --// to account for the fact that 3329 does not have a 512th root of unity, using --// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. --static void scalar_inverse_ntt(scalar *s) { -- int step = DEGREE / 2; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int offset = 2; offset < DEGREE; offset <<= 1) { -- step >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- uint32_t step_root = kInverseNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = s->c[j + offset]; -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); -+/************************************************* -+* Name: ntt -+* -+* Description: Inplace number-theoretic transform (NTT) in Rq. -+* input is in standard order, output is in bitreversed order -+* -+* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq -+**************************************************/ -+static void ntt(int16_t r[256]) { -+ unsigned int len, start, j, k; -+ int16_t t, zeta; -+ -+ k = 1; -+ for(len = 128; len >= 2; len >>= 1) { -+ for(start = 0; start < 256; start = j + len) { -+ zeta = zetas[k++]; -+ for(j = start; j < start + len; j++) { -+ t = fqmul(zeta, r[j + len]); -+ r[j + len] = r[j] - t; -+ r[j] = r[j] + t; - } -- k += 2 * offset; - } - } -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = reduce(s->c[i] * kInverseDegree); -- } - } - --static void vector_inverse_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_inverse_ntt(&a->v[i]); -+/************************************************* -+* Name: invntt_tomont -+* -+* Description: Inplace inverse number-theoretic transform in Rq and -+* multiplication by Montgomery factor 2^16. -+* Input is in bitreversed order, output is in standard order -+* -+* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq -+**************************************************/ -+static void invntt(int16_t r[256]) { -+ unsigned int start, len, j, k; -+ int16_t t, zeta; -+ const int16_t f = 1441; // mont^2/128 -+ -+ k = 127; -+ for(len = 2; len <= 128; len <<= 1) { -+ for(start = 0; start < 256; start = j + len) { -+ zeta = zetas[k--]; -+ for(j = start; j < start + len; j++) { -+ t = r[j]; -+ r[j] = barrett_reduce(t + r[j + len]); -+ r[j + len] = r[j + len] - t; -+ r[j + len] = fqmul(zeta, r[j + len]); -+ } -+ } - } -+ -+ for(j = 0; j < 256; j++) -+ r[j] = fqmul(r[j], f); -+} -+ -+/************************************************* -+* Name: basemul -+* -+* Description: Multiplication of polynomials in Zq[X]/(X^2-zeta) -+* used for multiplication of elements in Rq in NTT domain -+* -+* Arguments: - int16_t r[2]: pointer to the output polynomial -+* - const int16_t a[2]: pointer to the first factor -+* - const int16_t b[2]: pointer to the second factor -+* - int16_t zeta: integer defining the reduction polynomial -+**************************************************/ -+static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta) -+{ -+ r[0] = fqmul(a[1], b[1]); -+ r[0] = fqmul(r[0], zeta); -+ r[0] += fqmul(a[0], b[0]); -+ r[1] = fqmul(a[0], b[1]); -+ r[1] += fqmul(a[1], b[0]); - } - --static void scalar_add(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); -+// -+// poly.c -+// -+ -+/************************************************* -+* Name: poly_compress -+* -+* Description: Compression and subsequent serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (of length KYBER_POLYCOMPRESSEDBYTES) -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) -+{ -+ unsigned int i,j; -+ int16_t u; -+ uint32_t d0; -+ uint8_t t[8]; -+ -+#if (KYBER_POLYCOMPRESSEDBYTES == 128) -+ for(i=0;icoeffs[8*i+j]; -+ u += (u >> 15) & KYBER_Q; -+ d0 = u << 4; -+ d0 += 1665; -+ d0 *= 80635; -+ d0 >>= 28; -+ t[j] = d0 & 0xf; -+ } -+ -+ r[0] = t[0] | (t[1] << 4); -+ r[1] = t[2] | (t[3] << 4); -+ r[2] = t[4] | (t[5] << 4); -+ r[3] = t[6] | (t[7] << 4); -+ r += 4; - } -+#elif (KYBER_POLYCOMPRESSEDBYTES == 160) -+ for(i=0;icoeffs[8*i+j]; -+ u += (u >> 15) & KYBER_Q; -+ d0 = u << 5; -+ d0 += 1664; -+ d0 *= 40318; -+ d0 >>= 27; -+ t[j] = d0 & 0x1f; -+ } -+ -+ r[0] = (t[0] >> 0) | (t[1] << 5); -+ r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7); -+ r[2] = (t[3] >> 1) | (t[4] << 4); -+ r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6); -+ r[4] = (t[6] >> 2) | (t[7] << 3); -+ r += 5; -+ } -+#else -+#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" -+#endif - } - --static void scalar_sub(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); -+/************************************************* -+* Name: poly_decompress -+* -+* Description: De-serialization and subsequent decompression of a polynomial; -+* approximate inverse of poly_compress -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *a: pointer to input byte array -+* (of length KYBER_POLYCOMPRESSEDBYTES bytes) -+**************************************************/ -+static void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]) -+{ -+ unsigned int i; -+ -+#if (KYBER_POLYCOMPRESSEDBYTES == 128) -+ for(i=0;icoeffs[2*i+0] = (((uint16_t)(a[0] & 15)*KYBER_Q) + 8) >> 4; -+ r->coeffs[2*i+1] = (((uint16_t)(a[0] >> 4)*KYBER_Q) + 8) >> 4; -+ a += 1; -+ } -+#elif (KYBER_POLYCOMPRESSEDBYTES == 160) -+ unsigned int j; -+ uint8_t t[8]; -+ for(i=0;i> 0); -+ t[1] = (a[0] >> 5) | (a[1] << 3); -+ t[2] = (a[1] >> 2); -+ t[3] = (a[1] >> 7) | (a[2] << 1); -+ t[4] = (a[2] >> 4) | (a[3] << 4); -+ t[5] = (a[3] >> 1); -+ t[6] = (a[3] >> 6) | (a[4] << 2); -+ t[7] = (a[4] >> 3); -+ a += 5; -+ -+ for(j=0;j<8;j++) -+ r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; - } -+#else -+#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" -+#endif - } - --// Multiplying two scalars in the number theoretically transformed state. Since --// 3329 does not have a 512th root of unity, this means we have to interpret --// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 --// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is --// stored in the precomputed |kModRoots| table. Note that our Barrett transform --// only allows us to multipy two reduced numbers together, so we need some --// intermediate reduction steps, even if an uint64_t could hold 3 multiplied --// numbers. --static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE / 2; i++) { -- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; -- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; -- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; -- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; -- out->c[2 * i] = -- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); -- out->c[2 * i + 1] = reduce(img_real + real_img); -+/************************************************* -+* Name: poly_tobytes -+* -+* Description: Serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYBYTES bytes) -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) -+{ -+ unsigned int i; -+ uint16_t t0, t1; -+ -+ for(i=0;icoeffs[2*i]; -+ t0 += ((int16_t)t0 >> 15) & KYBER_Q; -+ t1 = a->coeffs[2*i+1]; -+ t1 += ((int16_t)t1 >> 15) & KYBER_Q; -+ r[3*i+0] = (t0 >> 0); -+ r[3*i+1] = (t0 >> 8) | (t1 << 4); -+ r[3*i+2] = (t1 >> 4); - } - } - --static void vector_add(vector *lhs, const vector *rhs) { -- for (int i = 0; i < RANK; i++) { -- scalar_add(&lhs->v[i], &rhs->v[i]); -+/************************************************* -+* Name: poly_frombytes -+* -+* Description: De-serialization of a polynomial; -+* inverse of poly_tobytes -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *a: pointer to input byte array -+* (of KYBER_POLYBYTES bytes) -+**************************************************/ -+static void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; -+ r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; - } - } - --static void matrix_mult(vector *out, const matrix *m, const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[i][j], &a->v[j]); -- scalar_add(&out->v[i], &product); -+/************************************************* -+* Name: poly_frommsg -+* -+* Description: Convert 32-byte message to polynomial -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *msg: pointer to input message -+**************************************************/ -+static void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]) -+{ -+ unsigned int i,j; -+ int16_t mask; -+ -+#if (KYBER_INDCPA_MSGBYTES != KYBER_N/8) -+#error "KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!" -+#endif -+ -+ for(i=0;i> j)&1); -+ r->coeffs[8*i+j] = mask & ((KYBER_Q+1)/2); - } - } - } - --static void matrix_mult_transpose(vector *out, const matrix *m, -- const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[j][i], &a->v[j]); -- scalar_add(&out->v[i], &product); -+/************************************************* -+* Name: poly_tomsg -+* -+* Description: Convert polynomial to 32-byte message -+* -+* Arguments: - uint8_t *msg: pointer to output message -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) -+{ -+ unsigned int i,j; -+ uint32_t t; -+ -+ for(i=0;icoeffs[8*i+j]; -+ t <<= 1; -+ t += 1665; -+ t *= 80635; -+ t >>= 28; -+ t &= 1; -+ msg[i] |= t << j; - } - } - } - --static void scalar_inner_product(scalar *out, const vector *lhs, -- const vector *rhs) { -- scalar_zero(out); -- for (int i = 0; i < RANK; i++) { -- scalar product; -- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); -- scalar_add(out, &product); -- } -+/************************************************* -+* Name: poly_getnoise_eta1 -+* -+* Description: Sample a polynomial deterministically from a seed and a nonce, -+* with output polynomial close to centered binomial distribution -+* with parameter KYBER_ETA1 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *seed: pointer to input seed -+* (of length KYBER_SYMBYTES bytes) -+* - uint8_t nonce: one-byte input nonce -+**************************************************/ -+static void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t buf[KYBER_ETA1*KYBER_N/4]; -+ prf(buf, sizeof(buf), seed, nonce); -+ poly_cbd_eta1(r, buf); - } - --// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get --// uniformly distributed elements. This is used for matrix expansion and only --// operates on public inputs. --static void scalar_from_keccak_vartime(scalar *out, -- struct BORINGSSL_keccak_st *keccak_ctx) { -- assert(keccak_ctx->squeeze_offset == 0); -- assert(keccak_ctx->rate_bytes == 168); -- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); -- -- int done = 0; -- while (done < DEGREE) { -- uint8_t block[168]; -- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); -- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { -- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); -- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; -- if (d1 < kPrime) { -- out->c[done++] = d1; -- } -- if (d2 < kPrime && done < DEGREE) { -- out->c[done++] = d2; -- } -- } -- } -+/************************************************* -+* Name: poly_getnoise_eta2 -+* -+* Description: Sample a polynomial deterministically from a seed and a nonce, -+* with output polynomial close to centered binomial distribution -+* with parameter KYBER_ETA2 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *seed: pointer to input seed -+* (of length KYBER_SYMBYTES bytes) -+* - uint8_t nonce: one-byte input nonce -+**************************************************/ -+static void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t buf[KYBER_ETA2*KYBER_N/4]; -+ prf(buf, sizeof(buf), seed, nonce); -+ poly_cbd_eta2(r, buf); - } - --// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call --// included. Creates binominally distributed elements by sampling 2*|eta| bits, --// and setting the coefficient to the count of the first bits minus the count of --// the second bits, resulting in a centered binomial distribution. Since eta is --// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, --// and 0 with probability 3/8. --static void scalar_centered_binomial_distribution_eta_2_with_prf( -- scalar *out, const uint8_t input[33]) { -- uint8_t entropy[128]; -- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); -- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); -- -- for (int i = 0; i < DEGREE; i += 2) { -- uint8_t byte = entropy[i / 2]; -- -- uint16_t value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i] = reduce_once(value); -- -- byte >>= 4; -- value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i + 1] = reduce_once(value); -- } -+ -+/************************************************* -+* Name: poly_ntt -+* -+* Description: Computes negacyclic number-theoretic transform (NTT) of -+* a polynomial in place; -+* inputs assumed to be in normal order, output in bitreversed order -+* -+* Arguments: - uint16_t *r: pointer to in/output polynomial -+**************************************************/ -+static void poly_ntt(poly *r) -+{ -+ ntt(r->coeffs); -+ poly_reduce(r); - } - --// Generates a secret vector by using --// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed --// appending and incrementing |counter| for entry of the vector. --static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, -- const uint8_t seed[32]) { -- uint8_t input[33]; -- OPENSSL_memcpy(input, seed, 32); -- for (int i = 0; i < RANK; i++) { -- input[32] = (*counter)++; -- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); -- } -+/************************************************* -+* Name: poly_invntt_tomont -+* -+* Description: Computes inverse of negacyclic number-theoretic transform (NTT) -+* of a polynomial in place; -+* inputs assumed to be in bitreversed order, output in normal order -+* -+* Arguments: - uint16_t *a: pointer to in/output polynomial -+**************************************************/ -+static void poly_invntt_tomont(poly *r) -+{ -+ invntt(r->coeffs); - } - --// Expands the matrix of a seed for key generation and for encaps-CPA. --static void matrix_expand(matrix *out, const uint8_t rho[32]) { -- uint8_t input[34]; -- OPENSSL_memcpy(input, rho, 32); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- input[32] = i; -- input[33] = j; -- struct BORINGSSL_keccak_st keccak_ctx; -- BORINGSSL_keccak_init(&keccak_ctx, boringssl_shake128); -- BORINGSSL_keccak_absorb(&keccak_ctx, input, sizeof(input)); -- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); -- } -+/************************************************* -+* Name: poly_basemul_montgomery -+* -+* Description: Multiplication of two polynomials in NTT domain -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_basemul_montgomery(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); -+ basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); - } - } - --static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, -- 0x1f, 0x3f, 0x7f, 0xff}; -- --static void scalar_encode(uint8_t *out, const scalar *s, int bits) { -- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); -- -- uint8_t out_byte = 0; -- int out_byte_bits = 0; -- -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = s->c[i]; -- int element_bits_done = 0; -- -- while (element_bits_done < bits) { -- int chunk_bits = bits - element_bits_done; -- int out_bits_remaining = 8 - out_byte_bits; -- if (chunk_bits >= out_bits_remaining) { -- chunk_bits = out_bits_remaining; -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- *out = out_byte; -- out++; -- out_byte_bits = 0; -- out_byte = 0; -- } else { -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- out_byte_bits += chunk_bits; -+/************************************************* -+* Name: poly_tomont -+* -+* Description: Inplace conversion of all coefficients of a polynomial -+* from normal domain to Montgomery domain -+* -+* Arguments: - poly *r: pointer to input/output polynomial -+**************************************************/ -+static void poly_tomont(poly *r) -+{ -+ unsigned int i; -+ const int16_t f = (1ULL << 32) % KYBER_Q; -+ for(i=0;icoeffs[i] = montgomery_reduce((int32_t)r->coeffs[i]*f); -+} -+ -+/************************************************* -+* Name: poly_reduce -+* -+* Description: Applies Barrett reduction to all coefficients of a polynomial -+* for details of the Barrett reduction see comments in reduce.c -+* -+* Arguments: - poly *r: pointer to input/output polynomial -+**************************************************/ -+static void poly_reduce(poly *r) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = barrett_reduce(r->coeffs[i]); -+} -+ -+/************************************************* -+* Name: poly_add -+* -+* Description: Add two polynomials; no modular reduction is performed -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_add(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = a->coeffs[i] + b->coeffs[i]; -+} -+ -+/************************************************* -+* Name: poly_sub -+* -+* Description: Subtract two polynomials; no modular reduction is performed -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_sub(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = a->coeffs[i] - b->coeffs[i]; -+} -+ -+// -+// polyvec.c -+// -+ -+/************************************************* -+* Name: polyvec_compress -+* -+* Description: Compress and serialize vector of polynomials -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYVECCOMPRESSEDBYTES) -+* - const polyvec *a: pointer to input vector of polynomials -+**************************************************/ -+static void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) -+{ -+ unsigned int i,j,k; -+ uint64_t d0; -+ -+#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) -+ uint16_t t[8]; -+ for(i=0;ivec[i].coeffs[8*j+k]; -+ t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; -+ d0 = t[k]; -+ d0 <<= 11; -+ d0 += 1664; -+ d0 *= 645084; -+ d0 >>= 31; -+ t[k] = d0 & 0x7ff; - } - -- element_bits_done += chunk_bits; -- element >>= chunk_bits; -+ r[ 0] = (t[0] >> 0); -+ r[ 1] = (t[0] >> 8) | (t[1] << 3); -+ r[ 2] = (t[1] >> 5) | (t[2] << 6); -+ r[ 3] = (t[2] >> 2); -+ r[ 4] = (t[2] >> 10) | (t[3] << 1); -+ r[ 5] = (t[3] >> 7) | (t[4] << 4); -+ r[ 6] = (t[4] >> 4) | (t[5] << 7); -+ r[ 7] = (t[5] >> 1); -+ r[ 8] = (t[5] >> 9) | (t[6] << 2); -+ r[ 9] = (t[6] >> 6) | (t[7] << 5); -+ r[10] = (t[7] >> 3); -+ r += 11; - } - } -+#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) -+ uint16_t t[4]; -+ for(i=0;ivec[i].coeffs[4*j+k]; -+ t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; -+ d0 = t[k]; -+ d0 <<= 10; -+ d0 += 1665; -+ d0 *= 1290167; -+ d0 >>= 32; -+ t[k] = d0 & 0x3ff; -+ } - -- if (out_byte_bits > 0) { -- *out = out_byte; -+ r[0] = (t[0] >> 0); -+ r[1] = (t[0] >> 8) | (t[1] << 2); -+ r[2] = (t[1] >> 6) | (t[2] << 4); -+ r[3] = (t[2] >> 4) | (t[3] << 6); -+ r[4] = (t[3] >> 2); -+ r += 5; -+ } - } -+#else -+#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" -+#endif - } - --// scalar_encode_1 is |scalar_encode| specialised for |bits| == 1. --static void scalar_encode_1(uint8_t out[32], const scalar *s) { -- for (int i = 0; i < DEGREE; i += 8) { -- uint8_t out_byte = 0; -- for (int j = 0; j < 8; j++) { -- out_byte |= (s->c[i + j] & 1) << j; -+/************************************************* -+* Name: polyvec_decompress -+* -+* Description: De-serialize and decompress vector of polynomials; -+* approximate inverse of polyvec_compress -+* -+* Arguments: - polyvec *r: pointer to output vector of polynomials -+* - const uint8_t *a: pointer to input byte array -+* (of length KYBER_POLYVECCOMPRESSEDBYTES) -+**************************************************/ -+static void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]) -+{ -+ unsigned int i,j,k; -+ -+#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) -+ uint16_t t[8]; -+ for(i=0;i> 0) | ((uint16_t)a[ 1] << 8); -+ t[1] = (a[1] >> 3) | ((uint16_t)a[ 2] << 5); -+ t[2] = (a[2] >> 6) | ((uint16_t)a[ 3] << 2) | ((uint16_t)a[4] << 10); -+ t[3] = (a[4] >> 1) | ((uint16_t)a[ 5] << 7); -+ t[4] = (a[5] >> 4) | ((uint16_t)a[ 6] << 4); -+ t[5] = (a[6] >> 7) | ((uint16_t)a[ 7] << 1) | ((uint16_t)a[8] << 9); -+ t[6] = (a[8] >> 2) | ((uint16_t)a[ 9] << 6); -+ t[7] = (a[9] >> 5) | ((uint16_t)a[10] << 3); -+ a += 11; -+ -+ for(k=0;k<8;k++) -+ r->vec[i].coeffs[8*j+k] = ((uint32_t)(t[k] & 0x7FF)*KYBER_Q + 1024) >> 11; -+ } -+ } -+#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) -+ uint16_t t[4]; -+ for(i=0;i> 0) | ((uint16_t)a[1] << 8); -+ t[1] = (a[1] >> 2) | ((uint16_t)a[2] << 6); -+ t[2] = (a[2] >> 4) | ((uint16_t)a[3] << 4); -+ t[3] = (a[3] >> 6) | ((uint16_t)a[4] << 2); -+ a += 5; -+ -+ for(k=0;k<4;k++) -+ r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; - } -- *out = out_byte; -- out++; - } -+#else -+#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" -+#endif - } - --// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 --// (DEGREE) is divisible by 8, the individual vector entries will always fill a --// whole number of bytes, so we do not need to worry about bit packing here. --static void vector_encode(uint8_t *out, const vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); -- } -+/************************************************* -+* Name: polyvec_tobytes -+* -+* Description: Serialize vector of polynomials -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYVECBYTES) -+* - const polyvec *a: pointer to input vector of polynomials -+**************************************************/ -+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); - } - --// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in --// |out|. It returns one on success and zero if any parsed value is >= --// |kPrime|. --static int scalar_decode(scalar *out, const uint8_t *in, int bits) { -- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); -+/************************************************* -+* Name: polyvec_frombytes -+* -+* Description: De-serialize vector of polynomials; -+* inverse of polyvec_tobytes -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* - const polyvec *a: pointer to input vector of polynomials -+* (of length KYBER_POLYVECBYTES) -+**************************************************/ -+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]) -+{ -+ unsigned int i; -+ for(i=0;ivec[i], a+i*KYBER_POLYBYTES); -+} - -- uint8_t in_byte = 0; -- int in_byte_bits_left = 0; -+/************************************************* -+* Name: polyvec_ntt -+* -+* Description: Apply forward NTT to all elements of a vector of polynomials -+* -+* Arguments: - polyvec *r: pointer to in/output vector of polynomials -+**************************************************/ -+static void polyvec_ntt(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = 0; -- int element_bits_done = 0; -+/************************************************* -+* Name: polyvec_invntt_tomont -+* -+* Description: Apply inverse NTT to all elements of a vector of polynomials -+* and multiply by Montgomery factor 2^16 -+* -+* Arguments: - polyvec *r: pointer to in/output vector of polynomials -+**************************************************/ -+static void polyvec_invntt_tomont(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- while (element_bits_done < bits) { -- if (in_byte_bits_left == 0) { -- in_byte = *in; -- in++; -- in_byte_bits_left = 8; -- } -+/************************************************* -+* Name: polyvec_basemul_acc_montgomery -+* -+* Description: Multiply elements of a and b in NTT domain, accumulate into r, -+* and multiply by 2^-16. -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const polyvec *a: pointer to first input vector of polynomials -+* - const polyvec *b: pointer to second input vector of polynomials -+**************************************************/ -+static void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b) -+{ -+ unsigned int i; -+ poly t; -+ -+ poly_basemul_montgomery(r, &a->vec[0], &b->vec[0]); -+ for(i=1;ivec[i], &b->vec[i]); -+ poly_add(r, r, &t); -+ } - -- int chunk_bits = bits - element_bits_done; -- if (chunk_bits > in_byte_bits_left) { -- chunk_bits = in_byte_bits_left; -- } -+ poly_reduce(r); -+} - -- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; -- in_byte_bits_left -= chunk_bits; -- in_byte >>= chunk_bits; -+/************************************************* -+* Name: polyvec_reduce -+* -+* Description: Applies Barrett reduction to each coefficient -+* of each element of a vector of polynomials; -+* for details of the Barrett reduction see comments in reduce.c -+* -+* Arguments: - polyvec *r: pointer to input/output polynomial -+**************************************************/ -+static void polyvec_reduce(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- element_bits_done += chunk_bits; -- } -+/************************************************* -+* Name: polyvec_add -+* -+* Description: Add vectors of polynomials -+* -+* Arguments: - polyvec *r: pointer to output vector of polynomials -+* - const polyvec *a: pointer to first input vector of polynomials -+* - const polyvec *b: pointer to second input vector of polynomials -+**************************************************/ -+static void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b) -+{ -+ unsigned int i; -+ for(i=0;ivec[i], &a->vec[i], &b->vec[i]); -+} - -- if (element >= kPrime) { -- return 0; -- } -- out->c[i] = element; -- } -+// -+// indcpa.c -+// -+ -+/************************************************* -+* Name: pack_pk -+* -+* Description: Serialize the public key as concatenation of the -+* serialized vector of polynomials pk -+* and the public seed used to generate the matrix A. -+* -+* Arguments: uint8_t *r: pointer to the output serialized public key -+* polyvec *pk: pointer to the input public-key polyvec -+* const uint8_t *seed: pointer to the input public seed -+**************************************************/ -+static void pack_pk(uint8_t r[KYBER_INDCPA_PUBLICKEYBYTES], -+ polyvec *pk, -+ const uint8_t seed[KYBER_SYMBYTES]) -+{ -+ size_t i; -+ polyvec_tobytes(r, pk); -+ for(i=0;ic[i + j] = in_byte & 1; -- in_byte >>= 1; -- } -+/************************************************* -+* Name: pack_sk -+* -+* Description: Serialize the secret key -+* -+* Arguments: - uint8_t *r: pointer to output serialized secret key -+* - polyvec *sk: pointer to input vector of polynomials (secret key) -+**************************************************/ -+static void pack_sk(uint8_t r[KYBER_INDCPA_SECRETKEYBYTES], polyvec *sk) -+{ -+ polyvec_tobytes(r, sk); -+} -+ -+/************************************************* -+* Name: unpack_sk -+* -+* Description: De-serialize the secret key; inverse of pack_sk -+* -+* Arguments: - polyvec *sk: pointer to output vector of polynomials (secret key) -+* - const uint8_t *packedsk: pointer to input serialized secret key -+**************************************************/ -+static void unpack_sk(polyvec *sk, const uint8_t packedsk[KYBER_INDCPA_SECRETKEYBYTES]) -+{ -+ polyvec_frombytes(sk, packedsk); -+} -+ -+/************************************************* -+* Name: pack_ciphertext -+* -+* Description: Serialize the ciphertext as concatenation of the -+* compressed and serialized vector of polynomials b -+* and the compressed and serialized polynomial v -+* -+* Arguments: uint8_t *r: pointer to the output serialized ciphertext -+* poly *pk: pointer to the input vector of polynomials b -+* poly *v: pointer to the input polynomial v -+**************************************************/ -+static void pack_ciphertext(uint8_t r[KYBER_INDCPA_BYTES], polyvec *b, poly *v) -+{ -+ polyvec_compress(r, b); -+ poly_compress(r+KYBER_POLYVECCOMPRESSEDBYTES, v); -+} -+ -+/************************************************* -+* Name: unpack_ciphertext -+* -+* Description: De-serialize and decompress ciphertext from a byte array; -+* approximate inverse of pack_ciphertext -+* -+* Arguments: - polyvec *b: pointer to the output vector of polynomials b -+* - poly *v: pointer to the output polynomial v -+* - const uint8_t *c: pointer to the input serialized ciphertext -+**************************************************/ -+static void unpack_ciphertext(polyvec *b, poly *v, const uint8_t c[KYBER_INDCPA_BYTES]) -+{ -+ polyvec_decompress(b, c); -+ poly_decompress(v, c+KYBER_POLYVECCOMPRESSEDBYTES); -+} -+ -+/************************************************* -+* Name: rej_uniform -+* -+* Description: Run rejection sampling on uniform random bytes to generate -+* uniform random integers mod q -+* -+* Arguments: - int16_t *r: pointer to output buffer -+* - unsigned int len: requested number of 16-bit integers (uniform mod q) -+* - const uint8_t *buf: pointer to input buffer (assumed to be uniformly random bytes) -+* - unsigned int buflen: length of input buffer in bytes -+* -+* Returns number of sampled 16-bit integers (at most len) -+**************************************************/ -+static unsigned int rej_uniform(int16_t *r, -+ unsigned int len, -+ const uint8_t *buf, -+ unsigned int buflen) -+{ -+ unsigned int ctr, pos; -+ uint16_t val0, val1; -+ -+ ctr = pos = 0; -+ while(ctr < len && pos + 3 <= buflen) { -+ val0 = ((buf[pos+0] >> 0) | ((uint16_t)buf[pos+1] << 8)) & 0xFFF; -+ val1 = ((buf[pos+1] >> 4) | ((uint16_t)buf[pos+2] << 4)) & 0xFFF; -+ pos += 3; -+ -+ if(val0 < KYBER_Q) -+ r[ctr++] = val0; -+ if(ctr < len && val1 < KYBER_Q) -+ r[ctr++] = val1; - } -+ -+ return ctr; - } - --// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on --// success or zero if any parsed value is >= |kPrime|. --static int vector_decode(vector *out, const uint8_t *in, int bits) { -- for (int i = 0; i < RANK; i++) { -- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { -- return 0; -+#define gen_a(A,B) gen_matrix(A,B,0) -+#define gen_at(A,B) gen_matrix(A,B,1) -+ -+/************************************************* -+* Name: gen_matrix -+* -+* Description: Deterministically generate matrix A (or the transpose of A) -+* from a seed. Entries of the matrix are polynomials that look -+* uniformly random. Performs rejection sampling on output of -+* a XOF -+* -+* Arguments: - polyvec *a: pointer to ouptput matrix A -+* - const uint8_t *seed: pointer to input seed -+* - int transposed: boolean deciding whether A or A^T is generated -+**************************************************/ -+#define GEN_MATRIX_NBLOCKS ((12*KYBER_N/8*(1 << 12)/KYBER_Q + XOF_BLOCKBYTES)/XOF_BLOCKBYTES) -+// Not static for benchmarking -+static void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed) -+{ -+ unsigned int ctr, i, j, k; -+ unsigned int buflen, off; -+ uint8_t buf[GEN_MATRIX_NBLOCKS*XOF_BLOCKBYTES+2]; -+ xof_state state; -+ -+ for(i=0;i> kBarrettShift); -- uint32_t remainder = shifted - quotient * kPrime; -- -- // Adjust the quotient to round correctly: -- // 0 <= remainder <= kHalfPrime round to 0 -- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 -- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 -- assert(remainder < 2u * kPrime); -- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); -- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); -- return quotient & ((1 << bits) - 1); --} -- --// Decompresses |x| by using an equi-distant representative. The formula is --// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to --// implement this logic using only bit operations. --static uint16_t decompress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x * kPrime; -- uint32_t power = 1 << bits; -- // This is |product| % power, since |power| is a power of 2. -- uint32_t remainder = product & (power - 1); -- // This is |product| / power, since |power| is a power of 2. -- uint32_t lower = product >> bits; -- // The rounding logic works since the first half of numbers mod |power| have a -- // 0 as first bit, and the second half has a 1 as first bit, since |power| is -- // a power of 2. As a 12 bit number, |remainder| is always positive, so we -- // will shift in 0s for a right shift. -- return lower + (remainder >> (bits - 1)); --} -- --static void scalar_compress(scalar *s, int bits) { -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = compress(s->c[i], bits); -+/************************************************* -+* Name: indcpa_keypair -+* -+* Description: Generates public and private key for the CPA-secure -+* public-key encryption scheme underlying Kyber -+* -+* Arguments: - uint8_t *pk: pointer to output public key -+* (of length KYBER_INDCPA_PUBLICKEYBYTES bytes) -+* - uint8_t *sk: pointer to output private key -+ (of length KYBER_INDCPA_SECRETKEYBYTES bytes) -+**************************************************/ -+static void indcpa_keypair(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], -+ const uint8_t seed[KYBER_SYMBYTES]) -+{ -+ unsigned int i; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ const uint8_t *publicseed = buf; -+ const uint8_t *noiseseed = buf+KYBER_SYMBYTES; -+ uint8_t nonce = 0; -+ polyvec a[KYBER_K], e, pkpv, skpv; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ hash_g(buf, buf, KYBER_SYMBYTES); -+ -+ gen_a(a, publicseed); -+ -+ for(i=0;ic[i] = decompress(s->c[i], bits); -- } -+/************************************************* -+* Name: indcpa_enc -+* -+* Description: Encryption function of the CPA-secure -+* public-key encryption scheme underlying Kyber. -+* -+* Arguments: - uint8_t *c: pointer to output ciphertext -+* (of length KYBER_INDCPA_BYTES bytes) -+* - const uint8_t *m: pointer to input message -+* (of length KYBER_INDCPA_MSGBYTES bytes) -+* - const uint8_t *pk: pointer to input public key -+* (of length KYBER_INDCPA_PUBLICKEYBYTES) -+* - const uint8_t *coins: pointer to input random coins used as seed -+* (of length KYBER_SYMBYTES) to deterministically -+* generate all randomness -+**************************************************/ -+static int indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ const uint8_t coins[KYBER_SYMBYTES]) -+{ -+ unsigned int i; -+ uint8_t seed[KYBER_SYMBYTES]; -+ uint8_t nonce = 0; -+ polyvec sp, pkpv, ep, at[KYBER_K], b; -+ poly v, k, epp; -+ -+ if (!unpack_pk(&pkpv, seed, pk)) -+ return 0; -+ -+ poly_frommsg(&k, m); -+ gen_at(at, seed); -+ -+ for(i=0;iv[i], bits); -- } -+/************************************************* -+* Name: indcpa_dec -+* -+* Description: Decryption function of the CPA-secure -+* public-key encryption scheme underlying Kyber. -+* -+* Arguments: - uint8_t *m: pointer to output decrypted message -+* (of length KYBER_INDCPA_MSGBYTES) -+* - const uint8_t *c: pointer to input ciphertext -+* (of length KYBER_INDCPA_BYTES) -+* - const uint8_t *sk: pointer to input secret key -+* (of length KYBER_INDCPA_SECRETKEYBYTES) -+**************************************************/ -+static void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]) -+{ -+ polyvec b, skpv; -+ poly v, mp; -+ -+ unpack_ciphertext(&b, &v, c); -+ unpack_sk(&skpv, sk); -+ -+ polyvec_ntt(&b); -+ polyvec_basemul_acc_montgomery(&mp, &skpv, &b); -+ poly_invntt_tomont(&mp); -+ -+ poly_sub(&mp, &v, &mp); -+ poly_reduce(&mp); -+ -+ poly_tomsg(m, &mp); - } - --static void vector_decompress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_decompress(&a->v[i], bits); -- } -+// -+// fips202.c -+// -+ -+/* Based on the public domain implementation in crypto_hash/keccakc512/simple/ from -+ * http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public domain "TweetFips202" -+ * implementation from https://twitter.com/tweetfips202 by Gilles Van Assche, Daniel J. Bernstein, -+ * and Peter Schwabe */ -+ -+#define NROUNDS 24 -+#define ROL(a, offset) ((a << offset) ^ (a >> (64-offset))) -+ -+/************************************************* -+* Name: load64 -+* -+* Description: Load 8 bytes into uint64_t in little-endian order -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns the loaded 64-bit unsigned integer -+**************************************************/ -+static uint64_t load64(const uint8_t x[8]) { -+ unsigned int i; -+ uint64_t r = 0; -+ -+ for(i=0;i<8;i++) -+ r |= (uint64_t)x[i] << 8*i; -+ -+ return r; - } - --struct public_key { -- vector t; -- uint8_t rho[32]; -- uint8_t public_key_hash[32]; -- matrix m; -+/************************************************* -+* Name: store64 -+* -+* Description: Store a 64-bit integer to array of 8 bytes in little-endian order -+* -+* Arguments: - uint8_t *x: pointer to the output byte array (allocated) -+* - uint64_t u: input 64-bit unsigned integer -+**************************************************/ -+static void store64(uint8_t x[8], uint64_t u) { -+ unsigned int i; -+ -+ for(i=0;i<8;i++) -+ x[i] = u >> 8*i; -+} -+ -+/* Keccak round constants */ -+static const uint64_t KeccakF_RoundConstants[NROUNDS] = { -+ (uint64_t)0x0000000000000001ULL, -+ (uint64_t)0x0000000000008082ULL, -+ (uint64_t)0x800000000000808aULL, -+ (uint64_t)0x8000000080008000ULL, -+ (uint64_t)0x000000000000808bULL, -+ (uint64_t)0x0000000080000001ULL, -+ (uint64_t)0x8000000080008081ULL, -+ (uint64_t)0x8000000000008009ULL, -+ (uint64_t)0x000000000000008aULL, -+ (uint64_t)0x0000000000000088ULL, -+ (uint64_t)0x0000000080008009ULL, -+ (uint64_t)0x000000008000000aULL, -+ (uint64_t)0x000000008000808bULL, -+ (uint64_t)0x800000000000008bULL, -+ (uint64_t)0x8000000000008089ULL, -+ (uint64_t)0x8000000000008003ULL, -+ (uint64_t)0x8000000000008002ULL, -+ (uint64_t)0x8000000000000080ULL, -+ (uint64_t)0x000000000000800aULL, -+ (uint64_t)0x800000008000000aULL, -+ (uint64_t)0x8000000080008081ULL, -+ (uint64_t)0x8000000000008080ULL, -+ (uint64_t)0x0000000080000001ULL, -+ (uint64_t)0x8000000080008008ULL - }; - --static struct public_key *public_key_from_external( -- const struct KYBER_public_key *external) { -- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), -- "Kyber public key is too small"); -- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), -- "Kyber public key align incorrect"); -- return (struct public_key *)external; -+/************************************************* -+* Name: KeccakF1600_StatePermute -+* -+* Description: The Keccak F1600 Permutation -+* -+* Arguments: - uint64_t *state: pointer to input/output Keccak state -+**************************************************/ -+static void KeccakF1600_StatePermute(uint64_t state[25]) -+{ -+ int round; -+ -+ uint64_t Aba, Abe, Abi, Abo, Abu; -+ uint64_t Aga, Age, Agi, Ago, Agu; -+ uint64_t Aka, Ake, Aki, Ako, Aku; -+ uint64_t Ama, Ame, Ami, Amo, Amu; -+ uint64_t Asa, Ase, Asi, Aso, Asu; -+ uint64_t BCa, BCe, BCi, BCo, BCu; -+ uint64_t Da, De, Di, Do, Du; -+ uint64_t Eba, Ebe, Ebi, Ebo, Ebu; -+ uint64_t Ega, Ege, Egi, Ego, Egu; -+ uint64_t Eka, Eke, Eki, Eko, Eku; -+ uint64_t Ema, Eme, Emi, Emo, Emu; -+ uint64_t Esa, Ese, Esi, Eso, Esu; -+ -+ //copyFromState(A, state) -+ Aba = state[ 0]; -+ Abe = state[ 1]; -+ Abi = state[ 2]; -+ Abo = state[ 3]; -+ Abu = state[ 4]; -+ Aga = state[ 5]; -+ Age = state[ 6]; -+ Agi = state[ 7]; -+ Ago = state[ 8]; -+ Agu = state[ 9]; -+ Aka = state[10]; -+ Ake = state[11]; -+ Aki = state[12]; -+ Ako = state[13]; -+ Aku = state[14]; -+ Ama = state[15]; -+ Ame = state[16]; -+ Ami = state[17]; -+ Amo = state[18]; -+ Amu = state[19]; -+ Asa = state[20]; -+ Ase = state[21]; -+ Asi = state[22]; -+ Aso = state[23]; -+ Asu = state[24]; -+ -+ for(round = 0; round < NROUNDS; round += 2) { -+ // prepareTheta -+ BCa = Aba^Aga^Aka^Ama^Asa; -+ BCe = Abe^Age^Ake^Ame^Ase; -+ BCi = Abi^Agi^Aki^Ami^Asi; -+ BCo = Abo^Ago^Ako^Amo^Aso; -+ BCu = Abu^Agu^Aku^Amu^Asu; -+ -+ //thetaRhoPiChiIotaPrepareTheta(round, A, E) -+ Da = BCu^ROL(BCe, 1); -+ De = BCa^ROL(BCi, 1); -+ Di = BCe^ROL(BCo, 1); -+ Do = BCi^ROL(BCu, 1); -+ Du = BCo^ROL(BCa, 1); -+ -+ Aba ^= Da; -+ BCa = Aba; -+ Age ^= De; -+ BCe = ROL(Age, 44); -+ Aki ^= Di; -+ BCi = ROL(Aki, 43); -+ Amo ^= Do; -+ BCo = ROL(Amo, 21); -+ Asu ^= Du; -+ BCu = ROL(Asu, 14); -+ Eba = BCa ^((~BCe)& BCi ); -+ Eba ^= (uint64_t)KeccakF_RoundConstants[round]; -+ Ebe = BCe ^((~BCi)& BCo ); -+ Ebi = BCi ^((~BCo)& BCu ); -+ Ebo = BCo ^((~BCu)& BCa ); -+ Ebu = BCu ^((~BCa)& BCe ); -+ -+ Abo ^= Do; -+ BCa = ROL(Abo, 28); -+ Agu ^= Du; -+ BCe = ROL(Agu, 20); -+ Aka ^= Da; -+ BCi = ROL(Aka, 3); -+ Ame ^= De; -+ BCo = ROL(Ame, 45); -+ Asi ^= Di; -+ BCu = ROL(Asi, 61); -+ Ega = BCa ^((~BCe)& BCi ); -+ Ege = BCe ^((~BCi)& BCo ); -+ Egi = BCi ^((~BCo)& BCu ); -+ Ego = BCo ^((~BCu)& BCa ); -+ Egu = BCu ^((~BCa)& BCe ); -+ -+ Abe ^= De; -+ BCa = ROL(Abe, 1); -+ Agi ^= Di; -+ BCe = ROL(Agi, 6); -+ Ako ^= Do; -+ BCi = ROL(Ako, 25); -+ Amu ^= Du; -+ BCo = ROL(Amu, 8); -+ Asa ^= Da; -+ BCu = ROL(Asa, 18); -+ Eka = BCa ^((~BCe)& BCi ); -+ Eke = BCe ^((~BCi)& BCo ); -+ Eki = BCi ^((~BCo)& BCu ); -+ Eko = BCo ^((~BCu)& BCa ); -+ Eku = BCu ^((~BCa)& BCe ); -+ -+ Abu ^= Du; -+ BCa = ROL(Abu, 27); -+ Aga ^= Da; -+ BCe = ROL(Aga, 36); -+ Ake ^= De; -+ BCi = ROL(Ake, 10); -+ Ami ^= Di; -+ BCo = ROL(Ami, 15); -+ Aso ^= Do; -+ BCu = ROL(Aso, 56); -+ Ema = BCa ^((~BCe)& BCi ); -+ Eme = BCe ^((~BCi)& BCo ); -+ Emi = BCi ^((~BCo)& BCu ); -+ Emo = BCo ^((~BCu)& BCa ); -+ Emu = BCu ^((~BCa)& BCe ); -+ -+ Abi ^= Di; -+ BCa = ROL(Abi, 62); -+ Ago ^= Do; -+ BCe = ROL(Ago, 55); -+ Aku ^= Du; -+ BCi = ROL(Aku, 39); -+ Ama ^= Da; -+ BCo = ROL(Ama, 41); -+ Ase ^= De; -+ BCu = ROL(Ase, 2); -+ Esa = BCa ^((~BCe)& BCi ); -+ Ese = BCe ^((~BCi)& BCo ); -+ Esi = BCi ^((~BCo)& BCu ); -+ Eso = BCo ^((~BCu)& BCa ); -+ Esu = BCu ^((~BCa)& BCe ); -+ -+ // prepareTheta -+ BCa = Eba^Ega^Eka^Ema^Esa; -+ BCe = Ebe^Ege^Eke^Eme^Ese; -+ BCi = Ebi^Egi^Eki^Emi^Esi; -+ BCo = Ebo^Ego^Eko^Emo^Eso; -+ BCu = Ebu^Egu^Eku^Emu^Esu; -+ -+ //thetaRhoPiChiIotaPrepareTheta(round+1, E, A) -+ Da = BCu^ROL(BCe, 1); -+ De = BCa^ROL(BCi, 1); -+ Di = BCe^ROL(BCo, 1); -+ Do = BCi^ROL(BCu, 1); -+ Du = BCo^ROL(BCa, 1); -+ -+ Eba ^= Da; -+ BCa = Eba; -+ Ege ^= De; -+ BCe = ROL(Ege, 44); -+ Eki ^= Di; -+ BCi = ROL(Eki, 43); -+ Emo ^= Do; -+ BCo = ROL(Emo, 21); -+ Esu ^= Du; -+ BCu = ROL(Esu, 14); -+ Aba = BCa ^((~BCe)& BCi ); -+ Aba ^= (uint64_t)KeccakF_RoundConstants[round+1]; -+ Abe = BCe ^((~BCi)& BCo ); -+ Abi = BCi ^((~BCo)& BCu ); -+ Abo = BCo ^((~BCu)& BCa ); -+ Abu = BCu ^((~BCa)& BCe ); -+ -+ Ebo ^= Do; -+ BCa = ROL(Ebo, 28); -+ Egu ^= Du; -+ BCe = ROL(Egu, 20); -+ Eka ^= Da; -+ BCi = ROL(Eka, 3); -+ Eme ^= De; -+ BCo = ROL(Eme, 45); -+ Esi ^= Di; -+ BCu = ROL(Esi, 61); -+ Aga = BCa ^((~BCe)& BCi ); -+ Age = BCe ^((~BCi)& BCo ); -+ Agi = BCi ^((~BCo)& BCu ); -+ Ago = BCo ^((~BCu)& BCa ); -+ Agu = BCu ^((~BCa)& BCe ); -+ -+ Ebe ^= De; -+ BCa = ROL(Ebe, 1); -+ Egi ^= Di; -+ BCe = ROL(Egi, 6); -+ Eko ^= Do; -+ BCi = ROL(Eko, 25); -+ Emu ^= Du; -+ BCo = ROL(Emu, 8); -+ Esa ^= Da; -+ BCu = ROL(Esa, 18); -+ Aka = BCa ^((~BCe)& BCi ); -+ Ake = BCe ^((~BCi)& BCo ); -+ Aki = BCi ^((~BCo)& BCu ); -+ Ako = BCo ^((~BCu)& BCa ); -+ Aku = BCu ^((~BCa)& BCe ); -+ -+ Ebu ^= Du; -+ BCa = ROL(Ebu, 27); -+ Ega ^= Da; -+ BCe = ROL(Ega, 36); -+ Eke ^= De; -+ BCi = ROL(Eke, 10); -+ Emi ^= Di; -+ BCo = ROL(Emi, 15); -+ Eso ^= Do; -+ BCu = ROL(Eso, 56); -+ Ama = BCa ^((~BCe)& BCi ); -+ Ame = BCe ^((~BCi)& BCo ); -+ Ami = BCi ^((~BCo)& BCu ); -+ Amo = BCo ^((~BCu)& BCa ); -+ Amu = BCu ^((~BCa)& BCe ); -+ -+ Ebi ^= Di; -+ BCa = ROL(Ebi, 62); -+ Ego ^= Do; -+ BCe = ROL(Ego, 55); -+ Eku ^= Du; -+ BCi = ROL(Eku, 39); -+ Ema ^= Da; -+ BCo = ROL(Ema, 41); -+ Ese ^= De; -+ BCu = ROL(Ese, 2); -+ Asa = BCa ^((~BCe)& BCi ); -+ Ase = BCe ^((~BCi)& BCo ); -+ Asi = BCi ^((~BCo)& BCu ); -+ Aso = BCo ^((~BCu)& BCa ); -+ Asu = BCu ^((~BCa)& BCe ); -+ } -+ -+ //copyToState(state, A) -+ state[ 0] = Aba; -+ state[ 1] = Abe; -+ state[ 2] = Abi; -+ state[ 3] = Abo; -+ state[ 4] = Abu; -+ state[ 5] = Aga; -+ state[ 6] = Age; -+ state[ 7] = Agi; -+ state[ 8] = Ago; -+ state[ 9] = Agu; -+ state[10] = Aka; -+ state[11] = Ake; -+ state[12] = Aki; -+ state[13] = Ako; -+ state[14] = Aku; -+ state[15] = Ama; -+ state[16] = Ame; -+ state[17] = Ami; -+ state[18] = Amo; -+ state[19] = Amu; -+ state[20] = Asa; -+ state[21] = Ase; -+ state[22] = Asi; -+ state[23] = Aso; -+ state[24] = Asu; - } - --struct private_key { -- struct public_key pub; -- vector s; -- uint8_t fo_failure_secret[32]; --}; - --static struct private_key *private_key_from_external( -- const struct KYBER_private_key *external) { -- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), -- "Kyber private key too small"); -- static_assert( -- alignof(struct KYBER_private_key) >= alignof(struct private_key), -- "Kyber private key align incorrect"); -- return (struct private_key *)external; --} -- --// Calls |KYBER_generate_key_external_entropy| with random bytes from --// |RAND_bytes|. --void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key) { -- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; -- RAND_bytes(entropy, sizeof(entropy)); -- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, -- entropy); --} -- --static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { -- uint8_t *vector_output; -- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { -- return 0; -+/************************************************* -+* Name: keccak_squeeze -+* -+* Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes. -+* Modifies the state. Can be called multiple times to keep -+* squeezing, i.e., is incremental. -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of bytes to be squeezed (written to out) -+* - uint64_t *s: pointer to input/output Keccak state -+* - unsigned int pos: number of bytes in current block already squeezed -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* -+* Returns new position pos in current block -+**************************************************/ -+static unsigned int keccak_squeeze(uint8_t *out, -+ size_t outlen, -+ uint64_t s[25], -+ unsigned int pos, -+ unsigned int r) -+{ -+ unsigned int i; -+ -+ while(outlen) { -+ if(pos == r) { -+ KeccakF1600_StatePermute(s); -+ pos = 0; -+ } -+ for(i=pos;i < r && i < pos+outlen; i++) -+ *out++ = s[i/8] >> 8*(i%8); -+ outlen -= i-pos; -+ pos = i; - } -- vector_encode(vector_output, &pub->t, kLog2Prime); -- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { -- return 0; -+ -+ return pos; -+} -+ -+/************************************************* -+* Name: keccak_absorb -+* -+* Description: Absorb step of Keccak; incremental. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+* - unsigned int pos: position in current block to be absorbed -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+* -+* Returns new position pos in current block -+**************************************************/ -+static unsigned int keccak_absorb(uint64_t s[25], -+ unsigned int pos, -+ unsigned int r, -+ const uint8_t *in, -+ size_t inlen) -+{ -+ unsigned int i; -+ -+ while(pos+inlen >= r) { -+ for(i=pos;ipub.rho, hashed, sizeof(priv->pub.rho)); -- matrix_expand(&priv->pub.m, rho); -- uint8_t counter = 0; -- vector_generate_secret_eta_2(&priv->s, &counter, sigma); -- vector_ntt(&priv->s); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, sigma); -- vector_ntt(&error); -- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); -- vector_add(&priv->pub.t, &error); -- -- CBB cbb; -- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); -- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { -- abort(); -+ -+/************************************************* -+* Name: keccak_absorb_once -+* -+* Description: Absorb step of Keccak; -+* non-incremental, starts by zeroeing the state. -+* -+* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+* - uint8_t p: domain-separation byte for different Keccak-derived functions -+**************************************************/ -+static void keccak_absorb_once(uint64_t s[25], -+ unsigned int r, -+ const uint8_t *in, -+ size_t inlen, -+ uint8_t p) -+{ -+ unsigned int i; -+ -+ for(i=0;i<25;i++) -+ s[i] = 0; -+ -+ while(inlen >= r) { -+ for(i=0;ipub.public_key_hash, sizeof(priv->pub.public_key_hash), -- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, -- boringssl_sha3_256); -- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); --} -- --void KYBER_public_from_private(struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key) { -- struct public_key *const pub = public_key_from_external(out_public_key); -- const struct private_key *const priv = private_key_from_external(private_key); -- *pub = priv->pub; --} -- --// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to --// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this --// would not result in a CCA secure scheme, since lattice schemes are vulnerable --// to decryption failure oracles. --static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], -- const struct public_key *pub, const uint8_t message[32], -- const uint8_t randomness[32]) { -- uint8_t counter = 0; -- vector secret; -- vector_generate_secret_eta_2(&secret, &counter, randomness); -- vector_ntt(&secret); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, randomness); -- uint8_t input[33]; -- OPENSSL_memcpy(input, randomness, 32); -- input[32] = counter; -- scalar scalar_error; -- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); -- vector u; -- matrix_mult(&u, &pub->m, &secret); -- vector_inverse_ntt(&u); -- vector_add(&u, &error); -- scalar v; -- scalar_inner_product(&v, &pub->t, &secret); -- scalar_inverse_ntt(&v); -- scalar_add(&v, &scalar_error); -- scalar expanded_message; -- scalar_decode_1(&expanded_message, message); -- scalar_decompress(&expanded_message, 1); -- scalar_add(&v, &expanded_message); -- vector_compress(&u, kDU); -- vector_encode(out, &u, kDU); -- scalar_compress(&v, kDV); -- scalar_encode(out + kCompressedVectorSize, &v, kDV); --} -- --// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| --void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key) { -- uint8_t entropy[KYBER_ENCAP_ENTROPY]; -- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); -- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, -- out_shared_secret_len, public_key, entropy); --} -- --// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there --// hashes the output of the system's random number generator, since the FO --// transform will reveal it to the decrypting party. There is no reason to do --// this when a secure random number generator is used. When an insecure random --// number generator is used, the caller should switch to a secure one before --// calling this method. --void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { -- const struct public_key *pub = public_key_from_external(public_key); -- uint8_t input[64]; -- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); -- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, -- sizeof(input) - KYBER_ENCAP_ENTROPY); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, -- sizeof(input), boringssl_sha3_512); -- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); -- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, -- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); -- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, -- prekey_and_randomness, sizeof(prekey_and_randomness), -- boringssl_shake256); --} -- --// Algorithm 6 of the Kyber spec. --static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { -- vector u; -- vector_decode(&u, ciphertext, kDU); -- vector_decompress(&u, kDU); -- vector_ntt(&u); -- scalar v; -- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); -- scalar_decompress(&v, kDV); -- scalar mask; -- scalar_inner_product(&mask, &priv->s, &u); -- scalar_inverse_ntt(&mask); -- scalar_sub(&v, &mask); -- scalar_compress(&v, 1); -- scalar_encode_1(out, &v); --} -- --// Algorithm 9 of the Kyber spec, performing the FO transform by running --// encrypt_cpa on the decrypted message. The spec does not allow the decryption --// failure to be passed on to the caller, and instead returns a result that is --// deterministic but unpredictable to anyone without knowledge of the private --// key. --void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key) { -- const struct private_key *priv = private_key_from_external(private_key); -- uint8_t decrypted[64]; -- decrypt_cpa(decrypted, priv, ciphertext); -- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, -- sizeof(decrypted) - 32); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), -- decrypted, sizeof(decrypted), boringssl_sha3_512); -- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, -- prekey_and_randomness + 32); -- uint8_t mask = -- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, -- sizeof(expected_ciphertext)), -- 0); -- uint8_t input[64]; -- for (int i = 0; i < 32; i++) { -- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], -- priv->fo_failure_secret[i]); -+ for(i=0;is, SHAKE128_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE128_RATE; - } - --// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate --// the value of |pub->public_key_hash|. --static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { -- CBS t_bytes; -- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || -- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || -- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { -- return 0; -- } -- matrix_expand(&pub->m, pub->rho); -- return 1; -+/************************************************* -+* Name: shake128_squeezeblocks -+* -+* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of -+* SHAKE128_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes new block has not yet been -+* started (state->pos = SHAKE128_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); -+} -+ -+/************************************************* -+* Name: shake256_squeeze -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many -+* bytes. Can be called multiple times to keep squeezing. -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t outlen : number of bytes to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) -+{ -+ state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb_once -+* -+* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: shake256_squeezeblocks -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of -+* SHAKE256_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes next block has not yet been -+* started (state->pos = SHAKE256_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb -+* -+* Description: Absorb step of the SHAKE256 XOF; incremental. -+* -+* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); -+} -+ -+/************************************************* -+* Name: shake256_finalize -+* -+* Description: Finalize absorb step of the SHAKE256 XOF. -+* -+* Arguments: - keccak_state *state: pointer to Keccak state -+**************************************************/ -+static void shake256_finalize(keccak_state *state) -+{ -+ keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: keccak_init -+* -+* Description: Initializes the Keccak state. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+**************************************************/ -+static void keccak_init(uint64_t s[25]) -+{ -+ unsigned int i; -+ for(i=0;i<25;i++) -+ s[i] = 0; -+} -+ -+/************************************************* -+* Name: shake256_init -+* -+* Description: Initilizes Keccak state for use as SHAKE256 XOF -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state -+**************************************************/ -+static void shake256_init(keccak_state *state) -+{ -+ keccak_init(state->s); -+ state->pos = 0; -+} -+ -+ -+/************************************************* -+* Name: shake256 -+* -+* Description: SHAKE256 XOF with non-incremental API -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: requested output length in bytes -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) -+{ -+ size_t nblocks; -+ keccak_state state; -+ -+ shake256_absorb_once(&state, in, inlen); -+ nblocks = outlen/SHAKE256_RATE; -+ shake256_squeezeblocks(out, nblocks, &state); -+ outlen -= nblocks*SHAKE256_RATE; -+ out += nblocks*SHAKE256_RATE; -+ shake256_squeeze(out, outlen, &state); -+} -+ -+/************************************************* -+* Name: sha3_256 -+* -+* Description: SHA3-256 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (32 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<4;i++) -+ store64(h+8*i,s[i]); -+} -+ -+/************************************************* -+* Name: sha3_512 -+* -+* Description: SHA3-512 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (64 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<8;i++) -+ store64(h+8*i,s[i]); -+} -+ -+// -+// symmetric-shake.c -+// -+ -+/************************************************* -+* Name: kyber_shake128_absorb -+* -+* Description: Absorb step of the SHAKE128 specialized for the Kyber context. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *seed: pointer to KYBER_SYMBYTES input to be absorbed into state -+* - uint8_t i: additional byte of input -+* - uint8_t j: additional byte of input -+**************************************************/ -+static void kyber_shake128_absorb(keccak_state *state, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y) -+{ -+ uint8_t extseed[KYBER_SYMBYTES+2]; -+ -+ memcpy(extseed, seed, KYBER_SYMBYTES); -+ extseed[KYBER_SYMBYTES+0] = x; -+ extseed[KYBER_SYMBYTES+1] = y; -+ -+ shake128_absorb_once(state, extseed, sizeof(extseed)); - } - --int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { -- struct public_key *pub = public_key_from_external(public_key); -- CBS orig_in = *in; -- if (!kyber_parse_public_key_no_hash(pub, in) || // -- CBS_len(in) != 0) { -+/************************************************* -+* Name: kyber_shake256_prf -+* -+* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input -+* and then generates outlen bytes of SHAKE256 output -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of requested output bytes -+* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) -+* - uint8_t nonce: single-byte nonce (public PRF input) -+**************************************************/ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t extkey[KYBER_SYMBYTES+1]; -+ -+ memcpy(extkey, key, KYBER_SYMBYTES); -+ extkey[KYBER_SYMBYTES] = nonce; -+ -+ shake256(out, outlen, extkey, sizeof(extkey)); -+} -+ -+// -+// kem.c -+// -+ -+// Modified crypto_kem_keypair to BoringSSL style API -+void generate_key(struct public_key *out_pub, struct private_key *out_priv, -+ const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) -+{ -+ size_t i; -+ uint8_t* pk = &out_pub->opaque[0]; -+ uint8_t* sk = &out_priv->opaque[0]; -+ -+ indcpa_keypair(pk, sk, seed); -+ for(i=0;iopaque[0]; -+ uint8_t *ct = out_ciphertext; -+ -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ -+ /* Don't release system RNG output */ -+ hash_h(buf, buf, KYBER_SYMBYTES); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); -+ hash_g(kr, buf, 2*KYBER_SYMBYTES); -+ -+ /* coins are in kr+KYBER_SYMBYTES */ -+ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) - return 0; -+ -+ if (mlkem == 1) { -+ memcpy(ss, kr, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } -- BORINGSSL_keccak(pub->public_key_hash, sizeof(pub->public_key_hash), -- CBS_data(&orig_in), CBS_len(&orig_in), boringssl_sha3_256); - return 1; - } - --int KYBER_marshal_private_key(CBB *out, -- const struct KYBER_private_key *private_key) { -- const struct private_key *const priv = private_key_from_external(private_key); -- uint8_t *s_output; -- if (!CBB_add_space(out, &s_output, kEncodedVectorSize)) { -- return 0; -+// Modified crypto_kem_decap to BoringSSL style API -+void decap(uint8_t out_shared_key[KYBER_SSBYTES], -+ const struct private_key *in_priv, -+ const uint8_t *ct, size_t ciphertext_len, int mlkem) -+{ -+ uint8_t *ss = out_shared_key; -+ const uint8_t *sk = &in_priv->opaque[0]; -+ -+ size_t i; -+ int fail = 1; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; -+ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; -+ -+ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { -+ indcpa_dec(buf, ct, sk); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ for(i=0;is, kLog2Prime); -- if (!kyber_marshal_public_key(out, &priv->pub) || -- !CBB_add_bytes(out, priv->pub.public_key_hash, -- sizeof(priv->pub.public_key_hash)) || -- !CBB_add_bytes(out, priv->fo_failure_secret, -- sizeof(priv->fo_failure_secret))) { -- return 0; -+ -+ if (mlkem == 1) { -+ /* Compute shared secret in case of rejection: ss2 = PRF(z || c). */ -+ uint8_t ss2[KYBER_SYMBYTES]; -+ keccak_state ks; -+ shake256_init(&ks); -+ shake256_absorb( -+ &ks, -+ sk + KYBER_SECRETKEYBYTES - KYBER_SYMBYTES, -+ KYBER_SYMBYTES -+ ); -+ shake256_absorb(&ks, ct, ciphertext_len); -+ shake256_finalize(&ks); -+ shake256_squeeze(ss2, KYBER_SYMBYTES, &ks); -+ -+ /* Set ss2 to the real shared secret if c = c' */ -+ cmov(ss2, kr, KYBER_SYMBYTES, 1-fail); -+ memcpy(ss, ss2, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, ciphertext_len); -+ -+ /* Overwrite pre-k with z on re-encryption failure */ -+ cmov(kr, sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, KYBER_SYMBYTES, fail); -+ -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } -- return 1; - } - --int KYBER_parse_private_key(struct KYBER_private_key *out_private_key, -- CBS *in) { -- struct private_key *const priv = private_key_from_external(out_private_key); -+void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], -+ const struct public_key *in_pub) { -+ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); -+} - -- CBS s_bytes; -- if (!CBS_get_bytes(in, &s_bytes, kEncodedVectorSize) || -- !vector_decode(&priv->s, CBS_data(&s_bytes), kLog2Prime) || -- !kyber_parse_public_key_no_hash(&priv->pub, in) || -- !CBS_copy_bytes(in, priv->pub.public_key_hash, -- sizeof(priv->pub.public_key_hash)) || -- !CBS_copy_bytes(in, priv->fo_failure_secret, -- sizeof(priv->fo_failure_secret)) || -- CBS_len(in) != 0) { -- return 0; -- } -- return 1; -+void parse_public_key(struct public_key *out, -+ const uint8_t in[KYBER_PUBLICKEYBYTES]) { -+ memcpy(&out->opaque, in, KYBER_PUBLICKEYBYTES); - } -diff --git a/crypto/kyber/kyber512.c b/crypto/kyber/kyber512.c -new file mode 100644 -index 000000000..21eed11a2 ---- /dev/null -+++ b/crypto/kyber/kyber512.c -@@ -0,0 +1,5 @@ -+#define KYBER_K 2 -+ -+#include "kyber.c" -+ -+ -diff --git a/crypto/kyber/kyber768.c b/crypto/kyber/kyber768.c -new file mode 100644 -index 000000000..3e572b72e ---- /dev/null -+++ b/crypto/kyber/kyber768.c -@@ -0,0 +1,4 @@ -+#define KYBER_K 3 -+ -+#include "kyber.c" -+ -diff --git a/crypto/kyber/kyber_test.cc b/crypto/kyber/kyber_test.cc -deleted file mode 100644 -index b9daa87d3..000000000 ---- a/crypto/kyber/kyber_test.cc -+++ /dev/null -@@ -1,184 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#include -- --#include -- --#include -- --#include --#include --#include -- --#include "../test/file_test.h" --#include "../test/test_util.h" --#include "../keccak/internal.h" --#include "./internal.h" -- -- --template --static std::vector Marshal(int (*marshal_func)(CBB *, const T *), -- const T *t) { -- bssl::ScopedCBB cbb; -- uint8_t *encoded; -- size_t encoded_len; -- if (!CBB_init(cbb.get(), 1) || // -- !marshal_func(cbb.get(), t) || // -- !CBB_finish(cbb.get(), &encoded, &encoded_len)) { -- abort(); -- } -- -- std::vector ret(encoded, encoded + encoded_len); -- OPENSSL_free(encoded); -- return ret; --} -- --TEST(KyberTest, Basic) { -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_private_key priv; -- KYBER_generate_key(encoded_public_key, &priv); -- -- uint8_t first_two_bytes[2]; -- OPENSSL_memcpy(first_two_bytes, encoded_public_key, sizeof(first_two_bytes)); -- OPENSSL_memset(encoded_public_key, 0xff, sizeof(first_two_bytes)); -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- KYBER_public_key pub; -- // Parsing should fail because the first coefficient is >= kPrime; -- ASSERT_FALSE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- -- OPENSSL_memcpy(encoded_public_key, first_two_bytes, sizeof(first_two_bytes)); -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- ASSERT_TRUE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- EXPECT_EQ(CBS_len(&encoded_public_key_cbs), 0u); -- -- EXPECT_EQ(Bytes(encoded_public_key), -- Bytes(Marshal(KYBER_marshal_public_key, &pub))); -- -- KYBER_public_key pub2; -- KYBER_public_from_private(&pub2, &priv); -- EXPECT_EQ(Bytes(encoded_public_key), -- Bytes(Marshal(KYBER_marshal_public_key, &pub2))); -- -- std::vector encoded_private_key( -- Marshal(KYBER_marshal_private_key, &priv)); -- EXPECT_EQ(encoded_private_key.size(), size_t{KYBER_PRIVATE_KEY_BYTES}); -- -- OPENSSL_memcpy(first_two_bytes, encoded_private_key.data(), -- sizeof(first_two_bytes)); -- OPENSSL_memset(encoded_private_key.data(), 0xff, sizeof(first_two_bytes)); -- CBS cbs; -- CBS_init(&cbs, encoded_private_key.data(), encoded_private_key.size()); -- KYBER_private_key priv2; -- // Parsing should fail because the first coefficient is >= kPrime. -- ASSERT_FALSE(KYBER_parse_private_key(&priv2, &cbs)); -- -- OPENSSL_memcpy(encoded_private_key.data(), first_two_bytes, -- sizeof(first_two_bytes)); -- CBS_init(&cbs, encoded_private_key.data(), encoded_private_key.size()); -- ASSERT_TRUE(KYBER_parse_private_key(&priv2, &cbs)); -- EXPECT_EQ(Bytes(encoded_private_key), -- Bytes(Marshal(KYBER_marshal_private_key, &priv2))); -- -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- uint8_t shared_secret1[64]; -- uint8_t shared_secret2[sizeof(shared_secret1)]; -- KYBER_encap(ciphertext, shared_secret1, sizeof(shared_secret1), &pub); -- KYBER_decap(shared_secret2, sizeof(shared_secret2), ciphertext, &priv); -- EXPECT_EQ(Bytes(shared_secret1), Bytes(shared_secret2)); -- KYBER_decap(shared_secret2, sizeof(shared_secret2), ciphertext, &priv2); -- EXPECT_EQ(Bytes(shared_secret1), Bytes(shared_secret2)); --} -- --static void KyberFileTest(FileTest *t) { -- std::vector seed, public_key_expected, private_key_expected, -- ciphertext_expected, shared_secret_expected, given_generate_entropy, -- given_encap_entropy_pre_hash; -- t->IgnoreAttribute("count"); -- ASSERT_TRUE(t->GetBytes(&seed, "seed")); -- ASSERT_TRUE(t->GetBytes(&public_key_expected, "pk")); -- ASSERT_TRUE(t->GetBytes(&private_key_expected, "sk")); -- ASSERT_TRUE(t->GetBytes(&ciphertext_expected, "ct")); -- ASSERT_TRUE(t->GetBytes(&shared_secret_expected, "ss")); -- ASSERT_TRUE(t->GetBytes(&given_generate_entropy, "generateEntropy")); -- ASSERT_TRUE( -- t->GetBytes(&given_encap_entropy_pre_hash, "encapEntropyPreHash")); -- -- KYBER_private_key priv; -- uint8_t encoded_private_key[KYBER_PRIVATE_KEY_BYTES]; -- KYBER_public_key pub; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- uint8_t gen_key_entropy[KYBER_GENERATE_KEY_ENTROPY]; -- uint8_t encap_entropy[KYBER_ENCAP_ENTROPY]; -- uint8_t encapsulated_key[32]; -- uint8_t decapsulated_key[32]; -- // The test vectors provide a CTR-DRBG seed which is used to generate the -- // input entropy. -- ASSERT_EQ(seed.size(), size_t{CTR_DRBG_ENTROPY_LEN}); -- { -- bssl::UniquePtr state( -- CTR_DRBG_new(seed.data(), nullptr, 0)); -- ASSERT_TRUE(state); -- ASSERT_TRUE( -- CTR_DRBG_generate(state.get(), gen_key_entropy, 32, nullptr, 0)); -- ASSERT_TRUE( -- CTR_DRBG_generate(state.get(), gen_key_entropy + 32, 32, nullptr, 0)); -- ASSERT_TRUE(CTR_DRBG_generate(state.get(), encap_entropy, -- KYBER_ENCAP_ENTROPY, nullptr, 0)); -- } -- -- EXPECT_EQ(Bytes(gen_key_entropy), Bytes(given_generate_entropy)); -- EXPECT_EQ(Bytes(encap_entropy), Bytes(given_encap_entropy_pre_hash)); -- -- BORINGSSL_keccak(encap_entropy, sizeof(encap_entropy), encap_entropy, -- sizeof(encap_entropy), boringssl_sha3_256); -- -- KYBER_generate_key_external_entropy(encoded_public_key, &priv, -- gen_key_entropy); -- CBB cbb; -- CBB_init_fixed(&cbb, encoded_private_key, sizeof(encoded_private_key)); -- ASSERT_TRUE(KYBER_marshal_private_key(&cbb, &priv)); -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- ASSERT_TRUE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- KYBER_encap_external_entropy(ciphertext, encapsulated_key, -- sizeof(encapsulated_key), &pub, encap_entropy); -- KYBER_decap(decapsulated_key, sizeof(decapsulated_key), ciphertext, &priv); -- -- EXPECT_EQ(Bytes(encapsulated_key), Bytes(decapsulated_key)); -- EXPECT_EQ(Bytes(private_key_expected), Bytes(encoded_private_key)); -- EXPECT_EQ(Bytes(public_key_expected), Bytes(encoded_public_key)); -- EXPECT_EQ(Bytes(ciphertext_expected), Bytes(ciphertext)); -- EXPECT_EQ(Bytes(shared_secret_expected), Bytes(encapsulated_key)); -- -- uint8_t corrupted_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- OPENSSL_memcpy(corrupted_ciphertext, ciphertext, KYBER_CIPHERTEXT_BYTES); -- corrupted_ciphertext[3] ^= 0x40; -- uint8_t corrupted_decapsulated_key[32]; -- KYBER_decap(corrupted_decapsulated_key, sizeof(corrupted_decapsulated_key), -- corrupted_ciphertext, &priv); -- // It would be nice to have actual test vectors for the failure case, but the -- // NIST submission currently does not include those, so we are just testing -- // for inequality. -- EXPECT_NE(Bytes(encapsulated_key), Bytes(corrupted_decapsulated_key)); --} -- --TEST(KyberTest, TestVectors) { -- FileTestGTest("crypto/kyber/kyber_tests.txt", KyberFileTest); --} -diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h -index 71ef2d2bd..74b99b098 100644 ---- a/crypto/obj/obj_dat.h -+++ b/crypto/obj/obj_dat.h -@@ -57,7 +57,7 @@ - /* This file is generated by crypto/obj/objects.go. */ - - --#define NUM_NID 965 -+#define NUM_NID 969 - - static const uint8_t kObjectData[] = { - /* NID_rsadsi */ -@@ -8783,6 +8783,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { - {"HKDF", "hkdf", NID_hkdf, 0, NULL, 0}, - {"X25519Kyber768Draft00", "X25519Kyber768Draft00", - NID_X25519Kyber768Draft00, 0, NULL, 0}, -+ {"X25519Kyber512Draft00", "X25519Kyber512Draft00", -+ NID_X25519Kyber512Draft00, 0, NULL, 0}, -+ {"P256Kyber768Draft00", "P256Kyber768Draft00", NID_P256Kyber768Draft00, 0, -+ NULL, 0}, -+ {"X25519Kyber768Draft00Old", "X25519Kyber768Draft00Old", -+ NID_X25519Kyber768Draft00Old, 0, NULL, 0}, -+ {"X25519MLKEM768", "X25519MLKEM768", NID_X25519MLKEM768, 0, NULL, 0}, - }; - - static const uint16_t kNIDsInShortNameOrder[] = { -@@ -8915,6 +8922,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { - 18 /* OU */, - 749 /* Oakley-EC2N-3 */, - 750 /* Oakley-EC2N-4 */, -+ 966 /* P256Kyber768Draft00 */, - 9 /* PBE-MD2-DES */, - 168 /* PBE-MD2-RC2-64 */, - 10 /* PBE-MD5-DES */, -@@ -8980,7 +8988,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { - 143 /* SXNetID */, - 458 /* UID */, - 948 /* X25519 */, -+ 965 /* X25519Kyber512Draft00 */, - 964 /* X25519Kyber768Draft00 */, -+ 967 /* X25519Kyber768Draft00Old */, -+ 968 /* X25519MLKEM768 */, - 961 /* X448 */, - 11 /* X500 */, - 378 /* X500algorithms */, -@@ -9827,6 +9838,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { - 366 /* OCSP Nonce */, - 371 /* OCSP Service Locator */, - 180 /* OCSP Signing */, -+ 966 /* P256Kyber768Draft00 */, - 161 /* PBES2 */, - 69 /* PBKDF2 */, - 162 /* PBMAC1 */, -@@ -9851,7 +9863,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { - 133 /* Time Stamping */, - 375 /* Trust Root */, - 948 /* X25519 */, -+ 965 /* X25519Kyber512Draft00 */, - 964 /* X25519Kyber768Draft00 */, -+ 967 /* X25519Kyber768Draft00Old */, -+ 968 /* X25519MLKEM768 */, - 961 /* X448 */, - 12 /* X509 */, - 402 /* X509v3 AC Targeting */, -diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num -index a0519acee..2a46adfe8 100644 ---- a/crypto/obj/obj_mac.num -+++ b/crypto/obj/obj_mac.num -@@ -952,3 +952,7 @@ X448 961 - sha512_256 962 - hkdf 963 - X25519Kyber768Draft00 964 -+X25519Kyber512Draft00 965 -+P256Kyber768Draft00 966 -+X25519Kyber768Draft00Old 967 -+X25519MLKEM768 968 -diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt -index 3ad32ea3d..347fc556a 100644 ---- a/crypto/obj/objects.txt -+++ b/crypto/obj/objects.txt -@@ -1332,8 +1332,12 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme - : dh-std-kdf - : dh-cofactor-kdf - --# NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs). -+# NID for Kyber hybrids (no corresponding OID). -+ : X25519Kyber512Draft00 - : X25519Kyber768Draft00 -+ : P256Kyber768Draft00 -+ : X25519Kyber768Draft00Old -+ : X25519MLKEM768 - - # See RFC 8410. - 1 3 101 110 : X25519 -diff --git a/include/openssl/kyber.h b/include/openssl/kyber.h -index cafae9d17..a05eb8957 100644 ---- a/include/openssl/kyber.h -+++ b/include/openssl/kyber.h -@@ -1,17 +1,3 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- - #ifndef OPENSSL_HEADER_KYBER_H - #define OPENSSL_HEADER_KYBER_H - -@@ -21,105 +7,104 @@ - extern "C" { - #endif - -+#define KYBER512_PUBLIC_KEY_BYTES 800 -+#define KYBER512_CIPHERTEXT_BYTES 768 -+#define KYBER512_PRIVATE_KEY_BYTES 1632 -+#define KYBER768_PUBLIC_KEY_BYTES 1184 -+#define KYBER768_CIPHERTEXT_BYTES 1088 -+#define KYBER768_PRIVATE_KEY_BYTES 2400 - --// Kyber768. -- -- --// KYBER_public_key contains a Kyber768 public key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_public_key { -- union { -- uint8_t bytes[512 * (3 + 9) + 32 + 32]; -- uint16_t alignment; -- } opaque; -+struct KYBER512_private_key { -+ uint8_t opaque[KYBER512_PRIVATE_KEY_BYTES]; - }; -- --// KYBER_private_key contains a Kyber768 private key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_private_key { -- union { -- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; -- uint16_t alignment; -- } opaque; -+struct KYBER768_private_key { -+ uint8_t opaque[KYBER768_PRIVATE_KEY_BYTES]; -+}; -+struct KYBER512_public_key { -+ uint8_t opaque[KYBER512_PUBLIC_KEY_BYTES]; -+}; -+struct KYBER768_public_key { -+ uint8_t opaque[KYBER768_PUBLIC_KEY_BYTES]; - }; - --// KYBER_PUBLIC_KEY_BYTES is the number of bytes in an encoded Kyber768 public --// key. --#define KYBER_PUBLIC_KEY_BYTES 1184 -- --// KYBER_generate_key generates a random public/private key pair, writes the --// encoded public key to |out_encoded_public_key| and sets |out_private_key| to --// the private key. --OPENSSL_EXPORT void KYBER_generate_key( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key); -- --// KYBER_public_from_private sets |*out_public_key| to the public key that --// corresponds to |private_key|. (This is faster than parsing the output of --// |KYBER_generate_key| if, for some reason, you need to encapsulate to a key --// that was just generated.) --OPENSSL_EXPORT void KYBER_public_from_private( -- struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key); -- --// KYBER_CIPHERTEXT_BYTES is number of bytes in the Kyber768 ciphertext. --#define KYBER_CIPHERTEXT_BYTES 1088 -- --// KYBER_encap encrypts a random secret key of length |out_shared_secret_len| to --// |public_key|, writes the ciphertext to |ciphertext|, and writes the random --// key to |out_shared_secret|. The party calling |KYBER_decap| must already know --// the correct value of |out_shared_secret_len|. --OPENSSL_EXPORT void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, -- size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key); -- --// KYBER_decap decrypts a key of length |out_shared_secret_len| from --// |ciphertext| using |private_key| and writes it to |out_shared_secret|. If --// |ciphertext| is invalid, |out_shared_secret| is filled with a key that --// will always be the same for the same |ciphertext| and |private_key|, but --// which appears to be random unless one has access to |private_key|. These --// alternatives occur in constant time. Any subsequent symmetric encryption --// using |out_shared_secret| must use an authenticated encryption scheme in --// order to discover the decapsulation failure. --OPENSSL_EXPORT void KYBER_decap( -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key); -- -- --// Serialisation of keys. -- --// KYBER_marshal_public_key serializes |public_key| to |out| in the standard --// format for Kyber public keys. It returns one on success or zero on allocation --// error. --OPENSSL_EXPORT int KYBER_marshal_public_key( -- CBB *out, const struct KYBER_public_key *public_key); -- --// KYBER_parse_public_key parses a public key, in the format generated by --// |KYBER_marshal_public_key|, from |in| and writes the result to --// |out_public_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_public_key( -- struct KYBER_public_key *out_public_key, CBS *in); -- --// KYBER_marshal_private_key serializes |private_key| to |out| in the standard --// format for Kyber private keys. It returns one on success or zero on --// allocation error. --OPENSSL_EXPORT int KYBER_marshal_private_key( -- CBB *out, const struct KYBER_private_key *private_key); -- --// KYBER_PRIVATE_KEY_BYTES is the length of the data produced by --// |KYBER_marshal_private_key|. --#define KYBER_PRIVATE_KEY_BYTES 2400 -- --// KYBER_parse_private_key parses a private key, in the format generated by --// |KYBER_marshal_private_key|, from |in| and writes the result to --// |out_private_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_private_key( -- struct KYBER_private_key *out_private_key, CBS *in); -- -+// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to -+// generate a keypair. -+#define KYBER_GENERATE_KEY_BYTES 64 -+ -+// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a -+// session key. -+#define KYBER_ENCAP_BYTES 32 -+ -+// KYBER_KEY_BYTES is the number of bytes in a shared key. -+#define KYBER_KEY_BYTES 32 -+ -+// KYBER512_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER512_generate_key( -+ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER768_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER768_generate_key( -+ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER512_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER768_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER512_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER512_marshal_public_key( -+ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); -+ -+// KYBER768_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER768_marshal_public_key( -+ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); -+ -+// KYBER512_parse_public_key sets |*out| to the public-key encoded in |in|. -+OPENSSL_EXPORT void KYBER512_parse_public_key( -+ struct KYBER512_public_key *out, const uint8_t in[KYBER512_PUBLIC_KEY_BYTES]); -+ -+// KYBER768_parse_public_key sets |*out| to the public-key encoded in |in|. -+OPENSSL_EXPORT void KYBER768_parse_public_key( -+ struct KYBER768_public_key *out, const uint8_t in[KYBER768_PUBLIC_KEY_BYTES]); - - #if defined(__cplusplus) - } // extern C -diff --git a/include/openssl/nid.h b/include/openssl/nid.h -index 4dd8841b1..5b102c610 100644 ---- a/include/openssl/nid.h -+++ b/include/openssl/nid.h -@@ -4255,6 +4255,18 @@ extern "C" { - #define SN_X25519Kyber768Draft00 "X25519Kyber768Draft00" - #define NID_X25519Kyber768Draft00 964 - -+#define SN_X25519Kyber512Draft00 "X25519Kyber512Draft00" -+#define NID_X25519Kyber512Draft00 965 -+ -+#define SN_P256Kyber768Draft00 "P256Kyber768Draft00" -+#define NID_P256Kyber768Draft00 966 -+ -+#define SN_X25519Kyber768Draft00Old "X25519Kyber768Draft00Old" -+#define NID_X25519Kyber768Draft00Old 967 -+ -+#define SN_X25519MLKEM768 "X25519MLKEM768" -+#define NID_X25519MLKEM768 968 -+ - - #if defined(__cplusplus) - } /* extern C */ -diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h -index 003e0a5f7..884685ba9 100644 ---- a/include/openssl/ssl.h -+++ b/include/openssl/ssl.h -@@ -2363,6 +2363,10 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); - #define SSL_GROUP_SECP521R1 25 - #define SSL_GROUP_X25519 29 - #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 -+#define SSL_GROUP_X25519_KYBER512_DRAFT00 0xfe30 -+#define SSL_GROUP_X25519_KYBER768_DRAFT00_OLD 0xfe31 -+#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 -+#define SSL_GROUP_X25519_MLKEM768 0x11ec - - // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. - // Each element of |group_ids| should be one of the |SSL_GROUP_*| constants. It -diff --git a/sources.cmake b/sources.cmake -index ba2f5bc9e..d7ef5153a 100644 ---- a/sources.cmake -+++ b/sources.cmake -@@ -52,7 +52,6 @@ set( - crypto/hrss/hrss_test.cc - crypto/impl_dispatch_test.cc - crypto/keccak/keccak_test.cc -- crypto/kyber/kyber_test.cc - crypto/lhash/lhash_test.cc - crypto/obj/obj_test.cc - crypto/pem/pem_test.cc -@@ -145,7 +144,6 @@ set( - crypto/hmac_extra/hmac_tests.txt - crypto/hpke/hpke_test_vectors.txt - crypto/keccak/keccak_tests.txt -- crypto/kyber/kyber_tests.txt - crypto/pkcs8/test/empty_password.p12 - crypto/pkcs8/test/no_encryption.p12 - crypto/pkcs8/test/nss.p12 -diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index b13400097..44a2d0f5c 100644 ---- a/ssl/extensions.cc -+++ b/ssl/extensions.cc -@@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { - static bool is_post_quantum_group(uint16_t id) { - switch (id) { - case SSL_GROUP_X25519_KYBER768_DRAFT00: -+ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: -+ case SSL_GROUP_X25519_KYBER512_DRAFT00: -+ case SSL_GROUP_P256_KYBER768_DRAFT00: -+ case SSL_GROUP_X25519_MLKEM768: - return true; - default: - return false; -@@ -307,6 +311,8 @@ bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello, - } - - static const uint16_t kDefaultGroups[] = { -+ SSL_GROUP_X25519_MLKEM768, -+ SSL_GROUP_P256_KYBER768_DRAFT00, - SSL_GROUP_X25519, - SSL_GROUP_SECP256R1, - SSL_GROUP_SECP384R1, -diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc -index 694bec11d..3e4d2e7c4 100644 ---- a/ssl/ssl_key_share.cc -+++ b/ssl/ssl_key_share.cc -@@ -26,6 +26,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -191,63 +192,292 @@ class X25519KeyShare : public SSLKeyShare { - uint8_t private_key_[32]; - }; - --class X25519Kyber768KeyShare : public SSLKeyShare { -+class P256Kyber768Draft00KeyShare : public SSLKeyShare { - public: -- X25519Kyber768KeyShare() {} -+ P256Kyber768Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_GROUP_P256_KYBER768_DRAFT00; } -+ -+ bool Generate(CBB *out) override { -+ assert(!p256_private_key_); -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ // Generate a P-256 private key. -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ p256_private_key_.reset(BN_new()); -+ if (!group || !p256_private_key_ || -+ !BN_rand_range_ex(p256_private_key_.get(), 1, -+ EC_GROUP_get0_order(group.get()))) { -+ return false; -+ } -+ -+ // Compute the corresponding P-256 public key and serialize it. -+ UniquePtr p256_public_key(EC_POINT_new(group.get())); -+ if (!p256_public_key || -+ !EC_POINT_mul(group.get(), p256_public_key.get(), p256_private_key_.get(), -+ NULL, NULL, bn_ctx.get()) || -+ !EC_POINT_point2cbb(out, group.get(), p256_public_key.get(), -+ POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) { -+ return false; -+ } -+ -+ -+ // Kyber -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - -- uint16_t GroupID() const override { -- return SSL_GROUP_X25519_KYBER768_DRAFT00; -+ if (!CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { -+ return false; -+ } -+ -+ return true; - } - -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ assert(!p256_private_key_); -+ -+ if (peer_key.size() != 65 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ if (!group) { -+ return false; -+ } -+ -+ // Parse peer point -+ UniquePtr peer_point(EC_POINT_new(group.get())); -+ UniquePtr result(EC_POINT_new(group.get())); -+ BIGNUM *x = BN_CTX_get(bn_ctx.get()); -+ if (!peer_point || !result || !x) { -+ return false; -+ } -+ -+ if (peer_key.empty() || peer_key[0] != POINT_CONVERSION_UNCOMPRESSED || -+ !EC_POINT_oct2point(group.get(), peer_point.get(), peer_key.data(), -+ 65, bn_ctx.get())) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ *out_alert = SSL_AD_DECODE_ERROR; -+ return false; -+ } -+ -+ p256_private_key_.reset(BN_new()); -+ if (!p256_private_key_ || !BN_rand_range_ex(p256_private_key_.get(), 1, -+ EC_GROUP_get0_order(group.get()))) { -+ return false; -+ } -+ -+ // Compute the corresponding P-256 public key and serialize it. -+ UniquePtr p256_public_key(EC_POINT_new(group.get())); -+ if (!p256_public_key || -+ !EC_POINT_mul(group.get(), p256_public_key.get(), p256_private_key_.get(), -+ NULL, NULL, bn_ctx.get()) || -+ !EC_POINT_point2cbb(out_public_key, group.get(), p256_public_key.get(), -+ POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) { -+ return false; -+ } -+ -+ // Compute the x-coordinate of |peer_key| * |p256_private_key_|. -+ if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(), -+ p256_private_key_.get(), bn_ctx.get()) || -+ !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL, -+ bn_ctx.get())) { -+ return false; -+ } -+ -+ // Encode the x-coordinate left-padded with zeros. -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES) || -+ !BN_bn2bin_padded(secret.data(), 32, x)) { -+ return false; -+ } -+ -+ -+ KYBER768_public_key peer_public_key; -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 65); -+ -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ -+ if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { -+ return false; -+ } -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { -+ assert(p256_private_key_); -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 65 + KYBER768_CIPHERTEXT_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ if (!group) { -+ return false; -+ } -+ -+ // Parse peer point -+ UniquePtr peer_point(EC_POINT_new(group.get())); -+ UniquePtr result(EC_POINT_new(group.get())); -+ BIGNUM *x = BN_CTX_get(bn_ctx.get()); -+ if (!peer_point || !result || !x) { -+ return false; -+ } -+ -+ if (peer_key.empty() || peer_key[0] != POINT_CONVERSION_UNCOMPRESSED || -+ !EC_POINT_oct2point(group.get(), peer_point.get(), peer_key.data(), -+ 65, bn_ctx.get())) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ *out_alert = SSL_AD_DECODE_ERROR; -+ return false; -+ } -+ -+ // Compute the x-coordinate of |peer_key| * |p256_private_key_|. -+ if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(), -+ p256_private_key_.get(), bn_ctx.get()) || -+ !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL, -+ bn_ctx.get())) { -+ return false; -+ } -+ -+ // Encode the x-coordinate left-padded with zeros. -+ if (!secret.Init(32 + KYBER_KEY_BYTES) || -+ !BN_bn2bin_padded(secret.data(), 32, x)) { -+ return false; -+ } -+ -+ KYBER768_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 65, peer_key.size() - 65, 0); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ UniquePtr p256_private_key_; -+ KYBER768_private_key kyber_private_key_; -+}; -+ -+class X25519Kyber768Draft00KeyShare : public SSLKeyShare { -+ public: -+ X25519Kyber768Draft00KeyShare(uint16_t group_id) : group_id_(group_id) { -+ assert(group_id == SSL_GROUP_X25519_KYBER768_DRAFT00 -+ || group_id == SSL_GROUP_X25519_KYBER768_DRAFT00_OLD); -+ } -+ -+ uint16_t GroupID() const override { return group_id_; } -+ - bool Generate(CBB *out) override { - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); +This patch adds: + +1. Enable X25519MLKEM768 by default. + +2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily + need for compliance reasons. (Note that this is not the codepoint + allocated for that exchange in the IANA table.) + Enables by default and in FIPS mode. + +3. Add SSL(_CTX)_use_second_keyshare. By default BoringSSL will send a + non post-quantum and a post-quantum keyshare if available. These + functions allow one to change the behaviour to only send a single + keyshare. +--- + crypto/obj/obj_dat.h | 6 +- + crypto/obj/obj_mac.num | 1 + + crypto/obj/objects.txt | 1 + + include/openssl/nid.h | 3 + + include/openssl/ssl.h | 15 ++++ + ssl/extensions.cc | 26 ++++--- + ssl/internal.h | 12 ++- + ssl/ssl_key_share.cc | 111 +++++++++++++++++++++++++++- + ssl/ssl_lib.cc | 16 +++- + ssl/ssl_test.cc | 19 ++++- + ssl/test/runner/basic_tests.go | 2 + + ssl/test/runner/cbc_tests.go | 3 + + ssl/test/runner/common.go | 2 +- + ssl/test/runner/curve_tests.go | 28 +++---- + ssl/test/runner/ech_tests.go | 24 +++++- + ssl/test/runner/extension_tests.go | 3 +- + ssl/test/runner/key_update_tests.go | 6 +- + tool/client.cc | 9 +++ + 18 files changed, 245 insertions(+), 42 deletions(-) + +diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h +index d8b86dcd2..6dd49ec36 100644 +--- a/crypto/obj/obj_dat.h ++++ b/crypto/obj/obj_dat.h +@@ -15,7 +15,7 @@ + // This file is generated by crypto/obj/objects.go. -- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(kyber_public_key, &kyber_private_key_); -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { -+ !CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { - return false; - } +-#define NUM_NID 971 ++#define NUM_NID 972 - return true; - } + static const uint8_t kObjectData[] = { + /* NID_rsadsi */ +@@ -8799,6 +8799,8 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { + {"id-ml-dsa-87", "ML-DSA-87", NID_ML_DSA_87, 9, &kObjectData[6223], 0}, + {"id-alg-ml-kem-768", "ML-KEM-768", NID_ML_KEM_768, 9, &kObjectData[6232], + 0}, ++ {"P256Kyber768Draft00", "P256Kyber768Draft00", NID_P256Kyber768Draft00, 0, ++ NULL, 0}, + }; -- bool Encap(CBB *out_ciphertext, Array *out_secret, -- uint8_t *out_alert, Span peer_key) override { -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { - Array secret; -- if (!secret.Init(32 + 32)) { -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } + static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8931,6 +8933,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { + 18 /* OU */, + 749 /* Oakley-EC2N-3 */, + 750 /* Oakley-EC2N-4 */, ++ 971 /* P256Kyber768Draft00 */, + 9 /* PBE-MD2-DES */, + 168 /* PBE-MD2-RC2-64 */, + 10 /* PBE-MD5-DES */, +@@ -9854,6 +9857,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { + 366 /* OCSP Nonce */, + 371 /* OCSP Service Locator */, + 180 /* OCSP Signing */, ++ 971 /* P256Kyber768Draft00 */, + 161 /* PBES2 */, + 69 /* PBKDF2 */, + 162 /* PBMAC1 */, +diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num +index ae863e29d..7231b9a58 100644 +--- a/crypto/obj/obj_mac.num ++++ b/crypto/obj/obj_mac.num +@@ -958,3 +958,4 @@ ML_DSA_44 967 + ML_DSA_65 968 + ML_DSA_87 969 + ML_KEM_768 970 ++P256Kyber768Draft00 971 +diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt +index 1e0cb76db..e8b249dfd 100644 +--- a/crypto/obj/objects.txt ++++ b/crypto/obj/objects.txt +@@ -1340,6 +1340,7 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme + + # NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs). + : X25519Kyber768Draft00 ++ : P256Kyber768Draft00 + : X25519MLKEM768 + + # See RFC 8410. +diff --git a/include/openssl/nid.h b/include/openssl/nid.h +index 83a1cf592..7265f15f6 100644 +--- a/include/openssl/nid.h ++++ b/include/openssl/nid.h +@@ -5508,6 +5508,9 @@ extern "C" { + #define OBJ_ML_KEM_768 2L, 16L, 840L, 1L, 101L, 3L, 4L, 4L, 2L + #define OBJ_ENC_ML_KEM_768 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02 - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); -- KYBER_public_key peer_kyber_pub; -- CBS peer_key_cbs; -- CBS peer_x25519_cbs; -- CBS peer_kyber_cbs; -- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); -- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || -- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, -- KYBER_PUBLIC_KEY_BYTES) || -- CBS_len(&peer_key_cbs) != 0 || -- !X25519(secret.data(), x25519_private_key_, -- CBS_data(&peer_x25519_cbs)) || -- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { ++#define SN_P256Kyber768Draft00 "P256Kyber768Draft00" ++#define NID_P256Kyber768Draft00 971 + -+ KYBER768_public_key peer_public_key; -+ if (peer_key.size() != 32 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } + + #if defined(__cplusplus) + } /* extern C */ +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index ff68ba69e..0730e769a 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h +@@ -2550,6 +2550,7 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + #define SSL_GROUP_X25519_MLKEM768 0x11ec + #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 + #define SSL_GROUP_MLKEM1024 0x0202 ++#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 + + // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. + // Each element of |group_ids| should be a unique one of the |SSL_GROUP_*| +@@ -5964,6 +5965,20 @@ OPENSSL_EXPORT int SSL_CTX_set1_curves_list(SSL_CTX *ctx, const char *curves); + // SSL_set1_curves_list calls |SSL_set1_groups_list|. + OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); + ++// By default, a client will send both a non post-quantum and a post-quantum ++// keyshare if available. ++// ++// SSL_use_second_keyshare controls this behaviour. If |enabled| is 0, then ++// a client using |ssl| will only send one keyshare. ++OPENSSL_EXPORT void SSL_use_second_keyshare(SSL *ssl, int enabled); + -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 32); ++// By default, a client will send both a non post-quantum and a post-quantum ++// keyshare if available. ++// ++// SSL_CTX_use_second_keyshare controls this behaviour. If |enabled| is 0, then ++// a client using |ctx| will only send one keyshare. ++OPENSSL_EXPORT void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled); + -+ if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + // TLSEXT_nid_unknown is a constant used in OpenSSL for + // |SSL_get_negotiated_group| to return an unrecognized group. BoringSSL never + // returns this value, but we define this constant for compatibility. +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index c5f90688c..e0514fed3 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc +@@ -101,6 +101,7 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { + static bool is_post_quantum_group(uint16_t id) { + switch (id) { + case SSL_GROUP_X25519_KYBER768_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: + case SSL_GROUP_X25519_MLKEM768: + case SSL_GROUP_MLKEM1024: + return true; +@@ -2241,18 +2242,21 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) { + if (!default_key_shares.TryPushBack(supported_group_list[0])) { return false; } - -- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, -- &peer_kyber_pub); -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); - -- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, -+ if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, x25519_public_key, - sizeof(x25519_public_key)) || -- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, -- sizeof(kyber_ciphertext))) { -+ !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { - return false; +- // We'll try to include one post-quantum and one classical initial key +- // share. +- for (size_t i = 1; i < supported_group_list.size(); i++) { +- if (is_post_quantum_group(default_key_shares[0]) == +- is_post_quantum_group(supported_group_list[i])) { +- continue; +- } +- if (!default_key_shares.TryPushBack(supported_group_list[i])) { +- return false; ++ ++ if (!ssl->config->disable_second_keyshare) { ++ // We'll try to include one post-quantum and one classical initial key ++ // share. ++ for (size_t i = 1; i < supported_group_list.size(); i++) { ++ if (is_post_quantum_group(default_key_shares[0]) == ++ is_post_quantum_group(supported_group_list[i])) { ++ continue; ++ } ++ if (!default_key_shares.TryPushBack(supported_group_list[i])) { ++ return false; ++ } ++ assert(default_key_shares[1] != default_key_shares[0]); ++ break; + } +- assert(default_key_shares[1] != default_key_shares[0]); +- break; } - -@@ -256,30 +486,233 @@ class X25519Kyber768KeyShare : public SSLKeyShare { + selected_key_shares.emplace(default_key_shares); } +diff --git a/ssl/internal.h b/ssl/internal.h +index a69505b47..1f5ce51e6 100644 +--- a/ssl/internal.h ++++ b/ssl/internal.h +@@ -955,7 +955,7 @@ struct NamedGroup { + Span NamedGroups(); + + // kNumNamedGroups is the number of supported groups. +-constexpr size_t kNumNamedGroups = 7u; ++constexpr size_t kNumNamedGroups = 8u; + + // DefaultSupportedGroupIds returns the list of IDs for the default groups that + // are supported when the caller hasn't explicitly configured supported groups. +@@ -3388,6 +3388,11 @@ struct SSL_CONFIG { + // permute_extensions is whether to permute extensions when sending messages. + bool permute_extensions : 1; + ++ // As a client by default we will send a non post-quantum share and ++ // a post-quantum share if available. If disable_second_keyshare is set, ++ // we will only send the most preferred keyshare. ++ bool disable_second_keyshare : 1; ++ + // aes_hw_override if set indicates we should override checking for aes + // hardware support, and use the value in aes_hw_override_value instead. + bool aes_hw_override : 1; +@@ -4015,6 +4020,11 @@ struct ssl_ctx_st : public bssl::RefCounted { + // permute_extensions is whether to permute extensions when sending messages. + bool permute_extensions : 1; + ++ // As a client by default we will send a non post-quantum share and ++ // a post-quantum share if available. If disable_second_keyshare is set, ++ // we will only send the most preferred keyshare. ++ bool disable_second_keyshare : 1; ++ + // allow_unknown_alpn_protos is whether the client allows unsolicited ALPN + // protocols from the peer. + bool allow_unknown_alpn_protos : 1; +diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc +index d155b5527..4fb08906b 100644 +--- a/ssl/ssl_key_share.cc ++++ b/ssl/ssl_key_share.cc +@@ -193,6 +193,109 @@ class X25519KeyShare : public SSLKeyShare { + uint8_t private_key_[32]; + }; - bool Decap(Array *out_secret, uint8_t *out_alert, -- Span ciphertext) override { -+ Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 32 + KYBER768_CIPHERTEXT_BYTES || -+ !X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ KYBER768_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 32, peer_key.size() - 32, 0); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; -+ KYBER768_private_key kyber_private_key_; -+ uint16_t group_id_; -+}; -+ -+class X25519MLKEM768KeyShare : public SSLKeyShare { ++class P256Kyber768Draft00KeyShare : public SSLKeyShare { + public: -+ X25519MLKEM768KeyShare() {} ++ P256Kyber768Draft00KeyShare() ++ : ecks_(EC_group_p256(), SSL_GROUP_SECP256R1) {} + -+ uint16_t GroupID() const override { return SSL_GROUP_X25519_MLKEM768; } ++ uint16_t GroupID() const override { ++ return SSL_GROUP_P256_KYBER768_DRAFT00; ++ } + + bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); ++ uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; ++ KYBER_generate_key(kyber_public_key, &kyber_private_key_); + -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, kyber_public_key_bytes, sizeof(kyber_public_key_bytes)) || -+ !CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key))) { ++ if(!ecks_.Generate(out) || ++ !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { + return false; + } + + return true; + } + -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } ++ bool Encap(CBB *out_ciphertext, Array *out_secret, ++ uint8_t *out_alert, Span peer_key) override { ++ Array ec_secret; + -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); ++ *out_alert = SSL_AD_INTERNAL_ERROR; + -+ KYBER768_public_key peer_public_key; -+ if (peer_key.size() != KYBER768_PUBLIC_KEY_BYTES + 32) { -+ *out_alert = SSL_AD_DECODE_ERROR; ++ if(peer_key.size() != p256_share_size + KYBER_PUBLIC_KEY_BYTES) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data()); -+ -+ if (!X25519(secret.data() + 32, x25519_private_key_, -+ peer_key.data() + KYBER768_PUBLIC_KEY_BYTES)) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ if (!ecks_.Encap(out_ciphertext, &ec_secret, out_alert, ++ peer_key.subspan(0, p256_share_size))) { + return false; + } + -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); ++ KYBER_public_key peer_kyber_pub; ++ CBS peer_kyber_cbs; ++ CBS_init(&peer_kyber_cbs, peer_key.data() + p256_share_size, ++ KYBER_PUBLIC_KEY_BYTES); + -+ if(!KYBER768_encap(ciphertext, secret.data(), &peer_public_key, entropy, 1)) { ++ if (!KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } -+ if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext)) || -+ !CBB_add_bytes(out_public_key, x25519_public_key, sizeof(x25519_public_key))) { ++ ++ uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; ++ Array secret; ++ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { + return false; + } ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); ++ KYBER_encap(kyber_ciphertext, secret.data() + p256_secret_size, ++ &peer_kyber_pub); + -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { - *out_alert = SSL_AD_INTERNAL_ERROR; - - Array secret; -- if (!secret.Init(32 + 32)) { -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - -- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || -- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { -+ if (peer_key.size() != KYBER768_CIPHERTEXT_BYTES + 32 || -+ !X25519(secret.data() + 32, x25519_private_key_, -+ peer_key.data() + KYBER768_CIPHERTEXT_BYTES )) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, -- &kyber_private_key_); -+ KYBER768_decap(secret.data(), &kyber_private_key_, -+ peer_key.data(), peer_key.size() - 32, 1); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; -+ KYBER768_private_key kyber_private_key_; -+}; -+ -+class X25519Kyber512Draft00KeyShare : public SSLKeyShare { -+ public: -+ X25519Kyber512Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_GROUP_X25519_KYBER512_DRAFT00; } -+ -+ bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER512_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER512_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER512_PUBLIC_KEY_BYTES]; -+ KYBER512_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -+ !CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { ++ if(!CBB_add_bytes(out_ciphertext, kyber_ciphertext, ++ sizeof(kyber_ciphertext))) { + return false; + } + ++ *out_secret = std::move(secret); + return true; + } + -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } ++ bool Decap(Array *out_secret, uint8_t *out_alert, ++ Span ciphertext) override { ++ *out_alert = SSL_AD_INTERNAL_ERROR; + -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); ++ Array ec_secret; + -+ KYBER512_public_key peer_public_key; -+ if (peer_key.size() != 32 + KYBER512_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; ++ if (ciphertext.size() != p256_share_size + KYBER_CIPHERTEXT_BYTES) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ KYBER512_parse_public_key(&peer_public_key, peer_key.data() + 32); -+ -+ if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ if (!ecks_.Decap(&ec_secret, out_alert, ++ ciphertext.subspan(0, p256_share_size))) { + return false; + } + -+ uint8_t ciphertext[KYBER512_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ -+ if(!KYBER512_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, x25519_public_key, -+ sizeof(x25519_public_key)) || -+ !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { ++ Array secret; ++ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { + return false; + } -+ ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); ++ KYBER_decap(secret.data() + p256_secret_size, ++ ciphertext.data() + p256_share_size, &kyber_private_key_); + *out_secret = std::move(secret); + return true; + } + -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 32 + KYBER512_CIPHERTEXT_BYTES || -+ !X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } ++ private: ++ ECKeyShare ecks_; ++ KYBER_private_key kyber_private_key_; + -+ KYBER512_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 32, peer_key.size() - 32, 0); ++ static constexpr size_t p256_share_size = 65; ++ static constexpr size_t p256_secret_size = 32; ++}; + - *out_secret = std::move(secret); - return true; - } - - private: - uint8_t x25519_private_key_[32]; -- KYBER_private_key kyber_private_key_; -+ KYBER512_private_key kyber_private_key_; - }; - - constexpr NamedGroup kNamedGroups[] = { -@@ -288,8 +721,16 @@ constexpr NamedGroup kNamedGroups[] = { - {NID_secp384r1, SSL_GROUP_SECP384R1, "P-384", "secp384r1"}, + // draft-tls-westerbaan-xyber768d00-03 + class X25519Kyber768KeyShare : public SSLKeyShare { + public: +@@ -441,9 +544,11 @@ constexpr NamedGroup kNamedGroups[] = { {NID_secp521r1, SSL_GROUP_SECP521R1, "P-521", "secp521r1"}, {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, -+ {NID_X25519Kyber512Draft00, SSL_GROUP_X25519_KYBER512_DRAFT00, -+ "X25519Kyber512Draft00", "Xyber512D00"}, {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, - "X25519Kyber768Draft00", ""}, -+ "X25519Kyber768Draft00", "Xyber768D00"}, -+ {NID_X25519Kyber768Draft00Old, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD, -+ "X25519Kyber768Draft00Old", "Xyber768D00Old"}, ++ "X25519Kyber768Draft00", "Xyber768D00"}, + {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, "X25519MLKEM768", ""}, + {NID_ML_KEM_1024, SSL_GROUP_MLKEM1024, "MLKEM1024", ""}, + {NID_P256Kyber768Draft00, SSL_GROUP_P256_KYBER768_DRAFT00, -+ "P256Kyber768Draft00", "P256Kyber768D00"}, -+ {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, -+ "X25519MLKEM768", "X25519MLKEM768"} ++ "P256Kyber768Draft00", "P256Kyber768D00"}, }; - } // namespace -@@ -310,8 +751,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { - return MakeUnique(EC_group_p521(), SSL_GROUP_SECP521R1); - case SSL_GROUP_X25519: - return MakeUnique(); -+ case SSL_GROUP_X25519_KYBER512_DRAFT00: -+ return UniquePtr(New()); - case SSL_GROUP_X25519_KYBER768_DRAFT00: -- return MakeUnique(); -+ return UniquePtr(New( -+ group_id)); -+ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: -+ return UniquePtr(New( -+ group_id)); + static_assert(std::size(kNamedGroups) == kNumNamedGroups, +@@ -455,6 +560,8 @@ Span NamedGroups() { return kNamedGroups; } + + Span DefaultSupportedGroupIds() { + static const uint16_t kDefaultSupportedGroupIds[] = { ++ SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, +@@ -478,6 +585,8 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { + return MakeUnique(); + case SSL_GROUP_MLKEM1024: + return MakeUnique(); + case SSL_GROUP_P256_KYBER768_DRAFT00: -+ return UniquePtr(New()); -+ case SSL_GROUP_X25519_MLKEM768: -+ return UniquePtr(New()); ++ return MakeUnique(); default: return nullptr; } diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 58b68e675..38c8e906c 100644 +index f64b103fb..fe5bb9bc7 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc -@@ -3260,7 +3260,7 @@ namespace fips202205 { +@@ -397,6 +397,7 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method) + channel_id_enabled(false), + grease_enabled(false), + permute_extensions(false), ++ disable_second_keyshare(false), + allow_unknown_alpn_protos(false), + false_start_allowed_without_alpn(false), + handoff(false), +@@ -527,6 +528,7 @@ SSL *SSL_new(SSL_CTX *ctx) { + ssl->config->retain_only_sha256_of_client_certs = + ctx->retain_only_sha256_of_client_certs; + ssl->config->permute_extensions = ctx->permute_extensions; ++ ssl->config->disable_second_keyshare = ctx->disable_second_keyshare; + ssl->config->aes_hw_override = ctx->aes_hw_override; + ssl->config->aes_hw_override_value = ctx->aes_hw_override_value; + ssl->config->compliance_policy = ctx->compliance_policy; +@@ -586,6 +588,7 @@ SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg) + jdk11_workaround(false), + quic_use_legacy_codepoint(false), + permute_extensions(false), ++ disable_second_keyshare(false), + alps_use_new_codepoint(true) { + assert(ssl); + } +@@ -3331,6 +3334,15 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { + return SSL_set1_groups_list(ssl, curves); + } + ++void SSL_use_second_keyshare(SSL *ssl, int enabled) { ++ ssl->config->disable_second_keyshare = !enabled; ++} ++ ++void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled) { ++ ctx->disable_second_keyshare = !enabled; ++} ++ ++ + namespace fips202205 { + + // (References are to SP 800-52r2): +@@ -3342,7 +3354,9 @@ namespace fips202205 { // Section 3.3.1 // "The server shall be configured to only use cipher suites that are // composed entirely of NIST approved algorithms" -static const uint16_t kGroups[] = {SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; -+static const uint16_t kGroups[] = {SSL_GROUP_P256_KYBER768_DRAFT00, SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; ++static const uint16_t kGroups[] = { ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc -index a8f4f215b..e0ebb505e 100644 +index 779a2c37a..36a0cab3b 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc -@@ -484,7 +484,34 @@ static const CurveTest kCurveTests[] = { - "P-256:X25519Kyber768Draft00", - { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER768_DRAFT00 }, - }, -- -+ { -+ "Xyber512D00", -+ { SSL_GROUP_X25519_KYBER512_DRAFT00 }, -+ }, -+ { -+ "Xyber768D00", -+ { SSL_GROUP_X25519_KYBER768_DRAFT00 }, -+ }, -+ { -+ "Xyber768D00:Xyber768D00Old", -+ { SSL_GROUP_X25519_KYBER768_DRAFT00, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD }, -+ }, -+ { -+ "P-256:Xyber512D00", -+ { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER512_DRAFT00 }, -+ }, -+ { -+ "P256Kyber768D00", -+ { SSL_GROUP_P256_KYBER768_DRAFT00 }, -+ }, -+ { -+ "X25519MLKEM768", -+ { SSL_GROUP_X25519_MLKEM768 }, -+ }, -+ { -+ "P-256:P256Kyber768D00", -+ { SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00 }, -+ }, - { - "P-256:P-384:P-521:X25519", +@@ -506,6 +506,14 @@ static const CurveTest kCurveTests[] = { + "MLKEM1024:X25519MLKEM768", + {SSL_GROUP_MLKEM1024, SSL_GROUP_X25519_MLKEM768}, + }, ++ { ++ "P256Kyber768Draft00", ++ {SSL_GROUP_P256_KYBER768_DRAFT00}, ++ }, ++ { ++ "P-256:P256Kyber768Draft00", ++ {SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00}, ++ }, + { -diff --git a/tool/speed.cc b/tool/speed.cc -index 942dcade1..f31e9e244 100644 ---- a/tool/speed.cc -+++ b/tool/speed.cc -@@ -1018,6 +1018,116 @@ static bool SpeedScrypt(const std::string &selected) { - return true; - } + "P-256:P-384:P-521:X25519", +@@ -668,7 +676,9 @@ TEST(SSLTest, CurveRules) { + } + + TEST(SSLTest, DefaultCurves) { +- const uint16_t kDefaults[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, ++ const uint16_t kDefaults[] = {SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1}; + + // Test the group ID APIs. +@@ -1522,6 +1532,9 @@ static bool GetClientHello(SSL *ssl, std::vector *out) { + static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, + size_t ticket_len) { + bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); ++ // RTG-3417 bas: we need to disable PQ here so that the small ClientHello ++ // padding tests properly tests things. ++ SSL_CTX_set1_curves_list(ctx.get(), "X25519"); + bssl::UniquePtr session = + CreateSessionWithTicket(session_version, ticket_len); + if (!ctx || !session) { +@@ -6815,7 +6828,9 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { + + // The default list of groups is used before applying the handoff. + EXPECT_THAT(server->config->supported_group_list, +- ElementsAreArray({SSL_GROUP_X25519, SSL_GROUP_SECP256R1, ++ ElementsAreArray({SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1})); + ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff)); + EXPECT_EQ(1u, server->config->supported_group_list.size()); +diff --git a/ssl/test/runner/basic_tests.go b/ssl/test/runner/basic_tests.go +index 08de8fa5f..dd945fa49 100644 +--- a/ssl/test/runner/basic_tests.go ++++ b/ssl/test/runner/basic_tests.go +@@ -129,6 +129,7 @@ read alert 1 0 + `write hs 1 + read hs 3 + write hs 1 ++write hs 1 + read hs 2 + read hs 11 + read hs 12 +@@ -1956,6 +1957,7 @@ read alert 1 0 + write hs 2 + write hs 8 + write hs 11 ++write hs 11 + write hs 15 + write hs 20 + read hs 20 +diff --git a/ssl/test/runner/cbc_tests.go b/ssl/test/runner/cbc_tests.go +index 6f49d12af..5e970b2b5 100644 +--- a/ssl/test/runner/cbc_tests.go ++++ b/ssl/test/runner/cbc_tests.go +@@ -14,6 +14,8 @@ + + package runner + ++import "strconv" ++ + func addCBCPaddingTests() { + testCases = append(testCases, testCase{ + name: "MaxCBCPadding", +@@ -104,6 +106,7 @@ func addCBCSplittingTests() { + "-partial-write", + // BoringSSL disables 3DES by default. + "-cipher", "ALL:3DES", ++ "-curves", strconv.Itoa(int(CurveX25519)), + }, + }) + } +diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go +index 7dbde72c9..9d18d9d45 100644 +--- a/ssl/test/runner/common.go ++++ b/ssl/test/runner/common.go +@@ -2095,7 +2095,7 @@ type ProtocolBugs struct { + FailIfHelloRetryRequested bool + + // FailIfPostQuantumOffered will cause a server to reject a ClientHello if +- // post-quantum curves are supported. ++ // post-quantum curves are not supported. + FailIfPostQuantumOffered bool + + // ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello +diff --git a/ssl/test/runner/curve_tests.go b/ssl/test/runner/curve_tests.go +index 8e7b0a45b..556bf314d 100644 +--- a/ssl/test/runner/curve_tests.go ++++ b/ssl/test/runner/curve_tests.go +@@ -579,17 +579,6 @@ func addCurveTests() { + }) + } + +- // ML-KEM and Kyber should not be offered by default as a client. +- testCases = append(testCases, testCase{ +- name: "PostQuantumNotEnabledByDefaultInClients", +- config: Config{ +- MinVersion: VersionTLS13, +- Bugs: ProtocolBugs{ +- FailIfPostQuantumOffered: true, +- }, +- }, +- }) +- + for _, curve := range testCurves { + if !isMLKEMGroup(curve.id) { + continue +@@ -679,18 +668,19 @@ func addCurveTests() { + }) + } + +- // As a server, ML-KEMs and Kyber are not yet supported by default. ++ // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share. + testCases = append(testCases, testCase{ +- testType: serverTest, +- name: "PostQuantumNotEnabledByDefaultForAServer", ++ name: "NotJustMLKEMKeyShare", + config: Config{ +- MinVersion: VersionTLS13, +- CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768, CurveX25519}, +- DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768}, ++ MinVersion: VersionTLS13, ++ Bugs: ProtocolBugs{ ++ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519}, ++ }, + }, + flags: []string{ +- "-server-preference", +- "-expect-curve-id", strconv.Itoa(int(CurveX25519)), ++ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), ++ "-curves", strconv.Itoa(int(CurveX25519)), ++ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), + }, + }) + +diff --git a/ssl/test/runner/ech_tests.go b/ssl/test/runner/ech_tests.go +index 2cd3c10d3..f19d8d20a 100644 +--- a/ssl/test/runner/ech_tests.go ++++ b/ssl/test/runner/ech_tests.go +@@ -451,7 +451,8 @@ func addEncryptedClientHelloTests() { + expectMsgCallback += clientAndServerHello + } + // EncryptedExtensions onwards. +- expectMsgCallback += `write hs 8 ++ if protocol != dtls { ++ expectMsgCallback += `write hs 8 + write hs 11 + write hs 15 + write hs 20 +@@ -462,6 +463,20 @@ write hs 4 + read ack + read ack + ` ++ } else { ++ expectMsgCallback += `write hs 8 ++write hs 11 ++write hs 11 ++write hs 15 ++write hs 20 ++read hs 20 ++write ack ++write hs 4 ++write hs 4 ++read ack ++read ack ++` ++ } + if protocol != dtls { + expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "write ack\n", "") + expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "read ack\n", "") +@@ -2349,8 +2364,11 @@ read ack + + // Test the message callback is correctly reported, with and without + // HelloRetryRequest. +- clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" +- clientAndServerHelloInitial := clientAndServerHello ++ clientAndServerHelloInitial := "write clienthelloinner\nwrite hs 1\nwrite hs 1\nread hs 2\n" ++ clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" ++ if protocol != dtls { ++ clientAndServerHelloInitial = clientAndServerHello ++ } + if protocol == tls { + clientAndServerHelloInitial += "write ccs\n" + } +diff --git a/ssl/test/runner/extension_tests.go b/ssl/test/runner/extension_tests.go +index d6adb7759..4eb80aa8e 100644 +--- a/ssl/test/runner/extension_tests.go ++++ b/ssl/test/runner/extension_tests.go +@@ -16,6 +16,7 @@ package runner + + import ( + "fmt" ++ "strconv" + ) + + func addExtensionTests() { +@@ -1967,7 +1968,7 @@ func addExtensionTests() { + // This hostname just needs to be long enough to push the + // ClientHello into F5's danger zone between 256 and 511 bytes + // long. +- flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"}, ++ flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com", "-curves", strconv.Itoa(int(CurveX25519))}, + }) + + // Test that illegal extensions in TLS 1.3 are rejected by the client if +diff --git a/ssl/test/runner/key_update_tests.go b/ssl/test/runner/key_update_tests.go +index 0a9053038..5ce709589 100644 +--- a/ssl/test/runner/key_update_tests.go ++++ b/ssl/test/runner/key_update_tests.go +@@ -14,7 +14,10 @@ + + package runner + +-import "slices" ++import ( ++ "slices" ++ "strconv" ++) + + func addKeyUpdateTests() { + // TLS tests. +@@ -295,6 +298,7 @@ func addKeyUpdateTests() { + }, + }, + shimSendsKeyUpdateBeforeRead: true, ++ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))}, + }) + + // Test that shim responds to KeyUpdate requests. +diff --git a/tool/client.cc b/tool/client.cc +index 0839d4880..be9b79259 100644 +--- a/tool/client.cc ++++ b/tool/client.cc +@@ -125,6 +125,11 @@ static const struct argument kArguments[] = { + kBooleanArgument, + "Permute extensions in handshake messages", + }, ++ { ++ "-disable-second-keyshare", ++ kBooleanArgument, ++ "Do not send a second keyshare", ++ }, + { + "-test-resumption", kBooleanArgument, + "Connect to the server twice. The first connection is closed once a " +@@ -538,6 +543,10 @@ bool Client(const std::vector &args) { + SSL_CTX_set_permute_extensions(ctx.get(), 1); + } -+static bool SpeedKyber768(const std::string &selected) { -+ if (!selected.empty() && selected != "Kyber768") { -+ return true; -+ } -+ -+ TimeResults results; -+ -+ if (!TimeFunction(&results, []() -> bool { -+ struct KYBER768_public_key pub; -+ struct KYBER768_private_key priv; -+ uint8_t entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER768_generate_key(&pub, &priv, entropy); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_generate_key.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 generate"); -+ -+ struct KYBER768_public_key pub; -+ struct KYBER768_private_key priv; -+ uint8_t key_entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(key_entropy, sizeof(key_entropy)); -+ KYBER768_generate_key(&pub, &priv, key_entropy); -+ -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool { -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER768_encap(ciphertext, shared_key, &pub, entropy, 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_encap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 encap"); -+ -+ if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool { -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ KYBER768_decap(shared_key, &priv, ciphertext, sizeof(ciphertext), 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_decap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 decap"); -+ -+ return true; -+} -+ -+static bool SpeedKyber512(const std::string &selected) { -+ if (!selected.empty() && selected != "Kyber512") { -+ return true; -+ } -+ -+ TimeResults results; -+ -+ if (!TimeFunction(&results, []() -> bool { -+ struct KYBER512_public_key pub; -+ struct KYBER512_private_key priv; -+ uint8_t entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER512_generate_key(&pub, &priv, entropy); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_generate_key.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber512 generate"); -+ -+ struct KYBER512_public_key pub; -+ struct KYBER512_private_key priv; -+ uint8_t key_entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(key_entropy, sizeof(key_entropy)); -+ KYBER512_generate_key(&pub, &priv, key_entropy); -+ -+ uint8_t ciphertext[KYBER512_CIPHERTEXT_BYTES]; -+ if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool { -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER512_encap(ciphertext, shared_key, &pub, entropy, 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_encap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber512 encap"); -+ -+ if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool { -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ KYBER512_decap(shared_key, &priv, ciphertext, sizeof(ciphertext), 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_decap.\n"); -+ return false; ++ if (args_map.count("-disable-second-keyshare") != 0) { ++ SSL_CTX_use_second_keyshare(ctx.get(), 0); + } + -+ results.Print("Kyber512 decap"); -+ -+ return true; -+} -+ - static bool SpeedHRSS(const std::string &selected) { - if (!selected.empty() && selected != "HRSS") { - return true; -@@ -1079,55 +1189,6 @@ static bool SpeedHRSS(const std::string &selected) { - return true; - } - --static bool SpeedKyber(const std::string &selected) { -- if (!selected.empty() && selected != "Kyber") { -- return true; -- } -- -- TimeResults results; -- -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- // This ciphertext is nonsense, but Kyber decap is constant-time so, for the -- // purposes of timing, it's fine. -- memset(ciphertext, 42, sizeof(ciphertext)); -- if (!TimeFunctionParallel(&results, [&]() -> bool { -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(encoded_public_key, &priv); -- uint8_t shared_secret[32]; -- KYBER_decap(shared_secret, sizeof(shared_secret), ciphertext, &priv); -- return true; -- })) { -- fprintf(stderr, "Failed to time KYBER_generate_key + KYBER_decap.\n"); -- return false; -- } -- -- results.Print("Kyber generate + decap"); -- -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(encoded_public_key, &priv); -- KYBER_public_key pub; -- if (!TimeFunctionParallel(&results, [&]() -> bool { -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { -- return false; -- } -- uint8_t shared_secret[32]; -- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); -- return true; -- })) { -- fprintf(stderr, "Failed to time KYBER_encap.\n"); -- return false; -- } -- -- results.Print("Kyber parse + encap"); -- -- return true; --} -- - static bool SpeedSpx(const std::string &selected) { - if (!selected.empty() && selected.find("spx") == std::string::npos) { - return true; -@@ -1661,7 +1722,8 @@ bool Speed(const std::vector &args) { - !SpeedScrypt(selected) || // - !SpeedRSAKeyGen(selected) || // - !SpeedHRSS(selected) || // -- !SpeedKyber(selected) || // -+ !SpeedKyber512(selected) || -+ !SpeedKyber768(selected) || - !SpeedSpx(selected) || // - !SpeedHashToCurve(selected) || // - !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1, + if (args_map.count("-root-certs") != 0) { + if (!SSL_CTX_load_verify_locations( + ctx.get(), args_map["-root-certs"].c_str(), nullptr)) { -- -2.50.1 (Apple Git-155) +2.40.0 diff --git a/boring-sys/patches/rpk.patch b/boring-sys/patches/rpk.patch index edf977088..512566e94 100644 --- a/boring-sys/patches/rpk.patch +++ b/boring-sys/patches/rpk.patch @@ -1,120 +1,146 @@ +From 9725dabfc86f57607e60e48e09e10615a05bb053 Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Wed, 17 Dec 2025 13:27:50 +0100 +Subject: [PATCH] Implement support for raw public keys as server certificates + (RFC 7250) + +--- + crypto/err/ssl.errordata | 1 + + include/openssl/ssl.h | 65 +++++++++++++ + include/openssl/tls1.h | 3 + + ssl/extensions.cc | 122 +++++++++++++++++++++++ + ssl/internal.h | 18 ++++ + ssl/ssl_cert.cc | 8 ++ + ssl/ssl_credential.cc | 48 +++++++++ + ssl/ssl_lib.cc | 52 +++++++++- + ssl/ssl_test.cc | 67 +++++++++++++ + ssl/test/bssl_shim.cc | 3 +- + ssl/test/runner/certificate_tests.go | 134 +++++++++++++++++++++++++- + ssl/test/runner/common.go | 19 ++++ + ssl/test/runner/handshake_client.go | 71 +++++++++++++- + ssl/test/runner/handshake_messages.go | 33 ++++++- + ssl/test/runner/handshake_server.go | 53 +++++++--- + ssl/test/runner/runner.go | 3 +- + ssl/test/test_config.cc | 92 +++++++++++++++--- + ssl/test/test_config.h | 2 + + ssl/tls13_both.cc | 98 +++++++++++++------ + ssl/tls13_server.cc | 34 ++++++- + 20 files changed, 861 insertions(+), 65 deletions(-) + +diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata +index 01c4ca616..c9f4994d9 100644 +--- a/crypto/err/ssl.errordata ++++ b/crypto/err/ssl.errordata +@@ -95,6 +95,7 @@ SSL,159,INVALID_MESSAGE + SSL,320,INVALID_OUTER_EXTENSION + SSL,251,INVALID_OUTER_RECORD_TYPE + SSL,269,INVALID_SCT_LIST ++SSL,331,INVALID_SERVER_CERTIFICATE_TYPE_LIST + SSL,295,INVALID_SIGNATURE_ALGORITHM + SSL,324,INVALID_SPAKE2PLUSV1_VALUE + SSL,160,INVALID_SSL_SESSION diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h -index 003e0a5f7..b8f8d49c8 100644 +index ff68ba69e..5a1cf42ca 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h -@@ -138,6 +138,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_SSL_H - #define OPENSSL_HEADER_SSL_H -@@ -1138,6 +1157,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( - SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs, - EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); - -+// SSL_CTX_set_nullchain_and_key sets the private key for a -+// TLS client or server. Reference to the given |EVP_PKEY| -+// object is added as needed. Exactly one of |privkey| or |privkey_method| -+// may be non-NULL. Returns one on success and zero on error. -+// Note the lack of a corresponding public-key certificate. -+// See SSL_CTX_set_server_raw_public_key_certificate. -+OPENSSL_EXPORT int SSL_CTX_set_nullchain_and_key( -+ SSL_CTX *ctx, -+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); -+ - // SSL_set_chain_and_key sets the certificate chain and private key for a TLS - // client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY| - // objects are added as needed. Exactly one of |privkey| or |privkey_method| -@@ -1146,6 +1175,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( - SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, - const SSL_PRIVATE_KEY_METHOD *privkey_method); - -+// SSL_set_nullchain_and_key sets the private key for a TLS -+// client or server. Reference to the given |EVP_PKEY| -+// object is added as needed. Exactly one of |privkey| or |privkey_method| -+// may be non-NULL. Returns one on success and zero on error. -+// Note the lack of a corresponding public-key certificate. -+// See SSL_set_server_raw_public_key_certificate. -+OPENSSL_EXPORT int SSL_set_nullchain_and_key( -+ SSL *ssl, EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method); -+ - // SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by - // |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this - // call. The return value may be |NULL| if no chain has been set. -@@ -3041,6 +3080,21 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); - OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); +@@ -1781,6 +1781,10 @@ OPENSSL_EXPORT STACK_OF(X509) *SSL_get_peer_full_cert_chain(const SSL *ssl); + OPENSSL_EXPORT const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates( + const SSL *ssl); ++// SSL_get0_peer_pubkey returns the peer's public key during a handshake, or ++// NULL if unavailable. The caller does not take ownership of the result. ++OPENSSL_EXPORT const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl); ++ + // SSL_get0_signed_cert_timestamp_list sets |*out| and |*out_len| to point to + // |*out_len| bytes of SCT information from the server. This is only valid if + // |ssl| is a client. The SCT information is a SignedCertificateTimestampList +@@ -3406,6 +3410,49 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); + // codepoint. By default, the old codepoint is used. + OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); -+// Server Certificate Type. ++// Server Certificate Type (RFC 7250). ++// ++// The Server Certificate Type extension (RFC 7301) allows negotiating ++// different server certificate types. This is used, for example, to receive ++// a raw public key instead of a full-fedged X.509 certificate from a server. + -+#define TLSEXT_CERTIFICATETYPE_X509 0 -+#define TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY 2 ++// SSL_CTX_set_server_certificate_types sets the server certificate type list ++// on |ctx| to |types|. This is the list of certificate types that the client ++// is willing to receive from the server. |types| must be an array ++// of |TLS_CERTIFICATE_TYPE_*| values. Configuring a non-empty array enables ++// the server_certificate_type extension on a client. ++OPENSSL_EXPORT int SSL_CTX_set_server_certificate_types(SSL_CTX *ctx, ++ const uint8_t *types, ++ size_t types_len); + -+OPENSSL_EXPORT int SSL_CTX_set_server_raw_public_key_certificate( -+ SSL_CTX *ctx, const uint8_t *raw_public_key, unsigned raw_public_key_len); ++// SSL_CTX_get0_server_certificate_types returns the server certificate type ++// list configured on |ctx|. ++OPENSSL_EXPORT void SSL_CTX_get0_server_certificate_types(const SSL_CTX *ctx, ++ const uint8_t **types, ++ size_t *types_len); + -+OPENSSL_EXPORT int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx); ++// SSL_set_server_certificate_types sets the server certificate type list ++// on |ssl| to |types|. This is the list of certificate types that the client ++// is willing to receive from the server. |types| must be an array ++// of |TLS_CERTIFICATE_TYPE_*| values. Configuring a non-empty array enables ++// the server_certificate_type extension on a client. ++OPENSSL_EXPORT int SSL_set_server_certificate_types(SSL *ssl, ++ const uint8_t *types, ++ size_t types_len); + -+OPENSSL_EXPORT int SSL_set_server_raw_public_key_certificate( -+ SSL *ssl, const uint8_t *raw_public_key, unsigned raw_public_key_len); ++// SSL_get0_server_certificate_types returns the server certificate type list ++// configured on |ssl|. ++OPENSSL_EXPORT void SSL_get0_server_certificate_types(const SSL *ssl, ++ const uint8_t **types, ++ size_t *types_len); + -+OPENSSL_EXPORT int SSL_has_server_raw_public_key_certificate(SSL *ssl); ++// SSL_get_server_certificate_type_selected gets the selected server ++// certificate type from |ssl|. ++OPENSSL_EXPORT uint8_t SSL_get_server_certificate_type_selected(const SSL *ssl); + ++#define TLS_CERTIFICATE_TYPE_X509 0 ++#define TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY 2 ++ + // Certificate compression. // - // Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this +@@ -3759,6 +3806,23 @@ OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void); + OPENSSL_EXPORT int SSL_CREDENTIAL_set1_delegated_credential( + SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc); + ++// Raw Public Key Credentials ++ ++// SSL_CREDENTIAL_new_raw_public_key returns a new, empty Raw Public Key ++// credential, or NULL on error. Callers should release the result with ++// |SSL_CREDENTIAL_free| when done. ++// ++// Callers should configure a raw public key and a private key on the ++// credential, then add it with |SSL_CTX_add1_credential|. ++OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key(void); ++ ++// SSL_CREDENTIAL_set1_spki |cred|'s raw public key from |spki|. ++// If |spki| is NULL, the public key is extracted from |cred|'s private key. ++// It returns one on success and zero on error, including if |spki| is ++// malformed or if it is NULL and |cred| has no private key. |spki| should ++// be a SubjectPublicKeyInfo structure, as described in RFC 5280. ++int SSL_CREDENTIAL_set1_spki(SSL_CREDENTIAL *cred, ++ CRYPTO_BUFFER *spki); + + // Password Authenticated Key Exchange (PAKE). + // +@@ -6569,6 +6633,7 @@ BSSL_NAMESPACE_END + #define SSL_R_INVALID_TRUST_ANCHOR_LIST 328 + #define SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST 329 + #define SSL_R_DUPLICATE_GROUP 330 ++#define SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST 331 + #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000 + #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010 + #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020 diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h -index c1207a3b7..ac6ed222a 100644 +index ea55e2a07..f55c0d441 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h -@@ -146,6 +146,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_TLS1_H - #define OPENSSL_HEADER_TLS1_H -@@ -197,6 +216,9 @@ extern "C" { +@@ -64,6 +64,9 @@ extern "C" { // ExtensionType value from RFC 7301 #define TLSEXT_TYPE_application_layer_protocol_negotiation 16 @@ -125,668 +151,1317 @@ index c1207a3b7..ac6ed222a 100644 #define TLSEXT_TYPE_padding 21 diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index b13400097..8694712fd 100644 +index c5f90688c..eb5ef58e8 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc -@@ -105,6 +105,25 @@ - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -3108,6 +3127,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, +@@ -3523,6 +3523,121 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, return true; } +// Server Certificate Type ++// ++// https://datatracker.ietf.org/doc/html/rfc7250#section-3 + -+static bool ext_server_certificate_type_add_clienthello(const SSL_HANDSHAKE *hs, -+ CBB *out, -+ CBB *out_compressible, -+ ssl_client_hello_type_t type) { -+ -+ if (hs->max_version <= TLS1_2_VERSION) { -+ return true; -+ } -+ -+ if (hs->config->server_certificate_type_list.empty()) { -+ return true; -+ } -+ -+ CBB contents, server_certificate_types; -+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) || -+ !CBB_add_u16_length_prefixed(out, &contents) || -+ !CBB_add_u8_length_prefixed(&contents, &server_certificate_types) || -+ !CBB_add_bytes(&server_certificate_types, -+ hs->config->server_certificate_type_list.data(), -+ hs->config->server_certificate_type_list.size()) || -+ !CBB_flush(out)) { ++bool ssl_is_valid_certificate_type_list(Span in) { ++ CBS type_list = in; ++ if (CBS_len(&type_list) == 0) { + return false; + } -+ ++ uint8_t type; ++ while (CBS_get_u8(&type_list, &type)) { ++ switch (type) { ++ case TLS_CERTIFICATE_TYPE_X509: ++ case TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY: ++ break; ++ default: ++ return false; ++ } ++ } + return true; +} + -+static bool ssl_is_certificate_type_allowed(CBS *certificate_type_list, -+ uint8_t certificate_type) -+{ -+ uint8_t supported_certificate_type; -+ while (CBS_len(certificate_type_list) > 0) { -+ if (!CBS_get_u8(certificate_type_list, -+ &supported_certificate_type)) { -+ break; -+ } -+ -+ if (supported_certificate_type != certificate_type) { -+ continue; -+ } -+ ++static bool ext_server_certificate_type_add_clienthello( ++ const SSL_HANDSHAKE *hs, CBB *out, CBB *out_compressible, ++ ssl_client_hello_type_t type) { ++ if (hs->max_version < TLS1_3_VERSION || ++ hs->config->server_certificate_type_list.empty()) { + return true; + } + -+ return false; ++ CBB contents, type_list; ++ return CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) && ++ CBB_add_u16_length_prefixed(out, &contents) && ++ CBB_add_u8_length_prefixed(&contents, &type_list) && ++ CBB_add_bytes(&type_list, ++ hs->config->server_certificate_type_list.data(), ++ hs->config->server_certificate_type_list.size()) && ++ CBB_flush(out); +} + +static bool ext_server_certificate_type_parse_serverhello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, -+ CBS *content) -+{ -+ if (hs->max_version <= TLS1_2_VERSION || -+ hs->config->server_certificate_type_list.empty()) { ++ CBS *contents) { ++ if (hs->ssl->s3->session_reused) { + return true; + } + -+ // Strict -+ if (!content) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; ++ uint8_t cert_type = TLS_CERTIFICATE_TYPE_X509; ++ if (contents != nullptr) { ++ assert(!hs->config->server_certificate_type_list.empty()); ++ if (!CBS_get_u8(contents, &cert_type) || CBS_len(contents) != 0) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ goto err; ++ } + } + -+ CBS certificate_type_list = -+ MakeConstSpan(hs->config->server_certificate_type_list); -+ -+ uint8_t certificate_type; -+ if (CBS_get_u8(content, &certificate_type) && -+ ssl_is_certificate_type_allowed(&certificate_type_list, -+ certificate_type)) { -+ hs->server_certificate_type = certificate_type; -+ hs->server_certificate_type_negotiated = 1; -+ return true; ++ if (!hs->config->server_certificate_type_list.empty() && ++ std::none_of( ++ hs->config->server_certificate_type_list.begin(), ++ hs->config->server_certificate_type_list.end(), ++ [cert_type](const auto &type) { return type == cert_type; })) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ goto err; + } + -+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ hs->server_certificate_type = cert_type; ++ return true; ++ ++err: + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; +} + +static bool ext_server_certificate_type_parse_clienthello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, -+ CBS *content) -+{ -+ if (!content) { ++ CBS *contents) { ++ if (contents == nullptr || ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) { + return true; + } + -+ if (hs->max_version <= TLS1_2_VERSION || -+ hs->config->server_certificate_type_list.empty()) { -+ return true; -+ } -+ -+ CBS certificate_type_list = -+ MakeConstSpan(hs->config->server_certificate_type_list); -+ + CBS type_list; -+ if (!CBS_get_u8_length_prefixed(content, &type_list)) { -+ type_list.len = 0; ++ if (!CBS_get_u8_length_prefixed(contents, &type_list) || ++ CBS_len(contents) != 0 || CBS_len(&type_list) == 0) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ return false; + } + -+ uint8_t type; -+ while(CBS_len(&type_list) > 0) { -+ if (!CBS_get_u8(&type_list, &type)) { -+ break; -+ } -+ -+ if (!ssl_is_certificate_type_allowed(&certificate_type_list, type)) { -+ continue; -+ } -+ -+ hs->server_certificate_type = type; -+ hs->server_certificate_type_negotiated = 1; -+ return true; ++ if (!hs->server_certificate_type_list.CopyFrom(type_list)) { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ return false; + } + -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; ++ return true; +} + +static bool ext_server_certificate_type_add_serverhello(SSL_HANDSHAKE *hs, -+ CBB *out) -+{ -+ if (!hs->server_certificate_type_negotiated) { ++ CBB *out) { ++ if (hs->ssl->s3->session_reused || hs->credential == nullptr || ++ hs->credential->type == SSLCredentialType::kX509) { + return true; + } + -+ CBB contents; -+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) || -+ !CBB_add_u16_length_prefixed(out, &contents) || -+ !CBB_add_u8(&contents, hs->server_certificate_type) || -+ !CBB_flush(out)) { -+ return false; ++ if (hs->credential->type != SSLCredentialType::kRawPublicKey) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; + } + -+ return true; ++ CBB cert_types; ++ return CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) && ++ CBB_add_u16_length_prefixed(out, &cert_types) && ++ CBB_add_u8(&cert_types, TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) && ++ CBB_flush(out); +} + // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { - { -@@ -3289,6 +3448,13 @@ static const struct tls_extension kExtensions[] = { - ignore_parse_clienthello, - ext_alps_add_serverhello_old, - }, -+ { -+ TLSEXT_TYPE_server_certificate_type, -+ ext_server_certificate_type_add_clienthello, -+ ext_server_certificate_type_parse_serverhello, -+ ext_server_certificate_type_parse_clienthello, -+ ext_server_certificate_type_add_serverhello, -+ }, + { +@@ -3727,6 +3842,13 @@ static const struct tls_extension kExtensions[] = { + ext_trust_anchors_parse_clienthello, + ext_trust_anchors_add_serverhello, + }, ++ { ++ TLSEXT_TYPE_server_certificate_type, ++ ext_server_certificate_type_add_clienthello, ++ ext_server_certificate_type_parse_serverhello, ++ ext_server_certificate_type_parse_clienthello, ++ ext_server_certificate_type_add_serverhello, ++ }, }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension)) -diff --git a/ssl/handshake.cc b/ssl/handshake.cc -index 8d5a23872..c8ca629e8 100644 ---- a/ssl/handshake.cc -+++ b/ssl/handshake.cc -@@ -109,6 +109,25 @@ - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -148,6 +167,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) - handback(false), - hints_requested(false), - cert_compression_negotiated(false), -+ server_certificate_type_negotiated(false), - apply_jdk11_workaround(false), - can_release_private_key(false), - channel_id_negotiated(false) { -@@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) { - - uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN; - enum ssl_verify_result_t ret; -- if (hs->config->custom_verify_callback != nullptr) { -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { -+ ret = ssl_verify_invalid; -+ EVP_PKEY *peer_pubkey = hs->peer_pubkey.get(); -+ CBS spki = MakeConstSpan(ssl->config->server_raw_public_key_certificate); -+ EVP_PKEY *pubkey = EVP_parse_public_key(&spki); -+ if (!pubkey) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); -+ alert = SSL_AD_INTERNAL_ERROR; -+ } else if (EVP_PKEY_cmp(peer_pubkey, pubkey) == 1 /* Equal */) { -+ ret = ssl_verify_ok; -+ } else { -+ alert = SSL_AD_BAD_CERTIFICATE; -+ } -+ } else if (hs->config->custom_verify_callback != nullptr) { - ret = hs->config->custom_verify_callback(ssl, &alert); - switch (ret) { - case ssl_verify_ok: diff --git a/ssl/internal.h b/ssl/internal.h -index c9facb699..d7363e729 100644 +index a69505b47..867c62bd6 100644 --- a/ssl/internal.h +++ b/ssl/internal.h -@@ -138,6 +138,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_SSL_INTERNAL_H - #define OPENSSL_HEADER_SSL_INTERNAL_H -@@ -1311,6 +1330,8 @@ int ssl_write_buffer_flush(SSL *ssl); - // configured. - bool ssl_has_certificate(const SSL_HANDSHAKE *hs); - -+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs); -+ - // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used - // by a TLS Certificate message. On success, it advances |cbs| and returns - // true. Otherwise, it returns false and sets |*out_alert| to an alert to send -@@ -1912,6 +1933,8 @@ struct SSL_HANDSHAKE { - // |cert_compression_negotiated| is true. - uint16_t cert_compression_alg_id; - -+ uint8_t server_certificate_type; -+ - // ech_hpke_ctx is the HPKE context used in ECH. On the server, it is - // initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is - // initialized if |selected_ech_config| is not nullptr. -@@ -2062,6 +2085,8 @@ struct SSL_HANDSHAKE { - // cert_compression_negotiated is true iff |cert_compression_alg_id| is valid. - bool cert_compression_negotiated : 1; - -+ bool server_certificate_type_negotiated : 1; -+ - // apply_jdk11_workaround is true if the peer is probably a JDK 11 client - // which implemented TLS 1.3 incorrectly. - bool apply_jdk11_workaround : 1; -@@ -3074,6 +3099,9 @@ struct SSL_CONFIG { - // along with their corresponding ALPS values. - GrowableArray alps_configs; +@@ -1408,6 +1408,7 @@ enum class SSLCredentialType { + kDelegated, + kSPAKE2PlusV1Client, + kSPAKE2PlusV1Server, ++ kRawPublicKey, + }; + + BSSL_NAMESPACE_END +@@ -2062,6 +2063,14 @@ struct SSL_HANDSHAKE { + // pake_verifier is the PAKE context for a server. + UniquePtr pake_verifier; ++ ++ // server_certificate_type_list indicates the types of certificates ++ // the client is able to process. + Array server_certificate_type_list; -+ Array server_raw_public_key_certificate; + - // Contains the QUIC transport params that this endpoint will send. - Array quic_transport_params; ++ // server_certificate_type indicates the type of certificates the server ++ // selected to send as the certificate payload. ++ uint8_t server_certificate_type = TLS_CERTIFICATE_TYPE_X509; + }; -@@ -3666,6 +3694,9 @@ struct ssl_ctx_st { - // format. - bssl::Array alpn_client_proto_list; + // kMaxTickets is the maximum number of tickets to send immediately after the +@@ -2256,6 +2265,10 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, + // identifiers list. + bool ssl_is_valid_trust_anchor_list(Span in); -+ bssl::Array server_certificate_type_list; -+ bssl::Array server_raw_public_key_certificate; ++// ssl_is_valid_certificate_type_list returns whether |in| is a valid ++// certificate type list. ++bool ssl_is_valid_certificate_type_list(Span in); ++ + struct SSLExtension { + SSLExtension(uint16_t type_arg, bool allowed_arg = true) + : type(type_arg), allowed(allowed_arg), present(false) { +@@ -3339,6 +3352,8 @@ struct SSL_CONFIG { + // negotiating a TLS 1.3 connection. + enum ssl_compliance_policy_t compliance_policy = ssl_compliance_policy_none; + ++ Array server_certificate_type_list; + - // SRTP profiles we are willing to do from RFC 5764 - bssl::UniquePtr srtp_profiles; + // verify_mode is a bitmask of |SSL_VERIFY_*| values. + uint8_t verify_mode = SSL_VERIFY_NONE; +@@ -3988,6 +4003,9 @@ struct ssl_ctx_st : public bssl::RefCounted { + // accepted from the peer in decreasing order of preference. + bssl::Array verify_sigalgs; + ++ // For a client, this contains the list of supported server certificate types. ++ bssl::Array server_certificate_type_list; ++ + // retain_only_sha256_of_client_certs is true if we should compute the SHA256 + // hash of the peer's certificate and then discard it to save memory and + // session space. Only effective on the server side. diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc -index aa46a8bb6..d90840fce 100644 +index 72218aeea..a1a8f328b 100644 --- a/ssl/ssl_cert.cc +++ b/ssl/ssl_cert.cc -@@ -111,6 +111,25 @@ - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ +@@ -587,6 +587,14 @@ const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates(const SSL *ssl) { + return session->certs.get(); + } - #include ++const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl) { ++ if (ssl->s3->hs == nullptr) { ++ return nullptr; ++ } ++ ++ return ssl->s3->hs->peer_pubkey.get(); ++} ++ + const STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) { + if (ssl->s3->hs == nullptr) { + return nullptr; +diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc +index bbbd76701..16a069c28 100644 +--- a/ssl/ssl_credential.cc ++++ b/ssl/ssl_credential.cc +@@ -164,6 +164,7 @@ bool ssl_credential_st::UsesX509() const { + return true; + case SSLCredentialType::kSPAKE2PlusV1Client: + case SSLCredentialType::kSPAKE2PlusV1Server: ++ case SSLCredentialType::kRawPublicKey: + return false; + } + abort(); +@@ -173,6 +174,7 @@ bool ssl_credential_st::UsesPrivateKey() const { + switch (type) { + case SSLCredentialType::kX509: + case SSLCredentialType::kDelegated: ++ case SSLCredentialType::kRawPublicKey: + return true; + case SSLCredentialType::kSPAKE2PlusV1Client: + case SSLCredentialType::kSPAKE2PlusV1Server: +@@ -335,6 +337,10 @@ SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) { + return New(SSLCredentialType::kDelegated); + } -@@ -302,6 +321,25 @@ static int cert_set_chain_and_key( ++SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key(void) { ++ return New(SSLCredentialType::kRawPublicKey); ++} ++ + void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred) { cred->UpRefInternal(); } + + void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred) { +@@ -448,6 +454,44 @@ int SSL_CREDENTIAL_set1_delegated_credential(SSL_CREDENTIAL *cred, return 1; } -+static int cert_set_key( -+ CERT *cert, -+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ if (privkey == NULL && privkey_method == NULL) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER); ++int SSL_CREDENTIAL_set1_spki(SSL_CREDENTIAL *cred, CRYPTO_BUFFER *spki) { ++ if (cred->type != SSLCredentialType::kRawPublicKey) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + -+ if (privkey != NULL && privkey_method != NULL) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD); ++ ScopedCBB cbb; ++ CBS cbs; ++ if (spki == nullptr) { ++ if (cred->privkey == nullptr) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_NO_PRIVATE_KEY_ASSIGNED); ++ return 0; ++ } ++ ++ if (!CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), cred->privkey.get())) { ++ return 0; ++ } ++ CBS_init(&cbs, CBB_data(cbb.get()), CBB_len(cbb.get())); ++ } else { ++ CRYPTO_BUFFER_init_CBS(spki, &cbs); ++ } ++ ++ bssl::UniquePtr pubkey = ++ ssl_parse_peer_subject_public_key_info(cbs); ++ if (pubkey == nullptr) { + return 0; + } + -+ cert->privatekey = UpRef(privkey); -+ cert->key_method = privkey_method; ++ if (cred->privkey != nullptr && ++ !ssl_compare_public_and_private_key(pubkey.get(), cred->privkey.get())) { ++ return 0; ++ } + ++ cred->pubkey = std::move(pubkey); + return 1; +} + - bool ssl_set_cert(CERT *cert, UniquePtr buffer) { - switch (check_leaf_cert_and_privkey(buffer.get(), cert->privatekey.get())) { - case leaf_cert_and_privkey_error: -@@ -343,6 +381,12 @@ bool ssl_has_certificate(const SSL_HANDSHAKE *hs) { - ssl_has_private_key(hs); + int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred, + CRYPTO_BUFFER *ocsp) { + if (!cred->UsesX509()) { +@@ -611,6 +655,10 @@ void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred, int idx) { } -+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs) { -+ return hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY && -+ ssl_has_private_key(hs); -+} + void SSL_CREDENTIAL_set_must_match_issuer(SSL_CREDENTIAL *cred, int match) { ++ if (cred->type == SSLCredentialType::kRawPublicKey) { ++ return; ++ } + - bool ssl_parse_cert_chain(uint8_t *out_alert, - UniquePtr *out_chain, - UniquePtr *out_pubkey, -@@ -721,11 +765,20 @@ bool ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey, - - bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs) { - SSL *const ssl = hs->ssl; -- if (!ssl_has_certificate(hs)) { -+ if (!ssl_has_certificate(hs) && -+ !ssl_has_raw_public_key_certificate(hs)) { - // Nothing to do. - return true; + cred->must_match_issuer = !!match; + } + +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index f64b103fb..d87c3f3c4 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc +@@ -534,7 +534,9 @@ SSL *SSL_new(SSL_CTX *ctx) { + if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) || + !ssl->config->alpn_client_proto_list.CopyFrom( + ctx->alpn_client_proto_list) || +- !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs)) { ++ !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs) || ++ !ssl->config->server_certificate_type_list.CopyFrom( ++ ctx->server_certificate_type_list)) { + return nullptr; } -+ if (ssl_has_raw_public_key_certificate(hs)) { -+ CBS spki = MakeConstSpan( -+ ssl->config->server_raw_public_key_certificate.data(), -+ ssl->config->server_raw_public_key_certificate.size()); -+ hs->local_pubkey = UniquePtr(EVP_parse_public_key(&spki)); -+ return hs->local_pubkey != NULL; +@@ -3305,6 +3307,54 @@ int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) { + return 1; + } + ++int SSL_CTX_set_server_certificate_types(SSL_CTX *ctx, const uint8_t *types, ++ size_t types_len) { ++ auto span = Span(types, types_len); ++ if (!span.empty() && !ssl_is_valid_certificate_type_list(span)) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST); ++ return 0; + } ++ return ctx->server_certificate_type_list.CopyFrom(span); ++} + - if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) { - return false; - } -@@ -880,6 +933,15 @@ int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs, - privkey, privkey_method); ++void SSL_CTX_get0_server_certificate_types(const SSL_CTX *ctx, ++ const uint8_t **types, ++ size_t *types_len) { ++ *types = ctx->server_certificate_type_list.data(); ++ *types_len = ctx->server_certificate_type_list.size(); ++} ++ ++int SSL_set_server_certificate_types(SSL *ssl, const uint8_t *types, ++ size_t types_len) { ++ if (ssl->server || ssl->config == nullptr) { ++ return 0; ++ } ++ auto span = Span(types, types_len); ++ if (!span.empty() && !ssl_is_valid_certificate_type_list(span)) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST); ++ return 0; ++ } ++ return ssl->config->server_certificate_type_list.CopyFrom(span); ++} ++ ++void SSL_get0_server_certificate_types(const SSL *ssl, const uint8_t **types, ++ size_t *types_len) { ++ if (ssl->server || ssl->config == nullptr) { ++ *types = nullptr; ++ *types_len = 0; ++ return; ++ } ++ *types = ssl->config->server_certificate_type_list.data(); ++ *types_len = ssl->config->server_certificate_type_list.size(); ++} ++ ++uint8_t SSL_get_server_certificate_type_selected(const SSL *ssl) { ++ if (ssl->s3->hs == nullptr) { ++ return TLS_CERTIFICATE_TYPE_X509; ++ } ++ return ssl->s3->hs->server_certificate_type; ++} ++ + uint16_t SSL_get_curve_id(const SSL *ssl) { return SSL_get_group_id(ssl); } + + const char *SSL_get_curve_name(uint16_t curve_id) { +diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc +index 779a2c37a..85aeb817f 100644 +--- a/ssl/ssl_test.cc ++++ b/ssl/ssl_test.cc +@@ -4398,6 +4398,73 @@ TEST_P(SSLVersionTest, DefaultTicketKeyRotation) { + new_session.get(), true /* reused */)); } -+int SSL_set_nullchain_and_key(SSL *ssl, -+ EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ if (!ssl->config) { -+ return 0; ++TEST_P(SSLVersionTest, RawPublicKeyCertificate) { ++ static const uint8_t kCertificateTypes[] = { ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY, 18}; ++ ASSERT_TRUE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 0)); ++ ASSERT_FALSE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 2)); ++ ASSERT_TRUE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 1)); ++ ++ SSL_CTX_set_custom_verify( ++ client_ctx_.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ++ [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t { ++ EXPECT_EQ(SSL_get_server_certificate_type_selected(ssl), ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); ++ ++ const EVP_PKEY *peer_pubkey = SSL_get0_peer_pubkey(ssl); ++ EXPECT_TRUE(peer_pubkey); ++ ++ if (!peer_pubkey) { ++ *out_alert = SSL_AD_CERTIFICATE_UNKNOWN; ++ return ssl_verify_invalid; ++ } ++ ++ SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); ++ if (EVP_PKEY_cmp(reinterpret_cast SSL_CTX_get_app_data(ctx), ++ peer_pubkey) == 1) { ++ return ssl_verify_ok; ++ } ++ ++ *out_alert = SSL_AD_BAD_CERTIFICATE; ++ return ssl_verify_invalid; ++ }); ++ SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_CLIENT); ++ ++ // Server is not configured for raw public keys. ++ ASSERT_FALSE(Connect()); ++ ++ bssl::UniquePtr cred(SSL_CREDENTIAL_new_raw_public_key()); ++ bssl::UniquePtr key = GetECDSATestKey(); ++ ASSERT_TRUE(SSL_CREDENTIAL_set1_private_key(cred.get(), key.get())); ++ ASSERT_FALSE(SSL_CTX_add1_credential(server_ctx_.get(), cred.get())); ++ ASSERT_TRUE(SSL_CREDENTIAL_set1_spki(cred.get(), nullptr)); ++ ASSERT_TRUE(SSL_CTX_add1_credential(server_ctx_.get(), cred.get())); ++ ++ // Client is expecting |wrong_key|. ++ bssl::UniquePtr wrong_key = GetTestKey(); ++ ASSERT_TRUE(wrong_key); ++ SSL_CTX_set_app_data(client_ctx_.get(), wrong_key.get()); ++ ASSERT_FALSE(Connect()); ++ ++ if (!is_tls13()) { ++ return; + } -+ return cert_set_key(ssl->config->cert.get(), privkey, privkey_method); ++ ++ SSL_CTX_set_app_data(client_ctx_.get(), key.get()); ++ ASSERT_TRUE(Connect()); ++ ++ bssl::UniquePtr session = ++ CreateClientSession(client_ctx_.get(), server_ctx_.get()); ++ ASSERT_TRUE(session); ++ ++ TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(), ++ session.get(), ++ true /* expect session reused */)); +} + - int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, - size_t num_certs, EVP_PKEY *privkey, - const SSL_PRIVATE_KEY_METHOD *privkey_method) { -@@ -887,6 +949,12 @@ int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, - privkey_method); + static int SwitchContext(SSL *ssl, int *out_alert, void *arg) { + SSL_CTX *ctx = reinterpret_cast(arg); + SSL_set_SSL_CTX(ssl, ctx); +diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc +index 0eba60d22..9e2fe8da9 100644 +--- a/ssl/test/bssl_shim.cc ++++ b/ssl/test/bssl_shim.cc +@@ -672,7 +672,8 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, + return false; + } + } else if (!config->is_server || config->require_any_client_certificate) { +- if (SSL_get_peer_cert_chain(ssl) == nullptr) { ++ if (!config->raw_public_key_mode && ++ SSL_get_peer_cert_chain(ssl) == nullptr) { + fprintf(stderr, "Received no peer certificate but expected one.\n"); + return false; + } +diff --git a/ssl/test/runner/certificate_tests.go b/ssl/test/runner/certificate_tests.go +index 7f6c4b82d..0007f9048 100644 +--- a/ssl/test/runner/certificate_tests.go ++++ b/ssl/test/runner/certificate_tests.go +@@ -14,7 +14,11 @@ + + package runner + +-import "crypto/x509" ++import ( ++ "crypto/x509" ++ "encoding/base64" ++ "strconv" ++) + + func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool { + certPool := x509.NewCertPool() +@@ -327,6 +331,134 @@ func addCertificateTests() { + } } -+int SSL_CTX_set_nullchain_and_key(SSL_CTX *ctx, -+ EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ return cert_set_key(ctx->cert.get(), privkey, privkey_method); ++func addRawPublicKeyCertificateTests() { ++ const decodeError = ":DECODE_ERROR:" ++ const unknownCertType = ":UNKNOWN_CERTIFICATE_TYPE:" ++ var extValueTests = []struct { ++ serverCertificateTypes []uint8 ++ expectedError string ++ }{ ++ // Explicitly requesting X.509 should be fine. ++ {[]uint8{certificateTypeX509}, ""}, ++ // ... even when mixed with unknown types. ++ {[]uint8{80, certificateTypeX509, 81, 82}, ""}, ++ // ... even when mixed with a request for raw public keys. ++ {[]uint8{certificateTypeRawPublicKey, certificateTypeX509, 81, 82}, ""}, ++ {[]uint8{certificateTypeX509, certificateTypeRawPublicKey, 81, 82}, ""}, ++ // Requesting only unknown certificate types should cause an error. ++ {[]uint8{80, 81, 82}, unknownCertType}, ++ // ... as should requesting a raw public key when the server is configured ++ // for X.509. ++ {[]uint8{certificateTypeRawPublicKey}, unknownCertType}, ++ // Listing no types is an error. ++ {[]uint8{}, decodeError}, ++ } ++ ++ for i, test := range extValueTests { ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server-ExtValue-" + strconv.Itoa(i), ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Bugs: ProtocolBugs{ ++ ServerCertificateTypes: test.serverCertificateTypes, ++ }, ++ }, ++ shouldFail: len(test.expectedError) != 0, ++ expectedError: test.expectedError, ++ }) ++ } ++ ++ // An X.509 client should be rejected by a raw-public-key server. ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server-TLS13X509Client", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ }, ++ shouldFail: true, ++ expectedError: unknownCertType, ++ }) ++ ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ }, ++ shimCertificate: &ecdsaP384Certificate, ++ }) ++ ++ leaf, _ := x509.ParseCertificate(ecdsaP384Certificate.Certificate[0]) ++ base64SPKI := base64.StdEncoding.EncodeToString(leaf.RawSubjectPublicKeyInfo) ++ wrongLeaf, _ := x509.ParseCertificate(ecdsaP256Certificate.Certificate[0]) ++ wrongBase64SPKI := base64.StdEncoding.EncodeToString(wrongLeaf.RawSubjectPublicKeyInfo) ++ ++ for _, ok := range []bool{false, true} { ++ expectedSPKI, suffix, expectedError := base64SPKI, "", "" ++ if !ok { ++ expectedSPKI = wrongBase64SPKI ++ suffix = "-Mismatch" ++ expectedError = ":CERTIFICATE_VERIFY_FAILED:" ++ } ++ ++ testCases = append(testCases, testCase{ ++ testType: clientTest, ++ name: "RawPublicKey-Client" + suffix, ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ "-verify-peer", ++ "-use-custom-verify-callback", ++ "-expect-spki", expectedSPKI, ++ }, ++ shouldFail: !ok, ++ expectedError: expectedError, ++ }) ++ } ++ ++ // Read the server's raw public key in a CompressedCertificate message. ++ testCases = append(testCases, testCase{ ++ testType: clientTest, ++ name: "RawPublicKey-Client-With-Compression", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ CertCompressionAlgs: map[uint16]CertCompressionAlg{ ++ expandingCompressionAlgID: expandingCompression, ++ }, ++ Bugs: ProtocolBugs{ ++ ExpectedCompressedCert: expandingCompressionAlgID, ++ }, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ "-verify-peer", ++ "-use-custom-verify-callback", ++ "-expect-spki", base64SPKI, ++ "-install-cert-compression-algs", ++ }, ++ }) +} + - const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) { - return ctx->cert->chain.get(); + func addRetainOnlySHA256ClientCertTests() { + for _, ver := range tlsVersions { + // Test that enabling +diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go +index 7dbde72c9..f1fb623ea 100644 +--- a/ssl/test/runner/common.go ++++ b/ssl/test/runner/common.go +@@ -140,6 +140,12 @@ func messageTypeToString(typ uint8) string { + return fmt.Sprintf("unknown(%d)", typ) } -diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 58b68e675..384debbd3 100644 ---- a/ssl/ssl_lib.cc -+++ b/ssl/ssl_lib.cc -@@ -137,6 +137,25 @@ - * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - #include ++// TLS certificate type extension values. ++const ( ++ certificateTypeX509 uint8 = 0 ++ certificateTypeRawPublicKey uint8 = 2 ++) ++ + // TLS compression types. + const ( + compressionNone uint8 = 0 +@@ -155,6 +161,7 @@ const ( + extensionUseSRTP uint16 = 14 + extensionALPN uint16 = 16 + extensionSignedCertificateTimestamp uint16 = 18 ++ extensionServerCertificateType uint16 = 20 // RFC7250 + extensionPadding uint16 = 21 + extensionExtendedMasterSecret uint16 = 23 + extensionCompressedCertAlgs uint16 = 27 +@@ -505,6 +512,14 @@ type Config struct { + // If Time is nil, TLS uses time.Now. + Time func() time.Time -@@ -687,6 +706,11 @@ SSL *SSL_new(SSL_CTX *ctx) { - ssl->config->handoff = ctx->handoff; - ssl->quic_method = ctx->quic_method; ++ // useServerRawPublicKeyCertificate indicates, for TLS 1.3 only, that raw ++ // public keys should be used. For servers, the DER-encoded X.509 ++ // SubjectPublicKeyInfo field of Certificates[0].Certificate[0] will be the ++ // CertificateEntry of Certificate messages, not including any ++ // CertificateEntry extensions. For clients, the field should be used to ++ // verify the server's Certificate message. ++ useServerRawPublicKeyCertificate bool ++ + // Credential contains the credential to present to the other side of + // the connection. Server configurations must include this field. + Credential *Credential +@@ -2172,6 +2187,10 @@ type ProtocolBugs struct { + // NewSessionTicket messages to have or not have the resumption_across_names + // flag set. + ExpectResumptionAcrossNames *bool ++ ++ // ServerCertificateTypes, if not nil, contains the contents of the server ++ // certificate types extension sent by a client, or echoed by a server. ++ ServerCertificateTypes []uint8 + } + + func (c *Config) serverInit() { +diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go +index 1c4610815..cce76f47b 100644 +--- a/ssl/test/runner/handshake_client.go ++++ b/ssl/test/runner/handshake_client.go +@@ -557,6 +557,14 @@ func (hs *clientHandshakeState) createClientHello(innerHello *clientHelloMsg, ec + hello.vers = mapClientHelloVersion(maxVersion, c.isDTLS) + } -+ ssl->config->server_certificate_type_list.CopyFrom( -+ ctx->server_certificate_type_list); -+ ssl->config->server_raw_public_key_certificate.CopyFrom( -+ ctx->server_raw_public_key_certificate); ++ if maxVersion >= VersionTLS13 && c.config.useServerRawPublicKeyCertificate { ++ hello.serverCertificateTypes = []uint8{certificateTypeRawPublicKey} ++ } + - if (!ssl->method->ssl_new(ssl.get()) || - !ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) { - return nullptr; -@@ -3249,6 +3273,53 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { - return SSL_set1_groups_list(ssl, curves); ++ if c.config.Bugs.ServerCertificateTypes != nil { ++ hello.serverCertificateTypes = c.config.Bugs.ServerCertificateTypes ++ } ++ + if c.config.Bugs.SendClientVersion != 0 { + hello.vers = c.config.Bugs.SendClientVersion + } +@@ -1345,11 +1353,22 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg any) error { + return errors.New("tls: server certificate unexpectedly did not match trust anchor") + } + +- if err := hs.verifyCertificates(certMsg); err != nil { +- return err ++ ex := encryptedExtensions.extensions ++ if c.config.useServerRawPublicKeyCertificate { ++ if !ex.hasServerCertificateType || ex.serverCertificateType != certificateTypeRawPublicKey { ++ c.sendAlert(alertUnsupportedCertificate) ++ return errors.New("tls: server did not support raw public keys") ++ } ++ if err := hs.verifyRawPublicKeyCertificates(certMsg); err != nil { ++ return err ++ } ++ } else { ++ if err := hs.verifyCertificates(certMsg); err != nil { ++ return err ++ } ++ c.ocspResponse = certMsg.certificates[0].ocspResponse ++ c.sctList = certMsg.certificates[0].sctList + } +- c.ocspResponse = certMsg.certificates[0].ocspResponse +- c.sctList = certMsg.certificates[0].sctList + + certVerifyMsg, err := readHandshakeType[certificateVerifyMsg](c) + if err != nil { +@@ -1854,6 +1873,50 @@ func delegatedCredentialSignedMessage(credBytes []byte, algorithm signatureAlgor + return ret } -+int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx, -+ const uint8_t *raw_public_key, unsigned raw_public_key_len) { -+ if (!ctx->server_raw_public_key_certificate.CopyFrom( -+ MakeConstSpan(raw_public_key, raw_public_key_len))) { -+ return 0; /* Failure */ -+ } ++func (hs *clientHandshakeState) verifyRawPublicKeyCertificates(certMsg *certificateMsg) error { ++ c := hs.c + -+ if (!ctx->server_certificate_type_list.Init(1)) { -+ return 0; -+ } -+ ctx->server_certificate_type_list[0] = TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY; ++ if len(certMsg.certificates) != 1 { ++ c.sendAlert(alertIllegalParameter) ++ return errors.New("tls: incorrect number of certificates") ++ } + -+ return 1; /* Success */ -+} ++ leafSPKI := certMsg.certificates[0].data + -+int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx) { -+ return !ctx->server_raw_public_key_certificate.empty(); -+} ++ if !c.config.InsecureSkipVerify { ++ expectedCert, err := x509.ParseCertificate(c.config.Credential.Certificate[0]) ++ if err != nil { ++ c.sendAlert(alertInternalError) ++ return errors.New("tls: failed to parse configured certificate: " + err.Error()) ++ } ++ expectedSPKI := expectedCert.RawSubjectPublicKeyInfo + -+int SSL_set_server_raw_public_key_certificate(SSL *ssl, -+ const uint8_t *raw_public_key, unsigned raw_public_key_len) { -+ if (!ssl->config) { -+ return 0; /* Failure */ -+ } ++ if !bytes.Equal(expectedSPKI, leafSPKI) { ++ c.sendAlert(alertBadCertificate) ++ return errors.New("tls: raw public key verification failed") ++ } ++ } + -+ if (!ssl->config->server_raw_public_key_certificate.CopyFrom( -+ MakeConstSpan(raw_public_key, raw_public_key_len))) { -+ return 0; -+ } ++ leafPublicKey, err := x509.ParsePKIXPublicKey(leafSPKI) ++ if err != nil { ++ c.sendAlert(alertBadCertificate) ++ return errors.New("tls: failed to parse raw public key certificate from server: " + err.Error()) ++ } + -+ if (!ssl->config->server_certificate_type_list.Init(1)) { -+ return 0; -+ } -+ ssl->config->server_certificate_type_list[0] = -+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY; ++ switch leafPublicKey.(type) { ++ case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: ++ break ++ default: ++ c.sendAlert(alertUnsupportedCertificate) ++ return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", leafPublicKey) ++ } ++ ++ c.peerCertificates = nil ++ hs.peerPublicKey = leafPublicKey + -+ return 1; /* Success */ ++ return nil +} + -+int SSL_has_server_raw_public_key_certificate(SSL *ssl) { -+ if (!ssl->config) { -+ return 0; /* Failure */ + func (hs *clientHandshakeState) verifyCertificates(certMsg *certificateMsg) error { + c := hs.c + +diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go +index d5097f37c..d570a76ab 100644 +--- a/ssl/test/runner/handshake_messages.go ++++ b/ssl/test/runner/handshake_messages.go +@@ -259,9 +259,10 @@ type clientHelloMsg struct { + prefixExtensions []uint16 + // The following fields are only filled in by |unmarshal| and ignored when + // marshaling a new ClientHello. +- echPayloadStart int +- echPayloadEnd int +- rawExtensions []byte ++ echPayloadStart int ++ echPayloadEnd int ++ rawExtensions []byte ++ serverCertificateTypes []uint8 + } + + func (m *clientHelloMsg) marshalKeyShares(bb *cryptobyte.Builder) { +@@ -634,6 +635,14 @@ func (m *clientHelloMsg) marshalBody(hello *cryptobyte.Builder, typ clientHelloT + body: body.BytesOrPanic(), + }) + } ++ if m.serverCertificateTypes != nil { ++ body := cryptobyte.NewBuilder(nil) ++ addUint8LengthPrefixedBytes(body, m.serverCertificateTypes) ++ extensions = append(extensions, extension{ ++ id: extensionServerCertificateType, ++ body: body.BytesOrPanic(), ++ }) ++ } + // The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11 + if len(m.pskIdentities) > 0 { + pskExtension := cryptobyte.NewBuilder(nil) +@@ -1144,6 +1153,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { + } + m.alpsProtocols = append(m.alpsProtocols, string(protocol)) + } ++ case extensionServerCertificateType: ++ if !readUint8LengthPrefixedBytes(&body, &m.serverCertificateTypes) || len(body) != 0 { ++ return false ++ } + case extensionApplicationSettingsOld: + var protocols cryptobyte.String + if !body.ReadUint16LengthPrefixed(&protocols) || len(body) != 0 { +@@ -1597,6 +1610,8 @@ type serverExtensions struct { + hasApplicationSettingsOld bool + echRetryConfigs []byte + trustAnchors [][]byte ++ hasServerCertificateType bool ++ serverCertificateType uint8 + } + + func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) { +@@ -1731,6 +1746,10 @@ func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) { + extensions.AddUint16(extensionEncryptedClientHello) + addUint16LengthPrefixedBytes(extensions, m.echRetryConfigs) + } ++ if m.hasServerCertificateType { ++ extensions.AddUint16(extensionServerCertificateType) ++ addUint16LengthPrefixedBytes(extensions, []byte{m.serverCertificateType}) ++ } + if len(m.trustAnchors) > 0 { + extensions.AddUint16(extensionTrustAnchors) + extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) { +@@ -1797,6 +1816,14 @@ func (m *serverExtensions) unmarshal(data cryptobyte.String, version uint16) boo + return false + } + m.channelIDRequested = true ++ case extensionServerCertificateType: ++ if version < VersionTLS13 { ++ return false ++ } ++ if !body.ReadUint8(&m.serverCertificateType) || len(body) != 0 { ++ return false ++ } ++ m.hasServerCertificateType = true + case extensionExtendedMasterSecret: + if len(body) != 0 { + return false +diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go +index 4e6ae98e4..14353b5e9 100644 +--- a/ssl/test/runner/handshake_server.go ++++ b/ssl/test/runner/handshake_server.go +@@ -28,20 +28,22 @@ import ( + // serverHandshakeState contains details of a server handshake in progress. + // It's discarded once the handshake has completed. + type serverHandshakeState struct { +- c *Conn +- clientHello *clientHelloMsg +- hello *serverHelloMsg +- suite *cipherSuite +- ellipticOk bool +- ecdsaOk bool +- sessionState *sessionState +- finishedHash finishedHash +- masterSecret []byte +- certsFromClient [][]byte +- cert *Credential +- finishedBytes []byte +- echHPKEContext *hpke.Context +- echConfigID uint8 ++ c *Conn ++ clientHello *clientHelloMsg ++ hello *serverHelloMsg ++ suite *cipherSuite ++ ellipticOk bool ++ ecdsaOk bool ++ sessionState *sessionState ++ finishedHash finishedHash ++ masterSecret []byte ++ certsFromClient [][]byte ++ cert *Credential ++ finishedBytes []byte ++ echHPKEContext *hpke.Context ++ echConfigID uint8 ++ hasServerCertificateType bool ++ serverCertificateType uint8 + } + + // serverHandshake performs a TLS handshake as a server. +@@ -983,6 +985,18 @@ func (hs *serverHandshakeState) doTLS13Handshake() error { + encryptedExtensions.extensions.hasEarlyData = true + } + ++ if c.vers >= VersionTLS13 && config.useServerRawPublicKeyCertificate { ++ for _, t := range hs.clientHello.serverCertificateTypes { ++ if t != certificateTypeRawPublicKey { ++ continue ++ } ++ hs.hasServerCertificateType = true ++ hs.serverCertificateType = certificateTypeRawPublicKey ++ encryptedExtensions.extensions.hasServerCertificateType = true ++ encryptedExtensions.extensions.serverCertificateType = certificateTypeRawPublicKey ++ } ++ } ++ + // Resolve ECDHE and compute the handshake secret. + if hs.hello.hasKeyShare { + // Once a curve has been selected and a key share identified, +@@ -1184,6 +1198,17 @@ func (hs *serverHandshakeState) doTLS13Handshake() error { + } + if !config.Bugs.EmptyCertificateList { + for i, certData := range useCert.Certificate { ++ if hs.hasServerCertificateType && ++ hs.serverCertificateType == certificateTypeRawPublicKey { ++ cert, err := x509.ParseCertificate(certData) ++ if err != nil { ++ return fmt.Errorf("tls: failed to parse configured certificate: %s", err.Error()) ++ } ++ certMsg.certificates = append( ++ certMsg.certificates, ++ certificateEntry{data: cert.RawSubjectPublicKeyInfo}) ++ break ++ } + cert := certificateEntry{ + data: certData, + } +diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go +index 57f9cc410..091e44f48 100644 +--- a/ssl/test/runner/runner.go ++++ b/ssl/test/runner/runner.go +@@ -789,7 +789,7 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr + tlsConn = Server(conn, config) + } + } else { +- config.InsecureSkipVerify = true ++ config.InsecureSkipVerify = !config.useServerRawPublicKeyCertificate + if test.protocol == dtls { + tlsConn = DTLSClient(conn, config) + } else { +@@ -2244,6 +2244,7 @@ func main() { + addKeyUpdateTests() + addPAKETests() + addTrustAnchorTests() ++ addRawPublicKeyCertificateTests() + + toAppend, err := convertToSplitHandshakeTests(testCases) + if err != nil { +diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc +index 9a7ee6a68..5b8cd5fa6 100644 +--- a/ssl/test/test_config.cc ++++ b/ssl/test/test_config.cc +@@ -37,6 +37,8 @@ + #include + + #include "../../crypto/internal.h" ++#include "../../crypto/mem_internal.h" ++#include "../../ssl/internal.h" + #include "handshake_util.h" + #include "mock_quic_transport.h" + #include "test_state.h" +@@ -639,6 +641,8 @@ const Flag *FindFlag(const char *name) { + OptionalBoolFalseFlag("-expect-not-resumable-across-names", + &TestConfig::expect_resumable_across_names), + BoolFlag("-no-server-name-ack", &TestConfig::no_server_name_ack), ++ BoolFlag("-raw-public-key-mode", &TestConfig::raw_public_key_mode), ++ Base64Flag("-expect-spki", &TestConfig::expect_spki), + }; + std::sort(ret.begin(), ret.end(), FlagNameComparator{}); + return ret; +@@ -1145,6 +1149,17 @@ static bool CheckVerifyCallback(SSL *ssl) { + fprintf(stderr, "ECH name did not match expected value.\n"); + return false; + } ++ if (!config->expect_spki.empty()) { ++ const EVP_PKEY *pkey = SSL_get0_peer_pubkey(ssl); ++ bssl::ScopedCBB cbb; ++ if (pkey == nullptr || !CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), pkey) || ++ OPENSSL_memcmp(config->expect_spki.data(), CBB_data(cbb.get()), ++ CBB_len(cbb.get())) != 0) { ++ fprintf(stderr, "Incorrect SPKI observed\n"); ++ return false; ++ } + } + + if (config->expect_peer_match_trust_anchor.has_value() && + !!SSL_peer_matched_trust_anchor(ssl) != +@@ -1852,17 +1867,64 @@ static bool InstallCertificate(SSL *ssl) { + return false; + } + ++ const TestConfig *config = GetTestConfig(ssl); + -+ return !ssl->config->server_raw_public_key_certificate.empty(); -+} + if (pkey) { + TestState *test_state = GetTestState(ssl); +- const TestConfig *config = GetTestConfig(ssl); +- if (config->async || config->handshake_hints) { ++ // Install a custom private key if testing asynchronous callbacks, or if ++ // testing handshake hints. In the handshake hints case, we wish to check ++ // that hints only mismatch when allowed. ++ const bool use_private_key_method = ++ config->async || config->handshake_hints; ++ if (use_private_key_method) { + // Install a custom private key if testing asynchronous callbacks, or if + // testing handshake hints. In the handshake hints case, we wish to check + // that hints only mismatch when allowed. + test_state->private_key = std::move(pkey); +- SSL_set_private_key_method(ssl, &g_async_private_key_method); +- } else if (!SSL_use_PrivateKey(ssl, pkey.get())) { +- return false; ++ } ++ ++ if (config->raw_public_key_mode) { ++ bssl::UniquePtr cred(SSL_CREDENTIAL_new_raw_public_key()); ++ if (cred == nullptr) { ++ return false; ++ } ++ ++ if (use_private_key_method) { ++ SSL_CREDENTIAL_set_private_key_method(cred.get(), ++ &g_async_private_key_method); + - namespace fips202205 { ++ bssl::ScopedCBB cbb; ++ if (!CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), test_state->private_key.get())) { ++ return false; ++ } ++ bssl::UniquePtr spki(CRYPTO_BUFFER_new( ++ CBB_data(cbb.get()), CBB_len(cbb.get()), /*pool=*/nullptr)); ++ if (spki == nullptr) { ++ return false; ++ } ++ if (!SSL_CREDENTIAL_set1_spki(cred.get(), spki.get())) { ++ return false; ++ } ++ } else { ++ if (!SSL_CREDENTIAL_set1_private_key(cred.get(), pkey.get())) { ++ return false; ++ } ++ if (!SSL_CREDENTIAL_set1_spki(cred.get(), nullptr)) { ++ return false; ++ } ++ } ++ ++ if (!SSL_add1_credential(ssl, cred.get())) { ++ return false; ++ } ++ return true; ++ } else { ++ if (use_private_key_method) { ++ SSL_set_private_key_method(ssl, &g_async_private_key_method); ++ } else if (!SSL_use_PrivateKey(ssl, pkey.get())) { ++ return false; ++ } + } + } + +@@ -2015,8 +2077,8 @@ bssl::UniquePtr TestConfig::SetupCtx(SSL_CTX *old_ctx) const { + } + + if (async && is_server) { +- // Disable the internal session cache. To test asynchronous session lookup, +- // we use an external session cache. ++ // Disable the internal session cache. To test asynchronous session ++ // lookup, we use an external session cache. + SSL_CTX_set_session_cache_mode( + ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback); +@@ -2338,6 +2400,12 @@ bssl::UniquePtr TestConfig::NewSSL( + if (verify_peer) { + mode = SSL_VERIFY_PEER; + } ++ static const uint8_t kCertificateTypes[] = { ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY}; ++ if (!is_server && raw_public_key_mode && ++ !SSL_set_server_certificate_types(ssl.get(), kCertificateTypes, 1)) { ++ return nullptr; ++ } + if (use_custom_verify_callback) { + SSL_set_custom_verify(ssl.get(), mode, CustomVerifyCallback); + } else if (mode != SSL_VERIFY_NONE) { +@@ -2484,8 +2552,8 @@ bssl::UniquePtr TestConfig::NewSSL( + if (enable_signed_cert_timestamps) { + SSL_enable_signed_cert_timestamps(ssl.get()); + } +- // (D)TLS 1.0 and 1.1 are disabled by default, but the runner expects them to +- // be enabled. ++ // (D)TLS 1.0 and 1.1 are disabled by default, but the runner expects them ++ // to be enabled. + // TODO(davidben): Update the tests to explicitly enable the versions they + // need. + if (!SSL_set_min_proto_version( +@@ -2495,7 +2563,8 @@ bssl::UniquePtr TestConfig::NewSSL( + if (min_version != 0 && !SSL_set_min_proto_version(ssl.get(), min_version)) { + return nullptr; + } +- // TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by default. ++ // TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by ++ // default. + if (is_dtls && max_version == 0 && + !SSL_set_max_proto_version(ssl.get(), DTLS1_3_VERSION)) { + return nullptr; +@@ -2515,7 +2584,8 @@ bssl::UniquePtr TestConfig::NewSSL( + SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once); + } + if (renegotiate_freely || forbid_renegotiation_after_handshake) { +- // |forbid_renegotiation_after_handshake| will disable renegotiation later. ++ // |forbid_renegotiation_after_handshake| will disable renegotiation ++ // later. + SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely); + } + if (renegotiate_ignore) { +diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h +index 9745de035..dc32f8136 100644 +--- a/ssl/test/test_config.h ++++ b/ssl/test/test_config.h +@@ -243,6 +243,8 @@ struct TestConfig { + bool resumption_across_names_enabled = false; + std::optional expect_resumable_across_names; + bool no_server_name_ack = false; ++ bool raw_public_key_mode = false; ++ std::vector expect_spki; + + std::vector handshaker_args; - // (References are to SP 800-52r2): diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc -index 5ab5a1c93..79135613e 100644 +index 257e4c997..951ab7d52 100644 --- a/ssl/tls13_both.cc +++ b/ssl/tls13_both.cc -@@ -11,6 +11,25 @@ - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -197,7 +216,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, - return false; +@@ -198,6 +198,45 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, } -- if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) { -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { + const bool is_leaf = sk_CRYPTO_BUFFER_num(certs.get()) == 0; ++ ++ // Parse out the extensions. ++ SSLExtension status_request( ++ TLSEXT_TYPE_status_request, ++ !ssl->server && hs->config->ocsp_stapling_enabled); ++ SSLExtension sct( ++ TLSEXT_TYPE_certificate_timestamp, ++ !ssl->server && hs->config->signed_cert_timestamps_enabled); ++ SSLExtension trust_anchors( ++ TLSEXT_TYPE_trust_anchors, ++ !ssl->server && is_leaf && ++ hs->config->requested_trust_anchors.has_value()); ++ uint8_t alert = SSL_AD_DECODE_ERROR; ++ if (!ssl_parse_extensions(&extensions, &alert, ++ {&status_request, &sct, &trust_anchors}, ++ /*ignore_unknown=*/false)) { ++ ssl_send_alert(ssl, SSL3_AL_FATAL, alert); ++ return false; ++ } ++ ++ if (!ssl->server && ++ hs->server_certificate_type == TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) { ++ if (pkey) { ++ // Only a single "certificate" is allowed if using raw public keys. ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); ++ return false; ++ } ++ + pkey = UniquePtr(EVP_parse_public_key(&certificate)); -+ if (!pkey) { ++ if (!pkey || CBS_len(&certificate) != 0) { + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); + OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); + return false; + } ++ ++ continue; + } -+ else if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) { ++ + if (is_leaf) { pkey = ssl_cert_parse_pubkey(&certificate); if (!pkey) { - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); -@@ -299,7 +327,10 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, +@@ -228,25 +267,6 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, + return false; + } + +- // Parse out the extensions. +- SSLExtension status_request( +- TLSEXT_TYPE_status_request, +- !ssl->server && hs->config->ocsp_stapling_enabled); +- SSLExtension sct( +- TLSEXT_TYPE_certificate_timestamp, +- !ssl->server && hs->config->signed_cert_timestamps_enabled); +- SSLExtension trust_anchors( +- TLSEXT_TYPE_trust_anchors, +- !ssl->server && is_leaf && +- hs->config->requested_trust_anchors.has_value()); +- uint8_t alert = SSL_AD_DECODE_ERROR; +- if (!ssl_parse_extensions(&extensions, &alert, +- {&status_request, &sct, &trust_anchors}, +- /*ignore_unknown=*/false)) { +- ssl_send_alert(ssl, SSL3_AL_FATAL, alert); +- return false; +- } +- + // All Certificate extensions are parsed, but only the leaf extensions are + // stored. + if (status_request.present) { +@@ -313,7 +333,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, + return false; } - if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { -- if (!allow_anonymous) { -+ if (!allow_anonymous && -+ !(hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == -+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY)) { +- if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { ++ if (!ssl->server && ++ hs->server_certificate_type == TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) { ++ if (!hs->peer_pubkey) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE); ++ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED); ++ return false; ++ } ++ ++ return true; ++ } else if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { + if (!allow_anonymous) { OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED); - return false; -@@ -416,6 +447,20 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { - return false; +@@ -436,13 +465,28 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { + return ssl_add_message_cbb(ssl, cbb.get()); } -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { -+ CBB leaf, extensions; -+ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || -+ !CBB_add_bytes(&leaf, -+ ssl->config->server_raw_public_key_certificate.data(), -+ ssl->config->server_raw_public_key_certificate.size()) || -+ !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { +- assert(hs->credential->UsesX509()); +- CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0); +- CBB leaf, extensions; +- if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || +- !CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf), +- CRYPTO_BUFFER_len(leaf_buf)) || +- !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { ++ CBB leaf; ++ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf)) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; ++ } ++ ++ if (hs->credential->type == SSLCredentialType::kRawPublicKey) { ++ if (!EVP_marshal_public_key(&leaf, cred->pubkey.get())) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; ++ } ++ } else { ++ CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0); ++ if (!CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf), ++ CRYPTO_BUFFER_len(leaf_buf))) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; + } -+ return ssl_add_message_cbb(ssl, cbb.get()); + } + - if (!ssl_has_certificate(hs)) { - return ssl_add_message_cbb(ssl, cbb.get()); ++ CBB extensions; ++ if (!CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; } diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc -index 707cf846b..6916606c2 100644 +index eade4bd66..9de0bea28 100644 --- a/ssl/tls13_server.cc +++ b/ssl/tls13_server.cc -@@ -11,6 +11,25 @@ - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include +@@ -261,6 +261,7 @@ bool ssl_check_tls13_credential_ignoring_issuer(SSL_HANDSHAKE *hs, + uint16_t *out_sigalg) { + switch (cred->type) { + case SSLCredentialType::kX509: ++ case SSLCredentialType::kRawPublicKey: + break; + case SSLCredentialType::kDelegated: + // Check that the peer supports the signature over the delegated +@@ -284,7 +285,34 @@ bool ssl_check_tls13_credential_ignoring_issuer(SSL_HANDSHAKE *hs, -@@ -860,7 +879,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) { - - // Send the server Certificate message, if necessary. - if (!ssl->s3->session_reused) { -- if (!ssl_has_certificate(hs)) { -+ if (!ssl_has_certificate(hs) && -+ !ssl_has_raw_public_key_certificate(hs)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET); - return ssl_hs_error; + static bool check_signature_credential(SSL_HANDSHAKE *hs, + const SSL_CREDENTIAL *cred, +- uint16_t *out_sigalg) { ++ uint16_t *out_sigalg, ++ uint8_t *cert_type) { ++ switch (cred->type) { ++ case SSLCredentialType::kDelegated: ++ case SSLCredentialType::kX509: ++ *cert_type = TLS_CERTIFICATE_TYPE_X509; ++ break; ++ case SSLCredentialType::kRawPublicKey: ++ if (hs->server_certificate_type_list.empty()) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ *cert_type = TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY; ++ break; ++ default: ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ ++ if (!hs->server_certificate_type_list.empty() && ++ std::none_of( ++ hs->server_certificate_type_list.begin(), ++ hs->server_certificate_type_list.end(), ++ [cert_type](const auto &type) { return *cert_type == type; })) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ + return ssl_check_tls13_credential_ignoring_issuer(hs, cred, out_sigalg) && + // Use this credential if it either matches a requested issuer, + // or does not require issuer matching. +@@ -359,9 +387,11 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) { + } + } else { + uint16_t sigalg; +- if (check_signature_credential(hs, cred, &sigalg)) { ++ uint8_t cert_type; ++ if (check_signature_credential(hs, cred, &sigalg, &cert_type)) { + hs->credential = UpRef(cred); + hs->signature_algorithm = sigalg; ++ hs->server_certificate_type = cert_type; + break; + } } +-- +2.40.0 + diff --git a/boring-sys/patches/underscore-wildcards.patch b/boring-sys/patches/underscore-wildcards.patch index 38e406a22..67272a733 100644 --- a/boring-sys/patches/underscore-wildcards.patch +++ b/boring-sys/patches/underscore-wildcards.patch @@ -1,10 +1,34 @@ -https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards +From 2128aa4382ba668e2c4f77bf18da719b2ad0087e Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Fri, 5 Dec 2025 08:19:56 +0100 +Subject: [PATCH] Introduce X509_CHECK_FLAG_UNDERSCORE_WILDCARDS +--- + crypto/x509/v3_utl.cc | 4 +++- + crypto/x509/x509_test.cc | 25 +++++++++++++++++++++++++ + include/openssl/x509.h | 3 +++ + 3 files changed, 31 insertions(+), 1 deletion(-) + +diff --git a/crypto/x509/v3_utl.cc b/crypto/x509/v3_utl.cc +index 015bbcad2..2b9b63430 100644 +--- a/crypto/x509/v3_utl.cc ++++ b/crypto/x509/v3_utl.cc +@@ -740,7 +740,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, + // Check that the part matched by the wildcard contains only + // permitted characters and only matches a single label. + for (p = wildcard_start; p != wildcard_end; ++p) { +- if (!OPENSSL_isalnum(*p) && *p != '-') { ++ if (!OPENSSL_isalnum(*p) && *p != '-' && ++ !(*p == '_' && ++ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { + return 0; + } + } diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc -index 9699b5a75..b0e9b34a6 100644 +index c6ce62dd1..f284f421f 100644 --- a/crypto/x509/x509_test.cc +++ b/crypto/x509/x509_test.cc -@@ -4420,6 +4420,31 @@ TEST(X509Test, Names) { +@@ -5209,6 +5209,31 @@ TEST(X509Test, Names) { /*invalid_emails=*/{}, /*flags=*/0, }, @@ -36,31 +60,20 @@ index 9699b5a75..b0e9b34a6 100644 }; size_t i = 0; -diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c -index bbc82e283..e61e1901d 100644 ---- a/crypto/x509v3/v3_utl.c -+++ b/crypto/x509v3/v3_utl.c -@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, - // Check that the part matched by the wildcard contains only - // permitted characters and only matches a single label. - for (p = wildcard_start; p != wildcard_end; ++p) { -- if (!OPENSSL_isalnum(*p) && *p != '-') { -+ if (!OPENSSL_isalnum(*p) && *p != '-' && -+ !(*p == '_' && -+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { - return 0; - } - } -diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h -index 2a2e02c2e..24e0604b0 100644 ---- a/include/openssl/x509v3.h -+++ b/include/openssl/x509v3.h -@@ -939,6 +939,8 @@ OPENSSL_EXPORT STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); - #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0 - // Skip the subject common name fallback if subjectAltNames is missing. +diff --git a/include/openssl/x509.h b/include/openssl/x509.h +index 926f365f4..cc538cceb 100644 +--- a/include/openssl/x509.h ++++ b/include/openssl/x509.h +@@ -3359,6 +3359,9 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, + // enabled when subjectAltNames is missing. #define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 -+// Allow underscores in DNS wildcard matches. -+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 - OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen, - unsigned int flags, char **peername); ++// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard matches. ++#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 ++ + // X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on |param| to + // |flags|. |flags| should be a combination of |X509_CHECK_FLAG_*| constants. + OPENSSL_EXPORT void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, +-- +2.40.0 + diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index 4ab18d11a..ad9683a08 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -4,7 +4,9 @@ use super::{ Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError, SslVerifyMode, }; +use crate::error::ErrorStack; use crate::ex_data::Index; +use crate::ssl::SslCredentialBuilder; use std::convert::identity; use std::future::Future; use std::pin::Pin; @@ -171,6 +173,21 @@ impl SslContextBuilder { } } +impl SslCredentialBuilder { + /// Configures a custom private key method on the context. + /// + /// A task waker must be set on `Ssl` values associated with the resulting + /// `SslContext` with [`SslRef::set_task_waker`]. + /// + /// See [`AsyncPrivateKeyMethod`] for more details. + pub fn set_async_private_key_method( + &mut self, + method: impl AsyncPrivateKeyMethod, + ) -> Result<(), ErrorStack> { + self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method))) + } +} + impl SslRef { pub fn set_async_custom_verify_callback(&mut self, mode: SslVerifyMode, callback: F) where diff --git a/boring/src/ssl/connector.rs b/boring/src/ssl/connector.rs index 111b45c2a..49f146aac 100644 --- a/boring/src/ssl/connector.rs +++ b/boring/src/ssl/connector.rs @@ -23,19 +23,9 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- "; -enum ContextType { - WithMethod(SslMethod), - #[cfg(feature = "rpk")] - Rpk, -} - #[allow(clippy::inconsistent_digit_grouping)] -fn ctx(ty: ContextType) -> Result { - let mut ctx = match ty { - ContextType::WithMethod(method) => SslContextBuilder::new(method), - #[cfg(feature = "rpk")] - ContextType::Rpk => SslContextBuilder::new_rpk(), - }?; +fn ctx(method: SslMethod) -> Result { + let mut ctx = SslContextBuilder::new(method)?; let mut opts = SslOptions::ALL | SslOptions::NO_COMPRESSION @@ -77,7 +67,7 @@ impl SslConnector { /// /// The default configuration is subject to change, and is currently derived from Python. pub fn builder(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_default_verify_paths()?; ctx.set_cipher_list( "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", @@ -87,17 +77,6 @@ impl SslConnector { Ok(SslConnectorBuilder(ctx)) } - /// Creates a new builder for TLS connections with raw public key. - #[cfg(feature = "rpk")] - pub fn rpk_builder() -> Result { - let mut ctx = ctx(ContextType::Rpk)?; - ctx.set_cipher_list( - "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", - )?; - - Ok(SslConnectorBuilder(ctx)) - } - /// Initiates a client-side TLS session on a stream. /// /// The domain is used for SNI and hostname verification. @@ -224,13 +203,7 @@ impl ConnectConfiguration { self.ssl.set_hostname(domain)?; } - #[cfg(feature = "rpk")] - let verify_hostname = !self.ssl.ssl_context().is_rpk() && self.verify_hostname; - - #[cfg(not(feature = "rpk"))] - let verify_hostname = self.verify_hostname; - - if verify_hostname { + if self.verify_hostname { setup_verify_hostname(&mut self.ssl, domain)?; } @@ -292,21 +265,6 @@ impl DerefMut for ConnectConfiguration { pub struct SslAcceptor(SslContext); impl SslAcceptor { - /// Creates a new builder configured to connect to clients that support Raw Public Keys. - #[cfg(feature = "rpk")] - pub fn rpk() -> Result { - let mut ctx = ctx(ContextType::Rpk)?; - ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); - let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; - ctx.set_tmp_dh(&dh)?; - ctx.set_cipher_list( - "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\ - ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ - DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" - )?; - Ok(SslAcceptorBuilder(ctx)) - } - /// Creates a new builder configured to connect to non-legacy clients. This should generally be /// considered a reasonable default choice. /// @@ -315,7 +273,7 @@ impl SslAcceptor { /// /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS pub fn mozilla_intermediate_v5(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; ctx.set_tmp_dh(&dh)?; @@ -336,7 +294,7 @@ impl SslAcceptor { /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS // FIXME remove in next major version pub fn mozilla_intermediate(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE); ctx.set_options(SslOptions::NO_TLSV1_3); let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; @@ -362,7 +320,7 @@ impl SslAcceptor { /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS // FIXME remove in next major version pub fn mozilla_modern(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options( SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1, ); diff --git a/boring/src/ssl/error.rs b/boring/src/ssl/error.rs index 5acad8200..2209c503a 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -249,8 +249,7 @@ fn fmt_mid_handshake_error( f: &mut fmt::Formatter, prefix: &str, ) -> fmt::Result { - #[cfg(feature = "rpk")] - if s.ssl().ssl_context().is_rpk() { + if !s.ssl().ssl_context().has_x509_support() { write!(f, "{}", prefix)?; return write!(f, " {}", s.error()); } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 78851c5f4..8914b5b0b 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -83,6 +83,8 @@ use crate::error::ErrorStack; use crate::ex_data::Index; use crate::hmac::HmacCtxRef; use crate::nid::Nid; +#[cfg(feature = "rpk")] +use crate::pkey::Public; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use crate::ssl::bio::BioMethod; @@ -243,59 +245,87 @@ bitflags! { /// A type specifying the kind of protocol an `SslContext` will speak. #[derive(Copy, Clone)] -pub struct SslMethod(*const ffi::SSL_METHOD); +pub struct SslMethod { + ptr: *const ffi::SSL_METHOD, + is_x509_method: bool, +} impl SslMethod { /// Support all versions of the TLS protocol. #[corresponds(TLS_method)] #[must_use] - pub fn tls() -> SslMethod { - unsafe { SslMethod(TLS_method()) } + pub fn tls() -> Self { + unsafe { + Self { + ptr: ffi::TLS_method(), + is_x509_method: true, + } + } } - /// Same as `tls`, but doesn't create X509 for certificates. - #[cfg(feature = "rpk")] - pub fn tls_with_buffer() -> SslMethod { - unsafe { SslMethod(ffi::TLS_with_buffers_method()) } + /// Same as `tls`, but doesn't create X.509 for certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + #[must_use] + pub unsafe fn tls_with_buffer() -> Self { + unsafe { + Self { + ptr: ffi::TLS_with_buffers_method(), + is_x509_method: false, + } + } } /// Support all versions of the DTLS protocol. #[corresponds(DTLS_method)] #[must_use] - pub fn dtls() -> SslMethod { - unsafe { SslMethod(DTLS_method()) } - } - - /// Support all versions of the TLS protocol, explicitly as a client. - #[corresponds(TLS_client_method)] - #[must_use] - pub fn tls_client() -> SslMethod { - unsafe { SslMethod(TLS_client_method()) } - } - - /// Support all versions of the TLS protocol, explicitly as a server. - #[corresponds(TLS_server_method)] - #[must_use] - pub fn tls_server() -> SslMethod { - unsafe { SslMethod(TLS_server_method()) } + pub fn dtls() -> Self { + unsafe { + Self { + ptr: ffi::DTLS_method(), + is_x509_method: true, + } + } } /// Constructs an `SslMethod` from a pointer to the underlying OpenSSL value. /// + /// This method assumes that the `SslMethod` is not configured for X.509 + /// certificates. The user can call `SslMethod::assume_x509_method` + /// to change that. + /// /// # Safety /// /// The caller must ensure the pointer is valid. #[corresponds(TLS_server_method)] #[must_use] pub unsafe fn from_ptr(ptr: *const ffi::SSL_METHOD) -> SslMethod { - SslMethod(ptr) + SslMethod { + ptr, + is_x509_method: false, + } + } + + /// Assumes that this `SslMethod` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.is_x509_method = true; } /// Returns a pointer to the underlying OpenSSL value. #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] pub fn as_ptr(&self) -> *const ffi::SSL_METHOD { - self.0 + self.ptr } } @@ -447,8 +477,9 @@ static SSL_INDEXES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static SESSION_CTX_INDEX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); -#[cfg(feature = "rpk")] -static RPK_FLAG_INDEX: LazyLock> = +static SSL_CREDENTIAL_INDEXES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static X509_FLAG_INDEX: LazyLock> = LazyLock::new(|| SslContext::new_ex_index().unwrap()); /// An error returned from the SNI callback. @@ -880,67 +911,11 @@ impl Ssl3AlertLevel { pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL); } -#[cfg(feature = "rpk")] -extern "C" fn rpk_verify_failure_callback( - _ssl: *mut ffi::SSL, - _out_alert: *mut u8, -) -> ffi::ssl_verify_result_t { - // Always verify the peer. - ffi::ssl_verify_result_t::ssl_verify_invalid -} - /// A builder for `SslContext`s. pub struct SslContextBuilder { ctx: SslContext, /// If it's not shared, it can be exposed as mutable has_shared_cert_store: bool, - #[cfg(feature = "rpk")] - is_rpk: bool, -} - -#[cfg(feature = "rpk")] -impl SslContextBuilder { - /// Creates a new `SslContextBuilder` to be used with Raw Public Key. - #[corresponds(SSL_CTX_new)] - pub fn new_rpk() -> Result { - unsafe { - init(); - let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?; - - let mut builder = SslContextBuilder::from_ptr(ctx); - builder.is_rpk = true; - builder.set_ex_data(*RPK_FLAG_INDEX, true); - - Ok(builder) - } - } - - /// Sets raw public key certificate in DER format. - pub fn set_rpk_certificate(&mut self, cert: &[u8]) -> Result<(), ErrorStack> { - unsafe { - cvt(ffi::SSL_CTX_set_server_raw_public_key_certificate( - self.as_ptr(), - cert.as_ptr(), - cert.len() as u32, - )) - .map(|_| ()) - } - } - - /// Sets RPK null chain private key. - pub fn set_null_chain_private_key(&mut self, key: &PKeyRef) -> Result<(), ErrorStack> - where - T: HasPrivate, - { - unsafe { - cvt(ffi::SSL_CTX_set_nullchain_and_key( - self.as_ptr(), - key.as_ptr(), - ptr::null_mut(), - )) - .map(|_| ()) - } - } } impl SslContextBuilder { @@ -950,31 +925,45 @@ impl SslContextBuilder { unsafe { init(); let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?; - Ok(SslContextBuilder::from_ptr(ctx)) + let mut builder = SslContextBuilder::from_ptr(ctx); + + if method.is_x509_method { + builder.ctx.assume_x509(); + } + + Ok(builder) } } /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. /// - #[cfg_attr( - feature = "rpk", - doc = "Keeps previous RPK state. Use `new_rpk()` to enable RPK." - )] + /// This method can find out whether `ctx` is configured for X.509 certificates + /// if `ctx` was itself a context created by this crate. If it was created by + /// other means and it supports X.509 certificates, the use can call + /// `SslContextBuilder::assume_x509`. /// /// # Safety /// /// The caller must ensure that the pointer is valid and uniquely owned by the builder. /// The context must own its cert store exclusively. - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { - let ctx = SslContext::from_ptr(ctx); - SslContextBuilder { - #[cfg(feature = "rpk")] - is_rpk: ctx.is_rpk(), + pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> Self { + Self { + ctx: SslContext::from_ptr(ctx), has_shared_cert_store: false, - ctx, } } + /// Assumes that this `SslContextBuilder` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.ctx.assume_x509(); + } + /// Returns a pointer to the raw OpenSSL value. #[must_use] pub fn as_ptr(&self) -> *mut ffi::SSL_CTX { @@ -1005,8 +994,7 @@ impl SslContextBuilder { where F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); // NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since // `set_verify_callback` does? @@ -1027,8 +1015,7 @@ impl SslContextBuilder { /// Configures the certificate verification method for new connections. #[corresponds(SSL_CTX_set_verify)] pub fn set_verify(&mut self, mode: SslVerifyMode) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None); @@ -1056,8 +1043,7 @@ impl SslContextBuilder { where F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); @@ -1084,9 +1070,6 @@ impl SslContextBuilder { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); - unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); ffi::SSL_CTX_set_custom_verify( @@ -1166,8 +1149,7 @@ impl SslContextBuilder { + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); @@ -1180,8 +1162,7 @@ impl SslContextBuilder { /// If the peer's certificate chain is longer than this value, verification will fail. #[corresponds(SSL_CTX_set_verify_depth)] pub fn set_verify_depth(&mut self, depth: u32) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int); @@ -1191,8 +1172,7 @@ impl SslContextBuilder { /// Sets a custom certificate store for verifying peer certificates. #[corresponds(SSL_CTX_set0_verify_cert_store)] pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt( @@ -1209,8 +1189,7 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set_cert_store)] #[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")] pub fn set_cert_store(&mut self, cert_store: X509Store) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); self.has_shared_cert_store = false; unsafe { @@ -1221,8 +1200,7 @@ impl SslContextBuilder { /// Replaces the context's certificate store, and allows mutating the store afterwards. #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); self.has_shared_cert_store = false; unsafe { @@ -1235,8 +1213,7 @@ impl SslContextBuilder { /// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic. #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); self.has_shared_cert_store = true; unsafe { @@ -1283,8 +1260,7 @@ impl SslContextBuilder { /// if present, or defaults specified at OpenSSL build time otherwise. #[corresponds(SSL_CTX_set_default_verify_paths)] pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) } } @@ -1294,8 +1270,7 @@ impl SslContextBuilder { /// The file should contain a sequence of PEM-formatted CA certificates. #[corresponds(SSL_CTX_load_verify_locations)] pub fn set_ca_file>(&mut self, file: P) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes()) .map_err(ErrorStack::internal_error)?; @@ -1315,8 +1290,7 @@ impl SslContextBuilder { /// as trusted by this method. #[corresponds(SSL_CTX_set_client_CA_list)] pub fn set_client_ca_list(&mut self, list: Stack) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr()); @@ -1328,8 +1302,7 @@ impl SslContextBuilder { /// requesting client-side TLS authentication. #[corresponds(SSL_CTX_add_client_CA)] pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) } } @@ -1366,8 +1339,7 @@ impl SslContextBuilder { file: P, file_type: SslFiletype, ) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes()) .map_err(ErrorStack::internal_error)?; @@ -1416,8 +1388,7 @@ impl SslContextBuilder { /// `set_certificate` to a trusted root. #[corresponds(SSL_CTX_add_extra_chain_cert)] pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?; @@ -1728,8 +1699,7 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_get_cert_store)] #[must_use] pub fn cert_store(&self) -> &X509StoreBuilderRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } @@ -1745,8 +1715,7 @@ impl SslContextBuilder { /// #[corresponds(SSL_CTX_get_cert_store)] pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); assert!( !self.has_shared_cert_store, @@ -1940,7 +1909,7 @@ impl SslContextBuilder { /// Sets or overwrites the extra data at the specified index. /// /// This can be used to provide data to callbacks registered with the context. Use the - /// `Ssl::new_ex_index` method to create an `Index`. + /// `SslContext::new_ex_index` method to create an `Index`. /// /// Any previous value will be returned and replaced by the new one. #[corresponds(SSL_CTX_set_ex_data)] @@ -2049,6 +2018,36 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } } + /// Adds a credential. + #[corresponds(SSL_CTX_add1_credential)] + pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CTX_add1_credential( + self.as_ptr(), + credential.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Sets the list of server certificate types that clients attached to this context + /// can process. + #[corresponds(SSL_CTX_set_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn set_server_certificate_types( + &mut self, + types: &[CertificateType], + ) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CTX_set_server_certificate_types( + self.as_ptr(), + types.as_ptr() as *const u8, + types.len(), + )) + .map(|_| ()) + } + } + /// Consumes the builder, returning a new `SslContext`. #[must_use] pub fn build(self) -> SslContext { @@ -2152,8 +2151,7 @@ impl SslContextRef { #[corresponds(SSL_CTX_get0_certificate)] #[must_use] pub fn certificate(&self) -> Option<&X509Ref> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); unsafe { let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr()); @@ -2183,8 +2181,7 @@ impl SslContextRef { #[corresponds(SSL_CTX_get_cert_store)] #[must_use] pub fn cert_store(&self) -> &X509StoreRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } @@ -2294,17 +2291,34 @@ impl SslContextRef { #[corresponds(SSL_CTX_get_verify_mode)] #[must_use] pub fn verify_mode(&self) -> SslVerifyMode { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) }; SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode") } - /// Returns `true` if context was created for Raw Public Key verification - #[cfg(feature = "rpk")] - pub fn is_rpk(&self) -> bool { - self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default() + /// Assumes that this `SslContext` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.replace_ex_data(*X509_FLAG_INDEX, true); + } + + /// Returns `true` if context is configured for X.509 certificates. + pub fn has_x509_support(&self) -> bool { + self.ex_data(*X509_FLAG_INDEX).copied().unwrap_or_default() + } + + #[track_caller] + fn check_x509(&self) { + assert!( + self.has_x509_support(), + "This context is not configured for X.509 certificates" + ); } /// Registers a list of ECH keys on the context. This list should contain new and old @@ -2315,6 +2329,26 @@ impl SslContextRef { pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } } + + /// Returns the list of server certificate types. + #[corresponds(SSL_CTX_get0_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { + let mut types = ptr::null(); + let mut types_len = 0; + unsafe { + ffi::SSL_CTX_get0_server_certificate_types(self.as_ptr(), &mut types, &mut types_len); + + if types_len == 0 { + return None; + } + + Some(slice::from_raw_parts( + types as *const CertificateType, + types_len, + )) + } + } } /// Error returned by the callback to get a session when operation @@ -2778,20 +2812,20 @@ impl Ssl { where S: Read + Write, { - #[cfg(feature = "rpk")] - { - let ctx = self.ssl_context(); - - if ctx.is_rpk() { - unsafe { - ffi::SSL_CTX_set_custom_verify( - ctx.as_ptr(), - SslVerifyMode::PEER.bits(), - Some(rpk_verify_failure_callback), - ); - } - } - } + // #[cfg(feature = "rpk")] + // { + // let ctx = self.ssl_context(); + + // if ctx.is_rpk() { + // unsafe { + // ffi::SSL_CTX_set_custom_verify( + // ctx.as_ptr(), + // SslVerifyMode::PEER.bits(), + // Some(rpk_verify_failure_callback), + // ); + // } + // } + // } SslStreamBuilder::new(self, stream).setup_accept() } @@ -2821,8 +2855,7 @@ impl fmt::Debug for SslRef { builder.field("state", &self.state_string_long()); - #[cfg(feature = "rpk")] - if !self.ssl_context().is_rpk() { + if self.ssl_context().has_x509_support() { builder.field("verify_result", &self.verify_result()); } @@ -2908,11 +2941,7 @@ impl SslRef { /// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify #[corresponds(SSL_set_verify)] pub fn set_verify(&mut self, mode: SslVerifyMode) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) } } @@ -2922,11 +2951,7 @@ impl SslRef { /// If the peer's certificate chain is longer than this value, verification will fail. #[corresponds(SSL_set_verify_depth)] pub fn set_verify_depth(&mut self, depth: u32) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_verify_depth(self.as_ptr(), depth as c_int); @@ -2937,11 +2962,7 @@ impl SslRef { #[corresponds(SSL_get_verify_mode)] #[must_use] pub fn verify_mode(&self) -> SslVerifyMode { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) }; SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode") @@ -2968,11 +2989,7 @@ impl SslRef { where F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { // this needs to be in an Arc since the callback can register a new callback! @@ -2988,11 +3005,7 @@ impl SslRef { /// Sets a custom certificate store for verifying peer certificates. #[corresponds(SSL_set0_verify_cert_store)] pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?; @@ -3010,11 +3023,7 @@ impl SslRef { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { // this needs to be in an Arc since the callback can register a new callback! @@ -3145,11 +3154,7 @@ impl SslRef { #[corresponds(SSL_get_peer_certificate)] #[must_use] pub fn peer_certificate(&self) -> Option { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_peer_certificate(self.as_ptr()); @@ -3168,11 +3173,7 @@ impl SslRef { #[corresponds(SSL_get_peer_cert_chain)] #[must_use] pub fn peer_cert_chain(&self) -> Option<&StackRef> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr()); @@ -3188,11 +3189,7 @@ impl SslRef { #[corresponds(SSL_get_certificate)] #[must_use] pub fn certificate(&self) -> Option<&X509Ref> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_certificate(self.as_ptr()); @@ -3431,6 +3428,12 @@ impl SslRef { /// It is most commonly used in the Server Name Indication (SNI) callback. #[corresponds(SSL_set_SSL_CTX)] pub fn set_ssl_context(&mut self, ctx: &SslContextRef) -> Result<(), ErrorStack> { + assert_eq!( + self.ssl_context().has_x509_support(), + ctx.has_x509_support(), + "X.509 certificate support in old and new contexts doesn't match", + ); + unsafe { cvt_p(ffi::SSL_set_SSL_CTX(self.as_ptr(), ctx.as_ptr())).map(|_| ()) } } @@ -3447,11 +3450,7 @@ impl SslRef { /// Returns a mutable reference to the X509 verification configuration. #[corresponds(SSL_get0_param)] pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) } } @@ -3464,11 +3463,7 @@ impl SslRef { /// Returns the certificate verification result. #[corresponds(SSL_get_verify_result)] pub fn verify_result(&self) -> X509VerifyResult { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) } } @@ -3724,11 +3719,7 @@ impl SslRef { /// as trusted by this method. #[corresponds(SSL_set_client_CA_list)] pub fn set_client_ca_list(&mut self, list: Stack) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) } mem::forget(list); @@ -3850,6 +3841,75 @@ impl SslRef { pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } } + + /// Adds a credential. + #[corresponds(SSL_add1_credential)] + pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { + unsafe { cvt_0i(ffi::SSL_add1_credential(self.as_ptr(), credential.as_ptr())).map(|_| ()) } + } + + /// Returns the public key sent by the other peer, `None` if there is no ongoing handshake. + #[corresponds(SSL_get0_peer_pubkey)] + #[cfg(feature = "rpk")] + pub fn peer_pubkey(&self) -> Option<&PKeyRef> { + unsafe { + let pubkey = ffi::SSL_get0_peer_pubkey(self.as_ptr()); + + if pubkey.is_null() { + return None; + } + + Some(PKeyRef::from_ptr(pubkey as *mut _)) + } + } + + /// Sets the list of server certificate types that clients attached to this `Ssl` + /// can process. + #[corresponds(SSL_set_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn set_server_certificate_types( + &mut self, + types: &[CertificateType], + ) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_set_server_certificate_types( + self.as_ptr(), + types.as_ptr() as *const u8, + types.len(), + )) + .map(|_| ()) + } + } + + /// Returns the list of server certificate types. + #[corresponds(SSL_get0_server_certificate_types)] + #[must_use] + #[cfg(feature = "rpk")] + pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { + let mut types = ptr::null(); + let mut types_len = 0; + unsafe { + ffi::SSL_get0_server_certificate_types(self.as_ptr(), &mut types, &mut types_len); + + if types_len == 0 { + return None; + } + + Some(slice::from_raw_parts( + types as *const CertificateType, + types_len, + )) + } + } + + /// Returns the server certificate type selected by the server, or `CertificateType::X509` + /// if there is no handshake. + #[corresponds(SSL_get_server_certificate_type_selected)] + #[must_use] + #[cfg(feature = "rpk")] + pub fn selected_server_certificate_type(&self) -> CertificateType { + unsafe { CertificateType(ffi::SSL_get_server_certificate_type_selected(self.as_ptr())) } + } } /// An SSL stream midway through the handshake process. @@ -4392,6 +4452,264 @@ impl SslStreamBuilder { } } +/// A credential. +pub struct SslCredential(NonNull); + +unsafe impl ForeignType for SslCredential { + type CType = ffi::SSL_CREDENTIAL; + type Ref = SslCredentialRef; + + #[inline] + unsafe fn from_ptr(ptr: *mut ffi::SSL_CREDENTIAL) -> Self { + Self(NonNull::new_unchecked(ptr)) + } + + #[inline] + fn as_ptr(&self) -> *mut ffi::SSL_CREDENTIAL { + self.0.as_ptr() + } +} + +impl Drop for SslCredential { + fn drop(&mut self) { + unsafe { ffi::SSL_CREDENTIAL_free(self.as_ptr()) } + } +} + +impl Deref for SslCredential { + type Target = SslCredentialRef; + + fn deref(&self) -> &SslCredentialRef { + unsafe { SslCredentialRef::from_ptr(self.as_ptr()) } + } +} + +impl SslCredential { + /// Create a credential suitable for a handshake using a raw public key. + #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] + #[cfg(feature = "rpk")] + pub fn new_raw_public_key() -> Result { + unsafe { + Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( + ffi::SSL_CREDENTIAL_new_raw_public_key(), + )?))) + } + } + + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_C_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *SSL_CREDENTIAL_INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } +} + +/// Reference to an [`SslCredential`]. +/// +/// [`SslCredential`]: struct.SslCredential.html +pub struct SslCredentialRef(Opaque); + +impl SslCredentialRef { + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + #[must_use] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + unsafe fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&mut *(data as *mut T)) + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + unsafe fn set_ex_data(&mut self, index: Index, data: T) { + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + unsafe fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + if let Some(old) = self.ex_data_mut(index) { + return Some(mem::replace(old, data)); + } + + self.set_ex_data(index, data); + + None + } +} + +unsafe impl Send for SslCredentialRef {} +unsafe impl Sync for SslCredentialRef {} + +unsafe impl ForeignTypeRef for SslCredentialRef { + type CType = ffi::SSL_CREDENTIAL; +} + +pub struct SslCredentialBuilder(SslCredential); + +impl SslCredentialBuilder { + /// Sets the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Note that if this method is called multiple times with the same index, any previous + /// value stored in the `SslCredentialBuilder` will be leaked. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn set_ex_data(&mut self, index: Index, data: T) { + unsafe { + self.as_mut().set_ex_data(index, data); + } + } + + /// Sets or overwrites the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Any previous value will be returned and replaced by the new one. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + unsafe { self.as_mut().replace_ex_data(index, data) } + } + + // Sets the private key of the credential. + #[corresponds(SSL_CREDENTIAL_set1_private_key)] + pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( + self.0.as_ptr(), + private_key.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures a custom private key method on the credential. + /// + /// See [`PrivateKeyMethod`] for more details. + #[corresponds(SSL_CREDENTIAL_set_private_key_method)] + pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> + where + M: PrivateKeyMethod, + { + unsafe { + let this = self.as_mut(); + + this.replace_ex_data(SslCredential::cached_ex_index::(), method); + + cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( + this.as_ptr(), + &ffi::SSL_PRIVATE_KEY_METHOD { + sign: Some(callbacks::raw_sign::), + decrypt: Some(callbacks::raw_decrypt::), + complete: Some(callbacks::raw_complete::), + }, + )) + .map(|_| ()) + } + } + + // Sets the SPKI of the raw public key credential. + // + // If `spki` is `None`, the SPKI is extracted from the credential's private key. + #[corresponds(SSL_CREDENTIAL_set1_spki)] + #[cfg(feature = "rpk")] + pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { + unsafe { + let spki = spki + .map(|spki| { + cvt_p(ffi::CRYPTO_BUFFER_new( + spki.as_ptr(), + spki.len(), + ptr::null_mut(), + )) + }) + .transpose()? + .unwrap_or(ptr::null_mut()); + + let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)); + + if spki.is_null() { + ffi::CRYPTO_BUFFER_free(spki); + } + + ret?; + + Ok(()) + } + } + + unsafe fn as_mut(&mut self) -> &mut SslCredentialRef { + SslCredentialRef::from_ptr_mut(self.0.as_ptr()) + } + + pub fn build(self) -> SslCredential { + self.0 + } +} + +/// A certificate type. +#[cfg(feature = "rpk")] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct CertificateType(u8); + +#[cfg(feature = "rpk")] +impl CertificateType { + /// A X.509 certificate. + pub const X509: Self = Self(ffi::TLS_CERTIFICATE_TYPE_X509 as u8); + + /// A raw public key. + pub const RAW_PUBLIC_KEY: Self = Self(ffi::TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY as u8); +} + /// The result of a shutdown request. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ShutdownResult { @@ -4516,8 +4834,6 @@ pub trait CertificateCompressor: Send + Sync + 'static { use crate::ffi::{SSL_CTX_up_ref, SSL_SESSION_get_master_key, SSL_SESSION_up_ref, SSL_is_server}; -use crate::ffi::{DTLS_method, TLS_client_method, TLS_method, TLS_server_method}; - use std::sync::Once; unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { @@ -4539,3 +4855,13 @@ unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } + +unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { + // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); + }); + + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) +} diff --git a/tokio-boring/examples/simple-async.rs b/tokio-boring/examples/simple-async.rs index f4a69a1c9..c51368bd1 100644 --- a/tokio-boring/examples/simple-async.rs +++ b/tokio-boring/examples/simple-async.rs @@ -6,7 +6,7 @@ async fn main() -> anyhow::Result<()> { let listener = TcpListener::bind("127.0.0.1:8080").await?; let (tcp_stream, _addr) = listener.accept().await?; - let server = ssl::SslMethod::tls_server(); + let server = ssl::SslMethod::tls(); let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?; ssl_builder.set_default_verify_paths()?; ssl_builder.set_verify(ssl::SslVerifyMode::PEER); diff --git a/tokio-boring/tests/rpk.rs b/tokio-boring/tests/rpk.rs index 5492767ab..b39f9d53c 100644 --- a/tokio-boring/tests/rpk.rs +++ b/tokio-boring/tests/rpk.rs @@ -1,109 +1,151 @@ -#[cfg(feature = "rpk")] -mod test_rpk { - use boring::pkey::PKey; - use boring::ssl::{SslAcceptor, SslConnector}; - use futures::future; - use std::future::Future; - use std::net::SocketAddr; - use std::pin::Pin; - use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; - use tokio::net::{TcpListener, TcpStream}; - use tokio_boring::{HandshakeError, SslStream}; - - fn create_server() -> ( - impl Future, HandshakeError>>, - SocketAddr, - ) { - let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); - - listener.set_nonblocking(true).unwrap(); - - let listener = TcpListener::from_std(listener).unwrap(); - let addr = listener.local_addr().unwrap(); +#![cfg(feature = "rpk")] + +use boring::pkey::PKey; +use boring::ssl::{ + CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslMethod, SslVerifyError, + SslVerifyMode, +}; +use futures::future; +use std::future::Future; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::OnceLock; +use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_boring::{HandshakeError, SslStream}; + +fn create_server() -> ( + impl Future, HandshakeError>>, + SocketAddr, +) { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + + listener.set_nonblocking(true).unwrap(); + + let listener = TcpListener::from_std(listener).unwrap(); + let addr = listener.local_addr().unwrap(); + + let server = async move { + let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap(); + let private_key = + PKey::private_key_from_pem(&std::fs::read("tests/key.pem").unwrap()).unwrap(); + let spki = std::fs::read("tests/pubkey.der").unwrap(); + + acceptor + .add_credential({ + let mut cred = SslCredential::new_raw_public_key().unwrap(); + + cred.set_private_key(&private_key).unwrap(); + cred.set_spki_bytes(Some(&spki)).unwrap(); + + &cred.build() + }) + .unwrap(); + + let acceptor = acceptor.build(); + + let stream = listener.accept().await.unwrap().0; + + tokio_boring::accept(&acceptor, stream).await + }; + + (server, addr) +} - let server = async move { - let mut acceptor = SslAcceptor::rpk().unwrap(); - let pkey = std::fs::read("tests/key.pem").unwrap(); - let pkey = PKey::private_key_from_pem(&pkey).unwrap(); - let cert = std::fs::read("tests/pubkey.der").unwrap(); +async fn connect( + addr: SocketAddr, + spki_path: &str, + is_ok_cell: &Arc>, +) -> Result, HandshakeError> { + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); + let spki = PKey::public_key_from_der(&std::fs::read(spki_path).unwrap()).unwrap(); + let is_ok_cell = Arc::clone(is_ok_cell); - acceptor.set_rpk_certificate(&cert).unwrap(); - acceptor.set_null_chain_private_key(&pkey).unwrap(); + connector + .set_server_certificate_types(&[CertificateType::RAW_PUBLIC_KEY]) + .unwrap(); - let acceptor = acceptor.build(); + connector.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| { + let public_key = ssl + .peer_pubkey() + .ok_or(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))?; - let stream = listener.accept().await.unwrap().0; + let is_ok = public_key.public_eq(&spki); - tokio_boring::accept(&acceptor, stream).await - }; + is_ok_cell.set(is_ok).unwrap(); - (server, addr) - } + if !is_ok { + return Err(SslVerifyError::Invalid(SslAlert::BAD_CERTIFICATE)); + } - #[tokio::test] - async fn server_rpk() { - let (stream, addr) = create_server(); + Ok(()) + }); - let server = async { - let mut stream = stream.await.unwrap(); - let mut buf = [0; 4]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(&buf, b"asdf"); + let config = connector.build().configure().unwrap(); - stream.write_all(b"jkl;").await.unwrap(); + tokio_boring::connect( + config, + "localhost", + TcpStream::connect(&addr).await.unwrap(), + ) + .await +} - future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx)) - .await - .unwrap(); - }; +#[tokio::test] +async fn server_rpk() { + let (stream, addr) = create_server(); - let client = async { - let mut connector = SslConnector::rpk_builder().unwrap(); - let cert = std::fs::read("tests/pubkey.der").unwrap(); + let server = async { + let mut stream = stream.await.unwrap(); + let mut buf = [0; 4]; + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, b"asdf"); - connector.set_rpk_certificate(&cert).unwrap(); - let config = connector.build().configure().unwrap(); + stream.write_all(b"jkl;").await.unwrap(); - let stream = TcpStream::connect(&addr).await.unwrap(); - let mut stream = tokio_boring::connect(config, "localhost", stream) - .await - .unwrap(); + future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx)) + .await + .unwrap(); + }; - stream.write_all(b"asdf").await.unwrap(); + let client = async { + let is_ok_cell = Arc::new(OnceLock::new()); + let mut stream = connect(addr, "tests/pubkey.der", &is_ok_cell) + .await + .unwrap(); - let mut buf = vec![]; - stream.read_to_end(&mut buf).await.unwrap(); - assert_eq!(buf, b"jkl;"); - }; + assert!(is_ok_cell.get().unwrap()); - future::join(server, client).await; - } + stream.write_all(b"asdf").await.unwrap(); - #[tokio::test] - async fn client_rpk_unknown_cert() { - let (stream, addr) = create_server(); + let mut buf = vec![]; + stream.read_to_end(&mut buf).await.unwrap(); + assert_eq!(buf, b"jkl;"); + }; - let server = async { - assert!(stream.await.is_err()); - }; + future::join(server, client).await; +} - let client = async { - let mut connector = SslConnector::rpk_builder().unwrap(); - let cert = std::fs::read("tests/pubkey2.der").unwrap(); +#[tokio::test] +async fn client_rpk_unknown_cert() { + let (stream, addr) = create_server(); - connector.set_rpk_certificate(&cert).unwrap(); - let config = connector.build().configure().unwrap(); + let server = async { + assert!(stream.await.is_err()); + }; - let stream = TcpStream::connect(&addr).await.unwrap(); + let client = async { + let is_ok_cell = Arc::new(OnceLock::new()); + let err = connect(addr, "tests/pubkey2.der", &is_ok_cell) + .await + .unwrap_err(); - let err = tokio_boring::connect(config, "localhost", stream) - .await - .unwrap_err(); + assert!(!is_ok_cell.get().unwrap()); - // NOTE: smoke test for https://github.com/cloudflare/boring/issues/140 - let _ = err.to_string(); - }; + // NOTE: smoke test for https://github.com/cloudflare/boring/issues/140 + let _ = err.to_string(); + }; - future::join(server, client).await; - } + future::join(server, client).await; }