diff options
-rw-r--r-- | automation/abi-check/expected-report-libssl3.so.txt | 13 | ||||
-rw-r--r-- | gtests/ssl_gtest/libssl_internals.c | 23 | ||||
-rw-r--r-- | gtests/ssl_gtest/libssl_internals.h | 2 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_agent.cc | 8 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_agent.h | 5 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_filter.h | 13 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_subcerts_unittest.cc | 120 | ||||
-rw-r--r-- | lib/ssl/ssl3con.c | 77 | ||||
-rw-r--r-- | lib/ssl/sslimpl.h | 1 | ||||
-rw-r--r-- | lib/ssl/sslinfo.c | 4 | ||||
-rw-r--r-- | lib/ssl/sslt.h | 13 | ||||
-rw-r--r-- | lib/ssl/tls13con.c | 13 |
12 files changed, 275 insertions, 17 deletions
diff --git a/automation/abi-check/expected-report-libssl3.so.txt b/automation/abi-check/expected-report-libssl3.so.txt index e69de29bb..bf902d170 100644 --- a/automation/abi-check/expected-report-libssl3.so.txt +++ b/automation/abi-check/expected-report-libssl3.so.txt @@ -0,0 +1,13 @@ + +1 function with some indirect sub-type change: + + [C]'function SECStatus SSL_GetPreliminaryChannelInfo(PRFileDesc*, SSLPreliminaryChannelInfo*, PRUintn)' at sslinfo.c:113:1 has some indirect sub-type changes: + parameter 2 of type 'SSLPreliminaryChannelInfo*' has sub-type changes: + in pointed to type 'typedef SSLPreliminaryChannelInfo' at sslt.h:424:1: + underlying type 'struct SSLPreliminaryChannelInfoStr' at sslt.h:373:1 changed: + type size changed from 192 to 288 (in bits) + 3 data member insertions: + 'PRBool SSLPreliminaryChannelInfoStr::peerDelegCred', at offset 192 (in bits) at sslt.h:418:1 + 'PRUint32 SSLPreliminaryChannelInfoStr::authKeyBits', at offset 224 (in bits) at sslt.h:419:1 + 'SSLSignatureScheme SSLPreliminaryChannelInfoStr::signatureScheme', at offset 256 (in bits) at sslt.h:420:1 + diff --git a/gtests/ssl_gtest/libssl_internals.c b/gtests/ssl_gtest/libssl_internals.c index d3e4a13ed..44eee9aa8 100644 --- a/gtests/ssl_gtest/libssl_internals.c +++ b/gtests/ssl_gtest/libssl_internals.c @@ -12,6 +12,29 @@ #include "seccomon.h" #include "selfencrypt.h" +SECStatus SSLInt_TweakChannelInfoForDC(PRFileDesc *fd, PRBool changeAuthKeyBits, + PRBool changeScheme) { + if (!fd) { + return SECFailure; + } + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + // Just toggle so we'll always have a valid value. + if (changeScheme) { + ss->sec.signatureScheme = (ss->sec.signatureScheme == ssl_sig_ed25519) + ? ssl_sig_ecdsa_secp256r1_sha256 + : ssl_sig_ed25519; + } + if (changeAuthKeyBits) { + ss->sec.authKeyBits = ss->sec.authKeyBits ? ss->sec.authKeyBits * 2 : 384; + } + + return SECSuccess; +} + SECStatus SSLInt_GetHandshakeRandoms(PRFileDesc *fd, SSL3Random client_random, SSL3Random server_random) { if (!fd) { diff --git a/gtests/ssl_gtest/libssl_internals.h b/gtests/ssl_gtest/libssl_internals.h index cfba19250..a908c9ab1 100644 --- a/gtests/ssl_gtest/libssl_internals.h +++ b/gtests/ssl_gtest/libssl_internals.h @@ -42,5 +42,7 @@ SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra); SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group); SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending); SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size); +SECStatus SSLInt_TweakChannelInfoForDC(PRFileDesc *fd, PRBool changeAuthKeyBits, + PRBool changeScheme); #endif // ndef libssl_internals_h_ diff --git a/gtests/ssl_gtest/tls_agent.cc b/gtests/ssl_gtest/tls_agent.cc index bd50434c6..0c6faabd9 100644 --- a/gtests/ssl_gtest/tls_agent.cc +++ b/gtests/ssl_gtest/tls_agent.cc @@ -842,6 +842,13 @@ void TlsAgent::ResetPreliminaryInfo() { expected_cipher_suite_ = 0; } +void TlsAgent::UpdatePreliminaryChannelInfo() { + SECStatus rv = SSL_GetPreliminaryChannelInfo(ssl_fd_.get(), &pre_info_, + sizeof(pre_info_)); + EXPECT_EQ(SECSuccess, rv); + EXPECT_EQ(sizeof(pre_info_), pre_info_.length); +} + void TlsAgent::ValidateCipherSpecs() { PRInt32 cipherSpecs = SSLInt_CountCipherSpecs(ssl_fd()); // We use one ciphersuite in each direction. @@ -904,6 +911,7 @@ void TlsAgent::Connected() { // Preliminary values are exposed through callbacks during the handshake. // If either expected values were set or the callbacks were called, check // that the final values are correct. + UpdatePreliminaryChannelInfo(); EXPECT_EQ(expected_version_, info_.protocolVersion); EXPECT_EQ(expected_cipher_suite_, info_.cipherSuite); diff --git a/gtests/ssl_gtest/tls_agent.h b/gtests/ssl_gtest/tls_agent.h index d01618baa..4b6cce8e0 100644 --- a/gtests/ssl_gtest/tls_agent.h +++ b/gtests/ssl_gtest/tls_agent.h @@ -134,6 +134,7 @@ class TlsAgent : public PollTarget { void AddDelegatedCredential(const std::string& dc_name, SSLSignatureScheme dcCertVerifyAlg, PRUint32 dcValidFor, PRTime now); + void UpdatePreliminaryChannelInfo(); bool ConfigServerCert(const std::string& name, bool updateKeyBits = false, const SSLExtraServerCertData* serverCertData = nullptr); @@ -228,6 +229,9 @@ class TlsAgent : public PollTarget { EXPECT_EQ(STATE_CONNECTED, state_); return info_; } + + const SSLPreliminaryChannelInfo& pre_info() const { return pre_info_; } + bool is_compressed() const { return info().compressionMethod != ssl_compression_null; } @@ -425,6 +429,7 @@ class TlsAgent : public PollTarget { bool handshake_callback_called_; bool resumption_callback_called_; SSLChannelInfo info_; + SSLPreliminaryChannelInfo pre_info_; SSLCipherSuiteInfo csinfo_; SSLVersionRange vrange_; PRErrorCode error_code_; diff --git a/gtests/ssl_gtest/tls_filter.h b/gtests/ssl_gtest/tls_filter.h index f036fbfca..ec954e732 100644 --- a/gtests/ssl_gtest/tls_filter.h +++ b/gtests/ssl_gtest/tls_filter.h @@ -441,6 +441,19 @@ class TlsExtensionDropper : public TlsExtensionFilter { uint16_t extension_; }; +class TlsHandshakeDropper : public TlsHandshakeFilter { + public: + TlsHandshakeDropper(const std::shared_ptr<TlsAgent>& a) + : TlsHandshakeFilter(a) {} + + protected: + PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) override { + return DROP; + } +}; + class TlsExtensionInjector : public TlsHandshakeFilter { public: TlsExtensionInjector(const std::shared_ptr<TlsAgent>& a, uint16_t ext, diff --git a/gtests/ssl_gtest/tls_subcerts_unittest.cc b/gtests/ssl_gtest/tls_subcerts_unittest.cc index 48e7a7b68..0882ef7ef 100644 --- a/gtests/ssl_gtest/tls_subcerts_unittest.cc +++ b/gtests/ssl_gtest/tls_subcerts_unittest.cc @@ -22,14 +22,63 @@ const std::string kDCId = TlsAgent::kServerEcdsa256; const SSLSignatureScheme kDCScheme = ssl_sig_ecdsa_secp256r1_sha256; const PRUint32 kDCValidFor = 60 * 60 * 24 * 7 /* 1 week (seconds */; +static void CheckPreliminaryPeerDelegCred( + const std::shared_ptr<TlsAgent>& client, bool expected, + PRUint32 key_bits = 0, SSLSignatureScheme sig_scheme = ssl_sig_none) { + EXPECT_NE(0U, (client->pre_info().valuesSet & ssl_preinfo_peer_auth)); + EXPECT_EQ(expected, client->pre_info().peerDelegCred); + if (expected) { + EXPECT_EQ(key_bits, client->pre_info().authKeyBits); + EXPECT_EQ(sig_scheme, client->pre_info().signatureScheme); + } +} + static void CheckPeerDelegCred(const std::shared_ptr<TlsAgent>& client, bool expected, PRUint32 key_bits = 0) { EXPECT_EQ(expected, client->info().peerDelegCred); + EXPECT_EQ(expected, client->pre_info().peerDelegCred); if (expected) { EXPECT_EQ(key_bits, client->info().authKeyBits); + EXPECT_EQ(key_bits, client->pre_info().authKeyBits); + EXPECT_EQ(client->info().signatureScheme, + client->pre_info().signatureScheme); } } +// AuthCertificate callbacks to simulate DC validation +static SECStatus CheckPreliminaryDC(TlsAgent* agent, bool checksig, + bool isServer) { + agent->UpdatePreliminaryChannelInfo(); + EXPECT_EQ(PR_TRUE, agent->pre_info().peerDelegCred); + EXPECT_EQ(256U, agent->pre_info().authKeyBits); + EXPECT_EQ(ssl_sig_ecdsa_secp256r1_sha256, agent->pre_info().signatureScheme); + return SECSuccess; +} + +static SECStatus CheckPreliminaryNoDC(TlsAgent* agent, bool checksig, + bool isServer) { + agent->UpdatePreliminaryChannelInfo(); + EXPECT_EQ(PR_FALSE, agent->pre_info().peerDelegCred); + return SECSuccess; +} + +// AuthCertificate callbacks for modifying DC attributes. +// This allows testing tls13_CertificateVerify for rejection +// of DC attributes that have changed since AuthCertificateHook +// may have handled them. +static SECStatus ModifyDCAuthKeyBits(TlsAgent* agent, bool checksig, + bool isServer) { + return SSLInt_TweakChannelInfoForDC(agent->ssl_fd(), + PR_TRUE, // Change authKeyBits + PR_FALSE); // Change scheme +} + +static SECStatus ModifyDCScheme(TlsAgent* agent, bool checksig, bool isServer) { + return SSLInt_TweakChannelInfoForDC(agent->ssl_fd(), + PR_FALSE, // Change authKeyBits + PR_TRUE); // Change scheme +} + // Attempt to configure a DC when either the DC or DC private key is missing. TEST_P(TlsConnectTls13, DCNotConfigured) { // Load and delegate the credential. @@ -388,6 +437,77 @@ TEST_P(TlsConnectTls13, DCConnectExpectedCertVerifyAlgNotSupported) { CheckPeerDelegCred(client_, false); } +// Check that preliminary channel info properly reflects the DC. +TEST_P(TlsConnectTls13, DCCheckPreliminaryInfo) { + Reset(kEcdsaDelegatorId); + EnsureTlsSetup(); + client_->EnableDelegatedCredentials(); + server_->AddDelegatedCredential(TlsAgent::kServerEcdsa256, + ssl_sig_ecdsa_secp256r1_sha256, kDCValidFor, + now()); + + auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_); + filter->SetHandshakeTypes( + {kTlsHandshakeCertificateVerify, kTlsHandshakeFinished}); + filter->EnableDecryption(); + StartConnect(); + client_->Handshake(); // Send ClientHello + server_->Handshake(); // Send ServerHello + + client_->SetAuthCertificateCallback(CheckPreliminaryDC); + client_->Handshake(); // Process response + + client_->UpdatePreliminaryChannelInfo(); + CheckPreliminaryPeerDelegCred(client_, true, 256, + ssl_sig_ecdsa_secp256r1_sha256); +} + +// Check that preliminary channel info properly reflects a lack of DC. +TEST_P(TlsConnectTls13, DCCheckPreliminaryInfoNoDC) { + Reset(kEcdsaDelegatorId); + EnsureTlsSetup(); + client_->EnableDelegatedCredentials(); + auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_); + filter->SetHandshakeTypes( + {kTlsHandshakeCertificateVerify, kTlsHandshakeFinished}); + filter->EnableDecryption(); + StartConnect(); + client_->Handshake(); // Send ClientHello + server_->Handshake(); // Send ServerHello + + client_->SetAuthCertificateCallback(CheckPreliminaryNoDC); + client_->Handshake(); // Process response + + client_->UpdatePreliminaryChannelInfo(); + CheckPreliminaryPeerDelegCred(client_, false); +} + +// Tweak the scheme in between |Cert| and |CertVerify|. +TEST_P(TlsConnectTls13, DCRejectModifiedDCScheme) { + Reset(kEcdsaDelegatorId); + client_->EnableDelegatedCredentials(); + client_->SetAuthCertificateCallback(ModifyDCScheme); + server_->AddDelegatedCredential(TlsAgent::kServerEcdsa521, + ssl_sig_ecdsa_secp521r1_sha512, kDCValidFor, + now()); + ConnectExpectAlert(client_, kTlsAlertIllegalParameter); + server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + client_->CheckErrorCode(SSL_ERROR_DC_CERT_VERIFY_ALG_MISMATCH); +} + +// Tweak the authKeyBits in between |Cert| and |CertVerify|. +TEST_P(TlsConnectTls13, DCRejectModifiedDCAuthKeyBits) { + Reset(kEcdsaDelegatorId); + client_->EnableDelegatedCredentials(); + client_->SetAuthCertificateCallback(ModifyDCAuthKeyBits); + server_->AddDelegatedCredential(TlsAgent::kServerEcdsa521, + ssl_sig_ecdsa_secp521r1_sha512, kDCValidFor, + now()); + ConnectExpectAlert(client_, kTlsAlertIllegalParameter); + server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); + client_->CheckErrorCode(SSL_ERROR_DC_CERT_VERIFY_ALG_MISMATCH); +} + class DCDelegation : public ::testing::Test {}; TEST_F(DCDelegation, DCDelegations) { diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index 79fdf6b04..29a34aeb5 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -21,6 +21,7 @@ #include "sslerr.h" #include "ssl3ext.h" #include "ssl3exthandle.h" +#include "tls13subcerts.h" #include "prtime.h" #include "prinrval.h" #include "prerror.h" @@ -11047,6 +11048,47 @@ ssl_SetAuthKeyBits(sslSocket *ss, const SECKEYPublicKey *pubKey) : illegal_parameter); return SECFailure; } + + /* PreliminaryChannelInfo.authKeyBits, scheme, and peerDelegCred are now valid. */ + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_peer_auth; + + return SECSuccess; +} + +SECStatus +ssl3_HandleServerSpki(sslSocket *ss) +{ + PORT_Assert(!ss->sec.isServer); + SECKEYPublicKey *pubKey; + + if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 && + tls13_IsVerifyingWithDelegatedCredential(ss)) { + sslDelegatedCredential *dc = ss->xtnData.peerDelegCred; + pubKey = SECKEY_ExtractPublicKey(dc->spki); + if (!pubKey) { + PORT_SetError(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE); + return SECFailure; + } + + /* Because we have only a single authType (ssl_auth_tls13_any) + * for TLS 1.3 at this point, set the scheme so that the + * callback can interpret |authKeyBits| correctly. + */ + ss->sec.signatureScheme = dc->expectedCertVerifyAlg; + } else { + pubKey = CERT_ExtractPublicKey(ss->sec.peerCert); + if (!pubKey) { + PORT_SetError(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE); + return SECFailure; + } + } + + SECStatus rv = ssl_SetAuthKeyBits(ss, pubKey); + SECKEY_DestroyPublicKey(pubKey); + if (rv != SECSuccess) { + return rv; /* Alert sent and code set. */ + } + return SECSuccess; } @@ -11061,6 +11103,26 @@ ssl3_AuthCertificate(sslSocket *ss) PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == ssl_preinfo_all); + + if (!ss->sec.isServer) { + /* Set the |spki| used to verify the handshake. When verifying with a + * delegated credential (DC), this corresponds to the DC public key; + * otherwise it correspond to the public key of the peer's end-entity + * certificate. */ + rv = ssl3_HandleServerSpki(ss); + if (rv != SECSuccess) { + /* Alert sent and code set (if not SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE). + * In either case, we're done here. */ + errCode = PORT_GetError(); + goto loser; + } + + if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { + ss->sec.authType = ss->ssl3.hs.kea_def->authKeyType; + ss->sec.keaType = ss->ssl3.hs.kea_def->exchKeyType; + } + } + /* * Ask caller-supplied callback function to validate cert chain. */ @@ -11099,21 +11161,6 @@ ssl3_AuthCertificate(sslSocket *ss) ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); if (!ss->sec.isServer) { - if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { - /* These are filled in in tls13_HandleCertificateVerify and - * tls13_HandleServerKeyShare (keaType). */ - SECKEYPublicKey *pubKey = CERT_ExtractPublicKey(ss->sec.peerCert); - if (pubKey) { - rv = ssl_SetAuthKeyBits(ss, pubKey); - SECKEY_DestroyPublicKey(pubKey); - if (rv != SECSuccess) { - return SECFailure; /* Alert sent and code set. */ - } - } - ss->sec.authType = ss->ssl3.hs.kea_def->authKeyType; - ss->sec.keaType = ss->ssl3.hs.kea_def->exchKeyType; - } - if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) { TLS13_SET_HS_STATE(ss, wait_cert_verify); } else { diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index faeedad12..4a393b281 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -1678,6 +1678,7 @@ SECStatus ssl3_SendEmptyCertificate(sslSocket *ss); void ssl3_CleanupPeerCerts(sslSocket *ss); SECStatus ssl3_SendCertificateStatus(sslSocket *ss); SECStatus ssl_SetAuthKeyBits(sslSocket *ss, const SECKEYPublicKey *pubKey); +SECStatus ssl3_HandleServerSpki(sslSocket *ss); SECStatus ssl3_AuthCertificate(sslSocket *ss); SECStatus ssl_ReadCertificateStatus(sslSocket *ss, PRUint8 *b, PRUint32 length); diff --git a/lib/ssl/sslinfo.c b/lib/ssl/sslinfo.c index 4834605c1..b069888e2 100644 --- a/lib/ssl/sslinfo.c +++ b/lib/ssl/sslinfo.c @@ -154,6 +154,10 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd, } inf.zeroRttCipherSuite = ss->ssl3.hs.zeroRttSuite; + inf.peerDelegCred = tls13_IsVerifyingWithDelegatedCredential(ss); + inf.authKeyBits = ss->sec.authKeyBits; + inf.signatureScheme = ss->sec.signatureScheme; + memcpy(info, &inf, inf.length); return SECSuccess; } diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h index 437fe5ccb..47efa2e4d 100644 --- a/lib/ssl/sslt.h +++ b/lib/ssl/sslt.h @@ -366,6 +366,9 @@ typedef struct SSLChannelInfoStr { #define ssl_preinfo_version (1U << 0) #define ssl_preinfo_cipher_suite (1U << 1) #define ssl_preinfo_0rtt_cipher_suite (1U << 2) +/* ssl_preinfo_peer_auth covers peerDelegCred, authKeyBits, and scheme. Not + * included in ssl_preinfo_all as it is client-only. */ +#define ssl_preinfo_peer_auth (1U << 3) /* ssl_preinfo_all doesn't contain ssl_preinfo_0rtt_cipher_suite because that * field is only set if 0-RTT is sent (client) or accepted (server). */ #define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite) @@ -406,6 +409,16 @@ typedef struct SSLPreliminaryChannelInfoStr { * after accepting 0-RTT, so this will contain the same value. */ PRUint16 zeroRttCipherSuite; + /* The following fields were added in NSS 3.48. */ + /* These fields contain information about the key that will be used in + * the CertificateVerify message. If Delegated Credentials are being used, + * this is the DC-contained SPKI, else the EE-cert SPKI. These fields are + * valid only after the Certificate message is handled. This can be determined + * by checking the valuesSet field against |ssl_preinfo_peer_auth|. */ + PRBool peerDelegCred; + PRUint32 authKeyBits; + SSLSignatureScheme signatureScheme; + /* When adding new fields to this structure, please document the * NSS version in which they were added. */ } SSLPreliminaryChannelInfo; diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c index c24684150..513f1ced7 100644 --- a/lib/ssl/tls13con.c +++ b/lib/ssl/tls13con.c @@ -4184,8 +4184,10 @@ tls13_HandleCertificateVerify(sslSocket *ss, PRUint8 *b, PRUint32 length) if (tls13_IsVerifyingWithDelegatedCredential(ss)) { /* DelegatedCredential.cred.expected_cert_verify_algorithm is expected * to match CertificateVerify.scheme. + * DelegatedCredential.cred.expected_cert_verify_algorithm must also be + * the same as was reported in ssl3_AuthCertificate. */ - if (sigScheme != dc->expectedCertVerifyAlg) { + if (sigScheme != dc->expectedCertVerifyAlg || sigScheme != ss->sec.signatureScheme) { FATAL_ERROR(ss, SSL_ERROR_DC_CERT_VERIFY_ALG_MISMATCH, illegal_parameter); return SECFailure; } @@ -4245,13 +4247,20 @@ tls13_HandleCertificateVerify(sslSocket *ss, PRUint8 *b, PRUint32 length) goto loser; } - /* Set the auth type. */ + /* Set the auth type and verify it is what we captured in ssl3_AuthCertificate */ if (!ss->sec.isServer) { ss->sec.authType = ssl_SignatureSchemeToAuthType(sigScheme); + + uint32_t prelimAuthKeyBits = ss->sec.authKeyBits; rv = ssl_SetAuthKeyBits(ss, pubKey); if (rv != SECSuccess) { goto loser; /* Alert sent and code set. */ } + + if (prelimAuthKeyBits != ss->sec.authKeyBits) { + FATAL_ERROR(ss, SSL_ERROR_DC_CERT_VERIFY_ALG_MISMATCH, illegal_parameter); + goto loser; + } } /* Request a client certificate now if one was requested. */ |