summaryrefslogtreecommitdiff
path: root/gtests/ssl_gtest/tls_protect.cc
diff options
context:
space:
mode:
authorMartin Thomson <martin.thomson@gmail.com>2018-10-23 15:35:13 +1100
committerMartin Thomson <martin.thomson@gmail.com>2018-10-23 15:35:13 +1100
commita9b4d3dbe96c8889dc331fe39490402cb40e5f56 (patch)
treea4c88d6cc94168208d49baeebeefd26a9aa0db86 /gtests/ssl_gtest/tls_protect.cc
parentcc253da3e8d632ea0f8268516788de77b7a60d9b (diff)
downloadnss-hg-a9b4d3dbe96c8889dc331fe39490402cb40e5f56.tar.gz
Bug 1471126 - Provide extra information needed to use record layer separation, r=ekr
This started as an attempt to remove the cipher spec update callback we use for testing. Using the new, public secrets interface should be better for that. In doing so, it became apparent that we needed more interfaces to NSS to support the use of these secrets. In particular: 1. We need to know what the KDF hash function is for a given cipher suite. This allows users of the secret to use the right hash function. 2. We need to know what cipher spec was picked when sending 0-RTT. NSS currently doesn't expose that information. (When receiving 0-RTT you can safely assume that the negotiated cipher suite is good to use.) 3. We need to know what epoch NSS is currently using. Otherwise, we can't be sure which epoch to feed it. Data from a good epoch is saved, whereas data from a bad epoch is lost, so applications need to know. So this patch adds these functions to the appropriate info functions and uses that information in tests to remove and re-add protection. The test changes are considerable. The main effect of the changes is to rely on the new functions for managing secrets, rather than the old interface. But with the changes in the other CLs for this bug, secrets appear before they are used, which complicates things considerably. For that, I've moved more logic into the TlsCipherSpec class, which now tracks per-epoch state, like sequence numbers and record drops. Trial decryption (yep) is used to identify the right cipher spec every time when decrypting, so tests are no longer tolerant of failures to decrypt. It's no longer possible to have a test enable decryption and pass when decryption fails; this is particularly true for some parameterized tests that assumed it was OK to enable decryption even for TLS 1.2 and earlier.
Diffstat (limited to 'gtests/ssl_gtest/tls_protect.cc')
-rw-r--r--gtests/ssl_gtest/tls_protect.cc151
1 files changed, 109 insertions, 42 deletions
diff --git a/gtests/ssl_gtest/tls_protect.cc b/gtests/ssl_gtest/tls_protect.cc
index c715a36a6..c92b5ccf7 100644
--- a/gtests/ssl_gtest/tls_protect.cc
+++ b/gtests/ssl_gtest/tls_protect.cc
@@ -7,6 +7,9 @@
#include "tls_protect.h"
#include "tls_filter.h"
+// Do this to avoid having to re-implement HKDF.
+#include "tls13hkdf.h"
+
namespace nss_test {
AeadCipher::~AeadCipher() {
@@ -15,32 +18,37 @@ AeadCipher::~AeadCipher() {
}
}
-bool AeadCipher::Init(PK11SymKey *key, const uint8_t *iv) {
- key_ = PK11_ReferenceSymKey(key);
+bool AeadCipher::Init(PK11SymKey* key, const uint8_t* iv) {
+ key_ = key;
if (!key_) return false;
memcpy(iv_, iv, sizeof(iv_));
+ if (g_ssl_gtest_verbose) {
+ EXPECT_EQ(SECSuccess, PK11_ExtractKeyValue(key_));
+ SECItem* raw_key = PK11_GetKeyData(key_);
+ std::cerr << "key: " << DataBuffer(raw_key->data, raw_key->len)
+ << std::endl;
+ std::cerr << "iv: " << DataBuffer(iv_, 12) << std::endl;
+ }
return true;
}
-void AeadCipher::FormatNonce(uint64_t seq, uint8_t *nonce) {
+void AeadCipher::FormatNonce(uint64_t seq, uint8_t* nonce) {
memcpy(nonce, iv_, 12);
for (size_t i = 0; i < 8; ++i) {
nonce[12 - (i + 1)] ^= seq & 0xff;
seq >>= 8;
}
-
- DataBuffer d(nonce, 12);
}
-bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length,
- const uint8_t *in, size_t inlen, uint8_t *out,
- size_t *outlen, size_t maxlen) {
+bool AeadCipher::AeadInner(bool decrypt, void* params, size_t param_length,
+ const uint8_t* in, size_t inlen, uint8_t* out,
+ size_t* outlen, size_t maxlen) {
SECStatus rv;
unsigned int uoutlen = 0;
SECItem param = {
- siBuffer, static_cast<unsigned char *>(params),
+ siBuffer, static_cast<unsigned char*>(params),
static_cast<unsigned int>(param_length),
};
@@ -54,28 +62,28 @@ bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length,
return rv == SECSuccess;
}
-bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len,
- uint64_t seq, const uint8_t *in, size_t inlen,
- uint8_t *out, size_t *outlen, size_t maxlen) {
+bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len,
+ uint64_t seq, const uint8_t* in, size_t inlen,
+ uint8_t* out, size_t* outlen, size_t maxlen) {
CK_GCM_PARAMS aeadParams;
unsigned char nonce[12];
memset(&aeadParams, 0, sizeof(aeadParams));
aeadParams.pIv = nonce;
aeadParams.ulIvLen = sizeof(nonce);
- aeadParams.pAAD = const_cast<uint8_t *>(hdr);
+ aeadParams.pAAD = const_cast<uint8_t*>(hdr);
aeadParams.ulAADLen = hdr_len;
aeadParams.ulTagBits = 128;
FormatNonce(seq, nonce);
- return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams),
- in, inlen, out, outlen, maxlen);
+ return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
+ inlen, out, outlen, maxlen);
}
-bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr,
+bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t* hdr,
size_t hdr_len, uint64_t seq,
- const uint8_t *in, size_t inlen,
- uint8_t *out, size_t *outlen,
+ const uint8_t* in, size_t inlen,
+ uint8_t* out, size_t* outlen,
size_t maxlen) {
CK_NSS_AEAD_PARAMS aeadParams;
unsigned char nonce[12];
@@ -83,67 +91,126 @@ bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr,
memset(&aeadParams, 0, sizeof(aeadParams));
aeadParams.pNonce = nonce;
aeadParams.ulNonceLen = sizeof(nonce);
- aeadParams.pAAD = const_cast<uint8_t *>(hdr);
+ aeadParams.pAAD = const_cast<uint8_t*>(hdr);
aeadParams.ulAADLen = hdr_len;
aeadParams.ulTagLen = 16;
FormatNonce(seq, nonce);
- return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams),
- in, inlen, out, outlen, maxlen);
+ return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
+ inlen, out, outlen, maxlen);
+}
+
+static uint64_t FirstSeqno(bool dtls, uint16_t epoc) {
+ if (dtls) {
+ return static_cast<uint64_t>(epoc) << 48;
+ }
+ return 0;
}
-bool TlsCipherSpec::Init(uint16_t epoc, SSLCipherAlgorithm cipher,
- PK11SymKey *key, const uint8_t *iv) {
- epoch_ = epoc;
- switch (cipher) {
+TlsCipherSpec::TlsCipherSpec(bool dtls, uint16_t epoc)
+ : dtls_(dtls),
+ epoch_(epoc),
+ in_seqno_(FirstSeqno(dtls, epoc)),
+ out_seqno_(FirstSeqno(dtls, epoc)) {}
+
+bool TlsCipherSpec::SetKeys(SSLCipherSuiteInfo* cipherinfo,
+ PK11SymKey* secret) {
+ CK_MECHANISM_TYPE mech;
+ switch (cipherinfo->symCipher) {
case ssl_calg_aes_gcm:
aead_.reset(new AeadCipherAesGcm());
+ mech = CKM_AES_GCM;
break;
case ssl_calg_chacha20:
aead_.reset(new AeadCipherChacha20Poly1305());
+ mech = CKM_NSS_CHACHA20_POLY1305;
break;
default:
return false;
}
+ PK11SymKey* key;
+ const std::string kPurposeKey = "key";
+ SECStatus rv = tls13_HkdfExpandLabel(
+ secret, cipherinfo->kdfHash, NULL, 0, kPurposeKey.c_str(),
+ kPurposeKey.length(), mech, cipherinfo->symKeyBits / 8, &key);
+ if (rv != SECSuccess) {
+ ADD_FAILURE() << "unable to derive key for epoch " << epoch_;
+ return false;
+ }
+
+ // No constant for IV length, but everything we know of uses 12.
+ uint8_t iv[12];
+ const std::string kPurposeIv = "iv";
+ rv = tls13_HkdfExpandLabelRaw(secret, cipherinfo->kdfHash, NULL, 0,
+ kPurposeIv.c_str(), kPurposeIv.length(), iv,
+ sizeof(iv));
+ if (rv != SECSuccess) {
+ ADD_FAILURE() << "unable to derive IV for epoch " << epoch_;
+ return false;
+ }
+
return aead_->Init(key, iv);
}
-bool TlsCipherSpec::Unprotect(const TlsRecordHeader &header,
- const DataBuffer &ciphertext,
- DataBuffer *plaintext) {
+bool TlsCipherSpec::Unprotect(const TlsRecordHeader& header,
+ const DataBuffer& ciphertext,
+ DataBuffer* plaintext) {
+ if (aead_ == nullptr) {
+ return false;
+ }
// Make space.
plaintext->Allocate(ciphertext.len());
auto header_bytes = header.header();
size_t len;
- bool ret =
- aead_->Aead(true, header_bytes.data(), header_bytes.len(),
- header.sequence_number(), ciphertext.data(), ciphertext.len(),
- plaintext->data(), &len, plaintext->len());
- if (!ret) return false;
+ uint64_t seqno;
+ if (dtls_) {
+ seqno = header.sequence_number();
+ } else {
+ seqno = in_seqno_;
+ }
+ bool ret = aead_->Aead(true, header_bytes.data(), header_bytes.len(), seqno,
+ ciphertext.data(), ciphertext.len(), plaintext->data(),
+ &len, plaintext->len());
+ if (!ret) {
+ return false;
+ }
+ RecordUnprotected(seqno);
plaintext->Truncate(len);
return true;
}
-bool TlsCipherSpec::Protect(const TlsRecordHeader &header,
- const DataBuffer &plaintext,
- DataBuffer *ciphertext) {
+bool TlsCipherSpec::Protect(const TlsRecordHeader& header,
+ const DataBuffer& plaintext,
+ DataBuffer* ciphertext) {
+ if (aead_ == nullptr) {
+ return false;
+ }
// Make a padded buffer.
-
ciphertext->Allocate(plaintext.len() +
32); // Room for any plausible auth tag
size_t len;
DataBuffer header_bytes;
(void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16);
- bool ret =
- aead_->Aead(false, header_bytes.data(), header_bytes.len(),
- header.sequence_number(), plaintext.data(), plaintext.len(),
- ciphertext->data(), &len, ciphertext->len());
- if (!ret) return false;
+ uint64_t seqno;
+ if (dtls_) {
+ seqno = header.sequence_number();
+ } else {
+ seqno = out_seqno_;
+ }
+
+ bool ret = aead_->Aead(false, header_bytes.data(), header_bytes.len(), seqno,
+ plaintext.data(), plaintext.len(), ciphertext->data(),
+ &len, ciphertext->len());
+ if (!ret) {
+ return false;
+ }
+
+ RecordProtected();
ciphertext->Truncate(len);
return true;