diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index a4bdef80..01b7581d 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -129,8 +129,12 @@ const ( // Hash functions for TLS 1.2 (See RFC 5246, section A.4.1) const ( + hashMD5 uint8 = 1 hashSHA1 uint8 = 2 + hashSHA224 uint8 = 3 hashSHA256 uint8 = 4 + hashSHA384 uint8 = 5 + hashSHA512 uint8 = 6 ) // Signature algorithms for TLS 1.2 (See RFC 5246, section A.4.1) @@ -346,6 +350,11 @@ type Config struct { // protection profiles to offer in DTLS-SRTP. SRTPProtectionProfiles []uint16 + // SignatureAndHashes, if not nil, overrides the default set of + // supported signature and hash algorithms to advertise in + // CertificateRequest. + SignatureAndHashes []signatureAndHash + // Bugs specifies optional misbehaviour to be used for testing other // implementations. Bugs ProtocolBugs @@ -541,6 +550,14 @@ type ProtocolBugs struct { // SendSRTPProtectionProfile, if non-zero, is the SRTP profile that the // server sends in the ServerHello instead of the negotiated one. SendSRTPProtectionProfile uint16 + + // NoSignatureAndHashes, if true, causes the client to omit the + // signature and hashes extension. + // + // For a server, it will cause an empty list to be sent in the + // CertificateRequest message. None the less, the configured set will + // still be enforced. + NoSignatureAndHashes bool } func (c *Config) serverInit() { @@ -655,6 +672,20 @@ func (c *Config) getCertificateForName(name string) *Certificate { return &c.Certificates[0] } +func (c *Config) signatureAndHashesForServer() []signatureAndHash { + if c != nil && c.SignatureAndHashes != nil { + return c.SignatureAndHashes + } + return supportedClientCertSignatureAlgorithms +} + +func (c *Config) signatureAndHashesForClient() []signatureAndHash { + if c != nil && c.SignatureAndHashes != nil { + return c.SignatureAndHashes + } + return supportedSKXSignatureAlgorithms +} + // BuildNameToCertificate parses c.Certificates and builds c.NameToCertificate // from the CommonName and SubjectAlternateName fields of each of the leaf // certificates. @@ -806,3 +837,12 @@ func initDefaultCipherSuites() { func unexpectedMessageError(wanted, got interface{}) error { return fmt.Errorf("tls: received unexpected handshake message of type %T when waiting for %T", got, wanted) } + +func isSupportedSignatureAndHash(sigHash signatureAndHash, sigHashes []signatureAndHash) bool { + for _, s := range sigHashes { + if s == sigHash { + return true + } + } + return false +} diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index 71712a90..c4dff89a 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go @@ -129,8 +129,8 @@ NextCipherSuite: return errors.New("tls: short read from Rand: " + err.Error()) } - if hello.vers >= VersionTLS12 { - hello.signatureAndHashes = supportedSKXSignatureAlgorithms + if hello.vers >= VersionTLS12 && !c.config.Bugs.NoSignatureAndHashes { + hello.signatureAndHashes = c.config.signatureAndHashesForClient() } var session *ClientSessionState diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index bd6f7022..32814d3b 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -467,7 +467,9 @@ func (hs *serverHandshakeState) doFullHandshake() error { } if c.vers >= VersionTLS12 { certReq.hasSignatureAndHash = true - certReq.signatureAndHashes = supportedClientCertSignatureAlgorithms + if !config.Bugs.NoSignatureAndHashes { + certReq.signatureAndHashes = config.signatureAndHashesForServer() + } } // An empty list of certificateAuthorities signals to @@ -567,6 +569,9 @@ func (hs *serverHandshakeState) doFullHandshake() error { var signatureAndHash signatureAndHash if certVerify.hasSignatureAndHash { signatureAndHash = certVerify.signatureAndHash + if !isSupportedSignatureAndHash(signatureAndHash, config.signatureAndHashesForServer()) { + return errors.New("tls: unsupported hash function for client certificate") + } } else { // Before TLS 1.2 the signature algorithm was implicit // from the key type, and only one hash per signature diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go index 47f34cb9..d59168f9 100644 --- a/ssl/test/runner/key_agreement.go +++ b/ssl/test/runner/key_agreement.go @@ -12,7 +12,6 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha1" - "crypto/sha256" "crypto/x509" "encoding/asn1" "errors" @@ -125,28 +124,20 @@ func md5SHA1Hash(slices [][]byte) []byte { return md5sha1 } -// sha256Hash implements TLS 1.2's hash function. -func sha256Hash(slices [][]byte) []byte { - h := sha256.New() - for _, slice := range slices { - h.Write(slice) - } - return h.Sum(nil) -} - // hashForServerKeyExchange hashes the given slices and returns their digest // and the identifier of the hash function used. The hashFunc argument is only // used for >= TLS 1.2 and precisely identifies the hash function to use. func hashForServerKeyExchange(sigType, hashFunc uint8, version uint16, slices ...[]byte) ([]byte, crypto.Hash, error) { if version >= VersionTLS12 { - switch hashFunc { - case hashSHA256: - return sha256Hash(slices), crypto.SHA256, nil - case hashSHA1: - return sha1Hash(slices), crypto.SHA1, nil - default: - return nil, crypto.Hash(0), errors.New("tls: unknown hash function used by peer") + hash, err := lookupTLSHash(hashFunc) + if err != nil { + return nil, 0, err } + h := hash.New() + for _, slice := range slices { + h.Write(slice) + } + return h.Sum(nil), hash, nil } if sigType == signatureECDSA { return sha1Hash(slices), crypto.SHA1, nil @@ -307,6 +298,10 @@ func (ka *signedKeyAgreement) verifyParameters(config *Config, clientHello *clie if len(sig) < 2 { return errServerKeyExchange } + + if !isSupportedSignatureAndHash(signatureAndHash{ka.sigType, tls12HashId}, config.signatureAndHashesForClient()) { + return errors.New("tls: unsupported hash function for ServerKeyExchange") + } } sigLen := int(sig[0])<<8 | int(sig[1]) if sigLen+2 != len(sig) { diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go index d45c080d..75a89331 100644 --- a/ssl/test/runner/prf.go +++ b/ssl/test/runner/prf.go @@ -185,6 +185,27 @@ func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clie return } +// lookupTLSHash looks up the corresponding crypto.Hash for a given +// TLS hash identifier. +func lookupTLSHash(hash uint8) (crypto.Hash, error) { + switch hash { + case hashMD5: + return crypto.MD5, nil + case hashSHA1: + return crypto.SHA1, nil + case hashSHA224: + return crypto.SHA224, nil + case hashSHA256: + return crypto.SHA256, nil + case hashSHA384: + return crypto.SHA384, nil + case hashSHA512: + return crypto.SHA512, nil + default: + return 0, errors.New("tls: unsupported hash algorithm") + } +} + func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash { if version >= VersionTLS12 { newHash := sha256.New @@ -331,11 +352,13 @@ func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash 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") + hashAlg, err := lookupTLSHash(signatureAndHash.hash) + if err != nil { + return nil, 0, err } - digest := sha256.Sum256(h.buffer) - return digest[:], crypto.SHA256, nil + hash := hashAlg.New() + hash.Write(h.buffer) + return hash.Sum(nil), hashAlg, nil } if signatureAndHash.signature == signatureECDSA { return h.server.Sum(nil), crypto.SHA1, nil diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 8c661a62..dca0479b 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -2030,6 +2030,114 @@ func addDTLSReplayTests() { }) } +var testHashes = []struct { + name string + id uint8 +}{ + {"SHA1", hashSHA1}, + {"SHA224", hashSHA224}, + {"SHA256", hashSHA256}, + {"SHA384", hashSHA384}, + {"SHA512", hashSHA512}, +} + +func addSigningHashTests() { + // Make sure each hash works. Include some fake hashes in the list and + // ensure they're ignored. + for _, hash := range testHashes { + testCases = append(testCases, testCase{ + name: "SigningHash-ClientAuth-" + hash.name, + config: Config{ + ClientAuth: RequireAnyClientCert, + SignatureAndHashes: []signatureAndHash{ + {signatureRSA, 42}, + {signatureRSA, hash.id}, + {signatureRSA, 255}, + }, + }, + flags: []string{ + "-cert-file", rsaCertificateFile, + "-key-file", rsaKeyFile, + }, + }) + + testCases = append(testCases, testCase{ + testType: serverTest, + name: "SigningHash-ServerKeyExchange-Sign-" + hash.name, + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + SignatureAndHashes: []signatureAndHash{ + {signatureRSA, 42}, + {signatureRSA, hash.id}, + {signatureRSA, 255}, + }, + }, + }) + } + + // Test that hash resolution takes the signature type into account. + testCases = append(testCases, testCase{ + name: "SigningHash-ClientAuth-SignatureType", + config: Config{ + ClientAuth: RequireAnyClientCert, + SignatureAndHashes: []signatureAndHash{ + {signatureECDSA, hashSHA512}, + {signatureRSA, hashSHA384}, + {signatureECDSA, hashSHA1}, + }, + }, + flags: []string{ + "-cert-file", rsaCertificateFile, + "-key-file", rsaKeyFile, + }, + }) + + testCases = append(testCases, testCase{ + testType: serverTest, + name: "SigningHash-ServerKeyExchange-SignatureType", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + SignatureAndHashes: []signatureAndHash{ + {signatureECDSA, hashSHA512}, + {signatureRSA, hashSHA384}, + {signatureECDSA, hashSHA1}, + }, + }, + }) + + // Test that, if the list is missing, the peer falls back to SHA-1. + testCases = append(testCases, testCase{ + name: "SigningHash-ClientAuth-Fallback", + config: Config{ + ClientAuth: RequireAnyClientCert, + SignatureAndHashes: []signatureAndHash{ + {signatureRSA, hashSHA1}, + }, + Bugs: ProtocolBugs{ + NoSignatureAndHashes: true, + }, + }, + flags: []string{ + "-cert-file", rsaCertificateFile, + "-key-file", rsaKeyFile, + }, + }) + + testCases = append(testCases, testCase{ + testType: serverTest, + name: "SigningHash-ServerKeyExchange-Fallback", + config: Config{ + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + SignatureAndHashes: []signatureAndHash{ + {signatureRSA, hashSHA1}, + }, + Bugs: ProtocolBugs{ + NoSignatureAndHashes: true, + }, + }, + }) +} + func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) { defer wg.Done() @@ -2088,6 +2196,7 @@ func main() { addExtendedMasterSecretTests() addRenegotiationTests() addDTLSReplayTests() + addSigningHashTests() for _, async := range []bool{false, true} { for _, splitHandshake := range []bool{false, true} { for _, protocol := range []protocol{tls, dtls} {