diff options
Diffstat (limited to 'ssl/test')
-rw-r--r-- | ssl/test/bssl_shim.cc | 114 | ||||
-rw-r--r-- | ssl/test/runner/channel_id_key.pem | 5 | ||||
-rw-r--r-- | ssl/test/runner/cipher_suites.go | 46 | ||||
-rw-r--r-- | ssl/test/runner/common.go | 84 | ||||
-rw-r--r-- | ssl/test/runner/conn.go | 10 | ||||
-rw-r--r-- | ssl/test/runner/handshake_client.go | 116 | ||||
-rw-r--r-- | ssl/test/runner/handshake_messages.go | 195 | ||||
-rw-r--r-- | ssl/test/runner/handshake_server.go | 119 | ||||
-rw-r--r-- | ssl/test/runner/key_agreement.go | 2 | ||||
-rw-r--r-- | ssl/test/runner/prf.go | 66 | ||||
-rw-r--r-- | ssl/test/runner/runner.go | 587 | ||||
-rw-r--r-- | ssl/test/runner/ticket.go | 33 | ||||
-rw-r--r-- | ssl/test/test_config.cc | 54 | ||||
-rw-r--r-- | ssl/test/test_config.h | 10 |
14 files changed, 1246 insertions, 195 deletions
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index c976e7c..5ee7c65 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -48,6 +48,20 @@ static const TestConfig *GetConfigPtr(SSL *ssl) { return (const TestConfig *)SSL_get_ex_data(ssl, g_ex_data_index); } +static EVP_PKEY *LoadPrivateKey(const std::string &file) { + BIO *bio = BIO_new(BIO_s_file()); + if (bio == NULL) { + return NULL; + } + if (!BIO_read_filename(bio, file.c_str())) { + BIO_free(bio); + return NULL; + } + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + BIO_free(bio); + return pkey; +} + static int early_callback_called = 0; static int select_certificate_callback(const struct ssl_early_callback_ctx *ctx) { @@ -125,6 +139,29 @@ static int next_proto_select_callback(SSL* ssl, return SSL_TLSEXT_ERR_OK; } +static int alpn_select_callback(SSL* ssl, + const uint8_t** out, + uint8_t* outlen, + const uint8_t* in, + unsigned inlen, + void* arg) { + const TestConfig *config = GetConfigPtr(ssl); + if (config->select_alpn.empty()) + return SSL_TLSEXT_ERR_NOACK; + + if (!config->expected_advertised_alpn.empty() && + (config->expected_advertised_alpn.size() != inlen || + memcmp(config->expected_advertised_alpn.data(), + in, inlen) != 0)) { + fprintf(stderr, "bad ALPN select callback inputs\n"); + exit(1); + } + + *out = (const uint8_t*)config->select_alpn.data(); + *outlen = config->select_alpn.size(); + return SSL_TLSEXT_ERR_OK; +} + static int cookie_generate_callback(SSL *ssl, uint8_t *cookie, size_t *cookie_len) { *cookie_len = 32; memset(cookie, 42, *cookie_len); @@ -199,12 +236,19 @@ static SSL_CTX *setup_ctx(const TestConfig *config) { SSL_CTX_set_next_protos_advertised_cb( ssl_ctx, next_protos_advertised_callback, NULL); - SSL_CTX_set_next_proto_select_cb( - ssl_ctx, next_proto_select_callback, NULL); + if (!config->select_next_proto.empty()) { + SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_callback, NULL); + } + + if (!config->select_alpn.empty()) { + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_callback, NULL); + } SSL_CTX_set_cookie_generate_cb(ssl_ctx, cookie_generate_callback); SSL_CTX_set_cookie_verify_cb(ssl_ctx, cookie_verify_callback); + ssl_ctx->tlsext_channel_id_enabled_new = 1; + DH_free(dh); return ssl_ctx; @@ -300,6 +344,33 @@ static int do_exchange(SSL_SESSION **out_session, if (config->cookie_exchange) { SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE); } + if (config->tls_d5_bug) { + SSL_set_options(ssl, SSL_OP_TLS_D5_BUG); + } + if (!config->expected_channel_id.empty()) { + SSL_enable_tls_channel_id(ssl); + } + if (!config->send_channel_id.empty()) { + EVP_PKEY *pkey = LoadPrivateKey(config->send_channel_id); + if (pkey == NULL) { + BIO_print_errors_fp(stdout); + return 1; + } + SSL_enable_tls_channel_id(ssl); + if (!SSL_set1_tls_channel_id(ssl, pkey)) { + EVP_PKEY_free(pkey); + BIO_print_errors_fp(stdout); + return 1; + } + EVP_PKEY_free(pkey); + } + if (!config->host_name.empty()) { + SSL_set_tlsext_host_name(ssl, config->host_name.c_str()); + } + if (!config->advertise_alpn.empty()) { + SSL_set_alpn_protos(ssl, (const uint8_t *)config->advertise_alpn.data(), + config->advertise_alpn.size()); + } BIO *bio = BIO_new_fd(fd, 1 /* take ownership */); if (bio == NULL) { @@ -340,8 +411,9 @@ static int do_exchange(SSL_SESSION **out_session, return 2; } - if (is_resume && !SSL_session_reused(ssl)) { - fprintf(stderr, "session was not reused\n"); + if (is_resume && (SSL_session_reused(ssl) == config->expect_session_miss)) { + fprintf(stderr, "session was%s reused\n", + SSL_session_reused(ssl) ? "" : " not"); return 2; } @@ -363,7 +435,7 @@ static int do_exchange(SSL_SESSION **out_session, if (!config->expected_certificate_types.empty()) { uint8_t *certificate_types; int num_certificate_types = - SSL_get0_certificate_types(ssl, &certificate_types); + SSL_get0_certificate_types(ssl, &certificate_types); if (num_certificate_types != (int)config->expected_certificate_types.size() || memcmp(certificate_types, @@ -386,6 +458,32 @@ static int do_exchange(SSL_SESSION **out_session, } } + if (!config->expected_alpn.empty()) { + const uint8_t *alpn_proto; + unsigned alpn_proto_len; + SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len); + if (alpn_proto_len != config->expected_alpn.size() || + memcmp(alpn_proto, config->expected_alpn.data(), + alpn_proto_len) != 0) { + fprintf(stderr, "negotiated alpn proto mismatch\n"); + return 2; + } + } + + if (!config->expected_channel_id.empty()) { + uint8_t channel_id[64]; + if (!SSL_get_tls_channel_id(ssl, channel_id, sizeof(channel_id))) { + fprintf(stderr, "no channel id negotiated\n"); + return 2; + } + if (config->expected_channel_id.size() != 64 || + memcmp(config->expected_channel_id.data(), + channel_id, 64) != 0) { + fprintf(stderr, "channel id mismatch\n"); + return 2; + } + } + if (config->write_different_record_sizes) { if (config->is_dtls) { fprintf(stderr, "write_different_record_sizes not supported for DTLS\n"); @@ -423,6 +521,12 @@ static int do_exchange(SSL_SESSION **out_session, } } } else { + if (config->shim_writes_first) { + int w; + do { + w = SSL_write(ssl, "hello", 5); + } while (config->async && retry_async(ssl, w, bio)); + } for (;;) { uint8_t buf[512]; int n; diff --git a/ssl/test/runner/channel_id_key.pem b/ssl/test/runner/channel_id_key.pem new file mode 100644 index 0000000..604752b --- /dev/null +++ b/ssl/test/runner/channel_id_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIPwxu50c7LEhVNRYJFRWBUnoaz7JSos96T5hBp4rjyptoAoGCCqGSM49 +AwEHoUQDQgAEzFSVTE5guxJRQ0VbZ8dicPs5e/DT7xpW7Yc9hq0VOchv7cbXuI/T +CwadDjGWX/oaz0ftFqrVmfkwZu+C58ioWg== +-----END EC PRIVATE KEY----- diff --git a/ssl/test/runner/cipher_suites.go b/ssl/test/runner/cipher_suites.go index ed26f09..6cd0de9 100644 --- a/ssl/test/runner/cipher_suites.go +++ b/ssl/test/runner/cipher_suites.go @@ -12,6 +12,8 @@ import ( "crypto/md5" "crypto/rc4" "crypto/sha1" + "crypto/sha256" + "crypto/sha512" "crypto/x509" "hash" ) @@ -79,20 +81,29 @@ var cipherSuites = []*cipherSuite{ {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM}, {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadAESGCM}, {TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, + {TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, {TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE | suiteNoDTLS, cipherRC4, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteNoDTLS, cipherRC4, macSHA1, nil}, + {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheRSAKA, suiteECDHE | suiteTLS12, cipherAES, macSHA256, nil}, + {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, cipherAES, macSHA256, nil}, {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil}, + {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, cipherAES, macSHA384, nil}, + {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12 | suiteSHA384, cipherAES, macSHA384, nil}, {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil}, {TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, dheRSAKA, suiteTLS12, nil, nil, aeadAESGCM}, {TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, dheRSAKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, + {TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, dheRSAKA, suiteTLS12, cipherAES, macSHA256, nil}, + {TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, 32, 32, 16, dheRSAKA, suiteTLS12, cipherAES, macSHA256, nil}, {TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, dheRSAKA, 0, cipherAES, macSHA1, nil}, {TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, dheRSAKA, 0, cipherAES, macSHA1, nil}, {TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, rsaKA, suiteTLS12, nil, nil, aeadAESGCM}, {TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, suiteNoDTLS, cipherRC4, macSHA1, nil}, {TLS_RSA_WITH_RC4_128_MD5, 16, 16, 0, rsaKA, suiteNoDTLS, cipherRC4, macMD5, nil}, + {TLS_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, rsaKA, suiteTLS12, cipherAES, macSHA256, nil}, + {TLS_RSA_WITH_AES_256_CBC_SHA256, 32, 32, 16, rsaKA, suiteTLS12, cipherAES, macSHA256, nil}, {TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil}, {TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil}, @@ -146,6 +157,30 @@ func macMD5(version uint16, key []byte) macFunction { return tls10MAC{hmac.New(md5.New, key)} } +func macSHA256(version uint16, key []byte) macFunction { + if version == VersionSSL30 { + mac := ssl30MAC{ + h: sha256.New(), + key: make([]byte, len(key)), + } + copy(mac.key, key) + return mac + } + return tls10MAC{hmac.New(sha256.New, key)} +} + +func macSHA384(version uint16, key []byte) macFunction { + if version == VersionSSL30 { + mac := ssl30MAC{ + h: sha512.New384(), + key: make([]byte, len(key)), + } + copy(mac.key, key) + return mac + } + return tls10MAC{hmac.New(sha512.New384, key)} +} + type macFunction interface { Size() int MAC(digestBuf, seq, header, length, data []byte) []byte @@ -304,6 +339,10 @@ const ( TLS_DHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0033 TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035 TLS_DHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0039 + TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c + TLS_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x003d + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x0067 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x006b TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009e @@ -315,8 +354,13 @@ const ( TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xc024 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xc028 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030 fallbackSCSV uint16 = 0x5600 ) diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index f14f4e9..cf244bc 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -7,6 +7,7 @@ package main import ( "container/list" "crypto" + "crypto/ecdsa" "crypto/rand" "crypto/x509" "fmt" @@ -47,19 +48,20 @@ const ( // TLS handshake message types. const ( - typeClientHello uint8 = 1 - typeServerHello uint8 = 2 - typeHelloVerifyRequest uint8 = 3 - typeNewSessionTicket uint8 = 4 - typeCertificate uint8 = 11 - typeServerKeyExchange uint8 = 12 - typeCertificateRequest uint8 = 13 - typeServerHelloDone uint8 = 14 - typeCertificateVerify uint8 = 15 - typeClientKeyExchange uint8 = 16 - typeFinished uint8 = 20 - typeCertificateStatus uint8 = 22 - typeNextProtocol uint8 = 67 // Not IANA assigned + typeClientHello uint8 = 1 + typeServerHello uint8 = 2 + typeHelloVerifyRequest uint8 = 3 + typeNewSessionTicket uint8 = 4 + typeCertificate uint8 = 11 + typeServerKeyExchange uint8 = 12 + typeCertificateRequest uint8 = 13 + typeServerHelloDone uint8 = 14 + typeCertificateVerify uint8 = 15 + typeClientKeyExchange uint8 = 16 + typeFinished uint8 = 20 + typeCertificateStatus uint8 = 22 + typeNextProtocol uint8 = 67 // Not IANA assigned + typeEncryptedExtensions uint8 = 203 // Not IANA assigned ) // TLS compression types. @@ -74,9 +76,11 @@ const ( extensionSupportedCurves uint16 = 10 extensionSupportedPoints uint16 = 11 extensionSignatureAlgorithms uint16 = 13 + extensionALPN uint16 = 16 extensionSessionTicket uint16 = 35 extensionNextProtoNeg uint16 = 13172 // not IANA assigned extensionRenegotiationInfo uint16 = 0xff01 + extensionChannelID uint16 = 30032 // not IANA assigned ) // TLS signaling cipher suite values @@ -135,24 +139,24 @@ const ( // signatureAndHash mirrors the TLS 1.2, SignatureAndHashAlgorithm struct. See // RFC 5246, section A.4.1. type signatureAndHash struct { - hash, signature uint8 + signature, hash uint8 } // supportedSKXSignatureAlgorithms contains the signature and hash algorithms // that the code advertises as supported in a TLS 1.2 ClientHello. var supportedSKXSignatureAlgorithms = []signatureAndHash{ - {hashSHA256, signatureRSA}, - {hashSHA256, signatureECDSA}, - {hashSHA1, signatureRSA}, - {hashSHA1, signatureECDSA}, + {signatureRSA, hashSHA256}, + {signatureECDSA, hashSHA256}, + {signatureRSA, hashSHA1}, + {signatureECDSA, hashSHA1}, } // supportedClientCertSignatureAlgorithms contains the signature and hash // algorithms that the code advertises as supported in a TLS 1.2 // CertificateRequest. var supportedClientCertSignatureAlgorithms = []signatureAndHash{ - {hashSHA256, signatureRSA}, - {hashSHA256, signatureECDSA}, + {signatureRSA, hashSHA256}, + {signatureECDSA, hashSHA256}, } // ConnectionState records basic TLS details about the connection. @@ -163,9 +167,11 @@ type ConnectionState struct { CipherSuite uint16 // cipher suite in use (TLS_RSA_WITH_RC4_128_SHA, ...) NegotiatedProtocol string // negotiated next protocol (from Config.NextProtos) NegotiatedProtocolIsMutual bool // negotiated protocol was advertised by server + NegotiatedProtocolFromALPN bool // protocol negotiated with ALPN ServerName string // server name requested by client, if any (server side only) PeerCertificates []*x509.Certificate // certificate chain presented by remote peer VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates + ChannelID *ecdsa.PublicKey // the channel ID for this connection } // ClientAuthType declares the policy the server will follow for @@ -187,6 +193,7 @@ type ClientSessionState struct { vers uint16 // SSL/TLS version negotiated for the session cipherSuite uint16 // Ciphersuite negotiated for the session masterSecret []byte // MasterSecret generated by client on a full handshake + handshakeHash []byte // Handshake hash for Channel ID purposes. serverCertificates []*x509.Certificate // Certificate chain presented by the server } @@ -307,6 +314,15 @@ type Config struct { // be used. CurvePreferences []CurveID + // ChannelID contains the ECDSA key for the client to use as + // its TLS Channel ID. + ChannelID *ecdsa.PrivateKey + + // RequestChannelID controls whether the server requests a TLS + // Channel ID. If negotiated, the client's public key is + // returned in the ConnectionState. + RequestChannelID bool + // Bugs specifies optional misbehaviour to be used for testing other // implementations. Bugs ProtocolBugs @@ -420,6 +436,34 @@ type ProtocolBugs struct { // SkipHelloVerifyRequest causes a DTLS server to skip the // HelloVerifyRequest message. SkipHelloVerifyRequest bool + + // ExpectFalseStart causes the server to, on full handshakes, + // expect the peer to False Start; the server Finished message + // isn't sent until we receive an application data record + // from the peer. + ExpectFalseStart bool + + // SSL3RSAKeyExchange causes the client to always send an RSA + // ClientKeyExchange message without the two-byte length + // prefix, as if it were SSL3. + SSL3RSAKeyExchange bool + + // SkipCipherVersionCheck causes the server to negotiate + // TLS 1.2 ciphers in earlier versions of TLS. + SkipCipherVersionCheck bool + + // ExpectServerName, if not empty, is the hostname the client + // must specify in the server_name extension. + ExpectServerName string + + // SwapNPNAndALPN switches the relative order between NPN and + // ALPN on the server. This is to test that server preference + // of ALPN works regardless of their relative order. + SwapNPNAndALPN bool + + // AllowSessionVersionMismatch causes the server to resume sessions + // regardless of the version associated with the session. + AllowSessionVersionMismatch bool } func (c *Config) serverInit() { diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index 5371a64..9f0c328 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go @@ -9,6 +9,7 @@ package main import ( "bytes" "crypto/cipher" + "crypto/ecdsa" "crypto/subtle" "crypto/x509" "errors" @@ -46,6 +47,9 @@ type Conn struct { clientProtocol string clientProtocolFallback bool + usedALPN bool + + channelID *ecdsa.PublicKey // input/output in, out halfConn // in.Mutex < out.Mutex @@ -657,7 +661,7 @@ func (c *Conn) readRecord(want recordType) error { return c.in.setErrorLocked(errors.New("tls: handshake or ChangeCipherSpec requested after handshake complete")) } case recordTypeApplicationData: - if !c.handshakeComplete { + if !c.handshakeComplete && !c.config.Bugs.ExpectFalseStart { c.sendAlert(alertInternalError) return c.in.setErrorLocked(errors.New("tls: application data record requested before handshake complete")) } @@ -937,6 +941,8 @@ func (c *Conn) readHandshake() (interface{}, error) { m = new(finishedMsg) case typeHelloVerifyRequest: m = new(helloVerifyRequestMsg) + case typeEncryptedExtensions: + m = new(encryptedExtensionsMsg) default: return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } @@ -1100,10 +1106,12 @@ func (c *Conn) ConnectionState() ConnectionState { state.NegotiatedProtocol = c.clientProtocol state.DidResume = c.didResume state.NegotiatedProtocolIsMutual = !c.clientProtocolFallback + state.NegotiatedProtocolFromALPN = c.usedALPN state.CipherSuite = c.cipherSuite state.PeerCertificates = c.peerCertificates state.VerifiedChains = c.verifiedChains state.ServerName = c.serverName + state.ChannelID = c.channelID } return state diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index ecc2bed..d78e767 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go @@ -8,6 +8,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/elliptic" "crypto/rsa" "crypto/subtle" "crypto/x509" @@ -42,6 +43,18 @@ func (c *Conn) clientHandshake() error { c.sendHandshakeSeq = 0 c.recvHandshakeSeq = 0 + nextProtosLength := 0 + for _, proto := range c.config.NextProtos { + if l := len(proto); l == 0 || l > 255 { + return errors.New("tls: invalid NextProtos value") + } else { + nextProtosLength += 1 + l + } + } + if nextProtosLength > 0xffff { + return errors.New("tls: NextProtos values too large") + } + hello := &clientHelloMsg{ isDTLS: c.isDTLS, vers: c.config.maxVersion(), @@ -53,7 +66,10 @@ func (c *Conn) clientHandshake() error { supportedPoints: []uint8{pointFormatUncompressed}, nextProtoNeg: len(c.config.NextProtos) > 0, secureRenegotiation: true, + alpnProtocols: c.config.NextProtos, duplicateExtension: c.config.Bugs.DuplicateExtension, + channelIDSupported: c.config.ChannelID != nil, + npnLast: c.config.Bugs.SwapNPNAndALPN, } if c.config.Bugs.SendClientVersion != 0 { @@ -238,7 +254,7 @@ NextCipherSuite: if err := hs.readFinished(); err != nil { return err } - if err := hs.sendFinished(); err != nil { + if err := hs.sendFinished(isResume); err != nil { return err } } else { @@ -248,7 +264,7 @@ NextCipherSuite: if err := hs.establishKeys(); err != nil { return err } - if err := hs.sendFinished(); err != nil { + if err := hs.sendFinished(isResume); err != nil { return err } if err := hs.readSessionTicket(); err != nil { @@ -472,6 +488,8 @@ func (hs *clientHandshakeState) doFullHandshake() error { c.writeRecord(recordTypeHandshake, ckx.marshal()) } + hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random) + if chainToSend != nil { var signed []byte certVerify := &certificateVerifyMsg{ @@ -485,7 +503,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { break } var digest []byte - digest, _, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash) + digest, _, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret) if err != nil { break } @@ -501,7 +519,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { } var digest []byte var hashFunc crypto.Hash - digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash) + digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(certVerify.signatureAndHash, hs.masterSecret) if err != nil { break } @@ -519,7 +537,8 @@ func (hs *clientHandshakeState) doFullHandshake() error { c.writeRecord(recordTypeHandshake, certVerify.marshal()) } - hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random) + hs.finishedHash.discardHandshakeBuffer() + return nil } @@ -560,15 +579,42 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { return false, errors.New("tls: server selected unsupported compression format") } - if !hs.hello.nextProtoNeg && hs.serverHello.nextProtoNeg { + clientDidNPN := hs.hello.nextProtoNeg + clientDidALPN := len(hs.hello.alpnProtocols) > 0 + serverHasNPN := hs.serverHello.nextProtoNeg + serverHasALPN := len(hs.serverHello.alpnProtocol) > 0 + + if !clientDidNPN && serverHasNPN { c.sendAlert(alertHandshakeFailure) return false, errors.New("server advertised unrequested NPN extension") } + if !clientDidALPN && serverHasALPN { + c.sendAlert(alertHandshakeFailure) + return false, errors.New("server advertised unrequested ALPN extension") + } + + if serverHasNPN && serverHasALPN { + c.sendAlert(alertHandshakeFailure) + return false, errors.New("server advertised both NPN and ALPN extensions") + } + + if serverHasALPN { + c.clientProtocol = hs.serverHello.alpnProtocol + c.clientProtocolFallback = false + c.usedALPN = true + } + + if !hs.hello.channelIDSupported && hs.serverHello.channelIDRequested { + c.sendAlert(alertHandshakeFailure) + return false, errors.New("server advertised unrequested Channel ID extension") + } + if hs.serverResumedSession() { // Restore masterSecret and peerCerts from previous state hs.masterSecret = hs.session.masterSecret c.peerCertificates = hs.session.serverCertificates + hs.finishedHash.discardHandshakeBuffer() return true, nil } return false, nil @@ -619,20 +665,22 @@ func (hs *clientHandshakeState) readSessionTicket() error { c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(sessionTicketMsg, msg) } - hs.writeServerHash(sessionTicketMsg.marshal()) hs.session = &ClientSessionState{ sessionTicket: sessionTicketMsg.ticket, vers: c.vers, cipherSuite: hs.suite.id, masterSecret: hs.masterSecret, + handshakeHash: hs.finishedHash.server.Sum(nil), serverCertificates: c.peerCertificates, } + hs.writeServerHash(sessionTicketMsg.marshal()) + return nil } -func (hs *clientHandshakeState) sendFinished() error { +func (hs *clientHandshakeState) sendFinished(isResume bool) error { c := hs.c var postCCSBytes []byte @@ -650,6 +698,34 @@ func (hs *clientHandshakeState) sendFinished() error { postCCSBytes = append(postCCSBytes, nextProtoBytes...) } + if hs.serverHello.channelIDRequested { + encryptedExtensions := new(encryptedExtensionsMsg) + if c.config.ChannelID.Curve != elliptic.P256() { + return fmt.Errorf("tls: Channel ID is not on P-256.") + } + var resumeHash []byte + if isResume { + resumeHash = hs.session.handshakeHash + } + r, s, err := ecdsa.Sign(c.config.rand(), c.config.ChannelID, hs.finishedHash.hashForChannelID(resumeHash)) + if err != nil { + return err + } + channelID := make([]byte, 128) + writeIntPadded(channelID[0:32], c.config.ChannelID.X) + writeIntPadded(channelID[32:64], c.config.ChannelID.Y) + writeIntPadded(channelID[64:96], r) + writeIntPadded(channelID[96:128], s) + encryptedExtensions.channelID = channelID + + c.channelID = &c.config.ChannelID.PublicKey + + encryptedExtensionsBytes := encryptedExtensions.marshal() + hs.writeHash(encryptedExtensionsBytes, seqno) + seqno++ + postCCSBytes = append(postCCSBytes, encryptedExtensionsBytes...) + } + finished := new(finishedMsg) if c.config.Bugs.EarlyChangeCipherSpec == 2 { finished.verifyData = hs.finishedHash.clientSum(nil) @@ -709,18 +785,28 @@ func clientSessionCacheKey(serverAddr net.Addr, config *Config) string { return serverAddr.String() } -// mutualProtocol finds the mutual Next Protocol Negotiation protocol given the -// set of client and server supported protocols. The set of client supported -// protocols must not be empty. It returns the resulting protocol and flag +// mutualProtocol finds the mutual Next Protocol Negotiation or ALPN protocol +// given list of possible protocols and a list of the preference order. The +// first list must not be empty. It returns the resulting protocol and flag // indicating if the fallback case was reached. -func mutualProtocol(clientProtos, serverProtos []string) (string, bool) { - for _, s := range serverProtos { - for _, c := range clientProtos { +func mutualProtocol(protos, preferenceProtos []string) (string, bool) { + for _, s := range preferenceProtos { + for _, c := range protos { if s == c { return s, false } } } - return clientProtos[0], true + return protos[0], true +} + +// writeIntPadded writes x into b, padded up with leading zeros as +// needed. +func writeIntPadded(b []byte, x *big.Int) { + for i := range b { + b[i] = 0 + } + xb := x.Bytes() + copy(b[len(b)-len(xb):], xb) } diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index 7fe8bf5..136360d 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go @@ -24,7 +24,10 @@ type clientHelloMsg struct { sessionTicket []uint8 signatureAndHashes []signatureAndHash secureRenegotiation bool + alpnProtocols []string duplicateExtension bool + channelIDSupported bool + npnLast bool } func (m *clientHelloMsg) equal(i interface{}) bool { @@ -49,7 +52,11 @@ func (m *clientHelloMsg) equal(i interface{}) bool { m.ticketSupported == m1.ticketSupported && bytes.Equal(m.sessionTicket, m1.sessionTicket) && eqSignatureAndHashes(m.signatureAndHashes, m1.signatureAndHashes) && - m.secureRenegotiation == m1.secureRenegotiation + m.secureRenegotiation == m1.secureRenegotiation && + eqStrings(m.alpnProtocols, m1.alpnProtocols) && + m.duplicateExtension == m1.duplicateExtension && + m.channelIDSupported == m1.channelIDSupported && + m.npnLast == m1.npnLast } func (m *clientHelloMsg) marshal() []byte { @@ -97,6 +104,20 @@ func (m *clientHelloMsg) marshal() []byte { if m.duplicateExtension { numExtensions += 2 } + if m.channelIDSupported { + numExtensions++ + } + if len(m.alpnProtocols) > 0 { + extensionsLength += 2 + for _, s := range m.alpnProtocols { + if l := len(s); l == 0 || l > 255 { + panic("invalid ALPN protocol") + } + extensionsLength++ + extensionsLength += len(s) + } + numExtensions++ + } if numExtensions > 0 { extensionsLength += 4 * numExtensions length += 2 + extensionsLength @@ -141,7 +162,7 @@ func (m *clientHelloMsg) marshal() []byte { z[1] = 0xff z = z[4:] } - if m.nextProtoNeg { + if m.nextProtoNeg && !m.npnLast { z[0] = byte(extensionNextProtoNeg >> 8) z[1] = byte(extensionNextProtoNeg & 0xff) // The length is always 0 @@ -260,6 +281,38 @@ func (m *clientHelloMsg) marshal() []byte { z[3] = 1 z = z[5:] } + if len(m.alpnProtocols) > 0 { + z[0] = byte(extensionALPN >> 8) + z[1] = byte(extensionALPN & 0xff) + lengths := z[2:] + z = z[6:] + + stringsLength := 0 + for _, s := range m.alpnProtocols { + l := len(s) + z[0] = byte(l) + copy(z[1:], s) + z = z[1+l:] + stringsLength += 1 + l + } + + lengths[2] = byte(stringsLength >> 8) + lengths[3] = byte(stringsLength) + stringsLength += 2 + lengths[0] = byte(stringsLength >> 8) + lengths[1] = byte(stringsLength) + } + if m.channelIDSupported { + z[0] = byte(extensionChannelID >> 8) + z[1] = byte(extensionChannelID & 0xff) + z = z[4:] + } + if m.nextProtoNeg && m.npnLast { + z[0] = byte(extensionNextProtoNeg >> 8) + z[1] = byte(extensionNextProtoNeg & 0xff) + // The length is always 0 + z = z[4:] + } if m.duplicateExtension { // Add a duplicate bogus extension at the beginning and end. z[0] = 0xff @@ -331,6 +384,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { m.ticketSupported = false m.sessionTicket = nil m.signatureAndHashes = nil + m.alpnProtocols = nil if len(data) == 0 { // ClientHello is optionally followed by extension data @@ -440,6 +494,29 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { return false } m.secureRenegotiation = true + case extensionALPN: + if length < 2 { + return false + } + l := int(data[0])<<8 | int(data[1]) + if l != length-2 { + return false + } + d := data[2:length] + for len(d) != 0 { + stringLen := int(d[0]) + d = d[1:] + if stringLen == 0 || stringLen > len(d) { + return false + } + m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen])) + d = d[stringLen:] + } + case extensionChannelID: + if length > 0 { + return false + } + m.channelIDSupported = true } data = data[length:] } @@ -460,7 +537,9 @@ type serverHelloMsg struct { ocspStapling bool ticketSupported bool secureRenegotiation bool + alpnProtocol string duplicateExtension bool + channelIDRequested bool } func (m *serverHelloMsg) equal(i interface{}) bool { @@ -480,7 +559,10 @@ func (m *serverHelloMsg) equal(i interface{}) bool { eqStrings(m.nextProtos, m1.nextProtos) && m.ocspStapling == m1.ocspStapling && m.ticketSupported == m1.ticketSupported && - m.secureRenegotiation == m1.secureRenegotiation + m.secureRenegotiation == m1.secureRenegotiation && + m.alpnProtocol == m1.alpnProtocol && + m.duplicateExtension == m1.duplicateExtension && + m.channelIDRequested == m1.channelIDRequested } func (m *serverHelloMsg) marshal() []byte { @@ -514,6 +596,17 @@ func (m *serverHelloMsg) marshal() []byte { if m.duplicateExtension { numExtensions += 2 } + if m.channelIDRequested { + numExtensions++ + } + if alpnLen := len(m.alpnProtocol); alpnLen > 0 { + if alpnLen >= 256 { + panic("invalid ALPN protocol") + } + extensionsLength += 2 + 1 + alpnLen + numExtensions++ + } + if numExtensions > 0 { extensionsLength += 4 * numExtensions length += 2 + extensionsLength @@ -581,6 +674,25 @@ func (m *serverHelloMsg) marshal() []byte { z[3] = 1 z = z[5:] } + if alpnLen := len(m.alpnProtocol); alpnLen > 0 { + z[0] = byte(extensionALPN >> 8) + z[1] = byte(extensionALPN & 0xff) + l := 2 + 1 + alpnLen + z[2] = byte(l >> 8) + z[3] = byte(l) + l -= 2 + z[4] = byte(l >> 8) + z[5] = byte(l) + l -= 1 + z[6] = byte(l) + copy(z[7:], []byte(m.alpnProtocol)) + z = z[7+alpnLen:] + } + if m.channelIDRequested { + z[0] = byte(extensionChannelID >> 8) + z[1] = byte(extensionChannelID & 0xff) + z = z[4:] + } if m.duplicateExtension { // Add a duplicate bogus extension at the beginning and end. z[0] = 0xff @@ -617,6 +729,7 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { m.nextProtos = nil m.ocspStapling = false m.ticketSupported = false + m.alpnProtocol = "" if len(data) == 0 { // ServerHello is optionally followed by extension data @@ -671,6 +784,27 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { return false } m.secureRenegotiation = true + case extensionALPN: + d := data[:length] + if len(d) < 3 { + return false + } + l := int(d[0])<<8 | int(d[1]) + if l != len(d)-2 { + return false + } + d = d[2:] + l = int(d[0]) + if l != len(d)-1 { + return false + } + d = d[1:] + m.alpnProtocol = string(d) + case extensionChannelID: + if length > 0 { + return false + } + m.channelIDRequested = true } data = data[length:] } @@ -1407,7 +1541,8 @@ func (m *helloVerifyRequestMsg) equal(i interface{}) bool { return false } - return m.vers == m1.vers && + return bytes.Equal(m.raw, m1.raw) && + m.vers == m1.vers && bytes.Equal(m.cookie, m1.cookie) } @@ -1447,6 +1582,58 @@ func (m *helloVerifyRequestMsg) unmarshal(data []byte) bool { return true } +type encryptedExtensionsMsg struct { + raw []byte + channelID []byte +} + +func (m *encryptedExtensionsMsg) equal(i interface{}) bool { + m1, ok := i.(*encryptedExtensionsMsg) + if !ok { + return false + } + + return bytes.Equal(m.raw, m1.raw) && + bytes.Equal(m.channelID, m1.channelID) +} + +func (m *encryptedExtensionsMsg) marshal() []byte { + if m.raw != nil { + return m.raw + } + + length := 2 + 2 + len(m.channelID) + + x := make([]byte, 4+length) + x[0] = typeEncryptedExtensions + x[1] = uint8(length >> 16) + x[2] = uint8(length >> 8) + x[3] = uint8(length) + x[4] = uint8(extensionChannelID >> 8) + x[5] = uint8(extensionChannelID & 0xff) + x[6] = uint8(len(m.channelID) >> 8) + x[7] = uint8(len(m.channelID) & 0xff) + copy(x[8:], m.channelID) + + return x +} + +func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool { + if len(data) != 4+2+2+128 { + return false + } + m.raw = data + if (uint16(data[4])<<8)|uint16(data[5]) != extensionChannelID { + return false + } + if int(data[6])<<8|int(data[7]) != 128 { + return false + } + m.channelID = data[4+2+2:] + + return true +} + func eqUint16s(x, y []uint16) bool { if len(x) != len(y) { return false diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index d5a660a..1eb3f11 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -8,6 +8,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/elliptic" "crypto/rsa" "crypto/subtle" "crypto/x509" @@ -15,6 +16,7 @@ import ( "errors" "fmt" "io" + "math/big" ) // serverHandshakeState contains details of a server handshake in progress. @@ -69,7 +71,7 @@ func (c *Conn) serverHandshake() error { if err := hs.sendFinished(); err != nil { return err } - if err := hs.readFinished(); err != nil { + if err := hs.readFinished(isResume); err != nil { return err } c.didResume = true @@ -82,9 +84,14 @@ func (c *Conn) serverHandshake() error { if err := hs.establishKeys(); err != nil { return err } - if err := hs.readFinished(); err != nil { + if err := hs.readFinished(isResume); err != nil { return err } + if c.config.Bugs.ExpectFalseStart { + if err := c.readRecord(recordTypeApplicationData); err != nil { + return err + } + } if err := hs.sendSessionTicket(); err != nil { return err } @@ -213,13 +220,22 @@ Curves: if len(hs.clientHello.serverName) > 0 { c.serverName = hs.clientHello.serverName } - // Although sending an empty NPN extension is reasonable, Firefox has - // had a bug around this. Best to send nothing at all if - // config.NextProtos is empty. See - // https://code.google.com/p/go/issues/detail?id=5445. - if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 { - hs.hello.nextProtoNeg = true - hs.hello.nextProtos = config.NextProtos + + if len(hs.clientHello.alpnProtocols) > 0 { + if selectedProto, fallback := mutualProtocol(hs.clientHello.alpnProtocols, c.config.NextProtos); !fallback { + hs.hello.alpnProtocol = selectedProto + c.clientProtocol = selectedProto + c.usedALPN = true + } + } else { + // Although sending an empty NPN extension is reasonable, Firefox has + // had a bug around this. Best to send nothing at all if + // config.NextProtos is empty. See + // https://code.google.com/p/go/issues/detail?id=5445. + if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 { + hs.hello.nextProtoNeg = true + hs.hello.nextProtos = config.NextProtos + } } if len(config.Certificates) == 0 { @@ -230,6 +246,13 @@ Curves: if len(hs.clientHello.serverName) > 0 { hs.cert = config.getCertificateForName(hs.clientHello.serverName) } + if expected := c.config.Bugs.ExpectServerName; expected != "" && expected != hs.clientHello.serverName { + return false, errors.New("tls: unexpected server name") + } + + if hs.clientHello.channelIDSupported && config.RequestChannelID { + hs.hello.channelIDRequested = true + } _, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey) @@ -279,16 +302,22 @@ Curves: func (hs *serverHandshakeState) checkForResumption() bool { c := hs.c - var ok bool - if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok { + if c.config.SessionTicketsDisabled { return false } - if hs.sessionState.vers > hs.clientHello.vers { + var ok bool + if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok { return false } - if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers { - return false + + if !c.config.Bugs.AllowSessionVersionMismatch { + if hs.sessionState.vers > hs.clientHello.vers { + return false + } + if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers { + return false + } } cipherSuiteOk := false @@ -331,6 +360,7 @@ func (hs *serverHandshakeState) doResumeHandshake() error { hs.hello.ticketSupported = c.config.Bugs.RenewTicketOnResume hs.finishedHash = newFinishedHash(c.vers, hs.suite) + hs.finishedHash.discardHandshakeBuffer() hs.writeClientHash(hs.clientHello.marshal()) hs.writeServerHash(hs.hello.marshal()) @@ -467,6 +497,13 @@ func (hs *serverHandshakeState) doFullHandshake() error { } hs.writeClientHash(ckx.marshal()) + preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers) + if err != nil { + c.sendAlert(alertHandshakeFailure) + return err + } + hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random) + // If we received a client cert in response to our certificate request message, // the client will send us a certificateVerifyMsg immediately after the // clientKeyExchangeMsg. This message is a digest of all preceding @@ -515,7 +552,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { break } var digest []byte - digest, _, err = hs.finishedHash.hashForClientCertificate(signatureAndHash) + digest, _, err = hs.finishedHash.hashForClientCertificate(signatureAndHash, hs.masterSecret) if err != nil { break } @@ -530,7 +567,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } var digest []byte var hashFunc crypto.Hash - digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(signatureAndHash) + digest, hashFunc, err = hs.finishedHash.hashForClientCertificate(signatureAndHash, hs.masterSecret) if err != nil { break } @@ -544,12 +581,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { hs.writeClientHash(certVerify.marshal()) } - preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers) - if err != nil { - c.sendAlert(alertHandshakeFailure) - return err - } - hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random) + hs.finishedHash.discardHandshakeBuffer() return nil } @@ -579,7 +611,7 @@ func (hs *serverHandshakeState) establishKeys() error { return nil } -func (hs *serverHandshakeState) readFinished() error { +func (hs *serverHandshakeState) readFinished(isResume bool) error { c := hs.c c.readRecord(recordTypeChangeCipherSpec) @@ -601,6 +633,36 @@ func (hs *serverHandshakeState) readFinished() error { c.clientProtocol = nextProto.proto } + if hs.hello.channelIDRequested { + msg, err := c.readHandshake() + if err != nil { + return err + } + encryptedExtensions, ok := msg.(*encryptedExtensionsMsg) + if !ok { + c.sendAlert(alertUnexpectedMessage) + return unexpectedMessageError(encryptedExtensions, msg) + } + x := new(big.Int).SetBytes(encryptedExtensions.channelID[0:32]) + y := new(big.Int).SetBytes(encryptedExtensions.channelID[32:64]) + r := new(big.Int).SetBytes(encryptedExtensions.channelID[64:96]) + s := new(big.Int).SetBytes(encryptedExtensions.channelID[96:128]) + if !elliptic.P256().IsOnCurve(x, y) { + return errors.New("tls: invalid channel ID public key") + } + channelID := &ecdsa.PublicKey{elliptic.P256(), x, y} + var resumeHash []byte + if isResume { + resumeHash = hs.sessionState.handshakeHash + } + if !ecdsa.Verify(channelID, hs.finishedHash.hashForChannelID(resumeHash), r, s) { + return errors.New("tls: invalid channel ID signature") + } + c.channelID = channelID + + hs.writeClientHash(encryptedExtensions.marshal()) + } + msg, err := c.readHandshake() if err != nil { return err @@ -632,10 +694,11 @@ func (hs *serverHandshakeState) sendSessionTicket() error { var err error state := sessionState{ - vers: c.vers, - cipherSuite: hs.suite.id, - masterSecret: hs.masterSecret, - certificates: hs.certsFromClient, + vers: c.vers, + cipherSuite: hs.suite.id, + masterSecret: hs.masterSecret, + certificates: hs.certsFromClient, + handshakeHash: hs.finishedHash.server.Sum(nil), } m.ticket, err = c.encryptTicket(&state) if err != nil { @@ -787,7 +850,7 @@ func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version if (candidate.flags&suiteECDSA != 0) != ecdsaOk { continue } - if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 { + if !c.config.Bugs.SkipCipherVersionCheck && version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 { continue } if c.isDTLS && candidate.flags&suiteNoDTLS != 0 { diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go index 2e2eff4..f8ba1f8 100644 --- a/ssl/test/runner/key_agreement.go +++ b/ssl/test/runner/key_agreement.go @@ -87,7 +87,7 @@ func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello return nil, nil, err } ckx := new(clientKeyExchangeMsg) - if clientHello.vers != VersionSSL30 { + if clientHello.vers != VersionSSL30 && !config.Bugs.SSL3RSAKeyExchange { ckx.ciphertext = make([]byte, len(encrypted)+2) ckx.ciphertext[0] = byte(len(encrypted) >> 8) ckx.ciphertext[1] = byte(len(encrypted)) diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go index 991196f..6d0db97 100644 --- a/ssl/test/runner/prf.go +++ b/ssl/test/runner/prf.go @@ -120,6 +120,8 @@ var masterSecretLabel = []byte("master secret") var keyExpansionLabel = []byte("key expansion") var clientFinishedLabel = []byte("client finished") var serverFinishedLabel = []byte("server finished") +var channelIDLabel = []byte("TLS Channel ID signature\x00") +var channelIDResumeLabel = []byte("Resumption\x00") func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) { switch version { @@ -180,9 +182,9 @@ func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash { newHash = sha512.New384 } - return finishedHash{newHash(), newHash(), nil, nil, version, prf12(newHash)} + return finishedHash{newHash(), newHash(), nil, nil, []byte{}, version, prf12(newHash)} } - return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), version, prf10} + return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), []byte{}, version, prf10} } // A finishedHash calculates the hash of a set of handshake messages suitable @@ -195,11 +197,15 @@ type finishedHash struct { clientMD5 hash.Hash serverMD5 hash.Hash + // In TLS 1.2 (and SSL 3 for implementation convenience), a + // full buffer is required. + buffer []byte + version uint16 prf func(result, secret, label, seed []byte) } -func (h finishedHash) Write(msg []byte) (n int, err error) { +func (h *finishedHash) Write(msg []byte) (n int, err error) { h.client.Write(msg) h.server.Write(msg) @@ -207,14 +213,19 @@ func (h finishedHash) Write(msg []byte) (n int, err error) { h.clientMD5.Write(msg) h.serverMD5.Write(msg) } + + if h.buffer != nil { + h.buffer = append(h.buffer, msg...) + } + return len(msg), nil } // finishedSum30 calculates the contents of the verify_data member of a SSLv3 // Finished message given the MD5 and SHA1 hashes of a set of handshake // messages. -func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []byte { - md5.Write(magic[:]) +func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic []byte) []byte { + md5.Write(magic) md5.Write(masterSecret) md5.Write(ssl30Pad1[:]) md5Digest := md5.Sum(nil) @@ -225,7 +236,7 @@ func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []by md5.Write(md5Digest) md5Digest = md5.Sum(nil) - sha1.Write(magic[:]) + sha1.Write(magic) sha1.Write(masterSecret) sha1.Write(ssl30Pad1[:40]) sha1Digest := sha1.Sum(nil) @@ -249,7 +260,7 @@ var ssl3ServerFinishedMagic = [4]byte{0x53, 0x52, 0x56, 0x52} // Finished message. func (h finishedHash) clientSum(masterSecret []byte) []byte { if h.version == VersionSSL30 { - return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic) + return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic[:]) } out := make([]byte, finishedVerifyLength) @@ -269,7 +280,7 @@ func (h finishedHash) clientSum(masterSecret []byte) []byte { // Finished message. func (h finishedHash) serverSum(masterSecret []byte) []byte { if h.version == VersionSSL30 { - return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic) + return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic[:]) } out := make([]byte, finishedVerifyLength) @@ -290,7 +301,7 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte { func (h finishedHash) selectClientCertSignatureAlgorithm(serverList []signatureAndHash, sigType uint8) (signatureAndHash, error) { if h.version < VersionTLS12 { // Nothing to negotiate before TLS 1.2. - return signatureAndHash{sigType, 0}, nil + return signatureAndHash{signature: sigType}, nil } for _, v := range serverList { @@ -303,13 +314,24 @@ func (h finishedHash) selectClientCertSignatureAlgorithm(serverList []signatureA // hashForClientCertificate returns a digest, hash function, and TLS 1.2 hash // id suitable for signing by a TLS client certificate. -func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash) ([]byte, crypto.Hash, error) { +func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash, masterSecret []byte) ([]byte, crypto.Hash, error) { + if h.version == VersionSSL30 { + if signatureAndHash.signature != signatureRSA { + return nil, 0, errors.New("tls: unsupported signature type for client certificate") + } + + md5Hash := md5.New() + md5Hash.Write(h.buffer) + sha1Hash := sha1.New() + sha1Hash.Write(h.buffer) + return finishedSum30(md5Hash, sha1Hash, masterSecret, nil), crypto.MD5SHA1, nil + } if h.version >= VersionTLS12 { if signatureAndHash.hash != hashSHA256 { return nil, 0, errors.New("tls: unsupported hash function for client certificate") } - digest := h.server.Sum(nil) - return digest, crypto.SHA256, nil + digest := sha256.Sum256(h.buffer) + return digest[:], crypto.SHA256, nil } if signatureAndHash.signature == signatureECDSA { digest := h.server.Sum(nil) @@ -321,3 +343,23 @@ func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash digest = h.server.Sum(digest) return digest, crypto.MD5SHA1, nil } + +// hashForChannelID returns the hash to be signed for TLS Channel +// ID. If a resumption, resumeHash has the previous handshake +// hash. Otherwise, it is nil. +func (h finishedHash) hashForChannelID(resumeHash []byte) []byte { + hash := sha256.New() + hash.Write(channelIDLabel) + if resumeHash != nil { + hash.Write(channelIDResumeLabel) + hash.Write(resumeHash) + } + hash.Write(h.server.Sum(nil)) + return hash.Sum(nil) +} + +// discardHandshakeBuffer is called when there is no more need to +// buffer the entirety of the handshake messages. +func (h *finishedHash) discardHandshakeBuffer() { + h.buffer = nil +} diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 4a5b762..323f43f 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -2,7 +2,11 @@ package main import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" "crypto/x509" + "encoding/base64" + "encoding/pem" "flag" "fmt" "io" @@ -25,11 +29,14 @@ const ( ) const ( - rsaKeyFile = "key.pem" - ecdsaKeyFile = "ecdsa_key.pem" + rsaKeyFile = "key.pem" + ecdsaKeyFile = "ecdsa_key.pem" + channelIDKeyFile = "channel_id_key.pem" ) var rsaCertificate, ecdsaCertificate Certificate +var channelIDKey *ecdsa.PrivateKey +var channelIDBytes []byte func initCertificates() { var err error @@ -42,6 +49,26 @@ func initCertificates() { if err != nil { panic(err) } + + channelIDPEMBlock, err := ioutil.ReadFile(channelIDKeyFile) + if err != nil { + panic(err) + } + channelIDDERBlock, _ := pem.Decode(channelIDPEMBlock) + if channelIDDERBlock.Type != "EC PRIVATE KEY" { + panic("bad key type") + } + channelIDKey, err = x509.ParseECPrivateKey(channelIDDERBlock.Bytes) + if err != nil { + panic(err) + } + if channelIDKey.Curve != elliptic.P256() { + panic("bad curve") + } + + channelIDBytes = make([]byte, 64) + writeIntPadded(channelIDBytes[:32], channelIDKey.X) + writeIntPadded(channelIDBytes[32:], channelIDKey.Y) } var certificateOnce sync.Once @@ -70,6 +97,11 @@ const ( dtls ) +const ( + alpn = 1 + npn = 2 +) + type testCase struct { testType testType protocol protocol @@ -83,6 +115,18 @@ type testCase struct { // expectedVersion, if non-zero, specifies the TLS version that must be // negotiated. expectedVersion uint16 + // expectedResumeVersion, if non-zero, specifies the TLS version that + // must be negotiated on resumption. If zero, expectedVersion is used. + expectedResumeVersion uint16 + // expectChannelID controls whether the connection should have + // negotiated a Channel ID with channelIDKey. + expectChannelID bool + // expectedNextProto controls whether the connection should + // negotiate a next protocol via NPN or ALPN. + expectedNextProto string + // expectedNextProtoType, if non-zero, is the expected next + // protocol negotiation mechanism. + expectedNextProtoType int // messageLen is the length, in bytes, of the test message that will be // sent. messageLen int @@ -91,11 +135,19 @@ type testCase struct { // keyFile is the path to the private key to use for the server. keyFile string // resumeSession controls whether a second connection should be tested - // which resumes the first session. + // which attempts to resume the first session. resumeSession bool + // resumeConfig, if not nil, points to a Config to be used on + // resumption. SessionTicketKey and ClientSessionCache are copied from + // the initial connection's config. If nil, the initial connection's + // config is used. + resumeConfig *Config // sendPrefix sends a prefix on the socket before actually performing a // handshake. sendPrefix string + // shimWritesFirst controls whether the shim sends an initial "hello" + // message before doing a roundtrip with the runner. + shimWritesFirst bool // flags, if not empty, contains a list of command-line flags that will // be passed to the shim program. flags []string @@ -160,7 +212,7 @@ var testCases = []testCase{ expectedLocalError: "no fallback SCSV found", }, { - name: "FallbackSCSV", + name: "SendFallbackSCSV", config: Config{ Bugs: ProtocolBugs{ FailIfNotFallbackSCSV: true, @@ -169,36 +221,6 @@ var testCases = []testCase{ flags: []string{"-fallback-scsv"}, }, { - testType: serverTest, - name: "ServerNameExtension", - config: Config{ - ServerName: "example.com", - }, - flags: []string{"-expect-server-name", "example.com"}, - }, - { - testType: clientTest, - name: "DuplicateExtensionClient", - config: Config{ - Bugs: ProtocolBugs{ - DuplicateExtension: true, - }, - }, - shouldFail: true, - expectedLocalError: "remote error: error decoding message", - }, - { - testType: serverTest, - name: "DuplicateExtensionServer", - config: Config{ - Bugs: ProtocolBugs{ - DuplicateExtension: true, - }, - }, - shouldFail: true, - expectedLocalError: "remote error: error decoding message", - }, - { name: "ClientCertificateTypes", config: Config{ ClientAuth: RequestClientCert, @@ -208,11 +230,14 @@ var testCases = []testCase{ CertTypeECDSASign, }, }, - flags: []string{"-expect-certificate-types", string([]byte{ - CertTypeDSSSign, - CertTypeRSASign, - CertTypeECDSASign, - })}, + flags: []string{ + "-expect-certificate-types", + base64.StdEncoding.EncodeToString([]byte{ + CertTypeDSSSign, + CertTypeRSASign, + CertTypeECDSASign, + }), + }, }, { name: "NoClientCertificate", @@ -447,9 +472,21 @@ var testCases = []testCase{ shouldFail: true, expectedError: ":HTTPS_PROXY_REQUEST:", }, + { + name: "SkipCipherVersionCheck", + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + MaxVersion: VersionTLS11, + Bugs: ProtocolBugs{ + SkipCipherVersionCheck: true, + }, + }, + shouldFail: true, + expectedError: ":WRONG_CIPHER_RETURNED:", + }, } -func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error { +func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, isResume bool) error { if test.protocol == dtls { conn = newPacketAdaptor(conn) } @@ -480,8 +517,50 @@ func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) e return err } - if vers := tlsConn.ConnectionState().Version; test.expectedVersion != 0 && vers != test.expectedVersion { - return fmt.Errorf("got version %x, expected %x", vers, test.expectedVersion) + // TODO(davidben): move all per-connection expectations into a dedicated + // expectations struct that can be specified separately for the two + // legs. + expectedVersion := test.expectedVersion + if isResume && test.expectedResumeVersion != 0 { + expectedVersion = test.expectedResumeVersion + } + if vers := tlsConn.ConnectionState().Version; expectedVersion != 0 && vers != expectedVersion { + return fmt.Errorf("got version %x, expected %x", vers, expectedVersion) + } + + if test.expectChannelID { + channelID := tlsConn.ConnectionState().ChannelID + if channelID == nil { + return fmt.Errorf("no channel ID negotiated") + } + if channelID.Curve != channelIDKey.Curve || + channelIDKey.X.Cmp(channelIDKey.X) != 0 || + channelIDKey.Y.Cmp(channelIDKey.Y) != 0 { + return fmt.Errorf("incorrect channel ID") + } + } + + if expected := test.expectedNextProto; expected != "" { + if actual := tlsConn.ConnectionState().NegotiatedProtocol; actual != expected { + return fmt.Errorf("next proto mismatch: got %s, wanted %s", actual, expected) + } + } + + if test.expectedNextProtoType != 0 { + if (test.expectedNextProtoType == alpn) != tlsConn.ConnectionState().NegotiatedProtocolFromALPN { + return fmt.Errorf("next proto type mismatch") + } + } + + if test.shimWritesFirst { + var buf [5]byte + _, err := io.ReadFull(tlsConn, buf[:]) + if err != nil { + return err + } + if string(buf[:]) != "hello" { + return fmt.Errorf("bad initial message") + } } if messageLen < 0 { @@ -601,6 +680,10 @@ func runTest(test *testCase, buildDir string) error { flags = append(flags, "-resume") } + if test.shimWritesFirst { + flags = append(flags, "-shim-writes-first") + } + flags = append(flags, test.flags...) var shim *exec.Cmd @@ -630,12 +713,25 @@ func runTest(test *testCase, buildDir string) error { } } - err := doExchange(test, &config, conn, test.messageLen) + err := doExchange(test, &config, conn, test.messageLen, + false /* not a resumption */) conn.Close() if err == nil && test.resumeSession { - err = doExchange(test, &config, connResume, test.messageLen) - connResume.Close() + var resumeConfig Config + if test.resumeConfig != nil { + resumeConfig = *test.resumeConfig + if len(resumeConfig.Certificates) == 0 { + resumeConfig.Certificates = []Certificate{getRSACertificate()} + } + resumeConfig.SessionTicketKey = config.SessionTicketKey + resumeConfig.ClientSessionCache = config.ClientSessionCache + } else { + resumeConfig = config + } + err = doExchange(test, &resumeConfig, connResume, test.messageLen, + true /* resumption */) } + connResume.Close() childErr := shim.Wait() @@ -697,25 +793,40 @@ var testCipherSuites = []struct { {"3DES-SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA}, {"AES128-GCM", TLS_RSA_WITH_AES_128_GCM_SHA256}, {"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA}, + {"AES128-SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256}, {"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384}, {"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA}, + {"AES256-SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256}, {"DHE-RSA-AES128-GCM", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, {"DHE-RSA-AES128-SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA}, + {"DHE-RSA-AES128-SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256}, {"DHE-RSA-AES256-GCM", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384}, {"DHE-RSA-AES256-SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA}, + {"DHE-RSA-AES256-SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256}, {"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, {"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, + {"ECDHE-ECDSA-AES128-SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, + {"ECDHE-ECDSA-AES256-GCM", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, {"ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + {"ECDHE-ECDSA-AES256-SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384}, {"ECDHE-ECDSA-RC4-SHA", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, {"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, {"ECDHE-RSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, + {"ECDHE-RSA-AES128-SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256}, {"ECDHE-RSA-AES256-GCM", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, {"ECDHE-RSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, + {"ECDHE-RSA-AES256-SHA384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384}, {"ECDHE-RSA-RC4-SHA", TLS_ECDHE_RSA_WITH_RC4_128_SHA}, {"RC4-MD5", TLS_RSA_WITH_RC4_128_MD5}, {"RC4-SHA", TLS_RSA_WITH_RC4_128_SHA}, } +func isTLS12Only(suiteName string) bool { + return strings.HasSuffix(suiteName, "-GCM") || + strings.HasSuffix(suiteName, "-SHA256") || + strings.HasSuffix(suiteName, "-SHA384") +} + func addCipherSuiteTests() { for _, suite := range testCipherSuites { var cert Certificate @@ -732,7 +843,7 @@ func addCipherSuiteTests() { } for _, ver := range tlsVersions { - if ver.version != VersionTLS12 && strings.HasSuffix(suite.name, "-GCM") { + if ver.version < VersionTLS12 && isTLS12Only(suite.name) { continue } @@ -902,29 +1013,14 @@ func addClientAuthTests() { certPool.AddCert(cert) for _, ver := range tlsVersions { - if ver.version == VersionSSL30 { - // TODO(davidben): The Go implementation does not - // correctly compute CertificateVerify hashes for SSLv3. - continue - } - - var cipherSuites []uint16 - if ver.version >= VersionTLS12 { - // Pick a SHA-256 cipher suite. The Go implementation - // does not correctly handle client auth with a SHA-384 - // cipher suite. - cipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} - } - testCases = append(testCases, testCase{ testType: clientTest, name: ver.name + "-Client-ClientAuth-RSA", config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - CipherSuites: cipherSuites, - ClientAuth: RequireAnyClientCert, - ClientCAs: certPool, + MinVersion: ver.version, + MaxVersion: ver.version, + ClientAuth: RequireAnyClientCert, + ClientCAs: certPool, }, flags: []string{ "-cert-file", rsaCertificateFile, @@ -932,36 +1028,41 @@ func addClientAuthTests() { }, }) testCases = append(testCases, testCase{ - testType: clientTest, - name: ver.name + "-Client-ClientAuth-ECDSA", - config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - CipherSuites: cipherSuites, - ClientAuth: RequireAnyClientCert, - ClientCAs: certPool, - }, - flags: []string{ - "-cert-file", ecdsaCertificateFile, - "-key-file", ecdsaKeyFile, - }, - }) - testCases = append(testCases, testCase{ testType: serverTest, name: ver.name + "-Server-ClientAuth-RSA", config: Config{ + MinVersion: ver.version, + MaxVersion: ver.version, Certificates: []Certificate{rsaCertificate}, }, flags: []string{"-require-any-client-certificate"}, }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: ver.name + "-Server-ClientAuth-ECDSA", - config: Config{ - Certificates: []Certificate{ecdsaCertificate}, - }, - flags: []string{"-require-any-client-certificate"}, - }) + if ver.version != VersionSSL30 { + testCases = append(testCases, testCase{ + testType: serverTest, + name: ver.name + "-Server-ClientAuth-ECDSA", + config: Config{ + MinVersion: ver.version, + MaxVersion: ver.version, + Certificates: []Certificate{ecdsaCertificate}, + }, + flags: []string{"-require-any-client-certificate"}, + }) + testCases = append(testCases, testCase{ + testType: clientTest, + name: ver.name + "-Client-ClientAuth-ECDSA", + config: Config{ + MinVersion: ver.version, + MaxVersion: ver.version, + ClientAuth: RequireAnyClientCert, + ClientCAs: certPool, + }, + flags: []string{ + "-cert-file", ecdsaCertificateFile, + "-key-file", ecdsaKeyFile, + }, + }) + } } } @@ -1029,8 +1130,7 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) testType: clientTest, name: "ClientAuth-Client" + suffix, config: Config{ - CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - ClientAuth: RequireAnyClientCert, + ClientAuth: RequireAnyClientCert, Bugs: ProtocolBugs{ MaxHandshakeRecordLength: maxHandshakeRecordLength, }, @@ -1080,13 +1180,14 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) protocol: protocol, name: "NPN-Client" + suffix, config: Config{ - CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - NextProtos: []string{"foo"}, + NextProtos: []string{"foo"}, Bugs: ProtocolBugs{ MaxHandshakeRecordLength: maxHandshakeRecordLength, }, }, - flags: append(flags, "-select-next-proto", "foo"), + flags: append(flags, "-select-next-proto", "foo"), + expectedNextProto: "foo", + expectedNextProtoType: npn, }) testCases = append(testCases, testCase{ protocol: protocol, @@ -1101,6 +1202,8 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) flags: append(flags, "-advertise-npn", "\x03foo\x03bar\x03baz", "-expect-next-proto", "bar"), + expectedNextProto: "bar", + expectedNextProtoType: npn, }) // Client does False Start and negotiates NPN. @@ -1111,13 +1214,34 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, Bugs: ProtocolBugs{ + ExpectFalseStart: true, MaxHandshakeRecordLength: maxHandshakeRecordLength, }, }, flags: append(flags, "-false-start", "-select-next-proto", "foo"), - resumeSession: true, + shimWritesFirst: true, + resumeSession: true, + }) + + // Client does False Start and negotiates ALPN. + testCases = append(testCases, testCase{ + protocol: protocol, + name: "FalseStart-ALPN" + suffix, + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + NextProtos: []string{"foo"}, + Bugs: ProtocolBugs{ + ExpectFalseStart: true, + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, + "-false-start", + "-advertise-alpn", "\x03foo"), + shimWritesFirst: true, + resumeSession: true, }) // False Start without session tickets. @@ -1127,14 +1251,19 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"foo"}, SessionTicketsDisabled: true, + Bugs: ProtocolBugs{ + ExpectFalseStart: true, + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, }, - flags: []string{ + flags: append(flags, "-false-start", "-select-next-proto", "foo", - }, + ), + shimWritesFirst: true, }) - // Client sends a V2ClientHello. + // Server parses a V2ClientHello. testCases = append(testCases, testCase{ protocol: protocol, testType: serverTest, @@ -1151,6 +1280,42 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) }, flags: flags, }) + + // Client sends a Channel ID. + testCases = append(testCases, testCase{ + protocol: protocol, + name: "ChannelID-Client" + suffix, + config: Config{ + RequestChannelID: true, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, + "-send-channel-id", channelIDKeyFile, + ), + resumeSession: true, + expectChannelID: true, + }) + + // Server accepts a Channel ID. + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "ChannelID-Server" + suffix, + config: Config{ + ChannelID: channelIDKey, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: append(flags, + "-expect-channel-id", + base64.StdEncoding.EncodeToString(channelIDBytes), + ), + resumeSession: true, + expectChannelID: true, + }) } else { testCases = append(testCases, testCase{ protocol: protocol, @@ -1216,6 +1381,231 @@ func addVersionNegotiationTests() { } } +func addD5BugTests() { + testCases = append(testCases, testCase{ + testType: serverTest, + name: "D5Bug-NoQuirk-Reject", + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + SSL3RSAKeyExchange: true, + }, + }, + shouldFail: true, + expectedError: ":TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG:", + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "D5Bug-Quirk-Normal", + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + }, + flags: []string{"-tls-d5-bug"}, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "D5Bug-Quirk-Bug", + config: Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + SSL3RSAKeyExchange: true, + }, + }, + flags: []string{"-tls-d5-bug"}, + }) +} + +func addExtensionTests() { + testCases = append(testCases, testCase{ + testType: clientTest, + name: "DuplicateExtensionClient", + config: Config{ + Bugs: ProtocolBugs{ + DuplicateExtension: true, + }, + }, + shouldFail: true, + expectedLocalError: "remote error: error decoding message", + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "DuplicateExtensionServer", + config: Config{ + Bugs: ProtocolBugs{ + DuplicateExtension: true, + }, + }, + shouldFail: true, + expectedLocalError: "remote error: error decoding message", + }) + testCases = append(testCases, testCase{ + testType: clientTest, + name: "ServerNameExtensionClient", + config: Config{ + Bugs: ProtocolBugs{ + ExpectServerName: "example.com", + }, + }, + flags: []string{"-host-name", "example.com"}, + }) + testCases = append(testCases, testCase{ + testType: clientTest, + name: "ServerNameExtensionClient", + config: Config{ + Bugs: ProtocolBugs{ + ExpectServerName: "mismatch.com", + }, + }, + flags: []string{"-host-name", "example.com"}, + shouldFail: true, + expectedLocalError: "tls: unexpected server name", + }) + testCases = append(testCases, testCase{ + testType: clientTest, + name: "ServerNameExtensionClient", + config: Config{ + Bugs: ProtocolBugs{ + ExpectServerName: "missing.com", + }, + }, + shouldFail: true, + expectedLocalError: "tls: unexpected server name", + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "ServerNameExtensionServer", + config: Config{ + ServerName: "example.com", + }, + flags: []string{"-expect-server-name", "example.com"}, + resumeSession: true, + }) + testCases = append(testCases, testCase{ + testType: clientTest, + name: "ALPNClient", + config: Config{ + NextProtos: []string{"foo"}, + }, + flags: []string{ + "-advertise-alpn", "\x03foo\x03bar\x03baz", + "-expect-alpn", "foo", + }, + expectedNextProto: "foo", + expectedNextProtoType: alpn, + resumeSession: true, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "ALPNServer", + config: Config{ + NextProtos: []string{"foo", "bar", "baz"}, + }, + flags: []string{ + "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", + "-select-alpn", "foo", + }, + expectedNextProto: "foo", + expectedNextProtoType: alpn, + resumeSession: true, + }) + // Test that the server prefers ALPN over NPN. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "ALPNServer-Preferred", + config: Config{ + NextProtos: []string{"foo", "bar", "baz"}, + }, + flags: []string{ + "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", + "-select-alpn", "foo", + "-advertise-npn", "\x03foo\x03bar\x03baz", + }, + expectedNextProto: "foo", + expectedNextProtoType: alpn, + resumeSession: true, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "ALPNServer-Preferred-Swapped", + config: Config{ + NextProtos: []string{"foo", "bar", "baz"}, + Bugs: ProtocolBugs{ + SwapNPNAndALPN: true, + }, + }, + flags: []string{ + "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", + "-select-alpn", "foo", + "-advertise-npn", "\x03foo\x03bar\x03baz", + }, + expectedNextProto: "foo", + expectedNextProtoType: alpn, + resumeSession: true, + }) +} + +func addResumptionVersionTests() { + // TODO(davidben): Once DTLS 1.2 is working, test that as well. + for _, sessionVers := range tlsVersions { + // TODO(davidben): SSLv3 is omitted here because runner does not + // support resumption with session IDs. + if sessionVers.version == VersionSSL30 { + continue + } + for _, resumeVers := range tlsVersions { + if resumeVers.version == VersionSSL30 { + continue + } + suffix := "-" + sessionVers.name + "-" + resumeVers.name + + // TODO(davidben): Write equivalent tests for the server + // and clean up the server's logic. This requires being + // able to give the shim a different set of SSL_OP_NO_* + // flags between the initial connection and the + // resume. Perhaps resumption should be tested by + // serializing the SSL_SESSION and starting a second + // shim. + testCases = append(testCases, testCase{ + name: "Resume-Client" + suffix, + resumeSession: true, + config: Config{ + MaxVersion: sessionVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + Bugs: ProtocolBugs{ + AllowSessionVersionMismatch: true, + }, + }, + expectedVersion: sessionVers.version, + resumeConfig: &Config{ + MaxVersion: resumeVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + Bugs: ProtocolBugs{ + AllowSessionVersionMismatch: true, + }, + }, + expectedResumeVersion: resumeVers.version, + }) + + testCases = append(testCases, testCase{ + name: "Resume-Client-NoResume" + suffix, + flags: []string{"-expect-session-miss"}, + resumeSession: true, + config: Config{ + MaxVersion: sessionVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + }, + expectedVersion: sessionVers.version, + resumeConfig: &Config{ + MaxVersion: resumeVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + SessionTicketsDisabled: true, + }, + expectedResumeVersion: resumeVers.version, + }) + } + } +} + func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) { defer wg.Done() @@ -1268,6 +1658,9 @@ func main() { addCBCSplittingTests() addClientAuthTests() addVersionNegotiationTests() + addD5BugTests() + addExtensionTests() + addResumptionVersionTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { for _, protocol := range []protocol{tls, dtls} { diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go index 519543b..74791d6 100644 --- a/ssl/test/runner/ticket.go +++ b/ssl/test/runner/ticket.go @@ -18,10 +18,11 @@ import ( // sessionState contains the information that is serialized into a session // ticket in order to later resume a connection. type sessionState struct { - vers uint16 - cipherSuite uint16 - masterSecret []byte - certificates [][]byte + vers uint16 + cipherSuite uint16 + masterSecret []byte + handshakeHash []byte + certificates [][]byte } func (s *sessionState) equal(i interface{}) bool { @@ -32,7 +33,8 @@ func (s *sessionState) equal(i interface{}) bool { if s.vers != s1.vers || s.cipherSuite != s1.cipherSuite || - !bytes.Equal(s.masterSecret, s1.masterSecret) { + !bytes.Equal(s.masterSecret, s1.masterSecret) || + !bytes.Equal(s.handshakeHash, s1.handshakeHash) { return false } @@ -50,7 +52,7 @@ func (s *sessionState) equal(i interface{}) bool { } func (s *sessionState) marshal() []byte { - length := 2 + 2 + 2 + len(s.masterSecret) + 2 + length := 2 + 2 + 2 + len(s.masterSecret) + 2 + len(s.handshakeHash) + 2 for _, cert := range s.certificates { length += 4 + len(cert) } @@ -67,6 +69,12 @@ func (s *sessionState) marshal() []byte { copy(x, s.masterSecret) x = x[len(s.masterSecret):] + x[0] = byte(len(s.handshakeHash) >> 8) + x[1] = byte(len(s.handshakeHash)) + x = x[2:] + copy(x, s.handshakeHash) + x = x[len(s.handshakeHash):] + x[0] = byte(len(s.certificates) >> 8) x[1] = byte(len(s.certificates)) x = x[2:] @@ -103,6 +111,19 @@ func (s *sessionState) unmarshal(data []byte) bool { return false } + handshakeHashLen := int(data[0])<<8 | int(data[1]) + data = data[2:] + if len(data) < handshakeHashLen { + return false + } + + s.handshakeHash = data[:handshakeHashLen] + data = data[handshakeHashLen:] + + if len(data) < 2 { + return false + } + numCerts := int(data[0])<<8 | int(data[1]) data = data[2:] diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 9716227..70543cc 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -16,6 +16,9 @@ #include <string.h> +#include <memory> + +#include <openssl/base64.h> namespace { @@ -50,25 +53,37 @@ const BoolFlag kBoolFlags[] = { { "-no-tls1", &TestConfig::no_tls1 }, { "-no-ssl3", &TestConfig::no_ssl3 }, { "-cookie-exchange", &TestConfig::cookie_exchange }, + { "-shim-writes-first", &TestConfig::shim_writes_first }, + { "-tls-d5-bug", &TestConfig::tls_d5_bug }, + { "-expect-session-miss", &TestConfig::expect_session_miss }, }; const size_t kNumBoolFlags = sizeof(kBoolFlags) / sizeof(kBoolFlags[0]); -// TODO(davidben): Some of these should be in a new kBase64Flags to allow NUL -// bytes. const StringFlag kStringFlags[] = { { "-key-file", &TestConfig::key_file }, { "-cert-file", &TestConfig::cert_file }, { "-expect-server-name", &TestConfig::expected_server_name }, - // Conveniently, 00 is not a certificate type. - { "-expect-certificate-types", &TestConfig::expected_certificate_types }, { "-advertise-npn", &TestConfig::advertise_npn }, { "-expect-next-proto", &TestConfig::expected_next_proto }, { "-select-next-proto", &TestConfig::select_next_proto }, + { "-send-channel-id", &TestConfig::send_channel_id }, + { "-host-name", &TestConfig::host_name }, + { "-advertise-alpn", &TestConfig::advertise_alpn }, + { "-expect-alpn", &TestConfig::expected_alpn }, + { "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn }, + { "-select-alpn", &TestConfig::select_alpn }, }; const size_t kNumStringFlags = sizeof(kStringFlags) / sizeof(kStringFlags[0]); +const StringFlag kBase64Flags[] = { + { "-expect-certificate-types", &TestConfig::expected_certificate_types }, + { "-expect-channel-id", &TestConfig::expected_channel_id }, +}; + +const size_t kNumBase64Flags = sizeof(kBase64Flags) / sizeof(kBase64Flags[0]); + } // namespace TestConfig::TestConfig() @@ -86,7 +101,10 @@ TestConfig::TestConfig() no_tls11(false), no_tls1(false), no_ssl3(false), - cookie_exchange(false) { + cookie_exchange(false), + shim_writes_first(false), + tls_d5_bug(false), + expect_session_miss(false) { } bool ParseConfig(int argc, char **argv, TestConfig *out_config) { @@ -117,6 +135,32 @@ bool ParseConfig(int argc, char **argv, TestConfig *out_config) { continue; } + for (j = 0; j < kNumBase64Flags; j++) { + if (strcmp(argv[i], kBase64Flags[j].flag) == 0) { + break; + } + } + if (j < kNumBase64Flags) { + i++; + if (i >= argc) { + fprintf(stderr, "Missing parameter\n"); + return false; + } + size_t len; + if (!EVP_DecodedLength(&len, strlen(argv[i]))) { + fprintf(stderr, "Invalid base64: %s\n", argv[i]); + } + std::unique_ptr<uint8_t[]> decoded(new uint8_t[len]); + if (!EVP_DecodeBase64(decoded.get(), &len, len, + reinterpret_cast<const uint8_t *>(argv[i]), + strlen(argv[i]))) { + fprintf(stderr, "Invalid base64: %s\n", argv[i]); + } + out_config->*(kBase64Flags[j].member) = std::string( + reinterpret_cast<const char *>(decoded.get()), len); + continue; + } + fprintf(stderr, "Unknown argument: %s\n", argv[i]); return false; } diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 34d720e..acce504 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -43,6 +43,16 @@ struct TestConfig { bool no_tls1; bool no_ssl3; bool cookie_exchange; + std::string expected_channel_id; + std::string send_channel_id; + bool shim_writes_first; + bool tls_d5_bug; + std::string host_name; + std::string advertise_alpn; + std::string expected_alpn; + std::string expected_advertised_alpn; + std::string select_alpn; + bool expect_session_miss; }; bool ParseConfig(int argc, char **argv, TestConfig *out_config); |