mirror of https://github.com/grpc/grpc-go.git
advancedtls: CRL checking for golang gRPC (#4489)
* Code for CRL checking for golang gRPC.
This commit is contained in:
parent
0103ea2d6c
commit
ce7bdf50ab
|
|
@ -0,0 +1,499 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var grpclogLogger = grpclog.Component("advancedtls")
|
||||||
|
|
||||||
|
// Cache is an interface to cache CRL files.
|
||||||
|
// The cache implemetation must be concurrency safe.
|
||||||
|
// A fixed size lru cache from golang-lru is recommended.
|
||||||
|
type Cache interface {
|
||||||
|
// Add adds a value to the cache.
|
||||||
|
Add(key, value interface{}) bool
|
||||||
|
// Get looks up a key's value from the cache.
|
||||||
|
Get(key interface{}) (value interface{}, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevocationConfig contains options for CRL lookup.
|
||||||
|
type RevocationConfig struct {
|
||||||
|
// RootDir is the directory to search for CRL files.
|
||||||
|
// Directory format must match OpenSSL X509_LOOKUP_hash_dir(3).
|
||||||
|
RootDir string
|
||||||
|
// AllowUndetermined controls if certificate chains with RevocationUndetermined
|
||||||
|
// revocation status are allowed to complete.
|
||||||
|
AllowUndetermined bool
|
||||||
|
// Cache will store CRL files if not nil, otherwise files are reloaded for every lookup.
|
||||||
|
Cache Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevocationStatus is the revocation status for a certificate or chain.
|
||||||
|
type RevocationStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RevocationUndetermined means we couldn't find or verify a CRL for the cert.
|
||||||
|
RevocationUndetermined RevocationStatus = iota
|
||||||
|
// RevocationUnrevoked means we found the CRL for the cert and the cert is not revoked.
|
||||||
|
RevocationUnrevoked
|
||||||
|
// RevocationRevoked means we found the CRL and the cert is revoked.
|
||||||
|
RevocationRevoked
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s RevocationStatus) String() string {
|
||||||
|
return [...]string{"RevocationUndetermined", "RevocationUnrevoked", "RevocationRevoked"}[s]
|
||||||
|
}
|
||||||
|
|
||||||
|
// certificateListExt contains a pkix.CertificateList and parsed
|
||||||
|
// extensions that aren't provided by the golang CRL parser.
|
||||||
|
type certificateListExt struct {
|
||||||
|
CertList *pkix.CertificateList
|
||||||
|
// RFC5280, 5.2.1, all conforming CRLs must have a AKID with the ID method.
|
||||||
|
AuthorityKeyID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagDirectoryName = 4
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RFC5280, 5.2.4 id-ce-deltaCRLIndicator OBJECT IDENTIFIER ::= { id-ce 27 }
|
||||||
|
oidDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27}
|
||||||
|
// RFC5280, 5.2.5 id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
|
||||||
|
oidIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
|
||||||
|
// RFC5280, 5.3.3 id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 }
|
||||||
|
oidCertificateIssuer = asn1.ObjectIdentifier{2, 5, 29, 29}
|
||||||
|
// RFC5290, 4.2.1.1 id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 }
|
||||||
|
oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
|
||||||
|
)
|
||||||
|
|
||||||
|
// x509NameHash implements the OpenSSL X509_NAME_hash function for hashed directory lookups.
|
||||||
|
func x509NameHash(r pkix.RDNSequence) string {
|
||||||
|
var canonBytes []byte
|
||||||
|
// First, canonicalize all the strings.
|
||||||
|
for _, rdnSet := range r {
|
||||||
|
for i, rdn := range rdnSet {
|
||||||
|
value, ok := rdn.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// OpenSSL trims all whitespace, does a tolower, and removes extra spaces between words.
|
||||||
|
// Implemented in x509_name_canon in OpenSSL
|
||||||
|
canonStr := strings.Join(strings.Fields(
|
||||||
|
strings.TrimSpace(strings.ToLower(value))), " ")
|
||||||
|
// Then it changes everything to UTF8 strings
|
||||||
|
rdnSet[i].Value = asn1.RawValue{Tag: asn1.TagUTF8String, Bytes: []byte(canonStr)}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, OpenSSL drops the initial sequence tag
|
||||||
|
// so we marshal all the RDNs separately instead of as a group.
|
||||||
|
for _, canonRdn := range r {
|
||||||
|
b, err := asn1.Marshal(canonRdn)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
canonBytes = append(canonBytes, b...)
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerHash := sha1.Sum(canonBytes)
|
||||||
|
// Openssl takes the first 4 bytes and encodes them as a little endian
|
||||||
|
// uint32 and then uses the hex to make the file name.
|
||||||
|
// In C++, this would be:
|
||||||
|
// (((unsigned long)md[0]) | ((unsigned long)md[1] << 8L) |
|
||||||
|
// ((unsigned long)md[2] << 16L) | ((unsigned long)md[3] << 24L)
|
||||||
|
// ) & 0xffffffffL;
|
||||||
|
fileHash := binary.LittleEndian.Uint32(issuerHash[0:4])
|
||||||
|
return fmt.Sprintf("%08x", fileHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckRevocation checks the connection for revoked certificates based on RFC5280.
|
||||||
|
// This implementation has the following major limitations:
|
||||||
|
// * Indirect CRL files are not supported.
|
||||||
|
// * CRL loading is only supported from directories in the X509_LOOKUP_hash_dir format.
|
||||||
|
// * OnlySomeReasons is not supported.
|
||||||
|
// * Delta CRL files are not supported.
|
||||||
|
// * Certificate CRLDistributionPoint must be URLs, but are then ignored and converted into a file path.
|
||||||
|
// * CRL checks are done after path building, which goes against RFC4158.
|
||||||
|
func CheckRevocation(conn tls.ConnectionState, cfg RevocationConfig) error {
|
||||||
|
return CheckChainRevocation(conn.VerifiedChains, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckChainRevocation checks the verified certificate chain
|
||||||
|
// for revoked certificates based on RFC5280.
|
||||||
|
func CheckChainRevocation(verifiedChains [][]*x509.Certificate, cfg RevocationConfig) error {
|
||||||
|
// Iterate the verified chains looking for one that is RevocationUnrevoked.
|
||||||
|
// A single RevocationUnrevoked chain is enough to allow the connection, and a single RevocationRevoked
|
||||||
|
// chain does not mean the connection should fail.
|
||||||
|
count := make(map[RevocationStatus]int)
|
||||||
|
for _, chain := range verifiedChains {
|
||||||
|
switch checkChain(chain, cfg) {
|
||||||
|
case RevocationUnrevoked:
|
||||||
|
// If any chain is RevocationUnrevoked then return no error.
|
||||||
|
return nil
|
||||||
|
case RevocationRevoked:
|
||||||
|
// If this chain is revoked, keep looking for another chain.
|
||||||
|
count[RevocationRevoked]++
|
||||||
|
continue
|
||||||
|
case RevocationUndetermined:
|
||||||
|
if cfg.AllowUndetermined {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
count[RevocationUndetermined]++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("no unrevoked chains found: %v", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkChain will determine and check all certificates in chain against the CRL
|
||||||
|
// defined in the certificate with the following rules:
|
||||||
|
// 1. If any certificate is RevocationRevoked, return RevocationRevoked.
|
||||||
|
// 2. If any certificate is RevocationUndetermined, return RevocationUndetermined.
|
||||||
|
// 3. If all certificates are RevocationUnrevoked, return RevocationUnrevoked.
|
||||||
|
func checkChain(chain []*x509.Certificate, cfg RevocationConfig) RevocationStatus {
|
||||||
|
chainStatus := RevocationUnrevoked
|
||||||
|
for _, c := range chain {
|
||||||
|
switch checkCert(c, chain, cfg) {
|
||||||
|
case RevocationRevoked:
|
||||||
|
// Easy case, if a cert in the chain is revoked, the chain is revoked.
|
||||||
|
return RevocationRevoked
|
||||||
|
case RevocationUndetermined:
|
||||||
|
// If we couldn't find the revocation status for a cert, the chain is at best RevocationUndetermined
|
||||||
|
// keep looking to see if we find a cert in the chain that's RevocationRevoked,
|
||||||
|
// but return RevocationUndetermined at a minimum.
|
||||||
|
chainStatus = RevocationUndetermined
|
||||||
|
case RevocationUnrevoked:
|
||||||
|
// Continue iterating up the cert chain.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chainStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func cachedCrl(rawIssuer []byte, cache Cache) (*certificateListExt, bool) {
|
||||||
|
val, ok := cache.Get(hex.EncodeToString(rawIssuer))
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
crl, ok := val.(*certificateListExt)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
// If the CRL is expired, force a reload.
|
||||||
|
if crl.CertList.HasExpired(time.Now()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return crl, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchIssuerCRL fetches and verifies the CRL for rawIssuer from disk or cache if configured in cfg.
|
||||||
|
func fetchIssuerCRL(crlDistributionPoint string, rawIssuer []byte, crlVerifyCrt []*x509.Certificate, cfg RevocationConfig) (*certificateListExt, error) {
|
||||||
|
if cfg.Cache != nil {
|
||||||
|
if crl, ok := cachedCrl(rawIssuer, cfg.Cache); ok {
|
||||||
|
return crl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crl, err := fetchCRL(crlDistributionPoint, rawIssuer, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetchCRL(%v) failed err = %v", crlDistributionPoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := verifyCRL(crl, rawIssuer, crlVerifyCrt); err != nil {
|
||||||
|
return nil, fmt.Errorf("verifyCRL(%v) failed err = %v", crlDistributionPoint, err)
|
||||||
|
}
|
||||||
|
if cfg.Cache != nil {
|
||||||
|
cfg.Cache.Add(hex.EncodeToString(rawIssuer), crl)
|
||||||
|
}
|
||||||
|
return crl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkCert checks a single certificate against the CRL defined in the certificate.
|
||||||
|
// It will fetch and verify the CRL(s) defined by CRLDistributionPoints.
|
||||||
|
// If we can't load any authoritative CRL files, the status is RevocationUndetermined.
|
||||||
|
// c is the certificate to check.
|
||||||
|
// crlVerifyCrt is the group of possible certificates to verify the crl.
|
||||||
|
func checkCert(c *x509.Certificate, crlVerifyCrt []*x509.Certificate, cfg RevocationConfig) RevocationStatus {
|
||||||
|
if len(c.CRLDistributionPoints) == 0 {
|
||||||
|
return RevocationUnrevoked
|
||||||
|
}
|
||||||
|
// Iterate through CRL distribution points to check for status
|
||||||
|
for _, dp := range c.CRLDistributionPoints {
|
||||||
|
crl, err := fetchIssuerCRL(dp, c.RawIssuer, crlVerifyCrt, cfg)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Warningf("getIssuerCRL(%v) err = %v", c.Issuer, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
revocation, err := checkCertRevocation(c, crl)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Warningf("checkCertRevocation(CRL %v) failed %v", crl.CertList.TBSCertList.Issuer, err)
|
||||||
|
// We couldn't check the CRL file for some reason, so continue
|
||||||
|
// to the next file
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Here we've gotten a CRL that loads and verifies.
|
||||||
|
// We only handle all-reasons CRL files, so this file
|
||||||
|
// is authoritative for the certificate.
|
||||||
|
return revocation
|
||||||
|
|
||||||
|
}
|
||||||
|
// We couldn't load any CRL files for the certificate, so we don't know if it's RevocationUnrevoked or not.
|
||||||
|
return RevocationUndetermined
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCertRevocation(c *x509.Certificate, crl *certificateListExt) (RevocationStatus, error) {
|
||||||
|
// Per section 5.3.3 we prime the certificate issuer with the CRL issuer.
|
||||||
|
// Subsequent entries use the previous entry's issuer.
|
||||||
|
rawEntryIssuer, err := asn1.Marshal(crl.CertList.TBSCertList.Issuer)
|
||||||
|
if err != nil {
|
||||||
|
return RevocationUndetermined, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all the revoked certificates.
|
||||||
|
for _, revCert := range crl.CertList.TBSCertList.RevokedCertificates {
|
||||||
|
// 5.3 Loop through CRL entry extensions for needed information.
|
||||||
|
for _, ext := range revCert.Extensions {
|
||||||
|
if oidCertificateIssuer.Equal(ext.Id) {
|
||||||
|
extIssuer, err := parseCertIssuerExt(ext)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Info(err)
|
||||||
|
if ext.Critical {
|
||||||
|
return RevocationUndetermined, err
|
||||||
|
}
|
||||||
|
// Since this is a non-critical extension, we can skip it even though
|
||||||
|
// there was a parsing failure.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rawEntryIssuer = extIssuer
|
||||||
|
} else if ext.Critical {
|
||||||
|
return RevocationUndetermined, fmt.Errorf("checkCertRevocation: Unhandled critical extension: %v", ext.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the issuer and serial number appear in the CRL, the certificate is revoked.
|
||||||
|
if bytes.Equal(c.RawIssuer, rawEntryIssuer) && c.SerialNumber.Cmp(revCert.SerialNumber) == 0 {
|
||||||
|
// CRL contains the serial, so return revoked.
|
||||||
|
return RevocationRevoked, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We did not find the serial in the CRL file that was valid for the cert
|
||||||
|
// so the certificate is not revoked.
|
||||||
|
return RevocationUnrevoked, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCertIssuerExt(ext pkix.Extension) ([]byte, error) {
|
||||||
|
// 5.3.3 Certificate Issuer
|
||||||
|
// CertificateIssuer ::= GeneralNames
|
||||||
|
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
||||||
|
var generalNames []asn1.RawValue
|
||||||
|
if rest, err := asn1.Unmarshal(ext.Value, &generalNames); err != nil || len(rest) != 0 {
|
||||||
|
return nil, fmt.Errorf("asn1.Unmarshal failed err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, generalName := range generalNames {
|
||||||
|
// GeneralName ::= CHOICE {
|
||||||
|
// otherName [0] OtherName,
|
||||||
|
// rfc822Name [1] IA5String,
|
||||||
|
// dNSName [2] IA5String,
|
||||||
|
// x400Address [3] ORAddress,
|
||||||
|
// directoryName [4] Name,
|
||||||
|
// ediPartyName [5] EDIPartyName,
|
||||||
|
// uniformResourceIdentifier [6] IA5String,
|
||||||
|
// iPAddress [7] OCTET STRING,
|
||||||
|
// registeredID [8] OBJECT IDENTIFIER }
|
||||||
|
if generalName.Tag == tagDirectoryName {
|
||||||
|
return generalName.Bytes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Conforming CRL issuers MUST include in this extension the
|
||||||
|
// distinguished name (DN) from the issuer field of the certificate that
|
||||||
|
// corresponds to this CRL entry.
|
||||||
|
// If we couldn't get a directoryName, we can't reason about this file so cert status is
|
||||||
|
// RevocationUndetermined.
|
||||||
|
return nil, errors.New("no DN found in certificate issuer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 5280, 4.2.1.1
|
||||||
|
type authKeyID struct {
|
||||||
|
ID []byte `asn1:"optional,tag:0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC5280, 5.2.5
|
||||||
|
// id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
|
||||||
|
|
||||||
|
// IssuingDistributionPoint ::= SEQUENCE {
|
||||||
|
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||||
|
// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
|
||||||
|
// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
|
||||||
|
// onlySomeReasons [3] ReasonFlags OPTIONAL,
|
||||||
|
// indirectCRL [4] BOOLEAN DEFAULT FALSE,
|
||||||
|
// onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
|
||||||
|
|
||||||
|
// -- at most one of onlyContainsUserCerts, onlyContainsCACerts,
|
||||||
|
// -- and onlyContainsAttributeCerts may be set to TRUE.
|
||||||
|
type issuingDistributionPoint struct {
|
||||||
|
DistributionPoint asn1.RawValue `asn1:"optional,tag:0"`
|
||||||
|
OnlyContainsUserCerts bool `asn1:"optional,tag:1"`
|
||||||
|
OnlyContainsCACerts bool `asn1:"optional,tag:2"`
|
||||||
|
OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"`
|
||||||
|
IndirectCRL bool `asn1:"optional,tag:4"`
|
||||||
|
OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCRLExtensions parses the extensions for a CRL
|
||||||
|
// and checks that they're supported by the parser.
|
||||||
|
func parseCRLExtensions(c *pkix.CertificateList) (*certificateListExt, error) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, errors.New("c is nil, expected any value")
|
||||||
|
}
|
||||||
|
certList := &certificateListExt{CertList: c}
|
||||||
|
|
||||||
|
for _, ext := range c.TBSCertList.Extensions {
|
||||||
|
switch {
|
||||||
|
case oidDeltaCRLIndicator.Equal(ext.Id):
|
||||||
|
return nil, fmt.Errorf("delta CRLs unsupported")
|
||||||
|
|
||||||
|
case oidAuthorityKeyIdentifier.Equal(ext.Id):
|
||||||
|
var a authKeyID
|
||||||
|
if rest, err := asn1.Unmarshal(ext.Value, &a); err != nil {
|
||||||
|
return nil, fmt.Errorf("asn1.Unmarshal failed. err = %v", err)
|
||||||
|
} else if len(rest) != 0 {
|
||||||
|
return nil, errors.New("trailing data after AKID extension")
|
||||||
|
}
|
||||||
|
certList.AuthorityKeyID = a.ID
|
||||||
|
|
||||||
|
case oidIssuingDistributionPoint.Equal(ext.Id):
|
||||||
|
var dp issuingDistributionPoint
|
||||||
|
if rest, err := asn1.Unmarshal(ext.Value, &dp); err != nil {
|
||||||
|
return nil, fmt.Errorf("asn1.Unmarshal failed. err = %v", err)
|
||||||
|
} else if len(rest) != 0 {
|
||||||
|
return nil, errors.New("trailing data after IssuingDistributionPoint extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dp.OnlyContainsUserCerts || dp.OnlyContainsCACerts || dp.OnlyContainsAttributeCerts {
|
||||||
|
return nil, errors.New("CRL only contains some certificate types")
|
||||||
|
}
|
||||||
|
if dp.IndirectCRL {
|
||||||
|
return nil, errors.New("indirect CRLs unsupported")
|
||||||
|
}
|
||||||
|
if dp.OnlySomeReasons.BitLength != 0 {
|
||||||
|
return nil, errors.New("onlySomeReasons unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
case ext.Critical:
|
||||||
|
return nil, fmt.Errorf("unsupported critical extension: %v", ext.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certList.AuthorityKeyID) == 0 {
|
||||||
|
return nil, errors.New("authority key identifier extension missing")
|
||||||
|
}
|
||||||
|
return certList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchCRL(loc string, rawIssuer []byte, cfg RevocationConfig) (*certificateListExt, error) {
|
||||||
|
var parsedCRL *certificateListExt
|
||||||
|
// 6.3.3 (a) (1) (ii)
|
||||||
|
// According to X509_LOOKUP_hash_dir the format is issuer_hash.rN where N is an increasing number.
|
||||||
|
// There are no gaps, so we break when we can't find a file.
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
// Unmarshal to RDNSeqence according to http://go/godoc/crypto/x509/pkix/#Name.
|
||||||
|
var r pkix.RDNSequence
|
||||||
|
rest, err := asn1.Unmarshal(rawIssuer, &r)
|
||||||
|
if len(rest) != 0 || err != nil {
|
||||||
|
return nil, fmt.Errorf("asn1.Unmarshal(Issuer) len(rest) = %v, err = %v", len(rest), err)
|
||||||
|
}
|
||||||
|
crlPath := fmt.Sprintf("%s.r%d", filepath.Join(cfg.RootDir, x509NameHash(r)), i)
|
||||||
|
crlBytes, err := ioutil.ReadFile(crlPath)
|
||||||
|
if err != nil {
|
||||||
|
// Break when we can't read a CRL file.
|
||||||
|
grpclogLogger.Infof("readFile: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
crl, err := x509.ParseCRL(crlBytes)
|
||||||
|
if err != nil {
|
||||||
|
// Parsing errors for a CRL shouldn't happen so fail.
|
||||||
|
return nil, fmt.Errorf("x509.ParseCrl(%v) failed err = %v", crlPath, err)
|
||||||
|
}
|
||||||
|
var certList *certificateListExt
|
||||||
|
if certList, err = parseCRLExtensions(crl); err != nil {
|
||||||
|
grpclogLogger.Infof("fetchCRL: unsupported crl %v, err = %v", crlPath, err)
|
||||||
|
// Continue to find a supported CRL
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rawCRLIssuer, err := asn1.Marshal(certList.CertList.TBSCertList.Issuer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("asn1.Marshal(%v) failed err = %v", certList.CertList.TBSCertList.Issuer, err)
|
||||||
|
}
|
||||||
|
// RFC5280, 6.3.3 (b) Verify the issuer and scope of the complete CRL.
|
||||||
|
if bytes.Equal(rawIssuer, rawCRLIssuer) {
|
||||||
|
parsedCRL = certList
|
||||||
|
// Continue to find the highest number in the .rN suffix.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedCRL == nil {
|
||||||
|
return nil, fmt.Errorf("fetchCrls no CRLs found for issuer")
|
||||||
|
}
|
||||||
|
return parsedCRL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyCRL(crl *certificateListExt, rawIssuer []byte, chain []*x509.Certificate) error {
|
||||||
|
// 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
|
||||||
|
// 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 {
|
||||||
|
// Use the key where the subject and KIDs match.
|
||||||
|
// This departs from RFC4158, 3.5.12 which states that KIDs
|
||||||
|
// cannot eliminate certificates, but RFC5280, 5.2.1 states that
|
||||||
|
// "Conforming CRL issuers MUST use the key identifier method, and MUST
|
||||||
|
// include this extension in all CRLs issued."
|
||||||
|
// So, this is much simpler than RFC4158 and should be compatible.
|
||||||
|
if bytes.Equal(c.SubjectKeyId, crl.AuthorityKeyID) && bytes.Equal(c.RawSubject, rawCRLIssuer) {
|
||||||
|
// RFC5280, 6.3.3 (g) Validate signature.
|
||||||
|
return c.CheckCRLSignature(crl.CertList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("verifyCRL: No certificates mached CRL issuer (%v)", crl.CertList.TBSCertList.Issuer)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,718 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
"google.golang.org/grpc/security/advancedtls/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestX509NameHash(t *testing.T) {
|
||||||
|
nameTests := []struct {
|
||||||
|
in pkix.Name
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
Country: []string{"US"},
|
||||||
|
Organization: []string{"Example"},
|
||||||
|
},
|
||||||
|
out: "9cdd41ff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
Country: []string{"us"},
|
||||||
|
Organization: []string{"example"},
|
||||||
|
},
|
||||||
|
out: "9cdd41ff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
Country: []string{" us"},
|
||||||
|
Organization: []string{"example"},
|
||||||
|
},
|
||||||
|
out: "9cdd41ff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
Country: []string{"US"},
|
||||||
|
Province: []string{"California"},
|
||||||
|
Locality: []string{"Mountain View"},
|
||||||
|
Organization: []string{"BoringSSL"},
|
||||||
|
},
|
||||||
|
out: "c24414d9",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
Country: []string{"US"},
|
||||||
|
Province: []string{"California"},
|
||||||
|
Locality: []string{"Mountain View"},
|
||||||
|
Organization: []string{"BoringSSL"},
|
||||||
|
},
|
||||||
|
out: "c24414d9",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
SerialNumber: "87f4514475ba0a2b",
|
||||||
|
},
|
||||||
|
out: "9dc713cd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
Country: []string{"US"},
|
||||||
|
Province: []string{"California"},
|
||||||
|
Locality: []string{"Mountain View"},
|
||||||
|
Organization: []string{"Google LLC"},
|
||||||
|
OrganizationalUnit: []string{"Production", "campus-sln"},
|
||||||
|
CommonName: "Root CA (2021-02-02T07:30:36-08:00)",
|
||||||
|
},
|
||||||
|
out: "0b35a562",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: pkix.Name{
|
||||||
|
ExtraNames: []pkix.AttributeTypeAndValue{
|
||||||
|
{Type: asn1.ObjectIdentifier{5, 5, 5, 5}, Value: "aaaa"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: "eea339da",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range nameTests {
|
||||||
|
t.Run(tt.in.String(), func(t *testing.T) {
|
||||||
|
h := x509NameHash(tt.in.ToRDNSequence())
|
||||||
|
if h != tt.out {
|
||||||
|
t.Errorf("x509NameHash(%v): Got %v wanted %v", tt.in, h, tt.out)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := x509.ParseCRL(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 := x509.ParseCRL(dummyCrlFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("x509.ParseCRL(dummyCrlFile) failed: %v", err)
|
||||||
|
}
|
||||||
|
crlExt := &certificateListExt{CertList: crl}
|
||||||
|
var crlIssuer pkix.Name
|
||||||
|
crlIssuer.FillFromRDNSequence(&crl.TBSCertList.Issuer)
|
||||||
|
|
||||||
|
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: crlIssuer,
|
||||||
|
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 := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ioutil.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) *pkix.CertificateList {
|
||||||
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("readFile(%v) failed err = %v", path, err)
|
||||||
|
}
|
||||||
|
crl, err := x509.ParseCRL(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseCrl(%v) failed err = %v", path, err)
|
||||||
|
}
|
||||||
|
return crl
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCachedCRL(t *testing.T) {
|
||||||
|
cache, err := lru.New(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("lru.New: err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
val interface{}
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Valid",
|
||||||
|
val: &certificateListExt{
|
||||||
|
CertList: &pkix.CertificateList{
|
||||||
|
TBSCertList: pkix.TBSCertificateList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Expired",
|
||||||
|
val: &certificateListExt{
|
||||||
|
CertList: &pkix.CertificateList{
|
||||||
|
TBSCertList: pkix.TBSCertificateList{
|
||||||
|
NextUpdate: time.Now().Add(-time.Hour),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Wrong Type",
|
||||||
|
val: "string",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Empty",
|
||||||
|
val: nil,
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
if tt.val != nil {
|
||||||
|
cache.Add(hex.EncodeToString([]byte(tt.desc)), tt.val)
|
||||||
|
}
|
||||||
|
_, ok := cachedCrl([]byte(tt.desc), cache)
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("Cache ok error expected %v vs %v", tt.ok, ok)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetIssuerCRLCache(t *testing.T) {
|
||||||
|
cache, err := lru.New(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("lru.New: err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
rawIssuer []byte
|
||||||
|
certs []*x509.Certificate
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Valid",
|
||||||
|
rawIssuer: makeChain(t, testdata.Path("crl/unrevoked.pem"))[1].RawIssuer,
|
||||||
|
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Unverified",
|
||||||
|
rawIssuer: makeChain(t, testdata.Path("crl/unrevoked.pem"))[1].RawIssuer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Not Found",
|
||||||
|
rawIssuer: []byte("not_found"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
cache.Purge()
|
||||||
|
_, err := fetchIssuerCRL("test", tt.rawIssuer, tt.certs, RevocationConfig{
|
||||||
|
RootDir: testdata.Path("."),
|
||||||
|
Cache: cache,
|
||||||
|
})
|
||||||
|
if err == nil && cache.Len() == 0 {
|
||||||
|
t.Error("Verified CRL not added to cache")
|
||||||
|
}
|
||||||
|
if err != nil && cache.Len() != 0 {
|
||||||
|
t.Error("Unverified CRL added to cache")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyCrl(t *testing.T) {
|
||||||
|
tampered := loadCRL(t, testdata.Path("crl/1.crl"))
|
||||||
|
// Change the signature so it won't verify
|
||||||
|
tampered.SignatureValue.Bytes[0]++
|
||||||
|
|
||||||
|
verifyTests := []struct {
|
||||||
|
desc string
|
||||||
|
crl *pkix.CertificateList
|
||||||
|
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 mached",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 mached",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Fail Tampered signature",
|
||||||
|
crl: tampered,
|
||||||
|
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
|
||||||
|
cert: makeChain(t, testdata.Path("crl/unrevoked.pem"))[1],
|
||||||
|
errWant: "verification failure",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range verifyTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
crlExt, err := parseCRLExtensions(tt.crl)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseCRLExtensions(%v) failed, err = %v", tt.crl.TBSCertList.Issuer, err)
|
||||||
|
}
|
||||||
|
err = verifyCRL(crlExt, tt.cert.RawIssuer, 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"))
|
||||||
|
cache, err := lru.New(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("lru.New: err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var revocationTests = []struct {
|
||||||
|
desc string
|
||||||
|
in tls.ConnectionState
|
||||||
|
revoked bool
|
||||||
|
allowUndetermined 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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Undetermined allowed",
|
||||||
|
in: tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{
|
||||||
|
{&x509.Certificate{CRLDistributionPoints: []string{"test"}}},
|
||||||
|
}},
|
||||||
|
revoked: false,
|
||||||
|
allowUndetermined: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range revocationTests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
err := CheckRevocation(tt.in, RevocationConfig{
|
||||||
|
RootDir: testdata.Path("crl"),
|
||||||
|
AllowUndetermined: tt.allowUndetermined,
|
||||||
|
Cache: cache,
|
||||||
|
})
|
||||||
|
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,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||||
|
IPAddresses: []net.IP{net.ParseIP("::1")},
|
||||||
|
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 := ioutil.TempDir("", "crl_dir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ioutil.TempDir failed err = %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
crl, err := cert.CreateCRL(rand.Reader, key, tt.revoked, time.Now(), time.Now().Add(time.Hour))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("templ.CreateCRL failed err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(path.Join(dir, fmt.Sprintf("%s.r0", x509NameHash(cert.Subject.ToRDNSequence()))), crl, 0777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ioutil.WriteFile failed err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cp := x509.NewCertPool()
|
||||||
|
cp.AddCert(cert)
|
||||||
|
cliCfg := tls.Config{
|
||||||
|
RootCAs: cp,
|
||||||
|
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||||
|
return CheckRevocation(cs, RevocationConfig{RootDir: dir})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ module google.golang.org/grpc/security/advancedtls/examples
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
google.golang.org/grpc v1.36.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
|
||||||
google.golang.org/grpc/security/advancedtls v0.0.0-20201112215255-90f1b3ee835b
|
google.golang.org/grpc/security/advancedtls v0.0.0-20201112215255-90f1b3ee835b
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/go-cmp v0.5.1 // indirect
|
github.com/google/go-cmp v0.5.1 // indirect
|
||||||
google.golang.org/grpc v1.36.0
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
5.crl
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
1.crl
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIIBYDCCAQYCAQEwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpH
|
||||||
|
b29nbGUgTExDMSYwEQYDVQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNs
|
||||||
|
bjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAyMS0wMi0wMlQwNzozMDozNi0wODowMCkX
|
||||||
|
DTIxMDIwMjE1MzAzNloXDTIxMDIwOTE1MzAzNlqgLzAtMB8GA1UdIwQYMBaAFPQN
|
||||||
|
tnCIBcG4ReQgoVi0kPgTROseMAoGA1UdFAQDAgEAMAoGCCqGSM49BAMCA0gAMEUC
|
||||||
|
IQDB9WEPBPHEo5xjCv8CT9okockJJnkLDOus6FypVLqj5QIgYw9/PYLwb41/Uc+4
|
||||||
|
LLTAsfdDWh7xBJmqvVQglMoJOEc=
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
2.crl
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIIBYDCCAQYCAQEwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpH
|
||||||
|
b29nbGUgTExDMSYwEQYDVQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNs
|
||||||
|
bjEsMCoGA1UEAxMjbm9kZSBDQSAoMjAyMS0wMi0wMlQwNzozMDozNi0wODowMCkX
|
||||||
|
DTIxMDIwMjE1MzAzNloXDTIxMDIwOTE1MzAzNlqgLzAtMB8GA1UdIwQYMBaAFBjo
|
||||||
|
V5Jnk/gp1k7fmWwkvTk/cF/IMAoGA1UdFAQDAgEAMAoGCCqGSM49BAMCA0gAMEUC
|
||||||
|
IQDgjA1Vj/pNFtNRL0vFEdapmFoArHM2+rn4IiP8jYLsCAIgAj2KEHbbtJ3zl5XP
|
||||||
|
WVW6ZyW7r3wIX+Bt3vLJWPrQtf8=
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIIBiDCCAS8CAQEwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpH
|
||||||
|
b29nbGUgTExDMSYwEQYDVQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNs
|
||||||
|
bjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAyMS0wMi0wMlQwNzozMTo1NC0wODowMCkX
|
||||||
|
DTIxMDIwMjE1MzE1NFoXDTIxMDIwOTE1MzE1NFowJzAlAhQAroEYW855BRqTrlov
|
||||||
|
5cBCGvkutxcNMjEwMjAyMTUzMTU0WqAvMC0wHwYDVR0jBBgwFoAUeq/TQ959KbWk
|
||||||
|
/um08jSTXogXpWUwCgYDVR0UBAMCAQEwCgYIKoZIzj0EAwIDRwAwRAIgaSOIhJDg
|
||||||
|
wOLYlbXkmxW0cqy/AfOUNYbz5D/8/FfvhosCICftg7Vzlu0Nh83jikyjy+wtkiJt
|
||||||
|
ZYNvGFQ3Sp2L3A9e
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIIBYDCCAQYCAQEwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpH
|
||||||
|
b29nbGUgTExDMSYwEQYDVQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNs
|
||||||
|
bjEsMCoGA1UEAxMjbm9kZSBDQSAoMjAyMS0wMi0wMlQwNzozMTo1NC0wODowMCkX
|
||||||
|
DTIxMDIwMjE1MzE1NFoXDTIxMDIwOTE1MzE1NFqgLzAtMB8GA1UdIwQYMBaAFIVn
|
||||||
|
8tIFgZpIdhomgYJ2c5ULLzpSMAoGA1UdFAQDAgEAMAoGCCqGSM49BAMCA0gAMEUC
|
||||||
|
ICupTvOqgAyRa1nn7+Pe/1vvlJPAQ8gUfTQsQ6XX3v6oAiEA08B2PsK6aTEwzjry
|
||||||
|
pXqhlUNZFzgaXrVVQuEJbyJ1qoU=
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIIBXzCCAQYCAQEwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpH
|
||||||
|
b29nbGUgTExDMSYwEQYDVQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNs
|
||||||
|
bjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAyMS0wMi0wMlQwNzozMjo1Ny0wODowMCkX
|
||||||
|
DTIxMDIwMjE1MzI1N1oXDTIxMDIwOTE1MzI1N1qgLzAtMB8GA1UdIwQYMBaAFN+g
|
||||||
|
xTAtSTlb5Qqvrbp4rZtsaNzqMAoGA1UdFAQDAgEAMAoGCCqGSM49BAMCA0cAMEQC
|
||||||
|
IHrRKjieY7w7gxvpkJAdszPZBlaSSp/c9wILutBTy7SyAiAwhaHfgas89iRfaBs2
|
||||||
|
EhGIeK39A+kSzqu6qEQBHpK36g==
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIIBiDCCAS8CAQEwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
|
||||||
|
EwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpH
|
||||||
|
b29nbGUgTExDMSYwEQYDVQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNs
|
||||||
|
bjEsMCoGA1UEAxMjbm9kZSBDQSAoMjAyMS0wMi0wMlQwNzozMjo1Ny0wODowMCkX
|
||||||
|
DTIxMDIwMjE1MzI1N1oXDTIxMDIwOTE1MzI1N1owJzAlAhQAxSe/pGmyvzN7mxm5
|
||||||
|
6ZJTYUXYuhcNMjEwMjAyMTUzMjU3WqAvMC0wHwYDVR0jBBgwFoAUpZ30UJXB4lI9
|
||||||
|
j2SzodCtRFckrRcwCgYDVR0UBAMCAQEwCgYIKoZIzj0EAwIDRwAwRAIgRg3u7t3b
|
||||||
|
oyV5FhMuGGzWnfIwnKclpT8imnp8tEN253sCIFUY7DjiDohwu4Zup3bWs1OaZ3q3
|
||||||
|
cm+j0H/oe8zzCAgp
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
4.crl
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3.crl
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
6.crl
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# CRL Test Data
|
||||||
|
|
||||||
|
This directory contains cert chains and CRL files for revocation testing.
|
||||||
|
|
||||||
|
To print the chain, use a command like,
|
||||||
|
|
||||||
|
```shell
|
||||||
|
openssl crl2pkcs7 -nocrl -certfile security/crl/x509/client/testdata/revokedLeaf.pem | openssl pkcs7 -print_certs -text -noout
|
||||||
|
```
|
||||||
|
|
||||||
|
The crl file symlinks are generated with `openssl rehash`
|
||||||
|
|
||||||
|
## unrevoked.pem
|
||||||
|
|
||||||
|
A certificate chain with CRL files and unrevoked certs
|
||||||
|
|
||||||
|
* Subject: C=US, ST=California, L=Mountain View, O=Google LLC, OU=Production,
|
||||||
|
OU=campus-sln, CN=Root CA (2021-02-02T07:30:36-08:00)
|
||||||
|
* 1.crl
|
||||||
|
|
||||||
|
NOTE: 1.crl file is symlinked with 5.crl to simulate two issuers that hash to
|
||||||
|
the same value to test that loading multiple files works.
|
||||||
|
|
||||||
|
* Subject: C=US, ST=California, L=Mountain View, O=Google LLC, OU=Production,
|
||||||
|
OU=campus-sln, CN=node CA (2021-02-02T07:30:36-08:00)
|
||||||
|
* 2.crl
|
||||||
|
|
||||||
|
## revokedInt.pem
|
||||||
|
|
||||||
|
Certificate chain where the intermediate is revoked
|
||||||
|
|
||||||
|
* Subject: C=US, ST=California, L=Mountain View, O=Google LLC, OU=Production,
|
||||||
|
OU=campus-sln, CN=Root CA (2021-02-02T07:31:54-08:00)
|
||||||
|
* 3.crl
|
||||||
|
* Subject: C=US, ST=California, L=Mountain View, O=Google LLC, OU=Production,
|
||||||
|
OU=campus-sln, CN=node CA (2021-02-02T07:31:54-08:00)
|
||||||
|
* 4.crl
|
||||||
|
|
||||||
|
## revokedLeaf.pem
|
||||||
|
|
||||||
|
Certificate chain where the leaf is revoked
|
||||||
|
|
||||||
|
* Subject: C=US, ST=California, L=Mountain View, O=Google LLC, OU=Production,
|
||||||
|
OU=campus-sln, CN=Root CA (2021-02-02T07:32:57-08:00)
|
||||||
|
* 5.crl
|
||||||
|
* Subject: C=US, ST=California, L=Mountain View, O=Google LLC, OU=Production,
|
||||||
|
OU=campus-sln, CN=node CA (2021-02-02T07:32:57-08:00)
|
||||||
|
* 6.crl
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
5.crl
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDAzCCAqmgAwIBAgITAWjKwm2dNQvkO62Jgyr5rAvVQzAKBggqhkjOPQQDAjCB
|
||||||
|
pTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1v
|
||||||
|
dW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxJjARBgNVBAsTClByb2R1
|
||||||
|
Y3Rpb24wEQYDVQQLEwpjYW1wdXMtc2xuMSwwKgYDVQQDEyNSb290IENBICgyMDIx
|
||||||
|
LTAyLTAyVDA3OjMxOjU0LTA4OjAwKTAgFw0yMTAyMDIxNTMxNTRaGA85OTk5MTIz
|
||||||
|
MTIzNTk1OVowgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
|
||||||
|
FAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYD
|
||||||
|
VQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjUm9v
|
||||||
|
dCBDQSAoMjAyMS0wMi0wMlQwNzozMTo1NC0wODowMCkwWTATBgcqhkjOPQIBBggq
|
||||||
|
hkjOPQMBBwNCAAQhA0/puhTtSxbVVHseVhL2z7QhpPyJs5Q4beKi7tpaYRDmVn6p
|
||||||
|
Phh+jbRzg8Qj4gKI/Q1rrdm4rKer63LHpdWdo4GzMIGwMA4GA1UdDwEB/wQEAwIB
|
||||||
|
BjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB
|
||||||
|
/zAdBgNVHQ4EFgQUeq/TQ959KbWk/um08jSTXogXpWUwHwYDVR0jBBgwFoAUeq/T
|
||||||
|
Q959KbWk/um08jSTXogXpWUwLgYDVR0RBCcwJYYjc3BpZmZlOi8vY2FtcHVzLXNs
|
||||||
|
bi5wcm9kLmdvb2dsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIgOSQZvyDPQwVOWnpF
|
||||||
|
zWvI+DS2yXIj/2T2EOvJz2XgcK4CIQCL0mh/+DxLiO4zzbInKr0mxpGSxSeZCUk7
|
||||||
|
1ZF7AeLlbw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDizCCAzKgAwIBAgIUAK6BGFvOeQUak65aL+XAQhr5LrcwCgYIKoZIzj0EAwIw
|
||||||
|
gaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
|
||||||
|
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYDVQQLEwpQcm9k
|
||||||
|
dWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAy
|
||||||
|
MS0wMi0wMlQwNzozMTo1NC0wODowMCkwIBcNMjEwMjAyMTUzMTU0WhgPOTk5OTEy
|
||||||
|
MzEyMzU5NTlaMIGlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW
|
||||||
|
MBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIExMQzEmMBEG
|
||||||
|
A1UECxMKUHJvZHVjdGlvbjARBgNVBAsTCmNhbXB1cy1zbG4xLDAqBgNVBAMTI25v
|
||||||
|
ZGUgQ0EgKDIwMjEtMDItMDJUMDc6MzE6NTQtMDg6MDApMFkwEwYHKoZIzj0CAQYI
|
||||||
|
KoZIzj0DAQcDQgAEye6UOlBos8Q3FFBiLahD9BaLTA18bO4MTPyv35T3lppvxD5X
|
||||||
|
U/AnEllOnx5OMtMjMBbIQjSkMbiQ9xNXoSqB6aOCATowggE2MA4GA1UdDwEB/wQE
|
||||||
|
AwIBBjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUw
|
||||||
|
AwEB/zAdBgNVHQ4EFgQUhWfy0gWBmkh2GiaBgnZzlQsvOlIwHwYDVR0jBBgwFoAU
|
||||||
|
eq/TQ959KbWk/um08jSTXogXpWUwMwYDVR0RBCwwKoYoc3BpZmZlOi8vbm9kZS5j
|
||||||
|
YW1wdXMtc2xuLnByb2QuZ29vZ2xlLmNvbTA7BgNVHR4BAf8EMTAvoC0wK4YpY3Nj
|
||||||
|
cy10ZWFtLm5vZGUuY2FtcHVzLXNsbi5wcm9kLmdvb2dsZS5jb20wQgYDVR0fBDsw
|
||||||
|
OTA3oDWgM4YxaHR0cDovL3N0YXRpYy5jb3JwLmdvb2dsZS5jb20vY3JsL2NhbXB1
|
||||||
|
cy1zbG4vbm9kZTAKBggqhkjOPQQDAgNHADBEAiA79rPu6ZO1/0qB6RxL7jVz1200
|
||||||
|
UTo8ioB4itbTzMnJqAIgJqp/Rc8OhpsfzQX8XnIIkl+SewT+tOxJT1MHVNMlVhc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC0DCCAnWgAwIBAgITXQ2c/C27OGqk4Pbu+MNJlOtpYTAKBggqhkjOPQQDAjCB
|
||||||
|
pTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1v
|
||||||
|
dW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxJjARBgNVBAsTClByb2R1
|
||||||
|
Y3Rpb24wEQYDVQQLEwpjYW1wdXMtc2xuMSwwKgYDVQQDEyNub2RlIENBICgyMDIx
|
||||||
|
LTAyLTAyVDA3OjMxOjU0LTA4OjAwKTAgFw0yMTAyMDIxNTMxNTRaGA85OTk5MTIz
|
||||||
|
MTIzNTk1OVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABN2/1le5d3hS/piw
|
||||||
|
hrNMHjd7gPEjzXwtuXQTzdV+aaeOf3ldnC6OnEF/bggym9MldQSJZLXPYSaoj430
|
||||||
|
Vu5PRNejggEkMIIBIDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH
|
||||||
|
AwIGCCsGAQUFBwMBMB0GA1UdDgQWBBTEewP3JgrJPekWWGGjChVqaMhaqTAfBgNV
|
||||||
|
HSMEGDAWgBSFZ/LSBYGaSHYaJoGCdnOVCy86UjBrBgNVHREBAf8EYTBfghZqemFi
|
||||||
|
MTIucHJvZC5nb29nbGUuY29thkVzcGlmZmU6Ly9jc2NzLXRlYW0ubm9kZS5jYW1w
|
||||||
|
dXMtc2xuLnByb2QuZ29vZ2xlLmNvbS9yb2xlL2JvcmctYWRtaW4tY28wQgYDVR0f
|
||||||
|
BDswOTA3oDWgM4YxaHR0cDovL3N0YXRpYy5jb3JwLmdvb2dsZS5jb20vY3JsL2Nh
|
||||||
|
bXB1cy1zbG4vbm9kZTAKBggqhkjOPQQDAgNJADBGAiEA9w4qp3nHpXo+6d7mZc69
|
||||||
|
QoALfP5ynfBCArt8bAlToo8CIQCgc/lTfl2BtBko+7h/w6pKxLeuoQkvCL5gHFyK
|
||||||
|
LXE6vA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDAzCCAqmgAwIBAgITTwodm6C4ZabFVUVa5yBw0TbzJTAKBggqhkjOPQQDAjCB
|
||||||
|
pTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1v
|
||||||
|
dW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxJjARBgNVBAsTClByb2R1
|
||||||
|
Y3Rpb24wEQYDVQQLEwpjYW1wdXMtc2xuMSwwKgYDVQQDEyNSb290IENBICgyMDIx
|
||||||
|
LTAyLTAyVDA3OjMyOjU3LTA4OjAwKTAgFw0yMTAyMDIxNTMyNTdaGA85OTk5MTIz
|
||||||
|
MTIzNTk1OVowgaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
|
||||||
|
FAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYD
|
||||||
|
VQQLEwpQcm9kdWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjUm9v
|
||||||
|
dCBDQSAoMjAyMS0wMi0wMlQwNzozMjo1Ny0wODowMCkwWTATBgcqhkjOPQIBBggq
|
||||||
|
hkjOPQMBBwNCAARoZnzQWvAoyhvCLA2cFIK17khSaA9aA+flS5X9fLRt4RsfPCx3
|
||||||
|
kim7wYKQSmBhQdc1UM4h3969r1c1Fvsh2H9qo4GzMIGwMA4GA1UdDwEB/wQEAwIB
|
||||||
|
BjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB
|
||||||
|
/zAdBgNVHQ4EFgQU36DFMC1JOVvlCq+tunitm2xo3OowHwYDVR0jBBgwFoAU36DF
|
||||||
|
MC1JOVvlCq+tunitm2xo3OowLgYDVR0RBCcwJYYjc3BpZmZlOi8vY2FtcHVzLXNs
|
||||||
|
bi5wcm9kLmdvb2dsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIgN7S9dQOQzNih92ag
|
||||||
|
7c5uQxuz+M6wnxWj/uwGQIIghRUCIQD2UDH6kkRSYQuyP0oN7XYO3XFjmZ2Yer6m
|
||||||
|
1ZS8fyWYYA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDjTCCAzKgAwIBAgIUAOmArBu9gihLTlqP3W7Et0UoocEwCgYIKoZIzj0EAwIw
|
||||||
|
gaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
|
||||||
|
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYDVQQLEwpQcm9k
|
||||||
|
dWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAy
|
||||||
|
MS0wMi0wMlQwNzozMjo1Ny0wODowMCkwIBcNMjEwMjAyMTUzMjU3WhgPOTk5OTEy
|
||||||
|
MzEyMzU5NTlaMIGlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW
|
||||||
|
MBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIExMQzEmMBEG
|
||||||
|
A1UECxMKUHJvZHVjdGlvbjARBgNVBAsTCmNhbXB1cy1zbG4xLDAqBgNVBAMTI25v
|
||||||
|
ZGUgQ0EgKDIwMjEtMDItMDJUMDc6MzI6NTctMDg6MDApMFkwEwYHKoZIzj0CAQYI
|
||||||
|
KoZIzj0DAQcDQgAEfrgVEVQfSEFeCF1/FGeW7oq0yxecenT1BESfj4Z0zJ8p7P9W
|
||||||
|
bj1o6Rn6dUNlEhGrx7E3/4NFJ0cL1BSNGHkjiqOCATowggE2MA4GA1UdDwEB/wQE
|
||||||
|
AwIBBjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUw
|
||||||
|
AwEB/zAdBgNVHQ4EFgQUpZ30UJXB4lI9j2SzodCtRFckrRcwHwYDVR0jBBgwFoAU
|
||||||
|
36DFMC1JOVvlCq+tunitm2xo3OowMwYDVR0RBCwwKoYoc3BpZmZlOi8vbm9kZS5j
|
||||||
|
YW1wdXMtc2xuLnByb2QuZ29vZ2xlLmNvbTA7BgNVHR4BAf8EMTAvoC0wK4YpY3Nj
|
||||||
|
cy10ZWFtLm5vZGUuY2FtcHVzLXNsbi5wcm9kLmdvb2dsZS5jb20wQgYDVR0fBDsw
|
||||||
|
OTA3oDWgM4YxaHR0cDovL3N0YXRpYy5jb3JwLmdvb2dsZS5jb20vY3JsL2NhbXB1
|
||||||
|
cy1zbG4vbm9kZTAKBggqhkjOPQQDAgNJADBGAiEAnuONgMqmbBlj4ibw5BgDtZUM
|
||||||
|
pboACSFJtEOJu4Yqjt0CIQDI5193J4wUcAY0BK0vO9rRfbNOIc+4ke9ieBDPSuhm
|
||||||
|
mA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICzzCCAnagAwIBAgIUAMUnv6Rpsr8ze5sZuemSU2FF2LowCgYIKoZIzj0EAwIw
|
||||||
|
gaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
|
||||||
|
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYDVQQLEwpQcm9k
|
||||||
|
dWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjbm9kZSBDQSAoMjAy
|
||||||
|
MS0wMi0wMlQwNzozMjo1Ny0wODowMCkwIBcNMjEwMjAyMTUzMjU3WhgPOTk5OTEy
|
||||||
|
MzEyMzU5NTlaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASCmYiIHUux5WFz
|
||||||
|
S0ksJzAPL7YTEh5o5MdXgLPB/WM6x9sVsQDSYU0PF5qc9vPNhkQzGBW79dkBnxhW
|
||||||
|
AGJkFr1Po4IBJDCCASAwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUF
|
||||||
|
BwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUCR1CGEdlks0qcxCExO0rP1B/Z7UwHwYD
|
||||||
|
VR0jBBgwFoAUpZ30UJXB4lI9j2SzodCtRFckrRcwawYDVR0RAQH/BGEwX4IWanph
|
||||||
|
YjEyLnByb2QuZ29vZ2xlLmNvbYZFc3BpZmZlOi8vY3Njcy10ZWFtLm5vZGUuY2Ft
|
||||||
|
cHVzLXNsbi5wcm9kLmdvb2dsZS5jb20vcm9sZS9ib3JnLWFkbWluLWNvMEIGA1Ud
|
||||||
|
HwQ7MDkwN6A1oDOGMWh0dHA6Ly9zdGF0aWMuY29ycC5nb29nbGUuY29tL2NybC9j
|
||||||
|
YW1wdXMtc2xuL25vZGUwCgYIKoZIzj0EAwIDRwAwRAIgK9vQYNoL8HlEwWv89ioG
|
||||||
|
aQ1+8swq6Bo/5mJBrdVLvY8CIGxo6M9vJkPdObmetWNC+lmKuZDoqJWI0AAmBT2J
|
||||||
|
mR2r
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDBDCCAqqgAwIBAgIUALy864QhnkTdceLH52k2XVOe8IQwCgYIKoZIzj0EAwIw
|
||||||
|
gaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
|
||||||
|
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYDVQQLEwpQcm9k
|
||||||
|
dWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAy
|
||||||
|
MS0wMi0wMlQwNzozMDozNi0wODowMCkwIBcNMjEwMjAyMTUzMDM2WhgPOTk5OTEy
|
||||||
|
MzEyMzU5NTlaMIGlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW
|
||||||
|
MBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIExMQzEmMBEG
|
||||||
|
A1UECxMKUHJvZHVjdGlvbjARBgNVBAsTCmNhbXB1cy1zbG4xLDAqBgNVBAMTI1Jv
|
||||||
|
b3QgQ0EgKDIwMjEtMDItMDJUMDc6MzA6MzYtMDg6MDApMFkwEwYHKoZIzj0CAQYI
|
||||||
|
KoZIzj0DAQcDQgAEYv/JS5hQ5kIgdKqYZWTKCO/6gloHAmIb1G8lmY0oXLXYNHQ4
|
||||||
|
qHN7/pPtlcHQp0WK/hM8IGvgOUDoynA8mj0H9KOBszCBsDAOBgNVHQ8BAf8EBAMC
|
||||||
|
AQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMB
|
||||||
|
Af8wHQYDVR0OBBYEFPQNtnCIBcG4ReQgoVi0kPgTROseMB8GA1UdIwQYMBaAFPQN
|
||||||
|
tnCIBcG4ReQgoVi0kPgTROseMC4GA1UdEQQnMCWGI3NwaWZmZTovL2NhbXB1cy1z
|
||||||
|
bG4ucHJvZC5nb29nbGUuY29tMAoGCCqGSM49BAMCA0gAMEUCIQDwBn20DB4X/7Uk
|
||||||
|
Q5BR8JxQYUPxOfvuedjfeA8bPvQ2FwIgOEWa0cXJs1JxarILJeCXtdXvBgu6LEGQ
|
||||||
|
3Pk/bgz8Gek=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDizCCAzKgAwIBAgIUAM/6RKQ7Vke0i4xp5LaAqV73cmIwCgYIKoZIzj0EAwIw
|
||||||
|
gaUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N
|
||||||
|
b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMSYwEQYDVQQLEwpQcm9k
|
||||||
|
dWN0aW9uMBEGA1UECxMKY2FtcHVzLXNsbjEsMCoGA1UEAxMjUm9vdCBDQSAoMjAy
|
||||||
|
MS0wMi0wMlQwNzozMDozNi0wODowMCkwIBcNMjEwMjAyMTUzMDM2WhgPOTk5OTEy
|
||||||
|
MzEyMzU5NTlaMIGlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW
|
||||||
|
MBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UEChMKR29vZ2xlIExMQzEmMBEG
|
||||||
|
A1UECxMKUHJvZHVjdGlvbjARBgNVBAsTCmNhbXB1cy1zbG4xLDAqBgNVBAMTI25v
|
||||||
|
ZGUgQ0EgKDIwMjEtMDItMDJUMDc6MzA6MzYtMDg6MDApMFkwEwYHKoZIzj0CAQYI
|
||||||
|
KoZIzj0DAQcDQgAEllnhxmMYiUPUgRGmenbnm10gXpM94zHx3D1/HumPs6arjYuT
|
||||||
|
Zlhx81XL+g4bu4HII2qcGdP+Hqj/MMFNDI9z4aOCATowggE2MA4GA1UdDwEB/wQE
|
||||||
|
AwIBBjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUw
|
||||||
|
AwEB/zAdBgNVHQ4EFgQUGOhXkmeT+CnWTt+ZbCS9OT9wX8gwHwYDVR0jBBgwFoAU
|
||||||
|
9A22cIgFwbhF5CChWLSQ+BNE6x4wMwYDVR0RBCwwKoYoc3BpZmZlOi8vbm9kZS5j
|
||||||
|
YW1wdXMtc2xuLnByb2QuZ29vZ2xlLmNvbTA7BgNVHR4BAf8EMTAvoC0wK4YpY3Nj
|
||||||
|
cy10ZWFtLm5vZGUuY2FtcHVzLXNsbi5wcm9kLmdvb2dsZS5jb20wQgYDVR0fBDsw
|
||||||
|
OTA3oDWgM4YxaHR0cDovL3N0YXRpYy5jb3JwLmdvb2dsZS5jb20vY3JsL2NhbXB1
|
||||||
|
cy1zbG4vbm9kZTAKBggqhkjOPQQDAgNHADBEAiA86egqPw0qyapAeMGbHxrmYZYa
|
||||||
|
i5ARQsSKRmQixgYizQIgW+2iRWN6Kbqt4WcwpmGv/xDckdRXakF5Ign/WUDO5u4=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICzzCCAnWgAwIBAgITYjjKfYZUKQNUjNyF+hLDGpHJKTAKBggqhkjOPQQDAjCB
|
||||||
|
pTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1v
|
||||||
|
dW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxJjARBgNVBAsTClByb2R1
|
||||||
|
Y3Rpb24wEQYDVQQLEwpjYW1wdXMtc2xuMSwwKgYDVQQDEyNub2RlIENBICgyMDIx
|
||||||
|
LTAyLTAyVDA3OjMwOjM2LTA4OjAwKTAgFw0yMTAyMDIxNTMwMzZaGA85OTk5MTIz
|
||||||
|
MTIzNTk1OVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD4r4+nCgZExYF8v
|
||||||
|
CLvGn0lY/cmam8mAkJDXRN2Ja2t+JwaTOptPmbbXft+1NTk5gCg5wB+FJCnaV3I/
|
||||||
|
HaxEhBWjggEkMIIBIDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH
|
||||||
|
AwIGCCsGAQUFBwMBMB0GA1UdDgQWBBTTCjXX1Txjc00tBg/5cFzpeCSKuDAfBgNV
|
||||||
|
HSMEGDAWgBQY6FeSZ5P4KdZO35lsJL05P3BfyDBrBgNVHREBAf8EYTBfghZqemFi
|
||||||
|
MTIucHJvZC5nb29nbGUuY29thkVzcGlmZmU6Ly9jc2NzLXRlYW0ubm9kZS5jYW1w
|
||||||
|
dXMtc2xuLnByb2QuZ29vZ2xlLmNvbS9yb2xlL2JvcmctYWRtaW4tY28wQgYDVR0f
|
||||||
|
BDswOTA3oDWgM4YxaHR0cDovL3N0YXRpYy5jb3JwLmdvb2dsZS5jb20vY3JsL2Nh
|
||||||
|
bXB1cy1zbG4vbm9kZTAKBggqhkjOPQQDAgNIADBFAiBq3URViNyMLpvzZHC1Y+4L
|
||||||
|
+35guyIJfjHu08P3S8/xswIhAJtWSQ1ZtozdOzGxg7GfUo4hR+5SP6rBTgIqXEfq
|
||||||
|
48fW
|
||||||
|
-----END CERTIFICATE-----
|
||||||
Loading…
Reference in New Issue