1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
From: Patrick Zheng <patrickzheng@microsoft.com>
Date: Mon, 2 Dec 2024 08:30:56 +0800
Subject: fix: enable timestamping cert chain revocation check during signing
(#482)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
---
example_signWithTimestmap_test.go | 17 ++++++++++++++---
notation.go | 6 ++++++
signer/signer.go | 22 +++++++++++++---------
signer/signer_test.go | 23 +++++++++++++++++++++++
4 files changed, 56 insertions(+), 12 deletions(-)
diff --git a/example_signWithTimestmap_test.go b/example_signWithTimestmap_test.go
index 8e0ebe5..ef4eeb4 100644
--- a/example_signWithTimestmap_test.go
+++ b/example_signWithTimestmap_test.go
@@ -21,6 +21,8 @@ import (
"oras.land/oras-go/v2/registry/remote"
+ "github.com/notaryproject/notation-core-go/revocation"
+ "github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/testhelper"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/registry"
@@ -77,12 +79,21 @@ func Example_signWithTimestamp() {
tsaRootCAs := x509.NewCertPool()
tsaRootCAs.AddCert(tsaRootCert)
+ // enable timestamping certificate chain revocation check
+ tsaRevocationValidator, err := revocation.NewWithOptions(revocation.Options{
+ CertChainPurpose: purpose.Timestamping,
+ })
+ if err != nil {
+ panic(err) // Handle error
+ }
+
// exampleSignOptions is an example of notation.SignOptions.
exampleSignOptions := notation.SignOptions{
SignerSignOptions: notation.SignerSignOptions{
- SignatureMediaType: exampleSignatureMediaType,
- Timestamper: httpTimestamper,
- TSARootCAs: tsaRootCAs,
+ SignatureMediaType: exampleSignatureMediaType,
+ Timestamper: httpTimestamper,
+ TSARootCAs: tsaRootCAs,
+ TSARevocationValidator: tsaRevocationValidator,
},
ArtifactReference: exampleArtifactReference,
}
diff --git a/notation.go b/notation.go
index d64fd2c..bb051d1 100644
--- a/notation.go
+++ b/notation.go
@@ -29,6 +29,7 @@ import (
orasRegistry "oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote"
+ "github.com/notaryproject/notation-core-go/revocation"
"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/signature/cose"
"github.com/notaryproject/notation-core-go/signature/jws"
@@ -67,6 +68,11 @@ type SignerSignOptions struct {
// TSARootCAs is the cert pool holding caller's TSA trust anchor
TSARootCAs *x509.CertPool
+
+ // TSARevocationValidator is used for validating revocation status of
+ // timestamping certificate chain with context during signing.
+ // When present, only used when timestamping is performed.
+ TSARevocationValidator revocation.Validator
}
// Signer is a generic interface for signing an OCI artifact.
diff --git a/signer/signer.go b/signer/signer.go
index 40d23ec..ae77f8c 100644
--- a/signer/signer.go
+++ b/signer/signer.go
@@ -107,7 +107,6 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
if err != nil {
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
}
-
var signingAgentId string
if opts.SigningAgent != "" {
signingAgentId = opts.SigningAgent
@@ -125,12 +124,13 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
ContentType: envelope.MediaTypePayloadV1,
Content: payloadBytes,
},
- Signer: s.signer,
- SigningTime: time.Now(),
- SigningScheme: signature.SigningSchemeX509,
- SigningAgent: signingAgentId,
- Timestamper: opts.Timestamper,
- TSARootCAs: opts.TSARootCAs,
+ Signer: s.signer,
+ SigningTime: time.Now(),
+ SigningScheme: signature.SigningSchemeX509,
+ SigningAgent: signingAgentId,
+ Timestamper: opts.Timestamper,
+ TSARootCAs: opts.TSARootCAs,
+ TSARevocationValidator: opts.TSARevocationValidator,
}
// Add expiry only if ExpiryDuration is not zero
@@ -144,6 +144,12 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
logger.Debugf(" Expiry: %v", signReq.Expiry)
logger.Debugf(" SigningScheme: %v", signReq.SigningScheme)
logger.Debugf(" SigningAgent: %v", signReq.SigningAgent)
+ if signReq.Timestamper != nil {
+ logger.Debug("Enabled timestamping")
+ if signReq.TSARevocationValidator != nil {
+ logger.Debug("Enabled timestamping certificate chain revocation check")
+ }
+ }
// Add ctx to the SignRequest
signReq = signReq.WithContext(ctx)
@@ -153,12 +159,10 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
if err != nil {
return nil, nil, err
}
-
sig, err := sigEnv.Sign(signReq)
if err != nil {
return nil, nil, err
}
-
envContent, err := sigEnv.Verify()
if err != nil {
return nil, nil, fmt.Errorf("generated signature failed verification: %v", err)
diff --git a/signer/signer_test.go b/signer/signer_test.go
index 80fc4af..2e9a844 100644
--- a/signer/signer_test.go
+++ b/signer/signer_test.go
@@ -31,6 +31,8 @@ import (
"testing"
"time"
+ "github.com/notaryproject/notation-core-go/revocation"
+ "github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/signature"
_ "github.com/notaryproject/notation-core-go/signature/cose"
_ "github.com/notaryproject/notation-core-go/signature/jws"
@@ -259,6 +261,27 @@ t.Skip("Disabled because it tries to access the network")
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
}
+
+ // timestamping with unknown authority
+ desc, sOpts = generateSigningContent()
+ sOpts.SignatureMediaType = envelopeType
+ sOpts.Timestamper, err = tspclient.NewHTTPTimestamper(nil, rfc3161URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sOpts.TSARootCAs = x509.NewCertPool()
+ tsaRevocationValidator, err := revocation.NewWithOptions(revocation.Options{
+ CertChainPurpose: purpose.Timestamping,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ sOpts.TSARevocationValidator = tsaRevocationValidator
+ _, _, err = s.Sign(ctx, desc, sOpts)
+ expectedErrMsg = "timestamp: failed to verify signed token: cms verification failure: x509: certificate signed by unknown authority"
+ if err == nil || err.Error() != expectedErrMsg {
+ t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
+ }
}
func TestSignWithoutExpiry(t *testing.T) {
|