]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/tls: support crypto.MessageSigner private keys
authorFilippo Valsorda <filippo@golang.org>
Wed, 26 Nov 2025 20:11:35 +0000 (21:11 +0100)
committerGopher Robot <gobot@golang.org>
Wed, 26 Nov 2025 23:17:42 +0000 (15:17 -0800)
Fixes #75656

Change-Id: I6bc71c80973765ef995d17b1450ea2026a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/724820
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
doc/next/6-stdlib/99-minor/crypto/tls/75656.md [new file with mode: 0644]
src/crypto/tls/auth.go
src/crypto/tls/common.go
src/crypto/tls/handshake_client.go
src/crypto/tls/handshake_client_tls13.go
src/crypto/tls/handshake_server.go
src/crypto/tls/handshake_server_tls13.go
src/crypto/tls/key_agreement.go
src/crypto/tls/prf.go
src/crypto/tls/tls_test.go

diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/75656.md b/doc/next/6-stdlib/99-minor/crypto/tls/75656.md
new file mode 100644 (file)
index 0000000..a2b8d9b
--- /dev/null
@@ -0,0 +1,2 @@
+If [Certificate.PrivateKey] implements [crypto.MessageSigner], its SignMessage
+method is used instead of Sign in TLS 1.2 and later.
index 7169e471056afa38820dab913f32e6d296f970ec..1b26dd50ef8c2eb3d5acee78082e5038c1f4dfdc 100644 (file)
@@ -18,9 +18,13 @@ import (
        "slices"
 )
 
-// verifyHandshakeSignature verifies a signature against pre-hashed
-// (if required) handshake contents.
+// verifyHandshakeSignature verifies a signature against unhashed handshake contents.
 func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, signed, sig []byte) error {
+       if hashFunc != directSigning {
+               h := hashFunc.New()
+               h.Write(signed)
+               signed = h.Sum(nil)
+       }
        switch sigType {
        case signatureECDSA:
                pubKey, ok := pubkey.(*ecdsa.PublicKey)
@@ -61,6 +65,32 @@ func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc c
        return nil
 }
 
+// verifyLegacyHandshakeSignature verifies a TLS 1.0 and 1.1 signature against
+// pre-hashed handshake contents.
+func verifyLegacyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, hashed, sig []byte) error {
+       switch sigType {
+       case signatureECDSA:
+               pubKey, ok := pubkey.(*ecdsa.PublicKey)
+               if !ok {
+                       return fmt.Errorf("expected an ECDSA public key, got %T", pubkey)
+               }
+               if !ecdsa.VerifyASN1(pubKey, hashed, sig) {
+                       return errors.New("ECDSA verification failure")
+               }
+       case signaturePKCS1v15:
+               pubKey, ok := pubkey.(*rsa.PublicKey)
+               if !ok {
+                       return fmt.Errorf("expected an RSA public key, got %T", pubkey)
+               }
+               if err := rsa.VerifyPKCS1v15(pubKey, hashFunc, hashed, sig); err != nil {
+                       return err
+               }
+       default:
+               return errors.New("internal error: unknown signature type")
+       }
+       return nil
+}
+
 const (
        serverSignatureContext = "TLS 1.3, server CertificateVerify\x00"
        clientSignatureContext = "TLS 1.3, client CertificateVerify\x00"
@@ -77,21 +107,15 @@ var signaturePadding = []byte{
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
 }
 
-// signedMessage returns the pre-hashed (if necessary) message to be signed by
-// certificate keys in TLS 1.3. See RFC 8446, Section 4.4.3.
-func signedMessage(sigHash crypto.Hash, context string, transcript hash.Hash) []byte {
-       if sigHash == directSigning {
-               b := &bytes.Buffer{}
-               b.Write(signaturePadding)
-               io.WriteString(b, context)
-               b.Write(transcript.Sum(nil))
-               return b.Bytes()
-       }
-       h := sigHash.New()
-       h.Write(signaturePadding)
-       io.WriteString(h, context)
-       h.Write(transcript.Sum(nil))
-       return h.Sum(nil)
+// signedMessage returns the (unhashed) message to be signed by certificate keys
+// in TLS 1.3. See RFC 8446, Section 4.4.3.
+func signedMessage(context string, transcript hash.Hash) []byte {
+       const maxSize = 64 /* signaturePadding */ + len(serverSignatureContext) + 512/8 /* SHA-512 */
+       b := bytes.NewBuffer(make([]byte, 0, maxSize))
+       b.Write(signaturePadding)
+       io.WriteString(b, context)
+       b.Write(transcript.Sum(nil))
+       return b.Bytes()
 }
 
 // typeAndHashFromSignatureScheme returns the corresponding signature type and
index 993cfaf7c06ac5920a08ebb86e6b061ffba9892e..099a11ca63da758274f86e2354a2bd1a7b06fee0 100644 (file)
@@ -1597,9 +1597,14 @@ var writerMutex sync.Mutex
 type Certificate struct {
        Certificate [][]byte
        // PrivateKey contains the private key corresponding to the public key in
-       // Leaf. This must implement crypto.Signer with an RSA, ECDSA or Ed25519 PublicKey.
+       // Leaf. This must implement [crypto.Signer] with an RSA, ECDSA or Ed25519
+       // PublicKey.
+       //
        // For a server up to TLS 1.2, it can also implement crypto.Decrypter with
        // an RSA PublicKey.
+       //
+       // If it implements [crypto.MessageSigner], SignMessage will be used instead
+       // of Sign for TLS 1.2 and later.
        PrivateKey crypto.PrivateKey
        // SupportedSignatureAlgorithms is an optional list restricting what
        // signature algorithms the PrivateKey can be used for.
index e1ddcb3f10689a724bf4777ea3be28248174dc9d..c2b1b7037a46cf87d8c87b822ed5c57eeb1d692a 100644 (file)
@@ -781,15 +781,13 @@ func (hs *clientHandshakeState) doFullHandshake() error {
                        return fmt.Errorf("tls: client certificate private key of type %T does not implement crypto.Signer", chainToSend.PrivateKey)
                }
 
-               var sigType uint8
-               var sigHash crypto.Hash
                if c.vers >= VersionTLS12 {
                        signatureAlgorithm, err := selectSignatureScheme(c.vers, chainToSend, certReq.supportedSignatureAlgorithms)
                        if err != nil {
                                c.sendAlert(alertHandshakeFailure)
                                return err
                        }
-                       sigType, sigHash, err = typeAndHashFromSignatureScheme(signatureAlgorithm)
+                       sigType, sigHash, err := typeAndHashFromSignatureScheme(signatureAlgorithm)
                        if err != nil {
                                return c.sendAlert(alertInternalError)
                        }
@@ -799,23 +797,31 @@ func (hs *clientHandshakeState) doFullHandshake() error {
                                tlssha1.Value() // ensure godebug is initialized
                                tlssha1.IncNonDefault()
                        }
+                       if hs.finishedHash.buffer == nil {
+                               c.sendAlert(alertInternalError)
+                               return errors.New("tls: internal error: did not keep handshake transcript for TLS 1.2")
+                       }
+                       signOpts := crypto.SignerOpts(sigHash)
+                       if sigType == signatureRSAPSS {
+                               signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
+                       }
+                       certVerify.signature, err = crypto.SignMessage(key, c.config.rand(), hs.finishedHash.buffer, signOpts)
+                       if err != nil {
+                               c.sendAlert(alertInternalError)
+                               return err
+                       }
                } else {
-                       sigType, sigHash, err = legacyTypeAndHashFromPublicKey(key.Public())
+                       sigType, sigHash, err := legacyTypeAndHashFromPublicKey(key.Public())
                        if err != nil {
                                c.sendAlert(alertIllegalParameter)
                                return err
                        }
-               }
-
-               signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash)
-               signOpts := crypto.SignerOpts(sigHash)
-               if sigType == signatureRSAPSS {
-                       signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
-               }
-               certVerify.signature, err = key.Sign(c.config.rand(), signed, signOpts)
-               if err != nil {
-                       c.sendAlert(alertInternalError)
-                       return err
+                       signed := hs.finishedHash.hashForClientCertificate(sigType)
+                       certVerify.signature, err = key.Sign(c.config.rand(), signed, sigHash)
+                       if err != nil {
+                               c.sendAlert(alertInternalError)
+                               return err
+                       }
                }
 
                if _, err := hs.c.writeHandshakeRecord(certVerify, &hs.finishedHash); err != nil {
index 2912d97f75e6118549abc8973919906fed6fc935..e696bd3a13dcb12e6a367e18b5c75ab2024e2e76 100644 (file)
@@ -664,7 +664,7 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
        if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 {
                return c.sendAlert(alertInternalError)
        }
-       signed := signedMessage(sigHash, serverSignatureContext, hs.transcript)
+       signed := signedMessage(serverSignatureContext, hs.transcript)
        if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,
                sigHash, signed, certVerify.signature); err != nil {
                c.sendAlert(alertDecryptError)
@@ -783,12 +783,12 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
                return c.sendAlert(alertInternalError)
        }
 
-       signed := signedMessage(sigHash, clientSignatureContext, hs.transcript)
+       signed := signedMessage(clientSignatureContext, hs.transcript)
        signOpts := crypto.SignerOpts(sigHash)
        if sigType == signatureRSAPSS {
                signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
        }
-       sig, err := cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)
+       sig, err := crypto.SignMessage(cert.PrivateKey.(crypto.Signer), c.config.rand(), signed, signOpts)
        if err != nil {
                c.sendAlert(alertInternalError)
                return errors.New("tls: failed to sign handshake: " + err.Error())
index 2d3efffcf054f89f32aa2b37e773c1f060f9de72..efdaeae6f7e39a23ae1f9b1cb6adbd7649d66935 100644 (file)
@@ -780,19 +780,27 @@ func (hs *serverHandshakeState) doFullHandshake() error {
                                tlssha1.Value() // ensure godebug is initialized
                                tlssha1.IncNonDefault()
                        }
+                       if hs.finishedHash.buffer == nil {
+                               c.sendAlert(alertInternalError)
+                               return errors.New("tls: internal error: did not keep handshake transcript for TLS 1.2")
+                       }
+                       if err := verifyHandshakeSignature(sigType, pub, sigHash, hs.finishedHash.buffer, certVerify.signature); err != nil {
+                               c.sendAlert(alertDecryptError)
+                               return errors.New("tls: invalid signature by the client certificate: " + err.Error())
+                       }
                } else {
                        sigType, sigHash, err = legacyTypeAndHashFromPublicKey(pub)
                        if err != nil {
                                c.sendAlert(alertIllegalParameter)
                                return err
                        }
+                       signed := hs.finishedHash.hashForClientCertificate(sigType)
+                       if err := verifyLegacyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil {
+                               c.sendAlert(alertDecryptError)
+                               return errors.New("tls: invalid signature by the client certificate: " + err.Error())
+                       }
                }
 
-               signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash)
-               if err := verifyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil {
-                       c.sendAlert(alertDecryptError)
-                       return errors.New("tls: invalid signature by the client certificate: " + err.Error())
-               }
                c.peerSigAlg = certVerify.signatureAlgorithm
 
                if err := transcriptMsg(certVerify, &hs.finishedHash); err != nil {
index 1182307936c2d4722afd15bba0e1be3dc950cd1e..3bed1359a3c85131daaaaf4d200d2371b278fd83 100644 (file)
@@ -845,12 +845,12 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
                return c.sendAlert(alertInternalError)
        }
 
-       signed := signedMessage(sigHash, serverSignatureContext, hs.transcript)
+       signed := signedMessage(serverSignatureContext, hs.transcript)
        signOpts := crypto.SignerOpts(sigHash)
        if sigType == signatureRSAPSS {
                signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
        }
-       sig, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)
+       sig, err := crypto.SignMessage(hs.cert.PrivateKey.(crypto.Signer), c.config.rand(), signed, signOpts)
        if err != nil {
                public := hs.cert.PrivateKey.(crypto.Signer).Public()
                if rsaKey, ok := public.(*rsa.PublicKey); ok && sigType == signatureRSAPSS &&
@@ -1081,7 +1081,7 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error {
                if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 {
                        return c.sendAlert(alertInternalError)
                }
-               signed := signedMessage(sigHash, clientSignatureContext, hs.transcript)
+               signed := signedMessage(clientSignatureContext, hs.transcript)
                if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,
                        sigHash, signed, certVerify.signature); err != nil {
                        c.sendAlert(alertDecryptError)
index 26f7bd2c520176431e286f5a67fff1d70aebc5ad..ad2be5ddf9b0f8e44b109f137f2961b886e7295d 100644 (file)
@@ -127,25 +127,8 @@ func md5SHA1Hash(slices [][]byte) []byte {
 }
 
 // hashForServerKeyExchange hashes the given slices and returns their digest
-// using the given hash function (for TLS 1.2) or using a default based on
-// the sigType (for earlier TLS versions). For Ed25519 signatures, which don't
-// do pre-hashing, it returns the concatenation of the slices.
-func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte {
-       if sigType == signatureEd25519 {
-               var signed []byte
-               for _, slice := range slices {
-                       signed = append(signed, slice...)
-               }
-               return signed
-       }
-       if version >= VersionTLS12 {
-               h := hashFunc.New()
-               for _, slice := range slices {
-                       h.Write(slice)
-               }
-               digest := h.Sum(nil)
-               return digest
-       }
+// using a hash based on the sigType. It can only be used for TLS 1.0 and 1.1.
+func hashForServerKeyExchange(sigType uint8, slices ...[]byte) []byte {
        if sigType == signatureECDSA {
                return sha1Hash(slices)
        }
@@ -207,14 +190,13 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer
                return nil, fmt.Errorf("tls: certificate private key of type %T does not implement crypto.Signer", cert.PrivateKey)
        }
 
-       var sigType uint8
-       var sigHash crypto.Hash
+       var sig []byte
        if ka.version >= VersionTLS12 {
                ka.signatureAlgorithm, err = selectSignatureScheme(ka.version, cert, clientHello.supportedSignatureAlgorithms)
                if err != nil {
                        return nil, err
                }
-               sigType, sigHash, err = typeAndHashFromSignatureScheme(ka.signatureAlgorithm)
+               sigType, sigHash, err := typeAndHashFromSignatureScheme(ka.signatureAlgorithm)
                if err != nil {
                        return nil, err
                }
@@ -222,25 +204,31 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer
                        tlssha1.Value() // ensure godebug is initialized
                        tlssha1.IncNonDefault()
                }
+               signed := slices.Concat(clientHello.random, hello.random, serverECDHEParams)
+               if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
+                       return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
+               }
+               signOpts := crypto.SignerOpts(sigHash)
+               if sigType == signatureRSAPSS {
+                       signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
+               }
+               sig, err = crypto.SignMessage(priv, config.rand(), signed, signOpts)
+               if err != nil {
+                       return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error())
+               }
        } else {
-               sigType, sigHash, err = legacyTypeAndHashFromPublicKey(priv.Public())
+               sigType, sigHash, err := legacyTypeAndHashFromPublicKey(priv.Public())
                if err != nil {
                        return nil, err
                }
-       }
-       if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
-               return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
-       }
-
-       signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, hello.random, serverECDHEParams)
-
-       signOpts := crypto.SignerOpts(sigHash)
-       if sigType == signatureRSAPSS {
-               signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
-       }
-       sig, err := priv.Sign(config.rand(), signed, signOpts)
-       if err != nil {
-               return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error())
+               signed := hashForServerKeyExchange(sigType, clientHello.random, hello.random, serverECDHEParams)
+               if (sigType == signaturePKCS1v15) != ka.isRSA {
+                       return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
+               }
+               sig, err = priv.Sign(config.rand(), signed, sigHash)
+               if err != nil {
+                       return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error())
+               }
        }
 
        skx := new(serverKeyExchangeMsg)
@@ -300,6 +288,18 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
        if len(sig) < 2 {
                return errServerKeyExchange
        }
+       if ka.version >= VersionTLS12 {
+               ka.signatureAlgorithm = SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1])
+               sig = sig[2:]
+               if len(sig) < 2 {
+                       return errServerKeyExchange
+               }
+       }
+       sigLen := int(sig[0])<<8 | int(sig[1])
+       if sigLen+2 != len(sig) {
+               return errServerKeyExchange
+       }
+       sig = sig[2:]
 
        if !slices.Contains(clientHello.supportedCurves, ka.curveID) {
                return errors.New("tls: server selected unoffered curve")
@@ -333,12 +333,6 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
        var sigType uint8
        var sigHash crypto.Hash
        if ka.version >= VersionTLS12 {
-               ka.signatureAlgorithm = SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1])
-               sig = sig[2:]
-               if len(sig) < 2 {
-                       return errServerKeyExchange
-               }
-
                if !isSupportedSignatureAlgorithm(ka.signatureAlgorithm, clientHello.supportedSignatureAlgorithms) {
                        return errors.New("tls: certificate used with invalid signature algorithm")
                }
@@ -350,26 +344,27 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
                        tlssha1.Value() // ensure godebug is initialized
                        tlssha1.IncNonDefault()
                }
+               if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
+                       return errServerKeyExchange
+               }
+               signed := slices.Concat(clientHello.random, serverHello.random, serverECDHEParams)
+               if err := verifyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil {
+                       return errors.New("tls: invalid signature by the server certificate: " + err.Error())
+               }
        } else {
                sigType, sigHash, err = legacyTypeAndHashFromPublicKey(cert.PublicKey)
                if err != nil {
                        return err
                }
+               if (sigType == signaturePKCS1v15) != ka.isRSA {
+                       return errServerKeyExchange
+               }
+               signed := hashForServerKeyExchange(sigType, clientHello.random, serverHello.random, serverECDHEParams)
+               if err := verifyLegacyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil {
+                       return errors.New("tls: invalid signature by the server certificate: " + err.Error())
+               }
        }
-       if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
-               return errServerKeyExchange
-       }
-
-       sigLen := int(sig[0])<<8 | int(sig[1])
-       if sigLen+2 != len(sig) {
-               return errServerKeyExchange
-       }
-       sig = sig[2:]
 
-       signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, serverHello.random, serverECDHEParams)
-       if err := verifyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil {
-               return errors.New("tls: invalid signature by the server certificate: " + err.Error())
-       }
        return nil
 }
 
index e7369542a732705d8580c6d97624acbe254d0e80..19f80ac2c91c208de582ce802e35517eeaac6934 100644 (file)
@@ -221,23 +221,9 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte {
        return h.prf(masterSecret, serverFinishedLabel, h.Sum(), finishedVerifyLength)
 }
 
-// hashForClientCertificate returns the handshake messages so far, pre-hashed if
-// necessary, suitable for signing by a TLS client certificate.
-func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash) []byte {
-       if (h.version >= VersionTLS12 || sigType == signatureEd25519) && h.buffer == nil {
-               panic("tls: handshake hash for a client certificate requested after discarding the handshake buffer")
-       }
-
-       if sigType == signatureEd25519 {
-               return h.buffer
-       }
-
-       if h.version >= VersionTLS12 {
-               hash := hashAlg.New()
-               hash.Write(h.buffer)
-               return hash.Sum(nil)
-       }
-
+// hashForClientCertificate returns the handshake messages so far, pre-hashed,
+// suitable for signing by a TLS 1.0 and 1.1 client certificate.
+func (h finishedHash) hashForClientCertificate(sigType uint8) []byte {
        if sigType == signatureECDSA {
                return h.server.Sum(nil)
        }
index af2828fd8da7c5e64be67fdccdc064c85e001fe5..39ebb9d2f1edd39d30fc6035840b700019bcce82 100644 (file)
@@ -2390,3 +2390,74 @@ func TestECH(t *testing.T) {
 
        check()
 }
+
+func TestMessageSigner(t *testing.T) {
+       t.Run("TLSv10", func(t *testing.T) { testMessageSigner(t, VersionTLS10) })
+       t.Run("TLSv12", func(t *testing.T) { testMessageSigner(t, VersionTLS12) })
+       t.Run("TLSv13", func(t *testing.T) { testMessageSigner(t, VersionTLS13) })
+}
+
+func testMessageSigner(t *testing.T, version uint16) {
+       clientConfig, serverConfig := testConfig.Clone(), testConfig.Clone()
+       serverConfig.ClientAuth = RequireAnyClientCert
+       clientConfig.MinVersion = version
+       clientConfig.MaxVersion = version
+       serverConfig.MinVersion = version
+       serverConfig.MaxVersion = version
+       clientConfig.Certificates = []Certificate{{
+               Certificate: [][]byte{testRSACertificate},
+               PrivateKey:  messageOnlySigner{testRSAPrivateKey},
+       }}
+       serverConfig.Certificates = []Certificate{{
+               Certificate: [][]byte{testRSACertificate},
+               PrivateKey:  messageOnlySigner{testRSAPrivateKey},
+       }}
+
+       _, _, err := testHandshake(t, clientConfig, serverConfig)
+       if version < VersionTLS12 {
+               if err == nil {
+                       t.Fatal("expected failure for TLS 1.0/1.1")
+               }
+       } else {
+               if err != nil {
+                       t.Fatalf("unexpected failure: %s", err)
+               }
+       }
+
+       clientConfig.Certificates = []Certificate{{
+               Certificate: [][]byte{testECDSACertificate},
+               PrivateKey:  messageOnlySigner{testECDSAPrivateKey},
+       }}
+       serverConfig.Certificates = []Certificate{{
+               Certificate: [][]byte{testECDSACertificate},
+               PrivateKey:  messageOnlySigner{testECDSAPrivateKey},
+       }}
+
+       _, _, err = testHandshake(t, clientConfig, serverConfig)
+       if version < VersionTLS12 {
+               if err == nil {
+                       t.Fatal("expected failure for TLS 1.0/1.1")
+               }
+       } else {
+               if err != nil {
+                       t.Fatalf("unexpected failure: %s", err)
+               }
+       }
+}
+
+type messageOnlySigner struct{ crypto.Signer }
+
+func (s messageOnlySigner) Public() crypto.PublicKey {
+       return s.Signer.Public()
+}
+
+func (s messageOnlySigner) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
+       return nil, errors.New("messageOnlySigner: Sign called")
+}
+
+func (s messageOnlySigner) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
+       h := opts.HashFunc().New()
+       h.Write(msg)
+       digest := h.Sum(nil)
+       return s.Signer.Sign(rand, digest, opts)
+}