mirror of https://github.com/grpc/grpc-go.git
589 lines
18 KiB
Go
589 lines
18 KiB
Go
/*
|
|
*
|
|
* Copyright 2021 gRPC authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package advancedtls
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"google.golang.org/grpc/security/advancedtls/testdata"
|
|
)
|
|
|
|
func TestUnsupportedCRLs(t *testing.T) {
|
|
crlBytesSomeReasons := []byte(`-----BEGIN X509 CRL-----
|
|
MIIEeDCCA2ACAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVVMxHjAcBgNV
|
|
BAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczETMBEGA1UEAxMKR1RTIENBIDFPMRcN
|
|
MjEwNDI2MTI1OTQxWhcNMjEwNTA2MTE1OTQwWjCCAn0wIgIRAPOOG3L4VLC7CAAA
|
|
AABxQgEXDTIxMDQxOTEyMTgxOFowIQIQUK0UwBZkVdQIAAAAAHFCBRcNMjEwNDE5
|
|
MTIxODE4WjAhAhBRIXBJaKoQkQgAAAAAcULHFw0yMTA0MjAxMjE4MTdaMCICEQCv
|
|
qQWUq5UxmQgAAAAAcULMFw0yMTA0MjAxMjE4MTdaMCICEQDdv5k1kKwKTQgAAAAA
|
|
cUOQFw0yMTA0MjExMjE4MTZaMCICEQDGIEfR8N9sEAgAAAAAcUOWFw0yMTA0MjEx
|
|
MjE4MThaMCECEBHgbLXlj5yUCAAAAABxQ/IXDTIxMDQyMTIzMDAyNlowIQIQE1wT
|
|
2GGYqKwIAAAAAHFD7xcNMjEwNDIxMjMwMDI5WjAiAhEAo/bSyDjpVtsIAAAAAHFE
|
|
txcNMjEwNDIyMjMwMDI3WjAhAhARdCrSrHE0dAgAAAAAcUS/Fw0yMTA0MjIyMzAw
|
|
MjhaMCECEHONohfWn3wwCAAAAABxRX8XDTIxMDQyMzIzMDAyOVowIgIRAOYkiUPA
|
|
os4vCAAAAABxRYgXDTIxMDQyMzIzMDAyOFowIQIQRNTow5Eg2gEIAAAAAHFGShcN
|
|
MjEwNDI0MjMwMDI2WjAhAhBX32dH4/WQ6AgAAAAAcUZNFw0yMTA0MjQyMzAwMjZa
|
|
MCICEQDHnUM1vsaP/wgAAAAAcUcQFw0yMTA0MjUyMzAwMjZaMCECEEm5rvmL8sj6
|
|
CAAAAABxRxQXDTIxMDQyNTIzMDAyN1owIQIQW16OQs4YQYkIAAAAAHFIABcNMjEw
|
|
NDI2MTI1NDA4WjAhAhAhSohpYsJtDQgAAAAAcUgEFw0yMTA0MjYxMjU0MDlaoGkw
|
|
ZzAfBgNVHSMEGDAWgBSY0fhuEOvPm+xgnxiQG6DrfQn9KzALBgNVHRQEBAICBngw
|
|
NwYDVR0cAQH/BC0wK6AmoCSGImh0dHA6Ly9jcmwucGtpLmdvb2cvR1RTMU8xY29y
|
|
ZS5jcmyBAf8wDQYJKoZIhvcNAQELBQADggEBADPBXbxVxMJ1HC7btXExRUpJHUlU
|
|
YbeCZGx6zj5F8pkopbmpV7cpewwhm848Fx4VaFFppZQZd92O08daEC6aEqoug4qF
|
|
z6ZrOLzhuKfpW8E93JjgL91v0FYN7iOcT7+ERKCwVEwEkuxszxs7ggW6OJYJNvHh
|
|
priIdmcPoiQ3ZrIRH0vE3BfUcNXnKFGATWuDkiRI0I4A5P7NiOf+lAuGZet3/eom
|
|
0chgts6sdau10GfeUpHUd4f8e93cS/QeLeG16z7LC8vRLstU3m3vrknpZbdGqSia
|
|
97w66mqcnQh9V0swZiEnVLmLufaiuDZJ+6nUzSvLqBlb/ei3T/tKV0BoKJA=
|
|
-----END X509 CRL-----`)
|
|
|
|
crlBytesIndirect := []byte(`-----BEGIN X509 CRL-----
|
|
MIIDGjCCAgICAQEwDQYJKoZIhvcNAQELBQAwdjELMAkGA1UEBhMCVVMxEzARBgNV
|
|
BAgTCkNhbGlmb3JuaWExFDASBgNVBAoTC1Rlc3RpbmcgTHRkMSowKAYDVQQLEyFU
|
|
ZXN0aW5nIEx0ZCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxEDAOBgNVBAMTB1Rlc3Qg
|
|
Q0EXDTIxMDExNjAyMjAxNloXDTIxMDEyMDA2MjAxNlowgfIwbAIBAhcNMjEwMTE2
|
|
MDIyMDE2WjBYMAoGA1UdFQQDCgEEMEoGA1UdHQEB/wRAMD6kPDA6MQwwCgYDVQQG
|
|
EwNVU0ExDTALBgNVBAcTBGhlcmUxCzAJBgNVBAoTAnVzMQ4wDAYDVQQDEwVUZXN0
|
|
MTAgAgEDFw0yMTAxMTYwMjIwMTZaMAwwCgYDVR0VBAMKAQEwYAIBBBcNMjEwMTE2
|
|
MDIyMDE2WjBMMEoGA1UdHQEB/wRAMD6kPDA6MQwwCgYDVQQGEwNVU0ExDTALBgNV
|
|
BAcTBGhlcmUxCzAJBgNVBAoTAnVzMQ4wDAYDVQQDEwVUZXN0MqBjMGEwHwYDVR0j
|
|
BBgwFoAURJSDWAOfhGCryBjl8dsQjBitl3swCgYDVR0UBAMCAQEwMgYDVR0cAQH/
|
|
BCgwJqAhoB+GHWh0dHA6Ly9jcmxzLnBraS5nb29nL3Rlc3QuY3JshAH/MA0GCSqG
|
|
SIb3DQEBCwUAA4IBAQBVXX67mr2wFPmEWCe6mf/wFnPl3xL6zNOl96YJtsd7ulcS
|
|
TEbdJpaUnWFQ23+Tpzdj/lI2aQhTg5Lvii3o+D8C5r/Jc5NhSOtVJJDI/IQLh4pG
|
|
NgGdljdbJQIT5D2Z71dgbq1ocxn8DefZIJjO3jp8VnAm7AIMX2tLTySzD2MpMeMq
|
|
XmcN4lG1e4nx+xjzp7MySYO42NRY3LkphVzJhu3dRBYhBKViRJxw9hLttChitJpF
|
|
6Kh6a0QzrEY/QDJGhE1VrAD2c5g/SKnHPDVoCWo4ACIICi76KQQSIWfIdp4W/SY3
|
|
qsSIp8gfxSyzkJP+Ngkm2DdLjlJQCZ9R0MZP9Xj4
|
|
-----END X509 CRL-----`)
|
|
|
|
var tests = []struct {
|
|
desc string
|
|
in []byte
|
|
}{
|
|
{
|
|
desc: "some reasons",
|
|
in: crlBytesSomeReasons,
|
|
},
|
|
{
|
|
desc: "indirect",
|
|
in: crlBytesIndirect,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
crl, err := parseRevocationList(tt.in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := parseCRLExtensions(crl); err == nil {
|
|
t.Error("expected error got ok")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckCertRevocation(t *testing.T) {
|
|
dummyCrlFile := []byte(`-----BEGIN X509 CRL-----
|
|
MIIDGjCCAgICAQEwDQYJKoZIhvcNAQELBQAwdjELMAkGA1UEBhMCVVMxEzARBgNV
|
|
BAgTCkNhbGlmb3JuaWExFDASBgNVBAoTC1Rlc3RpbmcgTHRkMSowKAYDVQQLEyFU
|
|
ZXN0aW5nIEx0ZCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxEDAOBgNVBAMTB1Rlc3Qg
|
|
Q0EXDTIxMDExNjAyMjAxNloXDTIxMDEyMDA2MjAxNlowgfIwbAIBAhcNMjEwMTE2
|
|
MDIyMDE2WjBYMAoGA1UdFQQDCgEEMEoGA1UdHQEB/wRAMD6kPDA6MQwwCgYDVQQG
|
|
EwNVU0ExDTALBgNVBAcTBGhlcmUxCzAJBgNVBAoTAnVzMQ4wDAYDVQQDEwVUZXN0
|
|
MTAgAgEDFw0yMTAxMTYwMjIwMTZaMAwwCgYDVR0VBAMKAQEwYAIBBBcNMjEwMTE2
|
|
MDIyMDE2WjBMMEoGA1UdHQEB/wRAMD6kPDA6MQwwCgYDVQQGEwNVU0ExDTALBgNV
|
|
BAcTBGhlcmUxCzAJBgNVBAoTAnVzMQ4wDAYDVQQDEwVUZXN0MqBjMGEwHwYDVR0j
|
|
BBgwFoAURJSDWAOfhGCryBjl8dsQjBitl3swCgYDVR0UBAMCAQEwMgYDVR0cAQH/
|
|
BCgwJqAhoB+GHWh0dHA6Ly9jcmxzLnBraS5nb29nL3Rlc3QuY3JshAH/MA0GCSqG
|
|
SIb3DQEBCwUAA4IBAQBVXX67mr2wFPmEWCe6mf/wFnPl3xL6zNOl96YJtsd7ulcS
|
|
TEbdJpaUnWFQ23+Tpzdj/lI2aQhTg5Lvii3o+D8C5r/Jc5NhSOtVJJDI/IQLh4pG
|
|
NgGdljdbJQIT5D2Z71dgbq1ocxn8DefZIJjO3jp8VnAm7AIMX2tLTySzD2MpMeMq
|
|
XmcN4lG1e4nx+xjzp7MySYO42NRY3LkphVzJhu3dRBYhBKViRJxw9hLttChitJpF
|
|
6Kh6a0QzrEY/QDJGhE1VrAD2c5g/SKnHPDVoCWo4ACIICi76KQQSIWfIdp4W/SY3
|
|
qsSIp8gfxSyzkJP+Ngkm2DdLjlJQCZ9R0MZP9Xj4
|
|
-----END X509 CRL-----`)
|
|
crl, err := parseRevocationList(dummyCrlFile)
|
|
if err != nil {
|
|
t.Fatalf("parseRevocationList(dummyCrlFile) failed: %v", err)
|
|
}
|
|
crlExt := &CRL{certList: crl}
|
|
|
|
var revocationTests = []struct {
|
|
desc string
|
|
in x509.Certificate
|
|
revoked revocationStatus
|
|
}{
|
|
{
|
|
desc: "Single revoked",
|
|
in: x509.Certificate{
|
|
Issuer: pkix.Name{
|
|
Country: []string{"USA"},
|
|
Locality: []string{"here"},
|
|
Organization: []string{"us"},
|
|
CommonName: "Test1",
|
|
},
|
|
SerialNumber: big.NewInt(2),
|
|
CRLDistributionPoints: []string{"test"},
|
|
},
|
|
revoked: RevocationRevoked,
|
|
},
|
|
{
|
|
desc: "Revoked no entry issuer",
|
|
in: x509.Certificate{
|
|
Issuer: pkix.Name{
|
|
Country: []string{"USA"},
|
|
Locality: []string{"here"},
|
|
Organization: []string{"us"},
|
|
CommonName: "Test1",
|
|
},
|
|
SerialNumber: big.NewInt(3),
|
|
CRLDistributionPoints: []string{"test"},
|
|
},
|
|
revoked: RevocationRevoked,
|
|
},
|
|
{
|
|
desc: "Revoked new entry issuer",
|
|
in: x509.Certificate{
|
|
Issuer: pkix.Name{
|
|
Country: []string{"USA"},
|
|
Locality: []string{"here"},
|
|
Organization: []string{"us"},
|
|
CommonName: "Test2",
|
|
},
|
|
SerialNumber: big.NewInt(4),
|
|
CRLDistributionPoints: []string{"test"},
|
|
},
|
|
revoked: RevocationRevoked,
|
|
},
|
|
{
|
|
desc: "Single unrevoked",
|
|
in: x509.Certificate{
|
|
Issuer: pkix.Name{
|
|
Country: []string{"USA"},
|
|
Locality: []string{"here"},
|
|
Organization: []string{"us"},
|
|
CommonName: "Test2",
|
|
},
|
|
SerialNumber: big.NewInt(1),
|
|
CRLDistributionPoints: []string{"test"},
|
|
},
|
|
revoked: RevocationUnrevoked,
|
|
},
|
|
{
|
|
desc: "Single unrevoked Issuer",
|
|
in: x509.Certificate{
|
|
Issuer: crl.Issuer,
|
|
SerialNumber: big.NewInt(2),
|
|
CRLDistributionPoints: []string{"test"},
|
|
},
|
|
revoked: RevocationUnrevoked,
|
|
},
|
|
}
|
|
|
|
for _, tt := range revocationTests {
|
|
rawIssuer, err := asn1.Marshal(tt.in.Issuer.ToRDNSequence())
|
|
if err != nil {
|
|
t.Fatalf("asn1.Marshal(%v) failed: %v", tt.in.Issuer.ToRDNSequence(), err)
|
|
}
|
|
tt.in.RawIssuer = rawIssuer
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
rev, err := checkCertRevocation(&tt.in, crlExt)
|
|
if err != nil {
|
|
t.Errorf("checkCertRevocation(%v) err = %v", tt.in.Issuer, err)
|
|
} else if rev != tt.revoked {
|
|
t.Errorf("checkCertRevocation(%v(%v)) returned %v wanted %v",
|
|
tt.in.Issuer, tt.in.SerialNumber, rev, tt.revoked)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeChain(t *testing.T, name string) []*x509.Certificate {
|
|
t.Helper()
|
|
|
|
certChain := make([]*x509.Certificate, 0)
|
|
|
|
rest, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("os.ReadFile(%v) failed %v", name, err)
|
|
}
|
|
for len(rest) > 0 {
|
|
var block *pem.Block
|
|
block, rest = pem.Decode(rest)
|
|
c, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
t.Fatalf("ParseCertificate error %v", err)
|
|
}
|
|
t.Logf("Parsed Cert sub = %v iss = %v", c.Subject, c.Issuer)
|
|
certChain = append(certChain, c)
|
|
}
|
|
return certChain
|
|
}
|
|
|
|
func loadCRL(t *testing.T, path string) *CRL {
|
|
crl, err := ReadCRLFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadCRLFile(%v) failed err = %v", path, err)
|
|
}
|
|
return crl
|
|
}
|
|
|
|
func checkRevocation(conn tls.ConnectionState, cfg RevocationOptions) error {
|
|
return checkChainRevocation(conn.VerifiedChains, cfg)
|
|
}
|
|
|
|
func TestVerifyCrl(t *testing.T) {
|
|
tamperedSignature := loadCRL(t, testdata.Path("crl/1.crl"))
|
|
// Change the signature so it won't verify
|
|
tamperedSignature.certList.Signature[0]++
|
|
tamperedContent := loadCRL(t, testdata.Path("crl/provider_crl_empty.pem"))
|
|
// Change the content so it won't find a match
|
|
tamperedContent.rawIssuer[0]++
|
|
|
|
verifyTests := []struct {
|
|
desc string
|
|
crl *CRL
|
|
certs []*x509.Certificate
|
|
cert *x509.Certificate
|
|
errWant string
|
|
}{
|
|
{
|
|
desc: "Pass intermediate",
|
|
crl: loadCRL(t, testdata.Path("crl/1.crl")),
|
|
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/unrevoked.pem"))[1],
|
|
errWant: "",
|
|
},
|
|
{
|
|
desc: "Pass leaf",
|
|
crl: loadCRL(t, testdata.Path("crl/2.crl")),
|
|
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/unrevoked.pem"))[2],
|
|
errWant: "",
|
|
},
|
|
{
|
|
desc: "Fail wrong cert chain",
|
|
crl: loadCRL(t, testdata.Path("crl/3.crl")),
|
|
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/revokedInt.pem"))[1],
|
|
errWant: "No certificates matched",
|
|
},
|
|
{
|
|
desc: "Fail no certs",
|
|
crl: loadCRL(t, testdata.Path("crl/1.crl")),
|
|
certs: []*x509.Certificate{},
|
|
cert: makeChain(t, testdata.Path("crl/unrevoked.pem"))[1],
|
|
errWant: "No certificates matched",
|
|
},
|
|
{
|
|
desc: "Fail Tampered signature",
|
|
crl: tamperedSignature,
|
|
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/unrevoked.pem"))[1],
|
|
errWant: "verification failure",
|
|
},
|
|
{
|
|
desc: "Fail Tampered content",
|
|
crl: tamperedContent,
|
|
certs: makeChain(t, testdata.Path("crl/provider_client_trust_cert.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/provider_client_trust_cert.pem"))[0],
|
|
errWant: "No certificates",
|
|
},
|
|
{
|
|
desc: "Fail CRL by malicious CA",
|
|
crl: loadCRL(t, testdata.Path("crl/provider_malicious_crl_empty.pem")),
|
|
certs: makeChain(t, testdata.Path("crl/provider_client_trust_cert.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/provider_client_trust_cert.pem"))[0],
|
|
errWant: "verification error",
|
|
},
|
|
{
|
|
desc: "Fail KeyUsage without cRLSign bit",
|
|
crl: loadCRL(t, testdata.Path("crl/provider_malicious_crl_empty.pem")),
|
|
certs: makeChain(t, testdata.Path("crl/provider_malicious_client_trust_cert.pem")),
|
|
cert: makeChain(t, testdata.Path("crl/provider_malicious_client_trust_cert.pem"))[0],
|
|
errWant: "certificate can't be used",
|
|
},
|
|
}
|
|
|
|
for _, tt := range verifyTests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
err := verifyCRL(tt.crl, tt.certs)
|
|
switch {
|
|
case tt.errWant == "" && err != nil:
|
|
t.Errorf("Valid CRL did not verify err = %v", err)
|
|
case tt.errWant != "" && err == nil:
|
|
t.Error("Invalid CRL verified")
|
|
case tt.errWant != "" && !strings.Contains(err.Error(), tt.errWant):
|
|
t.Errorf("fetchIssuerCRL(_, %v, %v, _) = %v; want Contains(%v)", tt.cert.RawIssuer, tt.certs, err, tt.errWant)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRevokedCert(t *testing.T) {
|
|
revokedIntChain := makeChain(t, testdata.Path("crl/revokedInt.pem"))
|
|
revokedLeafChain := makeChain(t, testdata.Path("crl/revokedLeaf.pem"))
|
|
validChain := makeChain(t, testdata.Path("crl/unrevoked.pem"))
|
|
rawCRLs := make([][]byte, 6)
|
|
for i := 1; i <= 6; i++ {
|
|
rawCRL, err := os.ReadFile(testdata.Path(fmt.Sprintf("crl/%d.crl", i)))
|
|
if err != nil {
|
|
t.Fatalf("readFile(%v) failed err = %v", fmt.Sprintf("crl/%d.crl", i), err)
|
|
}
|
|
rawCRLs = append(rawCRLs, rawCRL)
|
|
}
|
|
staticCRLProvider := NewStaticCRLProvider(rawCRLs)
|
|
directoryCRLProvider, err := NewFileWatcherCRLProvider(FileWatcherOptions{CRLDirectory: testdata.Path("crl")})
|
|
if err != nil {
|
|
t.Fatalf("NewFileWatcherCRLProvider: err = %v", err)
|
|
}
|
|
defer directoryCRLProvider.Close()
|
|
|
|
var revocationTests = []struct {
|
|
desc string
|
|
in tls.ConnectionState
|
|
revoked bool
|
|
denyUndetermined bool
|
|
}{
|
|
{
|
|
desc: "Single unrevoked",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{validChain}},
|
|
revoked: false,
|
|
},
|
|
{
|
|
desc: "Single revoked intermediate",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{revokedIntChain}},
|
|
revoked: true,
|
|
},
|
|
{
|
|
desc: "Single revoked leaf",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{revokedLeafChain}},
|
|
revoked: true,
|
|
},
|
|
{
|
|
desc: "Multi one revoked",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{validChain, revokedLeafChain}},
|
|
revoked: false,
|
|
},
|
|
{
|
|
desc: "Multi revoked",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{revokedLeafChain, revokedIntChain}},
|
|
revoked: true,
|
|
},
|
|
{
|
|
desc: "Multi unrevoked",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{validChain, validChain}},
|
|
revoked: false,
|
|
},
|
|
{
|
|
desc: "Undetermined revoked",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{
|
|
{&x509.Certificate{CRLDistributionPoints: []string{"test"}}},
|
|
}},
|
|
revoked: true,
|
|
denyUndetermined: true,
|
|
},
|
|
{
|
|
desc: "Undetermined allowed",
|
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{
|
|
{&x509.Certificate{CRLDistributionPoints: []string{"test"}}},
|
|
}},
|
|
revoked: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range revocationTests {
|
|
t.Run(fmt.Sprintf("%v with x509 crl dir", tt.desc), func(t *testing.T) {
|
|
err := checkRevocation(tt.in, RevocationOptions{
|
|
CRLProvider: directoryCRLProvider,
|
|
DenyUndetermined: tt.denyUndetermined,
|
|
})
|
|
t.Logf("checkRevocation err = %v", err)
|
|
if tt.revoked && err == nil {
|
|
t.Error("Revoked certificate chain was allowed")
|
|
} else if !tt.revoked && err != nil {
|
|
t.Error("Unrevoked certificate not allowed")
|
|
}
|
|
})
|
|
t.Run(fmt.Sprintf("%v with static provider", tt.desc), func(t *testing.T) {
|
|
err := checkRevocation(tt.in, RevocationOptions{
|
|
DenyUndetermined: tt.denyUndetermined,
|
|
CRLProvider: staticCRLProvider,
|
|
})
|
|
t.Logf("checkRevocation err = %v", err)
|
|
if tt.revoked && err == nil {
|
|
t.Error("Revoked certificate chain was allowed")
|
|
} else if !tt.revoked && err != nil {
|
|
t.Error("Unrevoked certificate not allowed")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func setupTLSConn(t *testing.T) (net.Listener, *x509.Certificate, *ecdsa.PrivateKey) {
|
|
t.Helper()
|
|
templ := x509.Certificate{
|
|
SerialNumber: big.NewInt(5),
|
|
BasicConstraintsValid: true,
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
IsCA: true,
|
|
Subject: pkix.Name{CommonName: "test-cert"},
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
IPAddresses: []net.IP{netip.MustParseAddr("::1").AsSlice()},
|
|
CRLDistributionPoints: []string{"http://static.corp.google.com/crl/campus-sln/borg"},
|
|
}
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("ecdsa.GenerateKey failed err = %v", err)
|
|
}
|
|
rawCert, err := x509.CreateCertificate(rand.Reader, &templ, &templ, key.Public(), key)
|
|
if err != nil {
|
|
t.Fatalf("x509.CreateCertificate failed err = %v", err)
|
|
}
|
|
cert, err := x509.ParseCertificate(rawCert)
|
|
if err != nil {
|
|
t.Fatalf("x509.ParseCertificate failed err = %v", err)
|
|
}
|
|
|
|
srvCfg := tls.Config{
|
|
Certificates: []tls.Certificate{
|
|
{
|
|
Certificate: [][]byte{cert.Raw},
|
|
PrivateKey: key,
|
|
},
|
|
},
|
|
}
|
|
l, err := tls.Listen("tcp6", "[::1]:0", &srvCfg)
|
|
if err != nil {
|
|
t.Fatalf("tls.Listen failed err = %v", err)
|
|
}
|
|
return l, cert, key
|
|
}
|
|
|
|
// TestVerifyConnection will setup a client/server connection and check revocation in the real TLS dialer
|
|
func TestVerifyConnection(t *testing.T) {
|
|
lis, cert, key := setupTLSConn(t)
|
|
defer func() {
|
|
lis.Close()
|
|
}()
|
|
|
|
var handshakeTests = []struct {
|
|
desc string
|
|
revoked []pkix.RevokedCertificate
|
|
success bool
|
|
}{
|
|
{
|
|
desc: "Empty CRL",
|
|
revoked: []pkix.RevokedCertificate{},
|
|
success: true,
|
|
},
|
|
{
|
|
desc: "Revoked Cert",
|
|
revoked: []pkix.RevokedCertificate{
|
|
{
|
|
SerialNumber: cert.SerialNumber,
|
|
RevocationTime: time.Now(),
|
|
},
|
|
},
|
|
success: false,
|
|
},
|
|
}
|
|
for _, tt := range handshakeTests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
// Accept one connection.
|
|
go func() {
|
|
conn, err := lis.Accept()
|
|
if err != nil {
|
|
t.Errorf("tls.Accept failed err = %v", err)
|
|
} else {
|
|
conn.Write([]byte("Hello, World!"))
|
|
conn.Close()
|
|
}
|
|
}()
|
|
|
|
dir, err := os.MkdirTemp("", "crl_dir")
|
|
if err != nil {
|
|
t.Fatalf("os.MkdirTemp failed err = %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
template := &x509.RevocationList{
|
|
RevokedCertificates: tt.revoked,
|
|
ThisUpdate: time.Now(),
|
|
NextUpdate: time.Now().Add(time.Hour),
|
|
Number: big.NewInt(1),
|
|
}
|
|
crl, err := x509.CreateRevocationList(rand.Reader, template, cert, key)
|
|
if err != nil {
|
|
t.Fatalf("templ.CreateRevocationList failed err = %v", err)
|
|
}
|
|
|
|
err = os.WriteFile(path.Join(dir, fmt.Sprintf("%s.r0", cert.Subject.ToRDNSequence())), crl, 0777)
|
|
if err != nil {
|
|
t.Fatalf("os.WriteFile failed err = %v", err)
|
|
}
|
|
|
|
cp := x509.NewCertPool()
|
|
cp.AddCert(cert)
|
|
provider, err := NewFileWatcherCRLProvider(FileWatcherOptions{CRLDirectory: dir})
|
|
if err != nil {
|
|
t.Errorf("NewFileWatcherCRLProvider: err = %v", err)
|
|
}
|
|
defer provider.Close()
|
|
cliCfg := tls.Config{
|
|
RootCAs: cp,
|
|
VerifyConnection: func(cs tls.ConnectionState) error {
|
|
return checkRevocation(cs, RevocationOptions{CRLProvider: provider})
|
|
},
|
|
}
|
|
conn, err := tls.Dial(lis.Addr().Network(), lis.Addr().String(), &cliCfg)
|
|
t.Logf("tls.Dial err = %v", err)
|
|
if tt.success && err != nil {
|
|
t.Errorf("Expected success got err = %v", err)
|
|
}
|
|
if !tt.success && err == nil {
|
|
t.Error("Expected error, but got success")
|
|
}
|
|
if err == nil {
|
|
conn.Close()
|
|
}
|
|
})
|
|
}
|
|
}
|