|
4 | 4 | #include <openssl/dh.h> |
5 | 5 | #include <openssl/evp.h> |
6 | 6 | #include <openssl/hmac.h> |
| 7 | +#include <openssl/pem.h> |
7 | 8 | #include <openssl/pkcs12.h> |
8 | 9 | #include <openssl/rand.h> |
9 | 10 | #include <openssl/x509v3.h> |
10 | 11 | #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK |
11 | 12 | #include <openssl/bytestring.h> |
12 | 13 | #include <openssl/cipher.h> |
13 | | -#include <openssl/pem.h> |
14 | 14 | #endif |
15 | 15 | #include <algorithm> |
16 | 16 | #include <array> |
|
21 | 21 | #include <openssl/core_names.h> |
22 | 22 | #include <openssl/params.h> |
23 | 23 | #include <openssl/provider.h> |
| 24 | +#include <openssl/store.h> |
| 25 | +#include <openssl/ui.h> |
24 | 26 | #if OPENSSL_WITH_ARGON2 |
25 | 27 | #include <openssl/thread.h> |
26 | 28 | #endif |
@@ -76,6 +78,17 @@ using BignumCtxPointer = DeleteFnPtr<BN_CTX, BN_CTX_free>; |
76 | 78 | using BignumGenCallbackPointer = DeleteFnPtr<BN_GENCB, BN_GENCB_free>; |
77 | 79 | using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>; |
78 | 80 |
|
| 81 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 82 | +using X509PubKeyPointer = DeleteFnPtr<X509_PUBKEY, X509_PUBKEY_free>; |
| 83 | +// OSSL_STORE_close() returns int, so it needs a void-returning adapter to be |
| 84 | +// usable as a DeleteFnPtr deleter. |
| 85 | +void CloseStoreCtx(OSSL_STORE_CTX* ctx) { |
| 86 | + OSSL_STORE_close(ctx); |
| 87 | +} |
| 88 | +using StoreCtxPointer = DeleteFnPtr<OSSL_STORE_CTX, CloseStoreCtx>; |
| 89 | +using UIMethodPointer = DeleteFnPtr<UI_METHOD, UI_destroy_method>; |
| 90 | +#endif |
| 91 | + |
79 | 92 | const EVP_CIPHER* GetCipherCtxCipher(const EVP_CIPHER_CTX* ctx) { |
80 | 93 | #if NCRYPTO_USE_OPENSSL3_PROVIDER |
81 | 94 | return EVP_CIPHER_CTX_get0_cipher(ctx); |
@@ -332,7 +345,7 @@ ClearErrorOnReturn::~ClearErrorOnReturn() { |
332 | 345 | ERR_clear_error(); |
333 | 346 | } |
334 | 347 |
|
335 | | -int ClearErrorOnReturn::peekError() { |
| 348 | +unsigned long ClearErrorOnReturn::peekError() { // NOLINT(runtime/int) |
336 | 349 | return ERR_peek_error(); |
337 | 350 | } |
338 | 351 |
|
@@ -346,7 +359,7 @@ MarkPopErrorOnReturn::~MarkPopErrorOnReturn() { |
346 | 359 | ERR_pop_to_mark(); |
347 | 360 | } |
348 | 361 |
|
349 | | -int MarkPopErrorOnReturn::peekError() { |
| 362 | +unsigned long MarkPopErrorOnReturn::peekError() { // NOLINT(runtime/int) |
350 | 363 | return ERR_peek_error(); |
351 | 364 | } |
352 | 365 |
|
@@ -840,6 +853,7 @@ int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { |
840 | 853 |
|
841 | 854 | int PasswordCallback(char* buf, int size, int rwflag, void* u) { |
842 | 855 | auto passphrase = static_cast<const Buffer<char>*>(u); |
| 856 | + if (size <= 0) return -1; |
843 | 857 | if (passphrase != nullptr) { |
844 | 858 | size_t buflen = static_cast<size_t>(size); |
845 | 859 | size_t len = passphrase->len; |
@@ -851,6 +865,31 @@ int PasswordCallback(char* buf, int size, int rwflag, void* u) { |
851 | 865 | return -1; |
852 | 866 | } |
853 | 867 |
|
| 868 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 869 | +namespace { |
| 870 | +struct StorePassphraseData { |
| 871 | + Buffer<char> passphrase{.data = nullptr, .len = 0}; |
| 872 | + bool has_passphrase = false; |
| 873 | + bool missing_passphrase = false; |
| 874 | +}; |
| 875 | + |
| 876 | +int StorePasswordCallback(char* buf, int size, int rwflag, void* u) { |
| 877 | + auto data = static_cast<StorePassphraseData*>(u); |
| 878 | + if (data == nullptr || !data->has_passphrase) { |
| 879 | + if (data != nullptr) data->missing_passphrase = true; |
| 880 | + return -1; |
| 881 | + } |
| 882 | + |
| 883 | + if (size <= 0) return -1; |
| 884 | + size_t buflen = static_cast<size_t>(size); |
| 885 | + size_t len = data->passphrase.len; |
| 886 | + if (buflen < len) return -1; |
| 887 | + memcpy(buf, reinterpret_cast<const char*>(data->passphrase.data), len); |
| 888 | + return len; |
| 889 | +} |
| 890 | +} // namespace |
| 891 | +#endif |
| 892 | + |
854 | 893 | // Algorithm: http://howardhinnant.github.io/date_algorithms.html |
855 | 894 | constexpr int days_from_epoch(int y, unsigned m, unsigned d) { |
856 | 895 | y -= m <= 2; |
@@ -3585,7 +3624,7 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( |
3585 | 3624 | const Buffer<const unsigned char>& buffer) { |
3586 | 3625 | static constexpr auto keyOrError = [](EVPKeyPointer pkey, |
3587 | 3626 | bool had_passphrase = false) { |
3588 | | - if (int err = ERR_peek_error()) { |
| 3627 | + if (unsigned long err = ERR_peek_error()) { // NOLINT(runtime/int) |
3589 | 3628 | if (ERR_GET_LIB(err) == ERR_LIB_PEM && |
3590 | 3629 | ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ && !had_passphrase) { |
3591 | 3630 | return ParseKeyResult(PKParseError::NEED_PASSPHRASE); |
@@ -3645,6 +3684,99 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( |
3645 | 3684 | }; |
3646 | 3685 | } |
3647 | 3686 |
|
| 3687 | +EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryLoadPrivateKeyFromStore( |
| 3688 | + const StorePrivateKeyConfig& config) { |
| 3689 | +#if !NCRYPTO_USE_OPENSSL3_PROVIDER |
| 3690 | + return ParseKeyResult(PKParseError::FAILED); |
| 3691 | +#else |
| 3692 | + // The error queue is left populated on failure so the caller can surface a |
| 3693 | + // `code` and an `opensslErrorStack`, matching TryParsePrivateKey(), and is |
| 3694 | + // cleared on success because decoders leave entries behind either way. |
| 3695 | + std::string uri_str(config.uri); |
| 3696 | + std::string properties_str; |
| 3697 | + const char* properties = nullptr; |
| 3698 | + if (config.properties.has_value() && !config.properties->empty()) { |
| 3699 | + properties_str.assign(config.properties->data(), config.properties->size()); |
| 3700 | + properties = properties_str.c_str(); |
| 3701 | + } |
| 3702 | + |
| 3703 | + // config.passphrase outlives this call, so no copy is needed. |
| 3704 | + Buffer<char> passbuf{.data = nullptr, .len = 0}; |
| 3705 | + if (config.passphrase.has_value()) { |
| 3706 | + passbuf.data = const_cast<char*>(config.passphrase->data); |
| 3707 | + passbuf.len = config.passphrase->len; |
| 3708 | + } |
| 3709 | + StorePassphraseData passphrase_data{ |
| 3710 | + .passphrase = passbuf, |
| 3711 | + .has_passphrase = config.passphrase.has_value(), |
| 3712 | + }; |
| 3713 | + // Declared before ctx so that reverse destruction closes the store first; |
| 3714 | + // it holds both for its lifetime. |
| 3715 | + UIMethodPointer ui_method( |
| 3716 | + UI_UTIL_wrap_read_pem_callback(StorePasswordCallback, 0)); |
| 3717 | + if (!ui_method) return ParseKeyResult(PKParseError::FAILED); |
| 3718 | + |
| 3719 | + // Errors from loaders that declined the URI are retained oldest-first, so the |
| 3720 | + // newest entry is the loader that actually handled it. Must run before ctx is |
| 3721 | + // destroyed, since OSSL_STORE_close() can push errors of its own. |
| 3722 | + const auto failed = [&](bool missing_passphrase) { |
| 3723 | + if (missing_passphrase) |
| 3724 | + return ParseKeyResult(PKParseError::NEED_PASSPHRASE); |
| 3725 | + return ParseKeyResult(PKParseError::FAILED, ERR_peek_last_error()); |
| 3726 | + }; |
| 3727 | + |
| 3728 | + const OSSL_PARAM store_params[] = {OSSL_PARAM_END}; |
| 3729 | + StoreCtxPointer ctx(OSSL_STORE_open_ex(uri_str.c_str(), |
| 3730 | + nullptr, |
| 3731 | + properties, |
| 3732 | + ui_method.get(), |
| 3733 | + &passphrase_data, |
| 3734 | + store_params, |
| 3735 | + nullptr, |
| 3736 | + nullptr)); |
| 3737 | + if (!ctx) return failed(passphrase_data.missing_passphrase); |
| 3738 | + |
| 3739 | + if (!OSSL_STORE_expect(ctx.get(), OSSL_STORE_INFO_PKEY)) { |
| 3740 | + return failed(passphrase_data.missing_passphrase); |
| 3741 | + } |
| 3742 | + |
| 3743 | + EVPKeyPointer pkey; |
| 3744 | + bool store_error = false; |
| 3745 | + while (!OSSL_STORE_eof(ctx.get())) { |
| 3746 | + OSSL_STORE_INFO* info = OSSL_STORE_load(ctx.get()); |
| 3747 | + if (info == nullptr) { |
| 3748 | + if (OSSL_STORE_error(ctx.get())) { |
| 3749 | + store_error = true; |
| 3750 | + break; |
| 3751 | + } |
| 3752 | + continue; |
| 3753 | + } |
| 3754 | + if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) { |
| 3755 | + EVP_PKEY* raw_pkey = OSSL_STORE_INFO_get1_PKEY(info); |
| 3756 | + if (raw_pkey != nullptr) { |
| 3757 | + pkey = EVPKeyPointer(raw_pkey); |
| 3758 | + } else { |
| 3759 | + store_error = true; |
| 3760 | + } |
| 3761 | + } |
| 3762 | + OSSL_STORE_INFO_free(info); |
| 3763 | + if (pkey || store_error) break; |
| 3764 | + } |
| 3765 | + |
| 3766 | + // missing_passphrase is sticky, so a key that loaded anyway wins over it. |
| 3767 | + if (pkey) { |
| 3768 | + ctx.reset(); |
| 3769 | + ERR_clear_error(); |
| 3770 | + return ParseKeyResult(std::move(pkey)); |
| 3771 | + } |
| 3772 | + |
| 3773 | + if (passphrase_data.missing_passphrase || store_error) { |
| 3774 | + return failed(passphrase_data.missing_passphrase); |
| 3775 | + } |
| 3776 | + return ParseKeyResult(PKParseError::NOT_RECOGNIZED); |
| 3777 | +#endif |
| 3778 | +} |
| 3779 | + |
3648 | 3780 | Result<BIOPointer, bool> EVPKeyPointer::writePrivateKey( |
3649 | 3781 | const PrivateKeyEncodingConfig& config) const { |
3650 | 3782 | if (config.format == PKFormatType::JWK) { |
@@ -3686,6 +3818,8 @@ Result<BIOPointer, bool> EVPKeyPointer::writePrivateKey( |
3686 | 3818 | #else |
3687 | 3819 | RSA* rsa = EVP_PKEY_get0_RSA(get()); |
3688 | 3820 | #endif |
| 3821 | + if (rsa == nullptr) return Result<BIOPointer, bool>(false); |
| 3822 | + |
3689 | 3823 | switch (config.format) { |
3690 | 3824 | case PKFormatType::PEM: { |
3691 | 3825 | err = PEM_write_bio_RSAPrivateKey( |
@@ -3761,6 +3895,8 @@ Result<BIOPointer, bool> EVPKeyPointer::writePrivateKey( |
3761 | 3895 | #else |
3762 | 3896 | EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); |
3763 | 3897 | #endif |
| 3898 | + if (ec == nullptr) return Result<BIOPointer, bool>(false); |
| 3899 | + |
3764 | 3900 | switch (config.format) { |
3765 | 3901 | case PKFormatType::PEM: { |
3766 | 3902 | err = PEM_write_bio_ECPrivateKey( |
@@ -3827,6 +3963,8 @@ Result<BIOPointer, bool> EVPKeyPointer::writePublicKey( |
3827 | 3963 | #else |
3828 | 3964 | RSA* rsa = EVP_PKEY_get0_RSA(get()); |
3829 | 3965 | #endif |
| 3966 | + if (rsa == nullptr) return Result<BIOPointer, bool>(false); |
| 3967 | + |
3830 | 3968 | if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { |
3831 | 3969 | // Encode PKCS#1 as PEM. |
3832 | 3970 | if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { |
@@ -3855,10 +3993,28 @@ Result<BIOPointer, bool> EVPKeyPointer::writePublicKey( |
3855 | 3993 |
|
3856 | 3994 | if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { |
3857 | 3995 | // Encode SPKI as PEM. |
| 3996 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 3997 | + // Build the SubjectPublicKeyInfo wrapper explicitly before PEM encoding. |
| 3998 | + // Provider-backed keys can fail the direct PEM_write_bio_PUBKEY() path even |
| 3999 | + // when OpenSSL can materialize the public wrapper with X509_PUBKEY_set(). |
| 4000 | + X509_PUBKEY* pubkey = nullptr; |
| 4001 | + if (X509_PUBKEY_set(&pubkey, get()) != 1) { |
| 4002 | + X509_PUBKEY_free(pubkey); |
| 4003 | + return Result<BIOPointer, bool>(false, |
| 4004 | + mark_pop_error_on_return.peekError()); |
| 4005 | + } |
| 4006 | + X509PubKeyPointer pubkey_ptr(pubkey); |
| 4007 | + if (PEM_write_bio_X509_PUBKEY(bio.get(), pubkey_ptr.get()) != 1) { |
| 4008 | + return Result<BIOPointer, bool>(false, |
| 4009 | + mark_pop_error_on_return.peekError()); |
| 4010 | + } |
| 4011 | +#else |
| 4012 | + // Non-OpenSSL >= 3 builds do not all declare PEM_write_bio_X509_PUBKEY(). |
3858 | 4013 | if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { |
3859 | 4014 | return Result<BIOPointer, bool>(false, |
3860 | 4015 | mark_pop_error_on_return.peekError()); |
3861 | 4016 | } |
| 4017 | +#endif |
3862 | 4018 | return bio; |
3863 | 4019 | } |
3864 | 4020 |
|
@@ -3929,21 +4085,37 @@ std::optional<uint32_t> EVPKeyPointer::getBytesOfRS() const { |
3929 | 4085 | bits = BignumPointer::GetBitCount(q.get()); |
3930 | 4086 | #else |
3931 | 4087 | const DSA* dsa_key = EVP_PKEY_get0_DSA(get()); |
| 4088 | + bool has_bits = false; |
3932 | 4089 | // Both r and s are computed mod q, so their width is limited by that of q. |
3933 | | - bits = BignumPointer::GetBitCount(DSA_get0_q(dsa_key)); |
| 4090 | + if (dsa_key != nullptr) { |
| 4091 | + const BIGNUM* q = DSA_get0_q(dsa_key); |
| 4092 | + if (q != nullptr) { |
| 4093 | + bits = BignumPointer::GetBitCount(q); |
| 4094 | + has_bits = true; |
| 4095 | + } |
| 4096 | + } |
| 4097 | + if (!has_bits) return std::nullopt; |
3934 | 4098 | #endif |
3935 | 4099 | } else if (id == EVP_PKEY_EC) { |
3936 | 4100 | #if NCRYPTO_USE_OPENSSL3_PROVIDER |
3937 | 4101 | Ec ec(get()); |
3938 | 4102 | if (!ec) return std::nullopt; |
3939 | | - bits = EC_GROUP_order_bits(ec.getGroup()); |
| 4103 | + const EC_GROUP* group = ec.getGroup(); |
| 4104 | + if (group == nullptr) return std::nullopt; |
| 4105 | + bits = EC_GROUP_order_bits(group); |
3940 | 4106 | #else |
3941 | | - bits = EC_GROUP_order_bits(ECKeyPointer::GetGroup(*this)); |
| 4107 | + const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(get()); |
| 4108 | + if (ec_key == nullptr) return std::nullopt; |
| 4109 | + const EC_GROUP* group = ECKeyPointer::GetGroup(ec_key); |
| 4110 | + if (group == nullptr) return std::nullopt; |
| 4111 | + bits = EC_GROUP_order_bits(group); |
3942 | 4112 | #endif |
3943 | 4113 | } else { |
3944 | 4114 | return std::nullopt; |
3945 | 4115 | } |
3946 | 4116 |
|
| 4117 | + if (bits <= 0) return std::nullopt; |
| 4118 | + |
3947 | 4119 | return (bits + 7) / 8; |
3948 | 4120 | } |
3949 | 4121 |
|
@@ -3982,12 +4154,12 @@ EVPKeyPointer::operator Dsa() const { |
3982 | 4154 |
|
3983 | 4155 | bool EVPKeyPointer::validateDsaParameters() const { |
3984 | 4156 | if (!pkey_) return false; |
3985 | | - /* Validate DSA2 parameters from FIPS 186-4 */ |
3986 | 4157 | #if OPENSSL_VERSION_MAJOR >= 3 |
3987 | 4158 | if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { |
3988 | 4159 | #else |
3989 | 4160 | if (FIPS_mode() && EVP_PKEY_DSA == id()) { |
3990 | 4161 | #endif |
| 4162 | + // Validate DSA2 parameters from FIPS 186-4. |
3991 | 4163 | #if NCRYPTO_USE_OPENSSL3_PROVIDER |
3992 | 4164 | DeleteFnPtr<BIGNUM, BN_free> p; |
3993 | 4165 | DeleteFnPtr<BIGNUM, BN_free> q; |
@@ -3999,9 +4171,11 @@ bool EVPKeyPointer::validateDsaParameters() const { |
3999 | 4171 | const BIGNUM* q_value = q.get(); |
4000 | 4172 | #else |
4001 | 4173 | const DSA* dsa = EVP_PKEY_get0_DSA(pkey_.get()); |
| 4174 | + if (dsa == nullptr) return false; |
4002 | 4175 | const BIGNUM* p; |
4003 | 4176 | const BIGNUM* q; |
4004 | 4177 | DSA_get0_pqg(dsa, &p, &q, nullptr); |
| 4178 | + if (p == nullptr || q == nullptr) return false; |
4005 | 4179 | const BIGNUM* p_value = p; |
4006 | 4180 | const BIGNUM* q_value = q; |
4007 | 4181 | #endif |
@@ -6452,9 +6626,14 @@ DataPointer EVPMDCtxPointer::sign( |
6452 | 6626 |
|
6453 | 6627 | bool EVPMDCtxPointer::verify(const Buffer<const unsigned char>& buf, |
6454 | 6628 | const Buffer<const unsigned char>& sig) const { |
6455 | | - if (!ctx_) return false; |
6456 | | - int ret = EVP_DigestVerify(ctx_.get(), sig.data, sig.len, buf.data, buf.len); |
6457 | | - return ret == 1; |
| 6629 | + return verifyOneShot(buf, sig) == 1; |
| 6630 | +} |
| 6631 | + |
| 6632 | +int EVPMDCtxPointer::verifyOneShot( |
| 6633 | + const Buffer<const unsigned char>& buf, |
| 6634 | + const Buffer<const unsigned char>& sig) const { |
| 6635 | + if (!ctx_) return -1; |
| 6636 | + return EVP_DigestVerify(ctx_.get(), sig.data, sig.len, buf.data, buf.len); |
6458 | 6637 | } |
6459 | 6638 |
|
6460 | 6639 | EVPMDCtxPointer EVPMDCtxPointer::New() { |
|