Compare commits
15 Commits
v1.2.0-rc.
...
main
Author | SHA1 | Date |
---|---|---|
|
a9c5e3f1a5 | |
|
ef8789627f | |
|
46726d8697 | |
|
b85e8f7e65 | |
|
4d73532534 | |
|
6a378d5686 | |
|
ea37e4e6c3 | |
|
fcf45123a3 | |
|
9c4662fe2b | |
|
441bbe882a | |
|
751008360b | |
|
b07b0ef090 | |
|
2bf73c7f71 | |
|
0c60ea723a | |
|
030abc293c |
|
@ -24,7 +24,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.22", "1.23"]
|
go-version: ["1.23", "1.24"]
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
security-events: write
|
security-events: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.22", "1.23"]
|
go-version: ["1.23", "1.24"]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|
|
@ -23,13 +23,13 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Check license header
|
- name: Check license header
|
||||||
uses: apache/skywalking-eyes/header@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91
|
uses: apache/skywalking-eyes/header@5c5b974209f0de5d905f37deb69369068ebfc15c
|
||||||
|
|
||||||
with:
|
with:
|
||||||
mode: check
|
mode: check
|
||||||
config: .github/licenserc.yml
|
config: .github/licenserc.yml
|
||||||
- name: Check dependencies license
|
- name: Check dependencies license
|
||||||
uses: apache/skywalking-eyes/dependency@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91
|
uses: apache/skywalking-eyes/dependency@5c5b974209f0de5d905f37deb69369068ebfc15c
|
||||||
with:
|
with:
|
||||||
config: .github/licenserc.yml
|
config: .github/licenserc.yml
|
||||||
flags:
|
flags:
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Repo-Level Owners (in alphabetical order)
|
# Repo-Level Owners (in alphabetical order)
|
||||||
# Note: This is only for the notaryproject/notation-core-go repo
|
# Note: This is only for the notaryproject/notation-core-go repo
|
||||||
* @gokarnm @JeyJeyGao @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1
|
* @gokarnm @niazfk @priteshbandi @rgnote @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1
|
||||||
|
|
|
@ -10,7 +10,6 @@ Yi Zha <yizha1@microsoft.com> (@yizha1)
|
||||||
|
|
||||||
# Repo-Level Maintainers (in alphabetical order)
|
# Repo-Level Maintainers (in alphabetical order)
|
||||||
# Note: This is for the notaryproject/notation-core-go repo
|
# Note: This is for the notaryproject/notation-core-go repo
|
||||||
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)
|
|
||||||
Milind Gokarn <gokarnm@amazon.com> (@gokarnm)
|
Milind Gokarn <gokarnm@amazon.com> (@gokarnm)
|
||||||
Patrick Zheng <patrickzheng@microsoft.com> (@Two-Hearts)
|
Patrick Zheng <patrickzheng@microsoft.com> (@Two-Hearts)
|
||||||
Rakesh Gariganti <garigant@amazon.com> (@rgnote)
|
Rakesh Gariganti <garigant@amazon.com> (@rgnote)
|
||||||
|
@ -18,3 +17,6 @@ Rakesh Gariganti <garigant@amazon.com> (@rgnote)
|
||||||
# Emeritus Org Maintainers (in alphabetical order)
|
# Emeritus Org Maintainers (in alphabetical order)
|
||||||
Justin Cormack <justin.cormack@docker.com> (@justincormack)
|
Justin Cormack <justin.cormack@docker.com> (@justincormack)
|
||||||
Steve Lasker <StevenLasker@hotmail.com> (@stevelasker)
|
Steve Lasker <StevenLasker@hotmail.com> (@stevelasker)
|
||||||
|
|
||||||
|
# Emeritus Repo-Level Maintainers (in alphabetical order)
|
||||||
|
Junjie Gao <junjiegao@microsoft.com> (@JeyJeyGao)
|
2
Makefile
2
Makefile
|
@ -29,7 +29,7 @@ clean:
|
||||||
.PHONY: check-line-endings
|
.PHONY: check-line-endings
|
||||||
check-line-endings: ## check line endings
|
check-line-endings: ## check line endings
|
||||||
! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF
|
! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF
|
||||||
! find scripts -name "*.sh" -type f -exec file "{}" ";" | grep CRLF
|
! find . -name "*.sh" -type f -exec file "{}" ";" | grep CRLF
|
||||||
|
|
||||||
.PHONY: fix-line-endings
|
.PHONY: fix-line-endings
|
||||||
fix-line-endings: ## fix line endings
|
fix-line-endings: ## fix line endings
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -1,13 +1,13 @@
|
||||||
module github.com/notaryproject/notation-core-go
|
module github.com/notaryproject/notation-core-go
|
||||||
|
|
||||||
go 1.22
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0
|
github.com/fxamacker/cbor/v2 v2.8.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/notaryproject/tspclient-go v1.0.0-rc.1
|
github.com/notaryproject/tspclient-go v1.0.0
|
||||||
github.com/veraison/go-cose v1.3.0
|
github.com/veraison/go-cose v1.3.0
|
||||||
golang.org/x/crypto v0.29.0
|
golang.org/x/crypto v0.37.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/x448/float16 v0.8.4 // indirect
|
require github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
|
16
go.sum
16
go.sum
|
@ -1,12 +1,12 @@
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/notaryproject/tspclient-go v1.0.0-rc.1 h1:KcHxlqg6Adt4kzGLw012i0YMLlwGwToiR129c6IQ7Ys=
|
github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4=
|
||||||
github.com/notaryproject/tspclient-go v1.0.0-rc.1/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs=
|
github.com/notaryproject/tspclient-go v1.0.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs=
|
||||||
github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
|
github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
|
||||||
github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc=
|
github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
|
|
|
@ -17,3 +17,6 @@ import "errors"
|
||||||
|
|
||||||
// ErrCacheMiss is returned when a cache miss occurs.
|
// ErrCacheMiss is returned when a cache miss occurs.
|
||||||
var ErrCacheMiss = errors.New("cache miss")
|
var ErrCacheMiss = errors.New("cache miss")
|
||||||
|
|
||||||
|
// errDeltaCRLNotFound is returned when a delta CRL is not found.
|
||||||
|
var errDeltaCRLNotFound = errors.New("delta CRL not found")
|
||||||
|
|
|
@ -18,6 +18,7 @@ package crl
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -25,6 +26,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"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
|
// 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 {
|
if f.Cache != nil {
|
||||||
bundle, err := f.Cache.Get(ctx, url)
|
bundle, err := f.Cache.Get(ctx, url)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// check expiry
|
// check expiry of base CRL and delta CRL
|
||||||
nextUpdate := bundle.BaseCRL.NextUpdate
|
if isEffective(bundle.BaseCRL) && (bundle.DeltaCRL == nil || isEffective(bundle.DeltaCRL)) {
|
||||||
if !nextUpdate.IsZero() && !time.Now().After(nextUpdate) {
|
|
||||||
return bundle, nil
|
return bundle, nil
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, ErrCacheMiss) && !f.DiscardCacheError {
|
} 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
|
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.
|
// 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) (*Bundle, error) {
|
||||||
// fetch base CRL
|
// fetch base CRL
|
||||||
|
@ -117,19 +126,109 @@ func (f *HTTPFetcher) fetch(ctx context.Context, url string) (*Bundle, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check delta CRL
|
// fetch delta CRL from base CRL extension
|
||||||
// TODO: support delta CRL https://github.com/notaryproject/notation-core-go/issues/228
|
deltaCRL, err := f.fetchDeltaCRL(ctx, base.Extensions)
|
||||||
for _, ext := range base.Extensions {
|
if err != nil && !errors.Is(err, errDeltaCRLNotFound) {
|
||||||
if ext.Id.Equal(oidFreshestCRL) {
|
return nil, err
|
||||||
return nil, errors.New("delta CRL is not supported")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Bundle{
|
return &Bundle{
|
||||||
BaseCRL: base,
|
BaseCRL: base,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
}, nil
|
}, 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) {
|
func fetchCRL(ctx context.Context, crlURL string, client *http.Client) (*x509.RevocationList, error) {
|
||||||
// validate URL
|
// validate URL
|
||||||
parsedURL, err := url.Parse(crlURL)
|
parsedURL, err := url.Parse(crlURL)
|
||||||
|
|
|
@ -19,11 +19,13 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -78,7 +80,7 @@ func TestFetch(t *testing.T) {
|
||||||
|
|
||||||
t.Run("fetch without cache", func(t *testing.T) {
|
t.Run("fetch without cache", func(t *testing.T) {
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: baseCRL.Raw},
|
Transport: &expectedRoundTripperMock{Body: baseCRL.Raw},
|
||||||
}
|
}
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -134,7 +136,7 @@ func TestFetch(t *testing.T) {
|
||||||
t.Run("cache miss", func(t *testing.T) {
|
t.Run("cache miss", func(t *testing.T) {
|
||||||
c := &memoryCache{}
|
c := &memoryCache{}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: baseCRL.Raw},
|
Transport: &expectedRoundTripperMock{Body: baseCRL.Raw},
|
||||||
}
|
}
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -177,7 +179,7 @@ func TestFetch(t *testing.T) {
|
||||||
|
|
||||||
// fetch the expired CRL
|
// fetch the expired CRL
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: baseCRL.Raw},
|
Transport: &expectedRoundTripperMock{Body: baseCRL.Raw},
|
||||||
}
|
}
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -195,46 +197,13 @@ func TestFetch(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("delta CRL is not supported", func(t *testing.T) {
|
|
||||||
c := &memoryCache{}
|
|
||||||
// prepare a CRL with refresh CRL extension
|
|
||||||
certChain := testhelper.GetRevokableRSAChainWithRevocations(2, false, true)
|
|
||||||
expiredCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
Number: big.NewInt(1),
|
|
||||||
NextUpdate: time.Now().Add(-1 * time.Hour),
|
|
||||||
ExtraExtensions: []pkix.Extension{
|
|
||||||
{
|
|
||||||
Id: oidFreshestCRL,
|
|
||||||
Value: []byte{0x01, 0x02, 0x03},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, certChain[1].Cert, certChain[1].PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create base CRL: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: expectedRoundTripperMock{Body: expiredCRLBytes},
|
|
||||||
}
|
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewHTTPFetcher() error = %v, want nil", err)
|
|
||||||
}
|
|
||||||
f.Cache = c
|
|
||||||
f.DiscardCacheError = true
|
|
||||||
_, err = f.Fetch(context.Background(), uncachedURL)
|
|
||||||
if !strings.Contains(err.Error(), "delta CRL is not supported") {
|
|
||||||
t.Errorf("Fetcher.Fetch() error = %v, want delta CRL is not supported", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Set cache error", func(t *testing.T) {
|
t.Run("Set cache error", func(t *testing.T) {
|
||||||
c := &errorCache{
|
c := &errorCache{
|
||||||
GetError: ErrCacheMiss,
|
GetError: ErrCacheMiss,
|
||||||
SetError: errors.New("cache error"),
|
SetError: errors.New("cache error"),
|
||||||
}
|
}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: baseCRL.Raw},
|
Transport: &expectedRoundTripperMock{Body: baseCRL.Raw},
|
||||||
}
|
}
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -256,7 +225,7 @@ func TestFetch(t *testing.T) {
|
||||||
GetError: errors.New("cache error"),
|
GetError: errors.New("cache error"),
|
||||||
}
|
}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: baseCRL.Raw},
|
Transport: &expectedRoundTripperMock{Body: baseCRL.Raw},
|
||||||
}
|
}
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -276,7 +245,7 @@ func TestFetch(t *testing.T) {
|
||||||
SetError: errors.New("cache error"),
|
SetError: errors.New("cache error"),
|
||||||
}
|
}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: baseCRL.Raw},
|
Transport: &expectedRoundTripperMock{Body: baseCRL.Raw},
|
||||||
}
|
}
|
||||||
f, err := NewHTTPFetcher(httpClient)
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -289,6 +258,217 @@ func TestFetch(t *testing.T) {
|
||||||
t.Errorf("Fetcher.Fetch() error = %v, want failed to store CRL to cache:", err)
|
t.Errorf("Fetcher.Fetch() error = %v, want failed to store CRL to cache:", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("test fetch delta CRL from base CRL extension failed", func(t *testing.T) {
|
||||||
|
crlWithDeltaCRL, err := os.ReadFile("testdata/crlWithMultipleFreshestCRLs.crl")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read CRL: %v", err)
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &expectedRoundTripperMock{
|
||||||
|
Body: crlWithDeltaCRL,
|
||||||
|
SecondRoundBody: []byte("invalid crl"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f, err := NewHTTPFetcher(httpClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewHTTPFetcher() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
_, err = f.Fetch(context.Background(), exampleURL)
|
||||||
|
expectedErrorMsg := "failed to retrieve CRL: x509: malformed crl"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFreshestCRL(t *testing.T) {
|
||||||
|
loadExtentsion := func(certPath string) pkix.Extension {
|
||||||
|
certData, err := os.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(certData)
|
||||||
|
if block == nil {
|
||||||
|
t.Fatalf("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range cert.Extensions {
|
||||||
|
if ext.Id.Equal([]int{2, 5, 29, 46}) { // id-ce-freshestCRL
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("freshestCRL extension not found")
|
||||||
|
return pkix.Extension{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("valid 1 delta CRL URL", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithDeltaCRL.cer"
|
||||||
|
freshestCRLExtension := loadExtentsion(certPath)
|
||||||
|
urls, err := parseCRLDistributionPoint(freshestCRLExtension.Value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse freshest CRL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(urls) != 1 {
|
||||||
|
t.Fatalf("expected 1 URL, got %d", len(urls))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(urls[0], "http://localhost:80") {
|
||||||
|
t.Fatalf("unexpected URL: %s", urls[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty extension", func(t *testing.T) {
|
||||||
|
_, err := parseCRLDistributionPoint(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("URL doesn't exist", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithZeroDeltaCRLURL.cer"
|
||||||
|
freshestCRLExtension := loadExtentsion(certPath)
|
||||||
|
url, err := parseCRLDistributionPoint(freshestCRLExtension.Value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse freshest CRL: %v", err)
|
||||||
|
}
|
||||||
|
if len(url) != 0 {
|
||||||
|
t.Fatalf("expected 0 URL, got %d", len(url))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non URI freshest CRL extension", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithNonURIDeltaCRL.cer"
|
||||||
|
freshestCRLExtension := loadExtentsion(certPath)
|
||||||
|
url, err := parseCRLDistributionPoint(freshestCRLExtension.Value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse freshest CRL: %v", err)
|
||||||
|
}
|
||||||
|
if len(url) != 0 {
|
||||||
|
t.Fatalf("expected 0 URL, got %d", len(url))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("certificate with incomplete freshest CRL extension", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithIncompleteFreshestCRL.cer"
|
||||||
|
freshestCRLExtension := loadExtentsion(certPath)
|
||||||
|
_, err := parseCRLDistributionPoint(freshestCRLExtension.Value)
|
||||||
|
expectErrorMsg := "x509: invalid CRL distribution point"
|
||||||
|
if err == nil || err.Error() != expectErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("certificate with incomplete freshest CRL extension2", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithIncompleteFreshestCRL2.cer"
|
||||||
|
freshestCRLExtension := loadExtentsion(certPath)
|
||||||
|
url, err := parseCRLDistributionPoint(freshestCRLExtension.Value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse freshest CRL: %v", err)
|
||||||
|
}
|
||||||
|
if len(url) != 0 {
|
||||||
|
t.Fatalf("expected 0 URL, got %d", len(url))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchDeltaCRL(t *testing.T) {
|
||||||
|
loadExtentsion := func(certPath string) []pkix.Extension {
|
||||||
|
certData, err := os.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(certData)
|
||||||
|
if block == nil {
|
||||||
|
t.Fatalf("failed to decode PEM block")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert.Extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaCRL, err := os.ReadFile("testdata/delta.crl")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read delta CRL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher, err := NewHTTPFetcher(&http.Client{
|
||||||
|
Transport: &expectedRoundTripperMock{Body: deltaCRL},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create fetcher: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("parse freshest CRL failed", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithIncompleteFreshestCRL.cer"
|
||||||
|
extensions := loadExtentsion(certPath)
|
||||||
|
_, err := fetcher.fetchDeltaCRL(context.Background(), extensions)
|
||||||
|
expectedErrorMsg := "failed to parse Freshest CRL extension: x509: invalid CRL distribution point"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("zero freshest CRL URL", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithZeroDeltaCRLURL.cer"
|
||||||
|
extensions := loadExtentsion(certPath)
|
||||||
|
_, err := fetcher.fetchDeltaCRL(context.Background(), extensions)
|
||||||
|
expectedErr := errDeltaCRLNotFound
|
||||||
|
if err == nil || !errors.Is(err, expectedErr) {
|
||||||
|
t.Fatalf("expected error %v, got %v", expectedErr, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one freshest CRL URL", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithDeltaCRL.cer"
|
||||||
|
extensions := loadExtentsion(certPath)
|
||||||
|
deltaCRL, err := fetcher.fetchDeltaCRL(context.Background(), extensions)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to process delta CRL: %v", err)
|
||||||
|
}
|
||||||
|
if deltaCRL == nil {
|
||||||
|
t.Fatalf("expected non-nil delta CRL")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple freshest CRL URLs failed", func(t *testing.T) {
|
||||||
|
fetcherWithError, err := NewHTTPFetcher(&http.Client{
|
||||||
|
Transport: errorRoundTripperMock{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create fetcher: %v", err)
|
||||||
|
}
|
||||||
|
certPath := "testdata/certificateWith2DeltaCRL.cer"
|
||||||
|
extensions := loadExtentsion(certPath)
|
||||||
|
_, err = fetcherWithError.fetchDeltaCRL(context.Background(), extensions)
|
||||||
|
expectedErrorMsg := "request failed"
|
||||||
|
if err == nil || !strings.Contains(err.Error(), expectedErrorMsg) {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("process delta crl from certificate extension failed", func(t *testing.T) {
|
||||||
|
certPath := "testdata/certificateWithIncompleteFreshestCRL.cer"
|
||||||
|
extensions := loadExtentsion(certPath)
|
||||||
|
_, err := fetcher.fetchDeltaCRL(context.Background(), extensions)
|
||||||
|
expectedErrorMsg := "failed to parse Freshest CRL extension: x509: invalid CRL distribution point"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDownload(t *testing.T) {
|
func TestDownload(t *testing.T) {
|
||||||
|
@ -343,7 +523,7 @@ func TestDownload(t *testing.T) {
|
||||||
|
|
||||||
t.Run("exceed the size limit", func(t *testing.T) {
|
t.Run("exceed the size limit", func(t *testing.T) {
|
||||||
_, err := fetchCRL(context.Background(), "http://localhost.test", &http.Client{
|
_, err := fetchCRL(context.Background(), "http://localhost.test", &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: make([]byte, maxCRLSize+1)},
|
Transport: &expectedRoundTripperMock{Body: make([]byte, maxCRLSize+1)},
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
|
@ -352,7 +532,7 @@ func TestDownload(t *testing.T) {
|
||||||
|
|
||||||
t.Run("invalid crl", func(t *testing.T) {
|
t.Run("invalid crl", func(t *testing.T) {
|
||||||
_, err := fetchCRL(context.Background(), "http://localhost.test", &http.Client{
|
_, err := fetchCRL(context.Background(), "http://localhost.test", &http.Client{
|
||||||
Transport: expectedRoundTripperMock{Body: []byte("invalid crl")},
|
Transport: &expectedRoundTripperMock{Body: []byte("invalid crl")},
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
|
@ -396,15 +576,25 @@ func (r errorReaderMock) Close() error {
|
||||||
|
|
||||||
type expectedRoundTripperMock struct {
|
type expectedRoundTripperMock struct {
|
||||||
Body []byte
|
Body []byte
|
||||||
|
SecondRoundBody []byte
|
||||||
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt expectedRoundTripperMock) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (rt *expectedRoundTripperMock) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if rt.count == 0 {
|
||||||
|
rt.count += 1
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
Request: req,
|
Request: req,
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewBuffer(rt.Body)),
|
Body: io.NopCloser(bytes.NewBuffer(rt.Body)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
return &http.Response{
|
||||||
|
Request: req,
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBuffer(rt.SecondRoundBody)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// memoryCache is an in-memory cache that stores CRL bundles for testing.
|
// memoryCache is an in-memory cache that stores CRL bundles for testing.
|
||||||
type memoryCache struct {
|
type memoryCache struct {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFdTCCA92gAwIBAgIUaHnHWrIVx0qzAZIwvELkQUEs+3cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTEjMCEGCgmSJomT8ixkAQEME2MtMG53d296cXZhd3ZhbzNlY2gxFTATBgNV
|
||||||
|
BAMMDE1hbmFnZW1lbnRDQTEjMCEGA1UECgwaRUpCQ0EgQ29udGFpbmVyIFF1aWNr
|
||||||
|
c3RhcnQwHhcNMjQxMTI1MDc1NzI3WhcNMjYxMTI1MDc1NzI2WjAYMRYwFAYDVQQD
|
||||||
|
DA1Ob3RhdGlvblRlc3QzMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAuJlXlrTm
|
||||||
|
VhEiLz75HgJlZGm0TE7W/7mYn0m03vV+9tBEA/ZV50ACkMDY0ewxxh4Ko6UsGq71
|
||||||
|
E466hSVggiaYaE4AdbL5kAolsnEm9/EDfYQNfgiQw0BI7axri9tJ19yZhj4Es31+
|
||||||
|
j4RJHAydB4i/qJi6T8cITdT6ViyzWM6AWGl7yRajggJ0MIICcDAMBgNVHRMBAf8E
|
||||||
|
AjAAMB8GA1UdIwQYMBaAFFPceeG3G4d5oezFSybFwehynbPqMIIBTQYDVR0uBIIB
|
||||||
|
RDCCAUAwggE8oIIBOKCCATSGgZdodHRwOi8vbG9jYWxob3N0OjgwL2VqYmNhL3B1
|
||||||
|
YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1kZWx0YWNybCZpc3N1ZXI9VUlE
|
||||||
|
JTNEYy0wbnd3b3pxdmF3dmFvM2VjaCUyQ0NOJTNETWFuYWdlbWVudENBJTJDTyUz
|
||||||
|
REVKQkNBK0NvbnRhaW5lcitRdWlja3N0YXJ0hoGXaHR0cDovL2xvY2FsaG9zdDo4
|
||||||
|
MC9lamJjYS9wdWJsaWN3ZWIvd2ViZGlzdC9jZXJ0ZGlzdD9jbWQ9ZGVsdGFjcmwm
|
||||||
|
aXNzdWVyPVVJRCUzRGMtMG53d296cXZhd3ZhbzNlY2glMkNDTiUzRE1hbmFnZW1l
|
||||||
|
bnRDQSUyQ08lM0RFSkJDQStDb250YWluZXIrUXVpY2tzdGFydDATBgNVHSUEDDAK
|
||||||
|
BggrBgEFBQcDATCBqQYDVR0fBIGhMIGeMIGboIGYoIGVhoGSaHR0cDovL2xvY2Fs
|
||||||
|
aG9zdDo4MC9lamJjYS9wdWJsaWN3ZWIvd2ViZGlzdC9jZXJ0ZGlzdD9jbWQ9Y3Js
|
||||||
|
Jmlzc3Vlcj1VSUQlM0RjLTBud3dvenF2YXd2YW8zZWNoJTJDQ04lM0RNYW5hZ2Vt
|
||||||
|
ZW50Q0ElMkNPJTNERUpCQ0ErQ29udGFpbmVyK1F1aWNrc3RhcnQwHQYDVR0OBBYE
|
||||||
|
FDHE82/06xOocYbvMIyGt2gofk88MA4GA1UdDwEB/wQEAwIFoDANBgkqhkiG9w0B
|
||||||
|
AQsFAAOCAYEAV7G7WMPn3tQNqB8RxYATV3eVhB3WC5BxqBbQzp2loNycDRmX95fa
|
||||||
|
7EV5xcPIUv42B+TzLu/ann9FLOMkqEhA+F5zsEomikUA+L4cuIWLXUhwIWwE2I/p
|
||||||
|
fgHJ61JtMMxv3rWQHyo6YpzpIAG23oxGXzrlN4/oNfWzWMIYlcl4xiHxC2vOKnNO
|
||||||
|
wId3Ck3jsJE10tImdD/tQYXh7h5ueESyPUZtqM/g2QPap+tEHArpgfAQdpEvRj1v
|
||||||
|
ZWAotcEIr+a5popE56UaE4a29DspVTA1rVchhKYl2gpDxieSQgQr61fWHXzRoKcd
|
||||||
|
FZ+NgqJuwd9CxrXbkl6EKDpefivwz9G4b3b6R8lMl+wmeTgPvSUYO34c8GsF143H
|
||||||
|
V/VKNoBvoz44QyLUf+1+XiHuUjfHaXCtXmDOoQ64M3d9gGrz23G5KJAUBubcbcdu
|
||||||
|
7Ah3D5zqvieGgoYt8qye1nsIVYC1KrYP2Kp5jWCadLvIwu2B0j7eA+LwN2MBWdlh
|
||||||
|
dRTSOVkICjWU
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE1TCCAz2gAwIBAgIUaHnHWrIVx0qzAZIwvELkQUEs+3cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTEjMCEGCgmSJomT8ixkAQEME2MtMG53d296cXZhd3ZhbzNlY2gxFTATBgNV
|
||||||
|
BAMMDE1hbmFnZW1lbnRDQTEjMCEGA1UECgwaRUpCQ0EgQ29udGFpbmVyIFF1aWNr
|
||||||
|
c3RhcnQwHhcNMjQxMTI1MDc1NzI3WhcNMjYxMTI1MDc1NzI2WjAYMRYwFAYDVQQD
|
||||||
|
DA1Ob3RhdGlvblRlc3QzMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAuJlXlrTm
|
||||||
|
VhEiLz75HgJlZGm0TE7W/7mYn0m03vV+9tBEA/ZV50ACkMDY0ewxxh4Ko6UsGq71
|
||||||
|
E466hSVggiaYaE4AdbL5kAolsnEm9/EDfYQNfgiQw0BI7axri9tJ19yZhj4Es31+
|
||||||
|
j4RJHAydB4i/qJi6T8cITdT6ViyzWM6AWGl7yRajggHUMIIB0DAMBgNVHRMBAf8E
|
||||||
|
AjAAMB8GA1UdIwQYMBaAFFPceeG3G4d5oezFSybFwehynbPqMIGuBgNVHS4EgaYw
|
||||||
|
gaMwgaCggZ2ggZqGgZdodHRwOi8vbG9jYWxob3N0OjgwL2VqYmNhL3B1YmxpY3dl
|
||||||
|
Yi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1kZWx0YWNybCZpc3N1ZXI9VUlEJTNEYy0w
|
||||||
|
bnd3b3pxdmF3dmFvM2VjaCUyQ0NOJTNETWFuYWdlbWVudENBJTJDTyUzREVKQkNB
|
||||||
|
K0NvbnRhaW5lcitRdWlja3N0YXJ0MBMGA1UdJQQMMAoGCCsGAQUFBwMBMIGpBgNV
|
||||||
|
HR8EgaEwgZ4wgZuggZiggZWGgZJodHRwOi8vbG9jYWxob3N0OjgwL2VqYmNhL3B1
|
||||||
|
YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1jcmwmaXNzdWVyPVVJRCUzRGMt
|
||||||
|
MG53d296cXZhd3ZhbzNlY2glMkNDTiUzRE1hbmFnZW1lbnRDQSUyQ08lM0RFSkJD
|
||||||
|
QStDb250YWluZXIrUXVpY2tzdGFydDAdBgNVHQ4EFgQUMcTzb/TrE6hxhu8wjIa3
|
||||||
|
aCh+TzwwDgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBgQBXsbtYw+fe
|
||||||
|
1A2oHxHFgBNXd5WEHdYLkHGoFtDOnaWg3JwNGZf3l9rsRXnFw8hS/jYH5PMu79qe
|
||||||
|
f0Us4ySoSED4XnOwSiaKRQD4vhy4hYtdSHAhbATYj+l+AcnrUm0wzG/etZAfKjpi
|
||||||
|
nOkgAbbejEZfOuU3j+g19bNYwhiVyXjGIfELa84qc07Ah3cKTeOwkTXS0iZ0P+1B
|
||||||
|
heHuHm54RLI9Rm2oz+DZA9qn60QcCumB8BB2kS9GPW9lYCi1wQiv5rmmikTnpRoT
|
||||||
|
hrb0OylVMDWtVyGEpiXaCkPGJ5JCBCvrV9YdfNGgpx0Vn42Com7B30LGtduSXoQo
|
||||||
|
Ol5+K/DP0bhvdvpHyUyX7CZ5OA+9JRg7fhzwawXXjcdX9Uo2gG+jPjhDItR/7X5e
|
||||||
|
Ie5SN8dpcK1eYM6hDrgzd32AavPbcbkokBQG5txtx27sCHcPnOq+J4aChi3yrJ7W
|
||||||
|
ewhVgLUqtg/YqnmNYJp0u8jC7YHSPt4D4vA3YwFZ2WF1FNI5WQgKNZQ=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDmTCCAgGgAwIBAgIUaHnHWrIVx0qzAZIwvELkQUEs+3cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTEjMCEGCgmSJomT8ixkAQEME2MtMG53d296cXZhd3ZhbzNlY2gxFTATBgNV
|
||||||
|
BAMMDE1hbmFnZW1lbnRDQTEjMCEGA1UECgwaRUpCQ0EgQ29udGFpbmVyIFF1aWNr
|
||||||
|
c3RhcnQwHhcNMjQxMTI1MDc1NzI3WhcNMjYxMTI1MDc1NzI2WjAYMRYwFAYDVQQD
|
||||||
|
DA1Ob3RhdGlvblRlc3QzMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAuJlXlrTm
|
||||||
|
VhEiLz75HgJlZGm0TE7W/7mYn0m03vV+9tBEA/ZV50ACkMDY0ewxxh4Ko6UsGq71
|
||||||
|
E466hSVggiaYaE4AdbL5kAolsnEm9/EDfYQNfgiQw0BI7axri9tJ19yZhj4Es31+
|
||||||
|
j4RJHAydB4i/qJi6T8cITdT6ViyzWM6AWGl7yRajgZkwgZYwDAYDVR0TAQH/BAIw
|
||||||
|
ADAfBgNVHSMEGDAWgBRT3HnhtxuHeaHsxUsmxcHocp2z6jANBgNVHS4EBjAEMAKg
|
||||||
|
ADATBgNVHSUEDDAKBggrBgEFBQcDATASBgNVHR8ECzAJMAegBaADhQH/MB0GA1Ud
|
||||||
|
DgQWBBQxxPNv9OsTqHGG7zCMhrdoKH5PPDAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZI
|
||||||
|
hvcNAQELBQADggGBAFexu1jD597UDagfEcWAE1d3lYQd1guQcagW0M6dpaDcnA0Z
|
||||||
|
l/eX2uxFecXDyFL+Ngfk8y7v2p5/RSzjJKhIQPhec7BKJopFAPi+HLiFi11IcCFs
|
||||||
|
BNiP6X4ByetSbTDMb961kB8qOmKc6SABtt6MRl865TeP6DX1s1jCGJXJeMYh8Qtr
|
||||||
|
zipzTsCHdwpN47CRNdLSJnQ/7UGF4e4ebnhEsj1GbajP4NkD2qfrRBwK6YHwEHaR
|
||||||
|
L0Y9b2VgKLXBCK/muaaKROelGhOGtvQ7KVUwNa1XIYSmJdoKQ8YnkkIEK+tX1h18
|
||||||
|
0aCnHRWfjYKibsHfQsa125JehCg6Xn4r8M/RuG92+kfJTJfsJnk4D70lGDt+HPBr
|
||||||
|
BdeNx1f1SjaAb6M+OEMi1H/tfl4h7lI3x2lwrV5gzqEOuDN3fYBq89txuSiQFAbm
|
||||||
|
3G3HbuwIdw+c6r4nhoKGLfKsntZ7CFWAtSq2D9iqeY1gmnS7yMLtgdI+3gPi8Ddj
|
||||||
|
AVnZYXUU0jlZCAo1lA==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDlzCCAf+gAwIBAgIUaHnHWrIVx0qzAZIwvELkQUEs+3cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTEjMCEGCgmSJomT8ixkAQEME2MtMG53d296cXZhd3ZhbzNlY2gxFTATBgNV
|
||||||
|
BAMMDE1hbmFnZW1lbnRDQTEjMCEGA1UECgwaRUpCQ0EgQ29udGFpbmVyIFF1aWNr
|
||||||
|
c3RhcnQwHhcNMjQxMTI1MDc1NzI3WhcNMjYxMTI1MDc1NzI2WjAYMRYwFAYDVQQD
|
||||||
|
DA1Ob3RhdGlvblRlc3QzMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAuJlXlrTm
|
||||||
|
VhEiLz75HgJlZGm0TE7W/7mYn0m03vV+9tBEA/ZV50ACkMDY0ewxxh4Ko6UsGq71
|
||||||
|
E466hSVggiaYaE4AdbL5kAolsnEm9/EDfYQNfgiQw0BI7axri9tJ19yZhj4Es31+
|
||||||
|
j4RJHAydB4i/qJi6T8cITdT6ViyzWM6AWGl7yRajgZcwgZQwDAYDVR0TAQH/BAIw
|
||||||
|
ADAfBgNVHSMEGDAWgBRT3HnhtxuHeaHsxUsmxcHocp2z6jALBgNVHS4EBDACMAAw
|
||||||
|
EwYDVR0lBAwwCgYIKwYBBQUHAwEwEgYDVR0fBAswCTAHoAWgA4UB/zAdBgNVHQ4E
|
||||||
|
FgQUMcTzb/TrE6hxhu8wjIa3aCh+TzwwDgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3
|
||||||
|
DQEBCwUAA4IBgQBXsbtYw+fe1A2oHxHFgBNXd5WEHdYLkHGoFtDOnaWg3JwNGZf3
|
||||||
|
l9rsRXnFw8hS/jYH5PMu79qef0Us4ySoSED4XnOwSiaKRQD4vhy4hYtdSHAhbATY
|
||||||
|
j+l+AcnrUm0wzG/etZAfKjpinOkgAbbejEZfOuU3j+g19bNYwhiVyXjGIfELa84q
|
||||||
|
c07Ah3cKTeOwkTXS0iZ0P+1BheHuHm54RLI9Rm2oz+DZA9qn60QcCumB8BB2kS9G
|
||||||
|
PW9lYCi1wQiv5rmmikTnpRoThrb0OylVMDWtVyGEpiXaCkPGJ5JCBCvrV9YdfNGg
|
||||||
|
px0Vn42Com7B30LGtduSXoQoOl5+K/DP0bhvdvpHyUyX7CZ5OA+9JRg7fhzwawXX
|
||||||
|
jcdX9Uo2gG+jPjhDItR/7X5eIe5SN8dpcK1eYM6hDrgzd32AavPbcbkokBQG5txt
|
||||||
|
x27sCHcPnOq+J4aChi3yrJ7WewhVgLUqtg/YqnmNYJp0u8jC7YHSPt4D4vA3YwFZ
|
||||||
|
2WF1FNI5WQgKNZQ=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDnjCCAgagAwIBAgIUaHnHWrIVx0qzAZIwvELkQUEs+3cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTEjMCEGCgmSJomT8ixkAQEME2MtMG53d296cXZhd3ZhbzNlY2gxFTATBgNV
|
||||||
|
BAMMDE1hbmFnZW1lbnRDQTEjMCEGA1UECgwaRUpCQ0EgQ29udGFpbmVyIFF1aWNr
|
||||||
|
c3RhcnQwHhcNMjQxMTI1MDc1NzI3WhcNMjYxMTI1MDc1NzI2WjAYMRYwFAYDVQQD
|
||||||
|
DA1Ob3RhdGlvblRlc3QzMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAuJlXlrTm
|
||||||
|
VhEiLz75HgJlZGm0TE7W/7mYn0m03vV+9tBEA/ZV50ACkMDY0ewxxh4Ko6UsGq71
|
||||||
|
E466hSVggiaYaE4AdbL5kAolsnEm9/EDfYQNfgiQw0BI7axri9tJ19yZhj4Es31+
|
||||||
|
j4RJHAydB4i/qJi6T8cITdT6ViyzWM6AWGl7yRajgZ4wgZswDAYDVR0TAQH/BAIw
|
||||||
|
ADAfBgNVHSMEGDAWgBRT3HnhtxuHeaHsxUsmxcHocp2z6jASBgNVHS4ECzAJMAeg
|
||||||
|
BaADhQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMBMBIGA1UdHwQLMAkwB6AFoAOFAf8w
|
||||||
|
HQYDVR0OBBYEFDHE82/06xOocYbvMIyGt2gofk88MA4GA1UdDwEB/wQEAwIFoDAN
|
||||||
|
BgkqhkiG9w0BAQsFAAOCAYEAV7G7WMPn3tQNqB8RxYATV3eVhB3WC5BxqBbQzp2l
|
||||||
|
oNycDRmX95fa7EV5xcPIUv42B+TzLu/ann9FLOMkqEhA+F5zsEomikUA+L4cuIWL
|
||||||
|
XUhwIWwE2I/pfgHJ61JtMMxv3rWQHyo6YpzpIAG23oxGXzrlN4/oNfWzWMIYlcl4
|
||||||
|
xiHxC2vOKnNOwId3Ck3jsJE10tImdD/tQYXh7h5ueESyPUZtqM/g2QPap+tEHArp
|
||||||
|
gfAQdpEvRj1vZWAotcEIr+a5popE56UaE4a29DspVTA1rVchhKYl2gpDxieSQgQr
|
||||||
|
61fWHXzRoKcdFZ+NgqJuwd9CxrXbkl6EKDpefivwz9G4b3b6R8lMl+wmeTgPvSUY
|
||||||
|
O34c8GsF143HV/VKNoBvoz44QyLUf+1+XiHuUjfHaXCtXmDOoQ64M3d9gGrz23G5
|
||||||
|
KJAUBubcbcdu7Ah3D5zqvieGgoYt8qye1nsIVYC1KrYP2Kp5jWCadLvIwu2B0j7e
|
||||||
|
A+LwN2MBWdlhdRTSOVkICjWU
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,25 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIENTCCAp2gAwIBAgIUaHnHWrIVx0qzAZIwvELkQUEs+3cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwYTEjMCEGCgmSJomT8ixkAQEME2MtMG53d296cXZhd3ZhbzNlY2gxFTATBgNV
|
||||||
|
BAMMDE1hbmFnZW1lbnRDQTEjMCEGA1UECgwaRUpCQ0EgQ29udGFpbmVyIFF1aWNr
|
||||||
|
c3RhcnQwHhcNMjQxMTI1MDc1NzI3WhcNMjYxMTI1MDc1NzI2WjAYMRYwFAYDVQQD
|
||||||
|
DA1Ob3RhdGlvblRlc3QzMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAuJlXlrTm
|
||||||
|
VhEiLz75HgJlZGm0TE7W/7mYn0m03vV+9tBEA/ZV50ACkMDY0ewxxh4Ko6UsGq71
|
||||||
|
E466hSVggiaYaE4AdbL5kAolsnEm9/EDfYQNfgiQw0BI7axri9tJ19yZhj4Es31+
|
||||||
|
j4RJHAydB4i/qJi6T8cITdT6ViyzWM6AWGl7yRajggE0MIIBMDAMBgNVHRMBAf8E
|
||||||
|
AjAAMB8GA1UdIwQYMBaAFFPceeG3G4d5oezFSybFwehynbPqMA8GA1UdLgQIMAYw
|
||||||
|
BKACoAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwgakGA1UdHwSBoTCBnjCBm6CBmKCB
|
||||||
|
lYaBkmh0dHA6Ly9sb2NhbGhvc3Q6ODAvZWpiY2EvcHVibGljd2ViL3dlYmRpc3Qv
|
||||||
|
Y2VydGRpc3Q/Y21kPWNybCZpc3N1ZXI9VUlEJTNEYy0wbnd3b3pxdmF3dmFvM2Vj
|
||||||
|
aCUyQ0NOJTNETWFuYWdlbWVudENBJTJDTyUzREVKQkNBK0NvbnRhaW5lcitRdWlj
|
||||||
|
a3N0YXJ0MB0GA1UdDgQWBBQxxPNv9OsTqHGG7zCMhrdoKH5PPDAOBgNVHQ8BAf8E
|
||||||
|
BAMCBaAwDQYJKoZIhvcNAQELBQADggGBAFexu1jD597UDagfEcWAE1d3lYQd1guQ
|
||||||
|
cagW0M6dpaDcnA0Zl/eX2uxFecXDyFL+Ngfk8y7v2p5/RSzjJKhIQPhec7BKJopF
|
||||||
|
APi+HLiFi11IcCFsBNiP6X4ByetSbTDMb961kB8qOmKc6SABtt6MRl865TeP6DX1
|
||||||
|
s1jCGJXJeMYh8QtrzipzTsCHdwpN47CRNdLSJnQ/7UGF4e4ebnhEsj1GbajP4NkD
|
||||||
|
2qfrRBwK6YHwEHaRL0Y9b2VgKLXBCK/muaaKROelGhOGtvQ7KVUwNa1XIYSmJdoK
|
||||||
|
Q8YnkkIEK+tX1h180aCnHRWfjYKibsHfQsa125JehCg6Xn4r8M/RuG92+kfJTJfs
|
||||||
|
Jnk4D70lGDt+HPBrBdeNx1f1SjaAb6M+OEMi1H/tfl4h7lI3x2lwrV5gzqEOuDN3
|
||||||
|
fYBq89txuSiQFAbm3G3HbuwIdw+c6r4nhoKGLfKsntZ7CFWAtSq2D9iqeY1gmnS7
|
||||||
|
yMLtgdI+3gPi8DdjAVnZYXUU0jlZCAo1lA==
|
||||||
|
-----END CERTIFICATE-----
|
Binary file not shown.
Binary file not shown.
|
@ -21,10 +21,32 @@ import (
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/notaryproject/notation-core-go/revocation/crl"
|
"github.com/notaryproject/notation-core-go/revocation/crl"
|
||||||
|
"github.com/notaryproject/notation-core-go/revocation/internal/x509util"
|
||||||
"github.com/notaryproject/notation-core-go/revocation/result"
|
"github.com/notaryproject/notation-core-go/revocation/result"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RFC 5280, 5.3.1
|
||||||
|
//
|
||||||
|
// CRLReason ::= ENUMERATED {
|
||||||
|
// unspecified (0),
|
||||||
|
// keyCompromise (1),
|
||||||
|
// cACompromise (2),
|
||||||
|
// affiliationChanged (3),
|
||||||
|
// superseded (4),
|
||||||
|
// cessationOfOperation (5),
|
||||||
|
// certificateHold (6),
|
||||||
|
// -- value 7 is not used
|
||||||
|
// removeFromCRL (8),
|
||||||
|
// privilegeWithdrawn (9),
|
||||||
|
// aACompromise (10) }
|
||||||
|
const (
|
||||||
|
reasonCodeCertificateHold = 6 // certificateHold
|
||||||
|
reasonCodeRemoveFromCRL = 8 // removeFromCRL
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -36,6 +58,10 @@ var (
|
||||||
// distribution point CRL extension. (See RFC 5280, Section 5.2.5)
|
// distribution point CRL extension. (See RFC 5280, Section 5.2.5)
|
||||||
oidIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
|
oidIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
|
||||||
|
|
||||||
|
// oidDeltaCRLIndicator is the object identifier for the delta CRL indicator
|
||||||
|
// (See RFC 5280, Section 5.2.4)
|
||||||
|
oidDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27}
|
||||||
|
|
||||||
// oidInvalidityDate is the object identifier for the invalidity date
|
// oidInvalidityDate is the object identifier for the invalidity date
|
||||||
// CRL entry extension. (See RFC 5280, Section 5.3.2)
|
// CRL entry extension. (See RFC 5280, Section 5.3.2)
|
||||||
oidInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24}
|
oidInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24}
|
||||||
|
@ -89,6 +115,7 @@ func CertCheckStatus(ctx context.Context, cert, issuer *x509.Certificate, opts C
|
||||||
serverResults = make([]*result.ServerResult, 0, len(cert.CRLDistributionPoints))
|
serverResults = make([]*result.ServerResult, 0, len(cert.CRLDistributionPoints))
|
||||||
lastErr error
|
lastErr error
|
||||||
crlURL string
|
crlURL string
|
||||||
|
hasFreshestCRLInCertificate = x509util.FindExtensionByOID(cert.Extensions, oidFreshestCRL) != nil
|
||||||
)
|
)
|
||||||
|
|
||||||
// The CRLDistributionPoints contains the URIs of all the CRL distribution
|
// The CRLDistributionPoints contains the URIs of all the CRL distribution
|
||||||
|
@ -105,12 +132,28 @@ func CertCheckStatus(ctx context.Context, cert, issuer *x509.Certificate, opts C
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validate(bundle.BaseCRL, issuer); err != nil {
|
if hasFreshestCRLInCertificate && bundle.DeltaCRL == nil {
|
||||||
|
// | deltaCRL URL in cert | deltaCRL URL in baseCRL | support it? |
|
||||||
|
// |----------------------|-------------------------|-------------|
|
||||||
|
// | True | True | Yes |
|
||||||
|
// | True | False | No |
|
||||||
|
// | False | True | Yes |
|
||||||
|
// | False | False | Yes |
|
||||||
|
//
|
||||||
|
// if only the certificate has the freshest CRL, the bundle.DeltaCRL
|
||||||
|
// should be nil. We don't support this case now because the delta
|
||||||
|
// CRLs may have different scopes, but the Go built-in function
|
||||||
|
// skips the scope of the base CRL when parsing the certificate.
|
||||||
|
lastErr = errors.New("freshest CRL from certificate extension is not supported")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validate(bundle, issuer); err != nil {
|
||||||
lastErr = fmt.Errorf("failed to validate CRL from %s: %w", crlURL, err)
|
lastErr = fmt.Errorf("failed to validate CRL from %s: %w", crlURL, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
crlResult, err := checkRevocation(cert, bundle.BaseCRL, opts.SigningTime, crlURL)
|
crlResult, err := checkRevocation(cert, bundle, opts.SigningTime, crlURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("failed to check revocation status from %s: %w", crlURL, err)
|
lastErr = fmt.Errorf("failed to check revocation status from %s: %w", crlURL, err)
|
||||||
break
|
break
|
||||||
|
@ -153,7 +196,44 @@ func Supported(cert *x509.Certificate) bool {
|
||||||
return cert != nil && len(cert.CRLDistributionPoints) > 0
|
return cert != nil && len(cert.CRLDistributionPoints) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate(crl *x509.RevocationList, issuer *x509.Certificate) error {
|
func validate(bundle *crl.Bundle, issuer *x509.Certificate) error {
|
||||||
|
// validate base CRL
|
||||||
|
baseCRL := bundle.BaseCRL
|
||||||
|
if err := validateCRL(baseCRL, issuer); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate base CRL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bundle.DeltaCRL == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate delta CRL
|
||||||
|
// RFC 5280, Section 5.2.4
|
||||||
|
deltaCRL := bundle.DeltaCRL
|
||||||
|
if err := validateCRL(deltaCRL, issuer); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate delta CRL: %w", err)
|
||||||
|
}
|
||||||
|
if deltaCRL.Number.Cmp(baseCRL.Number) <= 0 {
|
||||||
|
return fmt.Errorf("delta CRL number %d is not greater than the base CRL number %d", deltaCRL.Number, baseCRL.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check delta CRL indicator extension
|
||||||
|
extension := x509util.FindExtensionByOID(deltaCRL.Extensions, oidDeltaCRLIndicator)
|
||||||
|
if extension == nil {
|
||||||
|
return errors.New("delta CRL indicator extension is not found")
|
||||||
|
}
|
||||||
|
minimumBaseCRLNumber := new(big.Int)
|
||||||
|
value := cryptobyte.String(extension.Value)
|
||||||
|
if !value.ReadASN1Integer(minimumBaseCRLNumber) {
|
||||||
|
return errors.New("failed to parse delta CRL indicator extension")
|
||||||
|
}
|
||||||
|
if minimumBaseCRLNumber.Cmp(baseCRL.Number) > 0 {
|
||||||
|
return fmt.Errorf("delta CRL indicator %d is not less than or equal to the base CRL number %d", minimumBaseCRLNumber, baseCRL.Number)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCRL(crl *x509.RevocationList, issuer *x509.Certificate) error {
|
||||||
// check signature
|
// check signature
|
||||||
if err := crl.CheckSignatureFrom(issuer); err != nil {
|
if err := crl.CheckSignatureFrom(issuer); err != nil {
|
||||||
return fmt.Errorf("CRL is not signed by CA %s: %w,", issuer.Subject, err)
|
return fmt.Errorf("CRL is not signed by CA %s: %w,", issuer.Subject, err)
|
||||||
|
@ -170,12 +250,12 @@ func validate(crl *x509.RevocationList, issuer *x509.Certificate) error {
|
||||||
|
|
||||||
for _, ext := range crl.Extensions {
|
for _, ext := range crl.Extensions {
|
||||||
switch {
|
switch {
|
||||||
case ext.Id.Equal(oidFreshestCRL):
|
|
||||||
return errors.New("delta CRL is not supported")
|
|
||||||
case ext.Id.Equal(oidIssuingDistributionPoint):
|
case ext.Id.Equal(oidIssuingDistributionPoint):
|
||||||
// IssuingDistributionPoint is a critical extension that identifies
|
// IssuingDistributionPoint is a critical extension that identifies
|
||||||
// the scope of the CRL. Since we will check all the CRL
|
// the scope of the CRL. Since we will check all the CRL
|
||||||
// distribution points, it is not necessary to check this extension.
|
// distribution points, it is not necessary to check this extension.
|
||||||
|
case ext.Id.Equal(oidDeltaCRLIndicator):
|
||||||
|
// will be checked in validate()
|
||||||
default:
|
default:
|
||||||
if ext.Critical {
|
if ext.Critical {
|
||||||
// unsupported critical extensions is not allowed. (See RFC 5280, Section 5.2)
|
// unsupported critical extensions is not allowed. (See RFC 5280, Section 5.2)
|
||||||
|
@ -188,16 +268,43 @@ func validate(crl *x509.RevocationList, issuer *x509.Certificate) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRevocation checks if the certificate is revoked or not
|
// checkRevocation checks if the certificate is revoked or not
|
||||||
func checkRevocation(cert *x509.Certificate, baseCRL *x509.RevocationList, signingTime time.Time, crlURL string) (*result.ServerResult, error) {
|
func checkRevocation(cert *x509.Certificate, b *crl.Bundle, signingTime time.Time, crlURL string) (*result.ServerResult, error) {
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return nil, errors.New("certificate cannot be nil")
|
return nil, errors.New("certificate cannot be nil")
|
||||||
}
|
}
|
||||||
|
if b == nil {
|
||||||
if baseCRL == nil {
|
return nil, errors.New("CRL bundle cannot be nil")
|
||||||
|
}
|
||||||
|
if b.BaseCRL == nil {
|
||||||
return nil, errors.New("baseCRL cannot be nil")
|
return nil, errors.New("baseCRL cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, revocationEntry := range baseCRL.RevokedCertificateEntries {
|
// merge the base and delta CRLs in a single iterator
|
||||||
|
revocationListIter := func(yield func(*x509.RevocationListEntry) bool) {
|
||||||
|
for i := range b.BaseCRL.RevokedCertificateEntries {
|
||||||
|
if !yield(&b.BaseCRL.RevokedCertificateEntries[i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.DeltaCRL != nil {
|
||||||
|
for i := range b.DeltaCRL.RevokedCertificateEntries {
|
||||||
|
if !yield(&b.DeltaCRL.RevokedCertificateEntries[i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// latestTempRevokedEntry contains the most recent revocation entry with
|
||||||
|
// reasons such as CertificateHold or RemoveFromCRL.
|
||||||
|
//
|
||||||
|
// If the certificate is revoked with CertificateHold, it is temporarily
|
||||||
|
// revoked. If the certificate is shown in the CRL with RemoveFromCRL,
|
||||||
|
// it is unrevoked.
|
||||||
|
var latestTempRevokedEntry *x509.RevocationListEntry
|
||||||
|
|
||||||
|
// iterate over all the entries in the base and delta CRLs
|
||||||
|
for revocationEntry := range revocationListIter {
|
||||||
if revocationEntry.SerialNumber.Cmp(cert.SerialNumber) == 0 {
|
if revocationEntry.SerialNumber.Cmp(cert.SerialNumber) == 0 {
|
||||||
extensions, err := parseEntryExtensions(revocationEntry)
|
extensions, err := parseEntryExtensions(revocationEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -209,10 +316,22 @@ func checkRevocation(cert *x509.Certificate, baseCRL *x509.RevocationList, signi
|
||||||
signingTime.Before(extensions.invalidityDate) {
|
signingTime.Before(extensions.invalidityDate) {
|
||||||
// signing time is before the invalidity date which means the
|
// signing time is before the invalidity date which means the
|
||||||
// certificate is not revoked at the time of signing.
|
// certificate is not revoked at the time of signing.
|
||||||
break
|
return &result.ServerResult{
|
||||||
|
Result: result.ResultOK,
|
||||||
|
Server: crlURL,
|
||||||
|
RevocationMethod: result.RevocationMethodCRL,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// revoked
|
switch revocationEntry.ReasonCode {
|
||||||
|
case reasonCodeCertificateHold, reasonCodeRemoveFromCRL:
|
||||||
|
// temporarily revoked or unrevoked
|
||||||
|
if latestTempRevokedEntry == nil || latestTempRevokedEntry.RevocationTime.Before(revocationEntry.RevocationTime) {
|
||||||
|
// the revocation status depends on the most recent reason
|
||||||
|
latestTempRevokedEntry = revocationEntry
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// permanently revoked
|
||||||
return &result.ServerResult{
|
return &result.ServerResult{
|
||||||
Result: result.ResultRevoked,
|
Result: result.ResultRevoked,
|
||||||
Server: crlURL,
|
Server: crlURL,
|
||||||
|
@ -220,6 +339,15 @@ func checkRevocation(cert *x509.Certificate, baseCRL *x509.RevocationList, signi
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if latestTempRevokedEntry != nil && latestTempRevokedEntry.ReasonCode == reasonCodeCertificateHold {
|
||||||
|
// revoked with CertificateHold
|
||||||
|
return &result.ServerResult{
|
||||||
|
Result: result.ResultRevoked,
|
||||||
|
Server: crlURL,
|
||||||
|
RevocationMethod: result.RevocationMethodCRL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
return &result.ServerResult{
|
return &result.ServerResult{
|
||||||
Result: result.ResultOK,
|
Result: result.ResultOK,
|
||||||
|
@ -233,7 +361,7 @@ type entryExtensions struct {
|
||||||
invalidityDate time.Time
|
invalidityDate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseEntryExtensions(entry x509.RevocationListEntry) (entryExtensions, error) {
|
func parseEntryExtensions(entry *x509.RevocationListEntry) (entryExtensions, error) {
|
||||||
var extensions entryExtensions
|
var extensions entryExtensions
|
||||||
for _, ext := range entry.Extensions {
|
for _, ext := range entry.Extensions {
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -203,38 +203,6 @@ func TestCertCheckStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CRL with delta CRL is not checked", func(t *testing.T) {
|
|
||||||
memoryCache := &memoryCache{}
|
|
||||||
|
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
||||||
NextUpdate: time.Now().Add(time.Hour),
|
|
||||||
Number: big.NewInt(20240720),
|
|
||||||
ExtraExtensions: []pkix.Extension{
|
|
||||||
{
|
|
||||||
Id: oidFreshestCRL,
|
|
||||||
Critical: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, issuerCert, issuerKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fetcher, err := crlutils.NewHTTPFetcher(
|
|
||||||
&http.Client{Transport: expectedRoundTripperMock{Body: crlBytes}},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fetcher.Cache = memoryCache
|
|
||||||
fetcher.DiscardCacheError = true
|
|
||||||
r := CertCheckStatus(context.Background(), chain[0].Cert, issuerCert, CertCheckStatusOptions{
|
|
||||||
Fetcher: fetcher,
|
|
||||||
})
|
|
||||||
if !strings.Contains(r.ServerResults[0].Error.Error(), "delta CRL is not supported") {
|
|
||||||
t.Fatalf("unexpected error, got %v, expected %v", r.ServerResults[0].Error, "delta CRL is not supported")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
memoryCache := &memoryCache{}
|
memoryCache := &memoryCache{}
|
||||||
|
|
||||||
// create a stale CRL
|
// create a stale CRL
|
||||||
|
@ -328,6 +296,40 @@ func TestCertCheckStatus(t *testing.T) {
|
||||||
t.Fatalf("expected OK, got %s", r.Result)
|
t.Fatalf("expected OK, got %s", r.Result)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("freshest CRL from certificate extension is not supported", func(t *testing.T) {
|
||||||
|
chain[0].Cert.Extensions = []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oidFreshestCRL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
Number: big.NewInt(20240720),
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher, err := crlutils.NewHTTPFetcher(
|
||||||
|
&http.Client{Transport: expectedRoundTripperMock{Body: crlBytes}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fetcher.DiscardCacheError = true
|
||||||
|
r := CertCheckStatus(context.Background(), chain[0].Cert, issuerCert, CertCheckStatusOptions{
|
||||||
|
Fetcher: fetcher,
|
||||||
|
})
|
||||||
|
if r.Result != result.ResultUnknown {
|
||||||
|
t.Fatalf("expected Unknown, got %s", r.Result)
|
||||||
|
}
|
||||||
|
expectedErrorMsg := "freshest CRL from certificate extension is not supported"
|
||||||
|
if r.ServerResults[0].Error == nil || r.ServerResults[0].Error.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, r.ServerResults[0].Error)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
|
@ -349,7 +351,7 @@ func TestValidate(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validate(crl, issuerCert); err == nil {
|
if err := validateCRL(crl, issuerCert); err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -359,7 +361,7 @@ func TestValidate(t *testing.T) {
|
||||||
NextUpdate: time.Now().Add(time.Hour),
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validate(crl, &x509.Certificate{}); err == nil {
|
if err := validateCRL(crl, &x509.Certificate{}); err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -390,7 +392,7 @@ func TestValidate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validate(crl, issuerCert); err == nil {
|
if err := validateCRL(crl, issuerCert); err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -419,12 +421,11 @@ func TestValidate(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validate(crl, issuerCert); err != nil {
|
if err := validateCRL(crl, issuerCert); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("delta CRL is not supported", func(t *testing.T) {
|
|
||||||
chain := testhelper.GetRevokableRSAChainWithRevocations(1, false, true)
|
chain := testhelper.GetRevokableRSAChainWithRevocations(1, false, true)
|
||||||
issuerCert := chain[0].Cert
|
issuerCert := chain[0].Cert
|
||||||
issuerKey := chain[0].PrivateKey
|
issuerKey := chain[0].PrivateKey
|
||||||
|
@ -432,12 +433,6 @@ func TestValidate(t *testing.T) {
|
||||||
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
crlBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
NextUpdate: time.Now().Add(time.Hour),
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
Number: big.NewInt(20240720),
|
Number: big.NewInt(20240720),
|
||||||
ExtraExtensions: []pkix.Extension{
|
|
||||||
{
|
|
||||||
Id: oidFreshestCRL,
|
|
||||||
Critical: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, issuerCert, issuerKey)
|
}, issuerCert, issuerKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -448,8 +443,193 @@ func TestValidate(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validate(crl, issuerCert); err.Error() != "delta CRL is not supported" {
|
t.Run("valid crl and delta crl", func(t *testing.T) {
|
||||||
t.Fatalf("got %v, expected delta CRL is not supported", err)
|
deltaCRLIndicator := big.NewInt(20240720)
|
||||||
|
deltaCRLIndicatorBytes, err := asn1.Marshal(deltaCRLIndicator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
Number: big.NewInt(20240721),
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oidDeltaCRLIndicator,
|
||||||
|
Critical: true,
|
||||||
|
Value: deltaCRLIndicatorBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRL, err := x509.ParseRevocationList(deltaCRLBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bundle := &crlutils.Bundle{
|
||||||
|
BaseCRL: crl,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
|
}
|
||||||
|
if err := validate(bundle, issuerCert); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid delta crl", func(t *testing.T) {
|
||||||
|
deltaCRLIndicator := big.NewInt(20240720)
|
||||||
|
deltaCRLIndicatorBytes, err := asn1.Marshal(deltaCRLIndicator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
Number: big.NewInt(20240721),
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oidDeltaCRLIndicator,
|
||||||
|
Critical: true,
|
||||||
|
Value: deltaCRLIndicatorBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRL, err := x509.ParseRevocationList(deltaCRLBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bundle := &crlutils.Bundle{
|
||||||
|
BaseCRL: crl,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
|
}
|
||||||
|
err = validate(bundle, issuerCert)
|
||||||
|
expectedErrorMsg := "failed to validate delta CRL: CRL NextUpdate is not set"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid delta crl number", func(t *testing.T) {
|
||||||
|
deltaCRLIndicator := big.NewInt(20240720)
|
||||||
|
deltaCRLIndicatorBytes, err := asn1.Marshal(deltaCRLIndicator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
Number: big.NewInt(20240719),
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oidDeltaCRLIndicator,
|
||||||
|
Critical: true,
|
||||||
|
Value: deltaCRLIndicatorBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRL, err := x509.ParseRevocationList(deltaCRLBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bundle := &crlutils.Bundle{
|
||||||
|
BaseCRL: crl,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
|
}
|
||||||
|
err = validate(bundle, issuerCert)
|
||||||
|
expectedErrorMsg := "delta CRL number 20240719 is not greater than the base CRL number 20240720"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("delta crl without delta crl indicator", func(t *testing.T) {
|
||||||
|
deltaCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
Number: big.NewInt(20240721),
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRL, err := x509.ParseRevocationList(deltaCRLBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bundle := &crlutils.Bundle{
|
||||||
|
BaseCRL: crl,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
|
}
|
||||||
|
err = validate(bundle, issuerCert)
|
||||||
|
expectedErrorMsg := "delta CRL indicator extension is not found"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("delta crl minimum base crl number is greater than base crl", func(t *testing.T) {
|
||||||
|
deltaCRLIndicator := big.NewInt(20240721)
|
||||||
|
deltaCRLIndicatorBytes, err := asn1.Marshal(deltaCRLIndicator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
Number: big.NewInt(20240722),
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oidDeltaCRLIndicator,
|
||||||
|
Critical: true,
|
||||||
|
Value: deltaCRLIndicatorBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRL, err := x509.ParseRevocationList(deltaCRLBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bundle := &crlutils.Bundle{
|
||||||
|
BaseCRL: crl,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
|
}
|
||||||
|
err = validate(bundle, issuerCert)
|
||||||
|
expectedErrorMsg := "delta CRL indicator 20240721 is not less than or equal to the base CRL number 20240720"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("delta crl with invalid delta indicator extension", func(t *testing.T) {
|
||||||
|
deltaCRLBytes, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
||||||
|
NextUpdate: time.Now().Add(time.Hour),
|
||||||
|
Number: big.NewInt(20240722),
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oidDeltaCRLIndicator,
|
||||||
|
Critical: true,
|
||||||
|
Value: []byte("invalid"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, issuerCert, issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deltaCRL, err := x509.ParseRevocationList(deltaCRLBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bundle := &crlutils.Bundle{
|
||||||
|
BaseCRL: crl,
|
||||||
|
DeltaCRL: deltaCRL,
|
||||||
|
}
|
||||||
|
err = validate(bundle, issuerCert)
|
||||||
|
expectedErrorMsg := "failed to parse delta CRL indicator extension"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -461,14 +641,22 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
signingTime := time.Now()
|
signingTime := time.Now()
|
||||||
|
|
||||||
t.Run("certificate is nil", func(t *testing.T) {
|
t.Run("certificate is nil", func(t *testing.T) {
|
||||||
_, err := checkRevocation(nil, &x509.RevocationList{}, signingTime, "")
|
_, err := checkRevocation(nil, &crlutils.Bundle{BaseCRL: &x509.RevocationList{}}, signingTime, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CRL is nil", func(t *testing.T) {
|
t.Run("bundle is nil", func(t *testing.T) {
|
||||||
_, err := checkRevocation(cert, nil, signingTime, "")
|
_, err := checkRevocation(cert, nil, signingTime, "")
|
||||||
|
expectedErrorMsg := "CRL bundle cannot be nil"
|
||||||
|
if err == nil || err.Error() != expectedErrorMsg {
|
||||||
|
t.Fatalf("expected error %q, got %v", expectedErrorMsg, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CRL is nil", func(t *testing.T) {
|
||||||
|
_, err := checkRevocation(cert, &crlutils.Bundle{}, signingTime, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
|
@ -482,7 +670,7 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
r, err := checkRevocation(cert, baseCRL, signingTime, "")
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL}, signingTime, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -500,7 +688,26 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
r, err := checkRevocation(cert, baseCRL, signingTime, "")
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL}, signingTime, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r.Result != result.ResultRevoked {
|
||||||
|
t.Fatalf("expected revoked, got %s", r.Result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("revoked in delta CRL", func(t *testing.T) {
|
||||||
|
baseCRL := &x509.RevocationList{}
|
||||||
|
deltaCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
RevocationTime: time.Now().Add(-time.Hour),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL, DeltaCRL: deltaCRL}, signingTime, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -533,7 +740,7 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
r, err := checkRevocation(cert, baseCRL, signingTime, "")
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL}, signingTime, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -566,7 +773,7 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
r, err := checkRevocation(cert, baseCRL, signingTime, "")
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL}, signingTime, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -584,7 +791,7 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
r, err := checkRevocation(cert, baseCRL, time.Time{}, "")
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL}, time.Time{}, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -607,16 +814,129 @@ func TestCheckRevocation(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := checkRevocation(cert, baseCRL, signingTime, "")
|
_, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL}, signingTime, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("delta crl with certificate hold", func(t *testing.T) {
|
||||||
|
baseCRL := &x509.RevocationList{}
|
||||||
|
deltaCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeCertificateHold,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL, DeltaCRL: deltaCRL}, signingTime, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r.Result != result.ResultRevoked {
|
||||||
|
t.Fatalf("expected revoked, got %s", r.Result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("certificate hold and remove hold", func(t *testing.T) {
|
||||||
|
baseCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeCertificateHold,
|
||||||
|
RevocationTime: time.Now().Add(-time.Hour),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deltaCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeRemoveFromCRL,
|
||||||
|
RevocationTime: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL, DeltaCRL: deltaCRL}, signingTime, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r.Result != result.ResultOK {
|
||||||
|
t.Fatalf("expected OK, got %s", r.Result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("certificate hold and remove hold with other other certificate hold", func(t *testing.T) {
|
||||||
|
baseCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeCertificateHold,
|
||||||
|
RevocationTime: time.Now().Add(-time.Hour),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deltaCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeRemoveFromCRL,
|
||||||
|
RevocationTime: time.Now(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(2),
|
||||||
|
ReasonCode: reasonCodeCertificateHold,
|
||||||
|
RevocationTime: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL, DeltaCRL: deltaCRL}, signingTime, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r.Result != result.ResultOK {
|
||||||
|
t.Fatalf("expected OK, got %s", r.Result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("certificate hold, remove hold and hold again", func(t *testing.T) {
|
||||||
|
baseCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeCertificateHold,
|
||||||
|
RevocationTime: time.Now().Add(-2 * time.Hour),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deltaCRL := &x509.RevocationList{
|
||||||
|
RevokedCertificateEntries: []x509.RevocationListEntry{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeRemoveFromCRL,
|
||||||
|
RevocationTime: time.Now().Add(-time.Hour),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
ReasonCode: reasonCodeCertificateHold,
|
||||||
|
RevocationTime: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r, err := checkRevocation(cert, &crlutils.Bundle{BaseCRL: baseCRL, DeltaCRL: deltaCRL}, signingTime, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r.Result != result.ResultRevoked {
|
||||||
|
t.Fatalf("expected revoked, got %s", r.Result)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseEntryExtension(t *testing.T) {
|
func TestParseEntryExtension(t *testing.T) {
|
||||||
t.Run("unsupported critical extension", func(t *testing.T) {
|
t.Run("unsupported critical extension", func(t *testing.T) {
|
||||||
entry := x509.RevocationListEntry{
|
entry := &x509.RevocationListEntry{
|
||||||
Extensions: []pkix.Extension{
|
Extensions: []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: []int{1, 2, 3},
|
Id: []int{1, 2, 3},
|
||||||
|
@ -630,7 +950,7 @@ func TestParseEntryExtension(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid extension", func(t *testing.T) {
|
t.Run("valid extension", func(t *testing.T) {
|
||||||
entry := x509.RevocationListEntry{
|
entry := &x509.RevocationListEntry{
|
||||||
Extensions: []pkix.Extension{
|
Extensions: []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: []int{1, 2, 3},
|
Id: []int{1, 2, 3},
|
||||||
|
@ -652,7 +972,7 @@ func TestParseEntryExtension(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := x509.RevocationListEntry{
|
entry := &x509.RevocationListEntry{
|
||||||
Extensions: []pkix.Extension{
|
Extensions: []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: oidInvalidityDate,
|
Id: oidInvalidityDate,
|
||||||
|
@ -673,7 +993,7 @@ func TestParseEntryExtension(t *testing.T) {
|
||||||
|
|
||||||
t.Run("parse invalidityDate with error", func(t *testing.T) {
|
t.Run("parse invalidityDate with error", func(t *testing.T) {
|
||||||
// invalid invalidityDate extension
|
// invalid invalidityDate extension
|
||||||
entry := x509.RevocationListEntry{
|
entry := &x509.RevocationListEntry{
|
||||||
Extensions: []pkix.Extension{
|
Extensions: []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: oidInvalidityDate,
|
Id: oidInvalidityDate,
|
||||||
|
@ -695,7 +1015,7 @@ func TestParseEntryExtension(t *testing.T) {
|
||||||
}
|
}
|
||||||
invalidityDateBytes = append(invalidityDateBytes, 0x00)
|
invalidityDateBytes = append(invalidityDateBytes, 0x00)
|
||||||
|
|
||||||
entry = x509.RevocationListEntry{
|
entry = &x509.RevocationListEntry{
|
||||||
Extensions: []pkix.Extension{
|
Extensions: []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: oidInvalidityDate,
|
Id: oidInvalidityDate,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright The Notary Project 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 x509util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindExtensionByOID finds the extension by the given OID.
|
||||||
|
func FindExtensionByOID(extensions []pkix.Extension, oid asn1.ObjectIdentifier) *pkix.Extension {
|
||||||
|
idx := slices.IndexFunc(extensions, func(ext pkix.Extension) bool {
|
||||||
|
return ext.Id.Equal(oid)
|
||||||
|
})
|
||||||
|
if idx < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &extensions[idx]
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright The Notary Project 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 x509util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindExtensionByOID(t *testing.T) {
|
||||||
|
oid1 := asn1.ObjectIdentifier{1, 2, 3, 4}
|
||||||
|
oid2 := asn1.ObjectIdentifier{1, 2, 3, 5}
|
||||||
|
extensions := []pkix.Extension{
|
||||||
|
{Id: oid1, Value: []byte("value1")},
|
||||||
|
{Id: oid2, Value: []byte("value2")},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
oid asn1.ObjectIdentifier
|
||||||
|
extensions []pkix.Extension
|
||||||
|
expected *pkix.Extension
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Extension found",
|
||||||
|
oid: oid1,
|
||||||
|
extensions: extensions,
|
||||||
|
expected: &extensions[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extension not found",
|
||||||
|
oid: asn1.ObjectIdentifier{1, 2, 3, 6},
|
||||||
|
extensions: extensions,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := FindExtensionByOID(tt.extensions, tt.oid)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("expected %v, got %v", tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package x509util provides the method to validate the certificate chain for a
|
// Package x509util provides the method to validate the certificate chain for a
|
||||||
// specific purpose, including code signing and timestamping.
|
// specific purpose, including code signing and timestamping. It also provides
|
||||||
|
// the method to find the extension by the given OID.
|
||||||
package x509util
|
package x509util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -67,7 +67,7 @@ type SignatureAuthenticityError struct{}
|
||||||
|
|
||||||
// Error returns the default error message.
|
// Error returns the default error message.
|
||||||
func (e *SignatureAuthenticityError) Error() string {
|
func (e *SignatureAuthenticityError) Error() string {
|
||||||
return "signature is not produced by a trusted signer"
|
return "the signature's certificate chain does not contain any trusted certificate"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsupportedSigningKeyError is used when a signing key is not supported.
|
// UnsupportedSigningKeyError is used when a signing key is not supported.
|
||||||
|
|
|
@ -162,7 +162,7 @@ func TestSignatureEnvelopeNotFoundError(t *testing.T) {
|
||||||
|
|
||||||
func TestSignatureAuthenticityError(t *testing.T) {
|
func TestSignatureAuthenticityError(t *testing.T) {
|
||||||
err := &SignatureAuthenticityError{}
|
err := &SignatureAuthenticityError{}
|
||||||
expectMsg := "signature is not produced by a trusted signer"
|
expectMsg := "the signature's certificate chain does not contain any trusted certificate"
|
||||||
|
|
||||||
if err.Error() != expectMsg {
|
if err.Error() != expectMsg {
|
||||||
t.Errorf("Expected %v but got %v", expectMsg, err.Error())
|
t.Errorf("Expected %v but got %v", expectMsg, err.Error())
|
||||||
|
|
|
@ -122,22 +122,21 @@ func (s *localSigner) PrivateKey() crypto.PrivateKey {
|
||||||
return s.key
|
return s.key
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyAuthenticity verifies the certificate chain in the given SignerInfo
|
// VerifyAuthenticity iterates the certificate chain in signerInfo, for each
|
||||||
// with one of the trusted certificates and returns a certificate that matches
|
// certificate in the chain, it checks if the certificate matches with one of
|
||||||
// with one of the certificates in the SignerInfo.
|
// the trusted certificates in trustedCerts. It returns the first matching
|
||||||
|
// certificate. If no match is found, it returns an error.
|
||||||
//
|
//
|
||||||
// Reference: https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#steps
|
// Reference: https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#steps
|
||||||
func VerifyAuthenticity(signerInfo *SignerInfo, trustedCerts []*x509.Certificate) (*x509.Certificate, error) {
|
func VerifyAuthenticity(signerInfo *SignerInfo, trustedCerts []*x509.Certificate) (*x509.Certificate, error) {
|
||||||
if len(trustedCerts) == 0 {
|
if len(trustedCerts) == 0 {
|
||||||
return nil, &InvalidArgumentError{Param: "trustedCerts"}
|
return nil, &InvalidArgumentError{Param: "trustedCerts"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if signerInfo == nil {
|
if signerInfo == nil {
|
||||||
return nil, &InvalidArgumentError{Param: "signerInfo"}
|
return nil, &InvalidArgumentError{Param: "signerInfo"}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, trust := range trustedCerts {
|
|
||||||
for _, cert := range signerInfo.CertificateChain {
|
for _, cert := range signerInfo.CertificateChain {
|
||||||
|
for _, trust := range trustedCerts {
|
||||||
if trust.Equal(cert) {
|
if trust.Equal(cert) {
|
||||||
return trust, nil
|
return trust, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue