/* Copyright 2024 The BoringSSL Authors * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "../crypto/internal.h" #include "../crypto/spake2plus/internal.h" #include "internal.h" BSSL_NAMESPACE_BEGIN // new_leafless_chain returns a fresh stack of buffers set to {nullptr}. static UniquePtr new_leafless_chain(void) { UniquePtr 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 *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->InitForOverwrite(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; } bool ssl_credential_matches_requested_issuers(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred) { if (!cred->must_match_issuer) { // This credential does not need to match a requested issuer, so // it is good to use without a match. return true; } // If we have names sent by the CA extension, and this // credential matches it, it is good. if (hs->ca_names != nullptr) { for (const CRYPTO_BUFFER *ca_name : hs->ca_names.get()) { if (cred->ChainContainsIssuer( Span(CRYPTO_BUFFER_data(ca_name), CRYPTO_BUFFER_len(ca_name)))) { return true; } } } // TODO(bbe): Other forms of issuer matching go here. OPENSSL_PUT_ERROR(SSL, SSL_R_NO_MATCHING_ISSUER); return false; } 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(buffer)); return const_cast(buffer); } UniquePtr ssl_credential_st::Dup() const { assert(type == SSLCredentialType::kX509); UniquePtr ret = MakeUnique(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 { switch (type) { case SSLCredentialType::kX509: case SSLCredentialType::kDelegated: return true; case SSLCredentialType::kSPAKE2PlusV1Client: case SSLCredentialType::kSPAKE2PlusV1Server: return false; } abort(); } bool ssl_credential_st::UsesPrivateKey() const { switch (type) { case SSLCredentialType::kX509: case SSLCredentialType::kDelegated: return true; case SSLCredentialType::kSPAKE2PlusV1Client: case SSLCredentialType::kSPAKE2PlusV1Server: return false; } abort(); } 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 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 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 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::HasPAKEAttempts() const { return pake_limit.load() != 0; } bool ssl_credential_st::ClaimPAKEAttempt() const { uint32_t current = pake_limit.load(std::memory_order_relaxed); for (;;) { if (current == 0) { return false; } if (pake_limit.compare_exchange_weak(current, current - 1)) { break; } } return true; } void ssl_credential_st::RestorePAKEAttempt() const { // This should not overflow because it will only be paired with // ClaimPAKEAttempt. pake_limit.fetch_add(1); } bool ssl_credential_st::AppendIntermediateCert(UniquePtr 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(SSLCredentialType::kX509); } SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) { return New(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 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(Span(&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_spake2plusv1_register(uint8_t out_w0[32], uint8_t out_w1[32], uint8_t out_registration_record[65], const uint8_t *password, size_t password_len, const uint8_t *client_identity, size_t client_identity_len, const uint8_t *server_identity, size_t server_identity_len) { return spake2plus::Register( Span(out_w0, 32), Span(out_w1, 32), Span(out_registration_record, 65), Span(password, password_len), Span(client_identity, client_identity_len), Span(server_identity, server_identity_len)); } static UniquePtr ssl_credential_new_spake2plusv1( SSLCredentialType type, Span context, Span client_identity, Span server_identity, uint32_t limit) { assert(type == SSLCredentialType::kSPAKE2PlusV1Client || type == SSLCredentialType::kSPAKE2PlusV1Server); auto cred = MakeUnique(type); if (cred == nullptr) { return nullptr; } if (!cred->pake_context.CopyFrom(context) || !cred->client_identity.CopyFrom(client_identity) || !cred->server_identity.CopyFrom(server_identity)) { return nullptr; } cred->pake_limit.store(limit); return cred; } SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_client( const uint8_t *context, size_t context_len, const uint8_t *client_identity, size_t client_identity_len, const uint8_t *server_identity, size_t server_identity_len, uint32_t error_limit, const uint8_t *w0, size_t w0_len, const uint8_t *w1, size_t w1_len) { if (w0_len != spake2plus::kVerifierSize || w1_len != spake2plus::kVerifierSize || (context == nullptr && context_len != 0)) { OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SPAKE2PLUSV1_VALUE); return nullptr; } UniquePtr cred = ssl_credential_new_spake2plusv1( SSLCredentialType::kSPAKE2PlusV1Client, Span(context, context_len), Span(client_identity, client_identity_len), Span(server_identity, server_identity_len), error_limit); if (!cred) { return nullptr; } if (!cred->password_verifier_w0.CopyFrom(Span(w0, w0_len)) || !cred->password_verifier_w1.CopyFrom(Span(w1, w1_len))) { return nullptr; } return cred.release(); } SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_server( const uint8_t *context, size_t context_len, const uint8_t *client_identity, size_t client_identity_len, const uint8_t *server_identity, size_t server_identity_len, uint32_t rate_limit, const uint8_t *w0, size_t w0_len, const uint8_t *registration_record, size_t registration_record_len) { if (w0_len != spake2plus::kVerifierSize || registration_record_len != spake2plus::kRegistrationRecordSize || (context == nullptr && context_len != 0)) { return nullptr; } UniquePtr cred = ssl_credential_new_spake2plusv1( SSLCredentialType::kSPAKE2PlusV1Server, Span(context, context_len), Span(client_identity, client_identity_len), Span(server_identity, server_identity_len), rate_limit); if (!cred) { return nullptr; } if (!cred->password_verifier_w0.CopyFrom(Span(w0, w0_len)) || !cred->registration_record.CopyFrom( Span(registration_record, registration_record_len))) { return nullptr; } return cred.release(); } 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); } void SSL_CREDENTIAL_set_must_match_issuer(SSL_CREDENTIAL *cred) { cred->must_match_issuer = true; } void SSL_CREDENTIAL_clear_must_match_issuer(SSL_CREDENTIAL *cred) { cred->must_match_issuer = false; } int SSL_CREDENTIAL_must_match_issuer(const SSL_CREDENTIAL *cred) { return cred->must_match_issuer ? 1 : 0; }