Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CRL #214

Merged
merged 117 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 108 commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
62aa8fc
Squashed commit of the following:
JeyJeyGao Nov 29, 2023
eef8579
test: add unit test
JeyJeyGao Dec 7, 2023
feb151c
feat: crl support
JeyJeyGao Apr 18, 2024
56da59c
fix: update
JeyJeyGao Apr 18, 2024
7d869a7
Merge remote-tracking branch 'upstream/main' into feat/crl
JeyJeyGao Apr 18, 2024
a25275e
fix: update
JeyJeyGao May 29, 2024
a262bab
fix: update crl
JeyJeyGao Jul 8, 2024
7d591d6
Merge remote-tracking branch 'upstream/main' into feat/crl
JeyJeyGao Jul 8, 2024
e66d9fd
fix: update cache
JeyJeyGao Jul 11, 2024
ac1d240
fix: update
JeyJeyGao Jul 12, 2024
e789f29
Merge remote-tracking branch 'upstream/main' into feat/crl
JeyJeyGao Jul 12, 2024
31fa5a5
fix: update test
JeyJeyGao Jul 12, 2024
039be39
fix: update
JeyJeyGao Jul 12, 2024
3b1a7ed
fix: update
JeyJeyGao Jul 12, 2024
6875035
fix: update
JeyJeyGao Jul 12, 2024
5bbd8c7
fix: update
JeyJeyGao Jul 15, 2024
39c94d3
fix: deprecate revocation Mode
JeyJeyGao Jul 15, 2024
c85d472
fix: update
JeyJeyGao Jul 15, 2024
a8db155
fix: update
JeyJeyGao Jul 15, 2024
534d11e
fix: update
JeyJeyGao Jul 16, 2024
50964ba
fix: update
JeyJeyGao Jul 16, 2024
cf593b1
fix: update
JeyJeyGao Jul 19, 2024
c172845
fix: update
JeyJeyGao Jul 19, 2024
1aa53fd
fix: update
JeyJeyGao Jul 19, 2024
2d39c35
fix: update
JeyJeyGao Jul 19, 2024
4c01b9e
fix: update
JeyJeyGao Jul 19, 2024
5787e75
fix: remove cache
JeyJeyGao Jul 22, 2024
41746fb
fix: update
JeyJeyGao Jul 22, 2024
759b727
fix: refactor
JeyJeyGao Jul 22, 2024
17da500
fix: update
JeyJeyGao Jul 23, 2024
75884a5
fix: complete test for crl package
JeyJeyGao Jul 23, 2024
e92aff7
fix: update
JeyJeyGao Jul 23, 2024
5c5c041
fix: update github action rule for branches
JeyJeyGao Jul 24, 2024
efe7708
fix: update
JeyJeyGao Jul 24, 2024
28bbf22
fix: update
JeyJeyGao Jul 24, 2024
a8f3b4b
fix: update
JeyJeyGao Jul 24, 2024
93ee863
fix: update
JeyJeyGao Jul 24, 2024
99ad04c
fix: update
JeyJeyGao Jul 24, 2024
9eb5af5
fix: update
JeyJeyGao Jul 24, 2024
38d04ce
fix: update
JeyJeyGao Jul 24, 2024
6a5357e
fix: update
JeyJeyGao Jul 24, 2024
3f9a259
fix: update
JeyJeyGao Jul 25, 2024
6bf3ca2
fix: update
JeyJeyGao Jul 25, 2024
751397b
fix: add CRL size limit
JeyJeyGao Jul 25, 2024
3170db3
fix: restore workflow and update NewWithOptions
JeyJeyGao Jul 25, 2024
8df836d
Merge branch 'main' into feat/crl_no_cache
JeyJeyGao Jul 25, 2024
041a63d
fix: update
JeyJeyGao Jul 25, 2024
e170b90
fix: update
JeyJeyGao Jul 25, 2024
1c68a17
fix: update
JeyJeyGao Jul 26, 2024
c512907
Merge 215
JeyJeyGao Aug 8, 2024
281dd64
Squashed commit of the following:
JeyJeyGao Aug 8, 2024
f833d94
Merge remote-tracking branch 'upstream/main' into feat/crl_no_cache
JeyJeyGao Aug 8, 2024
2c677c1
fix: update
JeyJeyGao Aug 8, 2024
6c0db0a
fix: update
JeyJeyGao Aug 8, 2024
cf44e31
fix: update
JeyJeyGao Aug 8, 2024
a174c20
Merge remote-tracking branch 'upstream/main' into feat/crl_no_cache
JeyJeyGao Aug 8, 2024
3a45b12
fix: update
JeyJeyGao Aug 8, 2024
950f76d
fix: update
JeyJeyGao Aug 8, 2024
e32b2d7
fix: update
JeyJeyGao Aug 8, 2024
4c41371
fix: update
JeyJeyGao Aug 8, 2024
c76c7c4
fix: resolve comments
JeyJeyGao Aug 12, 2024
9be2722
fix: update
JeyJeyGao Aug 12, 2024
3e3934c
fix: update
JeyJeyGao Aug 13, 2024
8b64025
fix: update
JeyJeyGao Aug 13, 2024
1f2b8ae
fix: update
JeyJeyGao Aug 14, 2024
1b7b38f
fix: add issuing distribution point extension
JeyJeyGao Aug 16, 2024
8f6c2c0
fix: update
JeyJeyGao Aug 16, 2024
e2acd00
fix: add delta CRL not checked error
JeyJeyGao Aug 16, 2024
174b516
fix: add comment
JeyJeyGao Aug 16, 2024
fe77e59
fix: update
JeyJeyGao Aug 16, 2024
815cc5f
fix: add license
JeyJeyGao Aug 16, 2024
c3a484e
fix: update
JeyJeyGao Aug 19, 2024
15337d4
Squashed commit of the following:
JeyJeyGao Aug 19, 2024
148a7b9
Merge remote-tracking branch 'upstream/main' into feat/crl_no_cache
JeyJeyGao Aug 19, 2024
1dafcd5
fix: update
JeyJeyGao Aug 19, 2024
1f89b49
fix: resolve comments
JeyJeyGao Aug 20, 2024
1c4cbfb
fix: update
JeyJeyGao Aug 20, 2024
3bb154f
fix: update
JeyJeyGao Aug 20, 2024
5d2ada4
fix: update
JeyJeyGao Aug 21, 2024
679dd5b
fix: update
JeyJeyGao Aug 21, 2024
f395a1a
fix: update
JeyJeyGao Aug 21, 2024
dec2a8c
fix: update
JeyJeyGao Aug 21, 2024
4a5d93b
fix: update
JeyJeyGao Aug 21, 2024
61f6c1a
fix: update
JeyJeyGao Aug 21, 2024
a42c44c
fix: update
JeyJeyGao Aug 22, 2024
9d91fb6
fix: update
JeyJeyGao Aug 22, 2024
266d814
fix: update
JeyJeyGao Aug 22, 2024
7451fbe
fix: update
JeyJeyGao Aug 22, 2024
2748835
fix: update
JeyJeyGao Aug 22, 2024
3520a7b
fix: update
JeyJeyGao Aug 22, 2024
8aadc6c
fix: update
JeyJeyGao Aug 22, 2024
22540ea
fix: update
JeyJeyGao Aug 22, 2024
bf0eadb
fix: update
JeyJeyGao Aug 22, 2024
e7fedcf
fix: update
JeyJeyGao Aug 22, 2024
d643ea2
fix: update
JeyJeyGao Aug 22, 2024
c620d85
fix: update
JeyJeyGao Aug 22, 2024
d014978
Merge branch 'main' into feat/crl_no_cache
JeyJeyGao Aug 27, 2024
ab1ed37
fix: resolve comments
JeyJeyGao Sep 11, 2024
a89a11c
Merge branch 'feat/crl_no_cache' of https://github.com/JeyJeyGao/nota…
JeyJeyGao Sep 11, 2024
a0f9d47
fix: update
JeyJeyGao Sep 11, 2024
e2cf605
fix: update
JeyJeyGao Sep 11, 2024
1ec6ed4
fix: remove CertificateHold and RemoveFromCRL check
JeyJeyGao Sep 12, 2024
04ce2f0
fix: merge CRLResult with ServerResult
JeyJeyGao Sep 12, 2024
2320086
refactor: merge CRLResult and ServerResult
JeyJeyGao Sep 12, 2024
0ebc7f9
fix: update
JeyJeyGao Sep 12, 2024
02d07e2
fix: update
JeyJeyGao Sep 12, 2024
bb2315e
fix: add license
JeyJeyGao Sep 12, 2024
4e565ec
fix: update
JeyJeyGao Sep 12, 2024
5aa15e0
fix: update
JeyJeyGao Sep 12, 2024
e2b246d
fix: update
JeyJeyGao Sep 13, 2024
982561e
fix: update
JeyJeyGao Sep 13, 2024
f4400ac
fix: update
JeyJeyGao Sep 14, 2024
f422da2
fix: update
JeyJeyGao Sep 14, 2024
50f6e6d
fix: update
JeyJeyGao Sep 14, 2024
deec55e
fix: update
JeyJeyGao Sep 14, 2024
c6132af
fix: update
JeyJeyGao Sep 14, 2024
2bf101a
fix: update
JeyJeyGao Sep 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 291 additions & 0 deletions revocation/internal/crl/crl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
// 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 crl provides methods for checking the revocation status of a
// certificate using CRL
package crl

import (
"context"
"crypto/x509"
"encoding/asn1"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/notaryproject/notation-core-go/revocation/result"
)

// RevocationMethodCRL represents the CRL revocation method
const RevocationMethodCRL = 2

var (
// oidFreshestCRL is the object identifier for the distribution point
// for the delta CRL. (See RFC 5280, Section 5.2.6)
oidFreshestCRL = asn1.ObjectIdentifier{2, 5, 29, 46}

// oidIssuingDistributionPoint is the object identifier for the issuing
// distribution point CRL extension. (See RFC 5280, Section 5.2.5)
oidIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}

// oidInvalidityDate is the object identifier for the invalidity date
// CRL entry extension. (See RFC 5280, Section 5.3.2)
oidInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24}
)

// maxCRLSize is the maximum size of CRL in bytes
//
// CRL examples: https://chasersystems.com/blog/an-analysis-of-certificate-revocation-list-sizes/
const maxCRLSize = 32 * 1024 * 1024 // 32 MiB

// CertCheckStatusOptions specifies values that are needed to check CRL
type CertCheckStatusOptions struct {
// HTTPClient is the HTTP client used to download CRL
HTTPClient *http.Client

// SigningTime is used to compare with the invalidity date during revocation
// check
SigningTime time.Time
}

// CertCheckStatus checks the revocation status of a certificate using CRL
//
// The function checks the revocation status of the certificate by downloading
// the CRL from the CRL distribution points specified in the certificate.
//
// If the invalidity date extension is present in the CRL entry and SigningTime
// is not zero, the certificate is considered revoked if the SigningTime is
// after the invalidity date. (See RFC 5280, Section 5.3.2)
func CertCheckStatus(ctx context.Context, cert, issuer *x509.Certificate, opts CertCheckStatusOptions) *result.CertRevocationResult {
if !Supported(cert) {
// CRL not enabled for this certificate.
return &result.CertRevocationResult{
Result: result.ResultNonRevokable,
ServerResults: []*result.ServerResult{{
RevocationMethod: RevocationMethodCRL,
Result: result.ResultNonRevokable,
}},
RevocationMethod: RevocationMethodCRL,
}
}

// The CRLDistributionPoints contains the URIs of all the CRL distribution
// points. Since it does not distinguish the reason field, it needs to check
// all the URIs to avoid missing any partial CRLs.
//
// For the majority of the certificates, there is only one CRL distribution
// point with one CRL URI, which will be cached, so checking all the URIs is
// not a performance issue.
var (
results []*result.ServerResult
JeyJeyGao marked this conversation as resolved.
Show resolved Hide resolved
lastErr error
crlURL string
)
for _, crlURL = range cert.CRLDistributionPoints {
baseCRL, err := download(ctx, crlURL, opts.HTTPClient)
if err != nil {
lastErr = fmt.Errorf("failed to download CRL from %s: %w", crlURL, err)
break
}

if err = validate(baseCRL, issuer); err != nil {
lastErr = fmt.Errorf("failed to validate CRL from %s: %w", crlURL, err)
break
}

crlResult, err := checkRevocation(cert, baseCRL, opts.SigningTime, crlURL)
if err != nil {
lastErr = fmt.Errorf("failed to check revocation status from %s: %w", crlURL, err)
break
}
if crlResult.Result == result.ResultRevoked {
return &result.CertRevocationResult{
Result: result.ResultRevoked,
ServerResults: []*result.ServerResult{crlResult},
RevocationMethod: RevocationMethodCRL,
}
}

results = append(results, crlResult)
}

if lastErr != nil {
return &result.CertRevocationResult{
Result: result.ResultUnknown,
ServerResults: []*result.ServerResult{
{
Result: result.ResultUnknown,
Server: crlURL,
Error: lastErr,
RevocationMethod: RevocationMethodCRL,
}},
RevocationMethod: RevocationMethodCRL,
}
}

return &result.CertRevocationResult{
Result: result.ResultOK,
ServerResults: results,
RevocationMethod: RevocationMethodCRL,
}
}

// Supported checks if the certificate supports CRL.
func Supported(cert *x509.Certificate) bool {
priteshbandi marked this conversation as resolved.
Show resolved Hide resolved
return cert != nil && len(cert.CRLDistributionPoints) > 0
}

func validate(crl *x509.RevocationList, issuer *x509.Certificate) error {
// check signature
if err := crl.CheckSignatureFrom(issuer); err != nil {
return fmt.Errorf("CRL is not signed by CA %s: %w,", issuer.Subject, err)
}

// check validity
now := time.Now()
if !crl.NextUpdate.IsZero() && now.After(crl.NextUpdate) {
return fmt.Errorf("expired CRL. Current time %v is after CRL NextUpdate %v", now, crl.NextUpdate)
}

for _, ext := range crl.Extensions {
switch {
case ext.Id.Equal(oidFreshestCRL):
priteshbandi marked this conversation as resolved.
Show resolved Hide resolved
return ErrDeltaCRLNotSupported
case ext.Id.Equal(oidIssuingDistributionPoint):
// IssuingDistributionPoint is a critical extension that identifies
// the scope of the CRL. Since we will check all the CRL
// distribution points, it is not necessary to check this extension.
default:
if ext.Critical {
// unsupported critical extensions is not allowed. (See RFC 5280, Section 5.2)
return fmt.Errorf("unsupported critical extension found in CRL: %v", ext.Id)
}
}
}

return nil
}

// 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) {
if cert == nil {
return nil, errors.New("certificate cannot be nil")
}

if baseCRL == nil {
return nil, errors.New("baseCRL cannot be nil")
}

for _, revocationEntry := range baseCRL.RevokedCertificateEntries {
if revocationEntry.SerialNumber.Cmp(cert.SerialNumber) == 0 {
extensions, err := parseEntryExtensions(revocationEntry)
if err != nil {
return nil, err
}

// validate signingTime and invalidityDate
if !signingTime.IsZero() && !extensions.invalidityDate.IsZero() &&
signingTime.Before(extensions.invalidityDate) {
// signing time is before the invalidity date which means the
// certificate is not revoked at the time of signing.
break
}

// revoked
return &result.ServerResult{
Result: result.ResultRevoked,
Server: crlURL,
RevocationMethod: RevocationMethodCRL,
}, nil
}
}

return &result.ServerResult{
Result: result.ResultOK,
Server: crlURL,
RevocationMethod: RevocationMethodCRL,
}, nil
}

type entryExtensions struct {
// invalidityDate is the date when the key is invalid.
invalidityDate time.Time
}

func parseEntryExtensions(entry x509.RevocationListEntry) (entryExtensions, error) {
var extensions entryExtensions
for _, ext := range entry.Extensions {
switch {
case ext.Id.Equal(oidInvalidityDate):
var invalidityDate time.Time
rest, err := asn1.UnmarshalWithParams(ext.Value, &invalidityDate, "generalized")
if err != nil {
return entryExtensions{}, fmt.Errorf("failed to parse invalidity date: %w", err)
}
if len(rest) > 0 {
return entryExtensions{}, fmt.Errorf("invalid invalidity date extension: trailing data")
}

extensions.invalidityDate = invalidityDate
default:
if ext.Critical {
// unsupported critical extensions is not allowed. (See RFC 5280, Section 5.2)
return entryExtensions{}, fmt.Errorf("unsupported critical extension found in CRL: %v", ext.Id)
}
}
}

return extensions, nil
}

func download(ctx context.Context, crlURL string, client *http.Client) (*x509.RevocationList, error) {
// validate URL
parsedURL, err := url.Parse(crlURL)
if err != nil {
return nil, fmt.Errorf("invalid CRL URL: %w", err)
}
if parsedURL.Scheme != "http" {
return nil, fmt.Errorf("unsupported CRL endpoint: %s. Only urls with HTTP scheme is supported", crlURL)
}

// download CRL
req, err := http.NewRequestWithContext(ctx, http.MethodGet, crlURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create CRL request %q: %w", crlURL, err)
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed for %q: %w", crlURL, err)
}
defer resp.Body.Close()

// check response
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
JeyJeyGao marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("%s %q: failed to download with status code: %d", resp.Request.Method, resp.Request.URL, resp.StatusCode)
}

// read with size limit
limitedReader := io.LimitReader(resp.Body, maxCRLSize)
data, err := io.ReadAll(limitedReader)
if err != nil {
return nil, fmt.Errorf("failed to read CRL response from %q: %w", resp.Request.URL, err)
}
if len(data) == maxCRLSize {
return nil, fmt.Errorf("%s %q: CRL size reached the %d MiB size limit", resp.Request.Method, resp.Request.URL, maxCRLSize/1024/1024)
}

return x509.ParseRevocationList(data)
}
Loading
Loading