crl provider: Static and FileWatcher provider implementations (#6670)

* rename certificateListExt to CRL

* CRLProvider file

* Add CRLProvider to RevocationConfig

* Beginning refactor of CRL handling

* Shell of StaticCRLProvider

* basic static crl provider test

* use loadCRL helper

* refactor of CRL loading

* Table tests

* Table tests

* Add tests with Static CRL provider

* New certs to be used for CRL tests. Added test for passing and failing connections based on CRL check outcomes

* Main functionality of File Watcher (Directory) CRL provider

* Refactor async go routine, validate() func, add unit tests

* Custom error callback, related unit tests

* Error callback test improvement

* Comments for StaticCRLProvider

* Comments for public API

* go mod tidy

* Comments for tests

* Fix vet errors

* Change Static provider behavior to match C Core, address other PR comments

* Data race fix

* Test helper fn change

* Address PR comments

* Address PR comments (part 2)

* Migration from context to channel for controlling crl reloading goroutine

* Align in-memory CRL updates during directory scan to C++ behavior

* Improve comments for ScanCRLDirectory

* Base test case for Scan CRL Directory file manipulations

* full set of cases for CRL directory content manipulation

* Add comment for table test structure

* Fix for go.mod and go.sum

* Empty directoru workaround

* Delete deprecated crl functionality

* Restoring deprecated crl files

* Fit to grpctest.Tester pattern

* Update readme for crl provider tests

* Address PR comments

* Revert "Restoring deprecated crl files"

This reverts commit 56437603a4.

* Revert "Resolve conflicts with upstream - deletion of deprecated crl"

This reverts commit e0130640c46efd9a43649bf409c6e762ae66e225, reversing
changes made to 21f430135c.

Revert deletion

* Update link for gRFC proposal

* Address PR comments

* Address PR comments part 1

* Address PR comments part 2

* Address PR comments part 3

* Fix for go.mod and go.sum

* Fix comment typo

* Fix for gRFC tag

* Add more details to CRL api  godoc comments.

* Address PR comments

* Address PR comments

* Delete crl_deprecated.go and crl_deprecated_test.go

* Delete testdate/crl/provider/filewatcher directory and .gitignore under it

* Race test fix

* Address PR comments

* Address PR comments

* Refactor directory reloader test from checking size of crl map to querying individual entries approach

* Add extra case for RefreshDuration config test

* Update cpmment for table test structure

* Unexport scan scanCRLDirectory, drop related mutex, update the comments

* Update API comments, clear tmp dir after the tests

---------

Co-authored-by: Gregory Cooke <gregorycooke@google.com>
This commit is contained in:
erm-g 2023-10-31 00:41:22 +00:00 committed by GitHub
parent d7ea67b9f3
commit b82468a346
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1336 additions and 59 deletions

View File

@ -25,6 +25,7 @@ import (
"errors"
"fmt"
"net"
"os"
"testing"
lru "github.com/hashicorp/golang-lru"
@ -371,6 +372,27 @@ func (s) TestClientServerHandshake(t *testing.T) {
getRootCAsForServerBad := func(params *GetRootCAsParams) (*GetRootCAsResults, error) {
return nil, fmt.Errorf("bad root certificate reloading")
}
getRootCAsForClientCRL := func(params *GetRootCAsParams) (*GetRootCAsResults, error) {
return &GetRootCAsResults{TrustCerts: cs.ClientTrust3}, nil
}
getRootCAsForServerCRL := func(params *GetRootCAsParams) (*GetRootCAsResults, error) {
return &GetRootCAsResults{TrustCerts: cs.ServerTrust3}, nil
}
makeStaticCRLProvider := func(crlPath string) *RevocationConfig {
rawCRL, err := os.ReadFile(crlPath)
if err != nil {
t.Fatalf("readFile(%v) failed err = %v", crlPath, err)
}
cRLProvider := NewStaticCRLProvider([][]byte{rawCRL})
return &RevocationConfig{
AllowUndetermined: true,
CRLProvider: cRLProvider,
}
}
cache, err := lru.New(5)
if err != nil {
t.Fatalf("lru.New: err = %v", err)
@ -682,7 +704,7 @@ func (s) TestClientServerHandshake(t *testing.T) {
},
// Client: set valid credentials with the revocation config
// Server: set valid credentials with the revocation config
// Expected Behavior: success, because non of the certificate chains sent in the connection are revoked
// Expected Behavior: success, because none of the certificate chains sent in the connection are revoked
{
desc: "Client sets peer cert, reload root function with verifyFuncGood; Server sets peer cert, reload root function; mutualTLS",
clientCert: []tls.Certificate{cs.ClientCert1},
@ -704,6 +726,37 @@ func (s) TestClientServerHandshake(t *testing.T) {
Cache: cache,
},
},
// Client: set valid credentials with the revocation config
// Server: set valid credentials with the revocation config
// Expected Behavior: success, because none of the certificate chains sent in the connection are revoked
{
desc: "Client sets peer cert, reload root function with verifyFuncGood; Server sets peer cert, reload root function; Client uses CRL; mutualTLS",
clientCert: []tls.Certificate{cs.ClientCert3},
clientGetRoot: getRootCAsForClientCRL,
clientVerifyFunc: clientVerifyFuncGood,
clientVType: CertVerification,
clientRevocationConfig: makeStaticCRLProvider(testdata.Path("crl/provider_crl_empty.pem")),
serverMutualTLS: true,
serverCert: []tls.Certificate{cs.ServerCert3},
serverGetRoot: getRootCAsForServerCRL,
serverVType: CertVerification,
},
// Client: set valid credentials with the revocation config
// Server: set revoked credentials with the revocation config
// Expected Behavior: fail, server creds are revoked
{
desc: "Client sets peer cert, reload root function with verifyFuncGood; Server sets revoked cert; Client uses CRL; mutualTLS",
clientCert: []tls.Certificate{cs.ClientCert3},
clientGetRoot: getRootCAsForClientCRL,
clientVerifyFunc: clientVerifyFuncGood,
clientVType: CertVerification,
clientRevocationConfig: makeStaticCRLProvider(testdata.Path("crl/provider_crl_server_revoked.pem")),
serverMutualTLS: true,
serverCert: []tls.Certificate{cs.ServerCert3},
serverGetRoot: getRootCAsForServerCRL,
serverVType: CertVerification,
serverExpectError: true,
},
} {
test := test
t.Run(test.desc, func(t *testing.T) {

View File

@ -65,6 +65,11 @@ type RevocationConfig struct {
AllowUndetermined bool
// Cache will store CRL files if not nil, otherwise files are reloaded for every lookup.
Cache Cache
// CRLProvider is an alternative to using RootDir directly for the
// X509_LOOKUP_hash_dir approach to CRL files. If set, the CRLProvider's CRL
// function will be called when looking up and fetching CRLs during the
// handshake.
CRLProvider CRLProvider
}
// RevocationStatus is the revocation status for a certificate or chain.
@ -83,13 +88,46 @@ 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 *x509.RevocationList
// CRL contains a pkix.CertificateList and parsed extensions that aren't
// provided by the golang CRL parser.
// All CRLs should be loaded using NewCRL() for bytes directly or ReadCRLFile()
// to read directly from a filepath
type CRL struct {
certList *x509.RevocationList
// RFC5280, 5.2.1, all conforming CRLs must have a AKID with the ID method.
AuthorityKeyID []byte
RawIssuer []byte
authorityKeyID []byte
rawIssuer []byte
}
// NewCRL constructs new CRL from the provided byte array.
func NewCRL(b []byte) (*CRL, error) {
crl, err := parseRevocationList(b)
if err != nil {
return nil, fmt.Errorf("fail to parse CRL: %v", err)
}
crlExt, err := parseCRLExtensions(crl)
if err != nil {
return nil, fmt.Errorf("fail to parse CRL extensions: %v", err)
}
crlExt.rawIssuer, err = extractCRLIssuer(b)
if err != nil {
return nil, fmt.Errorf("fail to extract CRL issuer failed err= %v", err)
}
return crlExt, nil
}
// ReadCRLFile reads a file from the provided path, and returns constructed CRL
// struct from it.
func ReadCRLFile(path string) (*CRL, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot read file from provided path %q: %v", path, err)
}
crl, err := NewCRL(b)
if err != nil {
return nil, fmt.Errorf("cannot construct CRL from file %q: %v", path, err)
}
return crl, nil
}
const tagDirectoryName = 4
@ -215,31 +253,31 @@ func checkChain(chain []*x509.Certificate, cfg RevocationConfig) RevocationStatu
return chainStatus
}
func cachedCrl(rawIssuer []byte, cache Cache) (*certificateListExt, bool) {
func cachedCrl(rawIssuer []byte, cache Cache) (*CRL, bool) {
val, ok := cache.Get(hex.EncodeToString(rawIssuer))
if !ok {
return nil, false
}
crl, ok := val.(*certificateListExt)
crl, ok := val.(*CRL)
if !ok {
return nil, false
}
// If the CRL is expired, force a reload.
if hasExpired(crl.CertList, time.Now()) {
if hasExpired(crl.certList, 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(rawIssuer []byte, crlVerifyCrt []*x509.Certificate, cfg RevocationConfig) (*certificateListExt, error) {
func fetchIssuerCRL(rawIssuer []byte, crlVerifyCrt []*x509.Certificate, cfg RevocationConfig) (*CRL, error) {
if cfg.Cache != nil {
if crl, ok := cachedCrl(rawIssuer, cfg.Cache); ok {
return crl, nil
}
}
crl, err := fetchCRL(rawIssuer, cfg)
crl, err := fetchCRLOpenSSLHashDir(rawIssuer, cfg)
if err != nil {
return nil, fmt.Errorf("fetchCRL() failed: %v", err)
}
@ -253,21 +291,39 @@ func fetchIssuerCRL(rawIssuer []byte, crlVerifyCrt []*x509.Certificate, cfg Revo
return crl, nil
}
func fetchCRL(c *x509.Certificate, crlVerifyCrt []*x509.Certificate, cfg RevocationConfig) (*CRL, error) {
if cfg.CRLProvider != nil {
crl, err := cfg.CRLProvider.CRL(c)
if err != nil {
return nil, fmt.Errorf("CrlProvider failed err = %v", err)
}
if crl == nil {
return nil, fmt.Errorf("no CRL found for certificate's issuer")
}
return crl, nil
}
return fetchIssuerCRL(c.RawIssuer, crlVerifyCrt, cfg)
}
// checkCert checks a single certificate against the CRL defined in the certificate.
// It will fetch and verify the CRL(s) defined in the root directory specified by cfg.
// 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 {
crl, err := fetchIssuerCRL(c.RawIssuer, crlVerifyCrt, cfg)
crl, err := fetchCRL(c, crlVerifyCrt, cfg)
if err != nil {
// We couldn't load any CRL files for the certificate, so we don't know if it's RevocationUnrevoked or not.
grpclogLogger.Warningf("getIssuerCRL(%v) err = %v", c.Issuer, err)
// We couldn't load any CRL files for the certificate, so we don't know
// if it's RevocationUnrevoked or not. This is not necessarily a
// problem - it's not invalid to have no CRLs if you don't have any
// revocations for an issuer. We just return RevocationUndetermined and
// there is a setting for the user to control the handling of that.
grpclogLogger.Warningf("fetchCRL() err = %v", err)
return RevocationUndetermined
}
revocation, err := checkCertRevocation(c, crl)
if err != nil {
grpclogLogger.Warningf("checkCertRevocation(CRL %v) failed: %v", crl.CertList.Issuer, err)
grpclogLogger.Warningf("checkCertRevocation(CRL %v) failed: %v", crl.certList.Issuer, err)
// We couldn't check the CRL file for some reason, so we don't know if it's RevocationUnrevoked or not.
return RevocationUndetermined
}
@ -277,13 +333,13 @@ func checkCert(c *x509.Certificate, crlVerifyCrt []*x509.Certificate, cfg Revoca
return revocation
}
func checkCertRevocation(c *x509.Certificate, crl *certificateListExt) (RevocationStatus, error) {
func checkCertRevocation(c *x509.Certificate, crl *CRL) (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 := crl.RawIssuer
rawEntryIssuer := crl.rawIssuer
// Loop through all the revoked certificates.
for _, revCert := range crl.CertList.RevokedCertificates {
for _, revCert := range crl.certList.RevokedCertificates {
// 5.3 Loop through CRL entry extensions for needed information.
for _, ext := range revCert.Extensions {
if oidCertificateIssuer.Equal(ext.Id) {
@ -375,11 +431,11 @@ type issuingDistributionPoint struct {
// parseCRLExtensions parses the extensions for a CRL
// and checks that they're supported by the parser.
func parseCRLExtensions(c *x509.RevocationList) (*certificateListExt, error) {
func parseCRLExtensions(c *x509.RevocationList) (*CRL, error) {
if c == nil {
return nil, errors.New("c is nil, expected any value")
}
certList := &certificateListExt{CertList: c}
certList := &CRL{certList: c}
for _, ext := range c.Extensions {
switch {
@ -393,7 +449,7 @@ func parseCRLExtensions(c *x509.RevocationList) (*certificateListExt, error) {
} else if len(rest) != 0 {
return nil, errors.New("trailing data after AKID extension")
}
certList.AuthorityKeyID = a.ID
certList.authorityKeyID = a.ID
case oidIssuingDistributionPoint.Equal(ext.Id):
var dp issuingDistributionPoint
@ -418,14 +474,14 @@ func parseCRLExtensions(c *x509.RevocationList) (*certificateListExt, error) {
}
}
if len(certList.AuthorityKeyID) == 0 {
if len(certList.authorityKeyID) == 0 {
return nil, errors.New("authority key identifier extension missing")
}
return certList, nil
}
func fetchCRL(rawIssuer []byte, cfg RevocationConfig) (*certificateListExt, error) {
var parsedCRL *certificateListExt
func fetchCRLOpenSSLHashDir(rawIssuer []byte, cfg RevocationConfig) (*CRL, error) {
var parsedCRL *CRL
// 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.
@ -449,7 +505,7 @@ func fetchCRL(rawIssuer []byte, cfg RevocationConfig) (*certificateListExt, erro
// Parsing errors for a CRL shouldn't happen so fail.
return nil, fmt.Errorf("parseRevocationList(%v) failed: %v", crlPath, err)
}
var certList *certificateListExt
var certList *CRL
if certList, err = parseCRLExtensions(crl); err != nil {
grpclogLogger.Infof("fetchCRL: unsupported crl %v: %v", crlPath, err)
// Continue to find a supported CRL
@ -460,7 +516,7 @@ func fetchCRL(rawIssuer []byte, cfg RevocationConfig) (*certificateListExt, erro
if err != nil {
return nil, err
}
certList.RawIssuer = rawCRLIssuer
certList.rawIssuer = rawCRLIssuer
// RFC5280, 6.3.3 (b) Verify the issuer and scope of the complete CRL.
if bytes.Equal(rawIssuer, rawCRLIssuer) {
parsedCRL = certList
@ -475,7 +531,7 @@ func fetchCRL(rawIssuer []byte, cfg RevocationConfig) (*certificateListExt, erro
return parsedCRL, nil
}
func verifyCRL(crl *certificateListExt, rawIssuer []byte, chain []*x509.Certificate) error {
func verifyCRL(crl *CRL, 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.
@ -487,12 +543,12 @@ func verifyCRL(crl *certificateListExt, rawIssuer []byte, chain []*x509.Certific
// "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, crl.RawIssuer) {
if bytes.Equal(c.SubjectKeyId, crl.authorityKeyID) && bytes.Equal(c.RawSubject, crl.rawIssuer) {
// RFC5280, 6.3.3 (g) Validate signature.
return crl.CertList.CheckSignatureFrom(c)
return crl.certList.CheckSignatureFrom(c)
}
}
return fmt.Errorf("verifyCRL: No certificates mached CRL issuer (%v)", crl.CertList.Issuer)
return fmt.Errorf("verifyCRL: No certificates mached CRL issuer (%v)", crl.certList.Issuer)
}
// pemType is the type of a PEM encoded CRL.

View File

@ -0,0 +1,238 @@
/*
*
* Copyright 2023 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/x509"
"fmt"
"os"
"sync"
"time"
)
const defaultCRLRefreshDuration = 1 * time.Hour
const minCRLRefreshDuration = 1 * time.Minute
// CRLProvider is the interface to be implemented to enable custom CRL provider
// behavior, as defined in [gRFC A69].
//
// The interface defines how gRPC gets CRLs from the provider during handshakes,
// but doesn't prescribe a specific way to load and store CRLs. Such
// implementations can be used in RevocationConfig of advancedtls.ClientOptions
// and/or advancedtls.ServerOptions.
// Please note that checking CRLs is directly on the path of connection
// establishment, so implementations of the CRL function need to be fast, and
// slow things such as file IO should be done asynchronously.
//
// [gRFC A69]: https://github.com/grpc/proposal/pull/382
type CRLProvider interface {
// CRL accepts x509 Cert and returns a related CRL struct, which can contain
// either an empty or non-empty list of revoked certificates. If an error is
// thrown or (nil, nil) is returned, it indicates that we can't load any
// authoritative CRL files (which may not necessarily be a problem). It's not
// considered invalid to have no CRLs if there are no revocations for an
// issuer. In such cases, the status of the check CRL operation is marked as
// RevocationUndetermined, as defined in [RFC5280 - Undetermined].
//
// [RFC5280 - Undetermined]: https://datatracker.ietf.org/doc/html/rfc5280#section-6.3.3
CRL(cert *x509.Certificate) (*CRL, error)
}
// StaticCRLProvider implements CRLProvider interface by accepting raw content
// of CRL files at creation time and storing parsed CRL structs in-memory.
type StaticCRLProvider struct {
crls map[string]*CRL
}
// NewStaticCRLProvider processes raw content of CRL files, adds parsed CRL
// structs into in-memory, and returns a new instance of the StaticCRLProvider.
func NewStaticCRLProvider(rawCRLs [][]byte) *StaticCRLProvider {
p := StaticCRLProvider{}
p.crls = make(map[string]*CRL)
for idx, rawCRL := range rawCRLs {
cRL, err := NewCRL(rawCRL)
if err != nil {
grpclogLogger.Warningf("Can't parse raw CRL number %v from the slice: %v", idx, err)
continue
}
p.addCRL(cRL)
}
return &p
}
// AddCRL adds/updates provided CRL to in-memory storage.
func (p *StaticCRLProvider) addCRL(crl *CRL) {
key := crl.certList.Issuer.ToRDNSequence().String()
p.crls[key] = crl
}
// CRL returns CRL struct if it was passed to NewStaticCRLProvider.
func (p *StaticCRLProvider) CRL(cert *x509.Certificate) (*CRL, error) {
return p.crls[cert.Issuer.ToRDNSequence().String()], nil
}
// FileWatcherOptions represents a data structure holding a configuration for
// FileWatcherCRLProvider.
type FileWatcherOptions struct {
CRLDirectory string // Path of the directory containing CRL files
RefreshDuration time.Duration // Time interval between CRLDirectory scans, can't be smaller than 1 minute
CRLReloadingFailedCallback func(err error) // Custom callback executed when a CRL file cant be processed
}
// FileWatcherCRLProvider implements the CRLProvider interface by periodically
// scanning CRLDirectory (see FileWatcherOptions) and storing CRL structs
// in-memory. Users should call Close to stop the background refresh of
// CRLDirectory.
type FileWatcherCRLProvider struct {
crls map[string]*CRL
opts FileWatcherOptions
mu sync.Mutex
stop chan struct{}
done chan struct{}
}
// NewFileWatcherCRLProvider returns a new instance of the
// FileWatcherCRLProvider. It uses FileWatcherOptions to validate and apply
// configuration required for creating a new instance. Users should call Close
// to stop the background refresh of CRLDirectory.
func NewFileWatcherCRLProvider(o FileWatcherOptions) (*FileWatcherCRLProvider, error) {
if err := o.validate(); err != nil {
return nil, err
}
provider := &FileWatcherCRLProvider{
crls: make(map[string]*CRL),
opts: o,
stop: make(chan struct{}),
done: make(chan struct{}),
}
go provider.run()
return provider, nil
}
func (o *FileWatcherOptions) validate() error {
// Checks relates to CRLDirectory.
if o.CRLDirectory == "" {
return fmt.Errorf("advancedtls: CRLDirectory needs to be specified")
}
if _, err := os.ReadDir(o.CRLDirectory); err != nil {
return fmt.Errorf("advancedtls: CRLDirectory %v is not readable: %v", o.CRLDirectory, err)
}
// Checks related to RefreshDuration.
if o.RefreshDuration == 0 {
o.RefreshDuration = defaultCRLRefreshDuration
}
if o.RefreshDuration < minCRLRefreshDuration {
grpclogLogger.Warningf("RefreshDuration must be at least 1 minute: provided value %v, minimum value %v will be used.", o.RefreshDuration, minCRLRefreshDuration)
o.RefreshDuration = minCRLRefreshDuration
}
return nil
}
// Start starts watching the directory for CRL files and updates the provider accordingly.
func (p *FileWatcherCRLProvider) run() {
defer close(p.done)
ticker := time.NewTicker(p.opts.RefreshDuration)
defer ticker.Stop()
p.scanCRLDirectory()
for {
select {
case <-p.stop:
grpclogLogger.Infof("Scanning of CRLDirectory %v stopped", p.opts.CRLDirectory)
return
case <-ticker.C:
p.scanCRLDirectory()
}
}
}
// Close waits till the background refresh of CRLDirectory of
// FileWatcherCRLProvider is done and then stops it.
func (p *FileWatcherCRLProvider) Close() {
close(p.stop)
<-p.done
}
// scanCRLDirectory starts the process of scanning
// FileWatcherOptions.CRLDirectory and updating in-memory storage of CRL
// structs, as defined in [gRFC A69]. It's called periodically
// (see FileWatcherOptions.RefreshDuration) by run goroutine.
//
// [gRFC A69]: https://github.com/grpc/proposal/pull/382
func (p *FileWatcherCRLProvider) scanCRLDirectory() {
dir, err := os.Open(p.opts.CRLDirectory)
if err != nil {
grpclogLogger.Errorf("Can't open CRLDirectory %v", p.opts.CRLDirectory, err)
if p.opts.CRLReloadingFailedCallback != nil {
p.opts.CRLReloadingFailedCallback(err)
}
}
defer dir.Close()
files, err := dir.ReadDir(0)
if err != nil {
grpclogLogger.Errorf("Can't access files under CRLDirectory %v", p.opts.CRLDirectory, err)
if p.opts.CRLReloadingFailedCallback != nil {
p.opts.CRLReloadingFailedCallback(err)
}
}
tempCRLs := make(map[string]*CRL)
successCounter := 0
failCounter := 0
for _, file := range files {
filePath := fmt.Sprintf("%s/%s", p.opts.CRLDirectory, file.Name())
crl, err := ReadCRLFile(filePath)
if err != nil {
failCounter++
grpclogLogger.Warningf("Can't add CRL from file %v under CRLDirectory %v", filePath, p.opts.CRLDirectory, err)
if p.opts.CRLReloadingFailedCallback != nil {
p.opts.CRLReloadingFailedCallback(err)
}
continue
}
tempCRLs[crl.certList.Issuer.ToRDNSequence().String()] = crl
successCounter++
}
// Only if all the files are processed successfully we can swap maps (there
// might be deletions of entries in this case).
if len(files) == successCounter {
p.mu.Lock()
defer p.mu.Unlock()
p.crls = tempCRLs
grpclogLogger.Infof("Scan of CRLDirectory %v completed, %v files found and processed successfully, in-memory CRL storage flushed and repopulated", p.opts.CRLDirectory, len(files))
} else {
// Since some of the files failed we can only add/update entries in the map.
p.mu.Lock()
defer p.mu.Unlock()
for key, value := range tempCRLs {
p.crls[key] = value
}
grpclogLogger.Infof("Scan of CRLDirectory %v completed, %v files found, %v files processing failed, %v entries of in-memory CRL storage added/updated", p.opts.CRLDirectory, len(files), failCounter, successCounter)
}
}
// CRL retrieves the CRL associated with the given certificate's issuer DN from
// in-memory if it was loaded during FileWatcherOptions.CRLDirectory scan before
// the execution of this function.
func (p *FileWatcherCRLProvider) CRL(cert *x509.Certificate) (*CRL, error) {
p.mu.Lock()
defer p.mu.Unlock()
return p.crls[cert.Issuer.ToRDNSequence().String()], nil
}

View File

@ -0,0 +1,354 @@
/*
*
* Copyright 2023 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/x509"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/security/advancedtls/testdata"
)
// TestStaticCRLProvider tests how StaticCRLProvider handles the major four
// cases for CRL checks. It loads the CRLs under crl directory, constructs
// unrevoked, revoked leaf, and revoked intermediate chains, as well as a chain
// without CRL for issuer, and checks that its correctly processed.
func (s) TestStaticCRLProvider(t *testing.T) {
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)
}
p := NewStaticCRLProvider(rawCRLs)
// Each test data entry contains a description of a certificate chain,
// certificate chain itself, and if CRL is not expected to be found.
tests := []struct {
desc string
certs []*x509.Certificate
expectNoCRL bool
}{
{
desc: "Unrevoked chain",
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
},
{
desc: "Revoked Intermediate chain",
certs: makeChain(t, testdata.Path("crl/revokedInt.pem")),
},
{
desc: "Revoked leaf chain",
certs: makeChain(t, testdata.Path("crl/revokedLeaf.pem")),
},
{
desc: "Chain with no CRL for issuer",
certs: makeChain(t, testdata.Path("client_cert_1.pem")),
expectNoCRL: true,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
for _, c := range tt.certs {
crl, err := p.CRL(c)
if err != nil {
t.Fatalf("Expected error fetch from provider: %v", err)
}
if crl == nil && !tt.expectNoCRL {
t.Fatalf("CRL is unexpectedly nil")
}
}
})
}
}
// TestFileWatcherCRLProviderConfig checks creation of FileWatcherCRLProvider,
// and the validation of FileWatcherOptions configuration. The configurations include empty
// one, non existing CRLDirectory, invalid RefreshDuration, and the correct one.
func (s) TestFileWatcherCRLProviderConfig(t *testing.T) {
if _, err := NewFileWatcherCRLProvider(FileWatcherOptions{}); err == nil {
t.Fatalf("Empty FileWatcherOptions should not be allowed")
}
if _, err := NewFileWatcherCRLProvider(FileWatcherOptions{CRLDirectory: "I_do_not_exist"}); err == nil {
t.Fatalf("CRLDirectory must exist")
}
defaultProvider, err := NewFileWatcherCRLProvider(FileWatcherOptions{CRLDirectory: testdata.Path("crl")})
if err != nil {
t.Fatal("Unexpected error:", err)
}
if defaultProvider.opts.RefreshDuration != defaultCRLRefreshDuration {
t.Fatalf("RefreshDuration for defaultCRLRefreshDuration case is not properly updated by validate() func")
}
defaultProvider.Close()
tooFastRefreshProvider, err := NewFileWatcherCRLProvider(FileWatcherOptions{
CRLDirectory: testdata.Path("crl"),
RefreshDuration: 5 * time.Second,
})
if err != nil {
t.Fatal("Unexpected error:", err)
}
if tooFastRefreshProvider.opts.RefreshDuration != minCRLRefreshDuration {
t.Fatalf("RefreshDuration for minCRLRefreshDuration case is not properly updated by validate() func")
}
tooFastRefreshProvider.Close()
customCallback := func(err error) {
fmt.Printf("Custom error message: %v", err)
}
regularProvider, err := NewFileWatcherCRLProvider(FileWatcherOptions{
CRLDirectory: testdata.Path("crl"),
RefreshDuration: 2 * time.Hour,
CRLReloadingFailedCallback: customCallback,
})
if err != nil {
t.Fatal("Unexpected error while creating regular FileWatcherCRLProvider:", err)
}
if regularProvider.opts.RefreshDuration != 2*time.Hour {
t.Fatalf("Valid refreshDuration was incorrectly updated by validate() func")
}
regularProvider.Close()
}
// TestFileWatcherCRLProvider tests how FileWatcherCRLProvider handles the major
// four cases for CRL checks. It scans the CRLs under crl directory to populate
// the in-memory storage. Then we construct unrevoked, revoked leaf, and revoked
// intermediate chains, as well as a chain without CRL for issuer, and check
// that its correctly processed. Additionally, we also check if number of
// invocations of custom callback is correct.
func (s) TestFileWatcherCRLProvider(t *testing.T) {
const nonCRLFilesUnderCRLDirectory = 15
nonCRLFilesSet := make(map[string]struct{})
customCallback := func(err error) {
nonCRLFilesSet[err.Error()] = struct{}{}
}
p, err := NewFileWatcherCRLProvider(FileWatcherOptions{
CRLDirectory: testdata.Path("crl"),
RefreshDuration: 1 * time.Hour,
CRLReloadingFailedCallback: customCallback,
})
if err != nil {
t.Fatal("Unexpected error while creating FileWatcherCRLProvider:", err)
}
// We need to make sure that initial CRLDirectory scan is completed before
// querying the internal map.
p.Close()
// Each test data entry contains a description of a certificate chain,
// certificate chain itself, and if CRL is not expected to be found.
tests := []struct {
desc string
certs []*x509.Certificate
expectNoCRL bool
}{
{
desc: "Unrevoked chain",
certs: makeChain(t, testdata.Path("crl/unrevoked.pem")),
},
{
desc: "Revoked Intermediate chain",
certs: makeChain(t, testdata.Path("crl/revokedInt.pem")),
},
{
desc: "Revoked leaf chain",
certs: makeChain(t, testdata.Path("crl/revokedLeaf.pem")),
},
{
desc: "Chain with no CRL for issuer",
certs: makeChain(t, testdata.Path("client_cert_1.pem")),
expectNoCRL: true,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
for _, c := range tt.certs {
crl, err := p.CRL(c)
if err != nil {
t.Fatalf("Expected error fetch from provider: %v", err)
}
if crl == nil && !tt.expectNoCRL {
t.Fatalf("CRL is unexpectedly nil")
}
}
})
}
if diff := cmp.Diff(len(nonCRLFilesSet), nonCRLFilesUnderCRLDirectory); diff != "" {
t.Errorf("Unexpected number Number of callback executions\ndiff (-got +want):\n%s", diff)
}
}
// TestFileWatcherCRLProviderDirectoryScan tests how FileWatcherCRLProvider
// handles different contents of FileWatcherOptions.CRLDirectory.
// We update the content with various (correct and incorrect) CRL files and
// check if in-memory storage was properly updated. Please note that the same
// instance of FileWatcherCRLProvider is used for the whole test so test cases
// are not independent from each other.
func (s) TestFileWatcherCRLProviderDirectoryScan(t *testing.T) {
sourcePath := testdata.Path("crl")
targetPath := createTmpDir(t)
defer os.RemoveAll(targetPath)
p, err := NewFileWatcherCRLProvider(FileWatcherOptions{
CRLDirectory: targetPath,
RefreshDuration: 1 * time.Hour,
})
if err != nil {
t.Fatal("Unexpected error while creating FileWatcherCRLProvider:", err)
}
// Each test data entry contains a description of CRL directory content
// (including the expected number of entries in the FileWatcherCRLProvider
// map), the name of the files to be copied there before executing the test
// case, and information regarding whether a specific certificate is expected
// to be found in the map.
tests := []struct {
desc string
crlFileNames []string
certFileNames []struct {
fileName string
expected bool
}
}{
{
desc: "Simple addition (1 map entry)",
crlFileNames: []string{"1.crl"},
certFileNames: []struct {
fileName string
expected bool
}{
{"crl/unrevoked.pem", true},
},
},
{
desc: "Addition and deletion (2 map entries)",
crlFileNames: []string{"3.crl", "5.crl"},
certFileNames: []struct {
fileName string
expected bool
}{
{"crl/revokedInt.pem", true},
{"crl/revokedLeaf.pem", true},
{"crl/unrevoked.pem", false},
},
},
{
desc: "Addition and a corrupt file (3 map entries)",
crlFileNames: []string{"1.crl", "README.md"},
certFileNames: []struct {
fileName string
expected bool
}{
{"crl/revokedInt.pem", true},
{"crl/revokedLeaf.pem", true},
{"crl/unrevoked.pem", true},
}},
{
desc: "Full deletion (0 map entries)",
crlFileNames: []string{},
certFileNames: []struct {
fileName string
expected bool
}{
{"crl/revokedInt.pem", false},
{"crl/revokedLeaf.pem", false},
{"crl/unrevoked.pem", false},
}},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
copyFiles(sourcePath, targetPath, tt.crlFileNames, t)
p.scanCRLDirectory()
for _, certFileName := range tt.certFileNames {
c := makeChain(t, testdata.Path(certFileName.fileName))[0]
crl, err := p.CRL(c)
if err != nil {
t.Errorf("Cannot fetch CRL from provider: %v", err)
}
if crl == nil && certFileName.expected {
t.Errorf("CRL is unexpectedly nil")
}
if crl != nil && !certFileName.expected {
t.Errorf("CRL is unexpectedly not nil")
}
}
})
}
p.Close()
}
func copyFiles(sourcePath string, targetPath string, fileNames []string, t *testing.T) {
t.Helper()
targetDir, err := os.Open(targetPath)
if err != nil {
t.Fatalf("Can't open dir %v: %v", targetPath, err)
}
defer targetDir.Close()
names, err := targetDir.Readdirnames(-1)
if err != nil {
t.Fatalf("Can't read dir %v: %v", targetPath, err)
}
for _, name := range names {
err = os.RemoveAll(filepath.Join(testdata.Path(targetPath), name))
if err != nil {
t.Fatalf("Can't remove file %v: %v", name, err)
}
}
for _, fileName := range fileNames {
destinationPath := filepath.Join(targetPath, fileName)
sourceFile, err := os.Open(filepath.Join(sourcePath, fileName))
if err != nil {
t.Fatalf("Can't open file %v: %v", fileName, err)
}
defer sourceFile.Close()
destinationFile, err := os.Create(destinationPath)
if err != nil {
t.Fatalf("Can't create file %v: %v", destinationFile, err)
}
defer destinationFile.Close()
_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
t.Fatalf("Can't copy file %v to %v: %v", sourceFile, destinationFile, err)
}
}
}
func createTmpDir(t *testing.T) string {
t.Helper()
// Create a temp directory. Passing an empty string for the first argument
// uses the system temp directory.
dir, err := os.MkdirTemp("", "filewatcher*")
if err != nil {
t.Fatalf("os.MkdirTemp() failed: %v", err)
}
t.Logf("Using tmpdir: %s", dir)
return dir
}

View File

@ -223,7 +223,7 @@ qsSIp8gfxSyzkJP+Ngkm2DdLjlJQCZ9R0MZP9Xj4
if err != nil {
t.Fatalf("parseRevocationList(dummyCrlFile) failed: %v", err)
}
crlExt := &certificateListExt{CertList: crl}
crlExt := &CRL{certList: crl}
var crlIssuer pkix.Name = crl.Issuer
var revocationTests = []struct {
@ -338,24 +338,12 @@ func makeChain(t *testing.T, name string) []*x509.Certificate {
return certChain
}
func loadCRL(t *testing.T, path string) *certificateListExt {
b, err := os.ReadFile(path)
func loadCRL(t *testing.T, path string) *CRL {
crl, err := ReadCRLFile(path)
if err != nil {
t.Fatalf("readFile(%v) failed err = %v", path, err)
t.Fatalf("ReadCRLFile(%v) failed err = %v", path, err)
}
crl, err := parseRevocationList(b)
if err != nil {
t.Fatalf("parseCrl(%v) failed err = %v", path, err)
}
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
return crl
}
func TestCachedCRL(t *testing.T) {
@ -371,16 +359,16 @@ func TestCachedCRL(t *testing.T) {
}{
{
desc: "Valid",
val: &certificateListExt{
CertList: &x509.RevocationList{
val: &CRL{
certList: &x509.RevocationList{
NextUpdate: time.Now().Add(time.Hour),
}},
ok: true,
},
{
desc: "Expired",
val: &certificateListExt{
CertList: &x509.RevocationList{
val: &CRL{
certList: &x509.RevocationList{
NextUpdate: time.Now().Add(-time.Hour),
}},
ok: false,
@ -455,11 +443,11 @@ func TestGetIssuerCRLCache(t *testing.T) {
func TestVerifyCrl(t *testing.T) {
tampered := loadCRL(t, testdata.Path("crl/1.crl"))
// Change the signature so it won't verify
tampered.CertList.Signature[0]++
tampered.certList.Signature[0]++
verifyTests := []struct {
desc string
crl *certificateListExt
crl *CRL
certs []*x509.Certificate
cert *x509.Certificate
errWant string
@ -521,6 +509,15 @@ func TestRevokedCert(t *testing.T) {
revokedLeafChain := makeChain(t, testdata.Path("crl/revokedLeaf.pem"))
validChain := makeChain(t, testdata.Path("crl/unrevoked.pem"))
cache, err := lru.New(5)
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)
}
cRLProvider := NewStaticCRLProvider(rawCRLs)
if err != nil {
t.Fatalf("lru.New: err = %v", err)
}
@ -579,7 +576,7 @@ func TestRevokedCert(t *testing.T) {
}
for _, tt := range revocationTests {
t.Run(tt.desc, func(t *testing.T) {
t.Run(fmt.Sprintf("%v with x509 crl hash dir", tt.desc), func(t *testing.T) {
err := CheckRevocation(tt.in, RevocationConfig{
RootDir: testdata.Path("crl"),
AllowUndetermined: tt.allowUndetermined,
@ -592,6 +589,18 @@ func TestRevokedCert(t *testing.T) {
t.Error("Unrevoked certificate not allowed")
}
})
t.Run(fmt.Sprintf("%v with static provider", tt.desc), func(t *testing.T) {
err := CheckRevocation(tt.in, RevocationConfig{
AllowUndetermined: tt.allowUndetermined,
CRLProvider: cRLProvider,
})
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")
}
})
}
}
@ -639,6 +648,7 @@ func setupTLSConn(t *testing.T) (net.Listener, *x509.Certificate, *ecdsa.Private
}
// TestVerifyConnection will setup a client/server connection and check revocation in the real TLS dialer
// TODO add CRL provider tests here?
func TestVerifyConnection(t *testing.T) {
lis, cert, key := setupTLSConn(t)
defer func() {
@ -729,7 +739,7 @@ func TestIssuerNonPrintableString(t *testing.T) {
if err != nil {
t.Fatalf("failed to decode issuer: %s", err)
}
_, err = fetchCRL(rawIssuer, RevocationConfig{RootDir: testdata.Path("crl")})
_, err = fetchCRLOpenSSLHashDir(rawIssuer, RevocationConfig{RootDir: testdata.Path("crl")})
if err != nil {
t.Fatalf("fetchCRL failed: %s", err)
}
@ -756,8 +766,8 @@ func TestCRLCacheExpirationReloading(t *testing.T) {
// `3.crl`` revokes `revokedInt.pem`
crl := loadCRL(t, testdata.Path("crl/3.crl"))
// Modify the crl so that the cert is NOT revoked and add it to the cache
crl.CertList.RevokedCertificates = nil
crl.CertList.NextUpdate = time.Now().Add(time.Hour)
crl.certList.RevokedCertificates = nil
crl.certList.NextUpdate = time.Now().Add(time.Hour)
cache.Add(hex.EncodeToString(rawIssuer), crl)
var cfg = RevocationConfig{RootDir: testdata.Path("crl"), Cache: cache}
revocationStatus := checkChain(certs, cfg)
@ -766,7 +776,7 @@ func TestCRLCacheExpirationReloading(t *testing.T) {
}
// Modify the entry in the cache so that the cache will be refreshed
crl.CertList.NextUpdate = time.Now()
crl.certList.NextUpdate = time.Now()
cache.Add(hex.EncodeToString(rawIssuer), crl)
revocationStatus = checkChain(certs, cfg)

View File

@ -3,6 +3,7 @@ module google.golang.org/grpc/security/advancedtls
go 1.19
require (
github.com/google/go-cmp v0.5.9
github.com/hashicorp/golang-lru v0.5.4
golang.org/x/crypto v0.14.0
google.golang.org/grpc v1.58.2

View File

@ -3,6 +3,7 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=

View File

@ -35,12 +35,18 @@ type CertStore struct {
// ClientCert2 is the certificate sent by client to prove its identity.
// It is trusted by ServerTrust2.
ClientCert2 tls.Certificate
// ClientCert3 is the certificate sent by client to prove its identity.
// It is trusted by ServerTrust3. Used in CRL tests
ClientCert3 tls.Certificate
// ServerCert1 is the certificate sent by server to prove its identity.
// It is trusted by ClientTrust1.
ServerCert1 tls.Certificate
// ServerCert2 is the certificate sent by server to prove its identity.
// It is trusted by ClientTrust2.
ServerCert2 tls.Certificate
// ServerCert3 is a revoked certificate
// (this info is stored in crl_server_revoked.pem).
ServerCert3 tls.Certificate
// ServerPeer3 is the certificate sent by server to prove its identity.
ServerPeer3 tls.Certificate
// ServerPeerLocalhost1 is the certificate sent by server to prove its
@ -51,10 +57,14 @@ type CertStore struct {
ClientTrust1 *x509.CertPool
// ClientTrust2 is the root certificate used on the client side.
ClientTrust2 *x509.CertPool
// ClientTrust3 is the root certificate used on the client side.
ClientTrust3 *x509.CertPool
// ServerTrust1 is the root certificate used on the server side.
ServerTrust1 *x509.CertPool
// ServerTrust2 is the root certificate used on the server side.
ServerTrust2 *x509.CertPool
// ServerTrust2 is the root certificate used on the server side.
ServerTrust3 *x509.CertPool
}
func readTrustCert(fileName string) (*x509.CertPool, error) {
@ -79,12 +89,18 @@ func (cs *CertStore) LoadCerts() error {
if cs.ClientCert2, err = tls.LoadX509KeyPair(testdata.Path("client_cert_2.pem"), testdata.Path("client_key_2.pem")); err != nil {
return err
}
if cs.ClientCert3, err = tls.LoadX509KeyPair(testdata.Path("crl/provider_client_cert.pem"), testdata.Path("crl/provider_client_cert.key")); err != nil {
return err
}
if cs.ServerCert1, err = tls.LoadX509KeyPair(testdata.Path("server_cert_1.pem"), testdata.Path("server_key_1.pem")); err != nil {
return err
}
if cs.ServerCert2, err = tls.LoadX509KeyPair(testdata.Path("server_cert_2.pem"), testdata.Path("server_key_2.pem")); err != nil {
return err
}
if cs.ServerCert3, err = tls.LoadX509KeyPair(testdata.Path("crl/provider_server_cert.pem"), testdata.Path("crl/provider_server_cert.key")); err != nil {
return err
}
if cs.ServerPeer3, err = tls.LoadX509KeyPair(testdata.Path("server_cert_3.pem"), testdata.Path("server_key_3.pem")); err != nil {
return err
}
@ -97,11 +113,17 @@ func (cs *CertStore) LoadCerts() error {
if cs.ClientTrust2, err = readTrustCert(testdata.Path("client_trust_cert_2.pem")); err != nil {
return err
}
if cs.ClientTrust3, err = readTrustCert(testdata.Path("crl/provider_client_trust_cert.pem")); err != nil {
return err
}
if cs.ServerTrust1, err = readTrustCert(testdata.Path("server_trust_cert_1.pem")); err != nil {
return err
}
if cs.ServerTrust2, err = readTrustCert(testdata.Path("server_trust_cert_2.pem")); err != nil {
return err
}
if cs.ServerTrust3, err = readTrustCert(testdata.Path("crl/provider_server_trust_cert.pem")); err != nil {
return err
}
return nil
}

View File

@ -46,3 +46,74 @@ Certificate chain where the leaf is revoked
* 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
## Test Data for testing CRL providers functionality
To generate test data please follow the steps below or run provider_create.sh
script. All the files have `provider_` prefix.
We need to generate the following artifacts for testing CRL provider:
* server self signed CA cert
* client self signed CA cert
* server cert signed by client CA
* client cert signed by server CA
* empty crl file
* crl file containing information about revoked server cert
Please find the related commands below.
* Generate self signed CAs
```
$ openssl req -x509 -newkey rsa:4096 -keyout provider_server_trust_key.pem -out provider_server_trust_cert.pem -days 365 -subj "/C=US/ST=VA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.ca.com" -nodes
$ openssl req -x509 -newkey rsa:4096 -keyout provider_client_trust_key.pem -out provider_client_trust_cert.pem -days 365 -subj "/C=US/ST=CA/L=SVL/O=Internet Widgits Pty Ltd" -nodes
```
* Generate client and server certs signed by CAs
```
$ openssl req -newkey rsa:4096 -keyout provider_server_cert.key -out provider_new_cert.csr -nodes -subj "/C=US/ST=CA/L=DUMMYCITY/O=Internet Widgits Pty Ltd/CN=foo.bar.com" -sha256
$ openssl x509 -req -in provider_new_cert.csr -out provider_server_cert.pem -CA provider_client_trust_cert.pem -CAkey provider_client_trust_key.pem -CAcreateserial -days 3650 -sha256 -extfile provider_extensions.conf
$ openssl req -newkey rsa:4096 -keyout provider_client_cert.key -out provider_new_cert.csr -nodes -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.com" -sha256
$ openssl x509 -req -in provider_new_cert.csr -out provider_client_cert.pem -CA provider_server_trust_cert.pem -CAkey provider_server_trust_key.pem -CAcreateserial -days 3650 -sha256 -extfile provider_extensions.conf
```
Here is the content of `provider_extensions.conf` -
```
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
```
* Generate CRLs
For CRL generation we need 2 more files called `index.txt` and `crlnumber.txt`:
```
$ echo "1000" > provider_crlnumber.txt
$ touch provider_index.txt
```
Also we need another config `provider_crl.cnf` -
```
[ ca ]
default_ca = my_ca
[ my_ca ]
crl = crl.pem
default_md = sha256
database = provider_index.txt
crlnumber = provider_crlnumber.txt
default_crl_days = 30
default_crl_hours = 1
crl_extensions = crl_ext
[crl_ext]
# Authority Key Identifier extension
authorityKeyIdentifier=keyid:always,issuer:always
```
The commands to generate empty CRL file and CRL file containing revoked server
cert are below.
```
$ openssl ca -gencrl -keyfile provider_client_trust_key.pem -cert provider_client_trust_cert.pem -out provider_crl_empty.pem -config provider_crl.cnf
$ openssl ca -revoke provider_server_cert.pem -keyfile provider_client_trust_key.pem -cert provider_client_trust_cert.pem -config provider_crl.cnf
$ openssl ca -gencrl -keyfile provider_client_trust_key.pem -cert provider_client_trust_cert.pem -out provider_crl_server_revoked.pem -config provider_crl.cnf
```

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
zDK1RAz8JZHyOJ4tVxpw76AxPUOv+A==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFmTCCA4GgAwIBAgIUH+FcZgWO0XDKIU8T/mcyUE9YFhkwDQYJKoZIhvcNAQEL
BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
Fw0yMzEwMjAxODM1NTdaFw0zMzEwMTcxODM1NTdaMFcxCzAJBgNVBAYTAlVTMQsw
CQYDVQQIDAJDQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRgw
FgYDVQQDDA9mb28uYmFyLmhvby5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDo9wlMibqqY/mTBdAquY+JORumIunnTXQMrMriB2/afgOJtoo1UABE
2evNabeh+qBlzVe7ouFBLG7fq0MtmiUP38kZRTJQhoXqI/boYgRbGAz5cE23OfJZ
9cvJMAAiVdLXNvmcmWf+CxPN0bKZNgZ0HYpqkalLO5hLUvCc225Kie/0CzuWcrA8
GIxiMO/3VJj7vdWuiDbRPPGVBfEZEC7jCXUcLVyx17yvnxeODYjHwETVfVegSnHA
P4RY+H0HGwqu0ZBAWnbO9HC3q7o9CdW68KJYFP43c01AHO6vK1Kkkq3MkpP/uVx9
DGOc+FPovUuui8s8pLQ6SIee2zq12YmQFdDFkwV/iFd6drCNBFWZF2EPRqldfebw
qur8eMAkP7KlnhFQ7C1R5xseW09rsBU4PVtGAuUfE0ATLPIMebiAlPU/1yr4oXFv
PT2qjS6dtgyoYifJSeN6/W2cpVhzmZs0WfJabHBOz+W59oq8Tl19xRb6vekwpbEl
mt2MVXSN1TvweQQPEqh/SjVr8QIp1nwZ1kZAiYAfLvBF9kqpjodg/ZIFpQiL9KmW
wv30IZX4Xv5LvLA/cgjPcGUDxvpyV6VCIbmS/dmqZP/JDE27GKRCG+tsugpaKsZW
8zOJbrBgYVNUM0hU02pXiqMMHGFP/FdYQlxXsiq5FYSxYEtuRNNNgQIDAQABo1ow
WDAdBgNVHQ4EFgQUQd4QRGICOG9KgbDjJTtEXfcdS2swHwYDVR0jBBgwFoAU0UZz
FCfHiQfVrExiD2QPevGA5VIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwDQYJKoZI
hvcNAQELBQADggIBAGqa4kbO3mnjuJy9PXvMCvF1BwFhv3ytRhrGU+h9HVbw9l1i
dZMTvS2NRVj4hyqCvtgxOrXKdbGBxcZEajzSJa+rSDmET9PGd7DdBlCLp0VXYgr0
dQmtFPgwLCuTRYWqxixPMy1Hc2KWvljZ5K9fk8Rz+IL9Y3oqaClMz3yc7F5Ve2fd
dANKaIDdSVo9ScATMfineggfbz6L81dFamSzIBbvwfUX7Puop+Zq/g6sVz1vLg44
FPK8Etw5LaLC+C7CX7+YD7bCs/v6p/Uv2N6AjP7W6h6zu3HlptytMNRjpcl30ur2
j10AV4UuhlhZiwysvgJCHahcIaM+jVRXoWfDqnvrJjN1Pe4I7LkE4bNsumWl76An
V4/nRxXWQsXFhqOK3f/prKJzUJLr1Sfg4JafKFulb4HYqvNY16IQhqtSse+MyT0r
KwA+wqBREGosldOb8T9utgBxCmudPPXPlJjcER9WE1qzm5uHyeaNnewTZkF4xiCH
grrW3+ZVcjlLjqj5otGAnr1lMUA7K8bV1jJzB3o+QNCDL1y5uKeOGmLjP8xpQJQW
nPUQEmFa6YPWwWphE6LR5CURARQy9aRPILtPMommEc8KYXlBvYcRX1rGBVXjZNIE
Ibnt/7aJJ8BqwRBylhho1w7O14MZWDB6iR9xNW+4/pgaNYysS85WoF6z56UE
-----END CERTIFICATE-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFdzCCA1+gAwIBAgIUKqY2Cg+WHLt9amXOOJBNlBXjEHkwDQYJKoZIhvcNAQEL
BQAwSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzEwMjAxODM1NTRa
Fw0yNDEwMTkxODM1NTRaMEsxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG
A1UEBwwDU1ZMMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCt+gVYNPbMAsqrhziGDUFmM1aa
rxcKHlfu8DaKYYvs+KTguRRU69IozsdrXR6jzwCGiId926PuJ0FC0fuW52LC/2Xi
FW86MLwgJ0lP+C3WL5D4B6vLCVIaI2YWzLk+mfZ1PclsuvI2Ffq3UWXnZb9o7HIN
OgZ8TMISJGYDCtYHasNiWlYrQecrSf6KjRprT4+USXMDrUP3g0AEq9TYuxLXFZq3
CMxC+6sV3KWOsNKbAVqQ8xZ/iTgLSzYfifi3ljXliIqj+9vz5Xtb4fxzzwEdqNPJ
tzbPCE+8wOdVg55Lagomb/EYO3wqS4eOzbZ5odX6IMEmsOEOk1+35V6IZtlPSIP2
zk/JwtC4oZV8XexSu0aw+SSjKPosTQv5VGo1ptJhBxI7AVGWmaquX0C4JIsTIXn3
HLqFVjdyYSX/fx2yfodmw2xIGfoCbEE3NQAjD/k4zZWhXeqA8qR3kcgwMsI+zsug
LHVC8hbRY5YdQFCH3mBwBsj5PLkcKevRRWSoIHd3m79nTFJClYtr3w6ublwFIndi
lsQysXHG5C49zMDOBJkISQ95dcIJwoQm22ePxjbhs2XCr/zFrtMtmkaD2uQBAt9a
uShY0aXiifkdoZ7fSjbQ7u+DzeaJ64g2RHSCyRbLDtp9L0eolN3q7u00Uc/vSzHW
8DeLGVtdqkmQIh04PQIDAQABo1MwUTAdBgNVHQ4EFgQULiFveRb8zO102dF5iXGd
xqir/4IwHwYDVR0jBBgwFoAULiFveRb8zO102dF5iXGdxqir/4IwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAQXb+u9cl2aVtchhW/qhwPjOU82BP
r1hByxiG95pQFdtquf953/jZ4S0GoDFE1lrfqcoaxSKWM6tTIUQxJmJxVDJaA1JI
eyc4qXceEZ/GZYAlP1BqGTsxqWNYVA1suKYMlpz2GZDGY1/M7Ggy56P1V5YeAgce
IGM65aj6eyc5SDtNqWperkpJdr960CkzoHreSbKzcWny7yF5W5L5WeZrovQj+FKI
tdcsajxRbxcTl/zqAVifRPIazfU+0g9pV5WvqF8p8HKFn3LgWZFkR4LfPXzepVy9
/67R76vp48lmbUdMJ1llMHYHSvbjHwN7iT/MV7R1KUzpmjAJhcBg1GwrwT+hjcMW
bBfJYeAKXbekPWNUC0dpiAiaHR5znAjJ8zYtH9loFrZl5xlSIp8M8EFZiC9uecqL
REWrbjE6vZribAk5x6L2Qabr/7PBmhRPRMulC/Oc8TvBMa2YxjBZowEcXnYIIP0i
aLZ69Lrvle6axQT7ZYynSwrunRAlhVrDP8rAQH1UT1l3OV8GsZ0I3Mht0DEY/s83
/siCVm3vteMUkv7WA7aKTB63MFjWg8lajbOlmgqLExoHRCAnBNY/NsHkFpm9xt0F
vIrQtU3GXk5Uyx/Vh0se81+0u74UQqZYJ/PbOLTDzRXolGq+zp++eiUMHvYnCXU7
8vBcs8+CWNGSL20=
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
V5VXFg6RJMUHFm56NBP/NTCWFkBD2Dk=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,76 @@
#!/bin/bash
# The script contains a sequence of commands described in README.md
openssl req -x509 \
-newkey rsa:4096 \
-keyout provider_server_trust_key.pem \
-out provider_server_trust_cert.pem \
-days 365 \
-subj "/C=US/ST=VA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.ca.com" \
-nodes
openssl req -x509 \
-newkey rsa:4096 \
-keyout provider_client_trust_key.pem \
-out provider_client_trust_cert.pem \
-days 365 \
-subj "/C=US/ST=CA/L=SVL/O=Internet Widgits Pty Ltd" \
-nodes
openssl req -newkey rsa:4096 \
-keyout provider_server_cert.key \
-out provider_new_cert.csr \
-nodes \
-subj "/C=US/ST=CA/L=DUMMYCITY/O=Internet Widgits Pty Ltd/CN=foo.bar.com" \
-sha256
openssl x509 -req \
-in provider_new_cert.csr \
-out provider_server_cert.pem \
-CA provider_client_trust_cert.pem \
-CAkey provider_client_trust_key.pem \
-CAcreateserial \
-days 3650 \
-sha256 \
-extfile provider_extensions.conf
openssl req -newkey rsa:4096 \
-keyout provider_client_cert.key \
-out provider_new_cert.csr \
-nodes \
-subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.com" \
-sha256
openssl x509 -req \
-in provider_new_cert.csr \
-out provider_client_cert.pem \
-CA provider_server_trust_cert.pem \
-CAkey provider_server_trust_key.pem \
-CAcreateserial \
-days 3650 \
-sha256 \
-extfile provider_extensions.conf
echo "1000" > provider_crlnumber.txt
touch provider_index.txt
openssl ca -gencrl \
-keyfile provider_client_trust_key.pem \
-cert provider_client_trust_cert.pem \
-out provider_crl_empty.pem \
-config provider_crl.cnf
openssl ca -revoke provider_server_cert.pem \
-keyfile provider_client_trust_key.pem \
-cert provider_client_trust_cert.pem \
-config provider_crl.cnf
openssl ca -gencrl \
-keyfile provider_client_trust_key.pem \
-cert provider_client_trust_cert.pem \
-out provider_crl_server_revoked.pem \
-config provider_crl.cnf
rm *.csr

View File

@ -0,0 +1,14 @@
[ ca ]
default_ca = my_ca
[ my_ca ]
default_md = sha256
database = provider_index.txt
crlnumber = provider_crlnumber.txt
default_crl_days = 30
default_crl_hours = 1
crl_extensions = crl_ext
[crl_ext]
# Authority Key Identifier extension
authorityKeyIdentifier=keyid:always,issuer:always

View File

@ -0,0 +1,20 @@
-----BEGIN X509 CRL-----
MIIDWjCCAUICAQEwDQYJKoZIhvcNAQELBQAwSzELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNBMQwwCgYDVQQHDANTVkwxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZBcNMjMxMDIwMTgzNTU3WhcNMjMxMTE5MTkzNTU3WjAnMCUCFCBOb4be
aSBeU1CCmUkUOpLDLh4hFw0yMzEwMjAxODM0MTJaoIGZMIGWMIGGBgNVHSMEfzB9
gBQuIW95FvzM7XTZ0XmJcZ3GqKv/gqFPpE0wSzELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNBMQwwCgYDVQQHDANTVkwxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZIIUKqY2Cg+WHLt9amXOOJBNlBXjEHkwCwYDVR0UBAQCAhAAMA0GCSqG
SIb3DQEBCwUAA4ICAQCpo3SBHdRE+yCNw9dKcvIbDM01jPAYObxggP6YIuiS+ZgN
ozdDsoDBfzpo5gqR7tB4/PbD0TviJ25dWfWr9Kq2aobDrmDaemZ0tnURxiT+mI7e
327P9S1mrjSy07hlM0gX1CA9PmzrhCNiyS7w5EpHStTu768/ftMeostWJvRPBjIO
lVVEgibhxqxj7eWJ0xCMvwmp5oI3OXsjlkv2AvGvwrJI6EPbUKB8Ppa6LAEWaIfL
h0Uvd2U+FJCPdfHtQTTBarGdplS6cRxeR1EFfquHB78zoGE5ZH8sMUOclVAi3vJ7
81PKce+dUIFhePTh4IKH+OcuMydSaVs6O9Ju8/DNTNMQvUkFqA/9doCDql3aaN+9
bGjYEJLw5xWOfP8yF5qfppVahHWK4q9Ezm+vmALjcNMt/EcP6Gp7LhHUwnbE18+Y
krwJNG/RNnwDP0eNgeUQA+dGacEFnWX4RQ5lUIm1/DO1IBRHy5vali3qg/0Ryx/R
orafpcqLo6FUjZQz+W4URACzq37oRY4qMBr3yNzVpqaVpej8zluqEOZpf8gpo9EC
Cika7WeHaUk5U1FHAfzccIao+Xh/13nPQsEdR7VjsluNZHr/pOWZEigG0sBbIFxH
qfIZ4QaSnG0cr5XTkEYPu9W8SXD64Y9C1dyYtFFgsDyaBSSwOFvQfnbzZTOGEg==
-----END X509 CRL-----

View File

@ -0,0 +1,21 @@
-----BEGIN X509 CRL-----
MIIDgTCCAWkCAQEwDQYJKoZIhvcNAQELBQAwSzELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNBMQwwCgYDVQQHDANTVkwxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZBcNMjMxMDIwMTgzNTU3WhcNMjMxMTE5MTkzNTU3WjBOMCUCFCBOb4be
aSBeU1CCmUkUOpLDLh4hFw0yMzEwMjAxODM0MTJaMCUCFCBOb4beaSBeU1CCmUkU
OpLDLh4iFw0yMzEwMjAxODM1NTdaoIGZMIGWMIGGBgNVHSMEfzB9gBQuIW95FvzM
7XTZ0XmJcZ3GqKv/gqFPpE0wSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQww
CgYDVQQHDANTVkwxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIU
KqY2Cg+WHLt9amXOOJBNlBXjEHkwCwYDVR0UBAQCAhABMA0GCSqGSIb3DQEBCwUA
A4ICAQAylv3bjfdzUmWp8vVkIutABIwR4vrO54gVgslg0OUPC1C0iq/knpMywzT0
xej4IRULVAhkNFE1mOjan37ghqPOvn+WGUlQD1VzCCnCc5aWoLSHNvIpfNe4Z0tU
VUrFMIiRBEAy3dznoTsBjwiXVjHx4MJ7w0cLDBfEsQgpmmdurFkpSSQFkfqG8+BU
Hqde1vdFld7iCwA0ooYTrb/BFUq2/JSz5pgxPZib/oYAxgP452cD6EBeteCMDx6L
WeKYTDqEzrB5fjYJRksNN+FW7nM7saHC4fSwXeAoR5N6uMF+KYMZxhz84G2ljz9T
Wt2e1fJ0hAOu3cxsMmkKRZjpNPmSUMxerylbkL7VXWgms5sohZ62L4GElnxSqVNC
a5Trtqpe6b5UrqIyr6MGzrrVafU/3C+gv5agCO+TuU+9kMv3/EzPn36fTMhtn8NF
PjgoA3DVqkOdTi3FaGBgAzixzeKX5RBkhyB9vlx5ojm3cKyk+CBoBhogadr8FClZ
/0ss98j0cIbWb93LS6et69LvWSoDNLMYKz8zZLqws6o6cIdPITOJbUjss8b3R7i2
HfdElF7PJMJlW6L+1I8phhiQzCezCoBOdVD6YvdNmyxJj3m29bO3XrOdQf0Qj1QN
vYqyl+js5yNp6pEtwAxHfXqB6VZgmdG22kYHQFRaDdlU7VrzpA==
-----END X509 CRL-----

View File

@ -0,0 +1,4 @@
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
43iT2lxMrKbtvNlNluEqPbJ74GNB4T4c
-----END PRIVATE KEY-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFmjCCA4KgAwIBAgIUIE5vht5pIF5TUIKZSRQ6ksMuHiIwDQYJKoZIhvcNAQEL
BQAwSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzEwMjAxODM1NTVa
Fw0zMzEwMTcxODM1NTVaMGcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAG
A1UEBwwJRFVNTVlDSVRZMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM
dGQxFDASBgNVBAMMC2Zvby5iYXIuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAqn+0nu3KyN9vio3KTTz8E8ArmB0SrXTZWSIBmPlbNhDekUwtQxLU
05ukBnB3cYdbwjDb/X+5ikLx6OY5dyn02N6qW7YLCKycgi5fhsQS6RX851Pw8rXB
uQIaYcPcWAjBmtpRGxDNQuBuu/xZppEiziUT7AnobCJy0QvuMIC1E2yvL0xgT84z
TzBZap7UXwMcHCIhY6nWGAwVEZK4E+yI3xuSUUL+0AiX8e2xg9H5LQQgwP8Llx2S
aqIhlhl6BKAlGJMt9d8MokB1dDaiocxB/b9VTqME11OxAj5bZYXC71dcV3Am0XEG
rMA81YUfnKH0FONx6NQW0LR2YsxIWADRAtEflCpe7D3f3n0pjBzkDaFBt0KjEGCS
rr62bu8lzCjxXELr2yCMAWq8CnLlxmXNt9b9/SV242qWsA/cZJsXzgSzCEXVxp3a
VIl9noKdkIE2KgO4JcQo6viomGzcJ1LPeO4BlVWTavFu6vAcWjbgJZYpPYcoUKCA
juso1rdqMaEuvL7z2a5Sa+bFvQOVSNhbkuSFHORtWPEugfHsJyYm4C3Hyl94MaFJ
JEGfRLGDWGi1TjU9+SdGoIVBTq3dHhor5c8FzenbFiSAJ68aq4ehLqwlbGrKNKTb
vLSqwEKLsr6FVIllqsMMITmDmy05Klw99AbzovL/knrf+k2cACsx71sCAwEAAaNa
MFgwHQYDVR0OBBYEFF+qjGuxabV+x/NnfsDP50cFbBIrMB8GA1UdIwQYMBaAFC4h
b3kW/MztdNnReYlxncaoq/+CMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqG
SIb3DQEBCwUAA4ICAQBX4yzbJ4IewiGMTbSs2aH7x4WSRI7mrGBTwXBjdzQBaIn3
vNMaKbxmt8BC36MVyGy6xoPOZJZiR2t6P6QjfpIiruIIw5cmqjw/7feyxxKNXIzR
BWXm0Nt/btkjgyHqPvtR7DcT+ko7tG94OJzeqW0ZiHaO9egufhEQjmwi5siKSh8Q
Df9vzNu9tgqlLyQiJZbKW9gmXMG33dIn5Dx9QF2ax+7UXuFqYs8Dwq9slmVkMRrX
2b0/7exS2ExOZpDWPZGc4+jr94MbF5O7sPSF/mDTh4nRGXJN/QBbw6maGtdv/5ov
lJ2z8lwdKiGO548xBX5IcyQrC+qgqo9pEewxWhfX7jfZQ121xkJYBqa/RDFo+f6Q
lJExnsqmLW82ZBkPzdiwW9xCdpirmTHwtizUQ8JlkV3jy9PSxRCaNrCrlhUISdNd
OjcGIlnjgz1bTJGJubsDpIkYUrv6w5WSY1RqYK1B8DnNyoPsHMWC5J0MxKpa/w3L
8VK5FoZthP+zJpT6IOZsGGMZ+X9nuqrpbI43lHMBIC30/fVJalrK/AOOfz9VULG7
9ALv5OiP++If8+9wj4SOLhjpf+U5R+pH/g2oz3/ODGlJDV+ZX4FaxRUWeZwvGQv5
aIH5v0zgFZIpyaIsdEm/zONqzqcIyLRMjixe7Xr/Q1vKPZXIVEj0DHvvjsl6+Q==
-----END CERTIFICATE-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFlTCCA32gAwIBAgIUBnhgsNLyVwPm+wWr9DCNqjHi/DYwDQYJKoZIhvcNAQEL
BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
Fw0yMzEwMjAxODM1NTBaFw0yNDEwMTkxODM1NTBaMFoxCzAJBgNVBAYTAlVTMQsw
CQYDVQQIDAJWQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRsw
GQYDVQQDDBJmb28uYmFyLmhvby5jYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCsY0i8YoKj4iwbTk7osHmBmOcfOF1NEVlTRj7Nx7PQ9dEowvnt
zE1392SbDPWDc4VbLBlvK2etBpOgUGMMQpaoMUuhVwe0KX+B0mvmOmmscpFqIml2
5kRP6lF/ngCQD5W/NBPKyWR0FtghW0kBNU5DwPRTQH+h7LjWwegmOWi2DRwAiL08
IMDA6akaqyJtCcuY6stWTSxm2yaW7ucXjm/FZBbiXoP4+Fa/Pgjq7kPLCCn/KdSg
av8Rt2ErfYnJUVe7KHt08ZD/z8gMD89RB2nJ/L2Xy7mylDC5yqIayPYiNtVbN/Fb
OaHsaBuVAbOOGV41mF0PrWdj7KQd9zZnIgtEfmK2OggeWCk6qo8qKEigxKI9eYxg
OvXOYevk8ozqqYUvwyhKPqpvFQvepD0w739pWowbKxwW+6xJhPd1yVSye2OLF7Fx
rzPlRXV7GuEDNwqrNbXMU6UdN8795iAIE8dr545S77RyKABiMvcvZBEI/j8ucEQI
as2LaKALfj/yYvaCL4y9CoA4239Q5embElamxKAvgT3CAj5+jcYHGx+IkJUv/1zl
n/ju51C/xfRk/iXf6UU2taODEtBKbC0xzJbnMyXFuYeS59IGo443C9148Rn8eRdF
Cg/hHapmlI3YVAjx61o3Kdgf5aDEj+0LHggfI7bIt2Fil/hmz56dX6sSvQIDAQAB
o1MwUTAdBgNVHQ4EFgQU0UZzFCfHiQfVrExiD2QPevGA5VIwHwYDVR0jBBgwFoAU
0UZzFCfHiQfVrExiD2QPevGA5VIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
AQsFAAOCAgEAALpRRiFhBU4ZcreZ3dDMThABAmQi8DAaxpTsLTGsGvvyWXtVPvXH
yWUjZEYYYv9IydBLJ4nACKZtLGm/X0+jBl9H9pYWZP35OYCoFnqAMXTdpmXby442
F7/HWYBCt6z/9k2dN1Jb0ZoaA4uheBKOe+RP3sjv63il83F4hGXMToiTxQMRfaFj
aiQoXvs+06DozMEhB4d14+Bd18qdkJStFWWLUjsJ4TIpzWh4SKprMvePZoLjS/tZ
y7xatqwHsKoNvie0PjAolJpU+qcDlCp32UOkOYr8YDYojsYefQvb7qjUOfeUUocE
VI8mSLayEA8jF0ZIj4hPq4M1TVfUs168dEbsfnHy50H9yCCS+Qq4ubjiXW2M2U2Y
4XWAu0Kh1W/fxW5WVNwrahidmOc5oie4dAYOUcqZe8GnIG2hwBytUkPg9Vv/ImFw
jnJONDV2WekWAJfDSvRuVFi8RkxOyfCf7hhaGML6euwDwxWKxr/XjyZUSQud8l4a
PutvZB65SY4w8VawchPnZ/Hg9kbOtfauzJTyibiHBsiLZnihxrGK9JV8Nv5hg1es
gV9NIhbctR9N+faz631Dg9wZE3AJPEcuh3c2gYqG3UzbXgXk8xZXD/F6hYhTwqvs
W83vT5i+4qpTCnZZ9Zg7L9fo5KZb0DMTsdBUomH6oLJRKPu45l0rFOA=
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
FhP/nZ3Y5l4y3XdzasT9ityH9SM=
-----END PRIVATE KEY-----