Add tests for SSL_export_keying_material. Change-Id: Ic4d3ade08aa648ce70ada9981e894b6c1c4197c6 Reviewed-on: https://boringssl-review.googlesource.com/4215 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index b21b521..c7e77f8 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c
@@ -1709,7 +1709,7 @@ const char *label, size_t llen, const uint8_t *p, size_t plen, int use_context) { if (s->version < TLS1_VERSION) { - return -1; + return 0; } return s->enc_method->export_keying_material(s, out, olen, label, llen, p,
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index eca7b6b..751a80f 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -40,6 +40,7 @@ #include <openssl/ssl.h> #include <memory> +#include <vector> #include "../../crypto/test/scoped_types.h" #include "async_bio.h" @@ -471,7 +472,7 @@ memset(&test_state->clock_delta, 0, sizeof(test_state->clock_delta)); if (DTLSv1_handle_timeout(ssl) < 0) { - printf("Error retransmitting.\n"); + fprintf(stderr, "Error retransmitting.\n"); return false; } return true; @@ -850,6 +851,22 @@ } } + if (config->export_keying_material > 0) { + std::vector<uint8_t> result( + static_cast<size_t>(config->export_keying_material)); + if (!SSL_export_keying_material( + ssl.get(), result.data(), result.size(), + config->export_label.data(), config->export_label.size(), + reinterpret_cast<const uint8_t*>(config->export_context.data()), + config->export_context.size(), config->use_export_context)) { + fprintf(stderr, "failed to export keying material\n"); + return false; + } + if (WriteAll(ssl.get(), result.data(), result.size()) < 0) { + return false; + } + } + if (config->write_different_record_sizes) { if (config->is_dtls) { fprintf(stderr, "write_different_record_sizes not supported for DTLS\n"); @@ -963,21 +980,21 @@ ScopedSSL_CTX ssl_ctx = SetupCtx(&config); if (!ssl_ctx) { - BIO_print_errors_fp(stdout); + BIO_print_errors_fp(stderr); return 1; } ScopedSSL_SESSION session; if (!DoExchange(&session, ssl_ctx.get(), &config, false /* is_resume */, NULL /* session */)) { - BIO_print_errors_fp(stdout); + BIO_print_errors_fp(stderr); return 1; } if (config.resume && !DoExchange(NULL, ssl_ctx.get(), &config, true /* is_resume */, session.get())) { - BIO_print_errors_fp(stdout); + BIO_print_errors_fp(stderr); return 1; }
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index 90cf01f..0fe34b7 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go
@@ -37,14 +37,16 @@ handshakeComplete bool didResume bool // whether this connection was a session resumption extendedMasterSecret bool // whether this session used an extended master secret - cipherSuite uint16 + cipherSuite *cipherSuite ocspResponse []byte // stapled OCSP response peerCertificates []*x509.Certificate // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate // serverName contains the server name indicated by the client, if any. - serverName string + serverName string + clientRandom, serverRandom [32]byte + masterSecret [48]byte clientProtocol string clientProtocolFallback bool @@ -1276,7 +1278,7 @@ state.DidResume = c.didResume state.NegotiatedProtocolIsMutual = !c.clientProtocolFallback state.NegotiatedProtocolFromALPN = c.usedALPN - state.CipherSuite = c.cipherSuite + state.CipherSuite = c.cipherSuite.id state.PeerCertificates = c.peerCertificates state.VerifiedChains = c.verifiedChains state.ServerName = c.serverName @@ -1310,3 +1312,28 @@ } return c.peerCertificates[0].VerifyHostname(host) } + +// ExportKeyingMaterial exports keying material from the current connection +// state, as per RFC 5705. +func (c *Conn) ExportKeyingMaterial(length int, label, context []byte, useContext bool) ([]byte, error) { + c.handshakeMutex.Lock() + defer c.handshakeMutex.Unlock() + if !c.handshakeComplete { + return nil, errors.New("tls: handshake has not yet been performed") + } + + seedLen := len(c.clientRandom) + len(c.serverRandom) + if useContext { + seedLen += 2 + len(context) + } + seed := make([]byte, 0, seedLen) + seed = append(seed, c.clientRandom[:]...) + seed = append(seed, c.serverRandom[:]...) + if useContext { + seed = append(seed, byte(len(context)>>8), byte(len(context))) + seed = append(seed, context...) + } + result := make([]byte, length) + prfForVersion(c.vers, c.cipherSuite)(result, c.masterSecret[:], label, seed) + return result, nil +}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index f1e71b2..d7bec39 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -129,14 +129,14 @@ return errors.New("tls: short read from Rand: " + err.Error()) } - if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes && (c.cipherSuite == 0 || !c.config.Bugs.NoSignatureAlgorithmsOnRenego) { + if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes && (c.cipherSuite == nil || !c.config.Bugs.NoSignatureAlgorithmsOnRenego) { hello.signatureAndHashes = c.config.signatureAndHashesForClient() } var session *ClientSessionState var cacheKey string sessionCache := c.config.ClientSessionCache - if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != 0 { + if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != nil { sessionCache = nil } @@ -351,7 +351,10 @@ c.didResume = isResume c.handshakeComplete = true - c.cipherSuite = suite.id + c.cipherSuite = suite + copy(c.clientRandom[:], hs.hello.random) + copy(c.serverRandom[:], hs.serverHello.random) + copy(c.masterSecret[:], hs.masterSecret) return nil }
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index cf9d1ca..77fd0a5 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -113,6 +113,9 @@ } } c.handshakeComplete = true + copy(c.clientRandom[:], hs.clientHello.random) + copy(c.serverRandom[:], hs.hello.random) + copy(c.masterSecret[:], hs.masterSecret) return nil } @@ -376,7 +379,7 @@ func (hs *serverHandshakeState) checkForResumption() bool { c := hs.c - if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != 0 { + if c.config.Bugs.NeverResumeOnRenego && c.cipherSuite != nil { return false } @@ -880,7 +883,7 @@ c.dtlsFlushHandshake() } - c.cipherSuite = hs.suite.id + c.cipherSuite = hs.suite return nil }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index b328c15..f14833b 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -184,6 +184,12 @@ // damageFirstWrite, if true, configures the underlying transport to // damage the final byte of the first application data write. damageFirstWrite bool + // exportKeyingMaterial, if non-zero, configures the test to exchange + // keying material and verify they match. + exportKeyingMaterial int + exportLabel string + exportContext string + useExportContext bool // flags, if not empty, contains a list of command-line flags that will // be passed to the shim program. flags []string @@ -1117,6 +1123,20 @@ return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, test.expectedSRTPProtectionProfile) } + if test.exportKeyingMaterial > 0 { + actual := make([]byte, test.exportKeyingMaterial) + if _, err := io.ReadFull(tlsConn, actual); err != nil { + return err + } + expected, err := tlsConn.ExportKeyingMaterial(test.exportKeyingMaterial, []byte(test.exportLabel), []byte(test.exportContext), test.useExportContext) + if err != nil { + return err + } + if !bytes.Equal(actual, expected) { + return fmt.Errorf("keying material mismatch") + } + } + if test.shimWritesFirst { var buf [5]byte _, err := io.ReadFull(tlsConn, buf[:]) @@ -1293,6 +1313,15 @@ flags = append(flags, "-shim-writes-first") } + if test.exportKeyingMaterial > 0 { + flags = append(flags, "-export-keying-material", strconv.Itoa(test.exportKeyingMaterial)) + flags = append(flags, "-export-label", test.exportLabel) + flags = append(flags, "-export-context", test.exportContext) + if test.useExportContext { + flags = append(flags, "-use-export-context") + } + } + flags = append(flags, test.flags...) var shim *exec.Cmd @@ -1384,7 +1413,7 @@ stdout := string(stdoutBuf.Bytes()) stderr := string(stderrBuf.Bytes()) failed := err != nil || childErr != nil - correctFailure := len(test.expectedError) == 0 || strings.Contains(stdout, test.expectedError) + correctFailure := len(test.expectedError) == 0 || strings.Contains(stderr, test.expectedError) localError := "none" if err != nil { localError = err.Error() @@ -1411,10 +1440,10 @@ panic("internal error") } - return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s", msg, localError, childError, string(stdoutBuf.Bytes()), stderr) + return fmt.Errorf("%s: local error '%s', child error '%s', stdout:\n%s\nstderr:\n%s", msg, localError, childError, stdout, stderr) } - if !*useValgrind && len(stderr) > 0 { + if !*useValgrind && !failed && len(stderr) > 0 { println(stderr) } @@ -3181,6 +3210,61 @@ }) } +func addExportKeyingMaterialTests() { + for _, vers := range tlsVersions { + if vers.version == VersionSSL30 { + continue + } + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1024, + exportLabel: "label", + exportContext: "context", + useExportContext: true, + }) + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-NoContext-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1024, + }) + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-EmptyContext-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1024, + useExportContext: true, + }) + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-Small-" + vers.name, + config: Config{ + MaxVersion: vers.version, + }, + exportKeyingMaterial: 1, + exportLabel: "label", + exportContext: "context", + useExportContext: true, + }) + } + testCases = append(testCases, testCase{ + name: "ExportKeyingMaterial-SSL3", + config: Config{ + MaxVersion: VersionSSL30, + }, + exportKeyingMaterial: 1024, + exportLabel: "label", + exportContext: "context", + useExportContext: true, + shouldFail: true, + expectedError: "failed to export keying material", + }) +} + func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) { defer wg.Done() @@ -3278,6 +3362,7 @@ addSigningHashTests() addFastRadioPaddingTests() addDTLSRetransmitTests() + addExportKeyingMaterialTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { for _, protocol := range []protocol{tls, dtls} {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 253c6a1..2527837 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc
@@ -79,6 +79,7 @@ { "-fail-ddos-callback", &TestConfig::fail_ddos_callback }, { "-fail-second-ddos-callback", &TestConfig::fail_second_ddos_callback }, { "-handshake-never-done", &TestConfig::handshake_never_done }, + { "-use-export-context", &TestConfig::use_export_context }, }; const Flag<std::string> kStringFlags[] = { @@ -98,6 +99,8 @@ { "-psk-identity", &TestConfig::psk_identity }, { "-srtp-profiles", &TestConfig::srtp_profiles }, { "-cipher", &TestConfig::cipher }, + { "-export-label", &TestConfig::export_label }, + { "-export-context", &TestConfig::export_context }, }; const Flag<std::string> kBase64Flags[] = { @@ -113,6 +116,7 @@ { "-min-version", &TestConfig::min_version }, { "-max-version", &TestConfig::max_version }, { "-mtu", &TestConfig::mtu }, + { "-export-keying-material", &TestConfig::export_keying_material }, }; } // namespace
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 478a1e7..de1eda6 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h
@@ -73,6 +73,10 @@ bool fail_second_ddos_callback = false; std::string cipher; bool handshake_never_done = false; + int export_keying_material = 0; + std::string export_label; + std::string export_context; + bool use_export_context = false; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config);