From 6e2e57e04da44a6befc5a41925468d46f3799046 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 28 Jan 2025 18:09:31 -0800 Subject: [PATCH] Initial implementation from node.js --- .bazelrc | 1 + BUILD.bazel | 3 + WORKSPACE | 11 + external | 1 + include/dh-primes.h | 311 ++++ include/ncrypto.h | 1428 +++++++++++++++ src/ncrypto.cpp | 4030 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 5785 insertions(+) create mode 100644 WORKSPACE create mode 120000 external create mode 100644 include/dh-primes.h diff --git a/.bazelrc b/.bazelrc index 56fc02b..9775957 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,2 @@ +common --enable_workspace build --cxxopt="-std=c++20" diff --git a/BUILD.bazel b/BUILD.bazel index 927fdd4..dfd6c1a 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -4,4 +4,7 @@ cc_library( hdrs = glob(["include/*.h"]), includes = ["include"], visibility = ["//visibility:public"], + deps = [ + "@ssl" + ] ) diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..52ce7ce --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,11 @@ +workspace(name = "ncrypto") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "ssl", + sha256 = "76962c003a298f405d1a5d273a74a94f58b69f65d64b8574a82d4c21c5e407be", + strip_prefix = "google-boringssl-6abe184", + type = "tgz", + urls = ["https://github.com/google/boringssl/tarball/6abe18402eb2a5e9b00158c6459646a948c53060"], +) diff --git a/external b/external new file mode 120000 index 0000000..94b9b30 --- /dev/null +++ b/external @@ -0,0 +1 @@ +/home/jsnell/.cache/bazel/_bazel_jsnell/5f319b6a8e3a6d510bc9a4983389ea9e/execroot/_main/bazel-out/../../../external \ No newline at end of file diff --git a/include/dh-primes.h b/include/dh-primes.h new file mode 100644 index 0000000..213aaa7 --- /dev/null +++ b/include/dh-primes.h @@ -0,0 +1,311 @@ +/* ==================================================================== + * Copyright (c) 2011 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). */ + +#ifndef DEPS_NCRYPTO_DH_PRIMES_H_ +#define DEPS_NCRYPTO_DH_PRIMES_H_ + +#include + +#include +#include +#include + +extern "C" int bn_set_words(BIGNUM* bn, const BN_ULONG* words, size_t num); + +// Backporting primes that may not be supported in earlier boringssl versions. +// Intentionally keeping the existing C-style formatting. + +#define OPENSSL_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) + +#if defined(OPENSSL_64_BIT) +#define TOBN(hi, lo) ((BN_ULONG)(hi) << 32 | (lo)) +#elif defined(OPENSSL_32_BIT) +#define TOBN(hi, lo) (lo), (hi) +#else +#error "Must define either OPENSSL_32_BIT or OPENSSL_64_BIT" +#endif + +static BIGNUM* get_params(BIGNUM* ret, + const BN_ULONG* words, + size_t num_words) { + BIGNUM* alloc = nullptr; + if (ret == nullptr) { + alloc = BN_new(); + if (alloc == nullptr) { + return nullptr; + } + ret = alloc; + } + + if (!bn_set_words(ret, words, num_words)) { + BN_free(alloc); + return nullptr; + } + + return ret; +} + +BIGNUM* BN_get_rfc3526_prime_2048(BIGNUM* ret) { + static const BN_ULONG kWords[] = { + TOBN(0xffffffff, 0xffffffff), TOBN(0x15728e5a, 0x8aacaa68), + TOBN(0x15d22618, 0x98fa0510), TOBN(0x3995497c, 0xea956ae5), + TOBN(0xde2bcbf6, 0x95581718), TOBN(0xb5c55df0, 0x6f4c52c9), + TOBN(0x9b2783a2, 0xec07a28f), TOBN(0xe39e772c, 0x180e8603), + TOBN(0x32905e46, 0x2e36ce3b), TOBN(0xf1746c08, 0xca18217c), + TOBN(0x670c354e, 0x4abc9804), TOBN(0x9ed52907, 0x7096966d), + TOBN(0x1c62f356, 0x208552bb), TOBN(0x83655d23, 0xdca3ad96), + TOBN(0x69163fa8, 0xfd24cf5f), TOBN(0x98da4836, 0x1c55d39a), + TOBN(0xc2007cb8, 0xa163bf05), TOBN(0x49286651, 0xece45b3d), + TOBN(0xae9f2411, 0x7c4b1fe6), TOBN(0xee386bfb, 0x5a899fa5), + TOBN(0x0bff5cb6, 0xf406b7ed), TOBN(0xf44c42e9, 0xa637ed6b), + TOBN(0xe485b576, 0x625e7ec6), TOBN(0x4fe1356d, 0x6d51c245), + TOBN(0x302b0a6d, 0xf25f1437), TOBN(0xef9519b3, 0xcd3a431b), + TOBN(0x514a0879, 0x8e3404dd), TOBN(0x020bbea6, 0x3b139b22), + TOBN(0x29024e08, 0x8a67cc74), TOBN(0xc4c6628b, 0x80dc1cd1), + TOBN(0xc90fdaa2, 0x2168c234), TOBN(0xffffffff, 0xffffffff), + }; + return get_params(ret, kWords, OPENSSL_ARRAY_SIZE(kWords)); +} + +BIGNUM* BN_get_rfc3526_prime_3072(BIGNUM* ret) { + static const BN_ULONG kWords[] = { + TOBN(0xffffffff, 0xffffffff), TOBN(0x4b82d120, 0xa93ad2ca), + TOBN(0x43db5bfc, 0xe0fd108e), TOBN(0x08e24fa0, 0x74e5ab31), + TOBN(0x770988c0, 0xbad946e2), TOBN(0xbbe11757, 0x7a615d6c), + TOBN(0x521f2b18, 0x177b200c), TOBN(0xd8760273, 0x3ec86a64), + TOBN(0xf12ffa06, 0xd98a0864), TOBN(0xcee3d226, 0x1ad2ee6b), + TOBN(0x1e8c94e0, 0x4a25619d), TOBN(0xabf5ae8c, 0xdb0933d7), + TOBN(0xb3970f85, 0xa6e1e4c7), TOBN(0x8aea7157, 0x5d060c7d), + TOBN(0xecfb8504, 0x58dbef0a), TOBN(0xa85521ab, 0xdf1cba64), + TOBN(0xad33170d, 0x04507a33), TOBN(0x15728e5a, 0x8aaac42d), + TOBN(0x15d22618, 0x98fa0510), TOBN(0x3995497c, 0xea956ae5), + TOBN(0xde2bcbf6, 0x95581718), TOBN(0xb5c55df0, 0x6f4c52c9), + TOBN(0x9b2783a2, 0xec07a28f), TOBN(0xe39e772c, 0x180e8603), + TOBN(0x32905e46, 0x2e36ce3b), TOBN(0xf1746c08, 0xca18217c), + TOBN(0x670c354e, 0x4abc9804), TOBN(0x9ed52907, 0x7096966d), + TOBN(0x1c62f356, 0x208552bb), TOBN(0x83655d23, 0xdca3ad96), + TOBN(0x69163fa8, 0xfd24cf5f), TOBN(0x98da4836, 0x1c55d39a), + TOBN(0xc2007cb8, 0xa163bf05), TOBN(0x49286651, 0xece45b3d), + TOBN(0xae9f2411, 0x7c4b1fe6), TOBN(0xee386bfb, 0x5a899fa5), + TOBN(0x0bff5cb6, 0xf406b7ed), TOBN(0xf44c42e9, 0xa637ed6b), + TOBN(0xe485b576, 0x625e7ec6), TOBN(0x4fe1356d, 0x6d51c245), + TOBN(0x302b0a6d, 0xf25f1437), TOBN(0xef9519b3, 0xcd3a431b), + TOBN(0x514a0879, 0x8e3404dd), TOBN(0x020bbea6, 0x3b139b22), + TOBN(0x29024e08, 0x8a67cc74), TOBN(0xc4c6628b, 0x80dc1cd1), + TOBN(0xc90fdaa2, 0x2168c234), TOBN(0xffffffff, 0xffffffff), + }; + return get_params(ret, kWords, OPENSSL_ARRAY_SIZE(kWords)); +} + +BIGNUM* BN_get_rfc3526_prime_4096(BIGNUM* ret) { + static const BN_ULONG kWords[] = { + TOBN(0xffffffff, 0xffffffff), TOBN(0x4df435c9, 0x34063199), + TOBN(0x86ffb7dc, 0x90a6c08f), TOBN(0x93b4ea98, 0x8d8fddc1), + TOBN(0xd0069127, 0xd5b05aa9), TOBN(0xb81bdd76, 0x2170481c), + TOBN(0x1f612970, 0xcee2d7af), TOBN(0x233ba186, 0x515be7ed), + TOBN(0x99b2964f, 0xa090c3a2), TOBN(0x287c5947, 0x4e6bc05d), + TOBN(0x2e8efc14, 0x1fbecaa6), TOBN(0xdbbbc2db, 0x04de8ef9), + TOBN(0x2583e9ca, 0x2ad44ce8), TOBN(0x1a946834, 0xb6150bda), + TOBN(0x99c32718, 0x6af4e23c), TOBN(0x88719a10, 0xbdba5b26), + TOBN(0x1a723c12, 0xa787e6d7), TOBN(0x4b82d120, 0xa9210801), + TOBN(0x43db5bfc, 0xe0fd108e), TOBN(0x08e24fa0, 0x74e5ab31), + TOBN(0x770988c0, 0xbad946e2), TOBN(0xbbe11757, 0x7a615d6c), + TOBN(0x521f2b18, 0x177b200c), TOBN(0xd8760273, 0x3ec86a64), + TOBN(0xf12ffa06, 0xd98a0864), TOBN(0xcee3d226, 0x1ad2ee6b), + TOBN(0x1e8c94e0, 0x4a25619d), TOBN(0xabf5ae8c, 0xdb0933d7), + TOBN(0xb3970f85, 0xa6e1e4c7), TOBN(0x8aea7157, 0x5d060c7d), + TOBN(0xecfb8504, 0x58dbef0a), TOBN(0xa85521ab, 0xdf1cba64), + TOBN(0xad33170d, 0x04507a33), TOBN(0x15728e5a, 0x8aaac42d), + TOBN(0x15d22618, 0x98fa0510), TOBN(0x3995497c, 0xea956ae5), + TOBN(0xde2bcbf6, 0x95581718), TOBN(0xb5c55df0, 0x6f4c52c9), + TOBN(0x9b2783a2, 0xec07a28f), TOBN(0xe39e772c, 0x180e8603), + TOBN(0x32905e46, 0x2e36ce3b), TOBN(0xf1746c08, 0xca18217c), + TOBN(0x670c354e, 0x4abc9804), TOBN(0x9ed52907, 0x7096966d), + TOBN(0x1c62f356, 0x208552bb), TOBN(0x83655d23, 0xdca3ad96), + TOBN(0x69163fa8, 0xfd24cf5f), TOBN(0x98da4836, 0x1c55d39a), + TOBN(0xc2007cb8, 0xa163bf05), TOBN(0x49286651, 0xece45b3d), + TOBN(0xae9f2411, 0x7c4b1fe6), TOBN(0xee386bfb, 0x5a899fa5), + TOBN(0x0bff5cb6, 0xf406b7ed), TOBN(0xf44c42e9, 0xa637ed6b), + TOBN(0xe485b576, 0x625e7ec6), TOBN(0x4fe1356d, 0x6d51c245), + TOBN(0x302b0a6d, 0xf25f1437), TOBN(0xef9519b3, 0xcd3a431b), + TOBN(0x514a0879, 0x8e3404dd), TOBN(0x020bbea6, 0x3b139b22), + TOBN(0x29024e08, 0x8a67cc74), TOBN(0xc4c6628b, 0x80dc1cd1), + TOBN(0xc90fdaa2, 0x2168c234), TOBN(0xffffffff, 0xffffffff), + }; + return get_params(ret, kWords, OPENSSL_ARRAY_SIZE(kWords)); +} + +BIGNUM* BN_get_rfc3526_prime_6144(BIGNUM* ret) { + static const BN_ULONG kWords[] = { + TOBN(0xffffffff, 0xffffffff), TOBN(0xe694f91e, 0x6dcc4024), + TOBN(0x12bf2d5b, 0x0b7474d6), TOBN(0x043e8f66, 0x3f4860ee), + TOBN(0x387fe8d7, 0x6e3c0468), TOBN(0xda56c9ec, 0x2ef29632), + TOBN(0xeb19ccb1, 0xa313d55c), TOBN(0xf550aa3d, 0x8a1fbff0), + TOBN(0x06a1d58b, 0xb7c5da76), TOBN(0xa79715ee, 0xf29be328), + TOBN(0x14cc5ed2, 0x0f8037e0), TOBN(0xcc8f6d7e, 0xbf48e1d8), + TOBN(0x4bd407b2, 0x2b4154aa), TOBN(0x0f1d45b7, 0xff585ac5), + TOBN(0x23a97a7e, 0x36cc88be), TOBN(0x59e7c97f, 0xbec7e8f3), + TOBN(0xb5a84031, 0x900b1c9e), TOBN(0xd55e702f, 0x46980c82), + TOBN(0xf482d7ce, 0x6e74fef6), TOBN(0xf032ea15, 0xd1721d03), + TOBN(0x5983ca01, 0xc64b92ec), TOBN(0x6fb8f401, 0x378cd2bf), + TOBN(0x33205151, 0x2bd7af42), TOBN(0xdb7f1447, 0xe6cc254b), + TOBN(0x44ce6cba, 0xced4bb1b), TOBN(0xda3edbeb, 0xcf9b14ed), + TOBN(0x179727b0, 0x865a8918), TOBN(0xb06a53ed, 0x9027d831), + TOBN(0xe5db382f, 0x413001ae), TOBN(0xf8ff9406, 0xad9e530e), + TOBN(0xc9751e76, 0x3dba37bd), TOBN(0xc1d4dcb2, 0x602646de), + TOBN(0x36c3fab4, 0xd27c7026), TOBN(0x4df435c9, 0x34028492), + TOBN(0x86ffb7dc, 0x90a6c08f), TOBN(0x93b4ea98, 0x8d8fddc1), + TOBN(0xd0069127, 0xd5b05aa9), TOBN(0xb81bdd76, 0x2170481c), + TOBN(0x1f612970, 0xcee2d7af), TOBN(0x233ba186, 0x515be7ed), + TOBN(0x99b2964f, 0xa090c3a2), TOBN(0x287c5947, 0x4e6bc05d), + TOBN(0x2e8efc14, 0x1fbecaa6), TOBN(0xdbbbc2db, 0x04de8ef9), + TOBN(0x2583e9ca, 0x2ad44ce8), TOBN(0x1a946834, 0xb6150bda), + TOBN(0x99c32718, 0x6af4e23c), TOBN(0x88719a10, 0xbdba5b26), + TOBN(0x1a723c12, 0xa787e6d7), TOBN(0x4b82d120, 0xa9210801), + TOBN(0x43db5bfc, 0xe0fd108e), TOBN(0x08e24fa0, 0x74e5ab31), + TOBN(0x770988c0, 0xbad946e2), TOBN(0xbbe11757, 0x7a615d6c), + TOBN(0x521f2b18, 0x177b200c), TOBN(0xd8760273, 0x3ec86a64), + TOBN(0xf12ffa06, 0xd98a0864), TOBN(0xcee3d226, 0x1ad2ee6b), + TOBN(0x1e8c94e0, 0x4a25619d), TOBN(0xabf5ae8c, 0xdb0933d7), + TOBN(0xb3970f85, 0xa6e1e4c7), TOBN(0x8aea7157, 0x5d060c7d), + TOBN(0xecfb8504, 0x58dbef0a), TOBN(0xa85521ab, 0xdf1cba64), + TOBN(0xad33170d, 0x04507a33), TOBN(0x15728e5a, 0x8aaac42d), + TOBN(0x15d22618, 0x98fa0510), TOBN(0x3995497c, 0xea956ae5), + TOBN(0xde2bcbf6, 0x95581718), TOBN(0xb5c55df0, 0x6f4c52c9), + TOBN(0x9b2783a2, 0xec07a28f), TOBN(0xe39e772c, 0x180e8603), + TOBN(0x32905e46, 0x2e36ce3b), TOBN(0xf1746c08, 0xca18217c), + TOBN(0x670c354e, 0x4abc9804), TOBN(0x9ed52907, 0x7096966d), + TOBN(0x1c62f356, 0x208552bb), TOBN(0x83655d23, 0xdca3ad96), + TOBN(0x69163fa8, 0xfd24cf5f), TOBN(0x98da4836, 0x1c55d39a), + TOBN(0xc2007cb8, 0xa163bf05), TOBN(0x49286651, 0xece45b3d), + TOBN(0xae9f2411, 0x7c4b1fe6), TOBN(0xee386bfb, 0x5a899fa5), + TOBN(0x0bff5cb6, 0xf406b7ed), TOBN(0xf44c42e9, 0xa637ed6b), + TOBN(0xe485b576, 0x625e7ec6), TOBN(0x4fe1356d, 0x6d51c245), + TOBN(0x302b0a6d, 0xf25f1437), TOBN(0xef9519b3, 0xcd3a431b), + TOBN(0x514a0879, 0x8e3404dd), TOBN(0x020bbea6, 0x3b139b22), + TOBN(0x29024e08, 0x8a67cc74), TOBN(0xc4c6628b, 0x80dc1cd1), + TOBN(0xc90fdaa2, 0x2168c234), TOBN(0xffffffff, 0xffffffff), + }; + return get_params(ret, kWords, OPENSSL_ARRAY_SIZE(kWords)); +} + +BIGNUM* BN_get_rfc3526_prime_8192(BIGNUM* ret) { + static const BN_ULONG kWords[] = { + TOBN(0xffffffff, 0xffffffff), TOBN(0x60c980dd, 0x98edd3df), + TOBN(0xc81f56e8, 0x80b96e71), TOBN(0x9e3050e2, 0x765694df), + TOBN(0x9558e447, 0x5677e9aa), TOBN(0xc9190da6, 0xfc026e47), + TOBN(0x889a002e, 0xd5ee382b), TOBN(0x4009438b, 0x481c6cd7), + TOBN(0x359046f4, 0xeb879f92), TOBN(0xfaf36bc3, 0x1ecfa268), + TOBN(0xb1d510bd, 0x7ee74d73), TOBN(0xf9ab4819, 0x5ded7ea1), + TOBN(0x64f31cc5, 0x0846851d), TOBN(0x4597e899, 0xa0255dc1), + TOBN(0xdf310ee0, 0x74ab6a36), TOBN(0x6d2a13f8, 0x3f44f82d), + TOBN(0x062b3cf5, 0xb3a278a6), TOBN(0x79683303, 0xed5bdd3a), + TOBN(0xfa9d4b7f, 0xa2c087e8), TOBN(0x4bcbc886, 0x2f8385dd), + TOBN(0x3473fc64, 0x6cea306b), TOBN(0x13eb57a8, 0x1a23f0c7), + TOBN(0x22222e04, 0xa4037c07), TOBN(0xe3fdb8be, 0xfc848ad9), + TOBN(0x238f16cb, 0xe39d652d), TOBN(0x3423b474, 0x2bf1c978), + TOBN(0x3aab639c, 0x5ae4f568), TOBN(0x2576f693, 0x6ba42466), + TOBN(0x741fa7bf, 0x8afc47ed), TOBN(0x3bc832b6, 0x8d9dd300), + TOBN(0xd8bec4d0, 0x73b931ba), TOBN(0x38777cb6, 0xa932df8c), + TOBN(0x74a3926f, 0x12fee5e4), TOBN(0xe694f91e, 0x6dbe1159), + TOBN(0x12bf2d5b, 0x0b7474d6), TOBN(0x043e8f66, 0x3f4860ee), + TOBN(0x387fe8d7, 0x6e3c0468), TOBN(0xda56c9ec, 0x2ef29632), + TOBN(0xeb19ccb1, 0xa313d55c), TOBN(0xf550aa3d, 0x8a1fbff0), + TOBN(0x06a1d58b, 0xb7c5da76), TOBN(0xa79715ee, 0xf29be328), + TOBN(0x14cc5ed2, 0x0f8037e0), TOBN(0xcc8f6d7e, 0xbf48e1d8), + TOBN(0x4bd407b2, 0x2b4154aa), TOBN(0x0f1d45b7, 0xff585ac5), + TOBN(0x23a97a7e, 0x36cc88be), TOBN(0x59e7c97f, 0xbec7e8f3), + TOBN(0xb5a84031, 0x900b1c9e), TOBN(0xd55e702f, 0x46980c82), + TOBN(0xf482d7ce, 0x6e74fef6), TOBN(0xf032ea15, 0xd1721d03), + TOBN(0x5983ca01, 0xc64b92ec), TOBN(0x6fb8f401, 0x378cd2bf), + TOBN(0x33205151, 0x2bd7af42), TOBN(0xdb7f1447, 0xe6cc254b), + TOBN(0x44ce6cba, 0xced4bb1b), TOBN(0xda3edbeb, 0xcf9b14ed), + TOBN(0x179727b0, 0x865a8918), TOBN(0xb06a53ed, 0x9027d831), + TOBN(0xe5db382f, 0x413001ae), TOBN(0xf8ff9406, 0xad9e530e), + TOBN(0xc9751e76, 0x3dba37bd), TOBN(0xc1d4dcb2, 0x602646de), + TOBN(0x36c3fab4, 0xd27c7026), TOBN(0x4df435c9, 0x34028492), + TOBN(0x86ffb7dc, 0x90a6c08f), TOBN(0x93b4ea98, 0x8d8fddc1), + TOBN(0xd0069127, 0xd5b05aa9), TOBN(0xb81bdd76, 0x2170481c), + TOBN(0x1f612970, 0xcee2d7af), TOBN(0x233ba186, 0x515be7ed), + TOBN(0x99b2964f, 0xa090c3a2), TOBN(0x287c5947, 0x4e6bc05d), + TOBN(0x2e8efc14, 0x1fbecaa6), TOBN(0xdbbbc2db, 0x04de8ef9), + TOBN(0x2583e9ca, 0x2ad44ce8), TOBN(0x1a946834, 0xb6150bda), + TOBN(0x99c32718, 0x6af4e23c), TOBN(0x88719a10, 0xbdba5b26), + TOBN(0x1a723c12, 0xa787e6d7), TOBN(0x4b82d120, 0xa9210801), + TOBN(0x43db5bfc, 0xe0fd108e), TOBN(0x08e24fa0, 0x74e5ab31), + TOBN(0x770988c0, 0xbad946e2), TOBN(0xbbe11757, 0x7a615d6c), + TOBN(0x521f2b18, 0x177b200c), TOBN(0xd8760273, 0x3ec86a64), + TOBN(0xf12ffa06, 0xd98a0864), TOBN(0xcee3d226, 0x1ad2ee6b), + TOBN(0x1e8c94e0, 0x4a25619d), TOBN(0xabf5ae8c, 0xdb0933d7), + TOBN(0xb3970f85, 0xa6e1e4c7), TOBN(0x8aea7157, 0x5d060c7d), + TOBN(0xecfb8504, 0x58dbef0a), TOBN(0xa85521ab, 0xdf1cba64), + TOBN(0xad33170d, 0x04507a33), TOBN(0x15728e5a, 0x8aaac42d), + TOBN(0x15d22618, 0x98fa0510), TOBN(0x3995497c, 0xea956ae5), + TOBN(0xde2bcbf6, 0x95581718), TOBN(0xb5c55df0, 0x6f4c52c9), + TOBN(0x9b2783a2, 0xec07a28f), TOBN(0xe39e772c, 0x180e8603), + TOBN(0x32905e46, 0x2e36ce3b), TOBN(0xf1746c08, 0xca18217c), + TOBN(0x670c354e, 0x4abc9804), TOBN(0x9ed52907, 0x7096966d), + TOBN(0x1c62f356, 0x208552bb), TOBN(0x83655d23, 0xdca3ad96), + TOBN(0x69163fa8, 0xfd24cf5f), TOBN(0x98da4836, 0x1c55d39a), + TOBN(0xc2007cb8, 0xa163bf05), TOBN(0x49286651, 0xece45b3d), + TOBN(0xae9f2411, 0x7c4b1fe6), TOBN(0xee386bfb, 0x5a899fa5), + TOBN(0x0bff5cb6, 0xf406b7ed), TOBN(0xf44c42e9, 0xa637ed6b), + TOBN(0xe485b576, 0x625e7ec6), TOBN(0x4fe1356d, 0x6d51c245), + TOBN(0x302b0a6d, 0xf25f1437), TOBN(0xef9519b3, 0xcd3a431b), + TOBN(0x514a0879, 0x8e3404dd), TOBN(0x020bbea6, 0x3b139b22), + TOBN(0x29024e08, 0x8a67cc74), TOBN(0xc4c6628b, 0x80dc1cd1), + TOBN(0xc90fdaa2, 0x2168c234), TOBN(0xffffffff, 0xffffffff), + }; + return get_params(ret, kWords, OPENSSL_ARRAY_SIZE(kWords)); +} + +#endif // DEPS_NCRYPTO_DH_PRIMES_H_ diff --git a/include/ncrypto.h b/include/ncrypto.h index e69de29..f73c84e 100644 --- a/include/ncrypto.h +++ b/include/ncrypto.h @@ -0,0 +1,1428 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef OPENSSL_NO_ENGINE +#include +#endif // !OPENSSL_NO_ENGINE +// The FIPS-related functions are only available +// when the OpenSSL itself was compiled with FIPS support. +#if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3 +#include +#endif // OPENSSL_FIPS + +#if OPENSSL_VERSION_MAJOR >= 3 +#define OSSL3_CONST const +#else +#define OSSL3_CONST +#endif + +#ifdef __GNUC__ +#define NCRYPTO_MUST_USE_RESULT __attribute__((warn_unused_result)) +#else +#define NCRYPTO_MUST_USE_RESULT +#endif + +#ifdef OPENSSL_IS_BORINGSSL +// Boringssl has opted to use size_t for some size related +// APIs while Openssl is still using ints +using OPENSSL_SIZE_T = size_t; +#else +using OPENSSL_SIZE_T = int; +#endif + +namespace ncrypto { + +// ============================================================================ +// Utility macros + +#if NCRYPTO_DEVELOPMENT_CHECKS +#define NCRYPTO_STR(x) #x +#define NCRYPTO_REQUIRE(EXPR) \ + { \ + if (!(EXPR) { abort(); }) } + +#define NCRYPTO_FAIL(MESSAGE) \ + do { \ + std::cerr << "FAIL: " << (MESSAGE) << std::endl; \ + abort(); \ + } while (0); +#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE) \ + do { \ + if (LHS != RHS) { \ + std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \ + NCRYPTO_FAIL(MESSAGE); \ + } \ + } while (0); +#define NCRYPTO_ASSERT_TRUE(COND) \ + do { \ + if (!(COND)) { \ + std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \ + << std::endl; \ + NCRYPTO_FAIL(NCRYPTO_STR(COND)); \ + } \ + } while (0); +#else +#define NCRYPTO_FAIL(MESSAGE) +#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE) +#define NCRYPTO_ASSERT_TRUE(COND) +#endif + +#define NCRYPTO_DISALLOW_COPY(Name) \ + Name(const Name&) = delete; \ + Name& operator=(const Name&) = delete; +#define NCRYPTO_DISALLOW_MOVE(Name) \ + Name(Name&&) = delete; \ + Name& operator=(Name&&) = delete; +#define NCRYPTO_DISALLOW_COPY_AND_MOVE(Name) \ + NCRYPTO_DISALLOW_COPY(Name) \ + NCRYPTO_DISALLOW_MOVE(Name) +#define NCRYPTO_DISALLOW_NEW_DELETE() \ + void* operator new(size_t) = delete; \ + void operator delete(void*) = delete; + +[[noreturn]] inline void unreachable() { +#ifdef __GNUC__ + __builtin_unreachable(); +#elif defined(_MSC_VER) + __assume(false); +#else +#endif +} + +static constexpr int kX509NameFlagsMultiline = + ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_UTF8_CONVERT | + XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN; + +// ============================================================================ +// Error handling utilities + +// Capture the current OpenSSL Error Stack. The stack will be ordered such +// that the error currently at the top of the stack is at the end of the +// list and the error at the bottom of the stack is at the beginning. +class CryptoErrorList final { + public: + enum class Option { NONE, CAPTURE_ON_CONSTRUCT }; + CryptoErrorList(Option option = Option::CAPTURE_ON_CONSTRUCT); + + void capture(); + + // Add an error message to the end of the stack. + void add(std::string message); + + inline const std::string& peek_back() const { return errors_.back(); } + inline size_t size() const { return errors_.size(); } + inline bool empty() const { return errors_.empty(); } + + inline auto begin() const noexcept { return errors_.begin(); } + inline auto end() const noexcept { return errors_.end(); } + inline auto rbegin() const noexcept { return errors_.rbegin(); } + inline auto rend() const noexcept { return errors_.rend(); } + + std::optional pop_back(); + std::optional pop_front(); + + private: + std::list errors_; +}; + +// Forcibly clears the error stack on destruction. This stops stale errors +// from popping up later in the lifecycle of crypto operations where they +// would cause spurious failures. It is a rather blunt method, though, and +// ERR_clear_error() isn't necessarily cheap. +// +// If created with a pointer to a CryptoErrorList, the current OpenSSL error +// stack will be captured before clearing the error. +class ClearErrorOnReturn final { + public: + ClearErrorOnReturn(CryptoErrorList* errors = nullptr); + ~ClearErrorOnReturn(); + NCRYPTO_DISALLOW_COPY_AND_MOVE(ClearErrorOnReturn) + NCRYPTO_DISALLOW_NEW_DELETE() + + int peekError(); + + private: + CryptoErrorList* errors_; +}; + +// Pop errors from OpenSSL's error stack that were added between when this +// was constructed and destructed. +// +// If created with a pointer to a CryptoErrorList, the current OpenSSL error +// stack will be captured before resetting the error to the mark. +class MarkPopErrorOnReturn final { + public: + MarkPopErrorOnReturn(CryptoErrorList* errors = nullptr); + ~MarkPopErrorOnReturn(); + NCRYPTO_DISALLOW_COPY_AND_MOVE(MarkPopErrorOnReturn) + NCRYPTO_DISALLOW_NEW_DELETE() + + int peekError(); + + private: + CryptoErrorList* errors_; +}; + +// TODO(@jasnell): Eventually replace with std::expected when we are able to +// bump up to c++23. +template +struct Result final { + const bool has_value; + T value; + std::optional error = std::nullopt; + std::optional openssl_error = std::nullopt; + Result(T&& value) : has_value(true), value(std::move(value)) {} + Result(E&& error, std::optional openssl_error = std::nullopt) + : has_value(false), + error(std::move(error)), + openssl_error(std::move(openssl_error)) {} + inline operator bool() const { return has_value; } +}; + +// ============================================================================ +// Various smart pointer aliases for OpenSSL types. + +template +struct FunctionDeleter { + void operator()(T* pointer) const { function(pointer); } + typedef std::unique_ptr Pointer; +}; + +template +using DeleteFnPtr = typename FunctionDeleter::Pointer; + +using PKCS8Pointer = DeleteFnPtr; +using RSAPointer = DeleteFnPtr; +using SSLSessionPointer = DeleteFnPtr; + +class BIOPointer; +class BignumPointer; +class CipherCtxPointer; +class DataPointer; +class DHPointer; +class ECKeyPointer; +class EVPKeyPointer; +class EVPMDCtxPointer; +class SSLCtxPointer; +class SSLPointer; +class X509View; +class X509Pointer; +class ECDSASigPointer; +class ECGroupPointer; +class ECPointPointer; +class ECKeyPointer; +class Dsa; +class Rsa; +class Ec; + +struct StackOfXASN1Deleter { + void operator()(STACK_OF(ASN1_OBJECT) * p) const { + sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); + } +}; +using StackOfASN1 = std::unique_ptr; + +// An unowned, unmanaged pointer to a buffer of data. +template +struct Buffer { + T* data = nullptr; + size_t len = 0; +}; + +DataPointer hashDigest(const Buffer& data, + const EVP_MD* md); + +class Cipher final { + public: + Cipher() = default; + Cipher(const EVP_CIPHER* cipher) : cipher_(cipher) {} + Cipher(const Cipher&) = default; + Cipher& operator=(const Cipher&) = default; + inline Cipher& operator=(const EVP_CIPHER* cipher) { + cipher_ = cipher; + return *this; + } + NCRYPTO_DISALLOW_MOVE(Cipher) + + inline const EVP_CIPHER* get() const { return cipher_; } + inline operator const EVP_CIPHER*() const { return cipher_; } + inline operator bool() const { return cipher_ != nullptr; } + + int getNid() const; + int getMode() const; + int getIvLength() const; + int getKeyLength() const; + int getBlockSize() const; + std::string_view getModeLabel() const; + std::string_view getName() const; + + bool isSupportedAuthenticatedMode() const; + + static const Cipher FromName(std::string_view name); + static const Cipher FromNid(int nid); + static const Cipher FromCtx(const CipherCtxPointer& ctx); + + struct CipherParams { + int padding; + const EVP_MD* digest; + const Buffer label; + }; + + static DataPointer encrypt(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in); + static DataPointer decrypt(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in); + + static DataPointer sign(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in); + + static DataPointer recover(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in); + + static constexpr bool IsValidGCMTagLength(unsigned int tag_len) { + return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16); + } + + private: + const EVP_CIPHER* cipher_ = nullptr; +}; + +// ============================================================================ +// DSA + +class Dsa final { + public: + Dsa(); + Dsa(OSSL3_CONST DSA* dsa); + NCRYPTO_DISALLOW_COPY_AND_MOVE(Dsa) + + inline operator bool() const { return dsa_ != nullptr; } + inline operator OSSL3_CONST DSA*() const { return dsa_; } + + const BIGNUM* getP() const; + const BIGNUM* getQ() const; + size_t getModulusLength() const; + size_t getDivisorLength() const; + + private: + OSSL3_CONST DSA* dsa_; +}; + +// ============================================================================ +// RSA + +class Rsa final { + public: + Rsa(); + Rsa(OSSL3_CONST RSA* rsa); + NCRYPTO_DISALLOW_COPY_AND_MOVE(Rsa) + + inline operator bool() const { return rsa_ != nullptr; } + inline operator OSSL3_CONST RSA*() const { return rsa_; } + + struct PublicKey { + const BIGNUM* n; + const BIGNUM* e; + const BIGNUM* d; + }; + struct PrivateKey { + const BIGNUM* p; + const BIGNUM* q; + const BIGNUM* dp; + const BIGNUM* dq; + const BIGNUM* qi; + }; + struct PssParams { + std::string_view digest = "sha1"; + std::optional mgf1_digest = "sha1"; + int64_t salt_length = 20; + }; + + const PublicKey getPublicKey() const; + const PrivateKey getPrivateKey() const; + const std::optional getPssParams() const; + + bool setPublicKey(BignumPointer&& n, BignumPointer&& e); + bool setPrivateKey(BignumPointer&& d, + BignumPointer&& q, + BignumPointer&& p, + BignumPointer&& dp, + BignumPointer&& dq, + BignumPointer&& qi); + + using CipherParams = Cipher::CipherParams; + + static DataPointer encrypt(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in); + static DataPointer decrypt(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in); + + private: + OSSL3_CONST RSA* rsa_; +}; + +class Ec final { + public: + Ec(); + Ec(OSSL3_CONST EC_KEY* key); + NCRYPTO_DISALLOW_COPY_AND_MOVE(Ec) + + const EC_GROUP* getGroup() const; + int getCurve() const; + + inline operator bool() const { return ec_ != nullptr; } + inline operator OSSL3_CONST EC_KEY*() const { return ec_; } + + private: + OSSL3_CONST EC_KEY* ec_ = nullptr; +}; + +// A managed pointer to a buffer of data. When destroyed the underlying +// buffer will be freed. +class DataPointer final { + public: + static DataPointer Alloc(size_t len); + static DataPointer Copy(const Buffer& buffer); + + DataPointer() = default; + explicit DataPointer(void* data, size_t len); + explicit DataPointer(const Buffer& buffer); + DataPointer(DataPointer&& other) noexcept; + DataPointer& operator=(DataPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(DataPointer) + ~DataPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return data_ == nullptr; } + inline operator bool() const { return data_ != nullptr; } + + template + inline T* get() const noexcept { + return static_cast(data_); + } + + inline size_t size() const noexcept { return len_; } + void reset(void* data = nullptr, size_t len = 0); + void reset(const Buffer& buffer); + + // Sets the underlying data buffer to all zeros. + void zero(); + + DataPointer resize(size_t len); + + // Releases ownership of the underlying data buffer. It is the caller's + // responsibility to ensure the buffer is appropriately freed. + Buffer release(); + + // Returns a Buffer struct that is a view of the underlying data. + template + inline operator const Buffer() const { + return { + .data = static_cast(data_), + .len = len_, + }; + } + + private: + void* data_ = nullptr; + size_t len_ = 0; +}; + +class BIOPointer final { + public: + static BIOPointer NewMem(); + static BIOPointer NewSecMem(); + static BIOPointer New(const BIO_METHOD* method); + static BIOPointer New(const void* data, size_t len); + static BIOPointer New(const BIGNUM* bn); + static BIOPointer NewFile(std::string_view filename, std::string_view mode); + static BIOPointer NewFp(FILE* fd, int flags); + + template + static BIOPointer New(const Buffer& buf) { + return New(buf.data, buf.len); + } + + BIOPointer() = default; + BIOPointer(std::nullptr_t) : bio_(nullptr) {} + explicit BIOPointer(BIO* bio); + BIOPointer(BIOPointer&& other) noexcept; + BIOPointer& operator=(BIOPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(BIOPointer) + ~BIOPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return bio_ == nullptr; } + inline operator bool() const { return bio_ != nullptr; } + inline BIO* get() const noexcept { return bio_.get(); } + + inline operator BUF_MEM*() const { + BUF_MEM* mem = nullptr; + if (!bio_) return mem; + BIO_get_mem_ptr(bio_.get(), &mem); + return mem; + } + + inline operator BIO*() const { return bio_.get(); } + + void reset(BIO* bio = nullptr); + BIO* release(); + + bool resetBio() const; + + static int Write(BIOPointer* bio, std::string_view message); + + template + static void Printf(BIOPointer* bio, const char* format, Args... args) { + if (bio == nullptr || !*bio) return; + BIO_printf(bio->get(), format, std::forward(args...)); + } + + private: + mutable DeleteFnPtr bio_; +}; + +class BignumPointer final { + public: + BignumPointer() = default; + explicit BignumPointer(BIGNUM* bignum); + explicit BignumPointer(const unsigned char* data, size_t len); + BignumPointer(BignumPointer&& other) noexcept; + BignumPointer& operator=(BignumPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(BignumPointer) + ~BignumPointer(); + + int operator<=>(const BignumPointer& other) const noexcept; + int operator<=>(const BIGNUM* other) const noexcept; + inline operator bool() const { return bn_ != nullptr; } + inline BIGNUM* get() const noexcept { return bn_.get(); } + void reset(BIGNUM* bn = nullptr); + void reset(const unsigned char* data, size_t len); + BIGNUM* release(); + + bool isZero() const; + bool isOne() const; + + bool setWord(unsigned long w); // NOLINT(runtime/int) + unsigned long getWord() const; // NOLINT(runtime/int) + + size_t byteLength() const; + + DataPointer toHex() const; + DataPointer encode() const; + DataPointer encodePadded(size_t size) const; + size_t encodeInto(unsigned char* out) const; + size_t encodePaddedInto(unsigned char* out, size_t size) const; + + using PrimeCheckCallback = std::function; + int isPrime(int checks, + PrimeCheckCallback cb = defaultPrimeCheckCallback) const; + struct PrimeConfig { + int bits; + bool safe = false; + const BignumPointer& add; + const BignumPointer& rem; + }; + + static BignumPointer NewPrime( + const PrimeConfig& params, + PrimeCheckCallback cb = defaultPrimeCheckCallback); + + bool generate(const PrimeConfig& params, + PrimeCheckCallback cb = defaultPrimeCheckCallback) const; + + static BignumPointer New(); + static BignumPointer NewSecure(); + static BignumPointer NewSub(const BignumPointer& a, const BignumPointer& b); + static BignumPointer NewLShift(size_t length); + + static DataPointer Encode(const BIGNUM* bn); + static DataPointer EncodePadded(const BIGNUM* bn, size_t size); + static size_t EncodePaddedInto(const BIGNUM* bn, + unsigned char* out, + size_t size); + static int GetBitCount(const BIGNUM* bn); + static int GetByteCount(const BIGNUM* bn); + static unsigned long GetWord(const BIGNUM* bn); // NOLINT(runtime/int) + static const BIGNUM* One(); + + BignumPointer clone(); + + private: + DeleteFnPtr bn_; + + static bool defaultPrimeCheckCallback(int, int) { return 1; } +}; + +class CipherCtxPointer final { + public: + static CipherCtxPointer New(); + + CipherCtxPointer() = default; + explicit CipherCtxPointer(EVP_CIPHER_CTX* ctx); + CipherCtxPointer(CipherCtxPointer&& other) noexcept; + CipherCtxPointer& operator=(CipherCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(CipherCtxPointer) + ~CipherCtxPointer(); + + inline bool operator==(std::nullptr_t) const noexcept { + return ctx_ == nullptr; + } + inline operator bool() const { return ctx_ != nullptr; } + inline EVP_CIPHER_CTX* get() const { return ctx_.get(); } + inline operator EVP_CIPHER_CTX*() const { return ctx_.get(); } + void reset(EVP_CIPHER_CTX* ctx = nullptr); + EVP_CIPHER_CTX* release(); + + void setFlags(int flags); + bool setKeyLength(size_t length); + bool setIvLength(size_t length); + bool setAeadTag(const Buffer& tag); + bool setAeadTagLength(size_t length); + bool setPadding(bool padding); + bool init(const Cipher& cipher, + bool encrypt, + const unsigned char* key = nullptr, + const unsigned char* iv = nullptr); + + int getBlockSize() const; + int getMode() const; + int getNid() const; + + bool update(const Buffer& in, + unsigned char* out, + int* out_len, + bool finalize = false); + bool getAeadTag(size_t len, unsigned char* out); + + private: + DeleteFnPtr ctx_; +}; + +class EVPKeyCtxPointer final { + public: + EVPKeyCtxPointer(); + explicit EVPKeyCtxPointer(EVP_PKEY_CTX* ctx); + EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept; + EVPKeyCtxPointer& operator=(EVPKeyCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(EVPKeyCtxPointer) + ~EVPKeyCtxPointer(); + + inline bool operator==(std::nullptr_t) const noexcept { + return ctx_ == nullptr; + } + inline operator bool() const { return ctx_ != nullptr; } + inline EVP_PKEY_CTX* get() const { return ctx_.get(); } + void reset(EVP_PKEY_CTX* ctx = nullptr); + EVP_PKEY_CTX* release(); + + bool initForDerive(const EVPKeyPointer& peer); + DataPointer derive() const; + + bool initForParamgen(); + bool setDhParameters(int prime_size, uint32_t generator); + bool setDsaParameters(uint32_t bits, std::optional q_bits); + bool setEcParameters(int curve, int encoding); + + bool setRsaOaepMd(const EVP_MD* md); + bool setRsaMgf1Md(const EVP_MD* md); + bool setRsaPadding(int padding); + bool setRsaKeygenPubExp(BignumPointer&& e); + bool setRsaKeygenBits(int bits); + bool setRsaPssKeygenMd(const EVP_MD* md); + bool setRsaPssKeygenMgf1Md(const EVP_MD* md); + bool setRsaPssSaltlen(int salt_len); + bool setRsaImplicitRejection(); + bool setRsaOaepLabel(DataPointer&& data); + + bool setSignatureMd(const EVPMDCtxPointer& md); + + bool publicCheck() const; + bool privateCheck() const; + + bool verify(const Buffer& sig, + const Buffer& data); + DataPointer sign(const Buffer& data); + bool signInto(const Buffer& data, + Buffer* sig); + + static constexpr int kDefaultRsaExponent = 0x10001; + + static bool setRsaPadding(EVP_PKEY_CTX* ctx, + int padding, + std::optional salt_len = std::nullopt); + + EVPKeyPointer paramgen() const; + + bool initForEncrypt(); + bool initForDecrypt(); + bool initForKeygen(); + int initForVerify(); + int initForSign(); + + static EVPKeyCtxPointer New(const EVPKeyPointer& key); + static EVPKeyCtxPointer NewFromID(int id); + + private: + DeleteFnPtr ctx_; +}; + +class EVPKeyPointer final { + public: + static EVPKeyPointer New(); + static EVPKeyPointer NewRawPublic(int id, + const Buffer& data); + static EVPKeyPointer NewRawPrivate(int id, + const Buffer& data); + static EVPKeyPointer NewDH(DHPointer&& dh); + static EVPKeyPointer NewRSA(RSAPointer&& rsa); + + enum class PKEncodingType { + // RSAPublicKey / RSAPrivateKey according to PKCS#1. + PKCS1, + // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8. + PKCS8, + // SubjectPublicKeyInfo according to X.509. + SPKI, + // ECPrivateKey according to SEC1. + SEC1, + }; + + enum class PKFormatType { + DER, + PEM, + JWK, + }; + + enum class PKParseError { NOT_RECOGNIZED, NEED_PASSPHRASE, FAILED }; + using ParseKeyResult = Result; + + struct AsymmetricKeyEncodingConfig { + bool output_key_object = false; + PKFormatType format = PKFormatType::DER; + PKEncodingType type = PKEncodingType::PKCS8; + AsymmetricKeyEncodingConfig() = default; + AsymmetricKeyEncodingConfig(bool output_key_object, + PKFormatType format, + PKEncodingType type); + AsymmetricKeyEncodingConfig(const AsymmetricKeyEncodingConfig&) = default; + AsymmetricKeyEncodingConfig& operator=(const AsymmetricKeyEncodingConfig&) = + default; + }; + using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig; + + struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig { + const EVP_CIPHER* cipher = nullptr; + std::optional passphrase = std::nullopt; + PrivateKeyEncodingConfig() = default; + PrivateKeyEncodingConfig(bool output_key_object, + PKFormatType format, + PKEncodingType type) + : AsymmetricKeyEncodingConfig(output_key_object, format, type) {} + PrivateKeyEncodingConfig(const PrivateKeyEncodingConfig&); + PrivateKeyEncodingConfig& operator=(const PrivateKeyEncodingConfig&); + }; + + static ParseKeyResult TryParsePublicKey( + const PublicKeyEncodingConfig& config, + const Buffer& buffer); + + static ParseKeyResult TryParsePublicKeyPEM( + const Buffer& buffer); + + static ParseKeyResult TryParsePrivateKey( + const PrivateKeyEncodingConfig& config, + const Buffer& buffer); + + EVPKeyPointer() = default; + explicit EVPKeyPointer(EVP_PKEY* pkey); + EVPKeyPointer(EVPKeyPointer&& other) noexcept; + EVPKeyPointer& operator=(EVPKeyPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(EVPKeyPointer) + ~EVPKeyPointer(); + + bool assign(const ECKeyPointer& eckey); + bool set(const ECKeyPointer& eckey); + operator const EC_KEY*() const; + + inline bool operator==(std::nullptr_t) const noexcept { + return pkey_ == nullptr; + } + inline operator bool() const { return pkey_ != nullptr; } + inline EVP_PKEY* get() const { return pkey_.get(); } + void reset(EVP_PKEY* pkey = nullptr); + EVP_PKEY* release(); + + static int id(const EVP_PKEY* key); + static int base_id(const EVP_PKEY* key); + + int id() const; + int base_id() const; + int bits() const; + size_t size() const; + + size_t rawPublicKeySize() const; + size_t rawPrivateKeySize() const; + DataPointer rawPublicKey() const; + DataPointer rawPrivateKey() const; + BIOPointer derPublicKey() const; + + Result writePrivateKey( + const PrivateKeyEncodingConfig& config) const; + Result writePublicKey( + const PublicKeyEncodingConfig& config) const; + + EVPKeyCtxPointer newCtx() const; + + static bool IsRSAPrivateKey(const Buffer& buffer); + + std::optional getBytesOfRS() const; + int getDefaultSignPadding() const; + operator Rsa() const; + operator Dsa() const; + + bool isRsaVariant() const; + bool isOneShotVariant() const; + bool isSigVariant() const; + bool validateDsaParameters() const; + + private: + DeleteFnPtr pkey_; +}; + +class DHPointer final { + public: + enum class FindGroupOption { + NONE, + // There are known and documented security issues with prime groups smaller + // than 2048 bits. When the NO_SMALL_PRIMES option is set, these small prime + // groups will not be supported. + NO_SMALL_PRIMES, + }; + + static BignumPointer GetStandardGenerator(); + + static BignumPointer FindGroup( + const std::string_view name, + FindGroupOption option = FindGroupOption::NONE); + static DHPointer FromGroup(const std::string_view name, + FindGroupOption option = FindGroupOption::NONE); + + static DHPointer New(BignumPointer&& p, BignumPointer&& g); + static DHPointer New(size_t bits, unsigned int generator); + + DHPointer() = default; + explicit DHPointer(DH* dh); + DHPointer(DHPointer&& other) noexcept; + DHPointer& operator=(DHPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(DHPointer) + ~DHPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return dh_ == nullptr; } + inline operator bool() const { return dh_ != nullptr; } + inline DH* get() const { return dh_.get(); } + void reset(DH* dh = nullptr); + DH* release(); + + enum class CheckResult { + NONE, + P_NOT_PRIME = DH_CHECK_P_NOT_PRIME, + P_NOT_SAFE_PRIME = DH_CHECK_P_NOT_SAFE_PRIME, + UNABLE_TO_CHECK_GENERATOR = DH_UNABLE_TO_CHECK_GENERATOR, + NOT_SUITABLE_GENERATOR = DH_NOT_SUITABLE_GENERATOR, + Q_NOT_PRIME = DH_CHECK_Q_NOT_PRIME, +#ifndef OPENSSL_IS_BORINGSSL + // Boringssl does not define the DH_CHECK_INVALID_[Q or J]_VALUE + INVALID_Q = DH_CHECK_INVALID_Q_VALUE, + INVALID_J = DH_CHECK_INVALID_J_VALUE, +#endif + CHECK_FAILED = 512, + }; + CheckResult check(); + + enum class CheckPublicKeyResult { + NONE, +#ifndef OPENSSL_IS_BORINGSSL + // Boringssl does not define DH_R_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE + TOO_SMALL = DH_R_CHECK_PUBKEY_TOO_SMALL, + TOO_LARGE = DH_R_CHECK_PUBKEY_TOO_LARGE, + INVALID = DH_R_CHECK_PUBKEY_INVALID, +#else + INVALID = DH_R_INVALID_PUBKEY, +#endif + CHECK_FAILED = 512, + }; + // Check to see if the given public key is suitable for this DH instance. + CheckPublicKeyResult checkPublicKey(const BignumPointer& pub_key); + + DataPointer getPrime() const; + DataPointer getGenerator() const; + DataPointer getPublicKey() const; + DataPointer getPrivateKey() const; + DataPointer generateKeys() const; + DataPointer computeSecret(const BignumPointer& peer) const; + + bool setPublicKey(BignumPointer&& key); + bool setPrivateKey(BignumPointer&& key); + + size_t size() const; + + static DataPointer stateless(const EVPKeyPointer& ourKey, + const EVPKeyPointer& theirKey); + + private: + DeleteFnPtr dh_; +}; + +struct StackOfX509Deleter { + void operator()(STACK_OF(X509) * p) const { sk_X509_pop_free(p, X509_free); } +}; +using StackOfX509 = std::unique_ptr; + +class SSLCtxPointer final { + public: + SSLCtxPointer() = default; + explicit SSLCtxPointer(SSL_CTX* ctx); + SSLCtxPointer(SSLCtxPointer&& other) noexcept; + SSLCtxPointer& operator=(SSLCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(SSLCtxPointer) + ~SSLCtxPointer(); + + inline bool operator==(std::nullptr_t) const noexcept { + return ctx_ == nullptr; + } + inline operator bool() const { return ctx_ != nullptr; } + inline SSL_CTX* get() const { return ctx_.get(); } + void reset(SSL_CTX* ctx = nullptr); + void reset(const SSL_METHOD* method); + SSL_CTX* release(); + + bool setGroups(const char* groups); + void setStatusCallback(auto callback) { + if (!ctx_) return; + SSL_CTX_set_tlsext_status_cb(get(), callback); + SSL_CTX_set_tlsext_status_arg(get(), nullptr); + } + + static SSLCtxPointer NewServer(); + static SSLCtxPointer NewClient(); + static SSLCtxPointer New(const SSL_METHOD* method = TLS_method()); + + private: + DeleteFnPtr ctx_; +}; + +class SSLPointer final { + public: + SSLPointer() = default; + explicit SSLPointer(SSL* ssl); + SSLPointer(SSLPointer&& other) noexcept; + SSLPointer& operator=(SSLPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(SSLPointer) + ~SSLPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return ssl_ == nullptr; } + inline operator bool() const { return ssl_ != nullptr; } + inline SSL* get() const { return ssl_.get(); } + inline operator SSL*() const { return ssl_.get(); } + void reset(SSL* ssl = nullptr); + SSL* release(); + + bool setSession(const SSLSessionPointer& session); + bool setSniContext(const SSLCtxPointer& ctx) const; + + const std::string_view getClientHelloAlpn() const; + const std::string_view getClientHelloServerName() const; + + std::optional getServerName() const; + X509View getCertificate() const; + EVPKeyPointer getPeerTempKey() const; + const SSL_CIPHER* getCipher() const; + bool isServer() const; + + std::optional getCipherName() const; + std::optional getCipherStandardName() const; + std::optional getCipherVersion() const; + + std::optional verifyPeerCertificate() const; + + void getCiphers(std::function cb) const; + + static SSLPointer New(const SSLCtxPointer& ctx); + static std::optional GetServerName(const SSL* ssl); + + private: + DeleteFnPtr ssl_; +}; + +class X509Name final { + public: + X509Name(); + explicit X509Name(const X509_NAME* name); + NCRYPTO_DISALLOW_COPY_AND_MOVE(X509Name) + + inline operator const X509_NAME*() const { return name_; } + inline operator bool() const { return name_ != nullptr; } + inline const X509_NAME* get() const { return name_; } + inline size_t size() const { return total_; } + + class Iterator final { + public: + Iterator(const X509Name& name, int pos); + Iterator(const Iterator& other) = default; + Iterator(Iterator&& other) = default; + Iterator& operator=(const Iterator& other) = delete; + Iterator& operator=(Iterator&& other) = delete; + Iterator& operator++(); + operator bool() const; + bool operator==(const Iterator& other) const; + bool operator!=(const Iterator& other) const; + std::pair operator*() const; + + private: + const X509Name& name_; + int loc_; + }; + + inline Iterator begin() const { return Iterator(*this, 0); } + inline Iterator end() const { return Iterator(*this, total_); } + + private: + const X509_NAME* name_; + int total_; +}; + +class X509View final { + public: + static X509View From(const SSLPointer& ssl); + static X509View From(const SSLCtxPointer& ctx); + + X509View() = default; + inline explicit X509View(const X509* cert) : cert_(cert) {} + X509View(const X509View& other) = default; + X509View& operator=(const X509View& other) = default; + NCRYPTO_DISALLOW_MOVE(X509View) + + inline X509* get() const { return const_cast(cert_); } + inline operator X509*() const { return const_cast(cert_); } + inline operator const X509*() const { return cert_; } + + inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } + inline operator bool() const { return cert_ != nullptr; } + + BIOPointer toPEM() const; + BIOPointer toDER() const; + + const X509Name getSubjectName() const; + const X509Name getIssuerName() const; + BIOPointer getSubject() const; + BIOPointer getSubjectAltName() const; + BIOPointer getIssuer() const; + BIOPointer getInfoAccess() const; + BIOPointer getValidFrom() const; + BIOPointer getValidTo() const; + int64_t getValidFromTime() const; + int64_t getValidToTime() const; + DataPointer getSerialNumber() const; + Result getPublicKey() const; + StackOfASN1 getKeyUsage() const; + + bool isCA() const; + bool isIssuedBy(const X509View& other) const; + bool checkPrivateKey(const EVPKeyPointer& pkey) const; + bool checkPublicKey(const EVPKeyPointer& pkey) const; + + std::optional getFingerprint(const EVP_MD* method) const; + + X509Pointer clone() const; + + enum class CheckMatch { + NO_MATCH, + MATCH, + INVALID_NAME, + OPERATION_FAILED, + }; + CheckMatch checkHost(const std::string_view host, + int flags, + DataPointer* peerName = nullptr) const; + CheckMatch checkEmail(const std::string_view email, int flags) const; + CheckMatch checkIp(const std::string_view ip, int flags) const; + + using UsageCallback = std::function; + bool enumUsages(UsageCallback callback) const; + + template + using KeyCallback = std::function; + bool ifRsa(KeyCallback callback) const; + bool ifEc(KeyCallback callback) const; + + private: + const X509* cert_ = nullptr; +}; + +class X509Pointer final { + public: + static Result Parse(Buffer buffer); + static X509Pointer IssuerFrom(const SSLPointer& ssl, const X509View& view); + static X509Pointer IssuerFrom(const SSL_CTX* ctx, const X509View& view); + static X509Pointer PeerFrom(const SSLPointer& ssl); + + X509Pointer() = default; + explicit X509Pointer(X509* cert); + X509Pointer(X509Pointer&& other) noexcept; + X509Pointer& operator=(X509Pointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(X509Pointer) + ~X509Pointer(); + + inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } + inline operator bool() const { return cert_ != nullptr; } + inline X509* get() const { return cert_.get(); } + inline operator X509*() const { return cert_.get(); } + inline operator const X509*() const { return cert_.get(); } + void reset(X509* cert = nullptr); + X509* release(); + + X509View view() const; + operator X509View() const { return view(); } + + static std::string_view ErrorCode(int32_t err); + static std::optional ErrorReason(int32_t err); + + private: + DeleteFnPtr cert_; +}; + +class ECDSASigPointer final { + public: + explicit ECDSASigPointer(); + explicit ECDSASigPointer(ECDSA_SIG* sig); + ECDSASigPointer(ECDSASigPointer&& other) noexcept; + ECDSASigPointer& operator=(ECDSASigPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(ECDSASigPointer) + ~ECDSASigPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return sig_ == nullptr; } + inline operator bool() const { return sig_ != nullptr; } + inline ECDSA_SIG* get() const { return sig_.get(); } + inline operator ECDSA_SIG*() const { return sig_.get(); } + void reset(ECDSA_SIG* sig = nullptr); + ECDSA_SIG* release(); + + static ECDSASigPointer New(); + static ECDSASigPointer Parse(const Buffer& buffer); + + inline const BIGNUM* r() const { return pr_; } + inline const BIGNUM* s() const { return ps_; } + + bool setParams(BignumPointer&& r, BignumPointer&& s); + + Buffer encode() const; + + private: + DeleteFnPtr sig_; + const BIGNUM* pr_ = nullptr; + const BIGNUM* ps_ = nullptr; +}; + +class ECGroupPointer final { + public: + explicit ECGroupPointer(); + explicit ECGroupPointer(EC_GROUP* group); + ECGroupPointer(ECGroupPointer&& other) noexcept; + ECGroupPointer& operator=(ECGroupPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(ECGroupPointer) + ~ECGroupPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return group_ == nullptr; } + inline operator bool() const { return group_ != nullptr; } + inline EC_GROUP* get() const { return group_.get(); } + inline operator EC_GROUP*() const { return group_.get(); } + void reset(EC_GROUP* group = nullptr); + EC_GROUP* release(); + + static ECGroupPointer NewByCurveName(int nid); + + private: + DeleteFnPtr group_; +}; + +class ECPointPointer final { + public: + ECPointPointer(); + explicit ECPointPointer(EC_POINT* point); + ECPointPointer(ECPointPointer&& other) noexcept; + ECPointPointer& operator=(ECPointPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(ECPointPointer) + ~ECPointPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return point_ == nullptr; } + inline operator bool() const { return point_ != nullptr; } + inline EC_POINT* get() const { return point_.get(); } + inline operator EC_POINT*() const { return point_.get(); } + void reset(EC_POINT* point = nullptr); + EC_POINT* release(); + + bool setFromBuffer(const Buffer& buffer, + const EC_GROUP* group); + bool mul(const EC_GROUP* group, const BIGNUM* priv_key); + + static ECPointPointer New(const EC_GROUP* group); + + private: + DeleteFnPtr point_; +}; + +class ECKeyPointer final { + public: + ECKeyPointer(); + explicit ECKeyPointer(EC_KEY* key); + ECKeyPointer(ECKeyPointer&& other) noexcept; + ECKeyPointer& operator=(ECKeyPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(ECKeyPointer) + ~ECKeyPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return key_ == nullptr; } + inline operator bool() const { return key_ != nullptr; } + inline EC_KEY* get() const { return key_.get(); } + inline operator EC_KEY*() const { return key_.get(); } + void reset(EC_KEY* key = nullptr); + EC_KEY* release(); + + ECKeyPointer clone() const; + bool setPrivateKey(const BignumPointer& priv); + bool setPublicKey(const ECPointPointer& pub); + bool setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y); + bool generate(); + bool checkKey() const; + + const EC_GROUP* getGroup() const; + const BIGNUM* getPrivateKey() const; + const EC_POINT* getPublicKey() const; + + static ECKeyPointer New(const EC_GROUP* group); + static ECKeyPointer NewByCurveName(int nid); + + static const EC_POINT* GetPublicKey(const EC_KEY* key); + static const BIGNUM* GetPrivateKey(const EC_KEY* key); + static const EC_GROUP* GetGroup(const EC_KEY* key); + static int GetGroupName(const EC_KEY* key); + static bool Check(const EC_KEY* key); + + private: + DeleteFnPtr key_; +}; + +class EVPMDCtxPointer final { + public: + EVPMDCtxPointer(); + explicit EVPMDCtxPointer(EVP_MD_CTX* ctx); + EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept; + EVPMDCtxPointer& operator=(EVPMDCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(EVPMDCtxPointer) + ~EVPMDCtxPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; } + inline operator bool() const { return ctx_ != nullptr; } + inline EVP_MD_CTX* get() const { return ctx_.get(); } + inline operator EVP_MD_CTX*() const { return ctx_.get(); } + void reset(EVP_MD_CTX* ctx = nullptr); + EVP_MD_CTX* release(); + + bool digestInit(const EVP_MD* digest); + bool digestUpdate(const Buffer& in); + DataPointer digestFinal(size_t length); + bool digestFinalInto(Buffer* buf); + size_t getExpectedSize(); + + std::optional signInit(const EVPKeyPointer& key, + const EVP_MD* digest); + std::optional verifyInit(const EVPKeyPointer& key, + const EVP_MD* digest); + + DataPointer signOneShot(const Buffer& buf) const; + DataPointer sign(const Buffer& buf) const; + bool verify(const Buffer& buf, + const Buffer& sig) const; + + const EVP_MD* getDigest() const; + size_t getDigestSize() const; + bool hasXofFlag() const; + + bool copyTo(const EVPMDCtxPointer& other) const; + + static EVPMDCtxPointer New(); + + private: + DeleteFnPtr ctx_; +}; + +class HMACCtxPointer final { + public: + HMACCtxPointer(); + explicit HMACCtxPointer(HMAC_CTX* ctx); + HMACCtxPointer(HMACCtxPointer&& other) noexcept; + HMACCtxPointer& operator=(HMACCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(HMACCtxPointer) + ~HMACCtxPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; } + inline operator bool() const { return ctx_ != nullptr; } + inline HMAC_CTX* get() const { return ctx_.get(); } + inline operator HMAC_CTX*() const { return ctx_.get(); } + void reset(HMAC_CTX* ctx = nullptr); + HMAC_CTX* release(); + + bool init(const Buffer& buf, const EVP_MD* md); + bool update(const Buffer& buf); + DataPointer digest(); + bool digestInto(Buffer* buf); + + static HMACCtxPointer New(); + + private: + DeleteFnPtr ctx_; +}; + +#ifndef OPENSSL_NO_ENGINE +class EnginePointer final { + public: + EnginePointer() = default; + + explicit EnginePointer(ENGINE* engine_, bool finish_on_exit = false); + EnginePointer(EnginePointer&& other) noexcept; + EnginePointer& operator=(EnginePointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(EnginePointer) + ~EnginePointer(); + + inline operator bool() const { return engine != nullptr; } + inline ENGINE* get() { return engine; } + inline void setFinishOnExit() { finish_on_exit = true; } + + void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false); + + bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr); + bool init(bool finish_on_exit = false); + EVPKeyPointer loadPrivateKey(const std::string_view key_name); + + // Release ownership of the ENGINE* pointer. + ENGINE* release(); + + // Retrieve an OpenSSL Engine instance by name. If the name does not + // identify a valid named engine, the returned EnginePointer will be + // empty. + static EnginePointer getEngineByName(const std::string_view name, + CryptoErrorList* errors = nullptr); + + // Call once when initializing OpenSSL at startup for the process. + static void initEnginesOnce(); + + private: + ENGINE* engine = nullptr; + bool finish_on_exit = false; +}; +#endif // !OPENSSL_NO_ENGINE + +// ============================================================================ +// FIPS +bool isFipsEnabled(); + +bool setFipsEnabled(bool enabled, CryptoErrorList* errors); + +bool testFipsEnabled(); + +// ============================================================================ +// Various utilities + +bool CSPRNG(void* buffer, size_t length) NCRYPTO_MUST_USE_RESULT; + +// This callback is used to avoid the default passphrase callback in OpenSSL +// which will typically prompt for the passphrase. The prompting is designed +// for the OpenSSL CLI, but works poorly for some environments like Node.js +// because it involves synchronous interaction with the controlling terminal, +// something we never want, and use this function to avoid it. +int NoPasswordCallback(char* buf, int size, int rwflag, void* u); + +int PasswordCallback(char* buf, int size, int rwflag, void* u); + +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext); +bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext); + +// ============================================================================ +// SPKAC + +bool VerifySpkac(const char* input, size_t length); +BIOPointer ExportPublicKey(const char* input, size_t length); + +// The caller takes ownership of the returned Buffer +Buffer ExportChallenge(const char* input, size_t length); + +// ============================================================================ +// KDF + +const EVP_MD* getDigestByName(const std::string_view name); +const EVP_CIPHER* getCipherByName(const std::string_view name); + +// Verify that the specified HKDF output length is valid for the given digest. +// The maximum length for HKDF output for a given digest is 255 times the +// hash size for the given digest algorithm. +bool checkHkdfLength(const EVP_MD* md, size_t length); + +bool extractP1363(const Buffer& buf, + unsigned char* dest, + size_t n); + +DataPointer hkdf(const EVP_MD* md, + const Buffer& key, + const Buffer& info, + const Buffer& salt, + size_t length); + +bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem); + +DataPointer scrypt(const Buffer& pass, + const Buffer& salt, + uint64_t N, + uint64_t r, + uint64_t p, + uint64_t maxmem, + size_t length); + +DataPointer pbkdf2(const EVP_MD* md, + const Buffer& pass, + const Buffer& salt, + uint32_t iterations, + size_t length); + +// ============================================================================ +// Version metadata +#define NCRYPTO_VERSION "0.0.1" + +enum { + NCRYPTO_VERSION_MAJOR = 0, + NCRYPTO_VERSION_MINOR = 0, + NCRYPTO_VERSION_REVISION = 1, +}; + +} // namespace ncrypto diff --git a/src/ncrypto.cpp b/src/ncrypto.cpp index e69de29..83b59c8 100644 --- a/src/ncrypto.cpp +++ b/src/ncrypto.cpp @@ -0,0 +1,4030 @@ +#include "ncrypto.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if OPENSSL_VERSION_MAJOR >= 3 +#include +#endif +#ifdef OPENSSL_IS_BORINGSSL +#include "dh-primes.h" +#endif // OPENSSL_IS_BORINGSSL + +// EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. +#if OPENSSL_VERSION_NUMBER < 0x1010105fL +#define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \ + EVP_PKEY_CTX_ctrl((ctx), \ + EVP_PKEY_DSA, \ + EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, \ + (qbits), \ + nullptr) +#endif + +namespace ncrypto { +namespace { +using BignumCtxPointer = DeleteFnPtr; +using BignumGenCallbackPointer = DeleteFnPtr; +using NetscapeSPKIPointer = DeleteFnPtr; + +static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = + XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; +} // namespace + +// ============================================================================ + +ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors) + : errors_(errors) { + ERR_clear_error(); +} + +ClearErrorOnReturn::~ClearErrorOnReturn() { + if (errors_ != nullptr) errors_->capture(); + ERR_clear_error(); +} + +int ClearErrorOnReturn::peekError() { + return ERR_peek_error(); +} + +MarkPopErrorOnReturn::MarkPopErrorOnReturn(CryptoErrorList* errors) + : errors_(errors) { + ERR_set_mark(); +} + +MarkPopErrorOnReturn::~MarkPopErrorOnReturn() { + if (errors_ != nullptr) errors_->capture(); + ERR_pop_to_mark(); +} + +int MarkPopErrorOnReturn::peekError() { + return ERR_peek_error(); +} + +CryptoErrorList::CryptoErrorList(CryptoErrorList::Option option) { + if (option == Option::CAPTURE_ON_CONSTRUCT) capture(); +} + +void CryptoErrorList::capture() { + errors_.clear(); + while (const auto err = ERR_get_error()) { + char buf[256]; + ERR_error_string_n(err, buf, sizeof(buf)); + errors_.emplace_front(buf); + } +} + +void CryptoErrorList::add(std::string error) { + errors_.push_back(error); +} + +std::optional CryptoErrorList::pop_back() { + if (errors_.empty()) return std::nullopt; + std::string error = errors_.back(); + errors_.pop_back(); + return error; +} + +std::optional CryptoErrorList::pop_front() { + if (errors_.empty()) return std::nullopt; + std::string error = errors_.front(); + errors_.pop_front(); + return error; +} + +// ============================================================================ +DataPointer DataPointer::Alloc(size_t len) { +#ifdef OPENSSL_IS_BORINGSSL + // Boringssl does not implement OPENSSL_zalloc + auto ptr = OPENSSL_malloc(len); + if (ptr == nullptr) return {}; + memset(ptr, 0, len); + return DataPointer(ptr, len); +#else + return DataPointer(OPENSSL_zalloc(len), len); +#endif +} + +DataPointer DataPointer::Copy(const Buffer& buffer) { + return DataPointer(OPENSSL_memdup(buffer.data, buffer.len), buffer.len); +} + +DataPointer::DataPointer(void* data, size_t length) + : data_(data), len_(length) {} + +DataPointer::DataPointer(const Buffer& buffer) + : data_(buffer.data), len_(buffer.len) {} + +DataPointer::DataPointer(DataPointer&& other) noexcept + : data_(other.data_), len_(other.len_) { + other.data_ = nullptr; + other.len_ = 0; +} + +DataPointer& DataPointer::operator=(DataPointer&& other) noexcept { + if (this == &other) return *this; + this->~DataPointer(); + return *new (this) DataPointer(std::move(other)); +} + +DataPointer::~DataPointer() { + reset(); +} + +void DataPointer::zero() { + if (!data_) return; + OPENSSL_cleanse(data_, len_); +} + +void DataPointer::reset(void* data, size_t length) { + if (data_ != nullptr) { + OPENSSL_clear_free(data_, len_); + } + data_ = data; + len_ = length; +} + +void DataPointer::reset(const Buffer& buffer) { + reset(buffer.data, buffer.len); +} + +Buffer DataPointer::release() { + Buffer buf{ + .data = data_, + .len = len_, + }; + data_ = nullptr; + len_ = 0; + return buf; +} + +DataPointer DataPointer::resize(size_t len) { + size_t actual_len = std::min(len_, len); + auto buf = release(); + if (actual_len == len_) return DataPointer(buf); + buf.data = OPENSSL_realloc(buf.data, actual_len); + buf.len = actual_len; + return DataPointer(buf); +} + +// ============================================================================ +bool isFipsEnabled() { +#if OPENSSL_VERSION_MAJOR >= 3 + return EVP_default_properties_is_fips_enabled(nullptr) == 1; +#else + return FIPS_mode() == 1; +#endif +} + +bool setFipsEnabled(bool enable, CryptoErrorList* errors) { + if (isFipsEnabled() == enable) return true; + ClearErrorOnReturn clearErrorOnReturn(errors); +#if OPENSSL_VERSION_MAJOR >= 3 + return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1; +#else + return FIPS_mode_set(enable ? 1 : 0) == 1; +#endif +} + +bool testFipsEnabled() { +#if OPENSSL_VERSION_MAJOR >= 3 + OSSL_PROVIDER* fips_provider = nullptr; + if (OSSL_PROVIDER_available(nullptr, "fips")) { + fips_provider = OSSL_PROVIDER_load(nullptr, "fips"); + } + const auto enabled = fips_provider == nullptr ? 0 + : OSSL_PROVIDER_self_test(fips_provider) ? 1 + : 0; +#else +#ifdef OPENSSL_FIPS + const auto enabled = FIPS_selftest() ? 1 : 0; +#else // OPENSSL_FIPS + const auto enabled = 0; +#endif // OPENSSL_FIPS +#endif + + return enabled; +} + +// ============================================================================ +// Bignum +BignumPointer::BignumPointer(BIGNUM* bignum) : bn_(bignum) {} + +BignumPointer::BignumPointer(const unsigned char* data, size_t len) + : BignumPointer(BN_bin2bn(data, len, nullptr)) {} + +BignumPointer::BignumPointer(BignumPointer&& other) noexcept + : bn_(other.release()) {} + +BignumPointer BignumPointer::New() { + return BignumPointer(BN_new()); +} + +BignumPointer BignumPointer::NewSecure() { +#ifdef OPENSSL_IS_BORINGSSL + // Boringssl does not implement BN_secure_new. + return New(); +#else + return BignumPointer(BN_secure_new()); +#endif +} + +BignumPointer& BignumPointer::operator=(BignumPointer&& other) noexcept { + if (this == &other) return *this; + this->~BignumPointer(); + return *new (this) BignumPointer(std::move(other)); +} + +BignumPointer::~BignumPointer() { + reset(); +} + +void BignumPointer::reset(BIGNUM* bn) { + bn_.reset(bn); +} + +void BignumPointer::reset(const unsigned char* data, size_t len) { + reset(BN_bin2bn(data, len, nullptr)); +} + +BIGNUM* BignumPointer::release() { + return bn_.release(); +} + +size_t BignumPointer::byteLength() const { + if (bn_ == nullptr) return 0; + return BN_num_bytes(bn_.get()); +} + +DataPointer BignumPointer::encode() const { + return EncodePadded(bn_.get(), byteLength()); +} + +DataPointer BignumPointer::encodePadded(size_t size) const { + return EncodePadded(bn_.get(), size); +} + +size_t BignumPointer::encodeInto(unsigned char* out) const { + if (!bn_) return 0; + return BN_bn2bin(bn_.get(), out); +} + +size_t BignumPointer::encodePaddedInto(unsigned char* out, size_t size) const { + if (!bn_) return 0; + return BN_bn2binpad(bn_.get(), out, size); +} + +DataPointer BignumPointer::Encode(const BIGNUM* bn) { + return EncodePadded(bn, bn != nullptr ? BN_num_bytes(bn) : 0); +} + +bool BignumPointer::setWord(unsigned long w) { // NOLINT(runtime/int) + if (!bn_) return false; + return BN_set_word(bn_.get(), w) == 1; +} + +unsigned long BignumPointer::GetWord(const BIGNUM* bn) { // NOLINT(runtime/int) + return BN_get_word(bn); +} + +unsigned long BignumPointer::getWord() const { // NOLINT(runtime/int) + if (!bn_) return 0; + return GetWord(bn_.get()); +} + +DataPointer BignumPointer::EncodePadded(const BIGNUM* bn, size_t s) { + if (bn == nullptr) return DataPointer(); + size_t size = std::max(s, static_cast(GetByteCount(bn))); + auto buf = DataPointer::Alloc(size); + BN_bn2binpad(bn, reinterpret_cast(buf.get()), size); + return buf; +} +size_t BignumPointer::EncodePaddedInto(const BIGNUM* bn, + unsigned char* out, + size_t size) { + if (bn == nullptr) return 0; + return BN_bn2binpad(bn, out, size); +} + +int BignumPointer::operator<=>(const BignumPointer& other) const noexcept { + if (bn_ == nullptr && other.bn_ != nullptr) return -1; + if (bn_ != nullptr && other.bn_ == nullptr) return 1; + if (bn_ == nullptr && other.bn_ == nullptr) return 0; + return BN_cmp(bn_.get(), other.bn_.get()); +} + +int BignumPointer::operator<=>(const BIGNUM* other) const noexcept { + if (bn_ == nullptr && other != nullptr) return -1; + if (bn_ != nullptr && other == nullptr) return 1; + if (bn_ == nullptr && other == nullptr) return 0; + return BN_cmp(bn_.get(), other); +} + +DataPointer BignumPointer::toHex() const { + if (!bn_) return {}; + char* hex = BN_bn2hex(bn_.get()); + if (!hex) return {}; + return DataPointer(hex, strlen(hex)); +} + +int BignumPointer::GetBitCount(const BIGNUM* bn) { + return BN_num_bits(bn); +} + +int BignumPointer::GetByteCount(const BIGNUM* bn) { + return BN_num_bytes(bn); +} + +bool BignumPointer::isZero() const { + return bn_ && BN_is_zero(bn_.get()); +} + +bool BignumPointer::isOne() const { + return bn_ && BN_is_one(bn_.get()); +} + +const BIGNUM* BignumPointer::One() { + return BN_value_one(); +} + +BignumPointer BignumPointer::clone() { + if (!bn_) return {}; + return BignumPointer(BN_dup(bn_.get())); +} + +int BignumPointer::isPrime(int nchecks, + BignumPointer::PrimeCheckCallback innerCb) const { + BignumCtxPointer ctx(BN_CTX_new()); + BignumGenCallbackPointer cb(nullptr); + if (innerCb != nullptr) { + cb = BignumGenCallbackPointer(BN_GENCB_new()); + if (!cb) [[unlikely]] + return -1; + BN_GENCB_set( + cb.get(), + // TODO(@jasnell): This could be refactored to allow inlining. + // Not too important right now tho. + [](int a, int b, BN_GENCB* ctx) mutable -> int { + PrimeCheckCallback& ptr = + *static_cast(BN_GENCB_get_arg(ctx)); + return ptr(a, b) ? 1 : 0; + }, + &innerCb); + } + return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get()); +} + +BignumPointer BignumPointer::NewPrime(const PrimeConfig& params, + PrimeCheckCallback cb) { + BignumPointer prime(BN_new()); + if (!prime || !prime.generate(params, std::move(cb))) { + return {}; + } + return prime; +} + +bool BignumPointer::generate(const PrimeConfig& params, + PrimeCheckCallback innerCb) const { + // BN_generate_prime_ex() calls RAND_bytes_ex() internally. + // Make sure the CSPRNG is properly seeded. + std::ignore = CSPRNG(nullptr, 0); + BignumGenCallbackPointer cb(nullptr); + if (innerCb != nullptr) { + cb = BignumGenCallbackPointer(BN_GENCB_new()); + if (!cb) [[unlikely]] + return -1; + BN_GENCB_set( + cb.get(), + [](int a, int b, BN_GENCB* ctx) mutable -> int { + PrimeCheckCallback& ptr = + *static_cast(BN_GENCB_get_arg(ctx)); + return ptr(a, b) ? 1 : 0; + }, + &innerCb); + } + if (BN_generate_prime_ex(get(), + params.bits, + params.safe ? 1 : 0, + params.add.get(), + params.rem.get(), + cb.get()) == 0) { + return false; + } + + return true; +} + +BignumPointer BignumPointer::NewSub(const BignumPointer& a, + const BignumPointer& b) { + BignumPointer res = New(); + if (!res) return {}; + if (!BN_sub(res.get(), a.get(), b.get())) { + return {}; + } + return res; +} + +BignumPointer BignumPointer::NewLShift(size_t length) { + BignumPointer res = New(); + if (!res) return {}; + if (!BN_lshift(res.get(), One(), length)) { + return {}; + } + return res; +} + +// ============================================================================ +// Utility methods + +bool CSPRNG(void* buffer, size_t length) { + auto buf = reinterpret_cast(buffer); + do { + if (1 == RAND_status()) { +#if OPENSSL_VERSION_MAJOR >= 3 + if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) { + return true; + } +#else + while (length > INT_MAX && 1 == RAND_bytes(buf, INT_MAX)) { + buf += INT_MAX; + length -= INT_MAX; + } + if (length <= INT_MAX && 1 == RAND_bytes(buf, static_cast(length))) + return true; +#endif + } +#if OPENSSL_VERSION_MAJOR >= 3 + const auto code = ERR_peek_last_error(); + // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() + // and RAND_status() but fail in RAND_bytes() if it cannot look up + // a matching algorithm for the CSPRNG. + if (ERR_GET_LIB(code) == ERR_LIB_RAND) { + const auto reason = ERR_GET_REASON(code); + if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || + reason == RAND_R_UNABLE_TO_FETCH_DRBG || + reason == RAND_R_UNABLE_TO_CREATE_DRBG) { + return false; + } + } +#endif + } while (1 == RAND_poll()); + + return false; +} + +int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { + return 0; +} + +int PasswordCallback(char* buf, int size, int rwflag, void* u) { + auto passphrase = static_cast*>(u); + if (passphrase != nullptr) { + size_t buflen = static_cast(size); + size_t len = passphrase->len; + if (buflen < len) return -1; + memcpy(buf, reinterpret_cast(passphrase->data), len); + return len; + } + + return -1; +} + +// Algorithm: http://howardhinnant.github.io/date_algorithms.html +constexpr int days_from_epoch(int y, unsigned m, unsigned d) { + y -= m <= 2; + const int era = (y >= 0 ? y : y - 399) / 400; + const unsigned yoe = static_cast(y - era * 400); // [0, 399] + const unsigned doy = + (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] + return era * 146097 + static_cast(doe) - 719468; +} + +#ifndef OPENSSL_IS_BORINGSSL +// tm must be in UTC +// using time_t causes problems on 32-bit systems and windows x64. +int64_t PortableTimeGM(struct tm* t) { + int year = t->tm_year + 1900; + int month = t->tm_mon; + if (month > 11) { + year += month / 12; + month %= 12; + } else if (month < 0) { + int years_diff = (11 - month) / 12; + year -= years_diff; + month += 12 * years_diff; + } + int days_since_epoch = days_from_epoch(year, month + 1, t->tm_mday); + + return 60 * (60 * (24LL * static_cast(days_since_epoch) + + t->tm_hour) + + t->tm_min) + + t->tm_sec; +} +#endif + +// ============================================================================ +// SPKAC + +bool VerifySpkac(const char* input, size_t length) { +#ifdef OPENSSL_IS_BORINGSSL + // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, + // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. + // As such, we trim those characters here for compatibility. + // + // find_last_not_of can return npos, which is the maximum value of size_t. + // The + 1 will force a roll-ver to 0, which is the correct value. in that + // case. + length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; +#endif + NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); + if (!spki) return false; + + EVPKeyPointer pkey(X509_PUBKEY_get(spki->spkac->pubkey)); + return pkey ? NETSCAPE_SPKI_verify(spki.get(), pkey.get()) > 0 : false; +} + +BIOPointer ExportPublicKey(const char* input, size_t length) { + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + +#ifdef OPENSSL_IS_BORINGSSL + // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, + // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. + // As such, we trim those characters here for compatibility. + length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; +#endif + NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); + if (!spki) return {}; + + EVPKeyPointer pkey(NETSCAPE_SPKI_get_pubkey(spki.get())); + if (!pkey) return {}; + + if (PEM_write_bio_PUBKEY(bio.get(), pkey.get()) <= 0) return {}; + + return bio; +} + +Buffer ExportChallenge(const char* input, size_t length) { +#ifdef OPENSSL_IS_BORINGSSL + // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, + // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. + // As such, we trim those characters here for compatibility. + length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; +#endif + NetscapeSPKIPointer sp(NETSCAPE_SPKI_b64_decode(input, length)); + if (!sp) return {}; + + unsigned char* buf = nullptr; + int buf_size = ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge); + if (buf_size >= 0) { + return { + .data = reinterpret_cast(buf), + .len = static_cast(buf_size), + }; + } + + return {}; +} + +// ============================================================================ +namespace { +enum class AltNameOption { + NONE, + UTF8, +}; + +bool IsSafeAltName(const char* name, size_t length, AltNameOption option) { + for (size_t i = 0; i < length; i++) { + char c = name[i]; + switch (c) { + case '"': + case '\\': + // These mess with encoding rules. + // Fall through. + case ',': + // Commas make it impossible to split the list of subject alternative + // names unambiguously, which is why we have to escape. + // Fall through. + case '\'': + // Single quotes are unlikely to appear in any legitimate values, but + // they could be used to make a value look like it was escaped (i.e., + // enclosed in single/double quotes). + return false; + default: + if (option == AltNameOption::UTF8) { + // In UTF8 strings, we require escaping for any ASCII control + // character, but NOT for non-ASCII characters. Note that all bytes of + // any code point that consists of more than a single byte have their + // MSB set. + if (static_cast(c) < ' ' || c == '\x7f') { + return false; + } + } else { + // Check if the char is a control character or non-ASCII character. + // Note that char may or may not be a signed type. Regardless, + // non-ASCII values will always be outside of this range. + if (c < ' ' || c > '~') { + return false; + } + } + } + } + return true; +} + +void PrintAltName(const BIOPointer& out, + const char* name, + size_t length, + AltNameOption option = AltNameOption::NONE, + const char* safe_prefix = nullptr) { + if (IsSafeAltName(name, length, option)) { + // For backward-compatibility, append "safe" names without any + // modifications. + if (safe_prefix != nullptr) { + BIO_printf(out.get(), "%s:", safe_prefix); + } + BIO_write(out.get(), name, length); + } else { + // If a name is not "safe", we cannot embed it without special + // encoding. This does not usually happen, but we don't want to hide + // it from the user either. We use JSON compatible escaping here. + BIO_write(out.get(), "\"", 1); + if (safe_prefix != nullptr) { + BIO_printf(out.get(), "%s:", safe_prefix); + } + for (size_t j = 0; j < length; j++) { + char c = static_cast(name[j]); + if (c == '\\') { + BIO_write(out.get(), "\\\\", 2); + } else if (c == '"') { + BIO_write(out.get(), "\\\"", 2); + } else if ((c >= ' ' && c != ',' && c <= '~') || + (option == AltNameOption::UTF8 && (c & 0x80))) { + // Note that the above condition explicitly excludes commas, which means + // that those are encoded as Unicode escape sequences in the "else" + // block. That is not strictly necessary, and Node.js itself would parse + // it correctly either way. We only do this to account for third-party + // code that might be splitting the string at commas (as Node.js itself + // used to do). + BIO_write(out.get(), &c, 1); + } else { + // Control character or non-ASCII character. We treat everything as + // Latin-1, which corresponds to the first 255 Unicode code points. + const char hex[] = "0123456789abcdef"; + char u[] = {'\\', 'u', '0', '0', hex[(c & 0xf0) >> 4], hex[c & 0x0f]}; + BIO_write(out.get(), u, sizeof(u)); + } + } + BIO_write(out.get(), "\"", 1); + } +} + +// This function emulates the behavior of i2v_GENERAL_NAME in a safer and less +// ambiguous way. "othername:" entries use the GENERAL_NAME_print format. +bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { + if (gen->type == GEN_DNS) { + ASN1_IA5STRING* name = gen->d.dNSName; + BIO_write(out.get(), "DNS:", 4); + // Note that the preferred name syntax (see RFCs 5280 and 1034) with + // wildcards is a subset of what we consider "safe", so spec-compliant DNS + // names will never need to be escaped. + PrintAltName(out, reinterpret_cast(name->data), name->length); + } else if (gen->type == GEN_EMAIL) { + ASN1_IA5STRING* name = gen->d.rfc822Name; + BIO_write(out.get(), "email:", 6); + PrintAltName(out, reinterpret_cast(name->data), name->length); + } else if (gen->type == GEN_URI) { + ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; + BIO_write(out.get(), "URI:", 4); + // The set of "safe" names was designed to include just about any URI, + // with a few exceptions, most notably URIs that contains commas (see + // RFC 2396). In other words, most legitimate URIs will not require + // escaping. + PrintAltName(out, reinterpret_cast(name->data), name->length); + } else if (gen->type == GEN_DIRNAME) { + // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME + // object. The format was non standard and should be avoided. The use of + // X509_NAME_oneline is discouraged by OpenSSL but was required for backward + // compatibility. Conveniently, X509_NAME_oneline produced ASCII and the + // output was unlikely to contains commas or other characters that would + // require escaping. However, it SHOULD NOT produce ASCII output since an + // RFC5280 AttributeValue may be a UTF8String. + // Newer versions of Node.js have since switched to X509_NAME_print_ex to + // produce a better format at the cost of backward compatibility. The new + // format may contain Unicode characters and it is likely to contain commas, + // which require escaping. Fortunately, the recently safeguarded function + // PrintAltName handles all of that safely. + BIO_printf(out.get(), "DirName:"); + BIOPointer tmp(BIO_new(BIO_s_mem())); + NCRYPTO_ASSERT_TRUE(tmp); + if (X509_NAME_print_ex( + tmp.get(), gen->d.dirn, 0, kX509NameFlagsRFC2253WithinUtf8JSON) < + 0) { + return false; + } + char* oline = nullptr; + long n_bytes = BIO_get_mem_data(tmp.get(), &oline); // NOLINT(runtime/int) + NCRYPTO_ASSERT_TRUE(n_bytes >= 0); + PrintAltName(out, + oline, + static_cast(n_bytes), + ncrypto::AltNameOption::UTF8, + nullptr); + } else if (gen->type == GEN_IPADD) { + BIO_printf(out.get(), "IP Address:"); + const ASN1_OCTET_STRING* ip = gen->d.ip; + const unsigned char* b = ip->data; + if (ip->length == 4) { + BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); + } else if (ip->length == 16) { + for (unsigned int j = 0; j < 8; j++) { + uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; + BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); + } + } else { +#if OPENSSL_VERSION_MAJOR >= 3 + BIO_printf(out.get(), "", ip->length); +#else + BIO_printf(out.get(), ""); +#endif + } + } else if (gen->type == GEN_RID) { + // Unlike OpenSSL's default implementation, never print the OID as text and + // instead always print its numeric representation. + char oline[256]; + OBJ_obj2txt(oline, sizeof(oline), gen->d.rid, true); + BIO_printf(out.get(), "Registered ID:%s", oline); + } else if (gen->type == GEN_OTHERNAME) { + // The format that is used here is based on OpenSSL's implementation of + // GENERAL_NAME_print (as of OpenSSL 3.0.1). Earlier versions of Node.js + // instead produced the same format as i2v_GENERAL_NAME, which was somewhat + // awkward, especially when passed to translatePeerCertificate. + bool unicode = true; + const char* prefix = nullptr; + // OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print and may + // not define these NIDs. +#if OPENSSL_VERSION_MAJOR >= 3 + int nid = OBJ_obj2nid(gen->d.otherName->type_id); + switch (nid) { + case NID_id_on_SmtpUTF8Mailbox: + prefix = "SmtpUTF8Mailbox"; + break; + case NID_XmppAddr: + prefix = "XmppAddr"; + break; + case NID_SRVName: + prefix = "SRVName"; + unicode = false; + break; + case NID_ms_upn: + prefix = "UPN"; + break; + case NID_NAIRealm: + prefix = "NAIRealm"; + break; + } +#endif // OPENSSL_VERSION_MAJOR >= 3 + int val_type = gen->d.otherName->value->type; + if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) || + (!unicode && val_type != V_ASN1_IA5STRING)) { + BIO_printf(out.get(), "othername:"); + } else { + BIO_printf(out.get(), "othername:"); + if (unicode) { + auto name = gen->d.otherName->value->value.utf8string; + PrintAltName(out, + reinterpret_cast(name->data), + name->length, + AltNameOption::UTF8, + prefix); + } else { + auto name = gen->d.otherName->value->value.ia5string; + PrintAltName(out, + reinterpret_cast(name->data), + name->length, + AltNameOption::NONE, + prefix); + } + } + } else if (gen->type == GEN_X400) { + // TODO(tniessen): this is what OpenSSL does, implement properly instead + BIO_printf(out.get(), "X400Name:"); + } else if (gen->type == GEN_EDIPARTY) { + // TODO(tniessen): this is what OpenSSL does, implement properly instead + BIO_printf(out.get(), "EdiPartyName:"); + } else { + // This is safe because X509V3_EXT_d2i would have returned nullptr in this + // case already. + unreachable(); + } + + return true; +} +} // namespace + +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); + if (ret != NID_subject_alt_name) return false; + + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + if (names == nullptr) return false; + + bool ok = true; + + for (OPENSSL_SIZE_T i = 0; i < sk_GENERAL_NAME_num(names); i++) { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); + + if (i != 0) BIO_write(out.get(), ", ", 2); + + if (!(ok = ncrypto::PrintGeneralName(out, gen))) { + break; + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return ok; +} + +bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); + if (ret != NID_info_access) return false; + + AUTHORITY_INFO_ACCESS* descs = + static_cast(X509V3_EXT_d2i(ext)); + if (descs == nullptr) return false; + + bool ok = true; + + for (OPENSSL_SIZE_T i = 0; i < sk_ACCESS_DESCRIPTION_num(descs); i++) { + ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i); + + if (i != 0) BIO_write(out.get(), "\n", 1); + + char objtmp[80]; + i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method); + BIO_printf(out.get(), "%s - ", objtmp); + if (!(ok = ncrypto::PrintGeneralName(out, desc->location))) { + break; + } + } + sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free); + +#if OPENSSL_VERSION_MAJOR < 3 + BIO_write(out.get(), "\n", 1); +#endif + + return ok; +} + +// ============================================================================ +// X509Pointer + +X509Pointer::X509Pointer(X509* x509) : cert_(x509) {} + +X509Pointer::X509Pointer(X509Pointer&& other) noexcept + : cert_(other.release()) {} + +X509Pointer& X509Pointer::operator=(X509Pointer&& other) noexcept { + if (this == &other) return *this; + this->~X509Pointer(); + return *new (this) X509Pointer(std::move(other)); +} + +X509Pointer::~X509Pointer() { + reset(); +} + +void X509Pointer::reset(X509* x509) { + cert_.reset(x509); +} + +X509* X509Pointer::release() { + return cert_.release(); +} + +X509View X509Pointer::view() const { + return X509View(cert_.get()); +} + +BIOPointer X509View::toPEM() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + if (PEM_write_bio_X509(bio.get(), const_cast(cert_)) <= 0) return {}; + return bio; +} + +BIOPointer X509View::toDER() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + if (i2d_X509_bio(bio.get(), const_cast(cert_)) <= 0) return {}; + return bio; +} + +const X509Name X509View::getSubjectName() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + return X509Name(X509_get_subject_name(cert_)); +} + +const X509Name X509View::getIssuerName() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + return X509Name(X509_get_issuer_name(cert_)); +} + +BIOPointer X509View::getSubject() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + if (X509_NAME_print_ex(bio.get(), + X509_get_subject_name(cert_), + 0, + kX509NameFlagsMultiline) <= 0) { + return {}; + } + return bio; +} + +BIOPointer X509View::getSubjectAltName() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + int index = X509_get_ext_by_NID(cert_, NID_subject_alt_name, -1); + if (index < 0 || + !SafeX509SubjectAltNamePrint(bio, X509_get_ext(cert_, index))) { + return {}; + } + return bio; +} + +BIOPointer X509View::getIssuer() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + if (X509_NAME_print_ex( + bio.get(), X509_get_issuer_name(cert_), 0, kX509NameFlagsMultiline) <= + 0) { + return {}; + } + return bio; +} + +BIOPointer X509View::getInfoAccess() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + int index = X509_get_ext_by_NID(cert_, NID_info_access, -1); + if (index < 0) return {}; + if (!SafeX509InfoAccessPrint(bio, X509_get_ext(cert_, index))) { + return {}; + } + return bio; +} + +BIOPointer X509View::getValidFrom() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_)); + return bio; +} + +BIOPointer X509View::getValidTo() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + BIOPointer bio(BIO_new(BIO_s_mem())); + if (!bio) return {}; + ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_)); + return bio; +} + +int64_t X509View::getValidToTime() const { +#ifdef OPENSSL_IS_BORINGSSL + // Boringssl does not implement ASN1_TIME_to_tm in a public way, + // and only recently added ASN1_TIME_to_posix. Some boringssl + // users on older version may still need to patch around this + // or use a different implementation. + int64_t tp; + ASN1_TIME_to_posix(X509_get0_notAfter(cert_), &tp); + return tp; +#else + struct tm tp; + ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp); + return PortableTimeGM(&tp); +#endif +} + +int64_t X509View::getValidFromTime() const { +#ifdef OPENSSL_IS_BORINGSSL + int64_t tp; + ASN1_TIME_to_posix(X509_get0_notBefore(cert_), &tp); + return tp; +#else + struct tm tp; + ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp); + return PortableTimeGM(&tp); +#endif +} + +DataPointer X509View::getSerialNumber() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + if (ASN1_INTEGER* serial_number = + X509_get_serialNumber(const_cast(cert_))) { + if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) { + return bn.toHex(); + } + } + return {}; +} + +Result X509View::getPublicKey() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return Result(EVPKeyPointer{}); + auto pkey = EVPKeyPointer(X509_get_pubkey(const_cast(cert_))); + if (!pkey) return Result(ERR_get_error()); + return pkey; +} + +StackOfASN1 X509View::getKeyUsage() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + return StackOfASN1(static_cast( + X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); +} + +bool X509View::isCA() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return false; + return X509_check_ca(const_cast(cert_)) == 1; +} + +bool X509View::isIssuedBy(const X509View& issuer) const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr || issuer.cert_ == nullptr) return false; + return X509_check_issued(const_cast(issuer.cert_), + const_cast(cert_)) == X509_V_OK; +} + +bool X509View::checkPrivateKey(const EVPKeyPointer& pkey) const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr || pkey == nullptr) return false; + return X509_check_private_key(const_cast(cert_), pkey.get()) == 1; +} + +bool X509View::checkPublicKey(const EVPKeyPointer& pkey) const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr || pkey == nullptr) return false; + return X509_verify(const_cast(cert_), pkey.get()) == 1; +} + +X509View::CheckMatch X509View::checkHost(const std::string_view host, + int flags, + DataPointer* peerName) const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return CheckMatch::NO_MATCH; + char* peername; + switch (X509_check_host( + const_cast(cert_), host.data(), host.size(), flags, &peername)) { + case 0: + return CheckMatch::NO_MATCH; + case 1: { + if (peername != nullptr) { + DataPointer name(peername, strlen(peername)); + if (peerName != nullptr) *peerName = std::move(name); + } + return CheckMatch::MATCH; + } + case -2: + return CheckMatch::INVALID_NAME; + default: + return CheckMatch::OPERATION_FAILED; + } +} + +X509View::CheckMatch X509View::checkEmail(const std::string_view email, + int flags) const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return CheckMatch::NO_MATCH; + switch (X509_check_email( + const_cast(cert_), email.data(), email.size(), flags)) { + case 0: + return CheckMatch::NO_MATCH; + case 1: + return CheckMatch::MATCH; + case -2: + return CheckMatch::INVALID_NAME; + default: + return CheckMatch::OPERATION_FAILED; + } +} + +X509View::CheckMatch X509View::checkIp(const std::string_view ip, + int flags) const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return CheckMatch::NO_MATCH; + switch (X509_check_ip_asc(const_cast(cert_), ip.data(), flags)) { + case 0: + return CheckMatch::NO_MATCH; + case 1: + return CheckMatch::MATCH; + case -2: + return CheckMatch::INVALID_NAME; + default: + return CheckMatch::OPERATION_FAILED; + } +} + +X509View X509View::From(const SSLPointer& ssl) { + ClearErrorOnReturn clear_error_on_return; + if (!ssl) return {}; + return X509View(SSL_get_certificate(ssl.get())); +} + +X509View X509View::From(const SSLCtxPointer& ctx) { + ClearErrorOnReturn clear_error_on_return; + if (!ctx) return {}; + return X509View(SSL_CTX_get0_certificate(ctx.get())); +} + +std::optional X509View::getFingerprint( + const EVP_MD* method) const { + unsigned int md_size; + unsigned char md[EVP_MAX_MD_SIZE]; + static constexpr char hex[] = "0123456789ABCDEF"; + + if (X509_digest(get(), method, md, &md_size)) { + if (md_size == 0) return std::nullopt; + std::string fingerprint((md_size * 3) - 1, 0); + for (unsigned int i = 0; i < md_size; i++) { + auto idx = 3 * i; + fingerprint[idx] = hex[(md[i] & 0xf0) >> 4]; + fingerprint[idx + 1] = hex[(md[i] & 0x0f)]; + if (i == md_size - 1) break; + fingerprint[idx + 2] = ':'; + } + + return fingerprint; + } + + return std::nullopt; +} + +X509Pointer X509View::clone() const { + ClearErrorOnReturn clear_error_on_return; + if (!cert_) return {}; + return X509Pointer(X509_dup(const_cast(cert_))); +} + +Result X509Pointer::Parse( + Buffer buffer) { + ClearErrorOnReturn clearErrorOnReturn; + BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len)); + if (!bio) return Result(ERR_get_error()); + + X509Pointer pem( + PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr)); + if (pem) return Result(std::move(pem)); + BIO_reset(bio.get()); + + X509Pointer der(d2i_X509_bio(bio.get(), nullptr)); + if (der) return Result(std::move(der)); + + return Result(ERR_get_error()); +} + +bool X509View::enumUsages(UsageCallback callback) const { + if (cert_ == nullptr) return false; + StackOfASN1 eku(static_cast( + X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); + if (!eku) return false; + const int count = sk_ASN1_OBJECT_num(eku.get()); + char buf[256]{}; + + for (int i = 0; i < count; i++) { + if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= + 0) { + callback(buf); + } + } + return true; +} + +bool X509View::ifRsa(KeyCallback callback) const { + if (cert_ == nullptr) return true; + OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); + auto id = EVP_PKEY_id(pkey); + if (id == EVP_PKEY_RSA || id == EVP_PKEY_RSA2 || id == EVP_PKEY_RSA_PSS) { + Rsa rsa(EVP_PKEY_get0_RSA(pkey)); + if (!rsa) [[unlikely]] + return true; + return callback(rsa); + } + return true; +} + +bool X509View::ifEc(KeyCallback callback) const { + if (cert_ == nullptr) return true; + OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); + auto id = EVP_PKEY_id(pkey); + if (id == EVP_PKEY_EC) { + Ec ec(EVP_PKEY_get0_EC_KEY(pkey)); + if (!ec) [[unlikely]] + return true; + return callback(ec); + } + return true; +} + +X509Pointer X509Pointer::IssuerFrom(const SSLPointer& ssl, + const X509View& view) { + return IssuerFrom(SSL_get_SSL_CTX(ssl.get()), view); +} + +X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + DeleteFnPtr store_ctx( + X509_STORE_CTX_new()); + X509Pointer result; + X509* issuer; + if (store_ctx.get() != nullptr && + X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && + X509_STORE_CTX_get1_issuer(&issuer, store_ctx.get(), cert.get()) == 1) { + result.reset(issuer); + } + return result; +} + +X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) { + return X509Pointer(SSL_get_peer_certificate(ssl.get())); +} + +// When adding or removing errors below, please also update the list in the API +// documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md +// Also *please* update the respective section in doc/api/tls.md as well +std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) +#define CASE(CODE) \ + case X509_V_ERR_##CODE: \ + return #CODE; + switch (err) { + CASE(UNABLE_TO_GET_ISSUER_CERT) + CASE(UNABLE_TO_GET_CRL) + CASE(UNABLE_TO_DECRYPT_CERT_SIGNATURE) + CASE(UNABLE_TO_DECRYPT_CRL_SIGNATURE) + CASE(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) + CASE(CERT_SIGNATURE_FAILURE) + CASE(CRL_SIGNATURE_FAILURE) + CASE(CERT_NOT_YET_VALID) + CASE(CERT_HAS_EXPIRED) + CASE(CRL_NOT_YET_VALID) + CASE(CRL_HAS_EXPIRED) + CASE(ERROR_IN_CERT_NOT_BEFORE_FIELD) + CASE(ERROR_IN_CERT_NOT_AFTER_FIELD) + CASE(ERROR_IN_CRL_LAST_UPDATE_FIELD) + CASE(ERROR_IN_CRL_NEXT_UPDATE_FIELD) + CASE(OUT_OF_MEM) + CASE(DEPTH_ZERO_SELF_SIGNED_CERT) + CASE(SELF_SIGNED_CERT_IN_CHAIN) + CASE(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + CASE(UNABLE_TO_VERIFY_LEAF_SIGNATURE) + CASE(CERT_CHAIN_TOO_LONG) + CASE(CERT_REVOKED) + CASE(INVALID_CA) + CASE(PATH_LENGTH_EXCEEDED) + CASE(INVALID_PURPOSE) + CASE(CERT_UNTRUSTED) + CASE(CERT_REJECTED) + CASE(HOSTNAME_MISMATCH) + } +#undef CASE + return "UNSPECIFIED"; +} + +std::optional X509Pointer::ErrorReason(int32_t err) { + if (err == X509_V_OK) return std::nullopt; + return X509_verify_cert_error_string(err); +} + +// ============================================================================ +// BIOPointer + +BIOPointer::BIOPointer(BIO* bio) : bio_(bio) {} + +BIOPointer::BIOPointer(BIOPointer&& other) noexcept : bio_(other.release()) {} + +BIOPointer& BIOPointer::operator=(BIOPointer&& other) noexcept { + if (this == &other) return *this; + this->~BIOPointer(); + return *new (this) BIOPointer(std::move(other)); +} + +BIOPointer::~BIOPointer() { + reset(); +} + +void BIOPointer::reset(BIO* bio) { + bio_.reset(bio); +} + +BIO* BIOPointer::release() { + return bio_.release(); +} + +bool BIOPointer::resetBio() const { + if (!bio_) return 0; + return BIO_reset(bio_.get()) == 1; +} + +BIOPointer BIOPointer::NewMem() { + return BIOPointer(BIO_new(BIO_s_mem())); +} + +BIOPointer BIOPointer::NewSecMem() { +#ifdef OPENSSL_IS_BORINGSSL + // Boringssl does not implement the BIO_s_secmem API. + return BIOPointer(BIO_new(BIO_s_mem())); +#else + return BIOPointer(BIO_new(BIO_s_secmem())); +#endif +} + +BIOPointer BIOPointer::New(const BIO_METHOD* method) { + return BIOPointer(BIO_new(method)); +} + +BIOPointer BIOPointer::New(const void* data, size_t len) { + return BIOPointer(BIO_new_mem_buf(data, len)); +} + +BIOPointer BIOPointer::NewFile(std::string_view filename, + std::string_view mode) { + return BIOPointer(BIO_new_file(filename.data(), mode.data())); +} + +BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) { + return BIOPointer(BIO_new_fp(fd, close_flag)); +} + +BIOPointer BIOPointer::New(const BIGNUM* bn) { + auto res = NewMem(); + if (!res || !BN_print(res.get(), bn)) return {}; + return res; +} + +int BIOPointer::Write(BIOPointer* bio, std::string_view message) { + if (bio == nullptr || !*bio) return 0; + return BIO_write(bio->get(), message.data(), message.size()); +} + +// ============================================================================ +// DHPointer + +namespace { +bool EqualNoCase(const std::string_view a, const std::string_view b) { + if (a.size() != b.size()) return false; + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { + return std::tolower(a) == std::tolower(b); + }); +} +} // namespace + +DHPointer::DHPointer(DH* dh) : dh_(dh) {} + +DHPointer::DHPointer(DHPointer&& other) noexcept : dh_(other.release()) {} + +DHPointer& DHPointer::operator=(DHPointer&& other) noexcept { + if (this == &other) return *this; + this->~DHPointer(); + return *new (this) DHPointer(std::move(other)); +} + +DHPointer::~DHPointer() { + reset(); +} + +void DHPointer::reset(DH* dh) { + dh_.reset(dh); +} + +DH* DHPointer::release() { + return dh_.release(); +} + +BignumPointer DHPointer::FindGroup(const std::string_view name, + FindGroupOption option) { +#define V(n, p) \ + if (EqualNoCase(name, n)) return BignumPointer(p(nullptr)); + if (option != FindGroupOption::NO_SMALL_PRIMES) { +#ifndef OPENSSL_IS_BORINGSSL + // Boringssl does not support the 768 and 1024 small primes + V("modp1", BN_get_rfc2409_prime_768); + V("modp2", BN_get_rfc2409_prime_1024); +#endif + V("modp5", BN_get_rfc3526_prime_1536); + } + V("modp14", BN_get_rfc3526_prime_2048); + V("modp15", BN_get_rfc3526_prime_3072); + V("modp16", BN_get_rfc3526_prime_4096); + V("modp17", BN_get_rfc3526_prime_6144); + V("modp18", BN_get_rfc3526_prime_8192); +#undef V + return {}; +} + +BignumPointer DHPointer::GetStandardGenerator() { + auto bn = BignumPointer::New(); + if (!bn) return {}; + if (!bn.setWord(DH_GENERATOR_2)) return {}; + return bn; +} + +DHPointer DHPointer::FromGroup(const std::string_view name, + FindGroupOption option) { + auto group = FindGroup(name, option); + if (!group) return {}; // Unable to find the named group. + + auto generator = GetStandardGenerator(); + if (!generator) return {}; // Unable to create the generator. + + return New(std::move(group), std::move(generator)); +} + +DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) { + if (!p || !g) return {}; + + DHPointer dh(DH_new()); + if (!dh) return {}; + + if (DH_set0_pqg(dh.get(), p.get(), nullptr, g.get()) != 1) return {}; + + // If the call above is successful, the DH object takes ownership of the + // BIGNUMs, so we must release them here. Unfortunately coverity does not + // know that so we need to tell it not to complain. + // coverity[resource_leak] + p.release(); + // coverity[resource_leak] + g.release(); + + return dh; +} + +DHPointer DHPointer::New(size_t bits, unsigned int generator) { + DHPointer dh(DH_new()); + if (!dh) return {}; + + if (DH_generate_parameters_ex(dh.get(), bits, generator, nullptr) != 1) { + return {}; + } + + return dh; +} + +DHPointer::CheckResult DHPointer::check() { + ClearErrorOnReturn clearErrorOnReturn; + if (!dh_) return DHPointer::CheckResult::NONE; + int codes = 0; + if (DH_check(dh_.get(), &codes) != 1) + return DHPointer::CheckResult::CHECK_FAILED; + return static_cast(codes); +} + +DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( + const BignumPointer& pub_key) { + ClearErrorOnReturn clearErrorOnReturn; + if (!pub_key || !dh_) { + return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + } + int codes = 0; + if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) { + return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + } +#ifndef OPENSSL_IS_BORINGSSL + // Boringssl does not define DH_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE + if (codes & DH_CHECK_PUBKEY_TOO_SMALL) { + return DHPointer::CheckPublicKeyResult::TOO_SMALL; + } else if (codes & DH_CHECK_PUBKEY_TOO_LARGE) { + return DHPointer::CheckPublicKeyResult::TOO_LARGE; + } +#endif + if (codes != 0) { + return DHPointer::CheckPublicKeyResult::INVALID; + } + return CheckPublicKeyResult::NONE; +} + +DataPointer DHPointer::getPrime() const { + if (!dh_) return {}; + const BIGNUM* p; + DH_get0_pqg(dh_.get(), &p, nullptr, nullptr); + return BignumPointer::Encode(p); +} + +DataPointer DHPointer::getGenerator() const { + if (!dh_) return {}; + const BIGNUM* g; + DH_get0_pqg(dh_.get(), nullptr, nullptr, &g); + return BignumPointer::Encode(g); +} + +DataPointer DHPointer::getPublicKey() const { + if (!dh_) return {}; + const BIGNUM* pub_key; + DH_get0_key(dh_.get(), &pub_key, nullptr); + return BignumPointer::Encode(pub_key); +} + +DataPointer DHPointer::getPrivateKey() const { + if (!dh_) return {}; + const BIGNUM* pvt_key; + DH_get0_key(dh_.get(), nullptr, &pvt_key); + return BignumPointer::Encode(pvt_key); +} + +DataPointer DHPointer::generateKeys() const { + ClearErrorOnReturn clearErrorOnReturn; + if (!dh_) return {}; + + // Key generation failed + if (!DH_generate_key(dh_.get())) return {}; + + return getPublicKey(); +} + +size_t DHPointer::size() const { + if (!dh_) return 0; + int ret = DH_size(dh_.get()); + // DH_size can return a -1 on error but we just want to return a 0 + // in that case so we don't wrap around when returning the size_t. + return ret >= 0 ? static_cast(ret) : 0; +} + +DataPointer DHPointer::computeSecret(const BignumPointer& peer) const { + ClearErrorOnReturn clearErrorOnReturn; + if (!dh_ || !peer) return {}; + + auto dp = DataPointer::Alloc(size()); + if (!dp) return {}; + + int size = + DH_compute_key(static_cast(dp.get()), peer.get(), dh_.get()); + if (size < 0) return {}; + + // The size of the computed key can be smaller than the size of the DH key. + // We want to make sure that the key is correctly padded. + if (static_cast(size) < dp.size()) { + const size_t padding = dp.size() - size; + uint8_t* data = static_cast(dp.get()); + memmove(data + padding, data, size); + memset(data, 0, padding); + } + + return dp; +} + +bool DHPointer::setPublicKey(BignumPointer&& key) { + if (!dh_) return false; + if (DH_set0_key(dh_.get(), key.get(), nullptr) == 1) { + // If DH_set0_key returns successfully, then dh_ takes ownership of the + // BIGNUM, so we must release it here. Unfortunately coverity does not + // know that so we need to tell it not to complain. + // coverity[resource_leak] + key.release(); + return true; + } + return false; +} + +bool DHPointer::setPrivateKey(BignumPointer&& key) { + if (!dh_) return false; + if (DH_set0_key(dh_.get(), nullptr, key.get()) == 1) { + // If DH_set0_key returns successfully, then dh_ takes ownership of the + // BIGNUM, so we must release it here. Unfortunately coverity does not + // know that so we need to tell it not to complain. + // coverity[resource_leak] + key.release(); + return true; + } + return false; +} + +DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, + const EVPKeyPointer& theirKey) { + size_t out_size; + if (!ourKey || !theirKey) return {}; + + auto ctx = EVPKeyCtxPointer::New(ourKey); + if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 || + EVP_PKEY_derive_set_peer(ctx.get(), theirKey.get()) <= 0 || + EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0) { + return {}; + } + + if (out_size == 0) return {}; + + auto out = DataPointer::Alloc(out_size); + if (EVP_PKEY_derive( + ctx.get(), reinterpret_cast(out.get()), &out_size) <= 0) { + return {}; + } + + if (out_size < out.size()) { + const size_t padding = out.size() - out_size; + uint8_t* data = static_cast(out.get()); + memmove(data + padding, data, out_size); + memset(data, 0, padding); + } + + return out; +} + +// ============================================================================ +// KDF + +const EVP_MD* getDigestByName(const std::string_view name) { + // Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 + // exposed through the public API. + if (name == "dss1" || name == "DSS1") [[unlikely]] { + return EVP_sha1(); + } + return EVP_get_digestbyname(name.data()); +} + +const EVP_CIPHER* getCipherByName(const std::string_view name) { + return EVP_get_cipherbyname(name.data()); +} + +bool checkHkdfLength(const EVP_MD* md, size_t length) { + // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as + // the output of the hash function. 255 is a hard limit because HKDF appends + // an 8-bit counter to each HMAC'd message, starting at 1. + static constexpr size_t kMaxDigestMultiplier = 255; + size_t max_length = EVP_MD_size(md) * kMaxDigestMultiplier; + if (length > max_length) return false; + return true; +} + +DataPointer hkdf(const EVP_MD* md, + const Buffer& key, + const Buffer& info, + const Buffer& salt, + size_t length) { + ClearErrorOnReturn clearErrorOnReturn; + + if (!checkHkdfLength(md, length) || info.len > INT_MAX || + salt.len > INT_MAX) { + return {}; + } + + auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_HKDF); + if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || + !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) || + !EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) { + return {}; + } + + std::string_view actual_salt; + static const char default_salt[EVP_MAX_MD_SIZE] = {0}; + if (salt.len > 0) { + actual_salt = {reinterpret_cast(salt.data), salt.len}; + } else { + actual_salt = {default_salt, static_cast(EVP_MD_size(md))}; + } + + // We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead + // implement the extraction step ourselves because EVP_PKEY_derive does not + // handle zero-length keys, which are required for Web Crypto. + // TODO(jasnell): Once OpenSSL 1.1.1 support is dropped completely, and once + // BoringSSL is confirmed to support it, wen can hopefully drop this and use + // EVP_KDF directly which does support zero length keys. + unsigned char pseudorandom_key[EVP_MAX_MD_SIZE]; + unsigned pseudorandom_key_len = sizeof(pseudorandom_key); + + if (HMAC(md, + actual_salt.data(), + actual_salt.size(), + key.data, + key.len, + pseudorandom_key, + &pseudorandom_key_len) == nullptr) { + return {}; + } + if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || + !EVP_PKEY_CTX_set1_hkdf_key( + ctx.get(), pseudorandom_key, pseudorandom_key_len)) { + return {}; + } + + auto buf = DataPointer::Alloc(length); + if (!buf) return {}; + + if (EVP_PKEY_derive( + ctx.get(), static_cast(buf.get()), &length) <= 0) { + return {}; + } + + return buf; +} + +bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) { + return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) == + 1; +} + +DataPointer scrypt(const Buffer& pass, + const Buffer& salt, + uint64_t N, + uint64_t r, + uint64_t p, + uint64_t maxmem, + size_t length) { + ClearErrorOnReturn clearErrorOnReturn; + + if (pass.len > INT_MAX || salt.len > INT_MAX) { + return {}; + } + + auto dp = DataPointer::Alloc(length); + if (dp && EVP_PBE_scrypt(pass.data, + pass.len, + salt.data, + salt.len, + N, + r, + p, + maxmem, + reinterpret_cast(dp.get()), + length)) { + return dp; + } + + return {}; +} + +DataPointer pbkdf2(const EVP_MD* md, + const Buffer& pass, + const Buffer& salt, + uint32_t iterations, + size_t length) { + ClearErrorOnReturn clearErrorOnReturn; + + if (pass.len > INT_MAX || salt.len > INT_MAX || length > INT_MAX) { + return {}; + } + + auto dp = DataPointer::Alloc(length); + if (dp && PKCS5_PBKDF2_HMAC(pass.data, + pass.len, + salt.data, + salt.len, + iterations, + md, + length, + reinterpret_cast(dp.get()))) { + return dp; + } + + return {}; +} + +// ============================================================================ + +EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( + const PrivateKeyEncodingConfig& other) + : PrivateKeyEncodingConfig( + other.output_key_object, other.format, other.type) { + cipher = other.cipher; + if (other.passphrase.has_value()) { + auto& otherPassphrase = other.passphrase.value(); + auto newPassphrase = DataPointer::Alloc(otherPassphrase.size()); + memcpy(newPassphrase.get(), otherPassphrase.get(), otherPassphrase.size()); + passphrase = std::move(newPassphrase); + } +} + +EVPKeyPointer::AsymmetricKeyEncodingConfig::AsymmetricKeyEncodingConfig( + bool output_key_object, PKFormatType format, PKEncodingType type) + : output_key_object(output_key_object), format(format), type(type) {} + +EVPKeyPointer::PrivateKeyEncodingConfig& +EVPKeyPointer::PrivateKeyEncodingConfig::operator=( + const PrivateKeyEncodingConfig& other) { + if (this == &other) return *this; + this->~PrivateKeyEncodingConfig(); + return *new (this) PrivateKeyEncodingConfig(other); +} + +EVPKeyPointer EVPKeyPointer::New() { + return EVPKeyPointer(EVP_PKEY_new()); +} + +EVPKeyPointer EVPKeyPointer::NewRawPublic( + int id, const Buffer& data) { + if (id == 0) return {}; + return EVPKeyPointer( + EVP_PKEY_new_raw_public_key(id, nullptr, data.data, data.len)); +} + +EVPKeyPointer EVPKeyPointer::NewRawPrivate( + int id, const Buffer& data) { + if (id == 0) return {}; + return EVPKeyPointer( + EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); +} + +EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { + if (!dh) return {}; + auto key = New(); + if (!key) return {}; + if (EVP_PKEY_assign_DH(key.get(), dh.get())) { + dh.release(); + } + return key; +} + +EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) { + if (!rsa) return {}; + auto key = New(); + if (!key) return {}; + if (EVP_PKEY_assign_RSA(key.get(), rsa.get())) { + rsa.release(); + } + return key; +} + +EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {} + +EVPKeyPointer::EVPKeyPointer(EVPKeyPointer&& other) noexcept + : pkey_(other.release()) {} + +EVPKeyPointer& EVPKeyPointer::operator=(EVPKeyPointer&& other) noexcept { + if (this == &other) return *this; + this->~EVPKeyPointer(); + return *new (this) EVPKeyPointer(std::move(other)); +} + +EVPKeyPointer::~EVPKeyPointer() { + reset(); +} + +void EVPKeyPointer::reset(EVP_PKEY* pkey) { + pkey_.reset(pkey); +} + +EVP_PKEY* EVPKeyPointer::release() { + return pkey_.release(); +} + +int EVPKeyPointer::id(const EVP_PKEY* key) { + if (key == nullptr) return 0; + return EVP_PKEY_id(key); +} + +int EVPKeyPointer::base_id(const EVP_PKEY* key) { + if (key == nullptr) return 0; + return EVP_PKEY_base_id(key); +} + +int EVPKeyPointer::id() const { + return id(get()); +} + +int EVPKeyPointer::base_id() const { + return base_id(get()); +} + +int EVPKeyPointer::bits() const { + if (get() == nullptr) return 0; + return EVP_PKEY_bits(get()); +} + +size_t EVPKeyPointer::size() const { + if (get() == nullptr) return 0; + return EVP_PKEY_size(get()); +} + +EVPKeyCtxPointer EVPKeyPointer::newCtx() const { + if (!pkey_) return {}; + return EVPKeyCtxPointer::New(*this); +} + +size_t EVPKeyPointer::rawPublicKeySize() const { + if (!pkey_) return 0; + size_t len = 0; + if (EVP_PKEY_get_raw_public_key(get(), nullptr, &len) == 1) return len; + return 0; +} + +size_t EVPKeyPointer::rawPrivateKeySize() const { + if (!pkey_) return 0; + size_t len = 0; + if (EVP_PKEY_get_raw_private_key(get(), nullptr, &len) == 1) return len; + return 0; +} + +DataPointer EVPKeyPointer::rawPublicKey() const { + if (!pkey_) return {}; + if (auto data = DataPointer::Alloc(rawPublicKeySize())) { + const Buffer buf = data; + size_t len = data.size(); + if (EVP_PKEY_get_raw_public_key(get(), buf.data, &len) != 1) return {}; + return data; + } + return {}; +} + +DataPointer EVPKeyPointer::rawPrivateKey() const { + if (!pkey_) return {}; + if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { + const Buffer buf = data; + size_t len = data.size(); + if (EVP_PKEY_get_raw_private_key(get(), buf.data, &len) != 1) return {}; + return data; + } + return {}; +} + +BIOPointer EVPKeyPointer::derPublicKey() const { + if (!pkey_) return {}; + auto bio = BIOPointer::NewMem(); + if (!bio) return {}; + if (!i2d_PUBKEY_bio(bio.get(), get())) return {}; + return bio; +} + +bool EVPKeyPointer::assign(const ECKeyPointer& eckey) { + if (!pkey_ || !eckey) return {}; + return EVP_PKEY_assign_EC_KEY(pkey_.get(), eckey.get()); +} + +bool EVPKeyPointer::set(const ECKeyPointer& eckey) { + if (!pkey_ || !eckey) return false; + return EVP_PKEY_set1_EC_KEY(pkey_.get(), eckey); +} + +EVPKeyPointer::operator const EC_KEY*() const { + if (!pkey_) return nullptr; + return EVP_PKEY_get0_EC_KEY(pkey_.get()); +} + +namespace { +EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(const BIOPointer& bp, + const char* name, + auto&& parse) { + if (!bp.resetBio()) { + return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); + } + unsigned char* der_data; + long der_len; // NOLINT(runtime/int) + + // This skips surrounding data and decodes PEM to DER. + { + MarkPopErrorOnReturn mark_pop_error_on_return; + if (PEM_bytes_read_bio( + &der_data, &der_len, nullptr, name, bp.get(), nullptr, nullptr) != + 1) + return EVPKeyPointer::ParseKeyResult( + EVPKeyPointer::PKParseError::NOT_RECOGNIZED); + } + DataPointer data(der_data, der_len); + + // OpenSSL might modify the pointer, so we need to make a copy before parsing. + const unsigned char* p = der_data; + EVPKeyPointer pkey(parse(&p, der_len)); + if (!pkey) + return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); + return EVPKeyPointer::ParseKeyResult(std::move(pkey)); +} + +constexpr bool IsASN1Sequence(const unsigned char* data, + size_t size, + size_t* data_offset, + size_t* data_size) { + if (size < 2 || data[0] != 0x30) return false; + + if (data[1] & 0x80) { + // Long form. + size_t n_bytes = data[1] & ~0x80; + if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) return false; + size_t length = 0; + for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2]; + *data_offset = 2 + n_bytes; + *data_size = std::min(size - 2 - n_bytes, length); + } else { + // Short form. + *data_offset = 2; + *data_size = std::min(size - 2, data[1]); + } + + return true; +} + +constexpr bool IsEncryptedPrivateKeyInfo( + const Buffer& buffer) { + // Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE. + if (buffer.len == 0 || buffer.data == nullptr) return false; + size_t offset, len; + if (!IsASN1Sequence(buffer.data, buffer.len, &offset, &len)) return false; + + // A PrivateKeyInfo sequence always starts with an integer whereas an + // EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier. + return len >= 1 && buffer.data[offset] != 2; +} + +} // namespace + +bool EVPKeyPointer::IsRSAPrivateKey(const Buffer& buffer) { + // Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE. + size_t offset, len; + if (!IsASN1Sequence(buffer.data, buffer.len, &offset, &len)) return false; + + // An RSAPrivateKey sequence always starts with a single-byte integer whose + // value is either 0 or 1, whereas an RSAPublicKey starts with the modulus + // (which is the product of two primes and therefore at least 4), so we can + // decide the type of the structure based on the first three bytes of the + // sequence. + return len >= 3 && buffer.data[offset] == 2 && buffer.data[offset + 1] == 1 && + !(buffer.data[offset + 2] & 0xfe); +} + +EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM( + const Buffer& buffer) { + auto bp = BIOPointer::New(buffer.data, buffer.len); + if (!bp) return ParseKeyResult(PKParseError::FAILED); + + // Try parsing as SubjectPublicKeyInfo (SPKI) first. + if (auto ret = TryParsePublicKeyInner( + bp, + "PUBLIC KEY", + [](const unsigned char** p, long l) { // NOLINT(runtime/int) + return d2i_PUBKEY(nullptr, p, l); + })) { + return ret; + } + + // Maybe it is PKCS#1. + if (auto ret = TryParsePublicKeyInner( + bp, + "RSA PUBLIC KEY", + [](const unsigned char** p, long l) { // NOLINT(runtime/int) + return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l); + })) { + return ret; + } + + // X.509 fallback. + if (auto ret = TryParsePublicKeyInner( + bp, + "CERTIFICATE", + [](const unsigned char** p, long l) { // NOLINT(runtime/int) + X509Pointer x509(d2i_X509(nullptr, p, l)); + return x509 ? X509_get_pubkey(x509.get()) : nullptr; + })) { + return ret; + }; + + return ParseKeyResult(PKParseError::NOT_RECOGNIZED); +} + +EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( + const PublicKeyEncodingConfig& config, + const Buffer& buffer) { + if (config.format == PKFormatType::PEM) { + return TryParsePublicKeyPEM(buffer); + } + + if (config.format != PKFormatType::DER) { + return ParseKeyResult(PKParseError::FAILED); + } + + const unsigned char* start = buffer.data; + + EVP_PKEY* key = nullptr; + + if (config.type == PKEncodingType::PKCS1 && + (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { + return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); + } + + if (config.type == PKEncodingType::SPKI && + (key = d2i_PUBKEY(nullptr, &start, buffer.len))) { + return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); + } + + return ParseKeyResult(PKParseError::FAILED); +} + +namespace { +Buffer GetPassphrase( + const EVPKeyPointer::PrivateKeyEncodingConfig& config) { + Buffer pass{ + // OpenSSL will not actually dereference this pointer, so it can be any + // non-null pointer. We cannot assert that directly, which is why we + // intentionally use a pointer that will likely cause a segmentation fault + // when dereferenced. + .data = reinterpret_cast(-1), + .len = 0, + }; + if (config.passphrase.has_value()) { + auto& passphrase = config.passphrase.value(); + // The pass.data can't be a nullptr, even if the len is zero or else + // openssl will prompt for a password and we really don't want that. + if (passphrase.get() != nullptr) { + pass.data = static_cast(passphrase.get()); + } + pass.len = passphrase.size(); + } + return pass; +} +} // namespace + +EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( + const PrivateKeyEncodingConfig& config, + const Buffer& buffer) { + static constexpr auto keyOrError = [](EVPKeyPointer pkey, + bool had_passphrase = false) { + if (int err = ERR_peek_error()) { + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ && !had_passphrase) { + return ParseKeyResult(PKParseError::NEED_PASSPHRASE); + } + return ParseKeyResult(PKParseError::FAILED, err); + } + if (!pkey) return ParseKeyResult(PKParseError::FAILED); + return ParseKeyResult(std::move(pkey)); + }; + + auto bio = BIOPointer::New(buffer); + if (!bio) return ParseKeyResult(PKParseError::FAILED); + + auto passphrase = GetPassphrase(config); + + if (config.format == PKFormatType::PEM) { + auto key = PEM_read_bio_PrivateKey( + bio.get(), + nullptr, + PasswordCallback, + config.passphrase.has_value() ? &passphrase : nullptr); + return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); + } + + if (config.format != PKFormatType::DER) { + return ParseKeyResult(PKParseError::FAILED); + } + + switch (config.type) { + case PKEncodingType::PKCS1: { + auto key = d2i_PrivateKey_bio(bio.get(), nullptr); + return keyOrError(EVPKeyPointer(key)); + } + case PKEncodingType::PKCS8: { + if (IsEncryptedPrivateKeyInfo(buffer)) { + auto key = d2i_PKCS8PrivateKey_bio( + bio.get(), + nullptr, + PasswordCallback, + config.passphrase.has_value() ? &passphrase : nullptr); + return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); + } + + PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); + if (!p8inf) { + return ParseKeyResult(PKParseError::FAILED, ERR_peek_error()); + } + return keyOrError(EVPKeyPointer(EVP_PKCS82PKEY(p8inf.get()))); + } + case PKEncodingType::SEC1: { + auto key = d2i_PrivateKey_bio(bio.get(), nullptr); + return keyOrError(EVPKeyPointer(key)); + } + default: { + return ParseKeyResult(PKParseError::FAILED, ERR_peek_error()); + } + }; +} + +Result EVPKeyPointer::writePrivateKey( + const PrivateKeyEncodingConfig& config) const { + if (config.format == PKFormatType::JWK) { + return Result(false); + } + + auto bio = BIOPointer::NewMem(); + if (!bio) { + return Result(false); + } + + auto passphrase = GetPassphrase(config); + MarkPopErrorOnReturn mark_pop_error_on_return; + bool err; + + switch (config.type) { + case PKEncodingType::PKCS1: { + // PKCS1 is only permitted for RSA keys. + if (id() != EVP_PKEY_RSA) return Result(false); + +#if OPENSSL_VERSION_MAJOR >= 3 + const RSA* rsa = EVP_PKEY_get0_RSA(get()); +#else + RSA* rsa = EVP_PKEY_get0_RSA(get()); +#endif + switch (config.format) { + case PKFormatType::PEM: { + err = PEM_write_bio_RSAPrivateKey( + bio.get(), + rsa, + config.cipher, + reinterpret_cast(passphrase.data), + passphrase.len, + nullptr, + nullptr) != 1; + break; + } + case PKFormatType::DER: { + // Encoding PKCS1 as DER. This variation does not permit encryption. + err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1; + break; + } + default: { + // Should never get here. + return Result(false); + } + } + break; + } + case PKEncodingType::PKCS8: { + switch (config.format) { + case PKFormatType::PEM: { + // Encode PKCS#8 as PEM. + err = PEM_write_bio_PKCS8PrivateKey(bio.get(), + get(), + config.cipher, + passphrase.data, + passphrase.len, + nullptr, + nullptr) != 1; + break; + } + case PKFormatType::DER: { + err = i2d_PKCS8PrivateKey_bio(bio.get(), + get(), + config.cipher, + passphrase.data, + passphrase.len, + nullptr, + nullptr) != 1; + break; + } + default: { + // Should never get here. + return Result(false); + } + } + break; + } + case PKEncodingType::SEC1: { + // SEC1 is only permitted for EC keys + if (id() != EVP_PKEY_EC) return Result(false); + +#if OPENSSL_VERSION_MAJOR >= 3 + const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); +#else + EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); +#endif + switch (config.format) { + case PKFormatType::PEM: { + err = PEM_write_bio_ECPrivateKey( + bio.get(), + ec, + config.cipher, + reinterpret_cast(passphrase.data), + passphrase.len, + nullptr, + nullptr) != 1; + break; + } + case PKFormatType::DER: { + // Encoding SEC1 as DER. This variation does not permit encryption. + err = i2d_ECPrivateKey_bio(bio.get(), ec) != 1; + break; + } + default: { + // Should never get here. + return Result(false); + } + } + break; + } + default: { + // Not a valid private key encoding + return Result(false); + } + } + + if (err) { + // Failed to encode the private key. + return Result(false, + mark_pop_error_on_return.peekError()); + } + + return bio; +} + +Result EVPKeyPointer::writePublicKey( + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const { + auto bio = BIOPointer::NewMem(); + if (!bio) return Result(false); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) { + // PKCS#1 is only valid for RSA keys. +#if OPENSSL_VERSION_MAJOR >= 3 + const RSA* rsa = EVP_PKEY_get0_RSA(get()); +#else + RSA* rsa = EVP_PKEY_get0_RSA(get()); +#endif + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { + // Encode PKCS#1 as PEM. + if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { + return Result(false, + mark_pop_error_on_return.peekError()); + } + return bio; + } + + // Encode PKCS#1 as DER. + if (i2d_RSAPublicKey_bio(bio.get(), rsa) != 1) { + return Result(false, + mark_pop_error_on_return.peekError()); + } + return bio; + } + + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { + // Encode SPKI as PEM. + if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { + return Result(false, + mark_pop_error_on_return.peekError()); + } + return bio; + } + + // Encode SPKI as DER. + if (i2d_PUBKEY_bio(bio.get(), get()) != 1) { + return Result(false, + mark_pop_error_on_return.peekError()); + } + return bio; +} + +bool EVPKeyPointer::isRsaVariant() const { + if (!pkey_) return false; + int type = id(); + return type == EVP_PKEY_RSA || type == EVP_PKEY_RSA2 || + type == EVP_PKEY_RSA_PSS; +} + +bool EVPKeyPointer::isOneShotVariant() const { + if (!pkey_) return false; + int type = id(); + return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; +} + +bool EVPKeyPointer::isSigVariant() const { + if (!pkey_) return false; + int type = id(); + return type == EVP_PKEY_EC || type == EVP_PKEY_DSA; +} + +int EVPKeyPointer::getDefaultSignPadding() const { + return id() == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING; +} + +std::optional EVPKeyPointer::getBytesOfRS() const { + if (!pkey_) return std::nullopt; + int bits, id = base_id(); + + if (id == EVP_PKEY_DSA) { + const DSA* dsa_key = EVP_PKEY_get0_DSA(get()); + // Both r and s are computed mod q, so their width is limited by that of q. + bits = BignumPointer::GetBitCount(DSA_get0_q(dsa_key)); + } else if (id == EVP_PKEY_EC) { + bits = EC_GROUP_order_bits(ECKeyPointer::GetGroup(*this)); + } else { + return std::nullopt; + } + + return (bits + 7) / 8; +} + +EVPKeyPointer::operator Rsa() const { + int type = id(); + if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return {}; + + // TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL + // versions older than 1.1.1e via FIPS / dynamic linking. + OSSL3_CONST RSA* rsa; + if (OPENSSL_VERSION_NUMBER >= 0x1010105fL) { + rsa = EVP_PKEY_get0_RSA(get()); + } else { + rsa = static_cast(EVP_PKEY_get0(get())); + } + if (rsa == nullptr) return {}; + return Rsa(rsa); +} + +EVPKeyPointer::operator Dsa() const { + int type = id(); + if (type != EVP_PKEY_DSA) return {}; + + OSSL3_CONST DSA* dsa = EVP_PKEY_get0_DSA(get()); + if (dsa == nullptr) return {}; + return Dsa(dsa); +} + +bool EVPKeyPointer::validateDsaParameters() const { + if (!pkey_) return false; + /* Validate DSA2 parameters from FIPS 186-4 */ +#if OPENSSL_VERSION_MAJOR >= 3 + if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { +#else + if (FIPS_mode() && EVP_PKEY_DSA == id()) { +#endif + const DSA* dsa = EVP_PKEY_get0_DSA(pkey_.get()); + const BIGNUM* p; + const BIGNUM* q; + DSA_get0_pqg(dsa, &p, &q, nullptr); + int L = BignumPointer::GetBitCount(p); + int N = BignumPointer::GetBitCount(q); + + return (L == 1024 && N == 160) || (L == 2048 && N == 224) || + (L == 2048 && N == 256) || (L == 3072 && N == 256); + } + + return true; +} + +// ============================================================================ + +SSLPointer::SSLPointer(SSL* ssl) : ssl_(ssl) {} + +SSLPointer::SSLPointer(SSLPointer&& other) noexcept : ssl_(other.release()) {} + +SSLPointer& SSLPointer::operator=(SSLPointer&& other) noexcept { + if (this == &other) return *this; + this->~SSLPointer(); + return *new (this) SSLPointer(std::move(other)); +} + +SSLPointer::~SSLPointer() { + reset(); +} + +void SSLPointer::reset(SSL* ssl) { + ssl_.reset(ssl); +} + +SSL* SSLPointer::release() { + return ssl_.release(); +} + +SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) { + if (!ctx) return {}; + return SSLPointer(SSL_new(ctx.get())); +} + +void SSLPointer::getCiphers( + std::function cb) const { + if (!ssl_) return; + STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get()); + + // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just + // document them, but since there are only 5, easier to just add them manually + // and not have to explain their absence in the API docs. They are lower-cased + // because the docs say they will be. + static constexpr const char* TLS13_CIPHERS[] = { + "tls_aes_256_gcm_sha384", + "tls_chacha20_poly1305_sha256", + "tls_aes_128_gcm_sha256", + "tls_aes_128_ccm_8_sha256", + "tls_aes_128_ccm_sha256"}; + + const int n = sk_SSL_CIPHER_num(ciphers); + + for (int i = 0; i < n; ++i) { + const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); + cb(SSL_CIPHER_get_name(cipher)); + } + + for (unsigned i = 0; i < 5; ++i) { + cb(TLS13_CIPHERS[i]); + } +} + +bool SSLPointer::setSession(const SSLSessionPointer& session) { + if (!session || !ssl_) return false; + return SSL_set_session(get(), session.get()) == 1; +} + +bool SSLPointer::setSniContext(const SSLCtxPointer& ctx) const { + if (!ctx) return false; + auto x509 = ncrypto::X509View::From(ctx); + if (!x509) return false; + EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx.get()); + STACK_OF(X509) * chain; + int err = SSL_CTX_get0_chain_certs(ctx.get(), &chain); + if (err == 1) err = SSL_use_certificate(get(), x509); + if (err == 1) err = SSL_use_PrivateKey(get(), pkey); + if (err == 1 && chain != nullptr) err = SSL_set1_chain(get(), chain); + return err == 1; +} + +std::optional SSLPointer::verifyPeerCertificate() const { + if (!ssl_) return std::nullopt; + if (X509Pointer::PeerFrom(*this)) { + return SSL_get_verify_result(get()); + } + + const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(get()); + const SSL_SESSION* sess = SSL_get_session(get()); + // Allow no-cert for PSK authentication in TLS1.2 and lower. + // In TLS1.3 check that session was reused because TLS1.3 PSK + // looks like session resumption. + if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || + (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && + SSL_session_reused(get()))) { + return X509_V_OK; + } + + return std::nullopt; +} + +const std::string_view SSLPointer::getClientHelloAlpn() const { + if (ssl_ == nullptr) return {}; +#ifndef OPENSSL_IS_BORINGSSL + const unsigned char* buf; + size_t len; + size_t rem; + + if (!SSL_client_hello_get0_ext( + get(), + TLSEXT_TYPE_application_layer_protocol_negotiation, + &buf, + &rem) || + rem < 2) { + return {}; + } + + len = (buf[0] << 8) | buf[1]; + if (len + 2 != rem) return {}; + return reinterpret_cast(buf + 3); +#else + // Boringssl doesn't have a public API for this. + return {}; +#endif +} + +const std::string_view SSLPointer::getClientHelloServerName() const { + if (ssl_ == nullptr) return {}; +#ifndef OPENSSL_IS_BORINGSSL + const unsigned char* buf; + size_t len; + size_t rem; + + if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || + rem <= 2) { + return {}; + } + + len = (*buf << 8) | *(buf + 1); + if (len + 2 != rem) return {}; + rem = len; + + if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; + rem--; + if (rem <= 2) return {}; + len = (*(buf + 3) << 8) | *(buf + 4); + if (len + 2 > rem) return {}; + return reinterpret_cast(buf + 5); +#else + // Boringssl doesn't have a public API for this. + return {}; +#endif +} + +std::optional SSLPointer::GetServerName( + const SSL* ssl) { + if (ssl == nullptr) return std::nullopt; + auto res = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (res == nullptr) return std::nullopt; + return res; +} + +std::optional SSLPointer::getServerName() const { + if (!ssl_) return std::nullopt; + return GetServerName(get()); +} + +X509View SSLPointer::getCertificate() const { + if (!ssl_) return {}; + ClearErrorOnReturn clear_error_on_return; + return ncrypto::X509View(SSL_get_certificate(get())); +} + +const SSL_CIPHER* SSLPointer::getCipher() const { + if (!ssl_) return nullptr; + return SSL_get_current_cipher(get()); +} + +bool SSLPointer::isServer() const { + return SSL_is_server(get()) != 0; +} + +EVPKeyPointer SSLPointer::getPeerTempKey() const { + if (!ssl_) return {}; + EVP_PKEY* raw_key = nullptr; +#ifndef OPENSSL_IS_BORINGSSL + if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; +#else + if (!SSL_get_server_tmp_key(get(), &raw_key)) return {}; +#endif + return EVPKeyPointer(raw_key); +} + +std::optional SSLPointer::getCipherName() const { + auto cipher = getCipher(); + if (cipher == nullptr) return std::nullopt; + return SSL_CIPHER_get_name(cipher); +} + +std::optional SSLPointer::getCipherStandardName() const { + auto cipher = getCipher(); + if (cipher == nullptr) return std::nullopt; + return SSL_CIPHER_standard_name(cipher); +} + +std::optional SSLPointer::getCipherVersion() const { + auto cipher = getCipher(); + if (cipher == nullptr) return std::nullopt; + return SSL_CIPHER_get_version(cipher); +} + +SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} + +SSLCtxPointer::SSLCtxPointer(SSLCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +SSLCtxPointer& SSLCtxPointer::operator=(SSLCtxPointer&& other) noexcept { + if (this == &other) return *this; + this->~SSLCtxPointer(); + return *new (this) SSLCtxPointer(std::move(other)); +} + +SSLCtxPointer::~SSLCtxPointer() { + reset(); +} + +void SSLCtxPointer::reset(SSL_CTX* ctx) { + ctx_.reset(ctx); +} + +void SSLCtxPointer::reset(const SSL_METHOD* method) { + ctx_.reset(SSL_CTX_new(method)); +} + +SSL_CTX* SSLCtxPointer::release() { + return ctx_.release(); +} + +SSLCtxPointer SSLCtxPointer::NewServer() { + return SSLCtxPointer(SSL_CTX_new(TLS_server_method())); +} + +SSLCtxPointer SSLCtxPointer::NewClient() { + return SSLCtxPointer(SSL_CTX_new(TLS_client_method())); +} + +SSLCtxPointer SSLCtxPointer::New(const SSL_METHOD* method) { + return SSLCtxPointer(SSL_CTX_new(method)); +} + +bool SSLCtxPointer::setGroups(const char* groups) { + return SSL_CTX_set1_groups_list(get(), groups) == 1; +} + +// ============================================================================ + +const Cipher Cipher::FromName(std::string_view name) { + return Cipher(EVP_get_cipherbyname(name.data())); +} + +const Cipher Cipher::FromNid(int nid) { + return Cipher(EVP_get_cipherbynid(nid)); +} + +const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { + return Cipher(EVP_CIPHER_CTX_cipher(ctx.get())); +} + +int Cipher::getMode() const { + if (!cipher_) return 0; + return EVP_CIPHER_mode(cipher_); +} + +int Cipher::getIvLength() const { + if (!cipher_) return 0; + return EVP_CIPHER_iv_length(cipher_); +} + +int Cipher::getKeyLength() const { + if (!cipher_) return 0; + return EVP_CIPHER_key_length(cipher_); +} + +int Cipher::getBlockSize() const { + if (!cipher_) return 0; + return EVP_CIPHER_block_size(cipher_); +} + +int Cipher::getNid() const { + if (!cipher_) return 0; + return EVP_CIPHER_nid(cipher_); +} + +std::string_view Cipher::getModeLabel() const { + if (!cipher_) return {}; + switch (getMode()) { + case EVP_CIPH_CCM_MODE: + return "ccm"; + case EVP_CIPH_CFB_MODE: + return "cfb"; + case EVP_CIPH_CBC_MODE: + return "cbc"; + case EVP_CIPH_CTR_MODE: + return "ctr"; + case EVP_CIPH_ECB_MODE: + return "ecb"; + case EVP_CIPH_GCM_MODE: + return "gcm"; + case EVP_CIPH_OCB_MODE: + return "ocb"; + case EVP_CIPH_OFB_MODE: + return "ofb"; + case EVP_CIPH_WRAP_MODE: + return "wrap"; + case EVP_CIPH_XTS_MODE: + return "xts"; + case EVP_CIPH_STREAM_CIPHER: + return "stream"; + } + return "{unknown}"; +} + +std::string_view Cipher::getName() const { + if (!cipher_) return {}; + // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of + // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. + return OBJ_nid2sn(getNid()); +} + +bool Cipher::isSupportedAuthenticatedMode() const { + switch (getMode()) { + case EVP_CIPH_CCM_MODE: + case EVP_CIPH_GCM_MODE: +#ifndef OPENSSL_NO_OCB + case EVP_CIPH_OCB_MODE: +#endif + return true; + case EVP_CIPH_STREAM_CIPHER: + return getNid() == NID_chacha20_poly1305; + default: + return false; + } +} + +// ============================================================================ + +CipherCtxPointer CipherCtxPointer::New() { + auto ret = CipherCtxPointer(EVP_CIPHER_CTX_new()); + if (!ret) return {}; + EVP_CIPHER_CTX_init(ret.get()); + return ret; +} + +CipherCtxPointer::CipherCtxPointer(EVP_CIPHER_CTX* ctx) : ctx_(ctx) {} + +CipherCtxPointer::CipherCtxPointer(CipherCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +CipherCtxPointer& CipherCtxPointer::operator=( + CipherCtxPointer&& other) noexcept { + if (this == &other) return *this; + this->~CipherCtxPointer(); + return *new (this) CipherCtxPointer(std::move(other)); +} + +CipherCtxPointer::~CipherCtxPointer() { + reset(); +} + +void CipherCtxPointer::reset(EVP_CIPHER_CTX* ctx) { + ctx_.reset(ctx); +} + +EVP_CIPHER_CTX* CipherCtxPointer::release() { + return ctx_.release(); +} + +void CipherCtxPointer::setFlags(int flags) { + if (!ctx_) return; + EVP_CIPHER_CTX_set_flags(ctx_.get(), flags); +} + +bool CipherCtxPointer::setKeyLength(size_t length) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_set_key_length(ctx_.get(), length); +} + +bool CipherCtxPointer::setIvLength(size_t length) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl( + ctx_.get(), EVP_CTRL_AEAD_SET_IVLEN, length, nullptr); +} + +bool CipherCtxPointer::setAeadTag(const Buffer& tag) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl( + ctx_.get(), EVP_CTRL_AEAD_SET_TAG, tag.len, const_cast(tag.data)); +} + +bool CipherCtxPointer::setAeadTagLength(size_t length) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl( + ctx_.get(), EVP_CTRL_AEAD_SET_TAG, length, nullptr); +} + +bool CipherCtxPointer::setPadding(bool padding) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_set_padding(ctx_.get(), padding); +} + +int CipherCtxPointer::getBlockSize() const { + if (!ctx_) return 0; + return EVP_CIPHER_CTX_block_size(ctx_.get()); +} + +int CipherCtxPointer::getMode() const { + if (!ctx_) return 0; + return EVP_CIPHER_CTX_mode(ctx_.get()); +} + +int CipherCtxPointer::getNid() const { + if (!ctx_) return 0; + return EVP_CIPHER_CTX_nid(ctx_.get()); +} + +bool CipherCtxPointer::init(const Cipher& cipher, + bool encrypt, + const unsigned char* key, + const unsigned char* iv) { + if (!ctx_) return false; + return EVP_CipherInit_ex( + ctx_.get(), cipher, nullptr, key, iv, encrypt ? 1 : 0) == 1; +} + +bool CipherCtxPointer::update(const Buffer& in, + unsigned char* out, + int* out_len, + bool finalize) { + if (!ctx_) return false; + if (!finalize) { + return EVP_CipherUpdate(ctx_.get(), out, out_len, in.data, in.len) == 1; + } + return EVP_CipherFinal_ex(ctx_.get(), out, out_len) == 1; +} + +bool CipherCtxPointer::getAeadTag(size_t len, unsigned char* out) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, len, out); +} + +// ============================================================================ + +ECDSASigPointer::ECDSASigPointer() : sig_(nullptr) {} +ECDSASigPointer::ECDSASigPointer(ECDSA_SIG* sig) : sig_(sig) { + if (sig_) { + ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); + } +} +ECDSASigPointer::ECDSASigPointer(ECDSASigPointer&& other) noexcept + : sig_(other.release()) { + if (sig_) { + ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); + } +} + +ECDSASigPointer& ECDSASigPointer::operator=(ECDSASigPointer&& other) noexcept { + sig_.reset(other.release()); + if (sig_) { + ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); + } + return *this; +} + +ECDSASigPointer::~ECDSASigPointer() { + reset(); +} + +void ECDSASigPointer::reset(ECDSA_SIG* sig) { + sig_.reset(); + pr_ = nullptr; + ps_ = nullptr; +} + +ECDSA_SIG* ECDSASigPointer::release() { + pr_ = nullptr; + ps_ = nullptr; + return sig_.release(); +} + +ECDSASigPointer ECDSASigPointer::New() { + return ECDSASigPointer(ECDSA_SIG_new()); +} + +ECDSASigPointer ECDSASigPointer::Parse(const Buffer& sig) { + const unsigned char* ptr = sig.data; + return ECDSASigPointer(d2i_ECDSA_SIG(nullptr, &ptr, sig.len)); +} + +bool ECDSASigPointer::setParams(BignumPointer&& r, BignumPointer&& s) { + if (!sig_) return false; + return ECDSA_SIG_set0(sig_.get(), r.release(), s.release()); +} + +Buffer ECDSASigPointer::encode() const { + if (!sig_) + return { + .data = nullptr, + .len = 0, + }; + Buffer buf; + buf.len = i2d_ECDSA_SIG(sig_.get(), &buf.data); + return buf; +} + +// ============================================================================ + +ECGroupPointer::ECGroupPointer() : group_(nullptr) {} + +ECGroupPointer::ECGroupPointer(EC_GROUP* group) : group_(group) {} + +ECGroupPointer::ECGroupPointer(ECGroupPointer&& other) noexcept + : group_(other.release()) {} + +ECGroupPointer& ECGroupPointer::operator=(ECGroupPointer&& other) noexcept { + group_.reset(other.release()); + return *this; +} + +ECGroupPointer::~ECGroupPointer() { + reset(); +} + +void ECGroupPointer::reset(EC_GROUP* group) { + group_.reset(); +} + +EC_GROUP* ECGroupPointer::release() { + return group_.release(); +} + +ECGroupPointer ECGroupPointer::NewByCurveName(int nid) { + return ECGroupPointer(EC_GROUP_new_by_curve_name(nid)); +} + +// ============================================================================ + +ECPointPointer::ECPointPointer() : point_(nullptr) {} + +ECPointPointer::ECPointPointer(EC_POINT* point) : point_(point) {} + +ECPointPointer::ECPointPointer(ECPointPointer&& other) noexcept + : point_(other.release()) {} + +ECPointPointer& ECPointPointer::operator=(ECPointPointer&& other) noexcept { + point_.reset(other.release()); + return *this; +} + +ECPointPointer::~ECPointPointer() { + reset(); +} + +void ECPointPointer::reset(EC_POINT* point) { + point_.reset(point); +} + +EC_POINT* ECPointPointer::release() { + return point_.release(); +} + +ECPointPointer ECPointPointer::New(const EC_GROUP* group) { + return ECPointPointer(EC_POINT_new(group)); +} + +bool ECPointPointer::setFromBuffer(const Buffer& buffer, + const EC_GROUP* group) { + if (!point_) return false; + return EC_POINT_oct2point( + group, point_.get(), buffer.data, buffer.len, nullptr); +} + +bool ECPointPointer::mul(const EC_GROUP* group, const BIGNUM* priv_key) { + if (!point_) return false; + return EC_POINT_mul(group, point_.get(), priv_key, nullptr, nullptr, nullptr); +} + +// ============================================================================ + +ECKeyPointer::ECKeyPointer() : key_(nullptr) {} + +ECKeyPointer::ECKeyPointer(EC_KEY* key) : key_(key) {} + +ECKeyPointer::ECKeyPointer(ECKeyPointer&& other) noexcept + : key_(other.release()) {} + +ECKeyPointer& ECKeyPointer::operator=(ECKeyPointer&& other) noexcept { + key_.reset(other.release()); + return *this; +} + +ECKeyPointer::~ECKeyPointer() { + reset(); +} + +void ECKeyPointer::reset(EC_KEY* key) { + key_.reset(key); +} + +EC_KEY* ECKeyPointer::release() { + return key_.release(); +} + +ECKeyPointer ECKeyPointer::clone() const { + if (!key_) return {}; + return ECKeyPointer(EC_KEY_dup(key_.get())); +} + +bool ECKeyPointer::generate() { + if (!key_) return false; + return EC_KEY_generate_key(key_.get()); +} + +bool ECKeyPointer::setPublicKey(const ECPointPointer& pub) { + if (!key_) return false; + return EC_KEY_set_public_key(key_.get(), pub.get()) == 1; +} + +bool ECKeyPointer::setPublicKeyRaw(const BignumPointer& x, + const BignumPointer& y) { + if (!key_) return false; + return EC_KEY_set_public_key_affine_coordinates( + key_.get(), x.get(), y.get()) == 1; +} + +bool ECKeyPointer::setPrivateKey(const BignumPointer& priv) { + if (!key_) return false; + return EC_KEY_set_private_key(key_.get(), priv.get()) == 1; +} + +const BIGNUM* ECKeyPointer::getPrivateKey() const { + if (!key_) return nullptr; + return GetPrivateKey(key_.get()); +} + +const BIGNUM* ECKeyPointer::GetPrivateKey(const EC_KEY* key) { + return EC_KEY_get0_private_key(key); +} + +const EC_POINT* ECKeyPointer::getPublicKey() const { + if (!key_) return nullptr; + return GetPublicKey(key_.get()); +} + +const EC_POINT* ECKeyPointer::GetPublicKey(const EC_KEY* key) { + return EC_KEY_get0_public_key(key); +} + +const EC_GROUP* ECKeyPointer::getGroup() const { + if (!key_) return nullptr; + return GetGroup(key_.get()); +} + +const EC_GROUP* ECKeyPointer::GetGroup(const EC_KEY* key) { + return EC_KEY_get0_group(key); +} + +int ECKeyPointer::GetGroupName(const EC_KEY* key) { + const EC_GROUP* group = GetGroup(key); + return group ? EC_GROUP_get_curve_name(group) : 0; +} + +bool ECKeyPointer::Check(const EC_KEY* key) { + return EC_KEY_check_key(key) == 1; +} + +bool ECKeyPointer::checkKey() const { + if (!key_) return false; + return Check(key_.get()); +} + +ECKeyPointer ECKeyPointer::NewByCurveName(int nid) { + return ECKeyPointer(EC_KEY_new_by_curve_name(nid)); +} + +ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) { + auto ptr = ECKeyPointer(EC_KEY_new()); + if (!ptr) return {}; + if (!EC_KEY_set_group(ptr.get(), group)) return {}; + return ptr; +} + +// ============================================================================ + +EVPKeyCtxPointer::EVPKeyCtxPointer() : ctx_(nullptr) {} + +EVPKeyCtxPointer::EVPKeyCtxPointer(EVP_PKEY_CTX* ctx) : ctx_(ctx) {} + +EVPKeyCtxPointer::EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +EVPKeyCtxPointer& EVPKeyCtxPointer::operator=( + EVPKeyCtxPointer&& other) noexcept { + ctx_.reset(other.release()); + return *this; +} + +EVPKeyCtxPointer::~EVPKeyCtxPointer() { + reset(); +} + +void EVPKeyCtxPointer::reset(EVP_PKEY_CTX* ctx) { + ctx_.reset(ctx); +} + +EVP_PKEY_CTX* EVPKeyCtxPointer::release() { + return ctx_.release(); +} + +EVPKeyCtxPointer EVPKeyCtxPointer::New(const EVPKeyPointer& key) { + if (!key) return {}; + return EVPKeyCtxPointer(EVP_PKEY_CTX_new(key.get(), nullptr)); +} + +EVPKeyCtxPointer EVPKeyCtxPointer::NewFromID(int id) { + return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr)); +} + +bool EVPKeyCtxPointer::initForDerive(const EVPKeyPointer& peer) { + if (!ctx_) return false; + if (EVP_PKEY_derive_init(ctx_.get()) != 1) return false; + return EVP_PKEY_derive_set_peer(ctx_.get(), peer.get()) == 1; +} + +bool EVPKeyCtxPointer::initForKeygen() { + if (!ctx_) return false; + return EVP_PKEY_keygen_init(ctx_.get()) == 1; +} + +bool EVPKeyCtxPointer::initForParamgen() { + if (!ctx_) return false; + return EVP_PKEY_paramgen_init(ctx_.get()) == 1; +} + +int EVPKeyCtxPointer::initForVerify() { + if (!ctx_) return 0; + return EVP_PKEY_verify_init(ctx_.get()); +} + +int EVPKeyCtxPointer::initForSign() { + if (!ctx_) return 0; + return EVP_PKEY_sign_init(ctx_.get()); +} + +bool EVPKeyCtxPointer::setDhParameters(int prime_size, uint32_t generator) { +#ifndef OPENSSL_IS_BORINGSSL + if (!ctx_) return false; + return EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx_.get(), prime_size) == 1 && + EVP_PKEY_CTX_set_dh_paramgen_generator(ctx_.get(), generator) == 1; +#else + // TODO(jasnell): Boringssl appears not to support this operation. + // Is there an alternative approach that Boringssl does support? + return false; +#endif +} + +bool EVPKeyCtxPointer::setDsaParameters(uint32_t bits, + std::optional q_bits) { + if (!ctx_) return false; + if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx_.get(), bits) != 1) { + return false; + } + if (q_bits.has_value() && + EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx_.get(), q_bits.value()) != 1) { + return false; + } + return true; +} + +bool EVPKeyCtxPointer::setEcParameters(int curve, int encoding) { + if (!ctx_) return false; + return EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx_.get(), curve) == 1 && + EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1; +} + +bool EVPKeyCtxPointer::setRsaOaepMd(const EVP_MD* md) { + if (md == nullptr || !ctx_) return false; + return EVP_PKEY_CTX_set_rsa_oaep_md(ctx_.get(), md) > 0; +} + +bool EVPKeyCtxPointer::setRsaMgf1Md(const EVP_MD* md) { + if (md == nullptr || !ctx_) return false; + return EVP_PKEY_CTX_set_rsa_mgf1_md(ctx_.get(), md) > 0; +} + +bool EVPKeyCtxPointer::setRsaPadding(int padding) { + return setRsaPadding(ctx_.get(), padding, std::nullopt); +} + +bool EVPKeyCtxPointer::setRsaPadding(EVP_PKEY_CTX* ctx, + int padding, + std::optional salt_len) { + if (ctx == nullptr) return false; + if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0) { + return false; + } + if (padding == RSA_PKCS1_PSS_PADDING && salt_len.has_value()) { + return EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, salt_len.value()) > 0; + } + return true; +} + +bool EVPKeyCtxPointer::setRsaKeygenBits(int bits) { + if (!ctx_) return false; + return EVP_PKEY_CTX_set_rsa_keygen_bits(ctx_.get(), bits) == 1; +} + +bool EVPKeyCtxPointer::setRsaKeygenPubExp(BignumPointer&& e) { + if (!ctx_) return false; + if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1) { + // The ctx_ takes ownership of e on success. + e.release(); + return true; + } + return false; +} + +bool EVPKeyCtxPointer::setRsaPssKeygenMd(const EVP_MD* md) { + if (md == nullptr || !ctx_) return false; + return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md) > 0; +} + +bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const EVP_MD* md) { + if (md == nullptr || !ctx_) return false; + return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md) > 0; +} + +bool EVPKeyCtxPointer::setRsaPssSaltlen(int salt_len) { + if (!ctx_) return false; + return EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx_.get(), salt_len) > 0; +} + +bool EVPKeyCtxPointer::setRsaImplicitRejection() { +#ifndef OPENSSL_IS_BORINGSSL + if (!ctx_) return false; + return EVP_PKEY_CTX_ctrl_str( + ctx_.get(), "rsa_pkcs1_implicit_rejection", "1") > 0; + // From the doc -2 means that the option is not supported. + // The default for the option is enabled and if it has been + // specifically disabled we want to respect that so we will + // not throw an error if the option is supported regardless + // of how it is set. The call to set the value + // will not affect what is used since a different context is + // used in the call if the option is supported +#else + // TODO(jasnell): Boringssl appears not to support this operation. + // Is there an alternative approach that Boringssl does support? + return true; +#endif +} + +bool EVPKeyCtxPointer::setRsaOaepLabel(DataPointer&& data) { + if (!ctx_) return false; + if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx_.get(), + static_cast(data.get()), + data.size()) > 0) { + // The ctx_ takes ownership of data on success. + data.release(); + return true; + } + return false; +} + +bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) { + if (!ctx_) return false; + return EVP_PKEY_CTX_set_signature_md(ctx_.get(), EVP_MD_CTX_md(md.get())) == + 1; +} + +bool EVPKeyCtxPointer::initForEncrypt() { + if (!ctx_) return false; + return EVP_PKEY_encrypt_init(ctx_.get()) == 1; +} + +bool EVPKeyCtxPointer::initForDecrypt() { + if (!ctx_) return false; + return EVP_PKEY_decrypt_init(ctx_.get()) == 1; +} + +DataPointer EVPKeyCtxPointer::derive() const { + if (!ctx_) return {}; + size_t len = 0; + if (EVP_PKEY_derive(ctx_.get(), nullptr, &len) != 1) return {}; + auto data = DataPointer::Alloc(len); + if (!data) return {}; + if (EVP_PKEY_derive( + ctx_.get(), static_cast(data.get()), &len) != 1) { + return {}; + } + return data; +} + +EVPKeyPointer EVPKeyCtxPointer::paramgen() const { + if (!ctx_) return {}; + EVP_PKEY* key = nullptr; + if (EVP_PKEY_paramgen(ctx_.get(), &key) != 1) return {}; + return EVPKeyPointer(key); +} + +bool EVPKeyCtxPointer::publicCheck() const { + if (!ctx_) return false; +#ifndef OPENSSL_IS_BORINGSSL + return EVP_PKEY_public_check(ctx_.get()) == 1; +#if OPENSSL_VERSION_MAJOR >= 3 + return EVP_PKEY_public_check_quick(ctx_.get()) == 1; +#else + return EVP_PKEY_public_check(ctx_.get()) == 1; +#endif +#else // OPENSSL_IS_BORINGSSL + // Boringssl appears not to support this operation. + // TODO(jasnell): Is there an alternative approach that Boringssl does support? + return true; +#endif +} + +bool EVPKeyCtxPointer::privateCheck() const { + if (!ctx_) return false; +#ifndef OPENSSL_IS_BORINGSSL + return EVP_PKEY_check(ctx_.get()) == 1; +#else + // Boringssl appears not to support this operation. + // TODO(jasnell): Is there an alternative approach that Boringssl does support? + return true; +#endif +} + +bool EVPKeyCtxPointer::verify(const Buffer& sig, + const Buffer& data) { + if (!ctx_) return false; + return EVP_PKEY_verify(ctx_.get(), sig.data, sig.len, data.data, data.len) == + 1; +} + +DataPointer EVPKeyCtxPointer::sign(const Buffer& data) { + if (!ctx_) return {}; + size_t len = 0; + if (EVP_PKEY_sign(ctx_.get(), nullptr, &len, data.data, data.len) != 1) { + return {}; + } + auto buf = DataPointer::Alloc(len); + if (!buf) return {}; + if (EVP_PKEY_sign(ctx_.get(), + static_cast(buf.get()), + &len, + data.data, + data.len) != 1) { + return {}; + } + return buf.resize(len); +} + +bool EVPKeyCtxPointer::signInto(const Buffer& data, + Buffer* sig) { + if (!ctx_) return false; + size_t len = sig->len; + if (EVP_PKEY_sign(ctx_.get(), sig->data, &len, data.data, data.len) != 1) { + return false; + } + sig->len = len; + return true; +} + +// ============================================================================ + +namespace { + +using EVP_PKEY_cipher_init_t = int(EVP_PKEY_CTX* ctx); +using EVP_PKEY_cipher_t = int(EVP_PKEY_CTX* ctx, + unsigned char* out, + size_t* outlen, + const unsigned char* in, + size_t inlen); + +template +DataPointer RSA_Cipher(const EVPKeyPointer& key, + const Rsa::CipherParams& params, + const Buffer in) { + if (!key) return {}; + EVPKeyCtxPointer ctx = key.newCtx(); + + if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || + (params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) || + !ctx.setRsaMgf1Md(params.digest)))) { + return {}; + } + + if (params.label.len != 0 && params.label.data != nullptr && + !ctx.setRsaOaepLabel(DataPointer::Copy(params.label))) { + return {}; + } + + size_t out_len = 0; + if (cipher(ctx.get(), + nullptr, + &out_len, + reinterpret_cast(in.data), + in.len) <= 0) { + return {}; + } + + auto buf = DataPointer::Alloc(out_len); + if (!buf) return {}; + + if (cipher(ctx.get(), + static_cast(buf.get()), + &out_len, + static_cast(in.data), + in.len) <= 0) { + return {}; + } + + return buf.resize(out_len); +} + +template +DataPointer CipherImpl(const EVPKeyPointer& key, + const Rsa::CipherParams& params, + const Buffer in) { + if (!key) return {}; + EVPKeyCtxPointer ctx = key.newCtx(); + if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || + (params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) { + return {}; + } + + if (params.label.len != 0 && params.label.data != nullptr && + !ctx.setRsaOaepLabel(DataPointer::Copy(params.label))) { + return {}; + } + + size_t out_len = 0; + if (cipher(ctx.get(), + nullptr, + &out_len, + static_cast(in.data), + in.len) <= 0) { + return {}; + } + + auto buf = DataPointer::Alloc(out_len); + if (!buf) return {}; + + if (cipher(ctx.get(), + static_cast(buf.get()), + &out_len, + static_cast(in.data), + in.len) <= 0) { + return {}; + } + + return buf.resize(out_len); +} +} // namespace + +Rsa::Rsa() : rsa_(nullptr) {} + +Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {} + +const Rsa::PublicKey Rsa::getPublicKey() const { + if (rsa_ == nullptr) return {}; + PublicKey key; + RSA_get0_key(rsa_, &key.n, &key.e, &key.d); + return key; +} + +const Rsa::PrivateKey Rsa::getPrivateKey() const { + if (rsa_ == nullptr) return {}; + PrivateKey key; + RSA_get0_factors(rsa_, &key.p, &key.q); + RSA_get0_crt_params(rsa_, &key.dp, &key.dq, &key.qi); + return key; +} + +const std::optional Rsa::getPssParams() const { + if (rsa_ == nullptr) return std::nullopt; + const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa_); + if (params == nullptr) return std::nullopt; + Rsa::PssParams ret{ + .digest = OBJ_nid2ln(NID_sha1), + .mgf1_digest = OBJ_nid2ln(NID_sha1), + .salt_length = 20, + }; + + if (params->hashAlgorithm != nullptr) { + const ASN1_OBJECT* hash_obj; + X509_ALGOR_get0(&hash_obj, nullptr, nullptr, params->hashAlgorithm); + ret.digest = OBJ_nid2ln(OBJ_obj2nid(hash_obj)); + } + + if (params->maskGenAlgorithm != nullptr) { + const ASN1_OBJECT* mgf_obj; + X509_ALGOR_get0(&mgf_obj, nullptr, nullptr, params->maskGenAlgorithm); + int mgf_nid = OBJ_obj2nid(mgf_obj); + if (mgf_nid == NID_mgf1) { + const ASN1_OBJECT* mgf1_hash_obj; + X509_ALGOR_get0(&mgf1_hash_obj, nullptr, nullptr, params->maskHash); + ret.mgf1_digest = OBJ_nid2ln(OBJ_obj2nid(mgf1_hash_obj)); + } + } + + if (params->saltLength != nullptr) { + if (ASN1_INTEGER_get_int64(&ret.salt_length, params->saltLength) != 1) { + return std::nullopt; + } + } + return ret; +} + +bool Rsa::setPublicKey(BignumPointer&& n, BignumPointer&& e) { + if (!n || !e) return false; + if (RSA_set0_key(const_cast(rsa_), n.get(), e.get(), nullptr) == 1) { + n.release(); + e.release(); + return true; + } + return false; +} + +bool Rsa::setPrivateKey(BignumPointer&& d, + BignumPointer&& q, + BignumPointer&& p, + BignumPointer&& dp, + BignumPointer&& dq, + BignumPointer&& qi) { + if (!RSA_set0_key(const_cast(rsa_), nullptr, nullptr, d.get())) { + return false; + } + d.release(); + + if (!RSA_set0_factors(const_cast(rsa_), p.get(), q.get())) { + return false; + } + p.release(); + q.release(); + + if (!RSA_set0_crt_params( + const_cast(rsa_), dp.get(), dq.get(), qi.get())) { + return false; + } + dp.release(); + dq.release(); + qi.release(); + return true; +} + +DataPointer Rsa::encrypt(const EVPKeyPointer& key, + const Rsa::CipherParams& params, + const Buffer in) { + if (!key) return {}; + return RSA_Cipher(key, params, in); +} + +DataPointer Rsa::decrypt(const EVPKeyPointer& key, + const Rsa::CipherParams& params, + const Buffer in) { + if (!key) return {}; + return RSA_Cipher(key, params, in); +} + +DataPointer Cipher::encrypt(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in) { + // public operation + return CipherImpl(key, params, in); +} + +DataPointer Cipher::decrypt(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in) { + // private operation + return CipherImpl(key, params, in); +} + +DataPointer Cipher::sign(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in) { + // private operation + return CipherImpl(key, params, in); +} + +DataPointer Cipher::recover(const EVPKeyPointer& key, + const CipherParams& params, + const Buffer in) { + // public operation + return CipherImpl( + key, params, in); +} + +// ============================================================================ + +Ec::Ec() : ec_(nullptr) {} + +Ec::Ec(OSSL3_CONST EC_KEY* key) : ec_(key) {} + +const EC_GROUP* Ec::getGroup() const { + return ECKeyPointer::GetGroup(ec_); +} + +int Ec::getCurve() const { + return EC_GROUP_get_curve_name(getGroup()); +} + +// ============================================================================ + +EVPMDCtxPointer::EVPMDCtxPointer() : ctx_(nullptr) {} + +EVPMDCtxPointer::EVPMDCtxPointer(EVP_MD_CTX* ctx) : ctx_(ctx) {} + +EVPMDCtxPointer::EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +EVPMDCtxPointer& EVPMDCtxPointer::operator=(EVPMDCtxPointer&& other) noexcept { + ctx_.reset(other.release()); + return *this; +} + +EVPMDCtxPointer::~EVPMDCtxPointer() { + reset(); +} + +void EVPMDCtxPointer::reset(EVP_MD_CTX* ctx) { + ctx_.reset(ctx); +} + +EVP_MD_CTX* EVPMDCtxPointer::release() { + return ctx_.release(); +} + +bool EVPMDCtxPointer::digestInit(const EVP_MD* digest) { + if (!ctx_) return false; + return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; +} + +bool EVPMDCtxPointer::digestUpdate(const Buffer& in) { + if (!ctx_) return false; + return EVP_DigestUpdate(ctx_.get(), in.data, in.len) > 0; +} + +DataPointer EVPMDCtxPointer::digestFinal(size_t length) { + if (!ctx_) return {}; + + auto buf = DataPointer::Alloc(length); + if (!buf) return {}; + + Buffer buffer = buf; + + if (!digestFinalInto(&buffer)) [[unlikely]] { + return {}; + } + + return buf; +} + +bool EVPMDCtxPointer::digestFinalInto(Buffer* buf) { + if (!ctx_) return false; + + auto ptr = static_cast(buf->data); + + int ret = (buf->len == getExpectedSize()) + ? EVP_DigestFinal_ex(ctx_.get(), ptr, nullptr) + : EVP_DigestFinalXOF(ctx_.get(), ptr, buf->len); + + if (ret != 1) [[unlikely]] + return false; + + return true; +} + +size_t EVPMDCtxPointer::getExpectedSize() { + if (!ctx_) return 0; + return EVP_MD_CTX_size(ctx_.get()); +} + +size_t EVPMDCtxPointer::getDigestSize() const { + return EVP_MD_size(getDigest()); +} + +const EVP_MD* EVPMDCtxPointer::getDigest() const { + if (!ctx_) return nullptr; + return EVP_MD_CTX_md(ctx_.get()); +} + +bool EVPMDCtxPointer::hasXofFlag() const { + if (!ctx_) return false; + return (EVP_MD_flags(getDigest()) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF; +} + +bool EVPMDCtxPointer::copyTo(const EVPMDCtxPointer& other) const { + if (!ctx_ || !other) return {}; + if (EVP_MD_CTX_copy(other.get(), ctx_.get()) != 1) return false; + return true; +} + +std::optional EVPMDCtxPointer::signInit(const EVPKeyPointer& key, + const EVP_MD* digest) { + EVP_PKEY_CTX* ctx = nullptr; + if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { + return std::nullopt; + } + return ctx; +} + +std::optional EVPMDCtxPointer::verifyInit( + const EVPKeyPointer& key, const EVP_MD* digest) { + EVP_PKEY_CTX* ctx = nullptr; + if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { + return std::nullopt; + } + return ctx; +} + +DataPointer EVPMDCtxPointer::signOneShot( + const Buffer& buf) const { + if (!ctx_) return {}; + size_t len; + if (!EVP_DigestSign(ctx_.get(), nullptr, &len, buf.data, buf.len)) { + return {}; + } + auto data = DataPointer::Alloc(len); + if (!data) [[unlikely]] + return {}; + + if (!EVP_DigestSign(ctx_.get(), + static_cast(data.get()), + &len, + buf.data, + buf.len)) { + return {}; + } + return data; +} + +DataPointer EVPMDCtxPointer::sign( + const Buffer& buf) const { + if (!ctx_) [[unlikely]] + return {}; + size_t len; + if (!EVP_DigestSignUpdate(ctx_.get(), buf.data, buf.len) || + !EVP_DigestSignFinal(ctx_.get(), nullptr, &len)) { + return {}; + } + auto data = DataPointer::Alloc(len); + if (!data) [[unlikely]] + return {}; + if (!EVP_DigestSignFinal( + ctx_.get(), static_cast(data.get()), &len)) { + return {}; + } + return data.resize(len); +} + +bool EVPMDCtxPointer::verify(const Buffer& buf, + const Buffer& sig) const { + if (!ctx_) return false; + int ret = EVP_DigestVerify(ctx_.get(), sig.data, sig.len, buf.data, buf.len); + return ret == 1; +} + +EVPMDCtxPointer EVPMDCtxPointer::New() { + return EVPMDCtxPointer(EVP_MD_CTX_new()); +} + +// ============================================================================ + +bool extractP1363(const Buffer& buf, + unsigned char* dest, + size_t n) { + auto asn1_sig = ECDSASigPointer::Parse(buf); + if (!asn1_sig) return false; + + return BignumPointer::EncodePaddedInto(asn1_sig.r(), dest, n) > 0 && + BignumPointer::EncodePaddedInto(asn1_sig.s(), dest + n, n) > 0; +} + +// ============================================================================ + +HMACCtxPointer::HMACCtxPointer() : ctx_(nullptr) {} + +HMACCtxPointer::HMACCtxPointer(HMAC_CTX* ctx) : ctx_(ctx) {} + +HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept { + ctx_.reset(other.release()); + return *this; +} + +HMACCtxPointer::~HMACCtxPointer() { + reset(); +} + +void HMACCtxPointer::reset(HMAC_CTX* ctx) { + ctx_.reset(ctx); +} + +HMAC_CTX* HMACCtxPointer::release() { + return ctx_.release(); +} + +bool HMACCtxPointer::init(const Buffer& buf, const EVP_MD* md) { + if (!ctx_) return false; + return HMAC_Init_ex(ctx_.get(), buf.data, buf.len, md, nullptr) == 1; +} + +bool HMACCtxPointer::update(const Buffer& buf) { + if (!ctx_) return false; + return HMAC_Update(ctx_.get(), + static_cast(buf.data), + buf.len) == 1; +} + +DataPointer HMACCtxPointer::digest() { + auto data = DataPointer::Alloc(EVP_MAX_MD_SIZE); + if (!data) return {}; + Buffer buf = data; + if (!digestInto(&buf)) return {}; + return data.resize(buf.len); +} + +bool HMACCtxPointer::digestInto(Buffer* buf) { + if (!ctx_) return false; + + unsigned int len = buf->len; + if (!HMAC_Final(ctx_.get(), static_cast(buf->data), &len)) { + return false; + } + buf->len = len; + return true; +} + +HMACCtxPointer HMACCtxPointer::New() { + return HMACCtxPointer(HMAC_CTX_new()); +} + +DataPointer hashDigest(const Buffer& buf, + const EVP_MD* md) { + if (md == nullptr) return {}; + size_t md_len = EVP_MD_size(md); + unsigned int result_size; + auto data = DataPointer::Alloc(md_len); + if (!data) return {}; + + if (!EVP_Digest(buf.data, + buf.len, + reinterpret_cast(data.get()), + &result_size, + md, + nullptr)) { + return {}; + } + + return data.resize(result_size); +} + +// ============================================================================ + +X509Name::X509Name() : name_(nullptr), total_(0) {} + +X509Name::X509Name(const X509_NAME* name) + : name_(name), total_(X509_NAME_entry_count(name)) {} + +X509Name::Iterator::Iterator(const X509Name& name, int pos) + : name_(name), loc_(pos) {} + +X509Name::Iterator& X509Name::Iterator::operator++() { + ++loc_; + return *this; +} + +X509Name::Iterator::operator bool() const { + return loc_ < name_.total_; +} + +bool X509Name::Iterator::operator==(const Iterator& other) const { + return loc_ == other.loc_; +} + +bool X509Name::Iterator::operator!=(const Iterator& other) const { + return loc_ != other.loc_; +} + +std::pair X509Name::Iterator::operator*() const { + if (loc_ == name_.total_) return {{}, {}}; + + X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); + if (entry == nullptr) [[unlikely]] + return {{}, {}}; + + ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); + ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); + + if (name == nullptr || value == nullptr) [[unlikely]] { + return {{}, {}}; + } + + int nid = OBJ_obj2nid(name); + std::string name_str; + if (nid != NID_undef) { + name_str = std::string(OBJ_nid2sn(nid)); + } else { + char buf[80]; + OBJ_obj2txt(buf, sizeof(buf), name, 0); + name_str = std::string(buf); + } + + unsigned char* value_str; + int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); + + return { + std::move(name_str), + std::string(reinterpret_cast(value_str), value_str_size)}; +} + +// ============================================================================ + +Dsa::Dsa() : dsa_(nullptr) {} + +Dsa::Dsa(OSSL3_CONST DSA* dsa) : dsa_(dsa) {} + +const BIGNUM* Dsa::getP() const { + if (dsa_ == nullptr) return nullptr; + const BIGNUM* p; + DSA_get0_pqg(dsa_, &p, nullptr, nullptr); + return p; +} + +const BIGNUM* Dsa::getQ() const { + if (dsa_ == nullptr) return nullptr; + const BIGNUM* q; + DSA_get0_pqg(dsa_, nullptr, &q, nullptr); + return q; +} + +size_t Dsa::getModulusLength() const { + if (dsa_ == nullptr) return 0; + return BignumPointer::GetBitCount(getP()); +} + +size_t Dsa::getDivisorLength() const { + if (dsa_ == nullptr) return 0; + return BignumPointer::GetBitCount(getQ()); +} + +} // namespace ncrypto