-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: delta CRL #247
Open
JeyJeyGao
wants to merge
9
commits into
notaryproject:main
Choose a base branch
from
JeyJeyGao:feat/delta_crl
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
feat: delta CRL #247
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e34370f
feat: delta CRL
JeyJeyGao 8182be9
fix: license and variable names
JeyJeyGao 2049193
fix: optimize logic
JeyJeyGao 33d5590
fix: working on delta CRL
JeyJeyGao 7984f57
fix: bug
JeyJeyGao 77b5972
test: add test for fetcher
JeyJeyGao 0b6179a
test: complete test for validate
JeyJeyGao c9f936f
test: add test cases
JeyJeyGao c228487
Merge branch 'main' into feat/delta_crl
JeyJeyGao File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,13 +18,17 @@ | |
import ( | ||
"context" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"golang.org/x/crypto/cryptobyte" | ||
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" | ||
) | ||
|
||
// oidFreshestCRL is the object identifier for the distribution point | ||
|
@@ -44,6 +48,15 @@ | |
Fetch(ctx context.Context, url string) (*Bundle, error) | ||
} | ||
|
||
// FetcherWithCertificateExtensions is an interface that specifies methods used | ||
// for fetching CRL from the given URL with certificate extensions to identify | ||
// the Freshest CRL extension (Delta CRL) from certificate. | ||
type FetcherWithCertificateExtensions interface { | ||
// FetchWithCertificateExtensions retrieves the CRL from the given URL with | ||
// certificate extensions. | ||
FetchWithCertificateExtensions(ctx context.Context, url string, certificateExtensions *[]pkix.Extension) (*Bundle, error) | ||
} | ||
|
||
// HTTPFetcher is a Fetcher implementation that fetches CRL from the given URL | ||
type HTTPFetcher struct { | ||
// Cache stores fetched CRLs and reuses them until the CRL reaches the | ||
|
@@ -77,24 +90,28 @@ | |
// (e.g. cache miss), it will download the CRL from the URL and store it to the | ||
// cache. | ||
func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*Bundle, error) { | ||
return f.FetchWithCertificateExtensions(ctx, url, nil) | ||
} | ||
|
||
func (f *HTTPFetcher) FetchWithCertificateExtensions(ctx context.Context, url string, certificateExtensions *[]pkix.Extension) (*Bundle, error) { | ||
if url == "" { | ||
return nil, errors.New("CRL URL cannot be empty") | ||
} | ||
|
||
if f.Cache != nil { | ||
bundle, err := f.Cache.Get(ctx, url) | ||
if err == nil { | ||
// check expiry | ||
nextUpdate := bundle.BaseCRL.NextUpdate | ||
if !nextUpdate.IsZero() && !time.Now().After(nextUpdate) { | ||
// check expiry of base CRL and delta CRL | ||
if (bundle.BaseCRL != nil && isEffective(bundle.BaseCRL)) && | ||
(bundle.DeltaCRL == nil || isEffective(bundle.DeltaCRL)) { | ||
return bundle, nil | ||
} | ||
} else if !errors.Is(err, ErrCacheMiss) && !f.DiscardCacheError { | ||
return nil, fmt.Errorf("failed to retrieve CRL from cache: %w", err) | ||
} | ||
} | ||
|
||
bundle, err := f.fetch(ctx, url) | ||
bundle, err := f.fetch(ctx, url, certificateExtensions) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to retrieve CRL: %w", err) | ||
} | ||
|
@@ -109,27 +126,113 @@ | |
return bundle, nil | ||
} | ||
|
||
// isEffective checks if the CRL is effective by checking the NextUpdate time. | ||
func isEffective(crl *x509.RevocationList) bool { | ||
return !crl.NextUpdate.IsZero() && !time.Now().After(crl.NextUpdate) | ||
} | ||
|
||
// fetch downloads the CRL from the given URL. | ||
func (f *HTTPFetcher) fetch(ctx context.Context, url string) (*Bundle, error) { | ||
func (f *HTTPFetcher) fetch(ctx context.Context, url string, certificateExtensions *[]pkix.Extension) (*Bundle, error) { | ||
// fetch base CRL | ||
base, err := fetchCRL(ctx, url, f.httpClient) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// check delta CRL | ||
// TODO: support delta CRL https://github.com/notaryproject/notation-core-go/issues/228 | ||
for _, ext := range base.Extensions { | ||
if ext.Id.Equal(oidFreshestCRL) { | ||
return nil, errors.New("delta CRL is not supported") | ||
// fetch delta CRL from CRL extension | ||
deltaCRL, err := f.processDeltaCRL(&base.Extensions) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if certificateExtensions != nil && deltaCRL == nil { | ||
// fallback to fetch delta CRL from certificate extension | ||
deltaCRL, err = f.processDeltaCRL(certificateExtensions) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return &Bundle{ | ||
BaseCRL: base, | ||
BaseCRL: base, | ||
DeltaCRL: deltaCRL, | ||
}, nil | ||
} | ||
|
||
func (f *HTTPFetcher) processDeltaCRL(extensions *[]pkix.Extension) (*x509.RevocationList, error) { | ||
for _, ext := range *extensions { | ||
if ext.Id.Equal(oidFreshestCRL) { | ||
cdp, err := parseFreshestCRL(ext) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse Freshest CRL extension: %w", err) | ||
} | ||
if len(cdp) == 0 { | ||
return nil, nil | ||
} | ||
if len(cdp) > 1 { | ||
// to simplify the implementation, we only support one | ||
// Freshest CRL URL for now which is the common case | ||
return nil, errors.New("multiple Freshest CRL distribution points are not supported") | ||
} | ||
return fetchCRL(context.Background(), cdp[0], f.httpClient) | ||
} | ||
} | ||
return nil, nil | ||
} | ||
|
||
// parseFreshestCRL parses the Freshest CRL extension and returns the CRL URLs | ||
func parseFreshestCRL(ext pkix.Extension) ([]string, error) { | ||
var cdp []string | ||
// RFC 5280, 4.2.1.15 | ||
// id-ce-freshestCRL OBJECT IDENTIFIER ::= { id-ce 46 } | ||
// | ||
// FreshestCRL ::= CRLDistributionPoints | ||
// | ||
// RFC 5280, 4.2.1.13 | ||
// | ||
// CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint | ||
// | ||
// DistributionPoint ::= SEQUENCE { | ||
// distributionPoint [0] DistributionPointName OPTIONAL, | ||
// reasons [1] ReasonFlags OPTIONAL, | ||
// cRLIssuer [2] GeneralNames OPTIONAL } | ||
// | ||
// DistributionPointName ::= CHOICE { | ||
// fullName [0] GeneralNames, | ||
// nameRelativeToCRLIssuer [1] RelativeDistinguishedName } | ||
val := cryptobyte.String(ext.Value) | ||
if !val.ReadASN1(&val, cryptobyte_asn1.SEQUENCE) { | ||
return nil, errors.New("x509: invalid CRL distribution points") | ||
} | ||
for !val.Empty() { | ||
var dpDER cryptobyte.String | ||
if !val.ReadASN1(&dpDER, cryptobyte_asn1.SEQUENCE) { | ||
return nil, errors.New("x509: invalid CRL distribution point") | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those lines can hardly be triggered. |
||
var dpNameDER cryptobyte.String | ||
var dpNamePresent bool | ||
if !dpDER.ReadOptionalASN1(&dpNameDER, &dpNamePresent, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { | ||
return nil, errors.New("x509: invalid CRL distribution point") | ||
} | ||
if !dpNamePresent { | ||
continue | ||
} | ||
if !dpNameDER.ReadASN1(&dpNameDER, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { | ||
return nil, errors.New("x509: invalid CRL distribution point") | ||
} | ||
for !dpNameDER.Empty() { | ||
if !dpNameDER.PeekASN1Tag(cryptobyte_asn1.Tag(6).ContextSpecific()) { | ||
break | ||
} | ||
var uri cryptobyte.String | ||
if !dpNameDER.ReadASN1(&uri, cryptobyte_asn1.Tag(6).ContextSpecific()) { | ||
return nil, errors.New("x509: invalid CRL distribution point") | ||
} | ||
cdp = append(cdp, string(uri)) | ||
} | ||
} | ||
return cdp, nil | ||
} | ||
|
||
func fetchCRL(ctx context.Context, crlURL string, client *http.Client) (*x509.RevocationList, error) { | ||
// validate URL | ||
parsedURL, err := url.Parse(crlURL) | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There 2 sources of Delta CRL URL:
This interface enable user to pass a certificate extension to the fetcher for accessing the delta CRL.