Skip to content

Commit

Permalink
crl file cache
Browse files Browse the repository at this point in the history
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
  • Loading branch information
Two-Hearts committed Sep 21, 2024
1 parent a629577 commit 0d23fcd
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 32 deletions.
59 changes: 45 additions & 14 deletions verifier/crl/crl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ package crl
import (
"context"
"crypto/sha256"
"encoding/gob"
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -56,6 +58,15 @@ type FileCache struct {
root string
}

// fileCacheContent is the actual content saved in a FileCache
type fileCacheContent struct {
// RawBaseCRL is baseCRL.Raw
RawBaseCRL []byte `json:"rawBaseCRL"`

// RawDeltaCRL is deltaCRL.Raw
RawDeltaCRL []byte `json:"rawDeltaCRL,omitempty"`
}

// NewFileCache creates a FileCache with root as the root directory
func NewFileCache(root string) (*FileCache, error) {
if err := os.MkdirAll(root, 0700); err != nil {
Expand All @@ -71,25 +82,34 @@ func NewFileCache(root string) (*FileCache, error) {
func (c *FileCache) Get(ctx context.Context, url string) (*corecrl.Bundle, error) {
logger := log.GetLogger(ctx)

// read CRL bundle from file
// get content from file cache
f, err := os.Open(filepath.Join(c.root, c.fileName(url)))
if err != nil {
if os.IsNotExist(err) {
if errors.Is(err, fs.ErrNotExist) {
logger.Infof("CRL file cache miss. Key %q does not exist", url)
return nil, corecrl.ErrCacheMiss
}
return nil, fmt.Errorf("failed to get crl bundle from file cache with key %q: %w", url, err)
}
defer f.Close()
dec := gob.NewDecoder(f)
var bundle corecrl.Bundle
err = dec.Decode(&bundle)

// decode content to crl Bundle
var content fileCacheContent
err = json.NewDecoder(f).Decode(&content)
if err != nil {
return nil, fmt.Errorf("failed to decode file retrieved from file cache to CRL Bundle: %w", err)
return nil, fmt.Errorf("failed to decode file retrieved from file cache: %w", err)
}
// TODO: add DeltaCRL once notation-core-go supports it. Issue: https://github.com/notaryproject/notation-core-go/issues/228
baseCRL, err := x509.ParseRevocationList(content.RawBaseCRL)
if err != nil {
return nil, fmt.Errorf("failed to parse base CRL of file retrieved from file cache: %w", err)
}
bundle := corecrl.Bundle{
BaseCRL: baseCRL,
}

// check expiry
nextUpdate := bundle.BaseCRL.NextUpdate
nextUpdate := baseCRL.NextUpdate
if nextUpdate.IsZero() {
return nil, errors.New("crl bundle retrieved from file cache does not contain BaseCRL NextUpdate")
}
Expand All @@ -104,18 +124,29 @@ func (c *FileCache) Get(ctx context.Context, url string) (*corecrl.Bundle, error

// Set stores the CRL bundle in c with url as key.
func (c *FileCache) Set(ctx context.Context, url string, bundle *corecrl.Bundle) error {
// sanity check
if bundle == nil {
return errors.New("failed to store crl bundle in file cache: bundle cannot be nil")
}
// save to tmp file
if bundle.BaseCRL == nil {
return errors.New("failed to store crl bundle in file cache: bundle BaseCRL cannot be nil")
}

// actual content to be saved in the cache
//
// TODO: add DeltaCRL once notation-core-go supports it. Issue: https://github.com/notaryproject/notation-core-go/issues/228
content := fileCacheContent{
RawBaseCRL: bundle.BaseCRL.Raw,
}

// save content to tmp file
tmpFile, err := os.CreateTemp("", tmpFileName)
if err != nil {
return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
return fmt.Errorf("failed to store crl bundle in file cache: failed to create temp file: %w", err)

Check warning on line 145 in verifier/crl/crl.go

View check run for this annotation

Codecov / codecov/patch

verifier/crl/crl.go#L145

Added line #L145 was not covered by tests
}
enc := gob.NewEncoder(tmpFile)
err = enc.Encode(bundle)
err = json.NewEncoder(tmpFile).Encode(content)
if err != nil {
return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
return fmt.Errorf("failed to store crl bundle in file cache: failed to encode content: %w", err)

Check warning on line 149 in verifier/crl/crl.go

View check run for this annotation

Codecov / codecov/patch

verifier/crl/crl.go#L149

Added line #L149 was not covered by tests
}

// rename is atomic on UNIX-like platforms
Expand All @@ -126,7 +157,7 @@ func (c *FileCache) Set(ctx context.Context, url string, bundle *corecrl.Bundle)
return nil
}

// fileName returns the filename of the CRL bundle within c
// fileName returns the filename of the content stored in c
func (c *FileCache) fileName(url string) string {
hash := sha256.Sum256([]byte(url))
return hex.EncodeToString(hash[:])
Expand Down
40 changes: 33 additions & 7 deletions verifier/crl/crl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"crypto/rand"
"crypto/x509"
"encoding/json"
"errors"
"math/big"
"os"
Expand Down Expand Up @@ -49,10 +50,6 @@ func TestFileCache(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
cache, err := NewFileCache(root)
if err != nil {
t.Fatal(err)
}

t.Run("NewFileCache", func(t *testing.T) {
if err != nil {
t.Fatalf("expected no error, but got %v", err)
Expand Down Expand Up @@ -134,9 +131,9 @@ func TestGetFailed(t *testing.T) {
}
})

t.Run("invalid bundle", func(t *testing.T) {
t.Run("invalid content", func(t *testing.T) {
_, err := cache.Get(context.Background(), "invalid")
expectedErrMsg := "failed to decode file retrieved from file cache to CRL Bundle: unexpected EOF"
expectedErrMsg := "failed to decode file retrieved from file cache: invalid character 'i' looking for beginning of value"
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
}
Expand All @@ -154,6 +151,26 @@ func TestGetFailed(t *testing.T) {
if err != nil {
t.Fatalf("failed to parse base CRL: %v", err)
}

t.Run("invalid RawBaseCRL of content", func(t *testing.T) {
content := fileCacheContent{
RawBaseCRL: []byte("invalid"),
}
b, err := json.Marshal(content)
if err != nil {
t.Fatal(err)
}
invalidBundleFile := filepath.Join(tempDir, cache.fileName("invalidBundle"))
if err := os.WriteFile(invalidBundleFile, b, 0644); err != nil {
t.Fatalf("failed to write file: %v", err)
}
_, err = cache.Get(context.Background(), "invalidBundle")
expectedErrMsg := "failed to parse base CRL of file retrieved from file cache: x509: malformed crl"
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
}
})

t.Run("bundle with invalid NextUpdate", func(t *testing.T) {
ctx := context.Background()
expiredBundle := &corecrl.Bundle{BaseCRL: baseCRL}
Expand Down Expand Up @@ -222,7 +239,16 @@ func TestSetFailed(t *testing.T) {
}
})

t.Run("failed to create tmp file", func(t *testing.T) {
t.Run("nil bundle BaseCRL", func(t *testing.T) {
bundle := &corecrl.Bundle{}
err := cache.Set(ctx, key, bundle)
expectedErrMsg := "failed to store crl bundle in file cache: bundle BaseCRL cannot be nil"
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
}
})

t.Run("failed to write into cache due to permission denied", func(t *testing.T) {
if err := os.Chmod(tempDir, 0); err != nil {
t.Fatal(err)
}
Expand Down
11 changes: 0 additions & 11 deletions verifier/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
"oras.land/oras-go/v2/content"

"github.com/notaryproject/notation-core-go/revocation"
corecrl "github.com/notaryproject/notation-core-go/revocation/crl"
"github.com/notaryproject/notation-core-go/revocation/purpose"
revocationresult "github.com/notaryproject/notation-core-go/revocation/result"
"github.com/notaryproject/notation-core-go/signature"
Expand Down Expand Up @@ -183,13 +182,8 @@ func (v *verifier) setRevocation(verifierOptions VerifierOptions) error {
revocationTimestampingValidator := verifierOptions.RevocationTimestampingValidator
var err error
if revocationTimestampingValidator == nil {
crlFetcher, err := corecrl.NewHTTPFetcher(&http.Client{Timeout: 5 * time.Second})
if err != nil {
return err
}
revocationTimestampingValidator, err = revocation.NewWithOptions(revocation.Options{
OCSPHTTPClient: &http.Client{Timeout: 2 * time.Second},
CRLFetcher: crlFetcher,
CertChainPurpose: purpose.Timestamping,
})
if err != nil {
Expand All @@ -211,13 +205,8 @@ func (v *verifier) setRevocation(verifierOptions VerifierOptions) error {
}

// both RevocationCodeSigningValidator and RevocationClient are nil
crlFetcher, err := corecrl.NewHTTPFetcher(&http.Client{Timeout: 5 * time.Second})
if err != nil {
return err
}
revocationCodeSigningValidator, err = revocation.NewWithOptions(revocation.Options{
OCSPHTTPClient: &http.Client{Timeout: 2 * time.Second},
CRLFetcher: crlFetcher,
CertChainPurpose: purpose.CodeSigning,
})
if err != nil {
Expand Down

0 comments on commit 0d23fcd

Please sign in to comment.