From d7c8d7de9d84a5b31ba876af376c5742393a22f1 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Tue, 16 Jan 2018 13:50:59 +0100 Subject: [PATCH] Bug 1399439 - API for external TLS session caches, r=mt Reviewers: mt, ekr Bug #: 1399439 Differential Revision: https://phabricator.services.mozilla.com/D284 --HG-- extra : rebase_source : b0bb342c952973b4d1ef6cfff7c4810904a8bbb1 extra : histedit_source : 3edefe0328a634ce2aec862458b8cb483a7a2dbd --- cpputil/scoped_ptrs.h | 5 + gtests/ssl_gtest/ssl_loopback_unittest.cc | 25 +- gtests/ssl_gtest/ssl_resumption_unittest.cc | 140 +++- gtests/ssl_gtest/tls_agent.cc | 46 +- gtests/ssl_gtest/tls_agent.h | 15 + gtests/ssl_gtest/tls_connect.cc | 13 + gtests/ssl_gtest/tls_connect.h | 46 +- lib/certdb/stanpcertdb.c | 2 +- lib/ssl/selfencrypt.c | 44 +- lib/ssl/ssl3con.c | 66 +- lib/ssl/ssl3exthandle.c | 5 +- lib/ssl/sslcon.c | 17 +- lib/ssl/sslencode.c | 59 +- lib/ssl/sslencode.h | 30 +- lib/ssl/sslerr.h | 1 + lib/ssl/sslexp.h | 99 ++- lib/ssl/sslimpl.h | 61 +- lib/ssl/sslinit.c | 1 + lib/ssl/sslnonce.c | 780 +++++++++++++++++++- lib/ssl/sslsecur.c | 5 +- lib/ssl/sslsnce.c | 20 +- lib/ssl/sslsock.c | 161 +++- lib/ssl/tls13con.c | 17 +- lib/ssl/tls13hashstate.c | 34 +- 24 files changed, 1499 insertions(+), 193 deletions(-) diff --git a/cpputil/scoped_ptrs.h b/cpputil/scoped_ptrs.h index b92b8132b7..e86603a97d 100644 --- a/cpputil/scoped_ptrs.h +++ b/cpputil/scoped_ptrs.h @@ -12,6 +12,7 @@ #include "keyhi.h" #include "pk11pub.h" #include "pkcs11uri.h" +#include "sslexp.h" struct ScopedDelete { void operator()(CERTCertificate* cert) { CERT_DestroyCertificate(cert); } @@ -37,6 +38,9 @@ struct ScopedDelete { void operator()(PLArenaPool* arena) { PORT_FreeArena(arena, PR_FALSE); } void operator()(PK11Context* context) { PK11_DestroyContext(context, true); } void operator()(PK11GenericObject* obj) { PK11_DestroyGenericObject(obj); } + void operator()(SSLResumptionTokenInfo* token) { + SSL_DestroyResumptionTokenInfo(token); + } }; template @@ -68,6 +72,7 @@ SCOPED(PK11URI); SCOPED(PLArenaPool); SCOPED(PK11Context); SCOPED(PK11GenericObject); +SCOPED(SSLResumptionTokenInfo); #undef SCOPED diff --git a/gtests/ssl_gtest/ssl_loopback_unittest.cc b/gtests/ssl_gtest/ssl_loopback_unittest.cc index 4bc6e60ab4..127e539552 100644 --- a/gtests/ssl_gtest/ssl_loopback_unittest.cc +++ b/gtests/ssl_gtest/ssl_loopback_unittest.cc @@ -535,4 +535,27 @@ INSTANTIATE_TEST_CASE_P(Version12Plus, TlsConnectTls12Plus, ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll, TlsConnectTestBase::kTlsV12Plus)); -} // namespace nspr_test +INSTANTIATE_TEST_CASE_P( + GenericStream, TlsConnectGenericResumption, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream, + TlsConnectTestBase::kTlsVAll, + ::testing::Values(true, false))); +INSTANTIATE_TEST_CASE_P( + GenericDatagram, TlsConnectGenericResumption, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram, + TlsConnectTestBase::kTlsV11Plus, + ::testing::Values(true, false))); + +INSTANTIATE_TEST_CASE_P( + GenericStream, TlsConnectGenericResumptionToken, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream, + TlsConnectTestBase::kTlsVAll)); +INSTANTIATE_TEST_CASE_P( + GenericDatagram, TlsConnectGenericResumptionToken, + ::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram, + TlsConnectTestBase::kTlsV11Plus)); + +INSTANTIATE_TEST_CASE_P(GenericDatagram, TlsConnectTls13ResumptionToken, + TlsConnectTestBase::kTlsVariantsAll); + +} // namespace nss_test diff --git a/gtests/ssl_gtest/ssl_resumption_unittest.cc b/gtests/ssl_gtest/ssl_resumption_unittest.cc index 9cc6d1bfdf..8c449acaba 100644 --- a/gtests/ssl_gtest/ssl_resumption_unittest.cc +++ b/gtests/ssl_gtest/ssl_resumption_unittest.cc @@ -60,7 +60,7 @@ TEST_P(TlsConnectGenericPre13, ConnectResumed) { Connect(); } -TEST_P(TlsConnectGeneric, ConnectClientCacheDisabled) { +TEST_P(TlsConnectGenericResumption, ConnectClientCacheDisabled) { ConfigureSessionCache(RESUME_NONE, RESUME_SESSIONID); Connect(); SendReceive(); @@ -71,7 +71,7 @@ TEST_P(TlsConnectGeneric, ConnectClientCacheDisabled) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectServerCacheDisabled) { +TEST_P(TlsConnectGenericResumption, ConnectServerCacheDisabled) { ConfigureSessionCache(RESUME_SESSIONID, RESUME_NONE); Connect(); SendReceive(); @@ -82,7 +82,7 @@ TEST_P(TlsConnectGeneric, ConnectServerCacheDisabled) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectSessionCacheDisabled) { +TEST_P(TlsConnectGenericResumption, ConnectSessionCacheDisabled) { ConfigureSessionCache(RESUME_NONE, RESUME_NONE); Connect(); SendReceive(); @@ -93,7 +93,7 @@ TEST_P(TlsConnectGeneric, ConnectSessionCacheDisabled) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectResumeSupportBoth) { +TEST_P(TlsConnectGenericResumption, ConnectResumeSupportBoth) { // This prefers tickets. ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); Connect(); @@ -106,7 +106,7 @@ TEST_P(TlsConnectGeneric, ConnectResumeSupportBoth) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectResumeClientTicketServerBoth) { +TEST_P(TlsConnectGenericResumption, ConnectResumeClientTicketServerBoth) { // This causes no resumption because the client needs the // session cache to resume even with tickets. ConfigureSessionCache(RESUME_TICKET, RESUME_BOTH); @@ -120,7 +120,7 @@ TEST_P(TlsConnectGeneric, ConnectResumeClientTicketServerBoth) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectResumeClientBothTicketServerTicket) { +TEST_P(TlsConnectGenericResumption, ConnectResumeClientBothTicketServerTicket) { // This causes a ticket resumption. ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); Connect(); @@ -133,7 +133,7 @@ TEST_P(TlsConnectGeneric, ConnectResumeClientBothTicketServerTicket) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectResumeClientServerTicketOnly) { +TEST_P(TlsConnectGenericResumption, ConnectResumeClientServerTicketOnly) { // This causes no resumption because the client needs the // session cache to resume even with tickets. ConfigureSessionCache(RESUME_TICKET, RESUME_TICKET); @@ -147,7 +147,7 @@ TEST_P(TlsConnectGeneric, ConnectResumeClientServerTicketOnly) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectResumeClientBothServerNone) { +TEST_P(TlsConnectGenericResumption, ConnectResumeClientBothServerNone) { ConfigureSessionCache(RESUME_BOTH, RESUME_NONE); Connect(); SendReceive(); @@ -159,7 +159,7 @@ TEST_P(TlsConnectGeneric, ConnectResumeClientBothServerNone) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectResumeClientNoneServerBoth) { +TEST_P(TlsConnectGenericResumption, ConnectResumeClientNoneServerBoth) { ConfigureSessionCache(RESUME_NONE, RESUME_BOTH); Connect(); SendReceive(); @@ -202,7 +202,7 @@ TEST_P(TlsConnectGeneric, ConnectResumeClientBothTicketServerTicketForget) { SendReceive(); } -TEST_P(TlsConnectGeneric, ConnectWithExpiredTicketAtClient) { +TEST_P(TlsConnectGenericResumption, ConnectWithExpiredTicketAtClient) { SSLInt_SetTicketLifetime(1); // one second // This causes a ticket resumption. ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); @@ -417,7 +417,7 @@ static uint16_t ChooseAnotherCipher(uint16_t version) { } // Test that we don't resume when we can't negotiate the same cipher. -TEST_P(TlsConnectGeneric, TestResumeClientDifferentCipher) { +TEST_P(TlsConnectGenericResumption, TestResumeClientDifferentCipher) { ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); client_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); @@ -442,7 +442,7 @@ TEST_P(TlsConnectGeneric, TestResumeClientDifferentCipher) { } // Test that we don't resume when we can't negotiate the same cipher. -TEST_P(TlsConnectGeneric, TestResumeServerDifferentCipher) { +TEST_P(TlsConnectGenericResumption, TestResumeServerDifferentCipher) { ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); @@ -845,7 +845,7 @@ TEST_F(TlsConnectTest, TestTls13ResumptionForcedDowngrade) { server_->CheckErrorCode(SSL_ERROR_BAD_MAC_READ); } -TEST_P(TlsConnectGeneric, ReConnectTicket) { +TEST_P(TlsConnectGenericResumption, ReConnectTicket) { ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); @@ -877,7 +877,7 @@ TEST_P(TlsConnectGenericPre13, ReConnectCache) { ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); } -TEST_P(TlsConnectGeneric, ReConnectAgainTicket) { +TEST_P(TlsConnectGenericResumption, ReConnectAgainTicket) { ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); @@ -902,4 +902,116 @@ TEST_P(TlsConnectGeneric, ReConnectAgainTicket) { ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); } +void CheckGetInfoResult(uint32_t alpnSize, uint32_t earlyDataSize, + ScopedCERTCertificate& cert, + ScopedSSLResumptionTokenInfo& token) { + ASSERT_TRUE(cert); + ASSERT_TRUE(token->peerCert); + + // Check that the server cert is the correct one. + ASSERT_EQ(cert->derCert.len, token->peerCert->derCert.len); + EXPECT_EQ(0, memcmp(cert->derCert.data, token->peerCert->derCert.data, + cert->derCert.len)); + + ASSERT_EQ(alpnSize, token->alpnSelectionLen); + EXPECT_EQ(0, memcmp("a", token->alpnSelection, token->alpnSelectionLen)); + + ASSERT_EQ(earlyDataSize, token->maxEarlyDataSize); +} + +TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfo) { + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + Connect(); + SendReceive(); + + Reset(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + ExpectResumption(RESUME_TICKET); + + StartConnect(); + ASSERT_TRUE(client_->MaybeSetResumptionToken()); + + // Get resumption token infos + SSLResumptionTokenInfo tokenInfo = {0}; + ScopedSSLResumptionTokenInfo token(&tokenInfo); + client_->GetTokenInfo(token); + ScopedCERTCertificate cert( + PK11_FindCertFromNickname(server_->name().c_str(), nullptr)); + + CheckGetInfoResult(0, 0, cert, token); + + Handshake(); + CheckConnected(); + + SendReceive(); +} + +TEST_P(TlsConnectGenericResumptionToken, ConnectResumeGetInfoAlpn) { + EnableAlpn(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + Connect(); + CheckAlpn("a"); + SendReceive(); + + Reset(); + EnableAlpn(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + ExpectResumption(RESUME_TICKET); + + StartConnect(); + ASSERT_TRUE(client_->MaybeSetResumptionToken()); + + // Get resumption token infos + SSLResumptionTokenInfo tokenInfo = {0}; + ScopedSSLResumptionTokenInfo token(&tokenInfo); + client_->GetTokenInfo(token); + ScopedCERTCertificate cert( + PK11_FindCertFromNickname(server_->name().c_str(), nullptr)); + + CheckGetInfoResult(1, 0, cert, token); + + Handshake(); + CheckConnected(); + CheckAlpn("a"); + + SendReceive(); +} + +TEST_P(TlsConnectTls13ResumptionToken, ConnectResumeGetInfoZeroRtt) { + EnableAlpn(); + SSLInt_RolloverAntiReplay(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + server_->Set0RttEnabled(true); + Connect(); + CheckAlpn("a"); + SendReceive(); + + Reset(); + EnableAlpn(); + ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); + ExpectResumption(RESUME_TICKET); + + StartConnect(); + server_->Set0RttEnabled(true); + client_->Set0RttEnabled(true); + ASSERT_TRUE(client_->MaybeSetResumptionToken()); + + // Get resumption token infos + SSLResumptionTokenInfo tokenInfo = {0}; + ScopedSSLResumptionTokenInfo token(&tokenInfo); + client_->GetTokenInfo(token); + ScopedCERTCertificate cert( + PK11_FindCertFromNickname(server_->name().c_str(), nullptr)); + + CheckGetInfoResult(1, 1024, cert, token); + + ZeroRttSendReceive(true, true); + Handshake(); + ExpectEarlyDataAccepted(true); + CheckConnected(); + CheckAlpn("a"); + + SendReceive(); +} + } // namespace nss_test diff --git a/gtests/ssl_gtest/tls_agent.cc b/gtests/ssl_gtest/tls_agent.cc index 3b939bba86..ded09cb996 100644 --- a/gtests/ssl_gtest/tls_agent.cc +++ b/gtests/ssl_gtest/tls_agent.cc @@ -73,7 +73,8 @@ TlsAgent::TlsAgent(const std::string& name, Role role, handshake_callback_(), auth_certificate_callback_(), sni_callback_(), - skip_version_checks_(false) { + skip_version_checks_(false), + resumption_token_() { memset(&info_, 0, sizeof(info_)); memset(&csinfo_, 0, sizeof(csinfo_)); SECStatus rv = SSL_VersionRangeGetDefault(variant_, &vrange_); @@ -207,6 +208,29 @@ bool TlsAgent::EnsureTlsSetup(PRFileDesc* modelSocket) { return true; } +bool TlsAgent::MaybeSetResumptionToken() { + if (!resumption_token_.empty()) { + SECStatus rv = SSL_SetResumptionToken(ssl_fd(), resumption_token_.data(), + resumption_token_.size()); + + // rv is SECFailure with error set to SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR + // if the resumption token was bad (expired/malformed/etc.). + if (expect_resumption_) { + // Only in case we expect resumption this has to be successful. We might + // not expect resumption due to some reason but the token is totally fine. + EXPECT_EQ(SECSuccess, rv); + } + if (rv != SECSuccess) { + EXPECT_EQ(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR, PORT_GetError()); + resumption_token_.clear(); + EXPECT_FALSE(expect_resumption_); + if (expect_resumption_) return false; + } + } + + return true; +} + void TlsAgent::SetupClientAuth() { EXPECT_TRUE(EnsureTlsSetup()); ASSERT_EQ(CLIENT, role_); @@ -386,6 +410,26 @@ void TlsAgent::SetVersionRange(uint16_t minver, uint16_t maxver) { } } +SECStatus ResumptionTokenCallback(PRFileDesc* fd, + const PRUint8* resumptionToken, + unsigned int len, void* ctx) { + EXPECT_NE(nullptr, resumptionToken); + if (!resumptionToken) { + return SECFailure; + } + + std::vector new_token(resumptionToken, resumptionToken + len); + reinterpret_cast(ctx)->SetResumptionToken(new_token); + return SECSuccess; +} + +void TlsAgent::SetResumptionTokenCallback() { + EXPECT_TRUE(EnsureTlsSetup()); + SECStatus rv = + SSL_SetResumptionTokenCallback(ssl_fd(), ResumptionTokenCallback, this); + EXPECT_EQ(SECSuccess, rv); +} + void TlsAgent::GetVersionRange(uint16_t* minver, uint16_t* maxver) { *minver = vrange_.min; *maxver = vrange_.max; diff --git a/gtests/ssl_gtest/tls_agent.h b/gtests/ssl_gtest/tls_agent.h index b3fd892ae4..e578b8b099 100644 --- a/gtests/ssl_gtest/tls_agent.h +++ b/gtests/ssl_gtest/tls_agent.h @@ -165,6 +165,20 @@ class TlsAgent : public PollTarget { void DisableECDHEServerKeyReuse(); bool GetPeerChainLength(size_t* count); void CheckCipherSuite(uint16_t cipher_suite); + void SetResumptionTokenCallback(); + bool MaybeSetResumptionToken(); + void SetResumptionToken(const std::vector& resumption_token) { + resumption_token_ = resumption_token; + } + const std::vector& GetResumptionToken() const { + return resumption_token_; + } + void GetTokenInfo(ScopedSSLResumptionTokenInfo& token) { + SECStatus rv = SSL_GetResumptionTokenInfo( + resumption_token_.data(), resumption_token_.size(), token.get(), + sizeof(SSLResumptionTokenInfo)); + ASSERT_EQ(SECSuccess, rv); + } const std::string& name() const { return name_; } @@ -393,6 +407,7 @@ class TlsAgent : public PollTarget { AuthCertificateCallbackFunction auth_certificate_callback_; SniCallbackFunction sni_callback_; bool skip_version_checks_; + std::vector resumption_token_; }; inline std::ostream& operator<<(std::ostream& stream, diff --git a/gtests/ssl_gtest/tls_connect.cc b/gtests/ssl_gtest/tls_connect.cc index 8f5b1db595..c5cfda9ece 100644 --- a/gtests/ssl_gtest/tls_connect.cc +++ b/gtests/ssl_gtest/tls_connect.cc @@ -230,7 +230,9 @@ void TlsConnectTestBase::Reset() { void TlsConnectTestBase::Reset(const std::string& server_name, const std::string& client_name) { + auto token = client_->GetResumptionToken(); client_.reset(new TlsAgent(client_name, TlsAgent::CLIENT, variant_)); + client_->SetResumptionToken(token); server_.reset(new TlsAgent(server_name, TlsAgent::SERVER, variant_)); if (skip_version_checks_) { client_->SkipVersionChecks(); @@ -290,6 +292,7 @@ void TlsConnectTestBase::EnableExtendedMasterSecret() { void TlsConnectTestBase::Connect() { server_->StartConnect(server_model_ ? server_model_->ssl_fd() : nullptr); client_->StartConnect(client_model_ ? client_model_->ssl_fd() : nullptr); + client_->MaybeSetResumptionToken(); Handshake(); CheckConnected(); } @@ -755,6 +758,16 @@ TlsConnectTls12Plus::TlsConnectTls12Plus() TlsConnectTls13::TlsConnectTls13() : TlsConnectTestBase(GetParam(), SSL_LIBRARY_VERSION_TLS_1_3) {} +TlsConnectGenericResumption::TlsConnectGenericResumption() + : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())), + external_cache_(std::get<2>(GetParam())) {} + +TlsConnectTls13ResumptionToken::TlsConnectTls13ResumptionToken() + : TlsConnectTestBase(GetParam(), SSL_LIBRARY_VERSION_TLS_1_3) {} + +TlsConnectGenericResumptionToken::TlsConnectGenericResumptionToken() + : TlsConnectTestBase(std::get<0>(GetParam()), std::get<1>(GetParam())) {} + void TlsKeyExchangeTest::EnsureKeyShareSetup() { EnsureTlsSetup(); groups_capture_ = diff --git a/gtests/ssl_gtest/tls_connect.h b/gtests/ssl_gtest/tls_connect.h index c650dda1dc..730ec31ad4 100644 --- a/gtests/ssl_gtest/tls_connect.h +++ b/gtests/ssl_gtest/tls_connect.h @@ -55,7 +55,7 @@ class TlsConnectTestBase : public ::testing::Test { // Clear the server session cache. void ClearServerCache(); // Make sure TLS is configured for a connection. - void EnsureTlsSetup(); + virtual void EnsureTlsSetup(); // Reset and keep the same certificate names void Reset(); // Reset, and update the certificate names on both peers @@ -208,6 +208,50 @@ class TlsConnectGeneric : public TlsConnectTestBase, TlsConnectGeneric(); }; +class TlsConnectGenericResumption + : public TlsConnectTestBase, + public ::testing::WithParamInterface< + std::tuple> { + private: + bool external_cache_; + + public: + TlsConnectGenericResumption(); + + virtual void EnsureTlsSetup() { + TlsConnectTestBase::EnsureTlsSetup(); + // Enable external resumption token cache. + if (external_cache_) { + client_->SetResumptionTokenCallback(); + } + } +}; + +class TlsConnectTls13ResumptionToken + : public TlsConnectTestBase, + public ::testing::WithParamInterface { + public: + TlsConnectTls13ResumptionToken(); + + virtual void EnsureTlsSetup() { + TlsConnectTestBase::EnsureTlsSetup(); + client_->SetResumptionTokenCallback(); + } +}; + +class TlsConnectGenericResumptionToken + : public TlsConnectTestBase, + public ::testing::WithParamInterface< + std::tuple> { + public: + TlsConnectGenericResumptionToken(); + + virtual void EnsureTlsSetup() { + TlsConnectTestBase::EnsureTlsSetup(); + client_->SetResumptionTokenCallback(); + } +}; + // A Pre TLS 1.2 generic test. class TlsConnectPre12 : public TlsConnectTestBase, public ::testing::WithParamInterface< diff --git a/lib/certdb/stanpcertdb.c b/lib/certdb/stanpcertdb.c index beaa660406..a26ebf881a 100644 --- a/lib/certdb/stanpcertdb.c +++ b/lib/certdb/stanpcertdb.c @@ -343,7 +343,7 @@ CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert, /* First, see if it is already a temp cert */ c = NSSCryptoContext_FindCertificateByEncodedCertificate(gCC, &encoding); - if (!c) { + if (!c && handle) { /* Then, see if it is already a perm cert */ c = NSSTrustDomain_FindCertificateByEncodedCertificate(handle, &encoding); diff --git a/lib/ssl/selfencrypt.c b/lib/ssl/selfencrypt.c index 97217b4a6c..1c70f76353 100644 --- a/lib/ssl/selfencrypt.c +++ b/lib/ssl/selfencrypt.c @@ -192,75 +192,71 @@ ssl_SelfEncryptUnprotectInt( const PRUint8 *in, unsigned int inLen, PRUint8 *out, unsigned int *outLen, unsigned int maxOutLen) { - unsigned char *encodedKeyName; - unsigned char *iv; - SECItem ivItem = { siBuffer, NULL, 0 }; - SECItem inItem = { siBuffer, (unsigned char *)in, inLen }; - unsigned char *cipherText; - PRUint32 cipherTextLen; - unsigned char *encodedMac; - unsigned char computedMac[SHA256_LENGTH]; - unsigned int computedMacLen; - unsigned int bytesToMac; - SECStatus rv; + sslReader reader = SSL_READER(in, inLen); - rv = ssl3_ConsumeFromItem(&inItem, &encodedKeyName, - SELF_ENCRYPT_KEY_NAME_LEN); + sslReadBuffer encodedKeyNameBuffer = { 0 }; + SECStatus rv = sslRead_Read(&reader, SELF_ENCRYPT_KEY_NAME_LEN, + &encodedKeyNameBuffer); if (rv != SECSuccess) { return SECFailure; } - rv = ssl3_ConsumeFromItem(&inItem, &iv, AES_BLOCK_SIZE); + sslReadBuffer ivBuffer = { 0 }; + rv = sslRead_Read(&reader, AES_BLOCK_SIZE, &ivBuffer); if (rv != SECSuccess) { return SECFailure; } - rv = ssl3_ConsumeNumberFromItem(&inItem, &cipherTextLen, 2); + PRUint64 cipherTextLen = 0; + rv = sslRead_ReadNumber(&reader, 2, &cipherTextLen); if (rv != SECSuccess) { return SECFailure; } - rv = ssl3_ConsumeFromItem(&inItem, &cipherText, cipherTextLen); + sslReadBuffer cipherTextBuffer = { 0 }; + rv = sslRead_Read(&reader, (unsigned int)cipherTextLen, &cipherTextBuffer); if (rv != SECSuccess) { return SECFailure; } - bytesToMac = inItem.data - in; + unsigned int bytesToMac = reader.offset; - rv = ssl3_ConsumeFromItem(&inItem, &encodedMac, SHA256_LENGTH); + sslReadBuffer encodedMacBuffer = { 0 }; + rv = sslRead_Read(&reader, SHA256_LENGTH, &encodedMacBuffer); if (rv != SECSuccess) { return SECFailure; } /* Make sure we're at the end of the block. */ - if (inItem.len) { + if (reader.offset != reader.buf.len) { PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } /* Now that everything is decoded, we can make progress. */ /* 1. Check that we have the right key. */ - if (PORT_Memcmp(keyName, encodedKeyName, SELF_ENCRYPT_KEY_NAME_LEN)) { + if (PORT_Memcmp(keyName, encodedKeyNameBuffer.buf, SELF_ENCRYPT_KEY_NAME_LEN)) { PORT_SetError(SEC_ERROR_NOT_A_RECIPIENT); return SECFailure; } /* 2. Check the MAC */ + unsigned char computedMac[SHA256_LENGTH]; + unsigned int computedMacLen = 0; rv = ssl_MacBuffer(macKey, CKM_SHA256_HMAC, in, bytesToMac, computedMac, &computedMacLen, sizeof(computedMac)); if (rv != SECSuccess) { return SECFailure; } PORT_Assert(computedMacLen == SHA256_LENGTH); - if (NSS_SecureMemcmp(computedMac, encodedMac, computedMacLen) != 0) { + if (NSS_SecureMemcmp(computedMac, encodedMacBuffer.buf, computedMacLen) != 0) { PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } /* 3. OK, it verifies, now decrypt. */ - ivItem.data = iv; - ivItem.len = AES_BLOCK_SIZE; + SECItem ivItem = { siBuffer, (unsigned char *)ivBuffer.buf, AES_BLOCK_SIZE }; rv = PK11_Decrypt(encKey, CKM_AES_CBC_PAD, &ivItem, - out, outLen, maxOutLen, cipherText, cipherTextLen); + out, outLen, maxOutLen, cipherTextBuffer.buf, cipherTextLen); if (rv != SECSuccess) { return SECFailure; } diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index 2b96408dec..bffafa3d1e 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -852,7 +852,7 @@ ssl3_config_match_init(sslSocket *ss) * enabled, has a certificate (as needed), has a viable key agreement method, is * usable with the negotiated TLS version, and is otherwise usable. */ static PRBool -config_match(const ssl3CipherSuiteCfg *suite, int policy, +config_match(const ssl3CipherSuiteCfg *suite, PRUint8 policy, const SSLVersionRange *vrange, const sslSocket *ss) { const ssl3CipherSuiteDef *cipher_def; @@ -888,7 +888,7 @@ config_match(const ssl3CipherSuiteCfg *suite, int policy, /* Return the number of cipher suites that are usable. */ /* called from ssl3_SendClientHello */ static unsigned int -count_cipher_suites(sslSocket *ss, int policy) +count_cipher_suites(sslSocket *ss, PRUint8 policy) { unsigned int i, count = 0; @@ -2652,9 +2652,7 @@ ssl3_HandleNoCertificate(sslSocket *ss) (ss->opt.requireCertificate == SSL_REQUIRE_FIRST_HANDSHAKE))) { PRFileDesc *lower; - if (!ss->opt.noCache) { - ss->sec.uncache(ss->sec.ci.sid); - } + ssl_UncacheSessionID(ss); SSL3_SendAlert(ss, alert_fatal, bad_certificate); lower = ss->fd->lower; @@ -2716,8 +2714,8 @@ SSL3_SendAlert(sslSocket *ss, SSL3AlertLevel level, SSL3AlertDescription desc) ssl_GetSSL3HandshakeLock(ss); } if (level == alert_fatal) { - if (!ss->opt.noCache && ss->sec.ci.sid) { - ss->sec.uncache(ss->sec.ci.sid); + if (ss->sec.ci.sid) { + ssl_UncacheSessionID(ss); } } @@ -2981,9 +2979,7 @@ ssl3_HandleAlert(sslSocket *ss, sslBuffer *buf) } } if (level == alert_fatal) { - if (!ss->opt.noCache) { - ss->sec.uncache(ss->sec.ci.sid); - } + ssl_UncacheSessionID(ss); if ((ss->ssl3.hs.ws == wait_server_hello) && (desc == handshake_failure)) { /* XXX This is a hack. We're assuming that any handshake failure @@ -4603,13 +4599,24 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) } } - /* We ignore ss->sec.ci.sid here, and use ssl_Lookup because Lookup - * handles expired entries and other details. - * XXX If we've been called from ssl_BeginClientHandshake, then - * this lookup is duplicative and wasteful. - */ - sid = (ss->opt.noCache) ? NULL - : ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, ss->url); + /* Check if we have a ss->sec.ci.sid. + * Check that it's not expired. + * If we have an sid and it comes from an external cache, we use it. */ + if (ss->sec.ci.sid && ss->sec.ci.sid->cached == in_external_cache) { + PORT_Assert(!ss->sec.isServer); + sid = ss->sec.ci.sid; + SSL_TRC(3, ("%d: SSL3[%d]: using external resumption token in ClientHello", + SSL_GETPID(), ss->fd)); + } else if (!ss->opt.noCache) { + /* We ignore ss->sec.ci.sid here, and use ssl_Lookup because Lookup + * handles expired entries and other details. + * XXX If we've been called from ssl_BeginClientHandshake, then + * this lookup is duplicative and wasteful. + */ + sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, ss->url); + } else { + sid = NULL; + } /* We can't resume based on a different token. If the sid exists, * make sure the token that holds the master secret still exists ... @@ -4700,7 +4707,7 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) if (!sidOK) { SSL_AtomicIncrementLong(&ssl3stats.sch_sid_cache_not_ok); - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); sid = NULL; } @@ -5031,7 +5038,7 @@ ssl3_HandleHelloRequest(sslSocket *ss) } if (sid) { - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); ss->sec.ci.sid = NULL; } @@ -6606,7 +6613,7 @@ ssl3_HandleServerHelloPart2(sslSocket *ss, const SECItem *sidBytes, /* throw the old one away */ sid->u.ssl3.keys.resumable = PR_FALSE; - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); /* get a new sid */ @@ -7516,8 +7523,6 @@ ssl3_NewSessionID(sslSocket *ss, PRBool is_server) sid->u.ssl3.keys.resumable = PR_TRUE; sid->u.ssl3.policy = SSL_ALLOWED; - sid->u.ssl3.clientWriteKey = NULL; - sid->u.ssl3.serverWriteKey = NULL; sid->u.ssl3.keys.extendedMasterSecretUsed = PR_FALSE; if (is_server) { @@ -8246,7 +8251,7 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length) !ss->firstHsDone))) { SSL_AtomicIncrementLong(&ssl3stats.hch_sid_cache_not_ok); - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); sid = NULL; } @@ -8452,7 +8457,7 @@ ssl3_HandleClientHelloPart2(sslSocket *ss, } if (ss->sec.ci.sid) { - ss->sec.uncache(ss->sec.ci.sid); + ssl_UncacheSessionID(ss); PORT_Assert(ss->sec.ci.sid != sid); /* should be impossible, but ... */ if (ss->sec.ci.sid != sid) { ssl_FreeSID(ss->sec.ci.sid); @@ -8548,7 +8553,7 @@ ssl3_HandleClientHelloPart2(sslSocket *ss, if (sid) { /* we had a sid, but it's no longer valid, free it */ ss->statelessResume = PR_FALSE; SSL_AtomicIncrementLong(&ssl3stats.hch_sid_cache_not_ok); - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); sid = NULL; } @@ -8614,7 +8619,7 @@ ssl3_HandleClientHelloPart2(sslSocket *ss, /* FALLTHRU */ loser: if (sid && sid != ss->sec.ci.sid) { - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); } @@ -11154,6 +11159,7 @@ ssl3_FillInCachedSID(sslSocket *ss, sslSessionID *sid, PK11SymKey *secret) if (ss->xtnData.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT && ss->xtnData.nextProto.data) { + SECITEM_FreeItem(&sid->u.ssl3.alpnSelection, PR_FALSE); if (SECITEM_CopyItem( NULL, &sid->u.ssl3.alpnSelection, &ss->xtnData.nextProto) != SECSuccess) { return SECFailure; /* error already set. */ @@ -11183,7 +11189,7 @@ ssl3_FinishHandshake(sslSocket *ss) * the handshake is finished (we have verified the server's Finished * AND the server's certificate) before we update the ticket in the sid. * - * This must be done before we call ss->sec.cache(ss->sec.ci.sid) + * This must be done before we call ssl_CacheSessionID(ss) * because CacheSID requires the session ticket to already be set, and also * because of the lazy lock creation scheme used by CacheSID and * ssl3_SetSIDSessionTicket. @@ -11198,7 +11204,7 @@ ssl3_FinishHandshake(sslSocket *ss) if (ss->ssl3.hs.cacheSID) { PORT_Assert(ss->sec.ci.sid->cached == never_cached); - ss->sec.cache(ss->sec.ci.sid); + ssl_CacheSessionID(ss); ss->ssl3.hs.cacheSID = PR_FALSE; } @@ -12662,8 +12668,8 @@ ssl3_RedoHandshake(sslSocket *ss, PRBool flushCache) } if (sid && flushCache) { - ss->sec.uncache(sid); /* remove it from whichever cache it's in. */ - ssl_FreeSID(sid); /* dec ref count and free if zero. */ + ssl_UncacheSessionID(ss); /* remove it from whichever cache it's in. */ + ssl_FreeSID(sid); /* dec ref count and free if zero. */ ss->sec.ci.sid = NULL; } diff --git a/lib/ssl/ssl3exthandle.c b/lib/ssl/ssl3exthandle.c index 2c8aa0f331..acf5aae6bf 100644 --- a/lib/ssl/ssl3exthandle.c +++ b/lib/ssl/ssl3exthandle.c @@ -182,7 +182,7 @@ ssl3_ClientSendSessionTicketXtn(const sslSocket *ss, TLSExtensionData *xtnData, /* Never send an extension with a ticket for TLS 1.3, but * OK to send the empty one in case the server does 1.2. */ - if (sid->cached == in_client_cache && + if ((sid->cached == in_client_cache || sid->cached == in_external_cache) && sid->version >= SSL_LIBRARY_VERSION_TLS_1_3) { return SECSuccess; } @@ -1219,6 +1219,7 @@ ssl_CreateSIDFromTicket(sslSocket *ss, const SECItem *rawTicket, } } if (parsedTicket->alpnSelection.data != NULL) { + SECITEM_FreeItem(&sid->u.ssl3.alpnSelection, PR_FALSE); rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.alpnSelection, &parsedTicket->alpnSelection); if (rv != SECSuccess) { @@ -1245,7 +1246,7 @@ ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket, SECStatus rv; if (ss->sec.ci.sid != NULL) { - ss->sec.uncache(ss->sec.ci.sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(ss->sec.ci.sid); ss->sec.ci.sid = NULL; } diff --git a/lib/ssl/sslcon.c b/lib/ssl/sslcon.c index 4481706402..bc63e15371 100644 --- a/lib/ssl/sslcon.c +++ b/lib/ssl/sslcon.c @@ -119,13 +119,12 @@ ssl_CheckConfigSanity(sslSocket *ss) SECStatus ssl_BeginClientHandshake(sslSocket *ss) { - sslSessionID *sid; + sslSessionID *sid = NULL; SECStatus rv; PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss)); ss->sec.isServer = PR_FALSE; - ssl_ChooseSessionIDProcs(&ss->sec); rv = ssl_CheckConfigSanity(ss); if (rv != SECSuccess) @@ -156,19 +155,22 @@ ssl_BeginClientHandshake(sslSocket *ss) SSL_TRC(3, ("%d: SSL[%d]: sending client-hello", SSL_GETPID(), ss->fd)); - /* Try to find server in our session-id cache */ - if (ss->opt.noCache) { - sid = NULL; - } else { + /* If there's an sid set from an external cache, use it. */ + if (ss->sec.ci.sid && ss->sec.ci.sid->cached == in_external_cache) { + sid = ss->sec.ci.sid; + SSL_TRC(3, ("%d: SSL[%d]: using external token", SSL_GETPID(), ss->fd)); + } else if (!ss->opt.noCache) { + /* Try to find server in our session-id cache */ sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, ss->url); } + if (sid) { if (sid->version >= ss->vrange.min && sid->version <= ss->vrange.max) { PORT_Assert(!ss->sec.localCert); ss->sec.localCert = CERT_DupCertificate(sid->localCert); } else { - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); sid = NULL; } @@ -218,7 +220,6 @@ ssl_BeginServerHandshake(sslSocket *ss) ss->sec.isServer = PR_TRUE; ss->ssl3.hs.ws = wait_client_hello; - ssl_ChooseSessionIDProcs(&ss->sec); rv = ssl_CheckConfigSanity(ss); if (rv != SECSuccess) diff --git a/lib/ssl/sslencode.c b/lib/ssl/sslencode.c index f92b742554..e508804515 100644 --- a/lib/ssl/sslencode.c +++ b/lib/ssl/sslencode.c @@ -29,6 +29,7 @@ ssl_EncodeUintX(PRUint8 *to, PRUint64 value, unsigned int bytes) SECStatus sslBuffer_Grow(sslBuffer *b, unsigned int newLen) { + PORT_Assert(b); if (b->fixed) { PORT_Assert(newLen <= b->space); if (newLen > b->space) { @@ -84,6 +85,7 @@ sslBuffer_AppendVariable(sslBuffer *b, const PRUint8 *data, unsigned int len, unsigned int size) { PORT_Assert(size <= 4 && size > 0); + PORT_Assert(b); if (len >= (1ULL << (8 * size))) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -96,6 +98,7 @@ sslBuffer_AppendVariable(sslBuffer *b, const PRUint8 *data, unsigned int len, ssl_EncodeUintX(SSL_BUFFER_NEXT(b), len, size); b->len += size; if (len != 0) { + PORT_Assert(data); /* We sometimes pass NULL, 0 and memcpy() doesn't want NULL. */ PORT_Memcpy(SSL_BUFFER_NEXT(b), data, len); } @@ -172,37 +175,63 @@ sslBuffer_Clear(sslBuffer *b) } SECStatus -ssl3_ConsumeFromItem(SECItem *item, unsigned char **buf, unsigned int size) +sslRead_Read(sslReader *reader, unsigned int count, sslReadBuffer *out) { - if (size > item->len) { + if (!reader || !out) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (reader->buf.len < reader->offset || + count > SSL_READER_REMAINING(reader)) { PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } - *buf = item->data; - item->data += size; - item->len -= size; + out->buf = SSL_READER_CURRENT(reader); + out->len = count; + reader->offset += count; + return SECSuccess; } SECStatus -ssl3_ConsumeNumberFromItem(SECItem *item, PRUint32 *num, unsigned int size) +sslRead_ReadVariable(sslReader *reader, unsigned int sizeLen, sslReadBuffer *out) { - int i; - - if (size > item->len || size > sizeof(*num)) { + PRUint64 variableLen = 0; + SECStatus rv = sslRead_ReadNumber(reader, sizeLen, &variableLen); + if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } - - *num = 0; - for (i = 0; i < size; i++) { - *num = (*num << 8) + item->data[i]; + if (!variableLen) { + // It is ok to have an empty variable. + out->len = variableLen; + return SECSuccess; } + return sslRead_Read(reader, variableLen, out); +} - item->data += size; - item->len -= size; +SECStatus +sslRead_ReadNumber(sslReader *reader, unsigned int bytes, PRUint64 *num) +{ + if (!reader || !num) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (reader->buf.len < reader->offset || + bytes > SSL_READER_REMAINING(reader) || + bytes > 8) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + unsigned int i; + PRUint64 number = 0; + for (i = 0; i < bytes; i++) { + number = (number << 8) + reader->buf.buf[i + reader->offset]; + } + reader->offset = reader->offset + bytes; + *num = number; return SECSuccess; } diff --git a/lib/ssl/sslencode.h b/lib/ssl/sslencode.h index a1b04d88f6..f43e1c54bd 100644 --- a/lib/ssl/sslencode.h +++ b/lib/ssl/sslencode.h @@ -47,13 +47,6 @@ SECStatus sslBuffer_InsertLength(sslBuffer *b, unsigned int at, unsigned int size); void sslBuffer_Clear(sslBuffer *b); -/* All of these functions modify the underlying SECItem, and so should - * be performed on a shallow copy.*/ -SECStatus ssl3_ConsumeFromItem(SECItem *item, - PRUint8 **buf, unsigned int size); -SECStatus ssl3_ConsumeNumberFromItem(SECItem *item, - PRUint32 *num, unsigned int size); - SECStatus ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes); SECStatus ssl3_AppendHandshakeHeader(sslSocket *ss, @@ -66,4 +59,27 @@ SECStatus ssl3_AppendBufferToHandshake(sslSocket *ss, sslBuffer *buf); SECStatus ssl3_AppendBufferToHandshakeVariable(sslSocket *ss, sslBuffer *buf, unsigned int lenSize); +typedef struct { + const PRUint8 *buf; + unsigned int len; +} sslReadBuffer; +typedef struct { + sslReadBuffer buf; + unsigned int offset; +} sslReader; +#define SSL_READER(b, l) \ + { \ + { b, l }, 0 \ + } +#define SSL_READER_CURRENT(r) \ + ((r)->buf.buf + (r)->offset) +#define SSL_READER_REMAINING(r) \ + ((r)->buf.len - (r)->offset) +SECStatus sslRead_Read(sslReader *reader, unsigned int count, + sslReadBuffer *out); +SECStatus sslRead_ReadVariable(sslReader *reader, unsigned int sizeLen, + sslReadBuffer *out); +SECStatus sslRead_ReadNumber(sslReader *reader, unsigned int bytes, + PRUint64 *val); + #endif /* __sslencode_h_ */ diff --git a/lib/ssl/sslerr.h b/lib/ssl/sslerr.h index fcce1432ff..b94d0cc62e 100644 --- a/lib/ssl/sslerr.h +++ b/lib/ssl/sslerr.h @@ -261,6 +261,7 @@ typedef enum { SSL_ERROR_RX_MALFORMED_KEY_UPDATE = (SSL_ERROR_BASE + 170), SSL_ERROR_TOO_MANY_KEY_UPDATES = (SSL_ERROR_BASE + 171), SSL_ERROR_HANDSHAKE_FAILED = (SSL_ERROR_BASE + 172), + SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR = (SSL_ERROR_BASE + 173), SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ } SSLErrorCodes; #endif /* NO_SECURITY_ERROR_ENUM */ diff --git a/lib/ssl/sslexp.h b/lib/ssl/sslexp.h index 569add8610..7ecf2703ee 100644 --- a/lib/ssl/sslexp.h +++ b/lib/ssl/sslexp.h @@ -350,8 +350,103 @@ typedef SSLHelloRetryRequestAction(PR_CALLBACK *SSLHelloRetryRequestCallback)( (PRFileDesc * _fd, PRBool _requestUpdate), \ (fd, requestUpdate)) -#define SSL_UseAltServerHelloType(fd, enable) \ - SSL_DEPRECATED_EXPERIMENTAL_API +/* + * Session cache API. + */ + +/* + * Information that can be retrieved about a resumption token. + * See SSL_GetResumptionTokenInfo for details about how to use this API. + * Note that peerCert points to a certificate in the NSS database and must be + * copied by the application if it should be used after NSS shutdown or after + * calling SSL_DestroyResumptionTokenInfo. + */ +typedef struct SSLResumptionTokenInfoStr { + PRUint16 length; + CERTCertificate *peerCert; + PRUint8 *alpnSelection; + PRUint32 alpnSelectionLen; + PRUint32 maxEarlyDataSize; +} SSLResumptionTokenInfo; + +/* + * Allows applications to retrieve information about a resumption token. + * This does not require a TLS session. + * + * - The |tokenData| argument is a pointer to the resumption token as byte array + * of length |tokenLen|. + * - The |token| argument is a pointer to a SSLResumptionTokenInfo struct of + * of |len|. The struct gets filled by this function. + * See SSL_DestroyResumptionTokenInfo for information about how to manage the + * |token| memory. + */ +#define SSL_GetResumptionTokenInfo(tokenData, tokenLen, token, len) \ + SSL_EXPERIMENTAL_API("SSL_GetResumptionTokenInfo", \ + (const PRUint8 *_tokenData, unsigned int _tokenLen, \ + SSLResumptionTokenInfo *_token, PRUintn _len), \ + (tokenData, tokenLen, token, len)) + +/* + * SSL_GetResumptionTokenInfo allocates memory in order to populate |tokenInfo|. + * Any SSLResumptionTokenInfo struct filled with SSL_GetResumptionTokenInfo + * has to be freed with SSL_DestroyResumptionTokenInfo. + */ +#define SSL_DestroyResumptionTokenInfo(tokenInfo) \ + SSL_EXPERIMENTAL_API( \ + "SSL_DestroyResumptionTokenInfo", \ + (SSLResumptionTokenInfo * _tokenInfo), \ + (tokenInfo)) + +/* + * This is the function signature for function pointers used as resumption + * token callback. The caller has to copy the memory at |resumptionToken| with + * length |len| before returning. + * + * - The |fd| argument is the socket file descriptor. + * - The |resumptionToken| is a pointer to the resumption token as byte array + * of length |len|. + * - The |ctx| is a void pointer to the context set by the application in + * SSL_SetResumptionTokenCallback. + */ +typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)( + PRFileDesc *fd, const PRUint8 *resumptionToken, unsigned int len, + void *ctx); + +/* + * This allows setting a callback for external session caches to store + * resumption tokens. + * + * - The |fd| argument is the socket file descriptor. + * - The |cb| is a function pointer to an implementation of + * SSLResumptionTokenCallback. + * - The |ctx| is a pointer to some application specific context, which is + * returned when |cb| is called. + */ +#define SSL_SetResumptionTokenCallback(fd, cb, ctx) \ + SSL_EXPERIMENTAL_API( \ + "SSL_SetResumptionTokenCallback", \ + (PRFileDesc * _fd, SSLResumptionTokenCallback _cb, void *_ctx), \ + (fd, cb, ctx)) + +/* + * This allows setting a resumption token for a session. + * The function returns SECSuccess iff the resumption token can be used, + * SECFailure in any other case. The caller should remove the |token| from its + * cache when the function returns SECFailure. + * + * - The |fd| argument is the socket file descriptor. + * - The |token| is a pointer to the resumption token as byte array + * of length |len|. + */ +#define SSL_SetResumptionToken(fd, token, len) \ + SSL_EXPERIMENTAL_API( \ + "SSL_SetResumptionToken", \ + (PRFileDesc * _fd, const PRUint8 *_token, const unsigned int _len), \ + (fd, token, len)) + +/* Deprecated experimental APIs */ + +#define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API SEC_END_PROTOS diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index dee9aa20fe..231873a54e 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -168,8 +168,11 @@ struct ssl3CertNodeStr { typedef SECStatus (*sslHandshakeFunc)(sslSocket *ss); -typedef void (*sslSessionIDCacheFunc)(sslSessionID *sid); -typedef void (*sslSessionIDUncacheFunc)(sslSessionID *sid); +void ssl_CacheSessionID(sslSocket *ss); +void ssl_UncacheSessionID(sslSocket *ss); +void ssl_ServerCacheSessionID(sslSessionID *sid); +void ssl_ServerUncacheSessionID(sslSessionID *sid); + typedef sslSessionID *(*sslSessionIDLookupFunc)(const PRIPv6Addr *addr, unsigned char *sid, unsigned int sidLen, @@ -341,9 +344,11 @@ struct sslGatherStr { #define GS_HEADER 1 #define GS_DATA 2 +#define WRAPPED_MASTER_SECRET_SIZE 48 + typedef struct { - PRUint8 wrapped_master_secret[48]; - PRUint16 wrapped_master_secret_len; + PRUint8 wrapped_master_secret[WRAPPED_MASTER_SECRET_SIZE]; + PRUint8 wrapped_master_secret_len; PRUint8 resumable; PRUint8 extendedMasterSecretUsed; } ssl3SidKeys; /* 52 bytes */ @@ -351,7 +356,8 @@ typedef struct { typedef enum { never_cached, in_client_cache, in_server_cache, - invalid_cache /* no longer in any cache. */ + invalid_cache, /* no longer in any cache. */ + in_external_cache } Cached; #include "sslcert.h" @@ -398,17 +404,11 @@ struct sslSessionIDStr { PRUint8 sessionID[SSL3_SESSIONID_BYTES]; ssl3CipherSuite cipherSuite; - int policy; + PRUint8 policy; ssl3SidKeys keys; /* mechanism used to wrap master secret */ CK_MECHANISM_TYPE masterWrapMech; - /* The following values are NOT restored from the server's on-disk - * session cache, but are restored from the client's cache. - */ - PK11SymKey *clientWriteKey; - PK11SymKey *serverWriteKey; - /* The following values pertain to the slot that wrapped the ** master secret. (used only in client) */ @@ -740,7 +740,7 @@ struct ssl3StateStr { CERTCertificateList *clientCertChain; /* used by client */ PRBool sendEmptyCert; /* used by client */ - int policy; + PRUint8 policy; /* This says what cipher suites we can do, and should * be either SSL_ALLOWED or SSL_RESTRICTED */ @@ -897,14 +897,6 @@ struct sslSecurityInfoStr { /* The selected certificate (for servers only). */ const sslServerCert *serverCert; - /* - ** Procs used for SID cache (nonce) management. - ** Different implementations exist for clients/servers - ** The lookup proc is only used for servers. Baloney! - */ - sslSessionIDCacheFunc cache; - sslSessionIDUncacheFunc uncache; - /* These are used during a connection handshake */ sslConnectInfo ci; }; @@ -982,6 +974,8 @@ struct sslSocketStr { SSLHelloRetryRequestCallback hrrCallback; void *hrrCallbackArg; PRCList extensionHooks; + SSLResumptionTokenCallback resumptionTokenCallback; + void *resumptionTokenContext; PRIntervalTime rTimeout; /* timeout for NSPR I/O */ PRIntervalTime wTimeout; /* timeout for NSPR I/O */ @@ -1080,8 +1074,6 @@ extern PRUint32 ssl_max_early_data_size; extern const char *const ssl3_cipherName[]; extern sslSessionIDLookupFunc ssl_sid_lookup; -extern sslSessionIDCacheFunc ssl_sid_cache; -extern sslSessionIDUncacheFunc ssl_sid_uncache; extern const sslNamedGroupDef ssl_named_groups[]; @@ -1163,14 +1155,13 @@ extern SECStatus ssl_BeginClientHandshake(sslSocket *ss); extern SECStatus ssl_BeginServerHandshake(sslSocket *ss); extern int ssl_Do1stHandshake(sslSocket *ss); -extern void ssl_ChooseSessionIDProcs(sslSecurityInfo *sec); - extern SECStatus ssl3_InitPendingCipherSpecs(sslSocket *ss, PK11SymKey *secret, PRBool derive); extern sslSessionID *ssl3_NewSessionID(sslSocket *ss, PRBool is_server); extern sslSessionID *ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, const char *urlSvrName); extern void ssl_FreeSID(sslSessionID *sid); +extern void ssl_DestroySID(sslSessionID *sid, PRBool freeIt); extern int ssl3_SendApplicationData(sslSocket *ss, const PRUint8 *in, int len, int flags); @@ -1692,6 +1683,26 @@ PRBool ssl_AlpnTagAllowed(const sslSocket *ss, const SECItem *tag); void ssl_Trace(const char *format, ...); +void ssl_CacheExternalToken(sslSocket *ss); +SECStatus ssl_DecodeResumptionToken(sslSessionID *sid, const PRUint8 *encodedTicket, + PRUint32 encodedTicketLen); +PRBool ssl_IsResumptionTokenValid(sslSocket *ss); + +/* Remove when stable. */ + +SECStatus SSLExp_SetResumptionTokenCallback(PRFileDesc *fd, + SSLResumptionTokenCallback cb, + void *ctx); +SECStatus SSLExp_SetResumptionToken(PRFileDesc *fd, const PRUint8 *token, + unsigned int len); + +SECStatus SSLExp_GetResumptionTokenInfo(const PRUint8 *tokenData, unsigned int tokenLen, + SSLResumptionTokenInfo *token, unsigned int version); + +SECStatus SSLExp_DestroyResumptionTokenInfo(SSLResumptionTokenInfo *token); + +#define SSLResumptionTokenVersion 1 + SEC_END_PROTOS #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS) diff --git a/lib/ssl/sslinit.c b/lib/ssl/sslinit.c index 0f38c0b57b..07d57ce6ed 100644 --- a/lib/ssl/sslinit.c +++ b/lib/ssl/sslinit.c @@ -15,6 +15,7 @@ static int ssl_isInited = 0; static PRCallOnceType ssl_init = { 0 }; +PR_STATIC_ASSERT(sizeof(unsigned long) <= sizeof(PRUint64)); PRStatus ssl_InitCallOnce(void *arg) diff --git a/lib/ssl/sslnonce.c b/lib/ssl/sslnonce.c index 228834e3db..a2ed94b93c 100644 --- a/lib/ssl/sslnonce.c +++ b/lib/ssl/sslnonce.c @@ -15,6 +15,7 @@ #include "sslimpl.h" #include "sslproto.h" #include "nssilock.h" +#include "sslencode.h" #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS) #include #endif @@ -24,12 +25,13 @@ PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */ static sslSessionID *cache = NULL; static PZLock *cacheLock = NULL; -/* sids can be in one of 4 states: +/* sids can be in one of 5 states: * * never_cached, created, but not yet put into cache. * in_client_cache, in the client cache's linked list. * in_server_cache, entry came from the server's cache file. * invalid_cache has been removed from the cache. + * in_external_cache sid comes from an external cache. */ #define LOCK_CACHE lock_cache() @@ -164,8 +166,8 @@ lock_cache(void) /* BEWARE: This function gets called for both client and server SIDs !! * If the unreferenced sid is not in the cache, Free sid and its contents. */ -static void -ssl_DestroySID(sslSessionID *sid) +void +ssl_DestroySID(sslSessionID *sid, PRBool freeIt) { SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached)); PORT_Assert(sid->references == 0); @@ -186,11 +188,8 @@ ssl_DestroySID(sslSessionID *sid) PR_DestroyRWLock(sid->u.ssl3.lock); } - if (sid->peerID != NULL) - PORT_Free((void *)sid->peerID); /* CONST */ - - if (sid->urlSvrName != NULL) - PORT_Free((void *)sid->urlSvrName); /* CONST */ + PORT_Free((void *)sid->peerID); + PORT_Free((void *)sid->urlSvrName); if (sid->peerCert) { CERT_DestroyCertificate(sid->peerCert); @@ -205,7 +204,9 @@ ssl_DestroySID(sslSessionID *sid) SECITEM_FreeItem(&sid->u.ssl3.alpnSelection, PR_FALSE); - PORT_ZFree(sid, sizeof(sslSessionID)); + if (freeIt) { + PORT_ZFree(sid, sizeof(sslSessionID)); + } } /* BEWARE: This function gets called for both client and server SIDs !! @@ -220,7 +221,7 @@ ssl_FreeLockedSID(sslSessionID *sid) { PORT_Assert(sid->references >= 1); if (--sid->references == 0) { - ssl_DestroySID(sid); + ssl_DestroySID(sid, PR_TRUE); } } @@ -306,6 +307,7 @@ ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, static void CacheSID(sslSessionID *sid) { + PORT_Assert(sid); PORT_Assert(sid->cached == never_cached); SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " @@ -400,7 +402,7 @@ UncacheSID(sslSessionID *zap) /* If sid "zap" is in the cache, * removes sid from cache, and decrements reference count. * Although this function is static, it is called externally via - * ss->sec.uncache(). + * ssl_UncacheSessionID. */ static void LockAndUncacheSID(sslSessionID *zap) @@ -410,16 +412,758 @@ LockAndUncacheSID(sslSessionID *zap) UNLOCK_CACHE; } -/* choose client or server cache functions for this sslsocket. */ +SECStatus +ReadVariableFromBuffer(sslReader *reader, sslReadBuffer *readerBuffer, + uint8_t lenBytes, SECItem *dest) +{ + if (sslRead_ReadVariable(reader, lenBytes, readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer->len) { + SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer->buf, + readerBuffer->len }; + SECITEM_CopyItem(NULL, dest, &tempItem); + } + return SECSuccess; +} + +/* Fill sid with the values from the encoded resumption token. + * sid has to be allocated. + * We don't care about locks here as this cache entry is externally stored. + */ +SECStatus +ssl_DecodeResumptionToken(sslSessionID *sid, const PRUint8 *encodedToken, + PRUint32 encodedTokenLen) +{ + PORT_Assert(encodedTokenLen); + PORT_Assert(encodedToken); + PORT_Assert(sid); + if (!sid || !encodedToken || !encodedTokenLen) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (encodedToken[0] != SSLResumptionTokenVersion) { + /* Unknown token format version. */ + PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR); + return SECFailure; + } + + /* These variables are used across macros. Don't use them outside. */ + sslReader reader = SSL_READER(encodedToken, encodedTokenLen); + reader.offset += 1; // We read the version already. Skip the first byte. + sslReadBuffer readerBuffer = { 0 }; + PRUint64 tmpInt = 0; + + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->lastAccessTime = (PRTime)tmpInt; + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->expirationTime = (PRTime)tmpInt; + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.locked.sessionTicket.received_timestamp = (PRTime)tmpInt; + + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint = (PRUint32)tmpInt; + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.locked.sessionTicket.flags = (PRUint32)tmpInt; + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.locked.sessionTicket.ticket_age_add = (PRUint32)tmpInt; + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.locked.sessionTicket.max_early_data_size = (PRUint32)tmpInt; + + if (sslRead_ReadVariable(&reader, 3, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer.len) { + PORT_Assert(!sid->peerCert); + SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer.buf, + readerBuffer.len }; + sid->peerCert = CERT_NewTempCertificate(NULL, /* dbHandle */ + &tempItem, + NULL, PR_FALSE, PR_TRUE); + if (!sid->peerCert) { + return SECFailure; + } + } + + if (sslRead_ReadVariable(&reader, 2, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer.len) { + SECITEM_AllocArray(NULL, &sid->peerCertStatus, 1); + if (!sid->peerCertStatus.items) { + return SECFailure; + } + SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer.buf, + readerBuffer.len }; + SECITEM_CopyItem(NULL, &sid->peerCertStatus.items[0], &tempItem); + } + + if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer.len) { + sid->peerID = PORT_Strdup((const char *)readerBuffer.buf); + } + + if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer.len) { + if (sid->urlSvrName) { + PORT_Free((void *)sid->urlSvrName); + } + sid->urlSvrName = PORT_Strdup((const char *)readerBuffer.buf); + } + + if (sslRead_ReadVariable(&reader, 3, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer.len) { + PORT_Assert(!sid->localCert); + SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer.buf, + readerBuffer.len }; + sid->localCert = CERT_NewTempCertificate(NULL, /* dbHandle */ + &tempItem, + NULL, PR_FALSE, PR_TRUE); + } + + if (sslRead_ReadNumber(&reader, 8, &sid->addr.pr_s6_addr64[0]) != SECSuccess) { + return SECFailure; + } + if (sslRead_ReadNumber(&reader, 8, &sid->addr.pr_s6_addr64[1]) != SECSuccess) { + return SECFailure; + } + + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->port = (PRUint16)tmpInt; + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->version = (PRUint16)tmpInt; + + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->creationTime = (PRTime)tmpInt; + + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->authType = (SSLAuthType)tmpInt; + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->authKeyBits = (PRUint32)tmpInt; + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->keaType = (SSLKEAType)tmpInt; + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->keaKeyBits = (PRUint32)tmpInt; + if (sslRead_ReadNumber(&reader, 3, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->keaGroup = (SSLNamedGroup)tmpInt; + + if (sslRead_ReadNumber(&reader, 3, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->sigScheme = (SSLSignatureScheme)tmpInt; + + if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.sessionIDLength = (PRUint8)tmpInt; + + if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (readerBuffer.len) { + PORT_Memcpy(sid->u.ssl3.sessionID, readerBuffer.buf, readerBuffer.len); + } + + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.cipherSuite = (PRUint16)tmpInt; + if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.policy = (PRUint8)tmpInt; + + if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + PORT_Assert(readerBuffer.len == WRAPPED_MASTER_SECRET_SIZE); + if (readerBuffer.len != WRAPPED_MASTER_SECRET_SIZE) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + PORT_Memcpy(sid->u.ssl3.keys.wrapped_master_secret, readerBuffer.buf, + readerBuffer.len); + + if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.keys.wrapped_master_secret_len = (PRUint8)tmpInt; + if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.keys.extendedMasterSecretUsed = (PRUint8)tmpInt; + + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.masterWrapMech = (unsigned long)tmpInt; + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.masterModuleID = (unsigned long)tmpInt; + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.masterSlotID = (unsigned long)tmpInt; + + if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.masterWrapIndex = (PRUint32)tmpInt; + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.masterWrapSeries = (PRUint16)tmpInt; + + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.clAuthModuleID = (unsigned long)tmpInt; + if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.clAuthSlotID = (unsigned long)tmpInt; + if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.clAuthSeries = (PRUint16)tmpInt; + + if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.masterValid = (char)tmpInt; + if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { + return SECFailure; + } + sid->u.ssl3.clAuthValid = (char)tmpInt; + + if (ReadVariableFromBuffer(&reader, &readerBuffer, 1, + &sid->u.ssl3.srvName) != SECSuccess) { + return SECFailure; + } + if (ReadVariableFromBuffer(&reader, &readerBuffer, 2, + &sid->u.ssl3.signedCertTimestamps) != SECSuccess) { + return SECFailure; + } + if (ReadVariableFromBuffer(&reader, &readerBuffer, 1, + &sid->u.ssl3.alpnSelection) != SECSuccess) { + return SECFailure; + } + if (ReadVariableFromBuffer(&reader, &readerBuffer, 2, + &sid->u.ssl3.locked.sessionTicket.ticket) != SECSuccess) { + return SECFailure; + } + if (!sid->u.ssl3.locked.sessionTicket.ticket.len) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* At this point we must have read everything. */ + PORT_Assert(reader.offset == reader.buf.len); + if (reader.offset != reader.buf.len) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + return SECSuccess; +} + +PRBool +ssl_IsResumptionTokenValid(sslSocket *ss) +{ + PORT_Assert(ss); + sslSessionID *sid = ss->sec.ci.sid; + PORT_Assert(sid); + + // Check that the ticket didn't expire. + PRTime endTime = 0; + NewSessionTicket *ticket = &sid->u.ssl3.locked.sessionTicket; + if (ticket->ticket_lifetime_hint != 0) { + endTime = ticket->received_timestamp + + (PRTime)(ticket->ticket_lifetime_hint * PR_USEC_PER_SEC); + if (endTime < ssl_TimeUsec()) { + return PR_FALSE; + } + } + + // Check that the session entry didn't expire. + if (sid->expirationTime < ssl_TimeUsec()) { + return PR_FALSE; + } + + // Check that the server name (SNI) matches the one set for this session. + // Don't use the token if there's no server name. + if (sid->urlSvrName == NULL || PORT_Strcmp(ss->url, sid->urlSvrName) != 0) { + return PR_FALSE; + } + + // This shouldn't be false, but let's check it anyway. + if (!sid->u.ssl3.keys.resumable) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* Encode a session ticket into a byte array that can be handed out to a cache. + * Needed memory in encodedToken has to be allocated according to + * *encodedTokenLen. */ +static SECStatus +ssl_EncodeResumptionToken(sslSessionID *sid, sslBuffer *encodedTokenBuf) +{ + PORT_Assert(encodedTokenBuf); + PORT_Assert(sid); + if (!sid->u.ssl3.locked.sessionTicket.ticket.len || !encodedTokenBuf || + !sid || !sid->u.ssl3.keys.resumable || !sid->urlSvrName) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* Encoding format: + * 0-byte: version + * Integers are encoded according to their length. + * SECItems are prepended with a 64-bit length field followed by the bytes. + * Optional bytes are encoded as a 0-length item if not present. + */ + SECStatus rv = sslBuffer_AppendNumber(encodedTokenBuf, + SSLResumptionTokenVersion, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->lastAccessTime, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->expirationTime, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + // session ticket + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.locked.sessionTicket.received_timestamp, + 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint, + 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.locked.sessionTicket.flags, + 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.locked.sessionTicket.ticket_age_add, + 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.locked.sessionTicket.max_early_data_size, + 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->peerCert->derCert.data, + sid->peerCert->derCert.len, 3); + if (rv != SECSuccess) { + return SECFailure; + } + + if (sid->peerCertStatus.len > 1) { + /* This is not implemented so it shouldn't happen. + * If it gets implemented, this has to change. + */ + PORT_Assert(0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + if (sid->peerCertStatus.len == 1 && sid->peerCertStatus.items[0].len) { + rv = sslBuffer_AppendVariable(encodedTokenBuf, + sid->peerCertStatus.items[0].data, + sid->peerCertStatus.items[0].len, 2); + if (rv != SECSuccess) { + return SECFailure; + } + } else { + rv = sslBuffer_AppendVariable(encodedTokenBuf, NULL, 0, 2); + if (rv != SECSuccess) { + return SECFailure; + } + } + + PRUint64 len = sid->peerID ? strlen(sid->peerID) : 0; + if (len > PR_UINT8_MAX) { + // This string really shouldn't be that long. + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + rv = sslBuffer_AppendVariable(encodedTokenBuf, + (const unsigned char *)sid->peerID, len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + len = sid->urlSvrName ? strlen(sid->urlSvrName) : 0; + if (!len) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (len > PR_UINT8_MAX) { + // This string really shouldn't be that long. + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + rv = sslBuffer_AppendVariable(encodedTokenBuf, + (const unsigned char *)sid->urlSvrName, + len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + if (sid->localCert) { + rv = sslBuffer_AppendVariable(encodedTokenBuf, + sid->localCert->derCert.data, + sid->localCert->derCert.len, 3); + if (rv != SECSuccess) { + return SECFailure; + } + } else { + rv = sslBuffer_AppendVariable(encodedTokenBuf, NULL, 0, 3); + if (rv != SECSuccess) { + return SECFailure; + } + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->addr.pr_s6_addr64[0], 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->addr.pr_s6_addr64[1], 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->port, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->version, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->creationTime, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->authType, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->authKeyBits, 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->keaType, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->keaKeyBits, 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->keaGroup, 3); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->sigScheme, 3); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.sessionIDLength, 1); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.sessionID, + SSL3_SESSIONID_BYTES, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.cipherSuite, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.policy, 1); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendVariable(encodedTokenBuf, + sid->u.ssl3.keys.wrapped_master_secret, + WRAPPED_MASTER_SECRET_SIZE, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.keys.wrapped_master_secret_len, + 1); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, + sid->u.ssl3.keys.extendedMasterSecretUsed, + 1); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterWrapMech, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterModuleID, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterSlotID, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterWrapIndex, 4); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterWrapSeries, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.clAuthModuleID, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.clAuthSlotID, 8); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.clAuthSeries, 2); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterValid, 1); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.clAuthValid, 1); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.srvName.data, + sid->u.ssl3.srvName.len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendVariable(encodedTokenBuf, + sid->u.ssl3.signedCertTimestamps.data, + sid->u.ssl3.signedCertTimestamps.len, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendVariable(encodedTokenBuf, + sid->u.ssl3.alpnSelection.data, + sid->u.ssl3.alpnSelection.len, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + PORT_Assert(sid->u.ssl3.locked.sessionTicket.ticket.len > 1); + rv = sslBuffer_AppendVariable(encodedTokenBuf, + sid->u.ssl3.locked.sessionTicket.ticket.data, + sid->u.ssl3.locked.sessionTicket.ticket.len, + 2); + if (rv != SECSuccess) { + return SECFailure; + } + + return SECSuccess; +} + void -ssl_ChooseSessionIDProcs(sslSecurityInfo *sec) +ssl_CacheExternalToken(sslSocket *ss) { + PORT_Assert(ss); + sslSessionID *sid = ss->sec.ci.sid; + PORT_Assert(sid); + PORT_Assert(sid->cached == never_cached); + PORT_Assert(ss->resumptionTokenCallback); + + SSL_TRC(8, ("SSL [%d]: Cache External: sid=0x%x cached=%d " + "addr=0x%08x%08x%08x%08x port=0x%04x time=%x cached=%d", + ss->fd, + sid, sid->cached, sid->addr.pr_s6_addr32[0], + sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], + sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, + sid->cached)); + + /* This is only available for stateless resumption. */ + if (sid->u.ssl3.locked.sessionTicket.ticket.data == NULL) { + return; + } + + if (!sid->creationTime) { + sid->lastAccessTime = sid->creationTime = ssl_TimeUsec(); + } + if (!sid->expirationTime) { + sid->expirationTime = sid->creationTime + ssl3_sid_timeout; + } + + sslBuffer encodedToken = SSL_BUFFER_EMPTY; + + if (ssl_EncodeResumptionToken(sid, &encodedToken) != SECSuccess) { + SSL_TRC(3, ("SSL [%d]: encoding resumption token failed", ss->fd)); + return; + } + PORT_Assert(SSL_BUFFER_LEN(&encodedToken) > 0); + PRINT_BUF(40, (ss, "SSL: encoded resumption token", + SSL_BUFFER_BASE(&encodedToken), + SSL_BUFFER_LEN(&encodedToken))); + ss->resumptionTokenCallback(ss->fd, SSL_BUFFER_BASE(&encodedToken), + SSL_BUFFER_LEN(&encodedToken), + ss->resumptionTokenContext); + + sslBuffer_Clear(&encodedToken); +} + +void +ssl_CacheSessionID(sslSocket *ss) +{ + sslSecurityInfo *sec = &ss->sec; + PORT_Assert(sec); + + if (sec->ci.sid && !sec->ci.sid->u.ssl3.keys.resumable) { + return; + } + + if (!ss->sec.isServer && ss->resumptionTokenCallback) { + ssl_CacheExternalToken(ss); + return; + } + + PORT_Assert(!ss->resumptionTokenCallback); if (sec->isServer) { - sec->cache = ssl_sid_cache; - sec->uncache = ssl_sid_uncache; - } else { - sec->cache = CacheSID; - sec->uncache = LockAndUncacheSID; + ssl_ServerCacheSessionID(sec->ci.sid); + return; + } + + CacheSID(sec->ci.sid); +} + +void +ssl_UncacheSessionID(sslSocket *ss) +{ + if (ss->opt.noCache) { + return; + } + + sslSecurityInfo *sec = &ss->sec; + PORT_Assert(sec); + + if (sec->isServer) { + ssl_ServerUncacheSessionID(sec->ci.sid); + } else if (!ss->resumptionTokenCallback) { + LockAndUncacheSID(sec->ci.sid); } } diff --git a/lib/ssl/sslsecur.c b/lib/ssl/sslsecur.c index 3f7060f224..f09ec067ce 100644 --- a/lib/ssl/sslsecur.c +++ b/lib/ssl/sslsecur.c @@ -599,9 +599,6 @@ ssl_CopySecurityInfo(sslSocket *ss, sslSocket *os) if (os->sec.peerCert && !ss->sec.peerCert) goto loser; - ss->sec.cache = os->sec.cache; - ss->sec.uncache = os->sec.uncache; - return SECSuccess; loser: @@ -1159,7 +1156,7 @@ SSL_InvalidateSession(PRFileDesc *fd) ssl_GetSSL3HandshakeLock(ss); if (ss->sec.ci.sid) { - ss->sec.uncache(ss->sec.ci.sid); + ssl_UncacheSessionID(ss); rv = SECSuccess; } diff --git a/lib/ssl/sslsnce.c b/lib/ssl/sslsnce.c index 279f3c0154..d7abb3dc32 100644 --- a/lib/ssl/sslsnce.c +++ b/lib/ssl/sslsnce.c @@ -495,12 +495,6 @@ ConvertToSID(sidCacheEntry *from, PORT_Memcpy(to->u.ssl3.sessionID, from->sessionID, from->sessionIDLength); - /* the portions of the SID that are only restored on the client - * are set to invalid values on the server. - */ - to->u.ssl3.clientWriteKey = NULL; - to->u.ssl3.serverWriteKey = NULL; - to->urlSvrName = NULL; to->u.ssl3.masterModuleID = (SECMODModuleID)-1; /* invalid value */ @@ -735,9 +729,11 @@ ServerSessionIDLookup(const PRIPv6Addr *addr, /* ** Place a sid into the cache, if it isn't already there. */ -static void -ServerSessionIDCache(sslSessionID *sid) +void +ssl_ServerCacheSessionID(sslSessionID *sid) { + PORT_Assert(sid); + sidCacheEntry sce; PRUint32 now = 0; cacheDesc *cache = &globalCache; @@ -800,8 +796,8 @@ ServerSessionIDCache(sslSessionID *sid) ** Although this is static, it is called from ssl via global function pointer ** ssl_sid_uncache. This invalidates the referenced cache entry. */ -static void -ServerSessionIDUncache(sslSessionID *sid) +void +ssl_ServerUncacheSessionID(sslSessionID *sid) { cacheDesc *cache = &globalCache; PRUint8 *sessionID; @@ -1172,8 +1168,6 @@ ssl_ConfigServerSessionIDCacheInstanceWithOpt(cacheDesc *cache, } ssl_sid_lookup = ServerSessionIDLookup; - ssl_sid_cache = ServerSessionIDCache; - ssl_sid_uncache = ServerSessionIDUncache; return SECSuccess; } @@ -1356,8 +1350,6 @@ SSL_InheritMPServerSIDCacheInstance(cacheDesc *cache, const char *envString) ssl_InitSessionCacheLocks(PR_FALSE); ssl_sid_lookup = ServerSessionIDLookup; - ssl_sid_cache = ServerSessionIDCache; - ssl_sid_uncache = ServerSessionIDUncache; if (!envString) { envString = PR_GetEnvSecure(envVarName); diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c index 4893cb9f92..fbc0a17425 100644 --- a/lib/ssl/sslsock.c +++ b/lib/ssl/sslsock.c @@ -104,8 +104,6 @@ static SSLVersionRange versions_defaults_datagram = { (variant == ssl_variant_stream ? NSS_TLS_VERSION_MAX_POLICY : NSS_DTLS_VERSION_MAX_POLICY) sslSessionIDLookupFunc ssl_sid_lookup; -sslSessionIDCacheFunc ssl_sid_cache; -sslSessionIDUncacheFunc ssl_sid_uncache; static PRDescIdentity ssl_layer_id; @@ -356,6 +354,8 @@ ssl_DupSocket(sslSocket *os) os->namedGroupPreferences, sizeof(ss->namedGroupPreferences)); ss->additionalShares = os->additionalShares; + ss->resumptionTokenCallback = os->resumptionTokenCallback; + ss->resumptionTokenContext = os->resumptionTokenContext; /* Create security data */ rv = ssl_CopySecurityInfo(ss, os); @@ -3933,6 +3933,10 @@ struct { EXP(KeyUpdate), EXP(SendSessionTicket), EXP(SetupAntiReplay), + EXP(SetResumptionTokenCallback), + EXP(SetResumptionToken), + EXP(GetResumptionTokenInfo), + EXP(DestroyResumptionTokenInfo), #endif { "", NULL } }; @@ -3967,3 +3971,156 @@ ssl_ClearPRCList(PRCList *list, void (*f)(void *)) PORT_Free(cursor); } } + +/* Experimental APIs for session cache handling. */ + +SECStatus +SSLExp_SetResumptionTokenCallback(PRFileDesc *fd, + SSLResumptionTokenCallback cb, + void *ctx) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetResumptionTokenCallback", + SSL_GETPID(), fd)); + return SECFailure; + } + + ssl_Get1stHandshakeLock(ss); + ssl_GetSSL3HandshakeLock(ss); + ss->resumptionTokenCallback = cb; + ss->resumptionTokenContext = ctx; + ssl_ReleaseSSL3HandshakeLock(ss); + ssl_Release1stHandshakeLock(ss); + + return SECSuccess; +} + +SECStatus +SSLExp_SetResumptionToken(PRFileDesc *fd, const PRUint8 *token, + unsigned int len) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetResumptionToken", + SSL_GETPID(), fd)); + return SECFailure; + } + + ssl_Get1stHandshakeLock(ss); + ssl_GetSSL3HandshakeLock(ss); + + if (ss->firstHsDone || ss->ssl3.hs.ws != idle_handshake || + ss->sec.isServer || len == 0 || !token) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto done; + } + + // We override any previously set session. + if (ss->sec.ci.sid) { + ssl_FreeSID(ss->sec.ci.sid); + ss->sec.ci.sid = NULL; + } + + PRINT_BUF(50, (ss, "incoming resumption token", token, len)); + + ss->sec.ci.sid = ssl3_NewSessionID(ss, PR_FALSE); + if (!ss->sec.ci.sid) { + goto done; + } + + /* Populate NewSessionTicket values */ + SECStatus rv = ssl_DecodeResumptionToken(ss->sec.ci.sid, token, len); + if (rv != SECSuccess) { + // If decoding fails, we assume the token is bad. + PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR); + ssl_FreeSID(ss->sec.ci.sid); + ss->sec.ci.sid = NULL; + goto done; + } + + // Make sure that the token is valid. + if (!ssl_IsResumptionTokenValid(ss)) { + ssl_FreeSID(ss->sec.ci.sid); + ss->sec.ci.sid = NULL; + PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR); + goto done; + } + + /* Use the sid->cached as marker that this is from an external cache and + * we don't have to look up anything in the NSS internal cache. */ + ss->sec.ci.sid->cached = in_external_cache; + // This has to be 2 to not free this in sendClientHello. + ss->sec.ci.sid->references = 2; + ss->sec.ci.sid->lastAccessTime = ssl_TimeSec(); + + ssl_ReleaseSSL3HandshakeLock(ss); + ssl_Release1stHandshakeLock(ss); + return SECSuccess; + +done: + ssl_ReleaseSSL3HandshakeLock(ss); + ssl_Release1stHandshakeLock(ss); + + return SECFailure; +} + +SECStatus +SSLExp_DestroyResumptionTokenInfo(SSLResumptionTokenInfo *token) +{ + if (!token) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (token->peerCert) { + CERT_DestroyCertificate(token->peerCert); + } + PORT_Free(token->alpnSelection); + PORT_Memset(token, 0, token->length); + return SECSuccess; +} + +SECStatus +SSLExp_GetResumptionTokenInfo(const PRUint8 *tokenData, unsigned int tokenLen, + SSLResumptionTokenInfo *tokenOut, PRUintn len) +{ + if (!tokenData || !tokenOut || !tokenLen || + len > sizeof(SSLResumptionTokenInfo)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + sslSessionID sid = { 0 }; + SSLResumptionTokenInfo token; + + /* Populate sid values */ + if (ssl_DecodeResumptionToken(&sid, tokenData, tokenLen) != SECSuccess) { + // If decoding fails, we assume the token is bad. + PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR); + return SECFailure; + } + + token.peerCert = CERT_DupCertificate(sid.peerCert); + + token.alpnSelectionLen = sid.u.ssl3.alpnSelection.len; + token.alpnSelection = PORT_ZAlloc(token.alpnSelectionLen); + if (!token.alpnSelection) { + return SECFailure; + } + PORT_Memcpy(token.alpnSelection, sid.u.ssl3.alpnSelection.data, + token.alpnSelectionLen); + + if (sid.u.ssl3.locked.sessionTicket.flags & ticket_allow_early_data) { + token.maxEarlyDataSize = + sid.u.ssl3.locked.sessionTicket.max_early_data_size; + } else { + token.maxEarlyDataSize = 0; + } + + token.length = PR_MIN(sizeof(SSLResumptionTokenInfo), len); + PORT_Memcpy(tokenOut, &token, token.length); + + ssl_DestroySID(&sid, PR_FALSE); + return SECSuccess; +} diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c index cef40e10d9..42b8d169ac 100644 --- a/lib/ssl/tls13con.c +++ b/lib/ssl/tls13con.c @@ -462,7 +462,7 @@ tls13_SetupClientHello(sslSocket *ss) if (rv != SECSuccess) { FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); SSL_AtomicIncrementLong(&ssl3stats->sch_sid_cache_not_ok); - ss->sec.uncache(ss->sec.ci.sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(ss->sec.ci.sid); ss->sec.ci.sid = NULL; return SECFailure; @@ -1719,7 +1719,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss, } if (hrr) { if (sid) { /* Free the sid. */ - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); } PORT_Assert(ss->ssl3.hs.helloRetry); @@ -1769,8 +1769,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss, } else { if (sid) { /* we had a sid, but it's no longer valid, free it */ SSL_AtomicIncrementLong(&ssl3stats->hch_sid_cache_not_ok); - if (ss->sec.uncache) - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); sid = NULL; } @@ -1830,7 +1829,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss, if (sid) { /* We had a sid, but it's no longer valid, free it. */ SSL_AtomicIncrementLong(&ssl3stats->hch_sid_cache_not_ok); - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); } else { SSL_AtomicIncrementLong(&ssl3stats->hch_sid_cache_misses); @@ -1866,7 +1865,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss, loser: if (sid) { - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(sid); } return SECFailure; @@ -2539,7 +2538,7 @@ tls13_HandleServerHelloPart2(sslSocket *ss) } if (sid->cached == in_client_cache) { /* If we tried to resume and failed, let's not try again. */ - ss->sec.uncache(sid); + ssl_UncacheSessionID(ss); } } @@ -4681,7 +4680,7 @@ tls13_HandleNewSessionTicket(sslSocket *ss, PRUint8 *b, PRUint32 length) } /* Destroy the old SID. */ - ss->sec.uncache(ss->sec.ci.sid); + ssl_UncacheSessionID(ss); ssl_FreeSID(ss->sec.ci.sid); ss->sec.ci.sid = sid; } @@ -4707,7 +4706,7 @@ tls13_HandleNewSessionTicket(sslSocket *ss, PRUint8 *b, PRUint32 length) } /* Cache the session. */ - ss->sec.cache(ss->sec.ci.sid); + ssl_CacheSessionID(ss); } return SECSuccess; diff --git a/lib/ssl/tls13hashstate.c b/lib/ssl/tls13hashstate.c index e3232f524e..cc0ed286b0 100644 --- a/lib/ssl/tls13hashstate.c +++ b/lib/ssl/tls13hashstate.c @@ -88,36 +88,37 @@ tls13_RecoverHashState(sslSocket *ss, { SECStatus rv; unsigned char plaintext[1024]; - SECItem ptItem = { siBuffer, plaintext, 0 }; + unsigned int plaintextLen = 0; sslBuffer messageBuf = SSL_BUFFER_EMPTY; - PRUint32 sentinel; - PRUint32 cipherSuite; - PRUint32 group; + PRUint64 sentinel; + PRUint64 cipherSuite; + PRUint64 group; const sslNamedGroupDef *selectedGroup; - PRUint32 appTokenLen; - PRUint8 *appToken; + PRUint64 appTokenLen; rv = ssl_SelfEncryptUnprotect(ss, cookie, cookieLen, - ptItem.data, &ptItem.len, sizeof(plaintext)); + plaintext, &plaintextLen, sizeof(plaintext)); if (rv != SECSuccess) { return SECFailure; } + sslReader reader = SSL_READER(plaintext, plaintextLen); + /* Should start with 0xff. */ - rv = ssl3_ConsumeNumberFromItem(&ptItem, &sentinel, 1); + rv = sslRead_ReadNumber(&reader, 1, &sentinel); if ((rv != SECSuccess) || (sentinel != 0xff)) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } /* The cipher suite should be the same or there are some shenanigans. */ - rv = ssl3_ConsumeNumberFromItem(&ptItem, &cipherSuite, 2); + rv = sslRead_ReadNumber(&reader, 2, &cipherSuite); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } /* The named group, if any. */ - rv = ssl3_ConsumeNumberFromItem(&ptItem, &group, 2); + rv = sslRead_ReadNumber(&reader, 2, &group); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; @@ -126,7 +127,7 @@ tls13_RecoverHashState(sslSocket *ss, /* Application token. */ PORT_Assert(ss->xtnData.applicationToken.len == 0); - rv = ssl3_ConsumeNumberFromItem(&ptItem, &appTokenLen, 2); + rv = sslRead_ReadNumber(&reader, 2, &appTokenLen); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; @@ -137,15 +138,18 @@ tls13_RecoverHashState(sslSocket *ss, return SECFailure; } ss->xtnData.applicationToken.len = appTokenLen; - rv = ssl3_ConsumeFromItem(&ptItem, &appToken, appTokenLen); + sslReadBuffer appTokenReader = { 0 }; + rv = sslRead_Read(&reader, appTokenLen, &appTokenReader); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } - PORT_Memcpy(ss->xtnData.applicationToken.data, appToken, appTokenLen); + PORT_Assert(appTokenReader.len == appTokenLen); + PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen); /* The remainder is the hash. */ - if (ptItem.len != tls13_GetHashSize(ss)) { + unsigned int hashLen = SSL_READER_REMAINING(&reader); + if (hashLen != tls13_GetHashSize(ss)) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); return SECFailure; } @@ -153,7 +157,7 @@ tls13_RecoverHashState(sslSocket *ss, /* Now reinject the message. */ SSL_ASSERT_HASHES_EMPTY(ss); rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_message_hash, 0, - ptItem.data, ptItem.len); + SSL_READER_CURRENT(&reader), hashLen); if (rv != SECSuccess) { return SECFailure; }