Process (and ignore) DTLS 1.3 ACK messages.
Bug: 42290594
Change-Id: Icbff180fbfecfc704b2ea487b9de345f3c214d71
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/72147
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl3.h b/include/openssl/ssl3.h
index 221adf0..4a391cb 100644
--- a/include/openssl/ssl3.h
+++ b/include/openssl/ssl3.h
@@ -266,6 +266,7 @@
#define SSL3_RT_ALERT 21
#define SSL3_RT_HANDSHAKE 22
#define SSL3_RT_APPLICATION_DATA 23
+#define SSL3_RT_ACK 26
// Pseudo content type for SSL/TLS header info
#define SSL3_RT_HEADER 0x100
diff --git a/ssl/d1_both.cc b/ssl/d1_both.cc
index f9f3a90..f8ac34a 100644
--- a/ssl/d1_both.cc
+++ b/ssl/d1_both.cc
@@ -404,6 +404,9 @@
record);
return ssl_open_record_success;
+ case SSL3_RT_ACK:
+ return dtls1_process_ack(ssl, out_alert);
+
case SSL3_RT_HANDSHAKE:
// Break out to main processing.
break;
diff --git a/ssl/d1_pkt.cc b/ssl/d1_pkt.cc
index 1dc5775..e768e03 100644
--- a/ssl/d1_pkt.cc
+++ b/ssl/d1_pkt.cc
@@ -127,6 +127,22 @@
BSSL_NAMESPACE_BEGIN
+ssl_open_record_t dtls1_process_ack(SSL *ssl, uint8_t *out_alert) {
+ // ACKs are only allowed in DTLS 1.3. Reject them if we've negotiated a
+ // version and it's not 1.3. (It's theoretically possible to receive an ACK
+ // before version negotiation, e.g. due to packet loss or a server ACKing a
+ // ClientHello prior to sending the ServerHello, so if we don't have a version
+ // we'll accept the ACK.)
+ if (ssl->s3->version != 0 && ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
+ *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+ return ssl_open_record_error;
+ }
+ // TODO(crbug.com/42290594): Implement proper support for ACKs. Currently,
+ // this just drops the ACK on the floor.
+ return ssl_open_record_discard;
+}
+
ssl_open_record_t dtls1_open_app_data(SSL *ssl, Span<uint8_t> *out,
size_t *out_consumed, uint8_t *out_alert,
Span<uint8_t> in) {
@@ -180,6 +196,10 @@
// renegotiation attempt. Fall through to the error path.
}
+ if (type == SSL3_RT_ACK) {
+ return dtls1_process_ack(ssl, out_alert);
+ }
+
if (type != SSL3_RT_APPLICATION_DATA) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
*out_alert = SSL_AD_UNEXPECTED_MESSAGE;
diff --git a/ssl/internal.h b/ssl/internal.h
index 65ed013..c4a36ac 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -3688,6 +3688,7 @@
// on success and false on allocation failure.
bool ssl_hash_message(SSL_HANDSHAKE *hs, const SSLMessage &msg);
+ssl_open_record_t dtls1_process_ack(SSL *ssl, uint8_t *out_alert);
ssl_open_record_t dtls1_open_app_data(SSL *ssl, Span<uint8_t> *out,
size_t *out_consumed, uint8_t *out_alert,
Span<uint8_t> in);
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 2074335..d308db9 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -70,6 +70,7 @@
recordTypeHandshake recordType = 22
recordTypeApplicationData recordType = 23
recordTypePlaintextHandshake recordType = 24
+ recordTypeACK recordType = 26
)
// TLS handshake message types.
@@ -2011,6 +2012,10 @@
// the DTLS 1.3 record header.
DTLS13RecordHeaderSetCIDBit bool
+ // ACKEveryRecord sends an ACK record immediately on response to each
+ // handshake record received.
+ ACKEveryRecord bool
+
// EncryptSessionTicketKey, if non-nil, is the ticket key to use when
// encrypting tickets.
EncryptSessionTicketKey *[32]byte
diff --git a/ssl/test/runner/dtls.go b/ssl/test/runner/dtls.go
index 63bc864..e0b8604 100644
--- a/ssl/test/runner/dtls.go
+++ b/ssl/test/runner/dtls.go
@@ -22,6 +22,8 @@
"math/rand"
"net"
"slices"
+
+ "golang.org/x/crypto/cryptobyte"
)
func (c *Conn) readDTLS13RecordHeader(b []byte) (headerLen int, recordLen int, recTyp recordType, seq []byte, err error) {
@@ -147,6 +149,18 @@
return recordHeaderLen, recordLen, typ, b[3:11], nil
}
+func (c *Conn) writeACKs(seqnums []uint64) {
+ recordNumbers := new(cryptobyte.Builder)
+ epoch := binary.BigEndian.Uint16(c.in.seq[:2])
+ recordNumbers.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
+ for _, seq := range seqnums {
+ b.AddUint64(uint64(epoch))
+ b.AddUint64(seq)
+ }
+ })
+ c.writeRecord(recordTypeACK, recordNumbers.BytesOrPanic())
+}
+
func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, []byte, error) {
// Read a new packet only if the current one is empty.
var newPacket bool
@@ -184,6 +198,9 @@
// test.
return 0, nil, c.in.setErrorLocked(c.sendAlert(alertValue))
}
+ if c.config.Bugs.ACKEveryRecord {
+ c.writeACKs([]uint64{binary.BigEndian.Uint64(seq)})
+ }
if typ == 0 {
// readDTLSRecordHeader sets typ=0 when decoding the DTLS 1.3
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index ecfc61e..c0d4276 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -11515,6 +11515,30 @@
},
resumeSession: true,
})
+
+ // DTLS 1.3 ACK/retransmit tests
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS13-ImmediateACKs",
+ config: Config{
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ACKEveryRecord: true,
+ },
+ },
+ })
+ testCases = append(testCases, testCase{
+ protocol: dtls,
+ name: "DTLS12-RejectACKs",
+ config: Config{
+ MaxVersion: VersionTLS12,
+ Bugs: ProtocolBugs{
+ ACKEveryRecord: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_RECORD:",
+ })
}
func addExportKeyingMaterialTests() {