Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: notaryproject/notation-core-go
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.2.0
Choose a base ref
...
head repository: notaryproject/notation-core-go
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 2 commits
  • 19 files changed
  • 1 contributor

Commits on Jan 21, 2025

  1. feat: delta CRL (#247)

    Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
    JeyJeyGao authored Jan 21, 2025
    Copy the full SHA
    7510083 View commit details

Commits on Feb 21, 2025

  1. bump: update go v1.23 (#260)

    Bump:
    - updated go v1.23
    
    ---------
    
    Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
    JeyJeyGao authored Feb 21, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    441bbe8 View commit details
2 changes: 1 addition & 1 deletion .github/workflows/reusable-build.yml
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ["1.22", "1.23"]
go-version: ["1.23", "1.24"]
fail-fast: true
steps:
- name: Checkout
2 changes: 1 addition & 1 deletion .github/workflows/reusable-codeql.yml
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ jobs:
security-events: write
strategy:
matrix:
go-version: ["1.22", "1.23"]
go-version: ["1.23", "1.24"]
fail-fast: false
steps:
- name: Checkout repository
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/notaryproject/notation-core-go

go 1.22
go 1.23.0

require (
github.com/fxamacker/cbor/v2 v2.7.0
3 changes: 3 additions & 0 deletions revocation/crl/errors.go
Original file line number Diff line number Diff line change
@@ -17,3 +17,6 @@ import "errors"

// ErrCacheMiss is returned when a cache miss occurs.
var ErrCacheMiss = errors.New("cache miss")

// errDeltaCRLNotFound is returned when a delta CRL is not found.
var errDeltaCRLNotFound = errors.New("delta CRL not found")
119 changes: 109 additions & 10 deletions revocation/crl/fetcher.go
Original file line number Diff line number Diff line change
@@ -18,13 +18,18 @@ package crl
import (
"context"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/notaryproject/notation-core-go/revocation/internal/x509util"
"golang.org/x/crypto/cryptobyte"
cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
)

// oidFreshestCRL is the object identifier for the distribution point
@@ -84,9 +89,8 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*Bundle, error) {
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 isEffective(bundle.BaseCRL) && (bundle.DeltaCRL == nil || isEffective(bundle.DeltaCRL)) {
return bundle, nil
}
} else if !errors.Is(err, ErrCacheMiss) && !f.DiscardCacheError {
@@ -109,6 +113,11 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*Bundle, error) {
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) {
// fetch base CRL
@@ -117,19 +126,109 @@ func (f *HTTPFetcher) fetch(ctx context.Context, url string) (*Bundle, error) {
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 base CRL extension
deltaCRL, err := f.fetchDeltaCRL(ctx, base.Extensions)
if err != nil && !errors.Is(err, errDeltaCRLNotFound) {
return nil, err
}

return &Bundle{
BaseCRL: base,
BaseCRL: base,
DeltaCRL: deltaCRL,
}, nil
}

// fetchDeltaCRL fetches the delta CRL from the given extensions of base CRL.
//
// It returns errDeltaCRLNotFound if the delta CRL is not found.
func (f *HTTPFetcher) fetchDeltaCRL(ctx context.Context, extensions []pkix.Extension) (*x509.RevocationList, error) {
extension := x509util.FindExtensionByOID(extensions, oidFreshestCRL)
if extension == nil {
return nil, errDeltaCRLNotFound
}

// RFC 5280, 4.2.1.15
// id-ce-freshestCRL OBJECT IDENTIFIER ::= { id-ce 46 }
//
// FreshestCRL ::= CRLDistributionPoints
urls, err := parseCRLDistributionPoint(extension.Value)
if err != nil {
return nil, fmt.Errorf("failed to parse Freshest CRL extension: %w", err)
}
if len(urls) == 0 {
return nil, errDeltaCRLNotFound
}

var (
lastError error
deltaCRL *x509.RevocationList
)
for _, cdpURL := range urls {
// RFC 5280, 5.2.6
// Delta CRLs from the base CRL have the same scope as the base
// CRL, so the URLs are for redundancy and should be tried in
// order until one succeeds.
deltaCRL, lastError = fetchCRL(ctx, cdpURL, f.httpClient)
if lastError == nil {
return deltaCRL, nil
}
}
return nil, lastError
}

// parseCRLDistributionPoint parses the CRL extension and returns the CRL URLs
//
// value is the raw value of the CRL distribution point extension
func parseCRLDistributionPoint(value []byte) ([]string, error) {
var urls []string
// borrowed from crypto/x509: https://cs.opensource.google/go/go/+/refs/tags/go1.23.4:src/crypto/x509/parser.go;l=700-743
//
// 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(value)
if !val.ReadASN1(&val, cbasn1.SEQUENCE) {
return nil, errors.New("x509: invalid CRL distribution points")
}
for !val.Empty() {
var dpDER cryptobyte.String
if !val.ReadASN1(&dpDER, cbasn1.SEQUENCE) {
return nil, errors.New("x509: invalid CRL distribution point")
}
var dpNameDER cryptobyte.String
var dpNamePresent bool
if !dpDER.ReadOptionalASN1(&dpNameDER, &dpNamePresent, cbasn1.Tag(0).Constructed().ContextSpecific()) {
return nil, errors.New("x509: invalid CRL distribution point")
}
if !dpNamePresent {
continue
}
if !dpNameDER.ReadASN1(&dpNameDER, cbasn1.Tag(0).Constructed().ContextSpecific()) {
return nil, errors.New("x509: invalid CRL distribution point")
}
for !dpNameDER.Empty() {
if !dpNameDER.PeekASN1Tag(cbasn1.Tag(6).ContextSpecific()) {
break
}
var uri cryptobyte.String
if !dpNameDER.ReadASN1(&uri, cbasn1.Tag(6).ContextSpecific()) {
return nil, errors.New("x509: invalid CRL distribution point")
}
urls = append(urls, string(uri))
}
}
return urls, nil
}

func fetchCRL(ctx context.Context, crlURL string, client *http.Client) (*x509.RevocationList, error) {
// validate URL
parsedURL, err := url.Parse(crlURL)
Loading