From a034721a7e3088250bbb04c5fccedbfca966d49e Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 23 Jul 2024 07:50:01 +0800 Subject: [PATCH] feat: Timestamp (#978) Signed-off-by: Patrick Zheng --- cmd/notation/cert/add.go | 3 + cmd/notation/cert/delete.go | 3 + cmd/notation/cert/list.go | 3 + cmd/notation/cert/show.go | 3 + cmd/notation/sign.go | 80 ++++++++++++++--- go.mod | 12 +-- go.sum | 22 ++--- internal/httputil/client.go | 42 ++++++++- internal/testdata/intermediate.pem | 17 ++++ internal/testdata/notSelfIssued.crt | 21 +++++ internal/testdata/self-signed.crt | 20 +++++ internal/testdata/tsaRootCA.cer | Bin 0 -> 1428 bytes internal/trace/transport.go | 16 ++-- internal/x509/cert.go | 28 ++++++ internal/x509/cert_test.go | 67 +++++++++++++++ specs/commandline/sign.md | 46 ++++++---- specs/commandline/verify.md | 40 ++++++++- test/e2e/go.mod | 5 +- test/e2e/go.sum | 14 +-- test/e2e/internal/notation/host.go | 30 +++++++ test/e2e/plugin/go.mod | 11 +-- test/e2e/plugin/go.sum | 22 ++--- test/e2e/suite/command/sign.go | 81 ++++++++++++++++++ test/e2e/suite/command/verify.go | 34 ++++++++ .../e2e/suite/trustpolicy/multi_statements.go | 6 +- test/e2e/suite/trustpolicy/registry_scope.go | 4 +- .../suite/trustpolicy/verification_level.go | 6 +- .../testdata/config/timestamp/CertChain.pem | 22 +++++ .../timestamp/DigiCertTSARootSHA384.cer | Bin 0 -> 1428 bytes test/e2e/testdata/config/timestamp/Empty.txt | 0 .../config/timestamp/globalsignTSARoot.cer | Bin 0 -> 1415 bytes .../config/timestamp/intermediate.pem | 17 ++++ .../e2e/testdata/config/timestamp/invalid.crt | 2 + .../config/timestamp/notSelfIssued.crt | 21 +++++ ...mestamp_after_cert_expiry_trustpolicy.json | 17 ++++ .../trustpolicies/timestamp_trustpolicy.json | 17 ++++ 36 files changed, 649 insertions(+), 83 deletions(-) create mode 100644 internal/testdata/intermediate.pem create mode 100644 internal/testdata/notSelfIssued.crt create mode 100644 internal/testdata/self-signed.crt create mode 100644 internal/testdata/tsaRootCA.cer create mode 100644 internal/x509/cert.go create mode 100644 internal/x509/cert_test.go create mode 100644 test/e2e/testdata/config/timestamp/CertChain.pem create mode 100644 test/e2e/testdata/config/timestamp/DigiCertTSARootSHA384.cer create mode 100644 test/e2e/testdata/config/timestamp/Empty.txt create mode 100644 test/e2e/testdata/config/timestamp/globalsignTSARoot.cer create mode 100644 test/e2e/testdata/config/timestamp/intermediate.pem create mode 100644 test/e2e/testdata/config/timestamp/invalid.crt create mode 100644 test/e2e/testdata/config/timestamp/notSelfIssued.crt create mode 100644 test/e2e/testdata/config/trustpolicies/timestamp_after_cert_expiry_trustpolicy.json create mode 100644 test/e2e/testdata/config/trustpolicies/timestamp_trustpolicy.json diff --git a/cmd/notation/cert/add.go b/cmd/notation/cert/add.go index 4796436ae..c4d489d31 100644 --- a/cmd/notation/cert/add.go +++ b/cmd/notation/cert/add.go @@ -48,6 +48,9 @@ Example - Add a certificate to the "ca" type of a named store "acme-rockets": Example - Add a certificate to the "signingAuthority" type of a named store "wabbit-networks": notation cert add --type signingAuthority --store wabbit-networks wabbit-networks.pem + +Example - Add a certificate to the "tsa" type of a named store "timestamp": + notation cert add --type tsa --store timestamp wabbit-networks-timestamp.pem `, RunE: func(cmd *cobra.Command, args []string) error { return addCerts(opts) diff --git a/cmd/notation/cert/delete.go b/cmd/notation/cert/delete.go index 8b917b653..044f240c0 100644 --- a/cmd/notation/cert/delete.go +++ b/cmd/notation/cert/delete.go @@ -59,6 +59,9 @@ Example - Delete certificate "cert1.pem" with "signingAuthority" type from trust Example - Delete all certificates with "ca" type from the trust store "acme-rockets", without prompt for confirmation: notation cert delete --type ca --store acme-rockets -y --all + +Example - Delete certificate "wabbit-networks-timestamp.pem" with "tsa" type from trust store timestamp: + notation cert delete --type tsa --store timestamp wabbit-networks-timestamp.pem -y `, RunE: func(cmd *cobra.Command, args []string) error { return deleteCerts(opts) diff --git a/cmd/notation/cert/list.go b/cmd/notation/cert/list.go index bcaceebc3..6321ff7b8 100644 --- a/cmd/notation/cert/list.go +++ b/cmd/notation/cert/list.go @@ -54,6 +54,9 @@ Example - List all certificate files from trust store of type "ca" Example - List all certificate files from trust store "wabbit-networks" of type "signingAuthority" notation cert ls --type signingAuthority --store "wabbit-networks" + +Example - List all certificate files from trust store of type "tsa" + notation cert ls --type tsa `, RunE: func(cmd *cobra.Command, args []string) error { return listCerts(cmd.Context(), opts) diff --git a/cmd/notation/cert/show.go b/cmd/notation/cert/show.go index f8d561b7d..cc0ff1464 100644 --- a/cmd/notation/cert/show.go +++ b/cmd/notation/cert/show.go @@ -57,6 +57,9 @@ Example - Show details of certificate "cert1.pem" with type "ca" from trust stor Example - Show details of certificate "cert2.pem" with type "signingAuthority" from trust store "wabbit-networks": notation cert show --type signingAuthority --store wabbit-networks cert2.pem + +Example - Show details of certificate "wabbit-networks-timestamp.pem" with type "tsa" from trust store "timestamp": + notation cert show --type tsa --store timestamp wabbit-networks-timestamp.pem `, RunE: func(cmd *cobra.Command, args []string) error { return showCerts(cmd.Context(), opts) diff --git a/cmd/notation/sign.go b/cmd/notation/sign.go index 23b227a49..40c951028 100644 --- a/cmd/notation/sign.go +++ b/cmd/notation/sign.go @@ -14,34 +14,47 @@ package main import ( + "crypto/x509" "errors" "fmt" + "net/http" "os" "strings" "time" + corex509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation/cmd/notation/internal/experimental" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/envelope" + "github.com/notaryproject/notation/internal/httputil" + nx509 "github.com/notaryproject/notation/internal/x509" + "github.com/notaryproject/tspclient-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" + "golang.org/x/net/context" ) const referrersTagSchemaDeleteError = "failed to delete dangling referrers index" +// timestampingTimeout is the timeout when requesting timestamp countersignature +// from a TSA +const timestampingTimeout = 15 * time.Second + type signOpts struct { cmd.LoggingFlagOpts cmd.SignerFlagOpts SecureFlagOpts - expiry time.Duration - pluginConfig []string - userMetadata []string - reference string - allowReferrersAPI bool - forceReferrersTag bool - ociLayout bool - inputType inputType + expiry time.Duration + pluginConfig []string + userMetadata []string + reference string + allowReferrersAPI bool + forceReferrersTag bool + ociLayout bool + inputType inputType + tsaServerURL string + tsaRootCertificatePath string } func signCommand(opts *signOpts) *cobra.Command { @@ -74,6 +87,9 @@ Example - Sign an OCI artifact stored in a registry and specify the signature ex Example - Sign an OCI artifact and store signature using the Referrers API. If it's not supported, fallback to the Referrers tag schema notation sign --force-referrers-tag=false /@ + +Example - Sign an OCI artifact with timestamping: + notation sign --timestamp-url --timestamp-root-cert /@ ` experimentalExamples := ` Example - [Experimental] Sign an OCI artifact referenced in an OCI layout @@ -101,6 +117,16 @@ Example - [Experimental] Sign an OCI artifact identified by a tag and referenced return experimental.CheckFlagsAndWarn(cmd, "allow-referrers-api", "oci-layout") }, RunE: func(cmd *cobra.Command, args []string) error { + // timestamping + if cmd.Flags().Changed("timestamp-url") { + if opts.tsaServerURL == "" { + return errors.New("timestamping: tsa url cannot be empty") + } + if opts.tsaRootCertificatePath == "" { + return errors.New("timestamping: tsa root certificate path cannot be empty") + } + } + // allow-referrers-api flag is set if cmd.Flags().Changed("allow-referrers-api") { if opts.allowReferrersAPI { @@ -120,9 +146,12 @@ Example - [Experimental] Sign an OCI artifact identified by a tag and referenced cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig) cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataSignUsage) cmd.SetPflagReferrersAPI(command.Flags(), &opts.allowReferrersAPI, fmt.Sprintf(cmd.PflagReferrersUsageFormat, "sign")) + command.Flags().StringVar(&opts.tsaServerURL, "timestamp-url", "", "RFC 3161 Timestamping Authority (TSA) server URL") + command.Flags().StringVar(&opts.tsaRootCertificatePath, "timestamp-root-cert", "", "filepath of timestamp authority root certificate") cmd.SetPflagReferrersTag(command.Flags(), &opts.forceReferrersTag, "force to store signatures using the referrers tag schema") command.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] sign the artifact stored as OCI image layout") command.MarkFlagsMutuallyExclusive("oci-layout", "force-referrers-tag", "allow-referrers-api") + command.MarkFlagsRequiredTogether("timestamp-url", "timestamp-root-cert") experimental.HideFlags(command, experimentalExamples, []string{"oci-layout"}) return command } @@ -140,7 +169,7 @@ func runSign(command *cobra.Command, cmdOpts *signOpts) error { if err != nil { return err } - signOpts, err := prepareSigningOpts(cmdOpts) + signOpts, err := prepareSigningOpts(ctx, cmdOpts) if err != nil { return err } @@ -168,7 +197,7 @@ func runSign(command *cobra.Command, cmdOpts *signOpts) error { return nil } -func prepareSigningOpts(opts *signOpts) (notation.SignOptions, error) { +func prepareSigningOpts(ctx context.Context, opts *signOpts) (notation.SignOptions, error) { mediaType, err := envelope.GetEnvelopeMediaType(opts.SignerFlagOpts.SignatureFormat) if err != nil { return notation.SignOptions{}, err @@ -189,5 +218,36 @@ func prepareSigningOpts(opts *signOpts) (notation.SignOptions, error) { }, UserMetadata: userMetadata, } + if opts.tsaServerURL != "" { + // timestamping + fmt.Printf("Configured to timestamp with TSA %q\n", opts.tsaServerURL) + signOpts.Timestamper, err = tspclient.NewHTTPTimestamper(httputil.NewClient(ctx, &http.Client{Timeout: timestampingTimeout}), opts.tsaServerURL) + if err != nil { + return notation.SignOptions{}, fmt.Errorf("cannot get http timestamper for timestamping: %w", err) + } + + rootCerts, err := corex509.ReadCertificateFile(opts.tsaRootCertificatePath) + if err != nil { + return notation.SignOptions{}, err + } + if len(rootCerts) == 0 { + return notation.SignOptions{}, fmt.Errorf("cannot find any certificate from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) + } + if len(rootCerts) > 1 { + return notation.SignOptions{}, fmt.Errorf("found more than one certificates from %q. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) + } + tsaRootCert := rootCerts[0] + isRoot, err := nx509.IsRootCertificate(tsaRootCert) + if err != nil { + return notation.SignOptions{}, fmt.Errorf("failed to check root certificate with error: %w", err) + } + if !isRoot { + return notation.SignOptions{}, fmt.Errorf("certificate from %q is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file", opts.tsaRootCertificatePath) + + } + rootCAs := x509.NewCertPool() + rootCAs.AddCert(tsaRootCert) + signOpts.TSARootCAs = rootCAs + } return signOpts, nil } diff --git a/go.mod b/go.mod index 5ba669514..8989c6da8 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,22 @@ module github.com/notaryproject/notation go 1.22 require ( - github.com/notaryproject/notation-core-go v1.0.3 - github.com/notaryproject/notation-go v1.1.1 + github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b + github.com/notaryproject/notation-go v1.1.1-0.20240719045753-83409204754a + github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 + golang.org/x/net v0.22.0 golang.org/x/term v0.22.0 oras.land/oras-go/v2 v2.5.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect @@ -25,8 +27,8 @@ require ( github.com/notaryproject/notation-plugin-framework-go v1.0.0 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index be0b481c3..d3b526db0 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= @@ -35,12 +35,14 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/notaryproject/notation-core-go v1.0.3 h1:FCgvULSypEFrrNgvDRdHbKAGAgbXK43n/jKD9q2WECA= -github.com/notaryproject/notation-core-go v1.0.3/go.mod h1:eDo5/LTUp23mB7w0CckJLnl+p93oGdyiKDzzggpqTH4= -github.com/notaryproject/notation-go v1.1.1 h1:EAY8ERBWhrdaG9MIumSZ9xyUHktgr6OkCByd75HR+FA= -github.com/notaryproject/notation-go v1.1.1/go.mod h1:XykI2i5jHb6cGf+bcG/cIeNfNO2u4Xoy2mkuOKHjVVI= +github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b h1:uJ4bmNieZRkPj3UgmKr3bZr8vs7UJ2MdlJMeB0oOaZw= +github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b/go.mod h1:MdxSbL9F5h63EmtXWfYMWy7hEmGmOmsfN4B6KM2WyhY= +github.com/notaryproject/notation-go v1.1.1-0.20240719045753-83409204754a h1:o3kYOcQii0dMaDKdxnr1wPlEskXHHkDZDDb3kuss+W0= +github.com/notaryproject/notation-go v1.1.1-0.20240719045753-83409204754a/go.mod h1:FwHtZC29bBvFdJu0NYM5MHxSrHJGwhkPRvEgevNo9wo= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= +github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172 h1:Q8UsmeFMzyFuMMq4dlbIRJUi7khEKXKUe2H2Hm3W92Y= +github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -73,12 +75,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/internal/httputil/client.go b/internal/httputil/client.go index ee0324ed5..c9c39f676 100644 --- a/internal/httputil/client.go +++ b/internal/httputil/client.go @@ -22,14 +22,50 @@ import ( "oras.land/oras-go/v2/registry/remote/auth" ) -// NewAuthClient returns an *auth.Client +var userAgent = "notation/" + version.GetVersion() + +// NewAuthClient returns an *auth.Client with debug log and user agent set func NewAuthClient(ctx context.Context, httpClient *http.Client) *auth.Client { + httpClient = trace.SetHTTPDebugLog(ctx, httpClient) client := &auth.Client{ Client: httpClient, Cache: auth.NewCache(), ClientID: "notation", } - client.SetUserAgent("notation/" + version.GetVersion()) - trace.SetHTTPDebugLog(ctx, client) + client.SetUserAgent(userAgent) + return client +} + +// NewClient returns an *http.Client with debug log and user agent set +func NewClient(ctx context.Context, client *http.Client) *http.Client { + client = trace.SetHTTPDebugLog(ctx, client) + return SetUserAgent(client) +} + +type userAgentTransport struct { + base http.RoundTripper +} + +// RoundTrip returns t.Base.RoundTrip with user agent set in the request Header +func (t *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) { + r := req.Clone(req.Context()) + if r.Header == nil { + r.Header = http.Header{} + } + r.Header.Set("User-Agent", userAgent) + return t.base.RoundTrip(r) +} + +// SetUserAgent sets the user agent for all out-going requests. +func SetUserAgent(client *http.Client) *http.Client { + if client == nil { + client = &http.Client{} + } + if client.Transport == nil { + client.Transport = http.DefaultTransport + } + client.Transport = &userAgentTransport{ + base: client.Transport, + } return client } diff --git a/internal/testdata/intermediate.pem b/internal/testdata/intermediate.pem new file mode 100644 index 000000000..83e1ccece --- /dev/null +++ b/internal/testdata/intermediate.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyjCCAbKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290 +MCAXDTIyMDYzMDE5MjAwM1oYDzMwMjExMDMxMTkyMDAzWjAYMRYwFAYDVQQDDA1J +bnRlcm1lZGlhdGUxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTs +aiC/7+bho43kMVyHDwCsuocYp4PvYahB59NsKDR4QbrImU5ziaQ94D0DQqthe9pm +qOW0SxN/vSRJAZFELxacrB9hc1y4MjiDYaRSt/LVx7astylBV/QRpmxWSEqp0Avu +6nMJivIa1sD0WIEchizx6jG9BI5ULr9LbJICYvMgDalQR+0JGG+rKWnf1mPZyxEu +9zEh215LCg5K56P3W5kC8fKBXSdSgTqZAvHzp6u78qet9S8gARtOEfS03A/7y7MC +U0Sn2wdQyQdci0PBsR2sTZvUw179Cr93r5aRbb3I6jXgMWHAP2vvIndb9CM9ePyY +yEy4Je7oWVVfMQ3CWQIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud +DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEALR0apUQVbWGmagLUz4Y/bRsl +mY9EJJXCiLuSxVWd3offjZfQTlGkQkCAW9FOQnm7JhEtaaHF1+AEVLo56/Gsd/hk +sXsrBagYGi72jun7QTb6j7iZ3X9zanrP3SjdkpjVnqxRfH83diSh0r68Xruq1NSK +qhUy1V+KQaXF0SSEutPqdTCoXUyxyXohVLU78uqZX/jx9Nc1XDuW9AZd+hMsLdk8 +qGJqHYFvj2vOHGMTeYk8dWgMBthQeL0wdsg2AvKtAvn6FQXCN7mKCWjpFTtYsU8v +NsesS9M/i+geJjR/8/DDT3RP7S100BtCMm4XfHfmKcjXVaBh5evQVqGsa6TKLw== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/internal/testdata/notSelfIssued.crt b/internal/testdata/notSelfIssued.crt new file mode 100644 index 000000000..8f53e9ad5 --- /dev/null +++ b/internal/testdata/notSelfIssued.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJVUzEJ +MAcGA1UECBMAMQkwBwYDVQQHEwAxDzANBgNVBAoTBk5vdGFyeTENMAsGA1UEAxME +dGVzdDAeFw0yNDA3MjIwNTIwMzZaFw0yNDA4MjIwNTIwMzZaMEwxCzAJBgNVBAYT +AlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90 +YXJ5MQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAp7XmYGf3p4JS/y5v6AIc/TxQvPTwIRxVmctNmDm5kG3LDUoGAAJbicTUfI/0 +Un38j/PlHNKQz9hKPwV+oYotKuQrVhaA2fft+INl36tvgCAPr8yX3ToOMCLr/UlT +zQ7o9TB7IpnVT9DR9uik9MWfkz0Db5ARG1POquvSy2QM5wseEA58313YJ/7Em/Cq +FCH5s9THCfQKpb09MZ/RTEggNqU4zGADah8e1KieYeZntM/hrw7sW5oeUueKG4D4 +3kvL8o7n1k6C+w8LwaOGhYXCQ51JxTE3lnmTrDdFRuKGObpNFNbLxdJPVLuHT1Nu +bwVxj5APBJQyEyja3jJ9qLQANwIDAQABo1owWDAOBgNVHQ8BAf8EBAMCAgQwEwYD +VR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU +yPScYtI4hs0+ibHaRV9BsgY734AwDQYJKoZIhvcNAQELBQADggEBABJHh3NELq1b +jcJiJX76DwDTx+FGGN96/+T5622FGg1kHeAwuxS6pQODJNrVofbrhGAqaXTDT/Tz +0b0AA5XCohmBFZQRwMh+C5QkFiIcZ9VMMBc6KTQT8DEgjI6Qo/OW2TDGOoFuAhmh +4a1ACHszuHS55Th+0TKLqeZNA6DnL9IBm0RX1FJXbqhjX52ZnRH3Zqe7uML+kxKt +LUdfnxHrpA1G2ugyAj+K7K6vth5QpezwCS1PZD2s5vlJd6clawxm5qRyyU46ow/y +7bpTSEyg6PIWWh/qv4O2t4NMa1OoRkIXx/ppsKH9XwbRg/WZ0VWlGVS6GxBtLSpG +tkyaxSRLdz0= +-----END CERTIFICATE----- diff --git a/internal/testdata/self-signed.crt b/internal/testdata/self-signed.crt new file mode 100644 index 000000000..dd0094e90 --- /dev/null +++ b/internal/testdata/self-signed.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPjCCAiagAwIBAgIBeTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEP +MA0GA1UEAxMGYWxwaW5lMB4XDTIzMDUwOTA0NTUxMloXDTMzMDUxMDA0NTUxMlow +TjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8w +DQYDVQQKEwZOb3RhcnkxDzANBgNVBAMTBmFscGluZTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAK5hpq1229GGLjMK6i9KZhuUO+SV7rUFnWIDiIPO5yWx +YDkl+bGroeAvJYu6MVCMQ6FMRXD9jhnG6R+sAHwY7gVgcJ1OXak87PkLp/Ii1Cr7 +XkkySZeD+Br1vSQzfxs3pFG+iBCeVVkeZdsg+xqwnAlqAILXwIbTGRyJP1Xiu9nw +OeuX1YmxPl2m29Pt1EtfVCL9COsVKt5LgOVyWP/9ISWevOBqSCU9bk35HFo9VTeU +f6+ffhSMjv0Y9uwkFFOKXpcV8Sa3ArqyBmgQlUfGg1iwYlqiDE0fTYxiB3gLgETA +lmTm50J+WB9LoDrnrQpbXFLoegm+JV+uSD8J8H7DL2sCAwEAAaMnMCUwDgYDVR0P +AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IB +AQAt0Nvna1c4pPn8kzoN5VvmFmeIgdO/BJpmdhdg0WIQ9aeN/xPXXaVjPp1Mk7ed +XHAvBwQr0Gyzqyy7g/h0gdnAFG7f6blrRNzbrRBCq6cNqX8iwgK/9+2OYKxk1QWj +8Gx0cvu1DN1aXjPPGgQ2j3tHjJvJv32J/zuZa8gU40RPPSLaBlc5ZjpFmyi29sKl +TeeZ+F/Ssic51qXXw2CsYGGWK5yQ3xSCxbw6bb2G/s/YI7/KlWg9BktBJHzRu04Z +NR77W7/dyJ3Lj17PlW1XKmMOFHsQivagXeRCbmYZ43fX4ugFRFKL7KE0EgmGOWpJ +0xv+6ig93sqHzQ/0uv1YgFov +-----END CERTIFICATE----- diff --git a/internal/testdata/tsaRootCA.cer b/internal/testdata/tsaRootCA.cer new file mode 100644 index 0000000000000000000000000000000000000000..99bcc84b7e68b5b28e4444f6fa21bc7c2baf497d GIT binary patch literal 1428 zcmXqLVx3^n#9Xm}nTe5!Nq}{>bojhJMWaWS?0c7&m&O?IvTn?JkswJuAWqo zP1e*s_a@Ho#N;1}iL*^!vmT3k6D_sp^~v*R*O)lOZ>&mtSAN1{MOt|H{E&z~9_{V^ z%MEUZy*pJM`*`h1|G1~7&kaxCnjCkhufO5ewuv(wCR84-IKFM;k*!%07R&;@H?Ej3 z(PORc_}XMAFtK2DXp^JS_1i4PT6q&0YZQI1>{%zxTpC-EcGJqxWtOqSeva!=o=Xlr zTe%?p?h^Gq3;iv(3Py;3SBY`!Px*c@v!iTAnQdgOQ(1fG^vo)c4-XazNvF*!Id#ul z?m1ubx@TA3Pnu*k&-M<(6Ia#FZL?e?wd)Q{*>Wi{_qFlOqxZd87|ztnOg-HHU2)SU z!R@>2KV9u9&~Z#ywJ}-3WvWzJQr)+P4ZmNcEHl2?$^LNf_GivZBz7z-XMD&%g-20# zQ;4Q&XUw*5U*l71pZrEK z{)j?gcK*iIZQcHduDQm~Rrs?|?&yL3MH}n5)MkEtlBqvKR`=`8m78RrN;5GtGB7T7 zGH@{92PS7(VMfOPEUX61K+1p*B)|_6U;*Z-HUn7@pN~b1MdZ!($4!?CV^e(Y>!sU2 z-!)^M48K2eDg$OPU@Bu|*qwN@c4f{!@gozZ4=-HA(EB(ggFozi`MQFie`k5k+~v@ z;|G_o$XKF&XYNn+bq1|Fzoq+H+4VTN((ZIU8a!-?Xwiwjs2;$JM? zvV8rD@42RPYNEQXEwY&TxuW}v?-ZoX1m*e!D1M{-ku zW1+3RZ-H_fkJp{XOJ|IxwD59pPM7gN@Ge`S#Ng5cOA~=sMNvkM7okS?3O#RXhzIyS z+vj_+bj^iRza4itFI{!{FsqBdj@j%-zaF{nP!7&v%TEujciZY?pQjO3sdj0}ilph6 zfR&$*WHWvetKHnrfA0t)=$1ze_=^}`TkG{L*Rlgt&`^}&Rl)f&LXk-+o%1iN!lfsC)s~w6 z?Tp>inj~vqxWQ7QGU@KxG3!Bhh+^5 ouE}lQ!_OaFs=4ZwaQTyaJ&lTM*#+DM*S6cTUo72o{&QL#0GZBIfB*mh literal 0 HcmV?d00001 diff --git a/internal/trace/transport.go b/internal/trace/transport.go index a83ebda19..6d6f9546c 100644 --- a/internal/trace/transport.go +++ b/internal/trace/transport.go @@ -36,7 +36,6 @@ import ( "github.com/notaryproject/notation-go/log" "github.com/sirupsen/logrus" - "oras.land/oras-go/v2/registry/remote/auth" ) // Transport is an http.RoundTripper that keeps track of the in-flight @@ -87,15 +86,16 @@ func logHeader(header http.Header, e log.Logger) { } // SetHTTPDebugLog sets up http debug log with logrus.Logger -func SetHTTPDebugLog(ctx context.Context, authClient *auth.Client) { +func SetHTTPDebugLog(ctx context.Context, client *http.Client) *http.Client { if logrusLog, ok := log.GetLogger(ctx).(*logrus.Logger); !ok || logrusLog.Level != logrus.DebugLevel { - return + return client } - if authClient.Client == nil { - authClient.Client = &http.Client{} + if client == nil { + client = &http.Client{} } - if authClient.Client.Transport == nil { - authClient.Client.Transport = http.DefaultTransport + if client.Transport == nil { + client.Transport = http.DefaultTransport } - authClient.Client.Transport = NewTransport(authClient.Client.Transport) + client.Transport = NewTransport(client.Transport) + return client } diff --git a/internal/x509/cert.go b/internal/x509/cert.go new file mode 100644 index 000000000..c72ef76c4 --- /dev/null +++ b/internal/x509/cert.go @@ -0,0 +1,28 @@ +// 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 x509 + +import ( + "bytes" + "crypto/x509" +) + +// IsRootCertificate returns true if cert is a root certificate. +// A root certificate MUST be a self-signed and self-issued certificate. +func IsRootCertificate(cert *x509.Certificate) (bool, error) { + if err := cert.CheckSignatureFrom(cert); err != nil { + return false, err + } + return bytes.Equal(cert.RawSubject, cert.RawIssuer), nil +} diff --git a/internal/x509/cert_test.go b/internal/x509/cert_test.go new file mode 100644 index 000000000..3fb7fd7e4 --- /dev/null +++ b/internal/x509/cert_test.go @@ -0,0 +1,67 @@ +// 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 x509 + +import ( + "testing" + + corex509 "github.com/notaryproject/notation-core-go/x509" +) + +func TestIsRootCertificate(t *testing.T) { + tsaRoot, err := corex509.ReadCertificateFile("../testdata/tsaRootCA.cer") + if err != nil { + t.Fatal(err) + } + isRoot, err := IsRootCertificate(tsaRoot[0]) + if err != nil { + t.Fatal(err) + } + if !isRoot { + t.Fatal("expected IsRootCertificate to return true") + } + + intermediate, err := corex509.ReadCertificateFile("../testdata/intermediate.pem") + if err != nil { + t.Fatal(err) + } + expectedErrMsg := "crypto/rsa: verification error" + _, err = IsRootCertificate(intermediate[0]) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + + selfSigned, err := corex509.ReadCertificateFile("../testdata/self-signed.crt") + if err != nil { + t.Fatal(err) + } + expectedErrMsg = "x509: invalid signature: parent certificate cannot sign this kind of certificate" + _, err = IsRootCertificate(selfSigned[0]) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + + notSelfIssued, err := corex509.ReadCertificateFile("../testdata/notSelfIssued.crt") + if err != nil { + t.Fatal(err) + } + expectedErrMsg = "x509: invalid signature: parent certificate cannot sign this kind of certificate" + isRoot, err = IsRootCertificate(notSelfIssued[0]) + if err != nil { + t.Fatal(err) + } + if isRoot { + t.Fatal("expected IsRootCertificate to return false") + } +} diff --git a/specs/commandline/sign.md b/specs/commandline/sign.md index f7e9577ae..efc5dd6c0 100644 --- a/specs/commandline/sign.md +++ b/specs/commandline/sign.md @@ -30,21 +30,23 @@ Usage: notation sign [flags] Flags: - --force-referrers-tag force to store signatures using the referrers tag schema (default true) - -d, --debug debug mode - -e, --expiry duration optional expiry that provides a "best by use" time for the artifact. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m - -h, --help help for sign - --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag - --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing - -k, --key string signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags - --oci-layout [Experimental] sign the artifact stored as OCI image layout - -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) - --plugin string signing plugin name. This is mutually exclusive with the --key flag - --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values. - --signature-format string signature envelope format, options: "jws", "cose" (default "jws") - -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) - -m, --user-metadata stringArray {key}={value} pairs that are added to the signature payload - -v, --verbose verbose mode + --force-referrers-tag force to store signatures using the referrers tag schema (default true) + -d, --debug debug mode + -e, --expiry duration optional expiry that provides a "best by use" time for the artifact. The duration is specified in minutes(m) and/or hours(h). For example: 12h, 30m, 3h20m + -h, --help help for sign + --id string key id (required if --plugin is set). This is mutually exclusive with the --key flag + --insecure-registry use HTTP protocol while connecting to registries. Should be used only for testing + -k, --key string signing key name, for a key previously added to notation's key list. This is mutually exclusive with the --id and --plugin flags + --oci-layout [Experimental] sign the artifact stored as OCI image layout + -p, --password string password for registry operations (default to $NOTATION_PASSWORD if not specified) + --plugin string signing plugin name. This is mutually exclusive with the --key flag + --plugin-config stringArray {key}={value} pairs that are passed as it is to a plugin, refer plugin's documentation to set appropriate values. + --signature-format string signature envelope format, options: "jws", "cose" (default "jws") + --timestamp-root-cert string filepath of timestamp authority root certificate + --timestamp-url string RFC 3161 Timestamping Authority (TSA) server URL + -u, --username string username for registry operations (default to $NOTATION_USERNAME if not specified) + -m, --user-metadata stringArray {key}={value} pairs that are added to the signature payload + -v, --verbose verbose mode ``` ### Set config property for OCI image manifest @@ -155,6 +157,20 @@ Warning: Always sign the artifact using digest(`@sha256:...`) rather than a tag( Successfully signed localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` +### Sign an OCI artifact and timestamp the signature with user specified RFC3161 Timestamp Authority (TSA) + +```shell +# Prerequisites: +# A default signing key is configured using CLI "notation key". +# Signer knows the TSA url that they want to use to require a RFC 3161 timestamp. +# Signer has downloaded the TSA's root certificate in their file system. + +# Use option "--timestamp-url" to specify the timestamp authority URL. +# Use option "--timestamp-root-cert" to specify the filepath of the tsa root +# certificate. +notation sign --timestamp-url --timestamp-root-cert /@ +``` + ### [Experimental] Sign container images stored in OCI layout directory Container images can be stored in OCI image Layout defined in spec [OCI image layout][oci-image-layout]. It is a directory structure that contains files and folders. The OCI image layout could be a tarball or a directory in the filesystem. For example, a file named `hello-world.tar` or a directory named `hello-world`. Notation only supports signing images stored in OCI layout directory for now. Users can reference an image in the layout using either tags, or the exact digest. For example, use `hello-world:v1` or `hello-world@sha256xxx` to reference the image in OCI layout directory named `hello-world`. diff --git a/specs/commandline/verify.md b/specs/commandline/verify.md index fa56fdc94..99d474716 100644 --- a/specs/commandline/verify.md +++ b/specs/commandline/verify.md @@ -77,7 +77,29 @@ An example of `trustpolicy.json`: "level": "strict" }, "trustStores": [ "ca:wabbit-networks" ], // The trust stores that contains the X.509 trusted roots. - "trustedIdentities": [ // Identities that are trusted to sign the artifact. + "trustedIdentities": [ // Identities that are trusted to sign the artifact. It only includes identities of `ca` and `signingAuthority`. + "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Finance, CN=SecureBuilder" + ] + } + ] +} +``` + +An example of `trustpolicy.json` with RFC 3161 timestamp verification support: + +```jsonc +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "wabbit-networks-images", + "registryScopes": [ "localhost:5000/net-monitor" ], + "signatureVerification": { + "level": "strict", + "verifyTimestamp": "afterCertExpiry" // Only verify timestamp countersignatures if any code signing certificate has expired. DEFAULT: `always` + }, + "trustStores": [ "ca:wabbit-networks", "tsa:wabbit-networks-timestamp" ], // To enable timestamp verification, trust store type `tsa` MUST be configured. + "trustedIdentities": [ "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Finance, CN=SecureBuilder" ] } @@ -175,6 +197,22 @@ Warning: Always verify the artifact using digest(@sha256:...) rather than a tag Successfully verified signature for localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 ``` +### Verify signatures with RFC 3161 timestamp countersignature on an OCI artifact + +```shell +# Prerequisites: Configure TSA trust store by adding the root certificate of the trusted TSA into trust store named "wabbit-network-timestamp" of type "tsa" +notation certificate add --type tsa --store wabbit-networks-timestamp wabbit-networks-tsa.crt + +# Verify signatures on an OCI artifact +notation verify localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 +``` + +An example of output messages for a successful verification: + +```text +Successfully verified signature for localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 +``` + ### [Experimental] Verify container images in OCI layout directory Users should configure trust policy properly before verifying artifacts in OCI layout directory. According to trust policy specification, `registryScopes` property of trust policy configuration determines which trust policy is applicable for the given artifact. For example, an image stored in a remote registry is referenced by "localhost:5000/net-monitor:v1". In order to verify the image, the value of `registryScopes` should contain "localhost:5000/net-monitor", which is the repository URL of the image. However, the reference to the image stored in OCI layout directory doesn't contain repository URL information. Users can set `registryScopes` to the URL that the image is supposed to be stored in the registry, and then use flag `--scope` for `notation verify` command to determine which trust policy is used for verification. Here is an example of trust policy configured for image `hello-world:v1`: diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 78ae34e26..3ec40e084 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -3,7 +3,7 @@ module github.com/notaryproject/notation/test/e2e go 1.21 require ( - github.com/notaryproject/notation-core-go v1.0.3 + github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 github.com/opencontainers/image-spec v1.1.0 @@ -11,11 +11,12 @@ require ( ) require ( - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect + github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 92491efed..90c7a3af3 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -1,8 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -13,8 +13,10 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 h1:2XF1Vzq06X+inNqgJ9tRnGuw+ZVCB3FazXODD6JE1R8= github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= -github.com/notaryproject/notation-core-go v1.0.3 h1:FCgvULSypEFrrNgvDRdHbKAGAgbXK43n/jKD9q2WECA= -github.com/notaryproject/notation-core-go v1.0.3/go.mod h1:eDo5/LTUp23mB7w0CckJLnl+p93oGdyiKDzzggpqTH4= +github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b h1:uJ4bmNieZRkPj3UgmKr3bZr8vs7UJ2MdlJMeB0oOaZw= +github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b/go.mod h1:MdxSbL9F5h63EmtXWfYMWy7hEmGmOmsfN4B6KM2WyhY= +github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172 h1:Q8UsmeFMzyFuMMq4dlbIRJUi7khEKXKUe2H2Hm3W92Y= +github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= @@ -33,8 +35,8 @@ github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4 github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= diff --git a/test/e2e/internal/notation/host.go b/test/e2e/internal/notation/host.go index 4a93b9de0..168015a2b 100644 --- a/test/e2e/internal/notation/host.go +++ b/test/e2e/internal/notation/host.go @@ -129,6 +129,26 @@ func BaseOptions() []utils.HostOption { ) } +// TimestampOptions returns a list of timestamp Options for a valid +// notation testing environment. +func TimestampOptions(verifyTimestamp string) []utils.HostOption { + var trustPolicyOption utils.HostOption + if verifyTimestamp == "afterCertExpiry" { + trustPolicyOption = AddTrustPolicyOption("timestamp_after_cert_expiry_trustpolicy.json") + } else { + trustPolicyOption = AddTrustPolicyOption("timestamp_trustpolicy.json") + } + + return Opts( + AuthOption("", ""), + AddKeyOption("e2e.key", "e2e.crt"), + AddTrustStoreOption("e2e", filepath.Join(NotationE2ELocalKeysDir, "e2e.crt")), + AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer")), + AddTimestampTrustStoreOption("e2e", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer")), + trustPolicyOption, + ) +} + func BaseOptionsWithExperimental() []utils.HostOption { return Opts( AuthOption("", ""), @@ -189,6 +209,16 @@ func AddTrustStoreOption(namedstore string, srcCertPath string) utils.HostOption } } +// AddTimestampTrustStoreOption adds the test tsa cert to the trust store. +func AddTimestampTrustStoreOption(namedstore string, srcCertPath string) utils.HostOption { + return func(vhost *utils.VirtualHost) error { + vhost.Executor. + Exec("cert", "add", "--type", "tsa", "--store", namedstore, srcCertPath). + MatchKeyWords("Successfully added following certificates") + return nil + } +} + // AddTrustPolicyOption adds a valid trust policy for testing. func AddTrustPolicyOption(trustpolicyName string) utils.HostOption { return func(vhost *utils.VirtualHost) error { diff --git a/test/e2e/plugin/go.mod b/test/e2e/plugin/go.mod index 3ae596dcc..baf48309a 100644 --- a/test/e2e/plugin/go.mod +++ b/test/e2e/plugin/go.mod @@ -4,27 +4,28 @@ go 1.21 require ( github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/notaryproject/notation-core-go v1.0.3 - github.com/notaryproject/notation-go v1.1.1 + github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b + github.com/notaryproject/notation-go v1.1.1-0.20240719045753-83409204754a github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/spf13/cobra v1.7.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/veraison/go-cose v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.6.0 // indirect oras.land/oras-go/v2 v2.5.0 // indirect ) diff --git a/test/e2e/plugin/go.sum b/test/e2e/plugin/go.sum index f90f155d3..576f3b6ec 100644 --- a/test/e2e/plugin/go.sum +++ b/test/e2e/plugin/go.sum @@ -6,8 +6,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= @@ -37,12 +37,14 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/notaryproject/notation-core-go v1.0.3 h1:FCgvULSypEFrrNgvDRdHbKAGAgbXK43n/jKD9q2WECA= -github.com/notaryproject/notation-core-go v1.0.3/go.mod h1:eDo5/LTUp23mB7w0CckJLnl+p93oGdyiKDzzggpqTH4= -github.com/notaryproject/notation-go v1.1.1 h1:EAY8ERBWhrdaG9MIumSZ9xyUHktgr6OkCByd75HR+FA= -github.com/notaryproject/notation-go v1.1.1/go.mod h1:XykI2i5jHb6cGf+bcG/cIeNfNO2u4Xoy2mkuOKHjVVI= +github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b h1:uJ4bmNieZRkPj3UgmKr3bZr8vs7UJ2MdlJMeB0oOaZw= +github.com/notaryproject/notation-core-go v1.0.4-0.20240716001320-f45197cbd53b/go.mod h1:MdxSbL9F5h63EmtXWfYMWy7hEmGmOmsfN4B6KM2WyhY= +github.com/notaryproject/notation-go v1.1.1-0.20240719045753-83409204754a h1:o3kYOcQii0dMaDKdxnr1wPlEskXHHkDZDDb3kuss+W0= +github.com/notaryproject/notation-go v1.1.1-0.20240719045753-83409204754a/go.mod h1:FwHtZC29bBvFdJu0NYM5MHxSrHJGwhkPRvEgevNo9wo= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= +github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172 h1:Q8UsmeFMzyFuMMq4dlbIRJUi7khEKXKUe2H2Hm3W92Y= +github.com/notaryproject/tspclient-go v0.1.1-0.20240715235637-df25ef8d2172/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -72,12 +74,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/test/e2e/suite/command/sign.go b/test/e2e/suite/command/sign.go index 8354d8696..334e27e25 100644 --- a/test/e2e/suite/command/sign.go +++ b/test/e2e/suite/command/sign.go @@ -15,6 +15,7 @@ package command import ( "fmt" + "path/filepath" "time" . "github.com/notaryproject/notation/test/e2e/internal/notation" @@ -257,4 +258,84 @@ var _ = Describe("notation sign", func() { MatchKeyWords(VerifySuccessfully) }) }) + + It("with timestamping", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + }) + }) + + It("with timestamp-root-cert but no timestamp-url", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-url]") + }) + }) + + It("with timestamp-url but no timestamp-root-cert", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: if any flags in the group [timestamp-url timestamp-root-cert] are set they must all be set; missing [timestamp-root-cert]") + }) + }) + + It("with timestamping and empty tsa server", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: timestamping: tsa url cannot be empty") + }) + }) + + It("with timestamping and empty tsa root cert", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "dummy", "--timestamp-root-cert", "", artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: timestamping: tsa root certificate path cannot be empty") + }) + }) + + It("with timestamping and invalid tsa server", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://invalid.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: timestamp: Post \"http://invalid.com\""). + MatchErrKeyWords("server misbehaving") + }) + }) + + It("with timestamping and invalid tsa root certificate", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "invalid.crt"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("Error: x509: malformed certificate") + }) + }) + + It("with timestamping and empty tsa root certificate file", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "Empty.txt"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("cannot find any certificate from"). + MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") + }) + }) + + It("with timestamping and more than one certificates in tsa root certificate file", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "CertChain.pem"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("found more than one certificates"). + MatchErrKeyWords("Expecting single x509 root certificate in PEM or DER format from the file") + }) + }) + + It("with timestamping and intermediate certificate file", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "intermediate.pem"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("failed to check root certificate with error: crypto/rsa: verification error") + }) + }) + + It("with timestamping and not self-issued certificate file", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.ExpectFailure().Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "notSelfIssued.crt"), artifact.ReferenceWithDigest()). + MatchErrKeyWords("is not a root certificate. Expecting single x509 root certificate in PEM or DER format from the file") + }) + }) }) diff --git a/test/e2e/suite/command/verify.go b/test/e2e/suite/command/verify.go index f27384301..f5f8aa333 100644 --- a/test/e2e/suite/command/verify.go +++ b/test/e2e/suite/command/verify.go @@ -15,6 +15,7 @@ package command import ( "fmt" + "path/filepath" . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" @@ -211,4 +212,37 @@ var _ = Describe("notation verify", func() { MatchKeyWords(VerifySuccessfully) }) }) + + It("with timestamp verification disabled", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + + notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). + MatchKeyWords(VerifySuccessfully). + MatchErrKeyWords("Timestamp verification disabled") + }) + }) + + It("with timestamp verification", func() { + Host(TimestampOptions(""), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + + notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). + MatchKeyWords(VerifySuccessfully). + MatchErrKeyWords("Performing timestamp verification...") + }) + }) + + It("with verifyTimestamp set as afterCertExpiry", func() { + Host(TimestampOptions("afterCertExpiry"), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", "--timestamp-url", "http://timestamp.digicert.com", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "DigiCertTSARootSHA384.cer"), artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + + notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). + MatchKeyWords(VerifySuccessfully). + MatchErrKeyWords("Timestamp verification disabled: verifyTimestamp is set to \\\"afterCertExpiry\\\" and signing cert chain unexpired") + }) + }) }) diff --git a/test/e2e/suite/trustpolicy/multi_statements.go b/test/e2e/suite/trustpolicy/multi_statements.go index 2c8341f17..6364123b6 100644 --- a/test/e2e/suite/trustpolicy/multi_statements.go +++ b/test/e2e/suite/trustpolicy/multi_statements.go @@ -30,7 +30,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). - MatchErrContent("Error: registry scope \"localhost:5000/test-repo8\" is present in multiple trust policy statements, one registry scope value can only be associated with one statement\n") + MatchErrContent("Error: registry scope \"localhost:5000/test-repo8\" is present in multiple oci trust policy statements, one registry scope value can only be associated with one statement\n") }) }) @@ -56,7 +56,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). - MatchErrContent("Error: multiple trust policy statements use the same name \"e2e\", statement names must be unique\n") + MatchErrContent("Error: multiple oci trust policy statements use the same name \"e2e\", statement names must be unique\n") }) }) @@ -68,7 +68,7 @@ var _ = Describe("notation trust policy multi-statements test", func() { // test localhost:5000/test-repo notation.Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). - MatchErrContent("Error: registry scope \"*\" is present in multiple trust policy statements, one registry scope value can only be associated with one statement\n") + MatchErrContent("Error: registry scope \"*\" is present in multiple oci trust policy statements, one registry scope value can only be associated with one statement\n") }) }) }) diff --git a/test/e2e/suite/trustpolicy/registry_scope.go b/test/e2e/suite/trustpolicy/registry_scope.go index 7faa985e8..7eb19a5f4 100644 --- a/test/e2e/suite/trustpolicy/registry_scope.go +++ b/test/e2e/suite/trustpolicy/registry_scope.go @@ -111,7 +111,7 @@ var _ = Describe("notation trust policy registryScope test", func() { // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). - MatchErrKeyWords("registry scope \"localhost:5000/test-repo6\" is present in multiple trust policy statements") + MatchErrKeyWords("registry scope \"localhost:5000/test-repo6\" is present in multiple oci trust policy statements") }) }) @@ -137,7 +137,7 @@ var _ = Describe("notation trust policy registryScope test", func() { // test localhost:5000/test-repo OldNotation().Exec("sign", artifact.ReferenceWithDigest()).MatchKeyWords(SignSuccessfully) notation.ExpectFailure().Exec("verify", artifact.ReferenceWithDigest()). - MatchErrContent(fmt.Sprintf("Error: signature verification failed: artifact %q has no applicable trust policy. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: %s\n", artifact.ReferenceWithDigest(), trustPolicyLink)) + MatchErrContent(fmt.Sprintf("Error: signature verification failed: artifact %q has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: %s\n", artifact.ReferenceWithDigest(), trustPolicyLink)) }) }) }) diff --git a/test/e2e/suite/trustpolicy/verification_level.go b/test/e2e/suite/trustpolicy/verification_level.go index 6ada0259b..3f6c1197c 100644 --- a/test/e2e/suite/trustpolicy/verification_level.go +++ b/test/e2e/suite/trustpolicy/verification_level.go @@ -98,7 +98,7 @@ var _ = Describe("notation trust policy verification level test", func() { notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticTimestamp was set to \"log\"", - "error: certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" is not valid anymore, it was expired"). + "after certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" validity period, it was expired at \"Tue, 27 Jun 2023 06:10:00 +0000\""). MatchKeyWords(VerifySuccessfully) }) }) @@ -156,7 +156,7 @@ var _ = Describe("notation trust policy verification level test", func() { notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticTimestamp was set to \"log\"", - "error: certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" is not valid anymore, it was expired"). + "after certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" validity period, it was expired at \"Tue, 27 Jun 2023 06:10:00 +0000\""). MatchKeyWords(VerifySuccessfully) }) }) @@ -226,7 +226,7 @@ var _ = Describe("notation trust policy verification level test", func() { notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchErrKeyWords("Warning: authenticTimestamp was set to \"log\"", - "error: certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" is not valid anymore, it was expired"). + "after certificate \"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU\" validity period, it was expired at \"Tue, 27 Jun 2023 06:10:00 +0000\""). MatchKeyWords(VerifySuccessfully) }) }) diff --git a/test/e2e/testdata/config/timestamp/CertChain.pem b/test/e2e/testdata/config/timestamp/CertChain.pem new file mode 100644 index 000000000..ca25a329b --- /dev/null +++ b/test/e2e/testdata/config/timestamp/CertChain.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIBljCCATugAwIBAgIQJOyDt70f+HOyQMEt06yvpjAKBggqhkjOPQQDAjAkMRAw +DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTIyMDcyMjA1MjEz +N1oXDTIzMDcyMjA1MjEzN1owKDEQMA4GA1UEChMHQWNtZSBDbzEUMBIGA1UEAwwL +dGVzdF9jZXJ0XzEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATnf3lSRtUYOeph +UQZvUm5niB8kpm7kn6iAm2zwCTBeqKbUtgESCbN+x6TTpWZIaEo+CDu1rPUdicB3 +FUwXNzz8o0swSTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw +DAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwID +SQAwRgIhAMgdV/zJnwK0J4ZBXZVwAB6abpgNcESFScDeQQyIzRs8AiEAjjLTfkXp +CuoXnu5/hYy6Li7Smw3UbW3XKkekOELMFYo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBnjCCAUOgAwIBAgIQBUvhbMcjM35qmJzncyZ5tzAKBggqhkjOPQQDAjAkMRAw +DgYDVQQKEwdBY21lIENvMRAwDgYDVQQDEwdSb290IENBMB4XDTIyMDcyMjA1MjEz +N1oXDTIzMDcyMjA1MjEzN1owJDEQMA4GA1UEChMHQWNtZSBDbzEQMA4GA1UEAxMH +Um9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGmby5GUiBDR+Ge+s/R9 +EqOfoDwEdDBPYU0emJg8j8CPJGM0ldalI1Sk7YMTIi34clvfTqEixE7nDwQj8FjQ +VvCjVzBVMA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBTcOHZMx0z3I9Hi8oa2Kp0umdXOsTAKBggq +hkjOPQQDAgNJADBGAiEArHTaO3f6vaiI+4IOrR7SYSzeHIAqoFAWFcf1yOzxDA4C +IQDRcDIPWJd7pXvFJT/Q++Vkq9QuUhqrigCQDkgksnxf5w== +-----END CERTIFICATE----- diff --git a/test/e2e/testdata/config/timestamp/DigiCertTSARootSHA384.cer b/test/e2e/testdata/config/timestamp/DigiCertTSARootSHA384.cer new file mode 100644 index 0000000000000000000000000000000000000000..99bcc84b7e68b5b28e4444f6fa21bc7c2baf497d GIT binary patch literal 1428 zcmXqLVx3^n#9Xm}nTe5!Nq}{>bojhJMWaWS?0c7&m&O?IvTn?JkswJuAWqo zP1e*s_a@Ho#N;1}iL*^!vmT3k6D_sp^~v*R*O)lOZ>&mtSAN1{MOt|H{E&z~9_{V^ z%MEUZy*pJM`*`h1|G1~7&kaxCnjCkhufO5ewuv(wCR84-IKFM;k*!%07R&;@H?Ej3 z(PORc_}XMAFtK2DXp^JS_1i4PT6q&0YZQI1>{%zxTpC-EcGJqxWtOqSeva!=o=Xlr zTe%?p?h^Gq3;iv(3Py;3SBY`!Px*c@v!iTAnQdgOQ(1fG^vo)c4-XazNvF*!Id#ul z?m1ubx@TA3Pnu*k&-M<(6Ia#FZL?e?wd)Q{*>Wi{_qFlOqxZd87|ztnOg-HHU2)SU z!R@>2KV9u9&~Z#ywJ}-3WvWzJQr)+P4ZmNcEHl2?$^LNf_GivZBz7z-XMD&%g-20# zQ;4Q&XUw*5U*l71pZrEK z{)j?gcK*iIZQcHduDQm~Rrs?|?&yL3MH}n5)MkEtlBqvKR`=`8m78RrN;5GtGB7T7 zGH@{92PS7(VMfOPEUX61K+1p*B)|_6U;*Z-HUn7@pN~b1MdZ!($4!?CV^e(Y>!sU2 z-!)^M48K2eDg$OPU@Bu|*qwN@c4f{!@gozZ4=-HA(EB(ggFozi`MQFie`k5k+~v@ z;|G_o$XKF&XYNn+bq1|Fzoq+H+4VTN((ZIU8a!-?Xwiwjs2;$JM? zvV8rD@42RPYNEQXEwY&TxuW}v?-ZoX1m*e!D1M{-ku zW1+3RZ-H_fkJp{XOJ|IxwD59pPM7gN@Ge`S#Ng5cOA~=sMNvkM7okS?3O#RXhzIyS z+vj_+bj^iRza4itFI{!{FsqBdj@j%-zaF{nP!7&v%TEujciZY?pQjO3sdj0}ilph6 zfR&$*WHWvetKHnrfA0t)=$1ze_=^}`TkG{L*Rlgt&`^}&Rl)f&LXk-+o%1iN!lfsC)s~w6 z?Tp>inj~vqxWQ7QGU@KxG3!Bhh+^5 ouE}lQ!_OaFs=4ZwaQTyaJ&lTM*#+DM*S6cTUo72o{&QL#0GZBIfB*mh literal 0 HcmV?d00001 diff --git a/test/e2e/testdata/config/timestamp/Empty.txt b/test/e2e/testdata/config/timestamp/Empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/e2e/testdata/config/timestamp/globalsignTSARoot.cer b/test/e2e/testdata/config/timestamp/globalsignTSARoot.cer new file mode 100644 index 0000000000000000000000000000000000000000..3492b9555d8a927fc048accbdfd510d973a4bf8a GIT binary patch literal 1415 zcmXqLVr@2PV$NQ`%*4pV#OL~KH*>S`;nq}-Xa8LT4S3l&wc0$|zVk9N@~|=(_!ue} z$gwepvTzHFyXWL5CFTTYrspXH<>!|uI6ErnDg>Ds3L6N5RB#D%AymKwnUMtzrbf z7RhT}D$uKbzGw38+~7wZH9Z3C7XQ;Wl_%Ccc-3RGYRi&upQm~WvFHBn+0=Rd!h*!| z6A>jFt|(i5kF`3r==NOBd$BIM6FxSc4WIO@ZchM9vdR7I$rAgr%{Ey+S)*dM{loih zGQ#1Lx82+H<5BDO;zc`5SGj(AFxPBhen{8HqOd%@j-!E2E}~Q3Ek0^7ZML6Z6u0|l zHZ#w1gFS`jPnW$>4}38Fd-9e>uasbWK_l0SS*5b`tlC%^=G|_%ruy>^>pb;H zpEAqnS3j37ywS{m@&A1T!JnWsAklyin0{sX85#exumCd!n*l$FFAUt}2QM&?2T3clNEnDUU{`>etANRnk)c`j?Td?lrv{y!cYtNV z#LQ-Z{64y*oN&oy5m`>wTczfjL3M=6i0;sxcI$G64QH=U6C z7a^^oy&`9ATM4e8`Et>2H?s`?&tUywgCL-KSq* zf7`U1Go!Z543}AQX18c(@Gc!z{wGJ~Np6%dx#}}_*W2&%>s6+*RIFg${jTxpt@xU5 zuZ0szRF`kI^FE;yw)gmn_ZE+0CVi;XtY?ygew1xBAHBZk2sXLxY73M8i@v-TJt6k1Drt4hk L&8$%;mk0m=(y?My literal 0 HcmV?d00001 diff --git a/test/e2e/testdata/config/timestamp/intermediate.pem b/test/e2e/testdata/config/timestamp/intermediate.pem new file mode 100644 index 000000000..83e1ccece --- /dev/null +++ b/test/e2e/testdata/config/timestamp/intermediate.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyjCCAbKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290 +MCAXDTIyMDYzMDE5MjAwM1oYDzMwMjExMDMxMTkyMDAzWjAYMRYwFAYDVQQDDA1J +bnRlcm1lZGlhdGUxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTs +aiC/7+bho43kMVyHDwCsuocYp4PvYahB59NsKDR4QbrImU5ziaQ94D0DQqthe9pm +qOW0SxN/vSRJAZFELxacrB9hc1y4MjiDYaRSt/LVx7astylBV/QRpmxWSEqp0Avu +6nMJivIa1sD0WIEchizx6jG9BI5ULr9LbJICYvMgDalQR+0JGG+rKWnf1mPZyxEu +9zEh215LCg5K56P3W5kC8fKBXSdSgTqZAvHzp6u78qet9S8gARtOEfS03A/7y7MC +U0Sn2wdQyQdci0PBsR2sTZvUw179Cr93r5aRbb3I6jXgMWHAP2vvIndb9CM9ePyY +yEy4Je7oWVVfMQ3CWQIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud +DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEALR0apUQVbWGmagLUz4Y/bRsl +mY9EJJXCiLuSxVWd3offjZfQTlGkQkCAW9FOQnm7JhEtaaHF1+AEVLo56/Gsd/hk +sXsrBagYGi72jun7QTb6j7iZ3X9zanrP3SjdkpjVnqxRfH83diSh0r68Xruq1NSK +qhUy1V+KQaXF0SSEutPqdTCoXUyxyXohVLU78uqZX/jx9Nc1XDuW9AZd+hMsLdk8 +qGJqHYFvj2vOHGMTeYk8dWgMBthQeL0wdsg2AvKtAvn6FQXCN7mKCWjpFTtYsU8v +NsesS9M/i+geJjR/8/DDT3RP7S100BtCMm4XfHfmKcjXVaBh5evQVqGsa6TKLw== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/test/e2e/testdata/config/timestamp/invalid.crt b/test/e2e/testdata/config/timestamp/invalid.crt new file mode 100644 index 000000000..c412709aa --- /dev/null +++ b/test/e2e/testdata/config/timestamp/invalid.crt @@ -0,0 +1,2 @@ +-----BEGIN CERTIFICATE----- +-----END CERTIFICATE----- diff --git a/test/e2e/testdata/config/timestamp/notSelfIssued.crt b/test/e2e/testdata/config/timestamp/notSelfIssued.crt new file mode 100644 index 000000000..8f53e9ad5 --- /dev/null +++ b/test/e2e/testdata/config/timestamp/notSelfIssued.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJVUzEJ +MAcGA1UECBMAMQkwBwYDVQQHEwAxDzANBgNVBAoTBk5vdGFyeTENMAsGA1UEAxME +dGVzdDAeFw0yNDA3MjIwNTIwMzZaFw0yNDA4MjIwNTIwMzZaMEwxCzAJBgNVBAYT +AlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90 +YXJ5MQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAp7XmYGf3p4JS/y5v6AIc/TxQvPTwIRxVmctNmDm5kG3LDUoGAAJbicTUfI/0 +Un38j/PlHNKQz9hKPwV+oYotKuQrVhaA2fft+INl36tvgCAPr8yX3ToOMCLr/UlT +zQ7o9TB7IpnVT9DR9uik9MWfkz0Db5ARG1POquvSy2QM5wseEA58313YJ/7Em/Cq +FCH5s9THCfQKpb09MZ/RTEggNqU4zGADah8e1KieYeZntM/hrw7sW5oeUueKG4D4 +3kvL8o7n1k6C+w8LwaOGhYXCQ51JxTE3lnmTrDdFRuKGObpNFNbLxdJPVLuHT1Nu +bwVxj5APBJQyEyja3jJ9qLQANwIDAQABo1owWDAOBgNVHQ8BAf8EBAMCAgQwEwYD +VR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU +yPScYtI4hs0+ibHaRV9BsgY734AwDQYJKoZIhvcNAQELBQADggEBABJHh3NELq1b +jcJiJX76DwDTx+FGGN96/+T5622FGg1kHeAwuxS6pQODJNrVofbrhGAqaXTDT/Tz +0b0AA5XCohmBFZQRwMh+C5QkFiIcZ9VMMBc6KTQT8DEgjI6Qo/OW2TDGOoFuAhmh +4a1ACHszuHS55Th+0TKLqeZNA6DnL9IBm0RX1FJXbqhjX52ZnRH3Zqe7uML+kxKt +LUdfnxHrpA1G2ugyAj+K7K6vth5QpezwCS1PZD2s5vlJd6clawxm5qRyyU46ow/y +7bpTSEyg6PIWWh/qv4O2t4NMa1OoRkIXx/ppsKH9XwbRg/WZ0VWlGVS6GxBtLSpG +tkyaxSRLdz0= +-----END CERTIFICATE----- diff --git a/test/e2e/testdata/config/trustpolicies/timestamp_after_cert_expiry_trustpolicy.json b/test/e2e/testdata/config/trustpolicies/timestamp_after_cert_expiry_trustpolicy.json new file mode 100644 index 000000000..1d45bce1c --- /dev/null +++ b/test/e2e/testdata/config/trustpolicies/timestamp_after_cert_expiry_trustpolicy.json @@ -0,0 +1,17 @@ +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "e2e", + "registryScopes": [ "*" ], + "signatureVerification": { + "level" : "strict", + "verifyTimestamp": "afterCertExpiry" + }, + "trustStores": [ "ca:e2e", "tsa:e2e" ], + "trustedIdentities": [ + "*" + ] + } + ] +} \ No newline at end of file diff --git a/test/e2e/testdata/config/trustpolicies/timestamp_trustpolicy.json b/test/e2e/testdata/config/trustpolicies/timestamp_trustpolicy.json new file mode 100644 index 000000000..d210233b2 --- /dev/null +++ b/test/e2e/testdata/config/trustpolicies/timestamp_trustpolicy.json @@ -0,0 +1,17 @@ +{ + "version": "1.0", + "trustPolicies": [ + { + "name": "e2e", + "registryScopes": [ "*" ], + "signatureVerification": { + "level" : "strict", + "verifyTimestamp": "always" + }, + "trustStores": [ "ca:e2e", "tsa:e2e" ], + "trustedIdentities": [ + "*" + ] + } + ] +} \ No newline at end of file