Add server-side support for Signed Certificate Timestamps. Change-Id: Ifa44fef160fc9d67771eed165f8fc277f28a0222 Reviewed-on: https://boringssl-review.googlesource.com/5840 Reviewed-by: David Benjamin <davidben@chromium.org> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 4e2a64d..7109214 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -624,6 +624,18 @@ OPENSSL_EXPORT int SSL_get0_chain_certs(const SSL *ssl, STACK_OF(X509) **out_chain); +/* SSL_CTX_set_signed_cert_timestamp_list sets the list of signed certificate + * timestamps that is sent to clients that request it. The |list| argument must + * contain one or more SCT structures serialised as a SignedCertificateTimestamp + * List (see https://tools.ietf.org/html/rfc6962#section-3.3) – i.e. each SCT + * is prefixed by a big-endian, uint16 length and the concatenation of one or + * more such prefixed SCTs are themselves also prefixed by a uint16 length. It + * returns one on success and zero on error. The caller retains ownership of + * |list|. */ +OPENSSL_EXPORT int SSL_CTX_set_signed_cert_timestamp_list(SSL_CTX *ctx, + const uint8_t *list, + size_t list_len); + /* SSL_CTX_set_ocsp_response sets the OCSP reponse that is sent to clients * which request it. It returns one on success and zero on error. The caller * retains ownership of |response|. */ @@ -1472,6 +1484,10 @@ /* If true, a client will request certificate timestamps. */ char signed_cert_timestamps_enabled; + /* Signed certificate timestamp list to be sent to the client, if requested */ + uint8_t *signed_cert_timestamp_list; + size_t signed_cert_timestamp_list_length; + /* If true, a client will request a stapled OCSP response. */ char ocsp_stapling_enabled;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 373dffb..574ae7b 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c
@@ -1385,6 +1385,20 @@ *out_len = session->ocsp_response_length; } +int SSL_CTX_set_signed_cert_timestamp_list(SSL_CTX *ctx, const uint8_t *list, + size_t list_len) { + OPENSSL_free(ctx->signed_cert_timestamp_list); + ctx->signed_cert_timestamp_list_length = 0; + + ctx->signed_cert_timestamp_list = BUF_memdup(list, list_len); + if (ctx->signed_cert_timestamp_list == NULL) { + return 0; + } + ctx->signed_cert_timestamp_list_length = list_len; + + return 1; +} + int SSL_CTX_set_ocsp_response(SSL_CTX *ctx, const uint8_t *response, size_t response_len) { OPENSSL_free(ctx->ocsp_response); @@ -1739,6 +1753,7 @@ OPENSSL_free(ctx->tlsext_ellipticcurvelist); OPENSSL_free(ctx->alpn_client_proto_list); OPENSSL_free(ctx->ocsp_response); + OPENSSL_free(ctx->signed_cert_timestamp_list); EVP_PKEY_free(ctx->tlsext_channel_id_private); BIO_free(ctx->keylog_bio);
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c index 40b9752..36793f7 100644 --- a/ssl/t1_lib.c +++ b/ssl/t1_lib.c
@@ -1545,13 +1545,20 @@ static int ext_sct_parse_clienthello(SSL *ssl, uint8_t *out_alert, CBS *contents) { - /* The SCT extension is not supported as a server. */ - return 1; + return contents == NULL || CBS_len(contents) == 0; } static int ext_sct_add_serverhello(SSL *ssl, CBB *out) { - /* The SCT extension is not supported as a server. */ - return 1; + if (ssl->ctx->signed_cert_timestamp_list_length == 0) { + return 1; + } + + CBB contents; + return CBB_add_u16(out, TLSEXT_TYPE_certificate_timestamp) && + CBB_add_u16_length_prefixed(out, &contents) && + CBB_add_bytes(&contents, ssl->ctx->signed_cert_timestamp_list, + ssl->ctx->signed_cert_timestamp_list_length) && + CBB_flush(out); }
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index 9b7fcd6..c30eb55 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -700,6 +700,13 @@ SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), VerifySucceed, NULL); } + if (!config->signed_cert_timestamps.empty() && + !SSL_CTX_set_signed_cert_timestamp_list( + ssl_ctx.get(), (const uint8_t *)config->signed_cert_timestamps.data(), + config->signed_cert_timestamps.size())) { + return nullptr; + } + return ssl_ctx; }
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 273a75c..0b8bfe5 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go
@@ -190,6 +190,7 @@ ChannelID *ecdsa.PublicKey // the channel ID for this connection SRTPProtectionProfile uint16 // the negotiated DTLS-SRTP protection profile TLSUnique []byte + SCTList []byte // signed certificate timestamp list } // ClientAuthType declares the policy the server will follow for
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index 1b8700c..b23a104 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go
@@ -40,6 +40,7 @@ extendedMasterSecret bool // whether this session used an extended master secret cipherSuite *cipherSuite ocspResponse []byte // stapled OCSP response + sctList []byte // signed certificate timestamp list peerCertificates []*x509.Certificate // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. @@ -1343,6 +1344,7 @@ state.ChannelID = c.channelID state.SRTPProtectionProfile = c.srtpProtectionProfile state.TLSUnique = c.firstFinished[:] + state.SCTList = c.sctList } return state
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index c38334e..b96ced7 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -61,6 +61,7 @@ compressionMethods: []uint8{compressionNone}, random: make([]byte, 32), ocspStapling: true, + sctListSupported: true, serverName: c.config.ServerName, supportedCurves: c.config.curvePreferences(), supportedPoints: []uint8{pointFormatUncompressed}, @@ -371,6 +372,11 @@ copy(c.clientRandom[:], hs.hello.random) copy(c.serverRandom[:], hs.serverHello.random) copy(c.masterSecret[:], hs.masterSecret) + + if len(hs.serverHello.sctList) > 0 { + c.sctList = hs.serverHello.sctList + } + return nil }
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index 7926533..da85e7a 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go
@@ -989,14 +989,7 @@ } m.srtpMasterKeyIdentifier = string(d[1:]) case extensionSignedCertificateTimestamp: - if length < 2 { - return false - } - l := int(data[0])<<8 | int(data[1]) - if l != len(data)-2 { - return false - } - m.sctList = data[2:length] + m.sctList = data[:length] case extensionCustom: m.customExtension = string(data[:length]) }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 3c077bf..5259dae 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -157,6 +157,8 @@ expectedSRTPProtectionProfile uint16 // expectedOCSPResponse, if not nil, is the expected OCSP response to be received. expectedOCSPResponse []uint8 + // expectedSCTList, if not nil, is the expected SCT list to be received. + expectedSCTList []uint8 // messageLen is the length, in bytes, of the test message that will be // sent. messageLen int @@ -334,6 +336,10 @@ return fmt.Errorf("OCSP Response mismatch") } + if test.expectedSCTList != nil && !bytes.Equal(test.expectedSCTList, connState.SCTList) { + return fmt.Errorf("SCT list mismatch") + } + if test.exportKeyingMaterial > 0 { actual := make([]byte, test.exportKeyingMaterial) if _, err := io.ReadFull(tlsConn, actual); err != nil { @@ -3428,7 +3434,8 @@ }) // Test SCT list. testCases = append(testCases, testCase{ - name: "SignedCertificateTimestampList", + name: "SignedCertificateTimestampList-Client", + testType: clientTest, flags: []string{ "-enable-signed-cert-timestamps", "-expect-signed-cert-timestamps", @@ -3436,6 +3443,15 @@ }, }) testCases = append(testCases, testCase{ + name: "SignedCertificateTimestampList-Server", + testType: serverTest, + flags: []string{ + "-signed-cert-timestamps", + base64.StdEncoding.EncodeToString(testSCTList), + }, + expectedSCTList: testSCTList, + }) + testCases = append(testCases, testCase{ testType: clientTest, name: "ClientHelloPadding", config: Config{
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index ad14ed3..fe6dc93 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc
@@ -128,6 +128,7 @@ { "-expect-signed-cert-timestamps", &TestConfig::expected_signed_cert_timestamps }, { "-ocsp-response", &TestConfig::ocsp_response }, + { "-signed-cert-timestamps", &TestConfig::signed_cert_timestamps }, }; const Flag<int> kIntFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 5776095..76ee312 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h
@@ -95,6 +95,7 @@ bool verify_fail = false; bool verify_peer = false; bool expect_verify_result = false; + std::string signed_cert_timestamps; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config);