diff --git a/config/base_test.go b/config/base_test.go index 7fb8d8ff..1241ec38 100644 --- a/config/base_test.go +++ b/config/base_test.go @@ -17,6 +17,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "testing" "github.com/notaryproject/notation-go/dir" @@ -33,6 +34,9 @@ func TestLoadNonExistentFile(t *testing.T) { } func TestLoadSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } root := t.TempDir() dir.UserConfigDir = root fileName := "symlink" diff --git a/example_localVerify_test.go b/example_localVerify_test.go index f5248d26..50426763 100644 --- a/example_localVerify_test.go +++ b/example_localVerify_test.go @@ -31,7 +31,7 @@ import ( // examplePolicyDocument is an example of a valid trust policy document. // trust policy document should follow this spec: -// https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.1/specs/trust-store-trust-policy.md#trust-policy +// https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy var examplePolicyDocument = trustpolicy.Document{ Version: "1.0", TrustPolicies: []trustpolicy.TrustPolicy{ @@ -75,7 +75,7 @@ func Example_localVerify() { // createTrustStore creates a trust store directory for demo purpose. // Users could use the default trust store from Notary and add trusted // certificates into it following the trust store spec: - // https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.1/specs/trust-store-trust-policy.md#trust-store + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-store if err := createTrustStore(); err != nil { panic(err) // Handle error } @@ -173,7 +173,7 @@ func createTrustStore() error { // Users should replace `exampleX509Certificate` with their own trusted // certificate and add to the trust store, following the // Notary certificate requirements: - // https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.1/specs/signature-specification.md#certificate-requirements + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements exampleX509Certificate := `-----BEGIN CERTIFICATE----- MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEP diff --git a/example_remoteVerify_test.go b/example_remoteVerify_test.go index 4289b450..1fffed34 100644 --- a/example_remoteVerify_test.go +++ b/example_remoteVerify_test.go @@ -37,7 +37,7 @@ func Example_remoteVerify() { // examplePolicyDocument is an example of a valid trust policy document. // trust policy document should follow this spec: - // https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.1/specs/trust-store-trust-policy.md#trust-policy + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy examplePolicyDocument := trustpolicy.Document{ Version: "1.0", TrustPolicies: []trustpolicy.TrustPolicy{ @@ -52,9 +52,9 @@ func Example_remoteVerify() { } // generateTrustStore generates a trust store directory for demo purpose. - // Users could use the default trust store from Notary and add trusted - // certificates into it following the trust store spec: - // https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.1/specs/trust-store-trust-policy.md#trust-store + // Users should configure their own trust store and add trusted certificates + // into it following the trust store spec: + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-store if err := generateTrustStore(); err != nil { panic(err) // Handle error } @@ -102,7 +102,7 @@ func generateTrustStore() error { // Users should replace `exampleX509Certificate` with their own trusted // certificate and add to the trust store, following the // Notary certificate requirements: - // https://github.com/notaryproject/notaryproject/blob/v1.0.0-rc.1/specs/signature-specification.md#certificate-requirements + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements exampleX509Certificate := `-----BEGIN CERTIFICATE----- MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEP diff --git a/example_signWithTimestmap_test.go b/example_signWithTimestmap_test.go new file mode 100644 index 00000000..8e0ebe5c --- /dev/null +++ b/example_signWithTimestmap_test.go @@ -0,0 +1,99 @@ +// 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 notation_test + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + + "oras.land/oras-go/v2/registry/remote" + + "github.com/notaryproject/notation-core-go/testhelper" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/registry" + "github.com/notaryproject/notation-go/signer" + "github.com/notaryproject/tspclient-go" +) + +// Example_signWithTimestamp demonstrates how to use notation.Sign to sign an +// artifact with a RFC 3161 compliant timestamp countersignature and +// user trusted TSA root certificate +func Example_signWithTimestamp() { + // exampleArtifactReference is an example of the target artifact reference + var exampleArtifactReference = "localhost:5000/software@sha256:60043cf45eaebc4c0867fea485a039b598f52fd09fd5b07b0b2d2f88fad9d74e" + + // exampleCertTuple contains a RSA privateKey and a self-signed X509 + // certificate generated for demo purpose ONLY. + exampleCertTuple := testhelper.GetRSASelfSignedSigningCertTuple("Notation Example self-signed") + exampleCerts := []*x509.Certificate{exampleCertTuple.Cert} + + // exampleSigner is a notation.Signer given key and X509 certificate chain. + // Users should replace `exampleCertTuple.PrivateKey` with their own private + // key and replace `exampleCerts` with the corresponding full certificate + // chain, following the Notary certificate requirements: + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements + exampleSigner, err := signer.NewGenericSigner(exampleCertTuple.PrivateKey, exampleCerts) + if err != nil { + panic(err) // Handle error + } + + // exampleRepo is an example of registry.Repository. + remoteRepo, err := remote.NewRepository(exampleArtifactReference) + if err != nil { + panic(err) // Handle error + } + exampleRepo := registry.NewRepository(remoteRepo) + + // replace exampleRFC3161TSAServer with your trusted TSA server URL. + exampleRFC3161TSAServer := "" + httpTimestamper, err := tspclient.NewHTTPTimestamper(nil, exampleRFC3161TSAServer) + if err != nil { + panic(err) // Handle error + } + + // replace exampleTSARootCertPem with your trusted TSA root cert. + exampleTSARootCertPem := "" + block, _ := pem.Decode([]byte(exampleTSARootCertPem)) + if block == nil { + panic("failed to parse tsa root certificate PEM") + } + tsaRootCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + panic("failed to parse tsa root certificate: " + err.Error()) + } + tsaRootCAs := x509.NewCertPool() + tsaRootCAs.AddCert(tsaRootCert) + + // exampleSignOptions is an example of notation.SignOptions. + exampleSignOptions := notation.SignOptions{ + SignerSignOptions: notation.SignerSignOptions{ + SignatureMediaType: exampleSignatureMediaType, + Timestamper: httpTimestamper, + TSARootCAs: tsaRootCAs, + }, + ArtifactReference: exampleArtifactReference, + } + + targetDesc, err := notation.Sign(context.Background(), exampleSigner, exampleRepo, exampleSignOptions) + if err != nil { + panic(err) // Handle error + } + + fmt.Println("Successfully signed") + fmt.Println("targetDesc MediaType:", targetDesc.MediaType) + fmt.Println("targetDesc Digest:", targetDesc.Digest) + fmt.Println("targetDesc Size:", targetDesc.Size) +} diff --git a/example_verifyWithTimestamp_test.go b/example_verifyWithTimestamp_test.go new file mode 100644 index 00000000..56c21d59 --- /dev/null +++ b/example_verifyWithTimestamp_test.go @@ -0,0 +1,192 @@ +// 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 notation_test + +import ( + "context" + "fmt" + "os" + + _ "github.com/notaryproject/notation-core-go/signature/cose" + _ "github.com/notaryproject/notation-core-go/signature/jws" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/registry" + "github.com/notaryproject/notation-go/verifier" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" + "oras.land/oras-go/v2/registry/remote" +) + +// Example_verifyWithTimestamp demonstrates how to use notation.Verify to verify +// signature of an artifact including RFC 3161 compliant timestamp countersignature +func Example_verifyWithTimestamp() { + // exampleArtifactReference is an example of the target artifact reference + exampleArtifactReference := "localhost:5000/software@sha256:60043cf45eaebc4c0867fea485a039b598f52fd09fd5b07b0b2d2f88fad9d74e" + + // examplePolicyDocument is an example of a valid trust policy document with + // timestamping configurations. + // trust policy document should follow this spec: + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-policy + examplePolicyDocument := trustpolicy.Document{ + Version: "1.0", + TrustPolicies: []trustpolicy.TrustPolicy{ + { + Name: "test-statement-name", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + + // verify timestamp countersignature only if the signing + // certificate chain has expired. + // Default: trustpolicy.OptionAlways + VerifyTimestamp: trustpolicy.OptionAfterCertExpiry, + }, + + // `tsa` trust store type MUST be configured to enable + // timestamp verification + TrustStores: []string{"ca:valid-trust-store", "tsa:valid-tsa"}, + + // TrustedIdentities only contains trusted identities of `ca` + // and `signingAuthority` + TrustedIdentities: []string{"*"}, + }, + }, + } + + // generateTrustStoreWithTimestamp generates a trust store directory for demo purpose. + // Users should configure their own trust store and add trusted certificates + // into it following the trust store spec: + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#trust-store + if err := generateTrustStoreWithTimestamp(); err != nil { + panic(err) // Handle error + } + + // exampleVerifier is an example of notation.Verifier given + // trust policy document and X509 trust store. + exampleVerifier, err := verifier.New(&examplePolicyDocument, truststore.NewX509TrustStore(dir.ConfigFS()), nil) + if err != nil { + panic(err) // Handle error + } + + // exampleRepo is an example of registry.Repository. + remoteRepo, err := remote.NewRepository(exampleArtifactReference) + if err != nil { + panic(err) // Handle error + } + exampleRepo := registry.NewRepository(remoteRepo) + + // exampleVerifyOptions is an example of notation.VerifyOptions. + exampleVerifyOptions := notation.VerifyOptions{ + ArtifactReference: exampleArtifactReference, + MaxSignatureAttempts: 50, + } + + // remote verify core process + // upon successful verification, the target manifest descriptor + // and signature verification outcome are returned. + targetDesc, _, err := notation.Verify(context.Background(), exampleVerifier, exampleRepo, exampleVerifyOptions) + if err != nil { + panic(err) // Handle error + } + + fmt.Println("Successfully verified") + fmt.Println("targetDesc MediaType:", targetDesc.MediaType) + fmt.Println("targetDesc Digest:", targetDesc.Digest) + fmt.Println("targetDesc Size:", targetDesc.Size) +} + +func generateTrustStoreWithTimestamp() error { + // changing the path of the trust store for demo purpose. + // Users could keep the default value, i.e. os.UserConfigDir. + dir.UserConfigDir = "tmp" + + // an example of a valid X509 self-signed certificate for demo purpose ONLY. + // Users should replace `exampleX509Certificate` with their own trusted + // certificate and add to the trust store, following the + // Notary certificate requirements: + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements + exampleX509Certificate := `-----BEGIN CERTIFICATE----- +MIIDQDCCAiigAwIBAgIBUTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEP +MA0GA1UEAxMGYWxwaW5lMCAXDTAwMDgyOTEzNTAwMFoYDzIxMjMwODI5MTM1MDAw +WjBOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUx +DzANBgNVBAoTBk5vdGFyeTEPMA0GA1UEAxMGYWxwaW5lMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAocg3qEsyNDDLfB8OHD4dhi+M1NPK1Asy5NX84c+g +vacZuoPLTwmpOfm6nPt7GPPB9G7S6xxhFNbRxTYfYUjK+kaCj38XjBRf5lGewbSJ +KVkxQ82/axU70ceSW3JpazrageN9JUTZ/Jfi4MfnipITwcmMoiij8eGrHskjyVyZ +bJd0WMMKRDWVhLPUiPMVWt/4d7YtZItzacaQKtXmXgsTCTWpIols3gftNYjrQoMs +UelUdD8vOAWN9J28/SyC+uSh/K1KfyUlbqufn4di8DEBxntP5wnXYbJL1jtjsUgE +xAVjQxT1zI59X36m3t3YKqCQh1cud02L5onObY6zj57N6QIDAQABoycwJTAOBgNV +HQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQAD +ggEBAC8AjBLy7EsRpi6oguCdFSb6nRGjvF17N+b6mDb3sARnB8T1pxvzTT26ya+A +yWR+jjodEwbMIS+13lV+9qT2LwqlbOUNY519Pa2GRRY72JjeowWI3iKkKaMzfZUB +7lRTGXdEuZApLbTO/3JVcR9ffu00N1UaAP9YGElSt4JDJYA9M+d/Qto+HiIsE0Kj ++jdnwIYovPPOlryKOLfFb/r1GEq7n63xFZz83iyWNaZdsJ5N3YHxdOpkbBbCalOE +BDJTjQKqeAYBLoANNU0OBslmqHCSBTEnhbqJHN6QKyF09ScOl5LwM1QsTl0UY5si +GLAfj/jSf9OH9VLTPHOS8/N0Ka4= +-----END CERTIFICATE-----` + + // Adding the certificate into the trust store. + if err := os.MkdirAll("tmp/truststore/x509/ca/valid-trust-store", 0700); err != nil { + return err + } + if err := os.WriteFile("tmp/truststore/x509/ca/valid-trust-store/NotationExample.pem", []byte(exampleX509Certificate), 0600); err != nil { + return err + } + + // an example of a valid TSA root certificate for demo purpose ONLY. + // Users should replace `exampleTSARootCertificate` with their own trusted + // TSA root certificate and add to the trust store, following the + // Notary certificate requirements: + // https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/signature-specification.md#certificate-requirements + exampleTSARootCertificate := `-----BEGIN CERTIFICATE----- + MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi + MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 + d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg + RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV + UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu + Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG + SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y + ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If + xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV + ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO + DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ + jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ + CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi + EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM + fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY + uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK + chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t + 9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB + hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD + ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 + SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd + +SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc + fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa + sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N + cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N + 0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie + 4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI + r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 + /YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm + gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ + -----END CERTIFICATE-----` + + // Adding the tsa root certificate into the trust store. + if err := os.MkdirAll("tmp/truststore/x509/tsa/valid-tsa", 0700); err != nil { + return err + } + return os.WriteFile("tmp/truststore/x509/tsa/valid-tsa/NotationTSAExample.pem", []byte(exampleTSARootCertificate), 0600) +} diff --git a/go.mod b/go.mod index 73b5dbd5..61e27ab5 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.21 require ( github.com/go-ldap/ldap/v3 v3.4.8 - github.com/notaryproject/notation-core-go v1.0.3 + github.com/notaryproject/notation-core-go v1.0.4-0.20240708015912-faac9b7f3f10 github.com/notaryproject/notation-plugin-framework-go v1.0.0 + github.com/notaryproject/tspclient-go v0.1.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/veraison/go-cose v1.1.0 @@ -16,7 +17,7 @@ require ( 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/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.6.0 // indirect diff --git a/go.sum b/go.sum index 239c4b75..de10e43d 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1L 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= @@ -32,10 +32,12 @@ 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-core-go v1.0.4-0.20240708015912-faac9b7f3f10 h1:kXRTRPpJqj7DuSxYxfrVKcfQ3CijRisPdQQrt/+Y1bE= +github.com/notaryproject/notation-core-go v1.0.4-0.20240708015912-faac9b7f3f10/go.mod h1:6DN+zUYRhXx7swFMVSrai5J+7jqyuOCru1q9G+SbFno= 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.0 h1:kmtQuN32iwBAizOhPr+NZsxCErydoGcrfQy1ppJi5Vo= +github.com/notaryproject/tspclient-go v0.1.0/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= diff --git a/internal/mock/ocilayout/ocilayout_test.go b/internal/mock/ocilayout/ocilayout_test.go index 81b464f7..ad4bcb9f 100644 --- a/internal/mock/ocilayout/ocilayout_test.go +++ b/internal/mock/ocilayout/ocilayout_test.go @@ -15,6 +15,7 @@ package ocilayout import ( "os" + "runtime" "testing" ) @@ -26,7 +27,10 @@ func TestCopy(t *testing.T) { } }) - t.Run("invalid target path", func(t *testing.T) { + t.Run("invalid target path permission", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } tempDir := t.TempDir() // change the permission of the tempDir to make it invalid if err := os.Chmod(tempDir, 0); err != nil { diff --git a/notation.go b/notation.go index 1a6290e3..fcad813c 100644 --- a/notation.go +++ b/notation.go @@ -18,6 +18,7 @@ package notation import ( "context" "crypto/sha256" + "crypto/x509" "encoding/hex" "encoding/json" "errors" @@ -37,6 +38,7 @@ import ( "github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation-go/registry" "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/tspclient-go" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -61,6 +63,12 @@ type SignerSignOptions struct { // SigningAgent sets the signing agent name SigningAgent string + + // Timestamper denotes the timestamper for RFC 3161 timestamping + Timestamper tspclient.Timestamper + + // TSARootCAs is the cert pool holding caller's TSA trust anchor + TSARootCAs *x509.CertPool } // Signer is a generic interface for signing an OCI artifact. @@ -84,11 +92,12 @@ type SignBlobOptions struct { // BlobDescriptorGenerator creates descriptor using the digest Algorithm. // Below is the example of minimal descriptor, it must contain mediatype, digest and size of the artifact -// { -// "mediaType": "application/octet-stream", -// "digest": "sha256:2f3a23b6373afb134ddcd864be8e037e34a662d090d33ee849471ff73c873345", -// "size": 1024 -// } +// +// { +// "mediaType": "application/octet-stream", +// "digest": "sha256:2f3a23b6373afb134ddcd864be8e037e34a662d090d33ee849471ff73c873345", +// "size": 1024 +// } type BlobDescriptorGenerator func(digest.Algorithm) (ocispec.Descriptor, error) // BlobSigner is a generic interface for signing arbitrary data. diff --git a/registry/repository_test.go b/registry/repository_test.go index 50ea6885..708a974a 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "reflect" + "runtime" "strings" "testing" @@ -607,6 +608,9 @@ func TestNewOCIRepositoryFailed(t *testing.T) { }) t.Run("no permission to create new path", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } // create a directory in the temp dir dirPath := filepath.Join(t.TempDir(), "dir") err := os.Mkdir(dirPath, 0000) diff --git a/signer/signer.go b/signer/signer.go index 05d0ea8c..4bd995cd 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -113,6 +113,12 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts } else { signingAgentId = signingAgent } + if opts.Timestamper != nil && opts.TSARootCAs == nil { + return nil, nil, errors.New("timestamping: got Timestamper but nil TSARootCAs") + } + if opts.TSARootCAs != nil && opts.Timestamper == nil { + return nil, nil, errors.New("timestamping: got TSARootCAs but nil Timestamper") + } signReq := &signature.SignRequest{ Payload: signature.Payload{ ContentType: envelope.MediaTypePayloadV1, @@ -122,6 +128,8 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts SigningTime: time.Now(), SigningScheme: signature.SigningSchemeX509, SigningAgent: signingAgentId, + Timestamper: opts.Timestamper, + TSARootCAs: opts.TSARootCAs, } // Add expiry only if ExpiryDuration is not zero @@ -136,6 +144,9 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts logger.Debugf(" SigningScheme: %v", signReq.SigningScheme) logger.Debugf(" SigningAgent: %v", signReq.SigningAgent) + // Add ctx to the SignRequest + signReq = signReq.WithContext(ctx) + // perform signing sigEnv, err := signature.NewEnvelope(opts.SignatureMediaType) if err != nil { @@ -154,8 +165,6 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts if err := envelope.ValidatePayloadContentType(&envContent.Payload); err != nil { return nil, nil, err } - - // TODO: re-enable timestamping https://github.com/notaryproject/notation-go/issues/78 return sig, &envContent.SignerInfo, nil } diff --git a/signer/signer_test.go b/signer/signer_test.go index 31e16ca5..fd1f4fb5 100644 --- a/signer/signer_test.go +++ b/signer/signer_test.go @@ -34,13 +34,17 @@ import ( _ "github.com/notaryproject/notation-core-go/signature/cose" _ "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-core-go/testhelper" + nx509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/internal/envelope" "github.com/notaryproject/notation-go/plugin/proto" + "github.com/notaryproject/tspclient-go" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) +const rfc3161URL = "http://timestamp.digicert.com" + type keyCertPair struct { keySpecName string key crypto.PrivateKey @@ -117,7 +121,7 @@ func generateKeyBytes(key crypto.PrivateKey) (keyBytes []byte, err error) { return keyBytes, nil } -func prepareTestKeyCertFile(keyCert *keyCertPair, envelopeType, dir string) (string, string, error) { +func prepareTestKeyCertFile(keyCert *keyCertPair, dir string) (string, string, error) { keyPath, certPath := filepath.Join(dir, keyCert.keySpecName+".key"), filepath.Join(dir, keyCert.keySpecName+".cert") keyBytes, err := generateKeyBytes(keyCert.key) if err != nil { @@ -138,7 +142,7 @@ func prepareTestKeyCertFile(keyCert *keyCertPair, envelopeType, dir string) (str } func testSignerFromFile(t *testing.T, keyCert *keyCertPair, envelopeType, dir string) { - keyPath, certPath, err := prepareTestKeyCertFile(keyCert, envelopeType, dir) + keyPath, certPath, err := prepareTestKeyCertFile(keyCert, dir) if err != nil { t.Fatalf("prepareTestKeyCertFile() failed: %v", err) } @@ -208,10 +212,51 @@ func TestSignWithCertChain(t *testing.T) { for _, envelopeType := range signature.RegisteredEnvelopeTypes() { for _, keyCert := range keyCertPairCollections { t.Run(fmt.Sprintf("envelopeType=%v_keySpec=%v", envelopeType, keyCert.keySpecName), func(t *testing.T) { - validateSignWithCerts(t, envelopeType, keyCert.key, keyCert.certs) + validateSignWithCerts(t, envelopeType, keyCert.key, keyCert.certs, false) + }) + } + } +} + +func TestSignWithTimestamping(t *testing.T) { + // sign with key + for _, envelopeType := range signature.RegisteredEnvelopeTypes() { + for _, keyCert := range keyCertPairCollections { + t.Run(fmt.Sprintf("envelopeType=%v_keySpec=%v", envelopeType, keyCert.keySpecName), func(t *testing.T) { + validateSignWithCerts(t, envelopeType, keyCert.key, keyCert.certs, true) }) } } + + // timestamping without timestamper + envelopeType := signature.RegisteredEnvelopeTypes()[0] + keyCert := keyCertPairCollections[0] + s, err := New(keyCert.key, keyCert.certs) + if err != nil { + t.Fatalf("NewSigner() error = %v", err) + } + ctx := context.Background() + desc, sOpts := generateSigningContent() + sOpts.SignatureMediaType = envelopeType + sOpts.TSARootCAs = x509.NewCertPool() + _, _, err = s.Sign(ctx, desc, sOpts) + expectedErrMsg := "timestamping: got TSARootCAs but nil Timestamper" + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + + // timestamping without TSARootCAs + desc, sOpts = generateSigningContent() + sOpts.SignatureMediaType = envelopeType + sOpts.Timestamper, err = tspclient.NewHTTPTimestamper(nil, rfc3161URL) + if err != nil { + t.Fatal(err) + } + _, _, err = s.Sign(ctx, desc, sOpts) + expectedErrMsg = "timestamping: got Timestamper but nil TSARootCAs" + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } } func TestSignBlobWithCertChain(t *testing.T) { @@ -269,7 +314,7 @@ func signRSA(digest []byte, hash crypto.Hash, pk *rsa.PrivateKey) ([]byte, error return rsa.SignPSS(rand.Reader, pk, hash, digest, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) } -func signECDSA(digest []byte, hash crypto.Hash, pk *ecdsa.PrivateKey) ([]byte, error) { +func signECDSA(digest []byte, pk *ecdsa.PrivateKey) ([]byte, error) { r, s, err := ecdsa.Sign(rand.Reader, pk, digest) if err != nil { return nil, err @@ -289,7 +334,7 @@ func localSign(payload []byte, hash crypto.Hash, pk crypto.PrivateKey) ([]byte, case *rsa.PrivateKey: return signRSA(digest, hash, key) case *ecdsa.PrivateKey: - return signECDSA(digest, hash, key) + return signECDSA(digest, key) default: return nil, errors.New("signing private key not supported") } @@ -354,7 +399,7 @@ func verifySigningAgent(t *testing.T, signingAgentId string, metadata *proto.Get } } -func validateSignWithCerts(t *testing.T, envelopeType string, key crypto.PrivateKey, certs []*x509.Certificate) { +func validateSignWithCerts(t *testing.T, envelopeType string, key crypto.PrivateKey, certs []*x509.Certificate, timestamp bool) { s, err := New(key, certs) if err != nil { t.Fatalf("NewSigner() error = %v", err) @@ -363,6 +408,19 @@ func validateSignWithCerts(t *testing.T, envelopeType string, key crypto.Private ctx := context.Background() desc, sOpts := generateSigningContent() sOpts.SignatureMediaType = envelopeType + if timestamp { + sOpts.Timestamper, err = tspclient.NewHTTPTimestamper(nil, rfc3161URL) + if err != nil { + t.Fatal(err) + } + rootCerts, err := nx509.ReadCertificateFile("./testdata/DigiCertTSARootSHA384.cer") + if err != nil { + t.Fatal(err) + } + rootCAs := x509.NewCertPool() + rootCAs.AddCert(rootCerts[0]) + sOpts.TSARootCAs = rootCAs + } sig, _, err := s.Sign(ctx, desc, sOpts) if err != nil { t.Fatalf("Sign() error = %v", err) diff --git a/signer/testdata/DigiCertTSARootSHA384.cer b/signer/testdata/DigiCertTSARootSHA384.cer new file mode 100644 index 00000000..99bcc84b Binary files /dev/null and b/signer/testdata/DigiCertTSARootSHA384.cer differ diff --git a/verifier/helpers.go b/verifier/helpers.go index 03753fa2..f1835f8d 100644 --- a/verifier/helpers.go +++ b/verifier/helpers.go @@ -57,31 +57,7 @@ func loadX509TrustStores(ctx context.Context, scheme signature.SigningScheme, po default: return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, unrecognized signing scheme %q", scheme)} } - - processedStoreSet := set.New[string]() - var certificates []*x509.Certificate - for _, trustStore := range trustStores { - if processedStoreSet.Contains(trustStore) { - // we loaded this trust store already - continue - } - - storeType, name, found := strings.Cut(trustStore, ":") - if !found { - return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, trust policy statement %q is missing separator in trust store value %q. The required format is :", policyName, trustStore)} - } - if typeToLoad != truststore.Type(storeType) { - continue - } - - certs, err := x509TrustStore.GetCertificates(ctx, typeToLoad, name) - if err != nil { - return nil, err - } - certificates = append(certificates, certs...) - processedStoreSet.Add(trustStore) - } - return certificates, nil + return loadX509TrustStoresWithType(ctx, typeToLoad, policyName, trustStores, x509TrustStore) } // isCriticalFailure checks whether a VerificationResult fails the entire @@ -154,3 +130,56 @@ func getVerificationPluginMinVersion(signerInfo *signature.SignerInfo) (string, } return version, nil } + +func loadX509TSATrustStores(ctx context.Context, scheme signature.SigningScheme, policyName string, trustStores []string, x509TrustStore truststore.X509TrustStore) ([]*x509.Certificate, error) { + var typeToLoad truststore.Type + switch scheme { + case signature.SigningSchemeX509: + typeToLoad = truststore.TypeTSA + default: + return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the TSA trust store, signing scheme must be notary.x509, but got %s", scheme)} + } + return loadX509TrustStoresWithType(ctx, typeToLoad, policyName, trustStores, x509TrustStore) +} + +func loadX509TrustStoresWithType(ctx context.Context, trustStoreType truststore.Type, policyName string, trustStores []string, x509TrustStore truststore.X509TrustStore) ([]*x509.Certificate, error) { + processedStoreSet := set.New[string]() + var certificates []*x509.Certificate + for _, trustStore := range trustStores { + if processedStoreSet.Contains(trustStore) { + // we loaded this trust store already + continue + } + + storeType, name, found := strings.Cut(trustStore, ":") + if !found { + return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, trust policy statement %q is missing separator in trust store value %q. The required format is :", policyName, trustStore)} + } + if trustStoreType != truststore.Type(storeType) { + continue + } + + certs, err := x509TrustStore.GetCertificates(ctx, trustStoreType, name) + if err != nil { + return nil, err + } + certificates = append(certificates, certs...) + processedStoreSet.Add(trustStore) + } + return certificates, nil +} + +// isTSATrustStoreInPolicy checks if tsa trust store is configured in +// trust policy +func isTSATrustStoreInPolicy(policyName string, trustStores []string) (bool, error) { + for _, trustStore := range trustStores { + storeType, _, found := strings.Cut(trustStore, ":") + if !found { + return false, truststore.TrustStoreError{Msg: fmt.Sprintf("invalid trust policy statement: %q is missing separator in trust store value %q. The required format is :", policyName, trustStore)} + } + if truststore.Type(storeType) == truststore.TypeTSA { + return true, nil + } + } + return false, nil +} diff --git a/verifier/helpers_test.go b/verifier/helpers_test.go index ee785258..4803d4a1 100644 --- a/verifier/helpers_test.go +++ b/verifier/helpers_test.go @@ -64,17 +64,14 @@ func TestLoadX509TrustStore(t *testing.T) { dummyPolicy.TrustStores = []string{caStore, signingAuthorityStore} dir.UserConfigDir = "testdata" x509truststore := truststore.NewX509TrustStore(dir.ConfigFS()) - caCerts, err := loadX509TrustStores(context.Background(), signature.SigningSchemeX509, dummyPolicy.Name, dummyPolicy.TrustStores, x509truststore) + _, err := loadX509TrustStores(context.Background(), signature.SigningSchemeX509, dummyPolicy.Name, dummyPolicy.TrustStores, x509truststore) if err != nil { t.Fatalf("TestLoadX509TrustStore should not throw error for a valid trust store. Error: %v", err) } - saCerts, err := loadX509TrustStores(context.Background(), signature.SigningSchemeX509SigningAuthority, dummyPolicy.Name, dummyPolicy.TrustStores, x509truststore) + _, err = loadX509TrustStores(context.Background(), signature.SigningSchemeX509SigningAuthority, dummyPolicy.Name, dummyPolicy.TrustStores, x509truststore) if err != nil { t.Fatalf("TestLoadX509TrustStore should not throw error for a valid trust store. Error: %v", err) } - if len(caCerts) != 4 || len(saCerts) != 3 { - t.Fatalf("ca store should have 4 certs and signingAuthority store should have 3 certs") - } } func TestIsCriticalFailure(t *testing.T) { @@ -98,6 +95,34 @@ func TestIsCriticalFailure(t *testing.T) { } } +func TestLoadX509TSATrustStores(t *testing.T) { + policyDoc := trustpolicy.Document{ + Version: "1.0", + TrustPolicies: []trustpolicy.TrustPolicy{ + { + Name: "testTSA", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"}, + TrustStores: []string{"tsa:test-timestamp"}, + TrustedIdentities: []string{"*"}, + }, + }, + } + dir.UserConfigDir = "testdata" + x509truststore := truststore.NewX509TrustStore(dir.ConfigFS()) + policyStatement := policyDoc.TrustPolicies[0] + _, err := loadX509TSATrustStores(context.Background(), signature.SigningSchemeX509, policyStatement.Name, policyStatement.TrustStores, x509truststore) + if err != nil { + t.Fatalf("TestLoadX509TrustStore should not throw error for a valid trust store. Error: %v", err) + } + + _, err = loadX509TSATrustStores(context.Background(), signature.SigningSchemeX509SigningAuthority, policyStatement.Name, policyStatement.TrustStores, x509truststore) + expectedErrMsg := "error while loading the TSA trust store, signing scheme must be notary.x509, but got notary.x509.signingAuthority" + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } +} + func getArtifactDigestFromReference(artifactReference string) (string, error) { invalidUriErr := fmt.Errorf("artifact URI %q could not be parsed, make sure it is the fully qualified OCI artifact URI without the scheme/protocol. e.g domain.com:80/my/repository@sha256:digest", artifactReference) i := strings.LastIndex(artifactReference, "@") diff --git a/verifier/testdata/timestamp/countersignature/TimeStampToken.p7s b/verifier/testdata/timestamp/countersignature/TimeStampToken.p7s new file mode 100644 index 00000000..c036aac2 Binary files /dev/null and b/verifier/testdata/timestamp/countersignature/TimeStampToken.p7s differ diff --git a/verifier/testdata/timestamp/countersignature/TimeStampTokenWithInvalidTSTInfo.p7s b/verifier/testdata/timestamp/countersignature/TimeStampTokenWithInvalidTSTInfo.p7s new file mode 100644 index 00000000..153ea92f Binary files /dev/null and b/verifier/testdata/timestamp/countersignature/TimeStampTokenWithInvalidTSTInfo.p7s differ diff --git a/verifier/testdata/timestamp/countersignature/TimeStampTokenWithInvalideContentType.p7s b/verifier/testdata/timestamp/countersignature/TimeStampTokenWithInvalideContentType.p7s new file mode 100644 index 00000000..07522e19 Binary files /dev/null and b/verifier/testdata/timestamp/countersignature/TimeStampTokenWithInvalideContentType.p7s differ diff --git a/verifier/testdata/timestamp/countersignature/TimeStampTokenWithoutCertificate.p7s b/verifier/testdata/timestamp/countersignature/TimeStampTokenWithoutCertificate.p7s new file mode 100644 index 00000000..6a86a795 Binary files /dev/null and b/verifier/testdata/timestamp/countersignature/TimeStampTokenWithoutCertificate.p7s differ diff --git a/verifier/testdata/timestamp/sigEnv/coseExpiredWithTimestamp.sig b/verifier/testdata/timestamp/sigEnv/coseExpiredWithTimestamp.sig new file mode 100644 index 00000000..450ef17e Binary files /dev/null and b/verifier/testdata/timestamp/sigEnv/coseExpiredWithTimestamp.sig differ diff --git a/verifier/testdata/timestamp/sigEnv/coseWithTimestamp.sig b/verifier/testdata/timestamp/sigEnv/coseWithTimestamp.sig new file mode 100644 index 00000000..7bb54d76 Binary files /dev/null and b/verifier/testdata/timestamp/sigEnv/coseWithTimestamp.sig differ diff --git a/verifier/testdata/timestamp/sigEnv/jwsExpiredWithTimestamp.sig b/verifier/testdata/timestamp/sigEnv/jwsExpiredWithTimestamp.sig new file mode 100644 index 00000000..bcf35a74 --- /dev/null +++ b/verifier/testdata/timestamp/sigEnv/jwsExpiredWithTimestamp.sig @@ -0,0 +1 @@ +{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6YzA2NjllZjM0Y2RjMTQzMzJjMGYxYWIwYzJjMDFhY2I5MWQ5NjAxNGIxNzJmMWE3NmYzYTM5ZTYzZDFmMGJkYSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuZGlzdHJpYnV0aW9uLm1hbmlmZXN0LnYyK2pzb24iLCJzaXplIjo1Mjh9fQ","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA2LTE4VDE1OjIxOjM4KzA4OjAwIn0","header":{"io.cncf.notary.timestampSignature":"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","x5c":["MIIDQTCCAimgAwIBAgICALAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxEDAOBgNVBAMTB3Rlc3RUU0EwHhcNMjQwNjE4MDcyMDMxWhcNMjQwNjE4MDczMDMxWjBPMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEQMA4GA1UEAxMHdGVzdFRTQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM0yrldn1SFss6NnamscsDwjzZmUbCxy+8D11AucTnB9fo5bC2kaQIbe2s0DTD+g1c9TCHi9eB/PZT0izXirhImLnvxXwJTXgUV2Xf+6R8GbEGrK7v5nMXeZ0MJbaJIUqIhE1DCJOkzUDlYQoR2eV00D+BmOkEGIUbxGeKj7eXJuKbI8jB1iUniNKRpMuAz2T7QFLvB5Ma9QIIf2ewsVK8gVFlblbJ5F8qis8Uu6woRoC01ZaZhLxEuguJ5gnZJKfNxbMP4rReZm/PnRx84t+G0rbzsSJEQ8IPT1X0752jjs/6eyhlbdhP155CdVrGYYNgJ0nCBk52KrsD2z2RfDesUCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQC+r05Jwr32pWQRt9snm6FCYnLdBnPdthB0QOG27NmBaV3zaL/NkFsxpsy64AYa8pPG3169/6OR5lzfxjwAr8zAdc5ZGfNscdH3zjdQb4VEnhAwbl2vgD3kGE0P6hFwISqjip9OqdlwAWTrX3RSANoam7n6kMjMlIf+ewN3NCg9O+GqFqAGqKkPb7FJJMiQgnUWITxqQ7VD7C7vIe1EYQxsAlNE5ly/hG1MlyqV95U2kFoCilgyCgXpKVo+F5w+wRe22a7v0h8bFRYPdbwMczfg8ugGjUF7MgV6ysuUe1qVEhNGdsZAlrX60rxWokU2XCic/SD52xrggPxNVyhNXxwV"],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"xH1dvv-SMS8AlDKBzkG6zKKu9VztmCPCGma4HjIUqpohrx1_bVDn-IXU9311cWZcArNNw_UtfbaHYMSFjWVTi_p4brPrPb97tCHV-DeTVhNXrVb_2vf-6EWvDuOOPDyHFN-caVRxz7nBQkKQ4W0N2R-jex8eBXzHzRvY2VTNpowKWPYJKKmTt67zvnCcfnc4tbTPR_IT1bxe75oaxYJw3VwVV5B3-tETw7pczzNSgpeGw9TV2EY0_4Q8_TSk-eV6l0s3DFf-iI_zlhRA4UvpxT2m5LWsJJQuIsXg9or--vD7PmSVYRiM8x8EhkaZUBPen6sUolRXroOchaW4jg0wIQ"} \ No newline at end of file diff --git a/verifier/testdata/timestamp/sigEnv/jwsWithTimestamp.sig b/verifier/testdata/timestamp/sigEnv/jwsWithTimestamp.sig new file mode 100644 index 00000000..ec1c0f4e --- /dev/null +++ b/verifier/testdata/timestamp/sigEnv/jwsWithTimestamp.sig @@ -0,0 +1 @@ +{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6YzA2NjllZjM0Y2RjMTQzMzJjMGYxYWIwYzJjMDFhY2I5MWQ5NjAxNGIxNzJmMWE3NmYzYTM5ZTYzZDFmMGJkYSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuZGlzdHJpYnV0aW9uLm1hbmlmZXN0LnYyK2pzb24iLCJzaXplIjo1Mjh9fQ","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA2LTE4VDE0OjI5OjMzKzA4OjAwIn0","header":{"io.cncf.notary.timestampSignature":"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","x5c":["MIIDPjCCAiagAwIBAgIBeTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEPMA0GA1UEAxMGYWxwaW5lMB4XDTIzMDUwOTA0NTUxMloXDTMzMDUxMDA0NTUxMlowTjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxDzANBgNVBAMTBmFscGluZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5hpq1229GGLjMK6i9KZhuUO+SV7rUFnWIDiIPO5yWxYDkl+bGroeAvJYu6MVCMQ6FMRXD9jhnG6R+sAHwY7gVgcJ1OXak87PkLp/Ii1Cr7XkkySZeD+Br1vSQzfxs3pFG+iBCeVVkeZdsg+xqwnAlqAILXwIbTGRyJP1Xiu9nwOeuX1YmxPl2m29Pt1EtfVCL9COsVKt5LgOVyWP/9ISWevOBqSCU9bk35HFo9VTeUf6+ffhSMjv0Y9uwkFFOKXpcV8Sa3ArqyBmgQlUfGg1iwYlqiDE0fTYxiB3gLgETAlmTm50J+WB9LoDrnrQpbXFLoegm+JV+uSD8J8H7DL2sCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQAt0Nvna1c4pPn8kzoN5VvmFmeIgdO/BJpmdhdg0WIQ9aeN/xPXXaVjPp1Mk7edXHAvBwQr0Gyzqyy7g/h0gdnAFG7f6blrRNzbrRBCq6cNqX8iwgK/9+2OYKxk1QWj8Gx0cvu1DN1aXjPPGgQ2j3tHjJvJv32J/zuZa8gU40RPPSLaBlc5ZjpFmyi29sKlTeeZ+F/Ssic51qXXw2CsYGGWK5yQ3xSCxbw6bb2G/s/YI7/KlWg9BktBJHzRu04ZNR77W7/dyJ3Lj17PlW1XKmMOFHsQivagXeRCbmYZ43fX4ugFRFKL7KE0EgmGOWpJ0xv+6ig93sqHzQ/0uv1YgFov"],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"AkYX74o641U6t8_UOmbvykR6UJBI5VdYjFeY7Z3ESggszqbUMA4qhwuYjVuIF-hGEOLj52J4TwjZ-EJ1lxaz249-7LEhwI8N3VC2z_IyCnB4tsam4FfyR7J0lVZcP3haKemuaY5uAM1YYRouaQeuF3Toc_mSBdAjNDqXdS3ouDRFlvzYfyO4phxMQaikNDRM7oAu89aBrWL8RSQawgWaxdJT5rj8RN26D12F4PtG2w7r_8oQamnSBrMcEdl1lQFXBxbl-Yf_QQKjonPIEVcRi79IGgrzIqt00iN0inlm--rhULQ0mQpaAIMG6O0Pf53TzMKBju0WQZ6RbaUuba6kqg"} \ No newline at end of file diff --git a/verifier/testdata/timestamp/sigEnv/timestampAfterNotAfter.sig b/verifier/testdata/timestamp/sigEnv/timestampAfterNotAfter.sig new file mode 100644 index 00000000..05a27dbf Binary files /dev/null and b/verifier/testdata/timestamp/sigEnv/timestampAfterNotAfter.sig differ diff --git a/verifier/testdata/timestamp/sigEnv/timestampBeforeNotBefore.sig b/verifier/testdata/timestamp/sigEnv/timestampBeforeNotBefore.sig new file mode 100644 index 00000000..266a3c2f --- /dev/null +++ b/verifier/testdata/timestamp/sigEnv/timestampBeforeNotBefore.sig @@ -0,0 +1 @@ +{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6YzA2NjllZjM0Y2RjMTQzMzJjMGYxYWIwYzJjMDFhY2I5MWQ5NjAxNGIxNzJmMWE3NmYzYTM5ZTYzZDFmMGJkYSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuZGlzdHJpYnV0aW9uLm1hbmlmZXN0LnYyK2pzb24iLCJzaXplIjo1Mjh9fQ","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA2LTE5VDE3OjMwOjExKzA4OjAwIn0","header":{"io.cncf.notary.timestampSignature":"MIIWxwYJKoZIhvcNAQcCoIIWuDCCFrQCAQMxDTALBglghkgBZQMEAgEwgfsGCyqGSIb3DQEJEAEEoIHrBIHoMIHlAgEBBgkrBgEEAaAyAgMwMTANBglghkgBZQMEAgEFAAQgobZw/34jR09Y539sHffP97pzdDD9eB8hb9GvG8rFoKwCFEUCRS0+BQyd7vbAoGoD3L+AMGaUGA8yMDI0MDYxOTA5MzAxNFowAwIBAQIUfVQs1Q0Ij8odYfCaVHTDPQ8UyPugYKReMFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKDBBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDDClHbG9iYWxzaWduIFRTQSBmb3IgQWR2YW5jZWQgLSBHNCAtIDIwMjMxMaCCElMwggZrMIIEU6ADAgECAhABGXV0ccmS10TfpZbruXAVMA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIFRpbWVzdGFtcGluZyBDQSAtIFNIQTM4NCAtIEc0MB4XDTIzMTEwMjEwMzAwMloXDTM0MTIwNDEwMzAwMlowXDELMAkGA1UEBhMCQkUxGTAXBgNVBAoMEEdsb2JhbFNpZ24gbnYtc2ExMjAwBgNVBAMMKUdsb2JhbHNpZ24gVFNBIGZvciBBZHZhbmNlZCAtIEc0IC0gMjAyMzExMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsjVGdKqDjdWWpzxI1cXKqN9Uvgirod9c6UnQYF61pRr3Q1hDlzz0Z2MIMjwZfyvZVUbV1IzAXM+kxaUauqcrziZCITzlu6Gjld1g6hJVru/O3DKE7aO14D9z0TW4m7vFRN3huOvzWa2J9nGPWgr6CpIFhA2XAb8Fu/xYAbONydQFhaeQK26s09lO2qckNZZvqrOgQTvg+ecRjtVmAoAtPczPHqSWixeA3Ew5GiR1LMV3FtmRgyZ/5xOLokVU5rZKd+fSLS7nsDxI2caN+3r0K7ymIkii196teEeIDF+b9JFKBhz6A55qVh4rMOPzjlmwtB8eYTiGefmDg5SPCTBCM7QOt4iCGC0/14GLJ6Bp8dMFrsZFo8Ifm63pwMhP1+fGp0EyaP9ZylRc+7/ZHxbwUtLPuDWVpZHOox31dlKY7JFhiwmhrZmZ6KyUKJc4jAmuPJz4flGiN2rIcbodTw0mAVZ+V8N1QthT4GNj3X6eHahi6M7+mVlT9vMAiv2Tlc/RAgMBAAGjggGoMIIBpDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwHQYDVR0OBBYEFMS+7oc8iXQO3rPuGRuFDM5BTn+dMFYGA1UdIARPME0wCAYGZ4EMAQQCMEEGCSsGAQQBoDIBHjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAMBgNVHRMBAf8EAjAAMIGQBggrBgEFBQcBAQSBgzCBgDA5BggrBgEFBQcwAYYtaHR0cDovL29jc3AuZ2xvYmFsc2lnbi5jb20vY2EvZ3N0c2FjYXNoYTM4NGc0MEMGCCsGAQUFBzAChjdodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc3RzYWNhc2hhMzg0ZzQuY3J0MB8GA1UdIwQYMBaAFOoWxmnn48tXRTkzpPBAvtDDvWWWMEEGA1UdHwQ6MDgwNqA0oDKGMGh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3N0c2FjYXNoYTM4NGc0LmNybDANBgkqhkiG9w0BAQsFAAOCAgEAszLR6mf/29+ntDdEXe0evnYjyc4D7U3UauczjDxfIGLb7ulsODnlmXH7JpEfJ7FzXAMZz2gy0NXmdnhKqjyXMtIV7nocMjxgp0HKuT7xQerD0/HH5b7eGsbBjiLf1oDlj/KssiGNeLbEiYyUvTJzuNiXRDzXZmwNHBBcXqiIQL2grk5aLWRPBiWlhMY4cMdUcxSNVxapMByoCUnfpQEGA78Ts3SUmweBrw1BkFUpsGCpVPo4IDPOCboOTGdYgYap7YiGqZjjtuVGlyRHbEjREGrKcbI+XcM9LSGCa4Njb5fAkf77fZa5qsAczaWtkHiA1n1tiSpAjqwl+uuINiN+hvL7tQbtKIMss9qaem9IERaUNrVFQJ2BNQ3FsYybO+Y86XoYUPk5ipJ3u5EibqC5WyoBQCjk0UipMI6ihhI3TclZmcemA3/hkQp9CvgLQg5xrvbPuuUx+PkfLfDgCoyEjKU5ozuw8zbZQ9WbmCQP0MLjJj6t4fv7HqveBvb7aEHsMd+pyR6MGfY+9h6YLtFggVsmdPRUTkAE37Ve1Pfu8A2Hb0BAp2nqKMihqU93Eokxaumag3eLqcVEM+63aU4stKe0lXib1qpALDYap0RDs1LYYMZzV7981jXTI6NgQiLWFhXOExrpzvXlz1q+rD9z6fpzvxnNopdmyhxgDaK9VMwwggZZMIIEQaADAgECAg0B7BySQN79LkBdfEd0MA0GCSqGSIb3DQEBDAUAMEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTE4MDYyMDAwMDAwMFoXDTM0MTIxMDAwMDAwMFowWzELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExMTAvBgNVBAMTKEdsb2JhbFNpZ24gVGltZXN0YW1waW5nIENBIC0gU0hBMzg0IC0gRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDwAuIwI/rgG+GadLOvdYNfqUdSx2E6Y3w5I3ltdPwx5HQSGZb6zidiW64HiifuV6PENe2zNMeswwzrgGZt0ShKwSy7uXDycq6M95laXXauv0SofEEkjo+6xU//NkGrpy39eE5DiP6TGRfZ7jHPvIo7bmrEiPDul/bc8xigS5kcDoenJuGIyaDlmeKe9JxMP11b7Lbv0mXPRQtUPbFUUweLmW64VJmKqDGSO/J6ffwOWN+BauGwbB5lgirUIceU/kKWO/ELsX9/RpgOhz16ZevRVqkuvftYPbWF+lOZTVt07XJLog2CNxkM0KvqWsHvD9WZuT/0TzXxnA/TNxNS2SU07Zbv+GfqCL6PSXr/kLHU9ykV1/kNXdaHQx50xHAotIB7vSqbu4ThDqxvDbm19m1W/oodCT4kDmcmx/yyDaCUsLKUzHvmZ/6mWLLU2EESwVX9bpHFu7FMCEue1EIGbxsY1TbqZK7O/fUF5uJm0A4FIayxEQYjGeT7BTRE6giunUlnEYuC5a1ahqdm/TMDAd6ZJflxbumcXQJMYDzPAo8B/XLukvGnEt5CEk3sqSbldwKsDlcMCdFhniaI/MiyTdtk8EWfusE/VKPYdgKVbGqNyiJc9gwE4yn6S7Ac0zd0hNkdZqs0c48efXxeltY9GbCX6oxQkW2vV4Z+EDcdaxoU3wIDAQABo4IBKTCCASUwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFOoWxmnn48tXRTkzpPBAvtDDvWWWMB8GA1UdIwQYMBaAFK5sBaOTE+Ki5+LXHNbH8H/IZ1OgMD4GCCsGAQUFBwEBBDIwMDAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL3Jvb3RyNjA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL3Jvb3QtcjYuY3JsMEcGA1UdIARAMD4wPAYEVR0gADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQwFAAOCAgEAf+KI2VdnK0JfgacJC7rEuygYVtZMv9sbB3DG+wsJrQA6YDMfOcYWaxlASSUIHuSb99akDY8elvKGohfeQb9P4byrze7AI4zGhf5LFST5GETsH8KkrNCyz+zCVmUdvX/23oLIt59h07VGSJiXAmd6FpVK22LG0LMCzDRIRVXd7OlKn14U7XIQcXZw0g+W8+o3V5SRGK/cjZk4GVjCqaF+om4VJuq0+X8q5+dIZGkv0pqhcvb3JEt0Wn1yhjWzAlcfi5z8u6xM3vreU0yD/RKxtklVT3WdrG9KyC5qucqIwxIwTrIIc59eodaZzul9S5YszBZrGM3kWTeGCSziRdayzW6CdaXajR63Wy+ILj198fKRMAWcznt8oMWsr1EG8BHHHTDFUVZg6HyVPSLj1QokUyeXgPpIiScseeI85Zse46qEgok+wEr1If5iEO0dMPz2zOpIJ3yLdUJ/a8vzpWuVHwRYNAqJ7YJQ5NF7qMnmvkiqK1XZjbclIA4bUaDUY6qD6mxyYUrJ+kPExlfFnbY8sIuwuRwx773vFNgUQGwgHcIt6AvGjW2MtnHtUiH+PvafnzkarqzSL3ogsfSsqh3iLRSd+pZqHcY8yvPZHL9TTaRHWXyVxENB+SXiLBB+gfkNlKd98rUJ9dhgckBQlSDUQ0S++qCV5yBZtnjGpGqqIpswggWDMIIDa6ADAgECAg5F5rsDgzPDhWVI5v9FUTANBgkqhkiG9w0BAQwFADBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNDEyMTAwMDAwMDBaFw0zNDEyMTAwMDAwMDBaMEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlQfoc8pm+ewUyns89w0I8bRFCyyCtEjG61s8roO4QZIzFKRvf+kqzMawiGvFtonRxrL/FM5RFCHsSt0bWsbWh+5NOhUG7WRmC5KAykTec5RO86eJf094YwjIElBtQmYvTbl5KE1SGooagLcZgQ5+xIq8ZEwhHENo1z08isWyZtWQmrcxBsW+4m0yBqYe+bnrqqO4v76CY1DQ8BiJ3+QPefXqoh8q0nAue+e8k7ttU+JIfIwQBzj/ZrJ3YX7g6ow8qrSk9vOVShIHbf2MsonP0KBhd8hYdLDUIzr3XTrKotudCd5dRC2Q8YHNV5L6frxQBGM032uTGL5rNrI55KwkNrfw77YcE1eTtt6y+OKFt3OiuDWqRfLgnTahb1SK8XJWbi6IxVFCRBWU7qPFOJabTk5aC0fzBjZJdzC8cTflpuwhCHX85mEWP3fV2ZGXhAps1AJNdMAU7f05+4PyXhShBLAL6f7uj+FuC7IIs2FmCWqxBjplllnA8DX9ydoojRoRh3CBCqiadR2eOoYFAJ7bgNYl+dwFnidZTHY5W+r5paHYgw/R/98wEfmFzzNI9cptZBQselhP00sIScWVZBpjDnk99bOMylitnEJFeW4OhxlcVLFltr+Mm9wT6Q1vuC7cZ27JixG1hBSKABlwg3mRl5HUGie/Nx4yB9gUYzwoTK8CAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK5sBaOTE+Ki5+LXHNbH8H/IZ1OgMB8GA1UdIwQYMBaAFK5sBaOTE+Ki5+LXHNbH8H/IZ1OgMA0GCSqGSIb3DQEBDAUAA4ICAQCDJe3o0f2VUs2ewASgkWnmXNCE3tytok/oR3jWZZipW6g8h3wCitFutxZz5l/AVJjVdL7BzeIRka0jGD3d4XJElrSVXsB7jpl4FkMTVlezorM7tXfcQHKso+ubNT6xCCGh58RDN3kyvrXnnCxMvEMpmY4w06wh4OMd+tgHM3ZUACIquU0gLnBo2uVT/INc053y/0QMRGby0uO9RgAabQK6JV2NoTFR3VRGHE3bmZbvGhwEXKYV73jgef5d2z6qTFX9mhWpb+Gm+99wMOnD7kJG7cKTBYn6fWN7P9BxgXwA6JiuDng0wyX7rwqfIGvdOxOPEoziQRpIenOgd2nHtlx/gsge/lgbKCuobK1ebcAF0nu364D+JTf+AptorEJdw+71zNzwUHXSNmmc5nsE324GabbeCglIWYfrexRgemSqaUPvkcdM7BjdbO9TLYyZ4V7ycj7PVMi9Z+ykD0xF/9O5MCMHTI8Qv4aW2ZlatJlXHKTMuxWJU7osBQ/kxJ4ZsRg01Uyduu33H68klQR4qAO77oHl2l98i0qhkHQlp7M+S8gsVr3HyO844lyS8Hn3nIS6dC1hASB+ftHyTwdZX4stQ1LrRgyU4fVmR3l31VRbH60kN8tFWk6gREjI2LCZxRWECfbWSUnAZbjmGnFuoKjxguhFPmzWAtcKZ4MFWsmkEDGCA0kwggNFAgEBMG8wWzELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExMTAvBgNVBAMTKEdsb2JhbFNpZ24gVGltZXN0YW1waW5nIENBIC0gU0hBMzg0IC0gRzQCEAEZdXRxyZLXRN+lluu5cBUwCwYJYIZIAWUDBAIBoIIBLTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwKwYJKoZIhvcNAQk0MR4wHDALBglghkgBZQMEAgGhDQYJKoZIhvcNAQELBQAwLwYJKoZIhvcNAQkEMSIEICktYskcc3KyxPm5x9gyYYT9PRVOUPSmcJ9kDjiLPczJMIGwBgsqhkiG9w0BCRACLzGBoDCBnTCBmjCBlwQgC3miOa5CEI3vVrNUBb+PzY5Zp0uE7uLew9lxweoXNOwwczBfpF0wWzELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExMTAvBgNVBAMTKEdsb2JhbFNpZ24gVGltZXN0YW1waW5nIENBIC0gU0hBMzg0IC0gRzQCEAEZdXRxyZLXRN+lluu5cBUwDQYJKoZIhvcNAQELBQAEggGADVoZuMULq3djCO+GBYUNk72EqqsL6e4X5YkaQr9fHFZNMtRABuLGBHwLXf5X62LEDvsY8XuncdR8yRm37+qOb++YWzqy14GZkymRLE6CVmc9Bk0ubMr0NFEEqoi5UAkJ+uw4cdshxZz8OBh7jRQ6A5g1pmCuLUlWyLOpYwghZkrhf7LjbaPGXoW506UXpSMVpzCOhS/xZLQCb3qk6/+giVF13cws/vFRImkeBjcHjZb+Mg9BfzHAcCeF9AM6VZqGfACWVKHKAQohGIaKOe13ckHCXsET91DTkkvnJjyyoNjGTOpxBlg5UH4vdB8iUlaed/B80zOZiXzhwR7Tl1Si3D5n/LBak+ef8+fB7AZZBWF6u1iQaIcaf5u0TdTseFMiJyEg4AjaJCXesa5v2G23yBxZNC1UIfqiD506uFg1E6zX59krRhR3dZRK0qd4P3NcRwuXH4rRzRg+44B9+ICUnh5QmnRDqhnhKXPtDUnSVgcfIQ6W8/IU1N9vLylRJe4q","x5c":["MIIDRTCCAi2gAwIBAgICAKYwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxEDAOBgNVBAMTB3Rlc3RUU0EwIhgPMjA5OTA5MTgxMTU0MzRaGA8yMTAwMDkxODExNTQzNFowTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxEDAOBgNVBAMTB3Rlc3RUU0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDI7xKl3GyBZregnHgxUw7rb3yO5jSo31Pa+EhxghQ0/rRKc/1DtfMQURjDYDdjqRmEXq8rVyEAuaBXSKqBMq9bazP7Ot8N/B0OgRCgXwizn//Ha5XfpHqV9lUud4oztdxapejfT6UQSIVqtgWEbZkr4N74G5NV13LlITtWmHpTLo2LfE7jAXTaoCjo/U/eVFFc6X7jyXwaAVyNC2Pi45d/GOaFx/MGHnK6zbN8PeIh5KqInp0UNcHZLBbduxWQhdISULR/x6pVocqExv6zLmRbn5I65wrYL/8gpQPTeZv4S2COpB+25Xy8oyaM6tPa96Pi1NIXtChWO8+muXj1Z4VfAgMBAAGjJzAlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAXFaaITvi3skq+czzmbyebtrAa8I9iEbjmWSPjoaUir2NYOLWsyQ7+gkBlMcw5+anP+BC98VBgNVjuQ5oXwdu57xouW7jk/dI5uuKLOFxFdCG7FwW3ycD6GGgj+/2LthxNOxc7CnnMjUuSw2FKJKesiuHQJpdPjgw9cKs+fZF5tr6ZhX4yAUFqouZJ7Hc5JSj3zyEpIbFapVpSAK8O1/mct4KDtt1SmyYn34o55ggyLurrlZ9ctQWHT8xyjc6+b4lEKbilA+xjTt+/BLIs/v/8CVIUzz6OzTCwBraj3kayM7CdGKSysocnJZ/yUcHVw1hLs1+JIMj75i0T6s+GtuT4A=="],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"pWbiEQRI4e_8nPV2AJPGNHg289WVwqnK7xqH6byLwXjc0hWrkniUSkIPLd3XDhGdpqLbqSLazU3cVTbSphV25aW1GS1G3Qsa3W3wzcwfq5ZhVDmuwO4u_322SAw01s3hVVxXciYK-9bTKkkfHfE_9ZKVSf7zEr28vyuQ92aZd85P-oz3YfvvnAxBGLYp-RPOQNSmtD2IxJWWTNLtwnnntBqr0WdGXS7wWSmUWzcKS9kNEAER1CN8L9SCh2mvBcNfDJlwCoLNaQZK_ZtBIrDQHmQjYkjSBupFTKp_TFic45j79AS7_-4sMdscJbZJzR9Tav7JInMhIKvh0mIfSfkIVQ"} \ No newline at end of file diff --git a/verifier/testdata/timestamp/sigEnv/withoutTimestamp.sig b/verifier/testdata/timestamp/sigEnv/withoutTimestamp.sig new file mode 100644 index 00000000..64d36fbc --- /dev/null +++ b/verifier/testdata/timestamp/sigEnv/withoutTimestamp.sig @@ -0,0 +1 @@ +{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEyNTY6YzA2NjllZjM0Y2RjMTQzMzJjMGYxYWIwYzJjMDFhY2I5MWQ5NjAxNGIxNzJmMWE3NmYzYTM5ZTYzZDFmMGJkYSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuZGlzdHJpYnV0aW9uLm1hbmlmZXN0LnYyK2pzb24iLCJzaXplIjo1Mjh9fQ","protected":"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA2LTE4VDE3OjA4OjM1KzA4OjAwIn0","header":{"x5c":["MIIDPjCCAiagAwIBAgIBeTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEPMA0GA1UEAxMGYWxwaW5lMB4XDTIzMDUwOTA0NTUxMloXDTMzMDUxMDA0NTUxMlowTjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxDzANBgNVBAMTBmFscGluZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5hpq1229GGLjMK6i9KZhuUO+SV7rUFnWIDiIPO5yWxYDkl+bGroeAvJYu6MVCMQ6FMRXD9jhnG6R+sAHwY7gVgcJ1OXak87PkLp/Ii1Cr7XkkySZeD+Br1vSQzfxs3pFG+iBCeVVkeZdsg+xqwnAlqAILXwIbTGRyJP1Xiu9nwOeuX1YmxPl2m29Pt1EtfVCL9COsVKt5LgOVyWP/9ISWevOBqSCU9bk35HFo9VTeUf6+ffhSMjv0Y9uwkFFOKXpcV8Sa3ArqyBmgQlUfGg1iwYlqiDE0fTYxiB3gLgETAlmTm50J+WB9LoDrnrQpbXFLoegm+JV+uSD8J8H7DL2sCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQAt0Nvna1c4pPn8kzoN5VvmFmeIgdO/BJpmdhdg0WIQ9aeN/xPXXaVjPp1Mk7edXHAvBwQr0Gyzqyy7g/h0gdnAFG7f6blrRNzbrRBCq6cNqX8iwgK/9+2OYKxk1QWj8Gx0cvu1DN1aXjPPGgQ2j3tHjJvJv32J/zuZa8gU40RPPSLaBlc5ZjpFmyi29sKlTeeZ+F/Ssic51qXXw2CsYGGWK5yQ3xSCxbw6bb2G/s/YI7/KlWg9BktBJHzRu04ZNR77W7/dyJ3Lj17PlW1XKmMOFHsQivagXeRCbmYZ43fX4ugFRFKL7KE0EgmGOWpJ0xv+6ig93sqHzQ/0uv1YgFov"],"io.cncf.notary.signingAgent":"Notation/1.0.0"},"signature":"ToCyclYJtk-Gtb13j1sWW7FQ7iZA9Vq6u_x6nJD3pRkBXhtatvSBsaZ_mqFHKrJWEY3UOBzi2SYobCQYww0cVwbzeDetPhjBhmH-bW-N_pbjGntgB2K1owvJnlycUoOfC2RQ1eDa4mC7Dj1mKzA5Tb-qnNbrT75pvQKZjTY1RZaN6p_xKBJA-AAiQrgHEvlf4m8ZbvqtZ0x4_uiGwfWoNCqPtrZK71mEpPSjfOT3mN5FkZqY0L3jSKRtFRLd1rb0UA2RB-E0CshsNb-hJgTX4SIzUlgcVT10SJnKw0yy_QqrxhMlejOUiV8HHKgbsZqQg1kwFjP5QwzWr5HB6vbRzg"} \ No newline at end of file diff --git a/verifier/testdata/truststore/x509/ca/valid-trust-store/TestTimestamp.crt b/verifier/testdata/truststore/x509/ca/valid-trust-store/TestTimestamp.crt new file mode 100644 index 00000000..dd0094e9 --- /dev/null +++ b/verifier/testdata/truststore/x509/ca/valid-trust-store/TestTimestamp.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/verifier/testdata/truststore/x509/ca/valid-trust-store/TestTimestampNotYetValid.crt b/verifier/testdata/truststore/x509/ca/valid-trust-store/TestTimestampNotYetValid.crt new file mode 100644 index 00000000..b135a840 --- /dev/null +++ b/verifier/testdata/truststore/x509/ca/valid-trust-store/TestTimestampNotYetValid.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRTCCAi2gAwIBAgICAKYwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3Rhcnkx +EDAOBgNVBAMTB3Rlc3RUU0EwIhgPMjA5OTA5MTgxMTU0MzRaGA8yMTAwMDkxODEx +NTQzNFowTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0 +dGxlMQ8wDQYDVQQKEwZOb3RhcnkxEDAOBgNVBAMTB3Rlc3RUU0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDI7xKl3GyBZregnHgxUw7rb3yO5jSo31Pa ++EhxghQ0/rRKc/1DtfMQURjDYDdjqRmEXq8rVyEAuaBXSKqBMq9bazP7Ot8N/B0O +gRCgXwizn//Ha5XfpHqV9lUud4oztdxapejfT6UQSIVqtgWEbZkr4N74G5NV13Ll +ITtWmHpTLo2LfE7jAXTaoCjo/U/eVFFc6X7jyXwaAVyNC2Pi45d/GOaFx/MGHnK6 +zbN8PeIh5KqInp0UNcHZLBbduxWQhdISULR/x6pVocqExv6zLmRbn5I65wrYL/8g +pQPTeZv4S2COpB+25Xy8oyaM6tPa96Pi1NIXtChWO8+muXj1Z4VfAgMBAAGjJzAl +MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzANBgkqhkiG9w0B +AQsFAAOCAQEAXFaaITvi3skq+czzmbyebtrAa8I9iEbjmWSPjoaUir2NYOLWsyQ7 ++gkBlMcw5+anP+BC98VBgNVjuQ5oXwdu57xouW7jk/dI5uuKLOFxFdCG7FwW3ycD +6GGgj+/2LthxNOxc7CnnMjUuSw2FKJKesiuHQJpdPjgw9cKs+fZF5tr6ZhX4yAUF +qouZJ7Hc5JSj3zyEpIbFapVpSAK8O1/mct4KDtt1SmyYn34o55ggyLurrlZ9ctQW +HT8xyjc6+b4lEKbilA+xjTt+/BLIs/v/8CVIUzz6OzTCwBraj3kayM7CdGKSysoc +nJZ/yUcHVw1hLs1+JIMj75i0T6s+GtuT4A== +-----END CERTIFICATE----- diff --git a/verifier/testdata/truststore/x509/tsa/test-mismatch/wabbit-networks.io.crt b/verifier/testdata/truststore/x509/tsa/test-mismatch/wabbit-networks.io.crt new file mode 100644 index 00000000..60028b63 --- /dev/null +++ b/verifier/testdata/truststore/x509/tsa/test-mismatch/wabbit-networks.io.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVjCCAj6gAwIBAgIBUTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEb +MBkGA1UEAxMSd2FiYml0LW5ldHdvcmtzLmlvMB4XDTIzMDExOTA4MTkwN1oXDTMz +MDExOTA4MTkwN1owWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQH +EwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxGzAZBgNVBAMTEndhYmJpdC1uZXR3 +b3Jrcy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANHhlP+SiY7h +sGlf2mADOzJW/J9siqMkiQvSOx0OSM2yxetfVQL/abi4iqCXM6wkSxviBeNwIoYE +s4thMA8NGEbnKoXktyh9vmiLB1FW7HHr4QLwjgLzgWJKIQTy1JmDBecXZh56d0f3 +w3Yj1IDTvkIScXCNI+5v/08GUQKhyBwv7Fq9MYpo2lfXSI7V33BKKddXIxPGVWwK +GvPE0sg2VV7WM84ZZLdDKz2mq0PtPTHrSwg3hlK/mjn+blg3gsYQ4h9/7Z6nNaF9 +X0SdyESl841ZWrtMhAOFpIzLbz9ete8NRd3bYCRBIr5gscHWTf6lyUgy4xzsSwMH +PsGLM4A+Z00CAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsG +AQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQAbN0Eru56uTQSC28ZTf8D7VyCkYrrW +LYiJMYdOKBzzKV9mKaM0OGF2uyWwDaPxp9KTdLXmBp9EFq5SXXArFA+nRS7KinDA +e2O7A/9Std2XjKi927rkA2cj239d5lRsjWXqJXf9vAMV9a2FjUM/in2Eevlq7bvj +FE3l26VXCKtOs9ErmfxrL+6ETRKSVYOOG/rSHFv/SB2MlqDg5QsXC9lZjzL5/X/i +oe2qZKhp6X5DPpad1q1Q4ItKdTN+2EXyMyoHn1BJKNba7CUUvXf03EJebT/Im+qo +zfEksJeZJUSlSujANUPoCpsEYGWWQx5G+ViG05Sqs+6ppKrut+P+DVPo +-----END CERTIFICATE----- diff --git a/verifier/testdata/truststore/x509/tsa/test-timestamp/globalsignRoot.cer b/verifier/testdata/truststore/x509/tsa/test-timestamp/globalsignRoot.cer new file mode 100644 index 00000000..3492b955 Binary files /dev/null and b/verifier/testdata/truststore/x509/tsa/test-timestamp/globalsignRoot.cer differ diff --git a/verifier/timestamp_test.go b/verifier/timestamp_test.go new file mode 100644 index 00000000..288014de --- /dev/null +++ b/verifier/timestamp_test.go @@ -0,0 +1,421 @@ +// 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 verifier + +import ( + "context" + "crypto/x509" + "net/http" + "os" + "testing" + "time" + + "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" + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" +) + +func TestAuthenticTimestamp(t *testing.T) { + dir.UserConfigDir = "testdata" + trustStore := truststore.NewX509TrustStore(dir.ConfigFS()) + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:test-timestamp"}, + TrustedIdentities: []string{"*"}, + } + revocationTimestsampClient, err := revocation.NewTimestamp(&http.Client{Timeout: 5 * time.Second}) + if err != nil { + t.Fatalf("failed to get revocation timestamp client: %v", err) + } + // valid JWS signature envelope with timestamp countersignature + jwsEnvContent, err := parseEnvContent("testdata/timestamp/sigEnv/jwsWithTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + + // valid COSE signature envelope with timestamp countersignature + coseEnvContent, err := parseEnvContent("testdata/timestamp/sigEnv/coseWithTimestamp.sig", cose.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + + t.Run("verify Authentic Timestamp with jws format", func(t *testing.T) { + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: jwsEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + if err := authenticTimestampResult.Error; err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + }) + + t.Run("verify Authentic Timestamp with cose format", func(t *testing.T) { + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + if err := authenticTimestampResult.Error; err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + }) + + t.Run("verify Authentic Timestamp jws with expired codeSigning cert", func(t *testing.T) { + jwsEnvContent, err := parseEnvContent("testdata/timestamp/sigEnv/jwsExpiredWithTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: jwsEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + if err := authenticTimestampResult.Error; err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + }) + + t.Run("verify Authentic Timestamp cose with expired codeSigning cert", func(t *testing.T) { + coseEnvContent, err := parseEnvContent("testdata/timestamp/sigEnv/coseExpiredWithTimestamp.sig", cose.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + if err := authenticTimestampResult.Error; err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + }) + + t.Run("verify Authentic Timestamp with afterCertExpiry set", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAfterCertExpiry, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:test-timestamp"}, + TrustedIdentities: []string{"*"}, + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + if err := authenticTimestampResult.Error; err != nil { + t.Fatalf("expected nil error, but got %s", err) + } + }) + + t.Run("verify Authentic Timestamp failed due to invalid trust policy", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa"}, + TrustedIdentities: []string{"*"}, + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: jwsEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to check tsa trust store configuration in turst policy with error: invalid trust policy statement: \"test-timestamp\" is missing separator in trust store value \"tsa\". The required format is :" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to missing tsa in trust policy and expired codeSigning cert", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store"}, + TrustedIdentities: []string{"*"}, + } + coseEnvContent, err := parseEnvContent("testdata/timestamp/sigEnv/coseExpiredWithTimestamp.sig", cose.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "verification time is after certificate \"CN=testTSA,O=Notary,L=Seattle,ST=WA,C=US\" validity period, it was expired at \"Tue, 18 Jun 2024 07:30:31 +0000\"" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to missing timestamp countersignature", func(t *testing.T) { + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/withoutTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "no timestamp countersignature was found in the signature envelope" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to invalid timestamp countersignature content type", func(t *testing.T) { + signedToken, err := os.ReadFile("testdata/timestamp/countersignature/TimeStampTokenWithInvalideContentType.p7s") + if err != nil { + t.Fatalf("failed to get signedToken: %v", err) + } + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/withoutTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + envContent.SignerInfo.UnsignedAttributes.TimestampSignature = signedToken + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to parse timestamp countersignature with error: unexpected content type: 1.2.840.113549.1.7.1" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to invalid TSTInfo", func(t *testing.T) { + signedToken, err := os.ReadFile("testdata/timestamp/countersignature/TimeStampTokenWithInvalidTSTInfo.p7s") + if err != nil { + t.Fatalf("failed to get signedToken: %v", err) + } + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/withoutTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + envContent.SignerInfo.UnsignedAttributes.TimestampSignature = signedToken + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to get the timestamp TSTInfo with error: cannot unmarshal TSTInfo from timestamp token: asn1: structure error: tags don't match (23 vs {class:0 tag:16 length:3 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:24 set:false omitEmpty:false} Time @89" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to failed to validate TSTInfo", func(t *testing.T) { + signedToken, err := os.ReadFile("testdata/timestamp/countersignature/TimeStampToken.p7s") + if err != nil { + t.Fatalf("failed to get signedToken: %v", err) + } + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/withoutTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + envContent.SignerInfo.UnsignedAttributes.TimestampSignature = signedToken + envContent.SignerInfo.Signature = []byte("mismatch") + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to get timestamp from timestamp countersignature with error: invalid TSTInfo: mismatched message" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to failed to verify timestamp countersignature", func(t *testing.T) { + signedToken, err := os.ReadFile("testdata/timestamp/countersignature/TimeStampTokenWithoutCertificate.p7s") + if err != nil { + t.Fatalf("failed to get signedToken: %v", err) + } + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/withoutTimestamp.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + envContent.SignerInfo.UnsignedAttributes.TimestampSignature = signedToken + envContent.SignerInfo.Signature = []byte("notation") + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to verify the timestamp countersignature with error: failed to verify signed token: signing certificate not found in the timestamp token" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to trust store does not exist", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:does-not-exist"}, + TrustedIdentities: []string{"*"}, + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to load tsa trust store with error: the trust store \"does-not-exist\" of type \"tsa\" does not exist" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to empty trust store", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:test-empty"}, + TrustedIdentities: []string{"*"}, + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, dummyTrustStore{}, revocationTimestsampClient, outcome) + expectedErrMsg := "no trusted TSA certificate found in trust store" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to tsa not trust", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:test-mismatch"}, + TrustedIdentities: []string{"*"}, + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: coseEnvContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "failed to verify the timestamp countersignature with error: failed to verify signed token: cms verification failure: x509: certificate signed by unknown authority" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to timestamp before signing cert not before", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:test-timestamp"}, + TrustedIdentities: []string{"*"}, + } + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/timestampBeforeNotBefore.sig", jws.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "timestamp can be before certificate \"CN=testTSA,O=Notary,L=Seattle,ST=WA,C=US\" validity period, it will be valid from \"Fri, 18 Sep 2099 11:54:34 +0000\"" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("verify Authentic Timestamp failed due to timestamp after signing cert not after", func(t *testing.T) { + dummyTrustPolicy := &trustpolicy.TrustPolicy{ + Name: "test-timestamp", + RegistryScopes: []string{"*"}, + SignatureVerification: trustpolicy.SignatureVerification{ + VerificationLevel: trustpolicy.LevelStrict.Name, + VerifyTimestamp: trustpolicy.OptionAlways, + }, + TrustStores: []string{"ca:valid-trust-store", "tsa:test-timestamp"}, + TrustedIdentities: []string{"*"}, + } + envContent, err := parseEnvContent("testdata/timestamp/sigEnv/timestampAfterNotAfter.sig", cose.MediaTypeEnvelope) + if err != nil { + t.Fatalf("failed to get signature envelope content: %v", err) + } + outcome := ¬ation.VerificationOutcome{ + EnvelopeContent: envContent, + VerificationLevel: trustpolicy.LevelStrict, + } + authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestsampClient, outcome) + expectedErrMsg := "timestamp can be after certificate \"CN=testTSA,O=Notary,L=Seattle,ST=WA,C=US\" validity period, it was expired at \"Tue, 18 Sep 2001 11:54:34 +0000\"" + if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + }) +} + +func parseEnvContent(filepath, format string) (*signature.EnvelopeContent, error) { + sigEnvBytes, err := os.ReadFile(filepath) + if err != nil { + return nil, err + } + sigEnv, err := signature.ParseEnvelope(format, sigEnvBytes) + if err != nil { + return nil, err + } + return sigEnv.Content() +} + +type dummyTrustStore struct{} + +func (ts dummyTrustStore) GetCertificates(ctx context.Context, storeType truststore.Type, namedStore string) ([]*x509.Certificate, error) { + return nil, nil +} diff --git a/verifier/trustpolicy/oci_test.go b/verifier/trustpolicy/oci_test.go index 2eff90ec..e0d6beb5 100644 --- a/verifier/trustpolicy/oci_test.go +++ b/verifier/trustpolicy/oci_test.go @@ -175,6 +175,15 @@ func TestValidateInvalidPolicyDocument(t *testing.T) { t.Fatalf("policy statement with invalid SignatureVerification should return error") } + // Invalid SignatureVerification VerifyTimestamp + policyDoc = dummyOCIPolicyDocument() + policyDoc.TrustPolicies[0].SignatureVerification.VerifyTimestamp = "invalid" + expectedErrMsg := "oci trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: verifyTimestamp must be \"always\" or \"afterCertExpiry\", but got \"invalid\"" + err = policyDoc.Validate() + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, err) + } + // strict SignatureVerification should have a trust store policyDoc = dummyOCIPolicyDocument() policyDoc.TrustPolicies[0].TrustStores = []string{} @@ -352,13 +361,32 @@ func TestValidateValidPolicyDocument(t *testing.T) { policyStatement5.TrustedIdentities = []string{"*"} policyStatement5.SignatureVerification = SignatureVerification{VerificationLevel: "strict"} + policyStatement6 := policyStatement1.clone() + policyStatement6.Name = "test-statement-name-6" + policyStatement6.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor6"} + policyStatement6.SignatureVerification.VerifyTimestamp = "" + + policyStatement7 := policyStatement1.clone() + policyStatement7.Name = "test-statement-name-7" + policyStatement7.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor7"} + policyStatement7.SignatureVerification.VerifyTimestamp = OptionAlways + + policyStatement8 := policyStatement1.clone() + policyStatement8.Name = "test-statement-name-8" + policyStatement8.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor8"} + policyStatement8.SignatureVerification.VerifyTimestamp = OptionAfterCertExpiry + policyDoc.TrustPolicies = []OCITrustPolicy{ *policyStatement1, *policyStatement2, *policyStatement3, *policyStatement4, *policyStatement5, + *policyStatement6, + *policyStatement7, + *policyStatement8, } + err := policyDoc.Validate() if err != nil { t.Fatalf("validation failed on a good policy document. Error : %q", err) diff --git a/verifier/trustpolicy/trustpolicy.go b/verifier/trustpolicy/trustpolicy.go index e4f86c85..cbab4275 100644 --- a/verifier/trustpolicy/trustpolicy.go +++ b/verifier/trustpolicy/trustpolicy.go @@ -63,6 +63,19 @@ const ( ActionSkip ValidationAction = "skip" ) +// TimestampOption is an enum for timestamp verifiction options such as Always, +// AfterCertExpiry. +type TimestampOption string + +const ( + // OptionAlways denotes always perform timestamp verification + OptionAlways TimestampOption = "always" + + // OptionAfterCertExpiry denotes perform timestamp verification only if + // the signing certificate chain has expired + OptionAfterCertExpiry TimestampOption = "afterCertExpiry" +) + var ( LevelStrict = &VerificationLevel{ Name: "strict", @@ -136,6 +149,7 @@ var ( type SignatureVerification struct { VerificationLevel string `json:"level"` Override map[ValidationType]ValidationAction `json:"override,omitempty"` + VerifyTimestamp TimestampOption `json:"verifyTimestamp,omitempty"` } type errPolicyNotExist struct{} @@ -263,6 +277,11 @@ func validatePolicyCore(name string, signatureVerification SignatureVerification if err != nil { return fmt.Errorf("trust policy statement %q has invalid signatureVerification: %w", name, err) } + if signatureVerification.VerifyTimestamp != "" && + signatureVerification.VerifyTimestamp != OptionAlways && + signatureVerification.VerifyTimestamp != OptionAfterCertExpiry { + return fmt.Errorf("trust policy statement %q has invalid signatureVerification: verifyTimestamp must be %q or %q, but got %q", name, OptionAlways, OptionAfterCertExpiry, signatureVerification.VerifyTimestamp) + } // Any signature verification other than "skip" needs a trust store and // trusted identities diff --git a/verifier/trustpolicy/trustpolicy_test.go b/verifier/trustpolicy/trustpolicy_test.go index b8cb9dff..f58cadcc 100644 --- a/verifier/trustpolicy/trustpolicy_test.go +++ b/verifier/trustpolicy/trustpolicy_test.go @@ -243,7 +243,7 @@ func TestGetVerificationLevel(t *testing.T) { } else { for index, action := range tt.verificationActions { if action != level.Enforcement[ValidationTypes[index]] { - t.Errorf("%q verification action should be %q for Verification Level %q", ValidationTypes[index], action, tt.verificationLevel) + t.Errorf("%q verification action should be %q for Verification Level %v", ValidationTypes[index], action, tt.verificationLevel) } } } @@ -293,7 +293,7 @@ func TestCustomVerificationLevel(t *testing.T) { } for index, action := range tt.verificationActions { if action != level.Enforcement[ValidationTypes[index]] { - t.Errorf("%q verification action should be %q for custom verification %q", ValidationTypes[index], action, tt.customVerification) + t.Errorf("%q verification action should be %q for custom verification %v", ValidationTypes[index], action, tt.customVerification) } } } @@ -302,6 +302,9 @@ func TestCustomVerificationLevel(t *testing.T) { } func TestGetDocument(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } dir.UserConfigDir = "/" var ociDoc OCIDocument var blobDoc BlobDocument @@ -349,6 +352,9 @@ func TestGetDocumentErrors(t *testing.T) { }) t.Run("invalid json file", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } tempRoot := t.TempDir() path := filepath.Join(tempRoot, "invalid.json") if err := os.WriteFile(path, []byte(`{"invalid`), 0600); err != nil { diff --git a/verifier/truststore/truststore.go b/verifier/truststore/truststore.go index c98b2c6c..067f5e63 100644 --- a/verifier/truststore/truststore.go +++ b/verifier/truststore/truststore.go @@ -36,12 +36,14 @@ type Type string const ( TypeCA Type = "ca" TypeSigningAuthority Type = "signingAuthority" + TypeTSA Type = "tsa" ) var ( Types = []Type{ TypeCA, TypeSigningAuthority, + TypeTSA, } ) diff --git a/verifier/truststore/truststore_test.go b/verifier/truststore/truststore_test.go index 2f223a16..762553e5 100644 --- a/verifier/truststore/truststore_test.go +++ b/verifier/truststore/truststore_test.go @@ -28,13 +28,10 @@ var trustStore = NewX509TrustStore(dir.NewSysFS(filepath.FromSlash("../testdata/ // TestLoadTrustStore tests a valid trust store func TestLoadValidTrustStore(t *testing.T) { - certs, err := trustStore.GetCertificates(context.Background(), "ca", "valid-trust-store") + _, err := trustStore.GetCertificates(context.Background(), "ca", "valid-trust-store") if err != nil { t.Fatalf("could not get certificates from trust store. %q", err) } - if len(certs) != 4 { - t.Fatalf("unexpected number of certificates in the trust store, expected: %d, got: %d", 4, len(certs)) - } } // TestLoadValidTrustStoreWithSelfSignedSigningCertificate tests a valid trust store with self-signed signing certificate diff --git a/verifier/verifier.go b/verifier/verifier.go index 4ba87fec..4eeca168 100644 --- a/verifier/verifier.go +++ b/verifier/verifier.go @@ -32,6 +32,7 @@ import ( "github.com/notaryproject/notation-core-go/revocation" revocationresult "github.com/notaryproject/notation-core-go/revocation/result" "github.com/notaryproject/notation-core-go/signature" + nx509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/internal/envelope" @@ -44,6 +45,7 @@ import ( "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" pluginframework "github.com/notaryproject/notation-plugin-framework-go/plugin" + "github.com/notaryproject/tspclient-go" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -56,19 +58,24 @@ var algorithms = map[crypto.Hash]digest.Algorithm{ // verifier implements notation.Verifier, notation.BlobVerifier and notation.verifySkipper type verifier struct { - ociTrustPolicyDoc *trustpolicy.OCIDocument - blobTrustPolicyDoc *trustpolicy.BlobDocument - trustStore truststore.X509TrustStore - pluginManager plugin.Manager - revocationClient revocation.Revocation + ociTrustPolicyDoc *trustpolicy.OCIDocument + blobTrustPolicyDoc *trustpolicy.BlobDocument + trustStore truststore.X509TrustStore + pluginManager plugin.Manager + revocationClient revocation.Revocation + revocationTimestampClient revocation.Revocation } // VerifierOptions specifies additional parameters that can be set when using // the NewVerifierWithOptions constructor type VerifierOptions struct { // RevocationClient is an implementation of revocation.Revocation to use for - // verifying revocation + // verifying revocation of code signing certificate chain RevocationClient revocation.Revocation + + // RevocationTimestampClient is an implementaion of evocation.Revocation to + // use for verifying revocation of timestamping certificate chain + RevocationTimestampClient revocation.Revocation } // NewOCIVerifierFromConfig returns a OCI verifier based on local file system @@ -122,6 +129,15 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo } } + revocationTimestampClient := verifierOptions.RevocationTimestampClient + if revocationTimestampClient == nil { + var err error + revocationTimestampClient, err = revocation.NewTimestamp(&http.Client{Timeout: 2 * time.Second}) + if err != nil { + return nil, err + } + } + if trustStore == nil { return nil, errors.New("trustStore cannot be nil") } @@ -143,13 +159,13 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo } return &verifier{ - ociTrustPolicyDoc: ociTrustPolicy, - blobTrustPolicyDoc: blobTrustPolicy, - trustStore: trustStore, - pluginManager: pluginManager, - revocationClient: revocationClient, + ociTrustPolicyDoc: ociTrustPolicy, + blobTrustPolicyDoc: blobTrustPolicy, + trustStore: trustStore, + pluginManager: pluginManager, + revocationClient: revocationClient, + revocationTimestampClient: revocationTimestampClient, }, nil - } // NewFromConfig returns a OCI verifier based on local file system @@ -194,13 +210,13 @@ func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDesc logger := log.GetLogger(ctx) logger.Debugf("Verify signature of media type %v", opts.SignatureMediaType) if v.blobTrustPolicyDoc == nil { - return nil, errors.New("blobTrustPolicyDoc is nil") + return nil, errors.New("blobTrustPolicyDoc is nil") } var trustPolicy *trustpolicy.BlobTrustPolicy var err error if opts.TrustPolicyName == "" { - trustPolicy, err = v.blobTrustPolicyDoc.GetGlobalTrustPolicy(); + trustPolicy, err = v.blobTrustPolicyDoc.GetGlobalTrustPolicy() } else { trustPolicy, err = v.blobTrustPolicyDoc.GetApplicableTrustPolicy(opts.TrustPolicyName) } @@ -220,7 +236,7 @@ func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDesc logger.Debug("Skipping signature verification") return outcome, nil } - err = v.processSignature(ctx, signature, opts.SignatureMediaType, trustPolicy.Name, trustPolicy.TrustedIdentities, trustPolicy.TrustStores, opts.PluginConfig, outcome) + err = v.processSignature(ctx, signature, opts.SignatureMediaType, trustPolicy.Name, trustPolicy.TrustedIdentities, trustPolicy.TrustStores, trustPolicy.SignatureVerification, opts.PluginConfig, outcome) if err != nil { outcome.Error = err return outcome, err @@ -282,7 +298,7 @@ func (v *verifier) Verify(ctx context.Context, desc ocispec.Descriptor, signatur logger.Debugf("Verify signature against artifact %v referenced as %s in signature media type %v", desc.Digest, artifactRef, envelopeMediaType) if v.ociTrustPolicyDoc == nil { - return nil, errors.New("ociTrustPolicyDoc is nil") + return nil, errors.New("ociTrustPolicyDoc is nil") } trustPolicy, err := v.ociTrustPolicyDoc.GetApplicableTrustPolicy(artifactRef) @@ -303,7 +319,7 @@ func (v *verifier) Verify(ctx context.Context, desc ocispec.Descriptor, signatur logger.Debug("Skipping signature verification") return outcome, nil } - err = v.processSignature(ctx, signature, envelopeMediaType, trustPolicy.Name, trustPolicy.TrustedIdentities, trustPolicy.TrustStores, pluginConfig, outcome) + err = v.processSignature(ctx, signature, envelopeMediaType, trustPolicy.Name, trustPolicy.TrustedIdentities, trustPolicy.TrustStores, trustPolicy.SignatureVerification, pluginConfig, outcome) if err != nil { outcome.Error = err @@ -334,7 +350,7 @@ func (v *verifier) Verify(ctx context.Context, desc ocispec.Descriptor, signatur return outcome, outcome.Error } -func (v *verifier) processSignature(ctx context.Context, sigBlob []byte, envelopeMediaType, policyName string, trustedIdentities, trustStores []string, pluginConfig map[string]string, outcome *notation.VerificationOutcome) error { +func (v *verifier) processSignature(ctx context.Context, sigBlob []byte, envelopeMediaType, policyName string, trustedIdentities, trustStores []string, signatureVerification trustpolicy.SignatureVerification, pluginConfig map[string]string, outcome *notation.VerificationOutcome) error { logger := log.GetLogger(ctx) // verify integrity first. notation will always verify integrity no matter @@ -445,7 +461,7 @@ func (v *verifier) processSignature(ctx context.Context, sigBlob []byte, envelop // verify authentic timestamp logger.Debug("Validating authentic timestamp") - authenticTimestampResult := verifyAuthenticTimestamp(outcome) + authenticTimestampResult := verifyAuthenticTimestamp(ctx, policyName, trustStores, signatureVerification, v.trustStore, v.revocationTimestampClient, outcome) outcome.VerificationResults = append(outcome.VerificationResults, authenticTimestampResult) logVerificationResult(logger, authenticTimestampResult) if isCriticalFailure(authenticTimestampResult) { @@ -662,51 +678,34 @@ func verifyExpiry(outcome *notation.VerificationOutcome) *notation.ValidationRes } } -func verifyAuthenticTimestamp(outcome *notation.VerificationOutcome) *notation.ValidationResult { - invalidTimestamp := false - var err error - - if signerInfo := outcome.EnvelopeContent.SignerInfo; signerInfo.SignedAttributes.SigningScheme == signature.SigningSchemeX509 { - // TODO verify RFC3161 TSA signature if present (not in RC1) - // https://github.com/notaryproject/notation-go/issues/78 - if len(signerInfo.UnsignedAttributes.TimestampSignature) == 0 { - // if there is no TSA signature, then every certificate should be - // valid at the time of verification - now := time.Now() - for _, cert := range signerInfo.CertificateChain { - if now.Before(cert.NotBefore) { - invalidTimestamp = true - err = fmt.Errorf("certificate %q is not valid yet, it will be valid from %q", cert.Subject, cert.NotBefore.Format(time.RFC1123Z)) - break - } - if now.After(cert.NotAfter) { - invalidTimestamp = true - err = fmt.Errorf("certificate %q is not valid anymore, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z)) - break - } - } - } - } else if signerInfo.SignedAttributes.SigningScheme == signature.SigningSchemeX509SigningAuthority { - authenticSigningTime := signerInfo.SignedAttributes.SigningTime - // TODO use authenticSigningTime from signerInfo - // https://github.com/notaryproject/notation-core-go/issues/38 - for _, cert := range signerInfo.CertificateChain { - if authenticSigningTime.Before(cert.NotBefore) || authenticSigningTime.After(cert.NotAfter) { - invalidTimestamp = true - err = fmt.Errorf("certificate %q was not valid when the digital signature was produced at %q", cert.Subject, authenticSigningTime.Format(time.RFC1123Z)) - break - } - } - } +func verifyAuthenticTimestamp(ctx context.Context, policyName string, trustStores []string, signatureVerification trustpolicy.SignatureVerification, x509TrustStore truststore.X509TrustStore, r revocation.Revocation, outcome *notation.VerificationOutcome) *notation.ValidationResult { + logger := log.GetLogger(ctx) - if invalidTimestamp { + signerInfo := outcome.EnvelopeContent.SignerInfo + // under signing scheme notary.x509 + if signerInfo.SignedAttributes.SigningScheme == signature.SigningSchemeX509 { + logger.Debug("Under signing scheme notary.x509...") return ¬ation.ValidationResult{ - Error: err, + Error: verifyTimestamp(ctx, policyName, trustStores, signatureVerification, x509TrustStore, r, outcome), Type: trustpolicy.TypeAuthenticTimestamp, Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], } } + // under signing scheme notary.x509.signingAuthority + logger.Debug("Under signing scheme notary.x509.signingAuthority...") + authenticSigningTime := signerInfo.SignedAttributes.SigningTime + for _, cert := range signerInfo.CertificateChain { + if authenticSigningTime.Before(cert.NotBefore) || authenticSigningTime.After(cert.NotAfter) { + return ¬ation.ValidationResult{ + Error: fmt.Errorf("certificate %q was not valid when the digital signature was produced at %q", cert.Subject, authenticSigningTime.Format(time.RFC1123Z)), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + } + + // success return ¬ation.ValidationResult{ Type: trustpolicy.TypeAuthenticTimestamp, Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], @@ -724,12 +723,12 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca authenticSigningTime, err := outcome.EnvelopeContent.SignerInfo.AuthenticSigningTime() if err != nil { - logger.Debugf("not using authentic signing time due to error retrieving AuthenticSigningTime, err: %v", err) + logger.Debugf("Not using authentic signing time due to error retrieving AuthenticSigningTime, err: %v", err) authenticSigningTime = time.Time{} } certResults, err := r.Validate(outcome.EnvelopeContent.SignerInfo.CertificateChain, authenticSigningTime) if err != nil { - logger.Debug("error while checking revocation status, err: %s", err.Error()) + logger.Debug("Error while checking revocation status, err: %s", err.Error()) return ¬ation.ValidationResult{ Type: trustpolicy.TypeRevocation, Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeRevocation], @@ -741,6 +740,23 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca Type: trustpolicy.TypeRevocation, Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeRevocation], } + finalResult, problematicCertSubject := revocationFinalResult(certResults, outcome.EnvelopeContent.SignerInfo.CertificateChain, logger) + switch finalResult { + case revocationresult.ResultOK: + logger.Debug("No verification impacting errors encountered while checking revocation, status is OK") + case revocationresult.ResultRevoked: + result.Error = fmt.Errorf("signing certificate with subject %q is revoked", problematicCertSubject) + default: + // revocationresult.ResultUnknown + result.Error = fmt.Errorf("signing certificate with subject %q revocation status is unknown", problematicCertSubject) + } + + return result +} + +// revocationFinalResult returns the final revocation result and problematic +// certificate subject if the final result is not ResultOK +func revocationFinalResult(certResults []*revocationresult.CertRevocationResult, certChain []*x509.Certificate, logger log.Logger) (revocationresult.Result, string) { finalResult := revocationresult.ResultUnknown numOKResults := 0 var problematicCertSubject string @@ -748,14 +764,14 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca var revokedCertSubject string for i := len(certResults) - 1; i >= 0; i-- { if len(certResults[i].ServerResults) > 0 && certResults[i].ServerResults[0].Error != nil { - logger.Debugf("error for certificate #%d in chain with subject %v for server %q: %v", (i + 1), outcome.EnvelopeContent.SignerInfo.CertificateChain[i].Subject.String(), certResults[i].ServerResults[0].Server, certResults[i].ServerResults[0].Error) + logger.Debugf("Error for certificate #%d in chain with subject %v for server %q: %v", (i + 1), certChain[i].Subject.String(), certResults[i].ServerResults[0].Server, certResults[i].ServerResults[0].Error) } if certResults[i].Result == revocationresult.ResultOK || certResults[i].Result == revocationresult.ResultNonRevokable { numOKResults++ } else { finalResult = certResults[i].Result - problematicCertSubject = outcome.EnvelopeContent.SignerInfo.CertificateChain[i].Subject.String() + problematicCertSubject = certChain[i].Subject.String() if certResults[i].Result == revocationresult.ResultRevoked { revokedFound = true revokedCertSubject = problematicCertSubject @@ -769,18 +785,7 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca if numOKResults == len(certResults) { finalResult = revocationresult.ResultOK } - - switch finalResult { - case revocationresult.ResultOK: - logger.Debug("no verification impacting errors encountered while checking revocation, status is OK") - case revocationresult.ResultRevoked: - result.Error = fmt.Errorf("signing certificate with subject %q is revoked", problematicCertSubject) - default: - // revocationresult.ResultUnknown - result.Error = fmt.Errorf("signing certificate with subject %q revocation status is unknown", problematicCertSubject) - } - - return result + return finalResult, problematicCertSubject } func executePlugin(ctx context.Context, installedPlugin pluginframework.VerifyPlugin, capabilitiesToVerify []pluginframework.Capability, envelopeContent *signature.EnvelopeContent, trustedIdentities []string, pluginConfig map[string]string) (*pluginframework.VerifySignatureResponse, error) { @@ -898,3 +903,137 @@ func logVerificationResult(logger log.Logger, result *notation.ValidationResult) func isRequiredVerificationPluginVer(pluginVer string, minPluginVer string) bool { return semver.Compare("v"+pluginVer, "v"+minPluginVer) != -1 } + +// verifyTimestamp provides core verification logic of authentic timestamp under +// signing scheme `notary.x509`. +func verifyTimestamp(ctx context.Context, policyName string, trustStores []string, signatureVerification trustpolicy.SignatureVerification, x509TrustStore truststore.X509TrustStore, r revocation.Revocation, outcome *notation.VerificationOutcome) error { + logger := log.GetLogger(ctx) + + signerInfo := outcome.EnvelopeContent.SignerInfo + performTimestampVerification := true + + // check if tsa trust store is configured in trust policy + tsaEnabled, err := isTSATrustStoreInPolicy(policyName, trustStores) + if err != nil { + return fmt.Errorf("failed to check tsa trust store configuration in turst policy with error: %w", err) + } + if !tsaEnabled { + logger.Info("Timestamp verification disabled: no tsa trust store is configured in trust policy") + performTimestampVerification = false + } + + // check based on 'verifyTimestamp' field + timeOfVerification := time.Now() + if performTimestampVerification && + signatureVerification.VerifyTimestamp == trustpolicy.OptionAfterCertExpiry { + // check if signing cert chain has expired + var expired bool + for _, cert := range signerInfo.CertificateChain { + if timeOfVerification.After(cert.NotAfter) { + expired = true + break + } + } + if !expired { + logger.Infof("Timestamp verification disabled: verifyTimestamp is set to %q and signing cert chain unexpired", trustpolicy.OptionAfterCertExpiry) + performTimestampVerification = false + } + } + + // timestamp verification disabled, signing cert chain MUST be valid + // at time of verification + if !performTimestampVerification { + for _, cert := range signerInfo.CertificateChain { + if timeOfVerification.Before(cert.NotBefore) { + return fmt.Errorf("verification time is before certificate %q validity period, it will be valid from %q", cert.Subject, cert.NotBefore.Format(time.RFC1123Z)) + } + if timeOfVerification.After(cert.NotAfter) { + return fmt.Errorf("verification time is after certificate %q validity period, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z)) + } + } + + // success + return nil + } + + // Performing timestamp verification + logger.Info("Performing timestamp verification...") + + // 1. Timestamp countersignature MUST be present + logger.Debug("Checking timestamp countersignature existence...") + if len(signerInfo.UnsignedAttributes.TimestampSignature) == 0 { + return errors.New("no timestamp countersignature was found in the signature envelope") + } + + // 2. Verify the timestamp countersignature + logger.Debug("Verifying the timestamp countersignature...") + signedToken, err := tspclient.ParseSignedToken(signerInfo.UnsignedAttributes.TimestampSignature) + if err != nil { + return fmt.Errorf("failed to parse timestamp countersignature with error: %w", err) + } + info, err := signedToken.Info() + if err != nil { + return fmt.Errorf("failed to get the timestamp TSTInfo with error: %w", err) + } + timestamp, err := info.Validate(signerInfo.Signature) + if err != nil { + return fmt.Errorf("failed to get timestamp from timestamp countersignature with error: %w", err) + } + trustTSACerts, err := loadX509TSATrustStores(ctx, outcome.EnvelopeContent.SignerInfo.SignedAttributes.SigningScheme, policyName, trustStores, x509TrustStore) + if err != nil { + return fmt.Errorf("failed to load tsa trust store with error: %w", err) + } + if len(trustTSACerts) == 0 { + return errors.New("no trusted TSA certificate found in trust store") + } + rootCertPool := x509.NewCertPool() + for _, trustedCerts := range trustTSACerts { + rootCertPool.AddCert(trustedCerts) + } + tsaCertChain, err := signedToken.Verify(ctx, x509.VerifyOptions{ + CurrentTime: timestamp.Value, + Roots: rootCertPool, + }) + if err != nil { + return fmt.Errorf("failed to verify the timestamp countersignature with error: %w", err) + } + + // 3. Validate timestamping certificate chain + logger.Debug("Validating timestamping certificate chain...") + if err := nx509.ValidateTimestampingCertChain(tsaCertChain); err != nil { + return fmt.Errorf("failed to validate the timestamping certificate chain with error: %w", err) + } + logger.Info("TSA identity is: ", tsaCertChain[0].Subject) + + // 4. Check the timestamp against the signing certificate chain + logger.Debug("Checking the timestamp against the signing certificate chain...") + logger.Debugf("Timestamp range: [%v, %v]", timestamp.Value.Add(-timestamp.Accuracy), timestamp.Value.Add(timestamp.Accuracy)) + for _, cert := range signerInfo.CertificateChain { + if !timestamp.BoundedAfter(cert.NotBefore) { + return fmt.Errorf("timestamp can be before certificate %q validity period, it will be valid from %q", cert.Subject, cert.NotBefore.Format(time.RFC1123Z)) + } + if !timestamp.BoundedBefore(cert.NotAfter) { + return fmt.Errorf("timestamp can be after certificate %q validity period, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z)) + } + } + + // 5. Perform the timestamping certificate chain revocation check + logger.Debug("Checking timestamping certificate chain revocation...") + certResults, err := r.Validate(tsaCertChain, time.Time{}) + if err != nil { + return fmt.Errorf("failed to check timestamping certificate chain revocation with error: %w", err) + } + finalResult, problematicCertSubject := revocationFinalResult(certResults, tsaCertChain, logger) + switch finalResult { + case revocationresult.ResultOK: + logger.Debug("No verification impacting errors encountered while checking timestamping certificate chain revocation, status is OK") + case revocationresult.ResultRevoked: + return fmt.Errorf("timestamping certificate with subject %q is revoked", problematicCertSubject) + default: + // revocationresult.ResultUnknown + return fmt.Errorf("timestamping certificate with subject %q revocation status is unknown", problematicCertSubject) + } + + // success + return nil +} diff --git a/verifier/verifier_test.go b/verifier/verifier_test.go index 571c7499..c8104012 100644 --- a/verifier/verifier_test.go +++ b/verifier/verifier_test.go @@ -32,7 +32,6 @@ import ( "github.com/notaryproject/notation-core-go/signature" _ "github.com/notaryproject/notation-core-go/signature/cose" "github.com/notaryproject/notation-core-go/signature/jws" - _ "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-core-go/testhelper" corex509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" @@ -741,7 +740,14 @@ func TestNewVerifierWithOptionsError(t *testing.T) { if err != nil { t.Fatalf("unexpected error while creating revocation object: %v", err) } - opts := VerifierOptions{RevocationClient: r} + rt, err := revocation.NewTimestamp(&http.Client{}) + if err != nil { + t.Fatalf("unexpected error while creating revocation timestamp object: %v", err) + } + opts := VerifierOptions{ + RevocationClient: r, + RevocationTimestampClient: rt, + } _, err = NewVerifierWithOptions(nil, nil, store, pm, opts) if err == nil || err.Error() != "ociTrustPolicy and blobTrustPolicy both cannot be nil" { @@ -1331,9 +1337,7 @@ func verifyResult(outcome *notation.VerificationOutcome, expectedResult notation } // testTrustStore implements truststore.X509TrustStore and returns the trusted certificates for a given trust-store. -type testTrustStore struct { - certs []*x509.Certificate -} +type testTrustStore struct{} func (ts *testTrustStore) GetCertificates(_ context.Context, _ truststore.Type, _ string) ([]*x509.Certificate, error) { block, _ := pem.Decode([]byte(trustedCert))