Build up TLS 1.3 record-layer tests.

This also adds a missing check to the C half to ensure fake record types are
always correct, to keep implementations honest.

Change-Id: I1d65272e647ffa67018c721d52c639f8ba47d647
Reviewed-on: https://boringssl-review.googlesource.com/8510
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index dfe4ac8..64249ad 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -58,6 +58,7 @@
 SSL,157,INAPPROPRIATE_FALLBACK
 SSL,158,INVALID_COMMAND
 SSL,159,INVALID_MESSAGE
+SSL,251,INVALID_OUTER_RECORD_TYPE
 SSL,160,INVALID_SSL_SESSION
 SSL,161,INVALID_TICKET_KEYS_LENGTH
 SSL,162,LENGTH_MISMATCH
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 2b42093..d9cae0c 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -4611,6 +4611,7 @@
 #define SSL_R_X509_LIB 248
 #define SSL_R_X509_VERIFICATION_SETUP_PROBLEMS 249
 #define SSL_R_SHUTDOWN_WHILE_IN_INIT 250
+#define SSL_R_INVALID_OUTER_RECORD_TYPE 251
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 2a5565b..c9593ff 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -853,6 +853,19 @@
 	// CECPQ1BadNewhopePart corrupts the Newhope part of a CECPQ1 key exchange,
 	// as a trivial proof that it is actually used.
 	CECPQ1BadNewhopePart bool
+
+	// RecordPadding is the number of bytes of padding to add to each
+	// encrypted record in TLS 1.3.
+	RecordPadding int
+
+	// OmitRecordContents, if true, causes encrypted records in TLS 1.3 to
+	// be missing their body and content type. Padding, if configured, is
+	// still added.
+	OmitRecordContents bool
+
+	// OuterRecordType, if non-zero, is the outer record type to use instead
+	// of application data.
+	OuterRecordType recordType
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 3b4d0b7..e8f6857 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -523,17 +523,24 @@
 		case cipher.Stream:
 			c.XORKeyStream(payload, payload)
 		case *tlsAead:
-			contentTypeLen := 0
-			if hc.version >= VersionTLS13 {
-				contentTypeLen = 1
-			}
 			payloadLen := len(b.data) - recordHeaderLen - explicitIVLen
-			b.resize(len(b.data) + contentTypeLen + c.Overhead())
+			paddingLen := 0
 			if hc.version >= VersionTLS13 {
-				b.data[payloadLen+recordHeaderLen] = byte(typ)
-				payloadLen += 1
-				// TODO(nharper): Add ProtocolBugs to add
-				// padding.
+				payloadLen++
+				paddingLen = hc.config.Bugs.RecordPadding
+			}
+			if hc.config.Bugs.OmitRecordContents {
+				payloadLen = 0
+			}
+			b.resize(recordHeaderLen + explicitIVLen + payloadLen + paddingLen + c.Overhead())
+			if hc.version >= VersionTLS13 {
+				if !hc.config.Bugs.OmitRecordContents {
+					b.data[payloadLen+recordHeaderLen-1] = byte(typ)
+				}
+				for i := 0; i < hc.config.Bugs.RecordPadding; i++ {
+					b.data[payloadLen+recordHeaderLen+i] = 0
+				}
+				payloadLen += paddingLen
 			}
 			nonce := hc.outSeq[:]
 			if c.explicitNonce {
@@ -762,8 +769,9 @@
 	b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n)
 	ok, off, encTyp, err := c.in.decrypt(b)
 	if c.vers >= VersionTLS13 && c.in.cipher != nil {
-		// TODO(nharper): Check that outer type (typ) is
-		// application data.
+		if typ != recordTypeApplicationData {
+			return 0, nil, c.in.setErrorLocked(fmt.Errorf("tls: outer record type is not application data"))
+		}
 		typ = encTyp
 	}
 	if !ok {
@@ -971,8 +979,10 @@
 		b.resize(recordHeaderLen + explicitIVLen + m)
 		b.data[0] = byte(typ)
 		if c.vers >= VersionTLS13 && c.out.cipher != nil {
-			// TODO(nharper): Add a ProtocolBugs to skip this.
 			b.data[0] = byte(recordTypeApplicationData)
+			if outerType := c.config.Bugs.OuterRecordType; outerType != 0 {
+				b.data[0] = byte(outerType)
+			}
 		}
 		vers := c.vers
 		if vers == 0 || vers >= VersionTLS13 {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 709cd8d..3b95b2c 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -5298,6 +5298,59 @@
 	})
 }
 
+func addTLS13RecordTests() {
+	testCases = append(testCases, testCase{
+		name: "TLS13-RecordPadding",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				RecordPadding: 10,
+			},
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		name: "TLS13-EmptyRecords",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				OmitRecordContents: true,
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+	})
+
+	testCases = append(testCases, testCase{
+		name: "TLS13-OnlyPadding",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				OmitRecordContents: true,
+				RecordPadding:      10,
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+	})
+
+	testCases = append(testCases, testCase{
+		name: "TLS13-WrongOuterRecord",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			MinVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				OuterRecordType: recordTypeHandshake,
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":INVALID_OUTER_RECORD_TYPE:",
+	})
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -5398,6 +5451,7 @@
 	addCurveTests()
 	addCECPQ1Tests()
 	addKeyExchangeInfoTests()
+	addTLS13RecordTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
 			for _, protocol := range []protocol{tls, dtls} {
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index f1e866f..e357ed98 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -255,6 +255,13 @@
   if (ssl->s3->have_version &&
       ssl3_protocol_version(ssl) >= TLS1_3_VERSION &&
       ssl->s3->aead_read_ctx != NULL) {
+    /* The outer record type is always application_data. */
+    if (type != SSL3_RT_APPLICATION_DATA) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_OUTER_RECORD_TYPE);
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return ssl_open_record_error;
+    }
+
     do {
       if (!CBS_get_last_u8(out, &type)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);