summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Thomson <martin.thomson@gmail.com>2017-11-24 16:40:22 +1100
committerMartin Thomson <martin.thomson@gmail.com>2017-11-24 16:40:22 +1100
commit1ee76b91c89ce6e6166d3631e07d23e836e9dc26 (patch)
tree63c5bfefa0dd0d135f5841cac890b5002907ba52
parentcea9c7ec22e0483a4d5d03245cd03f443aeb752b (diff)
downloadnss-hg-1ee76b91c89ce6e6166d3631e07d23e836e9dc26.tar.gz
Bug 1396487 - Record size limiting extension, r=ekr
Summary: See draft-ietf-tls-record-limit for details. Reviewers: ekr Bug #: 1396487 Differential Revision: https://phabricator.services.mozilla.com/D23
-rw-r--r--gtests/ssl_gtest/manifest.mn1
-rw-r--r--gtests/ssl_gtest/ssl_custext_unittest.cc1
-rw-r--r--gtests/ssl_gtest/ssl_gtest.gyp1
-rw-r--r--gtests/ssl_gtest/ssl_record_unittest.cc13
-rw-r--r--gtests/ssl_gtest/ssl_recordsize_unittest.cc431
-rw-r--r--gtests/ssl_gtest/tls_agent.cc4
-rw-r--r--gtests/ssl_gtest/tls_filter.cc8
-rw-r--r--gtests/ssl_gtest/tls_filter.h3
-rw-r--r--lib/ssl/dtlscon.c11
-rw-r--r--lib/ssl/ssl.h22
-rw-r--r--lib/ssl/ssl3con.c36
-rw-r--r--lib/ssl/ssl3ext.c3
-rw-r--r--lib/ssl/ssl3ext.h3
-rw-r--r--lib/ssl/ssl3exthandle.c64
-rw-r--r--lib/ssl/ssl3exthandle.h7
-rw-r--r--lib/ssl/sslimpl.h1
-rw-r--r--lib/ssl/sslsock.c24
-rw-r--r--lib/ssl/sslspec.c1
-rw-r--r--lib/ssl/sslspec.h4
-rw-r--r--lib/ssl/sslt.h3
-rw-r--r--lib/ssl/tls13con.c24
21 files changed, 638 insertions, 27 deletions
diff --git a/gtests/ssl_gtest/manifest.mn b/gtests/ssl_gtest/manifest.mn
index 5d893bab3..8547e56d1 100644
--- a/gtests/ssl_gtest/manifest.mn
+++ b/gtests/ssl_gtest/manifest.mn
@@ -36,6 +36,7 @@ CPPSRCS = \
ssl_loopback_unittest.cc \
ssl_misc_unittest.cc \
ssl_record_unittest.cc \
+ ssl_recordsize_unittest.cc \
ssl_resumption_unittest.cc \
ssl_renegotiation_unittest.cc \
ssl_skip_unittest.cc \
diff --git a/gtests/ssl_gtest/ssl_custext_unittest.cc b/gtests/ssl_gtest/ssl_custext_unittest.cc
index c2f582a93..5be62e506 100644
--- a/gtests/ssl_gtest/ssl_custext_unittest.cc
+++ b/gtests/ssl_gtest/ssl_custext_unittest.cc
@@ -68,6 +68,7 @@ static const uint16_t kManyExtensions[] = {
ssl_next_proto_nego_xtn,
ssl_renegotiation_info_xtn,
ssl_tls13_short_header_xtn,
+ ssl_record_size_limit_xtn,
1,
0xffff};
// The list here includes all extensions we expect to use (SSL_MAX_EXTENSIONS),
diff --git a/gtests/ssl_gtest/ssl_gtest.gyp b/gtests/ssl_gtest/ssl_gtest.gyp
index e2a8d830a..17677713d 100644
--- a/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/gtests/ssl_gtest/ssl_gtest.gyp
@@ -37,6 +37,7 @@
'ssl_loopback_unittest.cc',
'ssl_misc_unittest.cc',
'ssl_record_unittest.cc',
+ 'ssl_recordsize_unittest.cc',
'ssl_resumption_unittest.cc',
'ssl_renegotiation_unittest.cc',
'ssl_skip_unittest.cc',
diff --git a/gtests/ssl_gtest/ssl_record_unittest.cc b/gtests/ssl_gtest/ssl_record_unittest.cc
index 97bbbba01..4c33c1936 100644
--- a/gtests/ssl_gtest/ssl_record_unittest.cc
+++ b/gtests/ssl_gtest/ssl_record_unittest.cc
@@ -104,15 +104,13 @@ TEST_P(TlsPaddingTest, LastByteOfPadWrong) {
class RecordReplacer : public TlsRecordFilter {
public:
RecordReplacer(const std::shared_ptr<TlsAgent>& a, size_t size)
- : TlsRecordFilter(a), enabled_(false), size_(size) {}
+ : TlsRecordFilter(a), size_(size) {
+ Disable();
+ }
PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
const DataBuffer& data,
DataBuffer* changed) override {
- if (!enabled_) {
- return KEEP;
- }
-
EXPECT_EQ(kTlsApplicationDataType, header.content_type());
changed->Allocate(size_);
@@ -120,14 +118,11 @@ class RecordReplacer : public TlsRecordFilter {
changed->data()[i] = i & 0xff;
}
- enabled_ = false;
+ Disable();
return CHANGE;
}
- void Enable() { enabled_ = true; }
-
private:
- bool enabled_;
size_t size_;
};
diff --git a/gtests/ssl_gtest/ssl_recordsize_unittest.cc b/gtests/ssl_gtest/ssl_recordsize_unittest.cc
new file mode 100644
index 000000000..8b9e28aff
--- /dev/null
+++ b/gtests/ssl_gtest/ssl_recordsize_unittest.cc
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "secerr.h"
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+#include "gtest_utils.h"
+#include "scoped_ptrs.h"
+#include "tls_connect.h"
+#include "tls_filter.h"
+#include "tls_parser.h"
+
+namespace nss_test {
+
+// This class tracks the maximum size of record that was sent, both cleartext
+// and plain. It only tracks records that have an outer type of
+// application_data. In TLS 1.3, this includes handshake messages.
+class TlsRecordMaximum : public TlsRecordFilter {
+ public:
+ TlsRecordMaximum(const std::shared_ptr<TlsAgent>& a)
+ : TlsRecordFilter(a), max_ciphertext_(0), max_plaintext_(0) {}
+
+ size_t max_ciphertext() const { return max_ciphertext_; }
+ size_t max_plaintext() const { return max_plaintext_; }
+
+ protected:
+ PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& record, size_t* offset,
+ DataBuffer* output) override {
+ std::cerr << "max: " << record << std::endl;
+ // Ignore unprotected packets.
+ if (header.content_type() != kTlsApplicationDataType) {
+ return KEEP;
+ }
+
+ max_ciphertext_ = (std::max)(max_ciphertext_, record.len());
+ return TlsRecordFilter::FilterRecord(header, record, offset, output);
+ }
+
+ PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& data,
+ DataBuffer* changed) override {
+ max_plaintext_ = (std::max)(max_plaintext_, data.len());
+ return KEEP;
+ }
+
+ private:
+ size_t max_ciphertext_;
+ size_t max_plaintext_;
+};
+
+void CheckRecordSizes(const std::shared_ptr<TlsAgent>& agent,
+ const std::shared_ptr<TlsRecordMaximum>& record_max,
+ size_t config) {
+ uint16_t cipher_suite;
+ ASSERT_TRUE(agent->cipher_suite(&cipher_suite));
+
+ size_t expansion;
+ size_t iv;
+ switch (cipher_suite) {
+ case TLS_AES_128_GCM_SHA256:
+ case TLS_AES_256_GCM_SHA384:
+ case TLS_CHACHA20_POLY1305_SHA256:
+ expansion = 16;
+ iv = 0;
+ break;
+
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ expansion = 16;
+ iv = 8;
+ break;
+
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ // Expansion is 20 for the MAC. Maximum block padding is 16. Maximum
+ // padding is added when the input plus the MAC is an exact multiple of
+ // the block size.
+ expansion = 20 + 16 - ((config + 20) % 16);
+ iv = 16;
+ break;
+
+ default:
+ ADD_FAILURE() << "No expansion set for ciphersuite "
+ << agent->cipher_suite_name();
+ return;
+ }
+
+ switch (agent->version()) {
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ EXPECT_EQ(0U, iv) << "No IV for TLS 1.3";
+ // We only have decryption in TLS 1.3.
+ EXPECT_EQ(config - 1, record_max->max_plaintext())
+ << "bad plaintext length for " << agent->role_str();
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ expansion += iv;
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ break;
+
+ default:
+ ADD_FAILURE() << "Unexpected version " << agent->version();
+ return;
+ }
+
+ EXPECT_EQ(config + expansion, record_max->max_ciphertext())
+ << "bad ciphertext length for " << agent->role_str();
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeMaximum) {
+ uint16_t max_record_size =
+ (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) ? 16385 : 16384;
+ size_t send_size = (version_ >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? max_record_size
+ : max_record_size + 1;
+
+ EnsureTlsSetup();
+ auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
+ client_max->EnableDecryption();
+ auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
+ server_max->EnableDecryption();
+
+ Connect();
+ client_->SendData(send_size, send_size);
+ server_->SendData(send_size, send_size);
+ server_->ReadBytes(send_size);
+ client_->ReadBytes(send_size);
+
+ CheckRecordSizes(client_, client_max, max_record_size);
+ CheckRecordSizes(server_, server_max, max_record_size);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) {
+ EnsureTlsSetup();
+ auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
+ server_max->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+ SendReceive(127); // Big enough for one record, allowing for 1+N splitting.
+
+ CheckRecordSizes(server_, server_max, 64);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) {
+ EnsureTlsSetup();
+ auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
+ client_max->EnableDecryption();
+
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+ SendReceive(127);
+
+ CheckRecordSizes(client_, client_max, 64);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeAsymmetric) {
+ EnsureTlsSetup();
+ auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
+ client_max->EnableDecryption();
+ auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
+ server_max->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 100);
+ Connect();
+ SendReceive(127);
+
+ CheckRecordSizes(client_, client_max, 100);
+ CheckRecordSizes(server_, server_max, 64);
+}
+
+// This just modifies the encrypted payload so to include a few extra zeros.
+class TlsRecordExpander : public TlsRecordFilter {
+ public:
+ TlsRecordExpander(const std::shared_ptr<TlsAgent>& a, size_t expansion)
+ : TlsRecordFilter(a), expansion_(expansion) {}
+
+ protected:
+ virtual PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& data,
+ DataBuffer* changed) {
+ if (header.content_type() != kTlsApplicationDataType) {
+ return KEEP;
+ }
+ changed->Allocate(data.len() + expansion_);
+ changed->Write(0, data.data(), data.len());
+ return CHANGE;
+ }
+
+ private:
+ size_t expansion_;
+};
+
+// Tweak the plaintext of server records so that they exceed the client's limit.
+TEST_P(TlsConnectTls13, RecordSizePlaintextExceed) {
+ EnsureTlsSetup();
+ auto server_expand = MakeTlsFilter<TlsRecordExpander>(server_, 1);
+ server_expand->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+
+ server_->SendData(100);
+
+ client_->ExpectReadWriteError();
+ ExpectAlert(client_, kTlsAlertRecordOverflow);
+ client_->ReadBytes(100);
+ EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, client_->error_code());
+
+ // Consume the alert at the server.
+ server_->Handshake();
+ server_->CheckErrorCode(SSL_ERROR_RECORD_OVERFLOW_ALERT);
+}
+
+// Tweak the ciphertext of server records so that they greatly exceed the limit.
+// This requires a much larger expansion than for plaintext to trigger the
+// guard, which runs before decryption (current allowance is 304 octets).
+TEST_P(TlsConnectTls13, RecordSizeCiphertextExceed) {
+ EnsureTlsSetup();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+
+ auto server_expand = MakeTlsFilter<TlsRecordExpander>(server_, 320);
+ server_->SendData(100);
+
+ client_->ExpectReadWriteError();
+ ExpectAlert(client_, kTlsAlertRecordOverflow);
+ client_->ReadBytes(100);
+ EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, client_->error_code());
+
+ // Consume the alert at the server.
+ server_->Handshake();
+ server_->CheckErrorCode(SSL_ERROR_RECORD_OVERFLOW_ALERT);
+}
+
+// This indiscriminately adds padding to application data records.
+class TlsRecordPadder : public TlsRecordFilter {
+ public:
+ TlsRecordPadder(const std::shared_ptr<TlsAgent>& a, size_t padding)
+ : TlsRecordFilter(a), padding_(padding) {}
+
+ protected:
+ PacketFilter::Action FilterRecord(const TlsRecordHeader& header,
+ const DataBuffer& record, size_t* offset,
+ DataBuffer* output) override {
+ if (header.content_type() != kTlsApplicationDataType) {
+ return KEEP;
+ }
+
+ uint8_t inner_content_type;
+ DataBuffer plaintext;
+ if (!Unprotect(header, record, &inner_content_type, &plaintext)) {
+ return KEEP;
+ }
+
+ if (inner_content_type != kTlsApplicationDataType) {
+ return KEEP;
+ }
+
+ DataBuffer ciphertext;
+ bool ok =
+ Protect(header, inner_content_type, plaintext, &ciphertext, padding_);
+ EXPECT_TRUE(ok);
+ if (!ok) {
+ return KEEP;
+ }
+ *offset = header.Write(output, *offset, ciphertext);
+ return CHANGE;
+ }
+
+ private:
+ size_t padding_;
+};
+
+TEST_P(TlsConnectTls13, RecordSizeExceedPad) {
+ EnsureTlsSetup();
+ auto server_max = std::make_shared<TlsRecordMaximum>(server_);
+ auto server_expand = std::make_shared<TlsRecordPadder>(server_, 1);
+ server_->SetFilter(std::make_shared<ChainedPacketFilter>(
+ ChainedPacketFilterInit({server_max, server_expand})));
+ server_expand->EnableDecryption();
+
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
+ Connect();
+
+ server_->SendData(100);
+
+ client_->ExpectReadWriteError();
+ ExpectAlert(client_, kTlsAlertRecordOverflow);
+ client_->ReadBytes(100);
+ EXPECT_EQ(SSL_ERROR_RX_RECORD_TOO_LONG, client_->error_code());
+
+ // Consume the alert at the server.
+ server_->Handshake();
+ server_->CheckErrorCode(SSL_ERROR_RECORD_OVERFLOW_ALERT);
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeBadValues) {
+ EnsureTlsSetup();
+ EXPECT_EQ(SECFailure,
+ SSL_OptionSet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, 63));
+ EXPECT_EQ(SECFailure,
+ SSL_OptionSet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, -1));
+ EXPECT_EQ(SECFailure,
+ SSL_OptionSet(server_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, 16386));
+ Connect();
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeGetValues) {
+ EnsureTlsSetup();
+ int v;
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionGet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(16385, v);
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 300);
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionGet(client_->ssl_fd(), SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(300, v);
+ Connect();
+}
+
+// The value of the extension is capped by the maximum version of the client.
+TEST_P(TlsConnectGeneric, RecordSizeCapExtensionClient) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385);
+ auto capture =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_record_size_limit_xtn);
+ capture->EnableDecryption();
+ Connect();
+
+ uint64_t val = 0;
+ EXPECT_TRUE(capture->extension().Read(0, 2, &val));
+ if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
+ EXPECT_EQ(16384U, val) << "Extension should be capped";
+ } else {
+ EXPECT_EQ(16385U, val);
+ }
+}
+
+// The value of the extension is capped by the maximum version of the server.
+TEST_P(TlsConnectGeneric, RecordSizeCapExtensionServer) {
+ EnsureTlsSetup();
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385);
+ auto capture =
+ MakeTlsFilter<TlsExtensionCapture>(server_, ssl_record_size_limit_xtn);
+ capture->EnableDecryption();
+ Connect();
+
+ uint64_t val = 0;
+ EXPECT_TRUE(capture->extension().Read(0, 2, &val));
+ if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
+ EXPECT_EQ(16384U, val) << "Extension should be capped";
+ } else {
+ EXPECT_EQ(16385U, val);
+ }
+}
+
+// Damage the client extension and the handshake fails, but the server
+// doesn't generate a validation error.
+TEST_P(TlsConnectGenericPre13, RecordSizeClientExtensionInvalid) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 1000);
+ static const uint8_t v[] = {0xf4, 0x1f};
+ MakeTlsFilter<TlsExtensionReplacer>(client_, ssl_record_size_limit_xtn,
+ DataBuffer(v, sizeof(v)));
+ ConnectExpectAlert(server_, kTlsAlertDecryptError);
+}
+
+// Special handling for TLS 1.3, where the alert isn't read.
+TEST_F(TlsConnectStreamTls13, RecordSizeClientExtensionInvalid) {
+ EnsureTlsSetup();
+ client_->SetOption(SSL_RECORD_SIZE_LIMIT, 1000);
+ static const uint8_t v[] = {0xf4, 0x1f};
+ MakeTlsFilter<TlsExtensionReplacer>(client_, ssl_record_size_limit_xtn,
+ DataBuffer(v, sizeof(v)));
+ client_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ ConnectExpectFail();
+}
+
+TEST_P(TlsConnectGeneric, RecordSizeServerExtensionInvalid) {
+ EnsureTlsSetup();
+ server_->SetOption(SSL_RECORD_SIZE_LIMIT, 1000);
+ static const uint8_t v[] = {0xf4, 0x1f};
+ auto replace = MakeTlsFilter<TlsExtensionReplacer>(
+ server_, ssl_record_size_limit_xtn, DataBuffer(v, sizeof(v)));
+ replace->EnableDecryption();
+ ConnectExpectAlert(client_, kTlsAlertIllegalParameter);
+}
+
+class RecordSizeDefaultsTest : public ::testing::Test {
+ public:
+ void SetUp() {
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionGetDefault(SSL_RECORD_SIZE_LIMIT, &default_));
+ }
+ void TearDown() {
+ // Make sure to restore the default value at the end.
+ EXPECT_EQ(SECSuccess,
+ SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, default_));
+ }
+
+ private:
+ PRIntn default_;
+};
+
+TEST_F(RecordSizeDefaultsTest, RecordSizeBadValues) {
+ EXPECT_EQ(SECFailure, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, 63));
+ EXPECT_EQ(SECFailure, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, -1));
+ EXPECT_EQ(SECFailure, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, 16386));
+}
+
+TEST_F(RecordSizeDefaultsTest, RecordSizeGetValue) {
+ int v;
+ EXPECT_EQ(SECSuccess, SSL_OptionGetDefault(SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(16385, v);
+ EXPECT_EQ(SECSuccess, SSL_OptionSetDefault(SSL_RECORD_SIZE_LIMIT, 3000));
+ EXPECT_EQ(SECSuccess, SSL_OptionGetDefault(SSL_RECORD_SIZE_LIMIT, &v));
+ EXPECT_EQ(3000, v);
+}
+
+} // namespace nss_test
diff --git a/gtests/ssl_gtest/tls_agent.cc b/gtests/ssl_gtest/tls_agent.cc
index 1cd6fe396..9bed1ce1b 100644
--- a/gtests/ssl_gtest/tls_agent.cc
+++ b/gtests/ssl_gtest/tls_agent.cc
@@ -939,9 +939,9 @@ static bool ErrorIsNonFatal(PRErrorCode code) {
}
void TlsAgent::SendData(size_t bytes, size_t blocksize) {
- uint8_t block[4096];
+ uint8_t block[16385]; // One larger than the maximum record size.
- ASSERT_LT(blocksize, sizeof(block));
+ ASSERT_LE(blocksize, sizeof(block));
while (bytes) {
size_t tosend = std::min(blocksize, bytes);
diff --git a/gtests/ssl_gtest/tls_filter.cc b/gtests/ssl_gtest/tls_filter.cc
index 5b49d9851..aa03cba70 100644
--- a/gtests/ssl_gtest/tls_filter.cc
+++ b/gtests/ssl_gtest/tls_filter.cc
@@ -410,7 +410,7 @@ bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header,
bool TlsRecordFilter::Protect(const TlsRecordHeader& header,
uint8_t inner_content_type,
const DataBuffer& plaintext,
- DataBuffer* ciphertext) {
+ DataBuffer* ciphertext, size_t padding) {
if (!cipher_spec_ || header.content_type() != kTlsApplicationDataType) {
*ciphertext = plaintext;
return true;
@@ -418,8 +418,10 @@ bool TlsRecordFilter::Protect(const TlsRecordHeader& header,
if (g_ssl_gtest_verbose) {
std::cerr << "protect: " << header.sequence_number() << std::endl;
}
- DataBuffer padded = plaintext;
- padded.Write(padded.len(), inner_content_type, 1);
+ DataBuffer padded;
+ padded.Allocate(plaintext.len() + 1 + padding);
+ size_t offset = padded.Write(0, plaintext.data(), plaintext.len());
+ padded.Write(offset, inner_content_type, 1);
return cipher_spec_->Protect(header, padded, ciphertext);
}
diff --git a/gtests/ssl_gtest/tls_filter.h b/gtests/ssl_gtest/tls_filter.h
index 90520361c..80c60b42f 100644
--- a/gtests/ssl_gtest/tls_filter.h
+++ b/gtests/ssl_gtest/tls_filter.h
@@ -120,7 +120,8 @@ class TlsRecordFilter : public PacketFilter {
bool Unprotect(const TlsRecordHeader& header, const DataBuffer& cipherText,
uint8_t* inner_content_type, DataBuffer* plaintext);
bool Protect(const TlsRecordHeader& header, uint8_t inner_content_type,
- const DataBuffer& plaintext, DataBuffer* ciphertext);
+ const DataBuffer& plaintext, DataBuffer* ciphertext,
+ size_t padding = 0);
protected:
// There are two filter functions which can be overriden. Both are
diff --git a/lib/ssl/dtlscon.c b/lib/ssl/dtlscon.c
index 6c3d7d24c..a82295c66 100644
--- a/lib/ssl/dtlscon.c
+++ b/lib/ssl/dtlscon.c
@@ -724,13 +724,16 @@ dtls_FragmentHandshake(sslSocket *ss, DTLSQueuedMessage *msg)
PORT_Assert(end <= contentLen);
fragmentLen = PR_MIN(end, contentLen) - fragmentOffset;
- /* Reduce to the space remaining in the MTU. Allow for any existing
- * messages, record expansion, and the handshake header. */
+ /* Limit further by the record size limit. Account for the header. */
+ fragmentLen = PR_MIN(fragmentLen,
+ msg->cwSpec->recordSizeLimit - DTLS_HS_HDR_LEN);
+
+ /* Reduce to the space remaining in the MTU. */
fragmentLen = PR_MIN(fragmentLen,
ss->ssl3.mtu - /* MTU estimate. */
- ss->pendingBuf.len - /* Less unsent records. */
+ ss->pendingBuf.len - /* Less any unsent records. */
DTLS_MAX_EXPANSION - /* Allow for expansion. */
- DTLS_HS_HDR_LEN); /* + handshake header. */
+ DTLS_HS_HDR_LEN); /* And the handshake header. */
PORT_Assert(fragmentLen > 0 || fragmentOffset == 0);
/* Make totally sure that we will fit in the buffer. This should be
diff --git a/lib/ssl/ssl.h b/lib/ssl/ssl.h
index 0280f73c3..ecc4f9506 100644
--- a/lib/ssl/ssl.h
+++ b/lib/ssl/ssl.h
@@ -243,6 +243,28 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd);
*/
#define SSL_ENABLE_0RTT_DATA 33
+/* Sets a limit to the size of encrypted records (see
+ * draft-ietf-tls-record-limit). This is the value that is advertised to peers,
+ * not a limit on the size of records that will be created. Setting this value
+ * reduces the size of records that will be received (not sent).
+ *
+ * This limit applies to the plaintext, but the records that appear on the wire
+ * will be bigger. This doesn't include record headers, IVs, block cipher
+ * padding, and authentication tags or MACs.
+ *
+ * NSS always advertises the record size limit extension. If this option is not
+ * set, the extension will contain the maximum allowed size for the selected TLS
+ * version (currently this is 16384 or 2^14 for TLS 1.2 and lower and 16385 for
+ * TLS 1.3).
+ *
+ * By default, NSS creates records that are the maximum size possible, using all
+ * the data that was written by the application. Writes larger than the maximum
+ * are split into maximum sized records, and any remainder (unless
+ * SSL_CBC_RANDOM_IV is enabled and active). If a peer advertises a record size
+ * limit then that value is used instead.
+ */
+#define SSL_RECORD_SIZE_LIMIT 34
+
/* Enables TLS 1.3 compatibility mode. In this mode, the client includes a fake
* session ID in the handshake and sends a ChangeCipherSpec. A server will
* always use the setting chosen by the client, so the value of this option has
diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c
index dd280d2ef..466fc296f 100644
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -1476,6 +1476,13 @@ ssl3_SetupBothPendingCipherSpecs(sslSocket *ss)
goto loser;
}
+ if (ssl3_ExtensionNegotiated(ss, ssl_record_size_limit_xtn)) {
+ ss->ssl3.prSpec->recordSizeLimit = PR_MIN(MAX_FRAGMENT_LENGTH,
+ ss->opt.recordSizeLimit);
+ ss->ssl3.pwSpec->recordSizeLimit = PR_MIN(MAX_FRAGMENT_LENGTH,
+ ss->xtnData.recordSizeLimit);
+ }
+
ssl_ReleaseSpecWriteLock(ss); /*******************************/
return SECSuccess;
@@ -2272,7 +2279,7 @@ ssl_ProtectNextRecord(sslSocket *ss, ssl3CipherSpec *spec, SSL3ContentType type,
unsigned int spaceNeeded;
SECStatus rv;
- contentLen = PR_MIN(nIn, MAX_FRAGMENT_LENGTH);
+ contentLen = PR_MIN(nIn, spec->recordSizeLimit);
spaceNeeded = contentLen + SSL3_BUFFER_FUDGE;
if (spec->version >= SSL_LIBRARY_VERSION_TLS_1_1 &&
spec->cipherDef->type == type_block) {
@@ -12147,6 +12154,11 @@ ssl3_GetCipherSpec(sslSocket *ss, SSL3Ciphertext *cText)
return NULL;
}
+/* MAX_EXPANSION is the amount by which a record might plausibly be expanded
+ * when protected. It's the worst case estimate, so the sum of block cipher
+ * padding (up to 256 octets) and HMAC (48 octets for SHA-384). */
+#define MAX_EXPANSION (256 + 48)
+
/* if cText is non-null, then decipher and check the MAC of the
* SSL record from cText->buf (typically gs->inbuf)
* into databuf (typically gs->buf), and any previous contents of databuf
@@ -12176,6 +12188,7 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
PRBool isTLS;
DTLSEpoch epoch;
ssl3CipherSpec *spec = NULL;
+ PRUint16 recordSizeLimit;
PRBool outOfOrderSpec = PR_FALSE;
SSL3ContentType rType;
sslBuffer *plaintext = &ss->gs.buf;
@@ -12230,12 +12243,20 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
return SECFailure;
}
- if (plaintext->space < MAX_FRAGMENT_LENGTH) {
- rv = sslBuffer_Grow(plaintext, MAX_FRAGMENT_LENGTH + 2048);
+ recordSizeLimit = spec->recordSizeLimit;
+ if (cText->buf->len > recordSizeLimit + MAX_EXPANSION) {
+ ssl_ReleaseSpecReadLock(ss); /*****************************/
+ SSL3_SendAlert(ss, alert_fatal, record_overflow);
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+
+ if (plaintext->space < recordSizeLimit + MAX_EXPANSION) {
+ rv = sslBuffer_Grow(plaintext, recordSizeLimit + MAX_EXPANSION);
if (rv != SECSuccess) {
ssl_ReleaseSpecReadLock(ss); /*************************/
SSL_DBG(("%d: SSL3[%d]: HandleRecord, tried to get %d bytes",
- SSL_GETPID(), ss->fd, MAX_FRAGMENT_LENGTH + 2048));
+ SSL_GETPID(), ss->fd, recordSizeLimit + MAX_EXPANSION));
/* sslBuffer_Grow has set a memory error code. */
/* Perhaps we should send an alert. (but we have no memory!) */
return SECFailure;
@@ -12294,7 +12315,10 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
if (IS_DTLS(ss) ||
(ss->sec.isServer &&
ss->ssl3.hs.zeroRttIgnore == ssl_0rtt_ignore_trial)) {
- /* Silently drop the packet */
+ /* Silently drop the packet unless we sent a fatal alert. */
+ if (ss->ssl3.fatalAlertSent) {
+ return SECFailure;
+ }
return SECSuccess;
}
@@ -12330,7 +12354,7 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
}
/* Check the length of the plaintext. */
- if (isTLS && plaintext->len > MAX_FRAGMENT_LENGTH) {
+ if (isTLS && plaintext->len > recordSizeLimit) {
plaintext->len = 0;
SSL3_SendAlert(ss, alert_fatal, record_overflow);
PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c
index 782425e57..9b6c719f8 100644
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -50,6 +50,7 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = {
{ ssl_tls13_early_data_xtn, &tls13_ServerHandleEarlyDataXtn },
{ ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn },
{ ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn },
+ { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn },
{ 0, NULL }
};
@@ -68,6 +69,7 @@ static const ssl3ExtensionHandler serverHelloHandlersTLS[] = {
{ ssl_tls13_key_share_xtn, &tls13_ClientHandleKeyShareXtn },
{ ssl_tls13_pre_shared_key_xtn, &tls13_ClientHandlePreSharedKeyXtn },
{ ssl_tls13_early_data_xtn, &tls13_ClientHandleEarlyDataXtn },
+ { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn },
{ 0, NULL }
};
@@ -134,6 +136,7 @@ static const sslExtensionBuilder clientHelloSendersTLS[] =
{ ssl_signature_algorithms_xtn, &ssl3_SendSigAlgsXtn },
{ ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn },
{ ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn },
+ { ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn },
/* The pre_shared_key extension MUST be last. */
{ ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn },
{ 0, NULL }
diff --git a/lib/ssl/ssl3ext.h b/lib/ssl/ssl3ext.h
index d0f75a599..6d77c7459 100644
--- a/lib/ssl/ssl3ext.h
+++ b/lib/ssl/ssl3ext.h
@@ -98,6 +98,9 @@ struct TLSExtensionDataStr {
/* The application token contains a value that was passed to the client via
* a session ticket, or the cookie in a HelloRetryRequest. */
SECItem applicationToken;
+
+ /* The record size limit set by the peer. Our value is kept in ss->opt. */
+ PRUint16 recordSizeLimit;
};
typedef struct TLSExtensionStr {
diff --git a/lib/ssl/ssl3exthandle.c b/lib/ssl/ssl3exthandle.c
index d08e5e435..d1f286dc3 100644
--- a/lib/ssl/ssl3exthandle.c
+++ b/lib/ssl/ssl3exthandle.c
@@ -1868,3 +1868,67 @@ ssl_HandleSupportedGroupsXtn(const sslSocket *ss, TLSExtensionData *xtnData,
return SECSuccess;
}
+
+SECStatus
+ssl_HandleRecordSizeLimitXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ SECItem *data)
+{
+ SECStatus rv;
+ PRUint32 limit;
+ PRUint32 maxLimit = (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? (MAX_FRAGMENT_LENGTH + 1)
+ : MAX_FRAGMENT_LENGTH;
+
+ rv = ssl3_ExtConsumeHandshakeNumber(ss, &limit, 2, &data->data, &data->len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ if (data->len != 0 || limit < 64) {
+ ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ return SECFailure;
+ }
+
+ if (ss->sec.isServer) {
+ rv = ssl3_RegisterExtensionSender(ss, xtnData, ssl_record_size_limit_xtn,
+ &ssl_SendRecordSizeLimitXtn);
+ if (rv != SECSuccess) {
+ return SECFailure; /* error already set. */
+ }
+ } else if (limit > maxLimit) {
+ /* The client can sensibly check the maximum. */
+ ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ return SECFailure;
+ }
+
+ /* We can't enforce the maximum on a server. But we do need to ensure
+ * that we don't apply a limit that is too large. */
+ xtnData->recordSizeLimit = PR_MIN(maxLimit, limit);
+ xtnData->negotiated[xtnData->numNegotiated++] = ssl_record_size_limit_xtn;
+ return SECSuccess;
+}
+
+SECStatus
+ssl_SendRecordSizeLimitXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ sslBuffer *buf, PRBool *added)
+{
+ PRUint32 maxLimit;
+ if (ss->sec.isServer) {
+ maxLimit = (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? (MAX_FRAGMENT_LENGTH + 1)
+ : MAX_FRAGMENT_LENGTH;
+ } else {
+ maxLimit = (ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? (MAX_FRAGMENT_LENGTH + 1)
+ : MAX_FRAGMENT_LENGTH;
+ }
+ PRUint32 limit = PR_MIN(ss->opt.recordSizeLimit, maxLimit);
+ SECStatus rv = sslBuffer_AppendNumber(buf, limit, 2);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ *added = PR_TRUE;
+ return SECSuccess;
+}
diff --git a/lib/ssl/ssl3exthandle.h b/lib/ssl/ssl3exthandle.h
index b84bd074c..eaf7f0081 100644
--- a/lib/ssl/ssl3exthandle.h
+++ b/lib/ssl/ssl3exthandle.h
@@ -119,4 +119,11 @@ SECStatus ssl_SendSupportedGroupsXtn(const sslSocket *ss,
SECStatus ssl3_SendSupportedPointFormatsXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
sslBuffer *buf, PRBool *added);
+SECStatus ssl_HandleRecordSizeLimitXtn(const sslSocket *ss,
+ TLSExtensionData *xtnData,
+ SECItem *data);
+SECStatus ssl_SendRecordSizeLimitXtn(const sslSocket *ss,
+ TLSExtensionData *xtnData,
+ sslBuffer *buf, PRBool *added);
+
#endif
diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h
index b358546be..a2209e90a 100644
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -236,6 +236,7 @@ typedef struct sslOptionsStr {
/* If SSL_SetNextProtoNego has been called, then this contains the
* list of supported protocols. */
SECItem nextProtoNego;
+ PRUint16 recordSizeLimit;
PRUint32 maxEarlyDataSize;
unsigned int useSecurity : 1;
diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c
index 286b35e7d..33595ffae 100644
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -55,6 +55,7 @@ static const sslSocketOps ssl_secure_ops = { /* SSL. */
static sslOptions ssl_defaults = {
.nextProtoNego = { siBuffer, NULL, 0 },
.maxEarlyDataSize = 1 << 16,
+ .recordSizeLimit = MAX_FRAGMENT_LENGTH + 1,
.useSecurity = PR_TRUE,
.useSocks = PR_FALSE,
.requestCertificate = PR_FALSE,
@@ -803,6 +804,15 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRIntn val)
ss->opt.enable0RttData = val;
break;
+ case SSL_RECORD_SIZE_LIMIT:
+ if (val < 64 || val > (MAX_FRAGMENT_LENGTH + 1)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ } else {
+ ss->opt.recordSizeLimit = val;
+ }
+ break;
+
case SSL_ENABLE_TLS13_COMPAT_MODE:
ss->opt.enableTls13CompatMode = val;
break;
@@ -944,6 +954,9 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRIntn *pVal)
case SSL_ENABLE_0RTT_DATA:
val = ss->opt.enable0RttData;
break;
+ case SSL_RECORD_SIZE_LIMIT:
+ val = ss->opt.recordSizeLimit;
+ break;
case SSL_ENABLE_TLS13_COMPAT_MODE:
val = ss->opt.enableTls13CompatMode;
break;
@@ -1067,6 +1080,9 @@ SSL_OptionGetDefault(PRInt32 which, PRIntn *pVal)
case SSL_ENABLE_0RTT_DATA:
val = ssl_defaults.enable0RttData;
break;
+ case SSL_RECORD_SIZE_LIMIT:
+ val = ssl_defaults.recordSizeLimit;
+ break;
case SSL_ENABLE_TLS13_COMPAT_MODE:
val = ssl_defaults.enableTls13CompatMode;
break;
@@ -1252,6 +1268,14 @@ SSL_OptionSetDefault(PRInt32 which, PRIntn val)
ssl_defaults.enable0RttData = val;
break;
+ case SSL_RECORD_SIZE_LIMIT:
+ if (val < 64 || val > (MAX_FRAGMENT_LENGTH + 1)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ssl_defaults.recordSizeLimit = val;
+ break;
+
case SSL_ENABLE_TLS13_COMPAT_MODE:
ssl_defaults.enableTls13CompatMode = val;
break;
diff --git a/lib/ssl/sslspec.c b/lib/ssl/sslspec.c
index 26c3eb546..7833eeab6 100644
--- a/lib/ssl/sslspec.c
+++ b/lib/ssl/sslspec.c
@@ -143,6 +143,7 @@ ssl_CreateCipherSpec(sslSocket *ss, CipherSpecDirection direction)
spec->refCt = 1;
spec->version = ss->version;
spec->direction = direction;
+ spec->recordSizeLimit = MAX_FRAGMENT_LENGTH;
SSL_TRC(10, ("%d: SSL[%d]: new %s spec %d ct=%d",
SSL_GETPID(), ss->fd, SPEC_DIR(spec), spec,
spec->refCt));
diff --git a/lib/ssl/sslspec.h b/lib/ssl/sslspec.h
index 207bd6ef6..b25601755 100644
--- a/lib/ssl/sslspec.h
+++ b/lib/ssl/sslspec.h
@@ -170,6 +170,10 @@ struct ssl3CipherSpecStr {
/* The number of 0-RTT bytes that can be sent or received in TLS 1.3. This
* will be zero for everything but 0-RTT. */
PRUint32 earlyDataRemaining;
+ /* The maximum plaintext length. This differs from the configured or
+ * negotiated value for TLS 1.3; it is reduced by one to account for the
+ * content type octet. */
+ PRUint16 recordSizeLimit;
};
typedef void (*sslCipherSpecChangedFunc)(void *arg,
diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h
index e2b80fb43..bb1bec7a3 100644
--- a/lib/ssl/sslt.h
+++ b/lib/ssl/sslt.h
@@ -432,6 +432,7 @@ typedef enum {
ssl_signed_cert_timestamp_xtn = 18,
ssl_padding_xtn = 21,
ssl_extended_master_secret_xtn = 23,
+ ssl_record_size_limit_xtn = 28,
ssl_session_ticket_xtn = 35,
/* 40 was used in draft versions of TLS 1.3; it is now reserved. */
ssl_tls13_pre_shared_key_xtn = 41,
@@ -454,7 +455,7 @@ typedef enum {
/* SSL_MAX_EXTENSIONS includes the maximum number of extensions that are
* supported for any single message type. That is, a ClientHello; ServerHello
* and TLS 1.3 NewSessionTicket and HelloRetryRequest extensions have fewer. */
-#define SSL_MAX_EXTENSIONS 20
+#define SSL_MAX_EXTENSIONS 21
/* Deprecated */
typedef enum {
diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c
index 3592f30f7..4d9170fb0 100644
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -3254,6 +3254,17 @@ tls13_SetupPendingCipherSpec(sslSocket *ss, ssl3CipherSpec *spec)
}
tls13_SetSpecRecordVersion(ss, spec);
+
+ /* The record size limit is reduced by one so that the remainder of the
+ * record handling code can use the same checks for all versions. */
+ if (ssl3_ExtensionNegotiated(ss, ssl_record_size_limit_xtn)) {
+ spec->recordSizeLimit = ((spec->direction == CipherSpecRead)
+ ? ss->opt.recordSizeLimit
+ : ss->xtnData.recordSizeLimit) -
+ 1;
+ } else {
+ spec->recordSizeLimit = MAX_FRAGMENT_LENGTH;
+ }
return SECSuccess;
}
@@ -4746,7 +4757,8 @@ static const struct {
{ ssl_tls13_cookie_xtn, _M2(client_hello, hello_retry_request) },
{ ssl_tls13_certificate_authorities_xtn, _M1(certificate_request) },
{ ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello,
- hello_retry_request) }
+ hello_retry_request) },
+ { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }
};
tls13ExtensionStatus
@@ -5015,6 +5027,16 @@ tls13_UnprotectRecord(sslSocket *ss,
return SECFailure;
}
+ /* There is a similar test in ssl3_HandleRecord, but this test is needed to
+ * account for padding. It's safe to do this here (including the alert),
+ * because it only confirms that the record exceeded the size limit, which
+ * is apparent from the size of the ciphertext. */
+ if (plaintext->len > spec->recordSizeLimit + 1) {
+ SSL3_SendAlert(ss, alert_fatal, record_overflow);
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+
/* The record is right-padded with 0s, followed by the true
* content type, so read from the right until we receive a
* nonzero byte. */