| /* Copyright (c) 2024, Google Inc. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| |
| #include <openssl/ssl.h> |
| |
| #include <assert.h> |
| |
| #include <openssl/span.h> |
| |
| #include "internal.h" |
| #include "../crypto/internal.h" |
| |
| |
| BSSL_NAMESPACE_BEGIN |
| |
| // new_leafless_chain returns a fresh stack of buffers set to {nullptr}. |
| static UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_leafless_chain(void) { |
| UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain(sk_CRYPTO_BUFFER_new_null()); |
| if (!chain || |
| !sk_CRYPTO_BUFFER_push(chain.get(), nullptr)) { |
| return nullptr; |
| } |
| |
| return chain; |
| } |
| |
| bool ssl_get_credential_list(SSL_HANDSHAKE *hs, Array<SSL_CREDENTIAL *> *out) { |
| CERT *cert = hs->config->cert.get(); |
| // Finish filling in the legacy credential if needed. |
| if (!cert->x509_method->ssl_auto_chain_if_needed(hs)) { |
| return false; |
| } |
| |
| size_t num_creds = cert->credentials.size(); |
| bool include_legacy = cert->legacy_credential->IsComplete(); |
| if (include_legacy) { |
| num_creds++; |
| } |
| |
| if (!out->Init(num_creds)) { |
| return false; |
| } |
| |
| for (size_t i = 0; i < cert->credentials.size(); i++) { |
| (*out)[i] = cert->credentials[i].get(); |
| } |
| if (include_legacy) { |
| (*out)[num_creds - 1] = cert->legacy_credential.get(); |
| } |
| return true; |
| } |
| |
| BSSL_NAMESPACE_END |
| |
| using namespace bssl; |
| |
| static CRYPTO_EX_DATA_CLASS g_ex_data_class = CRYPTO_EX_DATA_CLASS_INIT; |
| |
| ssl_credential_st::ssl_credential_st(SSLCredentialType type_arg) |
| : RefCounted(CheckSubClass()), type(type_arg) { |
| CRYPTO_new_ex_data(&ex_data); |
| } |
| |
| ssl_credential_st::~ssl_credential_st() { |
| CRYPTO_free_ex_data(&g_ex_data_class, this, &ex_data); |
| } |
| |
| static CRYPTO_BUFFER *buffer_up_ref(const CRYPTO_BUFFER *buffer) { |
| CRYPTO_BUFFER_up_ref(const_cast<CRYPTO_BUFFER *>(buffer)); |
| return const_cast<CRYPTO_BUFFER *>(buffer); |
| } |
| |
| UniquePtr<SSL_CREDENTIAL> ssl_credential_st::Dup() const { |
| assert(type == SSLCredentialType::kX509); |
| UniquePtr<SSL_CREDENTIAL> ret = MakeUnique<SSL_CREDENTIAL>(type); |
| if (ret == nullptr) { |
| return nullptr; |
| } |
| |
| ret->pubkey = UpRef(pubkey); |
| ret->privkey = UpRef(privkey); |
| ret->key_method = key_method; |
| if (!ret->sigalgs.CopyFrom(sigalgs)) { |
| return nullptr; |
| } |
| |
| if (chain) { |
| ret->chain.reset(sk_CRYPTO_BUFFER_deep_copy(chain.get(), buffer_up_ref, |
| CRYPTO_BUFFER_free)); |
| if (!ret->chain) { |
| return nullptr; |
| } |
| } |
| |
| ret->dc = UpRef(dc); |
| ret->signed_cert_timestamp_list = UpRef(signed_cert_timestamp_list); |
| ret->ocsp_response = UpRef(ocsp_response); |
| ret->dc_algorithm = dc_algorithm; |
| return ret; |
| } |
| |
| void ssl_credential_st::ClearCertAndKey() { |
| pubkey = nullptr; |
| privkey = nullptr; |
| key_method = nullptr; |
| chain = nullptr; |
| } |
| |
| bool ssl_credential_st::UsesX509() const { |
| // Currently, all credential types use X.509. However, we may add other |
| // certificate types in the future. Add the checks in the setters now, so we |
| // don't forget. |
| return true; |
| } |
| |
| bool ssl_credential_st::UsesPrivateKey() const { |
| // Currently, all credential types use private keys. However, we may add PSK |
| return true; |
| } |
| |
| bool ssl_credential_st::IsComplete() const { |
| // APIs like |SSL_use_certificate| and |SSL_set1_chain| configure the leaf and |
| // other certificates separately. It is possible for |chain| have a null leaf. |
| if (UsesX509() && (sk_CRYPTO_BUFFER_num(chain.get()) == 0 || |
| sk_CRYPTO_BUFFER_value(chain.get(), 0) == nullptr)) { |
| return false; |
| } |
| // We must have successfully extracted a public key from the certificate, |
| // delegated credential, etc. |
| if (UsesPrivateKey() && pubkey == nullptr) { |
| return false; |
| } |
| if (UsesPrivateKey() && privkey == nullptr && key_method == nullptr) { |
| return false; |
| } |
| if (type == SSLCredentialType::kDelegated && dc == nullptr) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool ssl_credential_st::SetLeafCert(UniquePtr<CRYPTO_BUFFER> leaf, |
| bool discard_key_on_mismatch) { |
| if (!UsesX509()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return false; |
| } |
| |
| const bool private_key_matches_leaf = type != SSLCredentialType::kDelegated; |
| |
| CBS cbs; |
| CRYPTO_BUFFER_init_CBS(leaf.get(), &cbs); |
| UniquePtr<EVP_PKEY> new_pubkey = ssl_cert_parse_pubkey(&cbs); |
| if (new_pubkey == nullptr) { |
| return false; |
| } |
| |
| if (!ssl_is_key_type_supported(EVP_PKEY_id(new_pubkey.get()))) { |
| OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); |
| return false; |
| } |
| |
| // An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA |
| // certificates, so sanity-check the key usage extension. |
| if (EVP_PKEY_id(new_pubkey.get()) == EVP_PKEY_EC && |
| !ssl_cert_check_key_usage(&cbs, key_usage_digital_signature)) { |
| OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); |
| return false; |
| } |
| |
| if (private_key_matches_leaf && privkey != nullptr && |
| !ssl_compare_public_and_private_key(new_pubkey.get(), privkey.get())) { |
| if (!discard_key_on_mismatch) { |
| return false; |
| } |
| ERR_clear_error(); |
| privkey = nullptr; |
| } |
| |
| if (chain == nullptr) { |
| chain = new_leafless_chain(); |
| if (chain == nullptr) { |
| return false; |
| } |
| } |
| |
| CRYPTO_BUFFER_free(sk_CRYPTO_BUFFER_value(chain.get(), 0)); |
| sk_CRYPTO_BUFFER_set(chain.get(), 0, leaf.release()); |
| if (private_key_matches_leaf) { |
| pubkey = std::move(new_pubkey); |
| } |
| return true; |
| } |
| |
| void ssl_credential_st::ClearIntermediateCerts() { |
| if (chain == nullptr) { |
| return; |
| } |
| |
| while (sk_CRYPTO_BUFFER_num(chain.get()) > 1) { |
| CRYPTO_BUFFER_free(sk_CRYPTO_BUFFER_pop(chain.get())); |
| } |
| } |
| |
| bool ssl_credential_st::ChainContainsIssuer( |
| bssl::Span<const uint8_t> dn) const { |
| if (UsesX509()) { |
| // TODO(bbe) This is used for matching a chain by CA name for the CA extension. |
| // If we require a chain to be present, we could remove any remaining parts |
| // of the chain after the found issuer, on the assumption that the peer |
| // sending the CA extension has the issuer in their trust store and does not |
| // need us to waste bytes on the wire. |
| CBS dn_cbs; |
| CBS_init(&dn_cbs, dn.data(), dn.size()); |
| for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain.get()); i++) { |
| const CRYPTO_BUFFER *cert = sk_CRYPTO_BUFFER_value(chain.get(), i); |
| CBS cert_cbs; |
| CRYPTO_BUFFER_init_CBS(cert, &cert_cbs); |
| if (ssl_cert_matches_issuer(&cert_cbs, &dn_cbs)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool ssl_credential_st::AppendIntermediateCert(UniquePtr<CRYPTO_BUFFER> cert) { |
| if (!UsesX509()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return false; |
| } |
| |
| if (chain == nullptr) { |
| chain = new_leafless_chain(); |
| if (chain == nullptr) { |
| return false; |
| } |
| } |
| |
| return PushToStack(chain.get(), std::move(cert)); |
| } |
| |
| SSL_CREDENTIAL *SSL_CREDENTIAL_new_x509(void) { |
| return New<SSL_CREDENTIAL>(SSLCredentialType::kX509); |
| } |
| |
| SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) { |
| return New<SSL_CREDENTIAL>(SSLCredentialType::kDelegated); |
| } |
| |
| void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred) { cred->UpRefInternal(); } |
| |
| void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred) { |
| if (cred != nullptr) { |
| cred->DecRefInternal(); |
| } |
| } |
| |
| int SSL_CREDENTIAL_set1_private_key(SSL_CREDENTIAL *cred, EVP_PKEY *key) { |
| if (!cred->UsesPrivateKey()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| |
| // If the public half has been configured, check |key| matches. |pubkey| will |
| // have been extracted from the certificate, delegated credential, etc. |
| if (cred->pubkey != nullptr && |
| !ssl_compare_public_and_private_key(cred->pubkey.get(), key)) { |
| return false; |
| } |
| |
| cred->privkey = UpRef(key); |
| cred->key_method = nullptr; |
| return 1; |
| } |
| |
| int SSL_CREDENTIAL_set_private_key_method( |
| SSL_CREDENTIAL *cred, const SSL_PRIVATE_KEY_METHOD *key_method) { |
| if (!cred->UsesPrivateKey()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| |
| cred->privkey = nullptr; |
| cred->key_method = key_method; |
| return 1; |
| } |
| |
| int SSL_CREDENTIAL_set1_cert_chain(SSL_CREDENTIAL *cred, |
| CRYPTO_BUFFER *const *certs, |
| size_t num_certs) { |
| if (!cred->UsesX509() || num_certs == 0) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| |
| if (!cred->SetLeafCert(UpRef(certs[0]), /*discard_key_on_mismatch=*/false)) { |
| return 0; |
| } |
| |
| cred->ClearIntermediateCerts(); |
| for (size_t i = 1; i < num_certs; i++) { |
| if (!cred->AppendIntermediateCert(UpRef(certs[i]))) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| int SSL_CREDENTIAL_set1_delegated_credential( |
| SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc) { |
| if (cred->type != SSLCredentialType::kDelegated) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| |
| // Parse the delegated credential to check for validity, and extract a few |
| // fields from it. See RFC 9345, section 4. |
| CBS cbs, spki, sig; |
| uint32_t valid_time; |
| uint16_t dc_cert_verify_algorithm, algorithm; |
| CRYPTO_BUFFER_init_CBS(dc, &cbs); |
| if (!CBS_get_u32(&cbs, &valid_time) || |
| !CBS_get_u16(&cbs, &dc_cert_verify_algorithm) || |
| !CBS_get_u24_length_prefixed(&cbs, &spki) || |
| !CBS_get_u16(&cbs, &algorithm) || |
| !CBS_get_u16_length_prefixed(&cbs, &sig) || // |
| CBS_len(&sig) == 0 || // |
| CBS_len(&cbs) != 0) { |
| OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); |
| return 0; |
| } |
| |
| // RFC 9345 forbids algorithms that use the rsaEncryption OID. As the |
| // RSASSA-PSS OID is unusably complicated, this effectively means we will not |
| // support RSA delegated credentials. |
| if (SSL_get_signature_algorithm_key_type(dc_cert_verify_algorithm) == |
| EVP_PKEY_RSA) { |
| OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM); |
| return 0; |
| } |
| |
| UniquePtr<EVP_PKEY> pubkey(EVP_parse_public_key(&spki)); |
| if (pubkey == nullptr || CBS_len(&spki) != 0) { |
| OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); |
| return 0; |
| } |
| |
| if (!cred->sigalgs.CopyFrom(MakeConstSpan(&dc_cert_verify_algorithm, 1))) { |
| return 0; |
| } |
| |
| if (cred->privkey != nullptr && |
| !ssl_compare_public_and_private_key(pubkey.get(), cred->privkey.get())) { |
| return 0; |
| } |
| |
| cred->dc = UpRef(dc); |
| cred->pubkey = std::move(pubkey); |
| cred->dc_algorithm = algorithm; |
| return 1; |
| } |
| |
| int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred, |
| CRYPTO_BUFFER *ocsp) { |
| if (!cred->UsesX509()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| |
| cred->ocsp_response = UpRef(ocsp); |
| return 1; |
| } |
| |
| int SSL_CREDENTIAL_set1_signed_cert_timestamp_list(SSL_CREDENTIAL *cred, |
| CRYPTO_BUFFER *sct_list) { |
| if (!cred->UsesX509()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| |
| CBS cbs; |
| CRYPTO_BUFFER_init_CBS(sct_list, &cbs); |
| if (!ssl_is_sct_list_valid(&cbs)) { |
| OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SCT_LIST); |
| return 0; |
| } |
| |
| cred->signed_cert_timestamp_list = UpRef(sct_list); |
| return 1; |
| } |
| |
| int SSL_CTX_add1_credential(SSL_CTX *ctx, SSL_CREDENTIAL *cred) { |
| if (!cred->IsComplete()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| return ctx->cert->credentials.Push(UpRef(cred)); |
| } |
| |
| int SSL_add1_credential(SSL *ssl, SSL_CREDENTIAL *cred) { |
| if (ssl->config == nullptr) { |
| return 0; |
| } |
| |
| if (!cred->IsComplete()) { |
| OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
| return 0; |
| } |
| return ssl->config->cert->credentials.Push(UpRef(cred)); |
| } |
| |
| const SSL_CREDENTIAL *SSL_get0_selected_credential(const SSL *ssl) { |
| if (ssl->s3->hs == nullptr) { |
| return nullptr; |
| } |
| return ssl->s3->hs->credential.get(); |
| } |
| |
| int SSL_CREDENTIAL_get_ex_new_index(long argl, void *argp, |
| CRYPTO_EX_unused *unused, |
| CRYPTO_EX_dup *dup_unused, |
| CRYPTO_EX_free *free_func) { |
| return CRYPTO_get_ex_new_index_ex(&g_ex_data_class, argl, argp, free_func); |
| } |
| |
| int SSL_CREDENTIAL_set_ex_data(SSL_CREDENTIAL *cred, int idx, void *arg) { |
| return CRYPTO_set_ex_data(&cred->ex_data, idx, arg); |
| } |
| |
| void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred, int idx) { |
| return CRYPTO_get_ex_data(&cred->ex_data, idx); |
| } |