mirror of https://github.com/grpc/grpc-go.git
security/advancedtls: fix CRL issuer comparison (#5130)
Fix CRL issuer comparison issue
This commit is contained in:
parent
449f1b222a
commit
231ca3b24e
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -34,6 +35,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,6 +86,7 @@ type certificateListExt struct {
|
||||||
CertList *pkix.CertificateList
|
CertList *pkix.CertificateList
|
||||||
// RFC5280, 5.2.1, all conforming CRLs must have a AKID with the ID method.
|
// RFC5280, 5.2.1, all conforming CRLs must have a AKID with the ID method.
|
||||||
AuthorityKeyID []byte
|
AuthorityKeyID []byte
|
||||||
|
RawIssuer []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagDirectoryName = 4
|
const tagDirectoryName = 4
|
||||||
|
|
@ -99,6 +103,11 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// x509NameHash implements the OpenSSL X509_NAME_hash function for hashed directory lookups.
|
// x509NameHash implements the OpenSSL X509_NAME_hash function for hashed directory lookups.
|
||||||
|
//
|
||||||
|
// NOTE: due to the behavior of asn1.Marshal, if the original encoding of the RDN sequence
|
||||||
|
// contains strings which do not use the ASN.1 PrintableString type, the name will not be
|
||||||
|
// re-encoded using those types, resulting in a hash which does not match that produced
|
||||||
|
// by OpenSSL.
|
||||||
func x509NameHash(r pkix.RDNSequence) string {
|
func x509NameHash(r pkix.RDNSequence) string {
|
||||||
var canonBytes []byte
|
var canonBytes []byte
|
||||||
// First, canonicalize all the strings.
|
// First, canonicalize all the strings.
|
||||||
|
|
@ -277,10 +286,7 @@ func checkCert(c *x509.Certificate, crlVerifyCrt []*x509.Certificate, cfg Revoca
|
||||||
func checkCertRevocation(c *x509.Certificate, crl *certificateListExt) (RevocationStatus, error) {
|
func checkCertRevocation(c *x509.Certificate, crl *certificateListExt) (RevocationStatus, error) {
|
||||||
// Per section 5.3.3 we prime the certificate issuer with the CRL issuer.
|
// Per section 5.3.3 we prime the certificate issuer with the CRL issuer.
|
||||||
// Subsequent entries use the previous entry's issuer.
|
// Subsequent entries use the previous entry's issuer.
|
||||||
rawEntryIssuer, err := asn1.Marshal(crl.CertList.TBSCertList.Issuer)
|
rawEntryIssuer := crl.RawIssuer
|
||||||
if err != nil {
|
|
||||||
return RevocationUndetermined, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through all the revoked certificates.
|
// Loop through all the revoked certificates.
|
||||||
for _, revCert := range crl.CertList.TBSCertList.RevokedCertificates {
|
for _, revCert := range crl.CertList.TBSCertList.RevokedCertificates {
|
||||||
|
|
@ -456,10 +462,11 @@ func fetchCRL(loc string, rawIssuer []byte, cfg RevocationConfig) (*certificateL
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rawCRLIssuer, err := asn1.Marshal(certList.CertList.TBSCertList.Issuer)
|
rawCRLIssuer, err := extractCRLIssuer(crlBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("asn1.Marshal(%v) failed err = %v", certList.CertList.TBSCertList.Issuer, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
certList.RawIssuer = rawCRLIssuer
|
||||||
// RFC5280, 6.3.3 (b) Verify the issuer and scope of the complete CRL.
|
// RFC5280, 6.3.3 (b) Verify the issuer and scope of the complete CRL.
|
||||||
if bytes.Equal(rawIssuer, rawCRLIssuer) {
|
if bytes.Equal(rawIssuer, rawCRLIssuer) {
|
||||||
parsedCRL = certList
|
parsedCRL = certList
|
||||||
|
|
@ -478,10 +485,6 @@ func verifyCRL(crl *certificateListExt, rawIssuer []byte, chain []*x509.Certific
|
||||||
// RFC5280, 6.3.3 (f) Obtain and validateate the certification path for the issuer of the complete CRL
|
// RFC5280, 6.3.3 (f) Obtain and validateate the certification path for the issuer of the complete CRL
|
||||||
// We intentionally limit our CRLs to be signed with the same certificate path as the certificate
|
// We intentionally limit our CRLs to be signed with the same certificate path as the certificate
|
||||||
// so we can use the chain from the connection.
|
// so we can use the chain from the connection.
|
||||||
rawCRLIssuer, err := asn1.Marshal(crl.CertList.TBSCertList.Issuer)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("asn1.Marshal(%v) failed err = %v", crl.CertList.TBSCertList.Issuer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range chain {
|
for _, c := range chain {
|
||||||
// Use the key where the subject and KIDs match.
|
// Use the key where the subject and KIDs match.
|
||||||
|
|
@ -490,10 +493,35 @@ func verifyCRL(crl *certificateListExt, rawIssuer []byte, chain []*x509.Certific
|
||||||
// "Conforming CRL issuers MUST use the key identifier method, and MUST
|
// "Conforming CRL issuers MUST use the key identifier method, and MUST
|
||||||
// include this extension in all CRLs issued."
|
// include this extension in all CRLs issued."
|
||||||
// So, this is much simpler than RFC4158 and should be compatible.
|
// So, this is much simpler than RFC4158 and should be compatible.
|
||||||
if bytes.Equal(c.SubjectKeyId, crl.AuthorityKeyID) && bytes.Equal(c.RawSubject, rawCRLIssuer) {
|
if bytes.Equal(c.SubjectKeyId, crl.AuthorityKeyID) && bytes.Equal(c.RawSubject, crl.RawIssuer) {
|
||||||
// RFC5280, 6.3.3 (g) Validate signature.
|
// RFC5280, 6.3.3 (g) Validate signature.
|
||||||
return c.CheckCRLSignature(crl.CertList)
|
return c.CheckCRLSignature(crl.CertList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("verifyCRL: No certificates mached CRL issuer (%v)", crl.CertList.TBSCertList.Issuer)
|
return fmt.Errorf("verifyCRL: No certificates mached CRL issuer (%v)", crl.CertList.TBSCertList.Issuer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var crlPemPrefix = []byte("-----BEGIN X509 CRL")
|
||||||
|
|
||||||
|
// extractCRLIssuer extracts the raw ASN.1 encoding of the CRL issuer. Due to the design of
|
||||||
|
// pkix.CertificateList and pkix.RDNSequence, it is not possible to reliably marshal the
|
||||||
|
// parsed Issuer to it's original raw encoding.
|
||||||
|
func extractCRLIssuer(crlBytes []byte) ([]byte, error) {
|
||||||
|
if bytes.HasPrefix(crlBytes, crlPemPrefix) {
|
||||||
|
block, _ := pem.Decode(crlBytes)
|
||||||
|
if block != nil && block.Type == "X509 CRL" {
|
||||||
|
crlBytes = block.Bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
der := cryptobyte.String(crlBytes)
|
||||||
|
var issuer cryptobyte.String
|
||||||
|
if !der.ReadASN1(&der, cbasn1.SEQUENCE) ||
|
||||||
|
!der.ReadASN1(&der, cbasn1.SEQUENCE) ||
|
||||||
|
!der.SkipOptionalASN1(cbasn1.INTEGER) ||
|
||||||
|
!der.SkipASN1(cbasn1.SEQUENCE) ||
|
||||||
|
!der.ReadASN1Element(&issuer, cbasn1.SEQUENCE) {
|
||||||
|
return nil, errors.New("extractCRLIssuer: invalid ASN.1 encoding")
|
||||||
|
}
|
||||||
|
return issuer, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ func makeChain(t *testing.T, name string) []*x509.Certificate {
|
||||||
return certChain
|
return certChain
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCRL(t *testing.T, path string) *pkix.CertificateList {
|
func loadCRL(t *testing.T, path string) *certificateListExt {
|
||||||
b, err := ioutil.ReadFile(path)
|
b, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("readFile(%v) failed err = %v", path, err)
|
t.Fatalf("readFile(%v) failed err = %v", path, err)
|
||||||
|
|
@ -346,7 +346,15 @@ func loadCRL(t *testing.T, path string) *pkix.CertificateList {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseCrl(%v) failed err = %v", path, err)
|
t.Fatalf("ParseCrl(%v) failed err = %v", path, err)
|
||||||
}
|
}
|
||||||
return crl
|
crlExt, err := parseCRLExtensions(crl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseCRLExtensions(%v) failed err = %v", path, err)
|
||||||
|
}
|
||||||
|
crlExt.RawIssuer, err = extractCRLIssuer(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("extractCRLIssuer(%v) failed err= %v", path, err)
|
||||||
|
}
|
||||||
|
return crlExt
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedCRL(t *testing.T) {
|
func TestCachedCRL(t *testing.T) {
|
||||||
|
|
@ -450,11 +458,11 @@ func TestGetIssuerCRLCache(t *testing.T) {
|
||||||
func TestVerifyCrl(t *testing.T) {
|
func TestVerifyCrl(t *testing.T) {
|
||||||
tampered := loadCRL(t, testdata.Path("crl/1.crl"))
|
tampered := loadCRL(t, testdata.Path("crl/1.crl"))
|
||||||
// Change the signature so it won't verify
|
// Change the signature so it won't verify
|
||||||
tampered.SignatureValue.Bytes[0]++
|
tampered.CertList.SignatureValue.Bytes[0]++
|
||||||
|
|
||||||
verifyTests := []struct {
|
verifyTests := []struct {
|
||||||
desc string
|
desc string
|
||||||
crl *pkix.CertificateList
|
crl *certificateListExt
|
||||||
certs []*x509.Certificate
|
certs []*x509.Certificate
|
||||||
cert *x509.Certificate
|
cert *x509.Certificate
|
||||||
errWant string
|
errWant string
|
||||||
|
|
@ -498,11 +506,7 @@ func TestVerifyCrl(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range verifyTests {
|
for _, tt := range verifyTests {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
crlExt, err := parseCRLExtensions(tt.crl)
|
err := verifyCRL(tt.crl, tt.cert.RawIssuer, tt.certs)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("parseCRLExtensions(%v) failed, err = %v", tt.crl.TBSCertList.Issuer, err)
|
|
||||||
}
|
|
||||||
err = verifyCRL(crlExt, tt.cert.RawIssuer, tt.certs)
|
|
||||||
switch {
|
switch {
|
||||||
case tt.errWant == "" && err != nil:
|
case tt.errWant == "" && err != nil:
|
||||||
t.Errorf("Valid CRL did not verify err = %v", err)
|
t.Errorf("Valid CRL did not verify err = %v", err)
|
||||||
|
|
@ -716,3 +720,14 @@ func TestVerifyConnection(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssuerNonPrintableString(t *testing.T) {
|
||||||
|
rawIssuer, err := hex.DecodeString("300c310a300806022a030c023a29")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to decode issuer: %s", err)
|
||||||
|
}
|
||||||
|
_, err = fetchCRL("", rawIssuer, RevocationConfig{RootDir: testdata.Path("crl")})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("fetchCRL failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ go 1.14
|
||||||
require (
|
require (
|
||||||
github.com/google/go-cmp v0.5.1 // indirect
|
github.com/google/go-cmp v0.5.1 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
google.golang.org/grpc v1.38.0
|
google.golang.org/grpc v1.38.0
|
||||||
google.golang.org/grpc/examples v0.0.0-20201112215255-90f1b3ee835b
|
google.golang.org/grpc/examples v0.0.0-20201112215255-90f1b3ee835b
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIHnMFICAQEwDQYJKoZIhvcNAQEMBQAwDDEKMAgGAioDDAI6KRcNMDkxMTEwMjMw
|
||||||
|
MDAwWhcNMDkxMTExMDAwMDAwWqASMBAwDgYDVR0jBAcwBYADAQIDMA0GCSqGSIb3
|
||||||
|
DQEBDAUAA4GBAMl2sjOjtOQ+OCsRyjM0IvqTn7lmdGJMvpYAym367JBamJPCbYrL
|
||||||
|
MifCjCA1ra7gG0MweZbpm4SG2YLakwi1/B+XhApQ5VVv5SwDn6Yy5zr9ePLEF7Iy
|
||||||
|
sP86e9s5XfOusLTW+Spre8q1vi7pJrRvUxhJGuUuLoM6Uhvh65ViilDJ
|
||||||
|
-----END X509 CRL-----
|
||||||
Loading…
Reference in New Issue