Joshua Boelter uploaded a change:
https://go-review.googlesource.com/26654
crypto/tls: add VerifyPeerCertificate to tls.Config
VerifyPeerCertificate returns an error if the peer should not be
trusted. It will be called after the initial handshake and before
any other verification checks on the cert or chain are performed.
This provides the callee an opportunity to augment the certificate
verification.
If VerifyPeerCertificate is not nil and returns an error,
then the handshake will fail.
Fixes #16363
Change-Id: I6a22f199f0e81b6f5d5f37c54d85ab878216bb22
---
M src/crypto/tls/common.go
M src/crypto/tls/handshake_client.go
M src/crypto/tls/handshake_client_test.go
M src/crypto/tls/handshake_server.go
M src/crypto/tls/handshake_server_test.go
M src/crypto/tls/tls_test.go
M src/net/http/transport.go
7 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go
index 9fc7420..32df896 100644
--- a/src/crypto/tls/common.go
+++ b/src/crypto/tls/common.go
@@ -304,6 +304,16 @@
// first element of Certificates will be used.
GetCertificate func(clientHello *ClientHelloInfo) (*Certificate, error)
+ // VerifyPeerCertificate returns an error if the peer should not be
+ // trusted. It will be called after the initial handshake and before
+ // any other verification checks on the cert or chain are performed.
+ // This provides the callee an opportunity to augment the certificate
+ // verification.
+ //
+ // If VerifyPeerCertificate is not nil and returns an error,
+ // then the handshake will fail.
+ VerifyPeerCertificate func(peer []*x509.Certificate) error
+
// RootCAs defines the set of root certificate authorities
// that clients use when verifying server certificates.
// If RootCAs is nil, TLS uses the host's root CA set.
@@ -430,6 +440,7 @@
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
+ VerifyPeerCertificate: c.VerifyPeerCertificate,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
diff --git a/src/crypto/tls/handshake_client.go
b/src/crypto/tls/handshake_client.go
index f789e6f..01c6e60 100644
--- a/src/crypto/tls/handshake_client.go
+++ b/src/crypto/tls/handshake_client.go
@@ -283,6 +283,14 @@
certs[i] = cert
}
+ if c.config.VerifyPeerCertificate != nil {
+ err := c.config.VerifyPeerCertificate(certs)
+ if err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
+
if !c.config.InsecureSkipVerify {
opts := x509.VerifyOptions{
Roots: c.config.RootCAs,
diff --git a/src/crypto/tls/handshake_client_test.go
b/src/crypto/tls/handshake_client_test.go
index ce987f1..a33a833 100644
--- a/src/crypto/tls/handshake_client_test.go
+++ b/src/crypto/tls/handshake_client_test.go
@@ -8,9 +8,11 @@
"bytes"
"crypto/ecdsa"
"crypto/rsa"
+ "crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
+ "encoding/hex"
"encoding/pem"
"errors"
"fmt"
@@ -956,6 +958,35 @@
}
}
+func TestHandshakeClientVerifyPeerCertificate(t *testing.T) {
+ config := *testConfig
+ config.VerifyPeerCertificate = func(peer []*x509.Certificate) error {
+ if len(peer) != 1 {
+ t.Fatalf("VerifyPeerCertificate: got:%x want:%x", len(peer), 1)
+ }
+
+ hash := sha256.Sum256(peer[0].Raw)
+ hashstr := hex.EncodeToString(hash[:])
+
+ if
hashstr != "bf348f30a3c02342f5dca297b66516879bc673f5656f81e18dc061418b119197"
{
+ t.Fatalf("VerifyPeerCertificate: got:%x want:%x",
hashstr, "bf348f30a3c02342f5dca297b66516879bc673f5656f81e18dc061418b119197")
+ }
+
+ return nil
+ }
+
+ test := &clientTest{
+ name: "ECDHE-ECDSA-AES",
+ command:
[]string{"openssl", "s_server", "-cipher", "ECDHE-ECDSA-AES128-SHA"},
+ cert: testECDSACertificate,
+ key: testECDSAPrivateKey,
+ config: &config,
+ }
+ // runClientTestTLS10(t, test)
+ // runClientTestTLS11(t, test)
+ runClientTestTLS12(t, test)
+}
+
// brokenConn wraps a net.Conn and causes all Writes after a certain
number to
// fail with brokenConnErr.
type brokenConn struct {
diff --git a/src/crypto/tls/handshake_server.go
b/src/crypto/tls/handshake_server.go
index 1aac729..73c80d5 100644
--- a/src/crypto/tls/handshake_server.go
+++ b/src/crypto/tls/handshake_server.go
@@ -709,6 +709,14 @@
}
}
+ if c.config.VerifyPeerCertificate != nil {
+ err := c.config.VerifyPeerCertificate(certs)
+ if err != nil {
+ c.sendAlert(alertBadCertificate)
+ return nil, err
+ }
+ }
+
if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
opts := x509.VerifyOptions{
Roots: c.config.ClientCAs,
diff --git a/src/crypto/tls/handshake_server_test.go
b/src/crypto/tls/handshake_server_test.go
index 9ae5d11..8caf8c1 100644
--- a/src/crypto/tls/handshake_server_test.go
+++ b/src/crypto/tls/handshake_server_test.go
@@ -9,6 +9,8 @@
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
@@ -976,6 +978,64 @@
runServerTestTLS11(t, test)
}
+func TestClientAuthVerifyPeerCertificate(t *testing.T) {
+ var certPath, keyPath, ecdsaCertPath, ecdsaKeyPath string
+
+ if *update {
+ certPath = tempFile(clientCertificatePEM)
+ defer os.Remove(certPath)
+ keyPath = tempFile(clientKeyPEM)
+ defer os.Remove(keyPath)
+ ecdsaCertPath = tempFile(clientECDSACertificatePEM)
+ defer os.Remove(ecdsaCertPath)
+ ecdsaKeyPath = tempFile(clientECDSAKeyPEM)
+ defer os.Remove(ecdsaKeyPath)
+ }
+
+ config := *testConfig
+ config.ClientAuth = RequestClientCert
+ config.VerifyPeerCertificate = func(peer []*x509.Certificate) error {
+ t.Log("VerifyPeerCertificate")
+
+ // we expect exactly one certificate with the following sha256 thumbprint
+ // bf348f30a3c02342f5dca297b66516879bc673f5656f81e18dc061418b119197
+
+ for _, c := range peer {
+ t.Log("Subject", c.Subject)
+ t.Log("Issuer ", c.Issuer)
+
+ hash := sha256.Sum256(c.Raw)
+ hashstr := hex.EncodeToString(hash[:])
+
+ t.Log("SHA256 ", hashstr)
+ }
+
+ return nil
+ }
+ test := &serverTest{
+ name: "ClientAuthRequestedNotGiven",
+ command:
[]string{"openssl", "s_client", "-no_ticket", "-cipher", "RC4-SHA"},
+ config: &config,
+ }
+ runServerTestTLS12(t, test)
+
+ test = &serverTest{
+ name: "ClientAuthRequestedAndGiven",
+ command:
[]string{"openssl", "s_client", "-no_ticket", "-cipher", "RC4-SHA", "-cert",
certPath, "-key", keyPath},
+ config: &config,
+ expectedPeerCerts: []string{clientCertificatePEM},
+ }
+ runServerTestTLS12(t, test)
+
+ test = &serverTest{
+ name: "ClientAuthRequestedAndECDSAGiven",
+ command:
[]string{"openssl", "s_client", "-no_ticket", "-cipher", "RC4-SHA", "-cert",
ecdsaCertPath, "-key", ecdsaKeyPath},
+ config: &config,
+ expectedPeerCerts: []string{clientECDSACertificatePEM},
+ }
+ runServerTestTLS12(t, test)
+}
+
// cert.pem and key.pem were generated with generate_cert.go
// Thus, they have no ExtKeyUsage fields and trigger an error
// when verification is turned on.
diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go
index 48b46a0..e38c5e5 100644
--- a/src/crypto/tls/tls_test.go
+++ b/src/crypto/tls/tls_test.go
@@ -477,7 +477,7 @@
case "Rand":
f.Set(reflect.ValueOf(io.Reader(os.Stdin)))
continue
- case "Time", "GetCertificate":
+ case "Time", "GetCertificate", "VerifyPeerCertificate":
// DeepEqual can't compare functions.
continue
case "Certificates":
diff --git a/src/net/http/transport.go b/src/net/http/transport.go
index 3046de5..b12f748 100644
--- a/src/net/http/transport.go
+++ b/src/net/http/transport.go
@@ -2076,6 +2076,7 @@
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
+ VerifyPeerCertificate: cfg.VerifyPeerCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
@@ -2109,6 +2110,7 @@
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
+ VerifyPeerCertificate: cfg.VerifyPeerCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
--
https://go-review.googlesource.com/26654