Compare commits

...

37 Commits
main ... v1.3.1

Author SHA1 Message Date
Patrick Zheng 961cef1f72
bump: release v1.3.1
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-02-20 13:39:26 +08:00
Patrick Zheng f171fb041e
bump: bump up dependencies for release-1.3 branch (#516)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-02-20 13:26:15 +08:00
Patrick Zheng 540cc34a01
fix: remove signing time check during authenticTimestamp (#514)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-02-20 11:26:16 +08:00
Patrick Zheng 186457697f
Merge pull request #507 from Two-Hearts/release-1.3
bump: release v1.3.0
2025-01-16 08:43:53 +08:00
Patrick Zheng 31ee0d664a
bump: release v1.3.0
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 15:36:00 +08:00
Patrick Zheng 6571758d1f
bump: bump up dependencies for release-1.3 branch (#506)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 15:29:00 +08:00
Patrick Zheng 6d122d56df
Merge pull request #505 from Two-Hearts/release-1.3
cherry-pick: cherry pick from main to release-1.3 branch
2025-01-15 14:47:47 +08:00
dependabot[bot] 84d0d150fa
build(deps): bump golang.org/x/crypto from 0.31.0 to 0.32.0 (#503)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 14:24:15 +08:00
dependabot[bot] e71dbfd465
build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.8 to 3.4.10 (#501)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 14:23:50 +08:00
Junjie Gao 4bde0fa9de
fix: `check-line-endings` command of Makefile (#499)
Fix:
- update the command to search script file in `.` instead of `scripts/`
folder to avoid returning an error.

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
Co-authored-by: Patrick Zheng <patrickzheng@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 14:23:50 +08:00
Patrick Zheng 009d035e1e
chore(verifier): improve log (#497)
This PR improves log readability of the library.

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2025-01-15 14:23:47 +08:00
Patrick Zheng 97ac273257
Merge pull request #495 from Two-Hearts/release-1.3
bump: release v1.3.0-rc.2
2024-12-13 15:14:32 +08:00
Patrick Zheng c3c9bcdcf3
bump: release v1.3.0-rc.2
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-13 13:08:08 +08:00
Patrick Zheng 24baed88e2
Merge pull request #494 from Two-Hearts/release-1.3
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-13 11:31:43 +08:00
Junjie Gao 27651322d8
fix: limit the plugin output size (#484)
Fix:
- set the plugin output limit for STDOUT and STDERR to be 10MiB

Test:
- when the plugin output size exceeds 64MiB, the output pipe breaks, and
the plugin process outputs an error to STDERR

Spec changes: https://github.com/notaryproject/specifications/pull/320
Resolves #187

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-13 08:28:45 +08:00
Patrick Zheng f952b58755
bump: bump up crypto for release-1.3 branch (#493)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-13 08:15:15 +08:00
Patrick Zheng 8aaebdd2c0
bump: bump up notation-core-go for `release-1.3` branch (#490)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-11 08:17:08 +08:00
Patrick Zheng 2d9fa222ca
chore: backport #469 for updating logs (#489)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-10 08:38:04 +08:00
Patrick Zheng 41d038038b
Merge pull request #487 from Two-Hearts/release-1.3
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-10 08:13:46 +08:00
Junjie Gao 5b21f2f461
perf(log): encode objects only when logged (#481)
Fix:
- replaced `.String()` with the `%v` format to avoid rendering the
string before actually logging it.

Resolves #480

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:17:01 +08:00
Patrick Zheng e7005a6d13
fix: enable timestamping cert chain revocation check during signing (#482)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:17:01 +08:00
Junjie Gao 0a9ff2a2e2
fix: add warning message for non-revokable certificate (#479)
Fix:
- added warning message for non-revokable certificate

---------

Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:17:00 +08:00
Patrick Zheng ffc04d8aa8
fix: timestamping (#478)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
Co-authored-by: Pritesh Bandi <priteshbandi@gmail.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:59 +08:00
Pritesh Bandi 2307de5f29
chore: Improve error message in case of plugin timeout (#472)
When a plugin exceeds the specified timeout or deadline for content
processing, the current error message displayed is ```signal: killed```.
This PR updates the error message to a more informative message:
```[plugin_name] [command_name] command execution timeout: signal:
killed```

---------

Signed-off-by: Pritesh Bandi <priteshbandi@gmail.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:59 +08:00
dependabot[bot] 5384dde419
build(deps): bump golang.org/x/mod from 0.21.0 to 0.22.0 (#477)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:58 +08:00
Patrick Zheng e277e402ba
fix: crl cache log and err msg (#475)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:58 +08:00
dependabot[bot] 5395b659c4
build(deps): bump golang.org/x/crypto from 0.28.0 to 0.29.0 (#476)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:57 +08:00
dependabot[bot] c32fb1aa7a
build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#474)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:57 +08:00
dependabot[bot] 0c84a376a5
build(deps): bump golang.org/x/crypto from 0.27.0 to 0.28.0 (#468)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:56 +08:00
Patrick Zheng 0b9f71fba4
chore: add crl cache debug logs (#473)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:56 +08:00
Patrick Zheng d66c7ab857
fix: added tsa trust store root cert validation (#471)
This PR adds tsa trust store root cert validation while getting
certificates from trust store. This is to fail fast if cert in TSA trust
store is not a root CA certificate.

Resolves #470

---------

Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:55 +08:00
Junjie Gao 3c3302258a
fix: OS error when setting CRL cache leads to denial of signature verification
Signed-off-by: Junjie Gao <junjiegao@microsoft.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 14:16:45 +08:00
dependabot[bot] 29912acbfe
build(deps): bump github.com/veraison/go-cose from 1.1.0 to 1.3.0 (#467)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 10:35:01 +08:00
AdamKorcz b95c1d54d5
test: add fuzz test (#459)
Adds a fuzz test from cncf-fuzzing:
https://github.com/cncf/cncf-fuzzing/blob/main/projects/notary/fuzz_pkix_test.go

Signed-off-by: Adam Korczynski <adam@adalogics.com>
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-12-09 10:34:57 +08:00
Shiwei Zhang fb1f0c819d
Merge pull request #465 from Two-Hearts/release-1.3
Signed-off-by: Shiwei Zhang <shizh@microsoft.com>
2024-09-29 19:45:58 -07:00
Patrick Zheng 26f491ea7d
bump: release v1.3.0-rc.1
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-09-27 09:25:12 +08:00
Patrick Zheng 3c49488081
refactor!: remove blob sign/verify for v1.3.0-rc.1 release (#464)
Signed-off-by: Patrick Zheng <patrickzheng@microsoft.com>
2024-09-27 08:25:24 +08:00
38 changed files with 729 additions and 1808 deletions

View File

@ -16,3 +16,6 @@ coverage:
project: project:
default: default:
target: 80% target: 80%
patch:
default:
target: 80%

View File

@ -29,7 +29,7 @@ clean:
.PHONY: check-line-endings .PHONY: check-line-endings
check-line-endings: ## check line endings check-line-endings: ## check line endings
! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF ! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF
! find scripts -name "*.sh" -type f -exec file "{}" ";" | grep CRLF ! find . -name "*.sh" -type f -exec file "{}" ";" | grep CRLF
.PHONY: fix-line-endings .PHONY: fix-line-endings
fix-line-endings: ## fix line endings fix-line-endings: ## fix line endings

View File

@ -1,36 +0,0 @@
# Release Checklist
## Overview
This document describes the checklist to publish a release for notation-go.
## Release Process from main
1. Check if there are any security vulnerabilities fixed and security advisories published before a release. Security advisories should be linked on the release notes.
2. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. For example, `version="v1.0.0-rc.1"`.
3. If there is new release in [notation-core-go](https://github.com/notaryproject/notation-core-go) library that are required to be upgraded in notation-go, update the dependency versions in the follow `go.mod` and `go.sum` files of notation-go:
- [go.mod](go.mod), [go.sum](go.sum)
4. Open a bump up PR and submit the changes in step 3 to the notation-go repository.
5. After PR from step 4 is merged. Create another PR to update the value of `signingAgent` defined in file [signer/signer.go](signer/signer.go) with `notation-go/<version>`, where `<version>` is `$version` from step 2 without the `v` prefix. For example, `notation-go/1.0.0-rc.1`. The commit message MUST follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) and could be `bump: release $version`. Record the digest of that commit as `<commit_digest>`. This PR is also used for voting purpose of the new release. Add the link of change logs and repo-level maintainer list in the PR's description. The PR title could be `bump: release $version`. Make sure to reach a majority of approvals from the [repo-level maintainers](MAINTAINERS) before merging it. This PR MUST be merged using [Create a merge commit](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github) method in GitHub.
6. After the voting PR is merged, execute `git clone https://github.com/notaryproject/notation-go.git` to clone the repository to your local file system.
7. Enter the cloned repository and execute `git checkout <commit_digest>` to switch to the specified branch based on the voting result.
8. Create a tag by running `git tag -am $version $version -s`.
9. Run `git tag` and ensure the desired tag name in the list looks correct, then push the new tag directly to the repository by running `git push origin $version`.
10. On notation-go GitHub page, goto [Tags](https://github.com/notaryproject/notation-go/tags). Your newly pushed tag should be shown on the top. Create a new release from the tag. Generate the release notes, revise the release description and change logs, and publish the release.
11. Announce the new release in the Notary Project community.
## Release Process from a release branch
1. Check if there are any security vulnerabilities fixed and security advisories published before a release. Security advisories should be linked on the release notes.
2. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. For example, `version="v1.2.0-rc.1"`.
3. If a new release branch is needed, from main branch's [commit list](https://github.com/notaryproject/notation-go/commits/main/), find the commit that you want to cut the release. Click `<>` (Browse repository at this point). Create branch with name `release-<version>` from the commit, where `<version>` is `$version` from step 2 with the major and minor versions only. For example `release-1.2`. If the release branch already exists, skip this step.
4. If there is new release in [notation-core-go](https://github.com/notaryproject/notation-core-go) library that are required to be upgraded in notation-go, update the dependency versions in the follow `go.mod` and `go.sum` files of notation-go:
- [go.mod](go.mod), [go.sum](go.sum)
5. Open a bump up PR and submit the changes in step 4 to the release branch.
6. After PR from step 5 is merged. Create another PR to update the value of `signingAgent` defined in file `signer/signer.go` with `notation-go/<version>`, where `<version>` is `$version` from step 2 without the `v` prefix. For example, `notation-go/1.2.0-rc.1`. The commit message MUST follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) and could be `bump: release $version`. Record the digest of that commit as `<commit_digest>`. This PR is also used for voting purpose of the new release. Add the link of change logs and repo-level maintainer list in the PR's description. The PR title could be `bump: release $version`. Make sure to reach a majority of approvals from the [repo-level maintainers](MAINTAINERS) before merging it. This PR MUST be merged using [Create a merge commit](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github) method in GitHub.
7. After the voting PR is merged, execute `git clone https://github.com/notaryproject/notation-go.git` to clone the repository to your local file system.
8. Enter the cloned repository and execute `git checkout <commit_digest>` to switch to the specified branch based on the voting result.
9. Create a tag by running `git tag -am $version $version -s`.
10. Run `git tag` and ensure the desired tag name in the list looks correct, then push the new tag directly to the repository by running `git push origin $version`.
11. On notation-go GitHub page, goto [Tags](https://github.com/notaryproject/notation-go/tags). Your newly pushed tag should be shown on the top. Create a new release from the tag. Generate the release notes, revise the release description and change logs, and publish the release.
12. Announce the new release in the Notary Project community.

View File

@ -58,14 +58,8 @@ const (
PathConfigFile = "config.json" PathConfigFile = "config.json"
// PathSigningKeys is the signingkeys file relative path. // PathSigningKeys is the signingkeys file relative path.
PathSigningKeys = "signingkeys.json" PathSigningKeys = "signingkeys.json"
// PathTrustPolicy is the OCI trust policy file relative path. // PathTrustPolicy is the trust policy file relative path.
// Deprecated: PathTrustPolicy exists for historical compatibility and should not be used.
// To get OCI trust policy path, use PathOCITrustPolicy.
PathTrustPolicy = "trustpolicy.json" PathTrustPolicy = "trustpolicy.json"
// PathOCITrustPolicy is the OCI trust policy file relative path.
PathOCITrustPolicy = "trustpolicy.oci.json"
// PathBlobTrustPolicy is the Blob trust policy file relative path.
PathBlobTrustPolicy = "trustpolicy.blob.json"
// LocalKeysDir is the directory name for local key relative path. // LocalKeysDir is the directory name for local key relative path.
LocalKeysDir = "localkeys" LocalKeysDir = "localkeys"
// LocalCertificateExtension defines the extension of the certificate files. // LocalCertificateExtension defines the extension of the certificate files.

View File

@ -1,85 +0,0 @@
// 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"
"strings"
"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/signature/jws"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/signer"
)
// ExampleSignBlob demonstrates how to use signer.BlobSign to sign arbitrary data.
func Example_signBlob() {
//exampleSigner implements notation.Signer and notation.BlobSigner. Given key and X509 certificate chain,
// it provides method to sign OCI artifacts or blobs.
// Users should replace `exampleCertTuple.PrivateKey` with their own private
// key and replace `exampleCerts` with the corresponding 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
}
// Both COSE ("application/cose") and JWS ("application/jose+json")
// signature mediaTypes are supported.
exampleSignatureMediaType := jws.MediaTypeEnvelope
exampleContentMediaType := "video/mp4"
// exampleSignOptions is an example of notation.SignBlobOptions.
exampleSignOptions := notation.SignBlobOptions{
SignerSignOptions: notation.SignerSignOptions{
SignatureMediaType: exampleSignatureMediaType,
SigningAgent: "example signing agent",
},
ContentMediaType: exampleContentMediaType,
UserMetadata: map[string]string{"buildId": "101"},
}
// exampleReader reads the data that needs to be signed. This data can be in a file or in memory.
exampleReader := strings.NewReader("example blob")
// Upon successful signing, signature envelope and signerInfo are returned.
// signatureEnvelope can be used in a verification process later on.
signatureEnvelope, signerInfo, err := notation.SignBlob(context.Background(), exampleSigner, exampleReader, exampleSignOptions)
if err != nil {
panic(err) // Handle error
}
fmt.Println("Successfully signed")
// a peek of the signature envelope generated
sigBlob, err := signature.ParseEnvelope(exampleSignatureMediaType, signatureEnvelope)
if err != nil {
panic(err) // Handle error
}
sigContent, err := sigBlob.Content()
if err != nil {
panic(err) // Handle error
}
fmt.Println("signature Payload ContentType:", sigContent.Payload.ContentType)
fmt.Println("signature Payload Content:", string(sigContent.Payload.Content))
fmt.Println("signerInfo SigningAgent:", signerInfo.UnsignedAttributes.SigningAgent)
// Output:
// Successfully signed
// signature Payload ContentType: application/vnd.cncf.notary.payload.v1+json
// signature Payload Content: {"targetArtifact":{"annotations":{"buildId":"101"},"digest":"sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a61","mediaType":"video/mp4","size":12}}
// signerInfo SigningAgent: example signing agent
}

View File

@ -21,6 +21,8 @@ import (
"oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote"
"github.com/notaryproject/notation-core-go/revocation"
"github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/testhelper" "github.com/notaryproject/notation-core-go/testhelper"
"github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/registry" "github.com/notaryproject/notation-go/registry"
@ -77,12 +79,21 @@ func Example_signWithTimestamp() {
tsaRootCAs := x509.NewCertPool() tsaRootCAs := x509.NewCertPool()
tsaRootCAs.AddCert(tsaRootCert) tsaRootCAs.AddCert(tsaRootCert)
// enable timestamping certificate chain revocation check
tsaRevocationValidator, err := revocation.NewWithOptions(revocation.Options{
CertChainPurpose: purpose.Timestamping,
})
if err != nil {
panic(err) // Handle error
}
// exampleSignOptions is an example of notation.SignOptions. // exampleSignOptions is an example of notation.SignOptions.
exampleSignOptions := notation.SignOptions{ exampleSignOptions := notation.SignOptions{
SignerSignOptions: notation.SignerSignOptions{ SignerSignOptions: notation.SignerSignOptions{
SignatureMediaType: exampleSignatureMediaType, SignatureMediaType: exampleSignatureMediaType,
Timestamper: httpTimestamper, Timestamper: httpTimestamper,
TSARootCAs: tsaRootCAs, TSARootCAs: tsaRootCAs,
TSARevocationValidator: tsaRevocationValidator,
}, },
ArtifactReference: exampleArtifactReference, ArtifactReference: exampleArtifactReference,
} }

View File

@ -1,151 +0,0 @@
// 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"
"strings"
"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"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/notaryproject/notation-go/verifier/truststore"
)
// examplePolicyDocument is an example of a valid trust policy document.
// trust policy document should follow this spec:
// https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#trust-policy
var exampleBlobPolicyDocument = trustpolicy.BlobDocument{
Version: "1.0",
TrustPolicies: []trustpolicy.BlobTrustPolicy{
{
Name: "test-statement-name",
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: trustpolicy.LevelStrict.Name, Override: map[trustpolicy.ValidationType]trustpolicy.ValidationAction{trustpolicy.TypeRevocation: trustpolicy.ActionSkip}},
TrustStores: []string{"ca:valid-trust-store"},
TrustedIdentities: []string{"*"},
},
},
}
// ExampleVerifyBlob demonstrates how to use verifier.Verify to verify a
// signature of the blob.
func Example_verifyBlob() {
// Both COSE ("application/cose") and JWS ("application/jose+json")
// signature mediaTypes are supported.
exampleSignatureMediaType := jws.MediaTypeEnvelope
// exampleSignatureEnvelope is a valid signature envelope.
exampleSignatureEnvelope := getSignatureEnvelope()
// createTrustStoreForBlobVerify 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/specs/trust-store-trust-policy.md#trust-store
if err := createTrustStoreForBlobVerify(); err != nil {
panic(err) // Handle error
}
// exampleVerifier implements notation.Verify and notation.VerifyBlob.
exampleVerifier, err := verifier.NewVerifier(nil, &exampleBlobPolicyDocument, truststore.NewX509TrustStore(dir.ConfigFS()), nil)
if err != nil {
panic(err) // Handle error
}
// exampleReader reads the data that needs to be verified. This data can be in a file or in memory.
exampleReader := strings.NewReader("example blob")
// exampleVerifyOptions is an example of notation.VerifierVerifyOptions
exampleVerifyOptions := notation.VerifyBlobOptions{
BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{
SignatureMediaType: exampleSignatureMediaType,
TrustPolicyName: "test-statement-name",
},
}
// upon successful verification, the signature verification outcome is
// returned.
_, outcome, err := notation.VerifyBlob(context.Background(), exampleVerifier, exampleReader, []byte(exampleSignatureEnvelope), exampleVerifyOptions)
if err != nil {
panic(err) // Handle error
}
fmt.Println("Successfully verified")
// a peek of the payload inside the signature envelope
fmt.Println("payload ContentType:", outcome.EnvelopeContent.Payload.ContentType)
// Note, upon successful verification, payload.TargetArtifact from the
// signature envelope matches exactly with our exampleTargetDescriptor.
// (This check has been done for the user inside verifier.Verify.)
fmt.Println("payload Content:", string(outcome.EnvelopeContent.Payload.Content))
// Output:
// Successfully verified
// payload ContentType: application/vnd.cncf.notary.payload.v1+json
// payload Content: {"targetArtifact":{"digest":"sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a61","mediaType":"video/mp4","size":12}}
}
func createTrustStoreForBlobVerify() 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.
// (This self-signed cert is paired with the private key used to
// generate the `exampleSignatureEnvelopePem` above.)
// 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-----
MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzEL
MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEl
MCMGA1UEAxMcTm90YXRpb24gRXhhbXBsZSBzZWxmLXNpZ25lZDAgFw0yNDA0MDQy
MTIwMjBaGA8yMTI0MDQwNDIxMjAyMFowZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgT
AldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxJTAjBgNVBAMT
HE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwggGiMA0GCSqGSIb3DQEBAQUA
A4IBjwAwggGKAoIBgQDGIiN4yCjSVqFELZwxK/BMb8BokP587L8oPrZ1g8H7LudB
moLNDT7vF9xccbCfU3yNuOd0WaOgnENiCs81VHidyJsj1Oz3u+0Zn3ng7V+uZr6m
AIO74efA9ClMiY4i4HIt8IAZF57AL2mzDnCITgSWxikf030Il85MI42STvA+qYuz
ZEOp3XvKo8bDgQFvbtgK0HYYMfrka7VDmIWVo0rBMGm5btI8HOYQ0r9aqsrCxLAv
1AQeOQm+wbRcp4R5PIUJr+REGn7JCbOyXg/7qqHXKKmvV5yrGaraw8gZ5pqP/RHK
XUJIfvD0Vf2epJmsvC+6vXkSWtz+cA8J4GQx4J4SXL57hoYkC5qv39SOLzlWls3I
6fgeO+SZ0sceMd8NKlom/L5eOJBfB3bTQB83hq/3bRtjT7/qCMsL3VcndKkS+vGF
JPw5uTH+pmBgHrLr6tRoRRjwRFuZ0dO05AbdjCaxgVDtFI3wNbaXn/1VlRGySQIS
UNWxCrUsSzndeqwmjqsCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
MAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBgQBdi0SaJAaeKBB0I+Fjcbmc
4zRvHE4GDSMSDnAK97nrZCZ9iwKuY4x6mv9lwQe2P3VXROoL9JmONNf0yaObOwQj
ILGnbe2rzYtUardz2gzh+6KNzJHspRvk1f06mp4496XQ3STMRSr8kno1svKQMy0Y
FRsGMKs4fWHavIAqNXg9ymrZvvXiatN2UiVtAA/jBFScZAWskeb2WHNzORi7H5Z1
mp5+IlNYQpzdIu/dvLVxzhh2UvkRdsQqsMgt/MOU84RncwUNZM4yI5EGPoaSJdsj
AGNd+UV6ur7QmVI2Q9EZNRlaDJtaoZmKns5j1SlmDXWKbdRmw42ORDudODj/pHA9
+u+ca9t3uLsbqO9yPm8m+6fyxffWS11QAH6O7EjydJWcEe5tYkPpL6kcaEyQKESm
5CDlsk+W3ElpaUu6tsnGKODvgdAN3m0noC+qxzCMqoCM4+M5V6OptR98MDl2FK0B
5+WF6YHBxf/uqDvFktUczjrIWuyfECywp05bpGAErGE=
-----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
}
return os.WriteFile("tmp/truststore/x509/ca/valid-trust-store/NotationBlobExample.pem", []byte(exampleX509Certificate), 0600)
}
func getSignatureEnvelope() string {
return `{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJkaWdlc3QiOiJzaGEzODQ6YjhhYjI0ZGFmYmE1Y2Y3ZTRjODljNTYyZjgxMWNmMTA0OTNkNDIwM2RhOTgyZDNiMTM0NWYzNjZjYTg2M2Q5YzJlZDMyM2RiZDBmYjdmZjgzYTgwMzAyY2VmZmE1YTYxIiwibWVkaWFUeXBlIjoidmlkZW8vbXA0Iiwic2l6ZSI6MTJ9fQ","protected":"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA0LTA0VDE0OjIwOjIxLTA3OjAwIn0","header":{"x5c":["MIIEbDCCAtSgAwIBAgIBUzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTElMCMGA1UEAxMcTm90YXRpb24gRXhhbXBsZSBzZWxmLXNpZ25lZDAgFw0yNDA0MDQyMTIwMjBaGA8yMTI0MDQwNDIxMjAyMFowZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxJTAjBgNVBAMTHE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDGIiN4yCjSVqFELZwxK/BMb8BokP587L8oPrZ1g8H7LudBmoLNDT7vF9xccbCfU3yNuOd0WaOgnENiCs81VHidyJsj1Oz3u+0Zn3ng7V+uZr6mAIO74efA9ClMiY4i4HIt8IAZF57AL2mzDnCITgSWxikf030Il85MI42STvA+qYuzZEOp3XvKo8bDgQFvbtgK0HYYMfrka7VDmIWVo0rBMGm5btI8HOYQ0r9aqsrCxLAv1AQeOQm+wbRcp4R5PIUJr+REGn7JCbOyXg/7qqHXKKmvV5yrGaraw8gZ5pqP/RHKXUJIfvD0Vf2epJmsvC+6vXkSWtz+cA8J4GQx4J4SXL57hoYkC5qv39SOLzlWls3I6fgeO+SZ0sceMd8NKlom/L5eOJBfB3bTQB83hq/3bRtjT7/qCMsL3VcndKkS+vGFJPw5uTH+pmBgHrLr6tRoRRjwRFuZ0dO05AbdjCaxgVDtFI3wNbaXn/1VlRGySQISUNWxCrUsSzndeqwmjqsCAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBgQBdi0SaJAaeKBB0I+Fjcbmc4zRvHE4GDSMSDnAK97nrZCZ9iwKuY4x6mv9lwQe2P3VXROoL9JmONNf0yaObOwQjILGnbe2rzYtUardz2gzh+6KNzJHspRvk1f06mp4496XQ3STMRSr8kno1svKQMy0YFRsGMKs4fWHavIAqNXg9ymrZvvXiatN2UiVtAA/jBFScZAWskeb2WHNzORi7H5Z1mp5+IlNYQpzdIu/dvLVxzhh2UvkRdsQqsMgt/MOU84RncwUNZM4yI5EGPoaSJdsjAGNd+UV6ur7QmVI2Q9EZNRlaDJtaoZmKns5j1SlmDXWKbdRmw42ORDudODj/pHA9+u+ca9t3uLsbqO9yPm8m+6fyxffWS11QAH6O7EjydJWcEe5tYkPpL6kcaEyQKESm5CDlsk+W3ElpaUu6tsnGKODvgdAN3m0noC+qxzCMqoCM4+M5V6OptR98MDl2FK0B5+WF6YHBxf/uqDvFktUczjrIWuyfECywp05bpGAErGE="],"io.cncf.notary.signingAgent":"example signing agent"},"signature":"liOjdgQ9BKuQTZGXRh3o6P8AMUIq_MKQReEcqA5h8M4RYs3DV_wXfaLCr2x_NRcwjTZsoO1_J77hmzkkk4L0IuFP8Qw0KKtmc83G0yFi4yYV5fwzrIbnhC2GRLuqLPnK-C4qYmv52ld3ebvo7XWwRHu30-VXePmTRFp6iG-eSAgkNgwhxSZ0ZmTFLG3ceNiX2bxpLHlXdPwA3aFKbd6nKrzo4CZ1ZyLNmAIaoA5-kmc0Hyt45trpxaaiWusI_pcTLw71YCqEAs32tEq3q6hRAgAZZN-Qvm9GyNp9EuaPiKjMbJFqtjome5ITxyNd-5t09dDCUgSe3t-iqv2Blm4E080AP1TYwUKLYklGniUP1dAtOau5G2juZLpl7tr4LQ99mycflnAmV7e79eEWXffvy5EAl77dW4_vM7lEemm08m2wddGuDOWXYb1j1r2_a5Xb92umHq6ZMhAp200A0pUkm9640x8z5jdudi_7KeezdqUK7ZMmSxHohiylyKD_20Cy"}`
}

18
go.mod
View File

@ -3,24 +3,24 @@ module github.com/notaryproject/notation-go
go 1.22.0 go 1.22.0
require ( require (
github.com/go-ldap/ldap/v3 v3.4.8 github.com/go-ldap/ldap/v3 v3.4.10
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737 github.com/notaryproject/notation-core-go v1.2.0
github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/notaryproject/notation-plugin-framework-go v1.0.0
github.com/notaryproject/tspclient-go v0.2.0 github.com/notaryproject/tspclient-go v1.0.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/image-spec v1.1.0
github.com/veraison/go-cose v1.1.0 github.com/veraison/go-cose v1.3.0
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.33.0
golang.org/x/mod v0.21.0 golang.org/x/mod v0.23.0
oras.land/oras-go/v2 v2.5.0 oras.land/oras-go/v2 v2.5.0
) )
require ( require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.10.0 // indirect
) )

65
go.sum
View File

@ -7,12 +7,13 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 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.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
@ -32,12 +33,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/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 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737 h1:Hp93KBCABE29+6zdS0GTg0T1SXj6qGatJyN1JMvTQqk= github.com/notaryproject/notation-core-go v1.2.0 h1:WElMG9X0YXJhBd0A4VOxLNalTLrTjvqtIAj7JHr5X08=
github.com/notaryproject/notation-core-go v1.1.1-0.20240920045731-0786f51de737/go.mod h1:b/70rA4OgOHlg0A7pb8zTWKJadFO6781zS3a37KHEJQ= github.com/notaryproject/notation-core-go v1.2.0/go.mod h1:+y3L1dOs2/ZwJIU5Imo7BBvZ/M3CFjXkydGGdK09EtA=
github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= 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/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics=
github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4=
github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/notaryproject/tspclient-go v1.0.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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 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= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@ -52,22 +53,27 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@ -75,14 +81,19 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -90,24 +101,34 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -119,8 +119,15 @@ func TrimFileExtension(fileName string) string {
// WriteFile writes content to a temporary file and moves it to path. // WriteFile writes content to a temporary file and moves it to path.
// If path already exists and is a file, WriteFile overwrites it. // If path already exists and is a file, WriteFile overwrites it.
func WriteFile(path string, content []byte) (writeErr error) { //
tempFile, err := os.CreateTemp("", tempFileNamePrefix) // Parameters:
// - tempDir is the directory to create the temporary file. It should be
// in the same mount point as path. If tempDir is empty, the default
// directory for temporary files is used.
// - path is the destination file path.
// - content is the content to write.
func WriteFile(tempDir, path string, content []byte) (writeErr error) {
tempFile, err := os.CreateTemp(tempDir, tempFileNamePrefix)
if err != nil { if err != nil {
return fmt.Errorf("failed to create temp file: %w", err) return fmt.Errorf("failed to create temp file: %w", err)
} }

View File

@ -30,7 +30,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -52,7 +52,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -87,7 +87,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// forbid reading // forbid reading
@ -113,7 +113,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// forbid dest directory operation // forbid dest directory operation
@ -139,7 +139,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// forbid writing to destTempDir // forbid writing to destTempDir
@ -159,7 +159,7 @@ func TestCopyToDir(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := WriteFile(filename, data); err != nil { if err := WriteFile(tempDir, filename, data); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -192,7 +192,7 @@ func TestWriteFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = WriteFile(filepath.Join(tempDir, "testFile"), content) err = WriteFile(tempDir, filepath.Join(tempDir, "testFile"), content)
if err == nil || !strings.Contains(err.Error(), "permission denied") { if err == nil || !strings.Contains(err.Error(), "permission denied") {
t.Fatalf("expected permission denied error, but got %s", err) t.Fatalf("expected permission denied error, but got %s", err)
} }

View File

@ -0,0 +1,53 @@
// 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 io provides a LimitWriter that writes to an underlying writer up to
// a limit.
package io
import (
"errors"
"io"
)
// ErrLimitExceeded is returned when the write limit is exceeded.
var ErrLimitExceeded = errors.New("write limit exceeded")
// LimitedWriter is a writer that writes to an underlying writer up to a limit.
type LimitedWriter struct {
W io.Writer // underlying writer
N int64 // remaining bytes
}
// LimitWriter returns a new LimitWriter that writes to w.
//
// parameters:
// w: the writer to write to
// limit: the maximum number of bytes to write
func LimitWriter(w io.Writer, limit int64) *LimitedWriter {
return &LimitedWriter{W: w, N: limit}
}
// Write writes p to the underlying writer up to the limit.
func (l *LimitedWriter) Write(p []byte) (int, error) {
if l.N <= 0 {
return 0, ErrLimitExceeded
}
if int64(len(p)) > l.N {
p = p[:l.N]
}
n, err := l.W.Write(p)
l.N -= int64(n)
return n, err
}

View File

@ -0,0 +1,67 @@
// Copyright The Notary Project Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package io
import (
"bytes"
"errors"
"testing"
)
func TestLimitWriter(t *testing.T) {
limit := int64(10)
tests := []struct {
input string
expected string
written int
}{
{"hello", "hello", 5},
{" world", " world", 6},
{"!", "!", 1},
{"1234567891011", "1234567891", 10},
}
for _, tt := range tests {
var buf bytes.Buffer
lw := LimitWriter(&buf, limit)
n, err := lw.Write([]byte(tt.input))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != tt.written {
t.Errorf("expected %d bytes written, got %d", tt.written, n)
}
if buf.String() != tt.expected {
t.Errorf("expected buffer %q, got %q", tt.expected, buf.String())
}
}
}
func TestLimitWriterFailed(t *testing.T) {
limit := int64(10)
longString := "1234567891011"
var buf bytes.Buffer
lw := LimitWriter(&buf, limit)
_, err := lw.Write([]byte(longString))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = lw.Write([]byte(longString))
expectedErr := errors.New("write limit exceeded")
if err.Error() != expectedErr.Error() {
t.Errorf("expected error %v, got %v", expectedErr, err)
}
}

View File

@ -0,0 +1,24 @@
// 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 pkix
import (
"testing"
)
func FuzzParseDistinguishedName(f *testing.F) {
f.Fuzz(func(t *testing.T, name string) {
_, _ = ParseDistinguishedName(name)
})
}

View File

@ -23,14 +23,13 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"mime"
"strings" "strings"
"time" "time"
orasRegistry "oras.land/oras-go/v2/registry" orasRegistry "oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote"
"github.com/notaryproject/notation-core-go/revocation"
"github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/signature/cose" "github.com/notaryproject/notation-core-go/signature/cose"
"github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-core-go/signature/jws"
@ -69,6 +68,11 @@ type SignerSignOptions struct {
// TSARootCAs is the cert pool holding caller's TSA trust anchor // TSARootCAs is the cert pool holding caller's TSA trust anchor
TSARootCAs *x509.CertPool TSARootCAs *x509.CertPool
// TSARevocationValidator is used for validating revocation status of
// timestamping certificate chain with context during signing.
// When present, only used when timestamping is performed.
TSARevocationValidator revocation.Validator
} }
// Signer is a generic interface for signing an OCI artifact. // Signer is a generic interface for signing an OCI artifact.
@ -80,35 +84,6 @@ type Signer interface {
Sign(ctx context.Context, desc ocispec.Descriptor, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error) Sign(ctx context.Context, desc ocispec.Descriptor, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
} }
// SignBlobOptions contains parameters for notation.SignBlob.
type SignBlobOptions struct {
SignerSignOptions
// ContentMediaType is the media-type of the blob being signed.
ContentMediaType string
// UserMetadata contains key-value pairs that are added to the signature
// payload
UserMetadata map[string]string
}
// 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
// }
type BlobDescriptorGenerator func(digest.Algorithm) (ocispec.Descriptor, error)
// BlobSigner is a generic interface for signing arbitrary data.
// The interface allows signing with local or remote keys,
// and packing in various signature formats.
type BlobSigner interface {
// SignBlob signs the descriptor returned by genDesc ,
// and returns the signature and SignerInfo
SignBlob(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
}
// signerAnnotation facilitates return of manifest annotations by signers // signerAnnotation facilitates return of manifest annotations by signers
type signerAnnotation interface { type signerAnnotation interface {
// PluginAnnotations returns signature manifest annotations returned from // PluginAnnotations returns signature manifest annotations returned from
@ -159,7 +134,7 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts
} }
// artifactRef is a tag // artifactRef is a tag
logger.Warnf("Always sign the artifact using digest(`@sha256:...`) rather than a tag(`:%s`) because tags are mutable and a tag reference can point to a different artifact than the one signed", artifactRef) logger.Warnf("Always sign the artifact using digest(`@sha256:...`) rather than a tag(`:%s`) because tags are mutable and a tag reference can point to a different artifact than the one signed", artifactRef)
logger.Infof("Resolved artifact tag `%s` to digest `%s` before signing", artifactRef, targetDesc.Digest.String()) logger.Infof("Resolved artifact tag `%s` to digest `%v` before signing", artifactRef, targetDesc.Digest)
} }
descToSign, err := addUserMetadataToDescriptor(ctx, targetDesc, signOpts.UserMetadata) descToSign, err := addUserMetadataToDescriptor(ctx, targetDesc, signOpts.UserMetadata)
if err != nil { if err != nil {
@ -194,29 +169,6 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts
return targetDesc, nil return targetDesc, nil
} }
// SignBlob signs the arbitrary data and returns the signature
func SignBlob(ctx context.Context, signer BlobSigner, blobReader io.Reader, signBlobOpts SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
// sanity checks
if err := validateSignArguments(signer, signBlobOpts.SignerSignOptions); err != nil {
return nil, nil, err
}
if blobReader == nil {
return nil, nil, errors.New("blobReader cannot be nil")
}
if signBlobOpts.ContentMediaType == "" {
return nil, nil, errors.New("content media-type cannot be empty")
}
if err := validateContentMediaType(signBlobOpts.ContentMediaType); err != nil {
return nil, nil, err
}
getDescFunc := getDescriptorFunc(ctx, blobReader, signBlobOpts.ContentMediaType, signBlobOpts.UserMetadata)
return signer.SignBlob(ctx, getDescFunc, signBlobOpts.SignerSignOptions)
}
func validateSignArguments(signer any, signOpts SignerSignOptions) error { func validateSignArguments(signer any, signOpts SignerSignOptions) error {
if signer == nil { if signer == nil {
return errors.New("signer cannot be nil") return errors.New("signer cannot be nil")
@ -348,33 +300,6 @@ type Verifier interface {
Verify(ctx context.Context, desc ocispec.Descriptor, signature []byte, opts VerifierVerifyOptions) (*VerificationOutcome, error) Verify(ctx context.Context, desc ocispec.Descriptor, signature []byte, opts VerifierVerifyOptions) (*VerificationOutcome, error)
} }
// BlobVerifierVerifyOptions contains parameters for BlobVerifier.Verify.
type BlobVerifierVerifyOptions struct {
// SignatureMediaType is the envelope type of the signature.
// Currently only `application/jose+json` and `application/cose` are
// supported.
SignatureMediaType string
// PluginConfig is a map of plugin configs.
PluginConfig map[string]string
// UserMetadata contains key-value pairs that must be present in the
// signature.
UserMetadata map[string]string
// TrustPolicyName is the name of trust policy picked by caller.
// If empty, the global trust policy will be applied.
TrustPolicyName string
}
// BlobVerifier is a generic interface for verifying a blob.
type BlobVerifier interface {
// VerifyBlob verifies the `signature` against the target artifact using the
// descriptor returned by descGenFunc parameter and
// returns the outcome upon successful verification.
VerifyBlob(ctx context.Context, descGenFunc BlobDescriptorGenerator, signature []byte, opts BlobVerifierVerifyOptions) (*VerificationOutcome, error)
}
type verifySkipper interface { type verifySkipper interface {
// SkipVerify validates whether the verification level is skip. // SkipVerify validates whether the verification level is skip.
SkipVerify(ctx context.Context, opts VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error) SkipVerify(ctx context.Context, opts VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error)
@ -399,55 +324,6 @@ type VerifyOptions struct {
UserMetadata map[string]string UserMetadata map[string]string
} }
// VerifyBlobOptions contains parameters for notation.VerifyBlob.
type VerifyBlobOptions struct {
BlobVerifierVerifyOptions
// ContentMediaType is the media-type type of the content being verified.
ContentMediaType string
}
// VerifyBlob performs signature verification for a blob using notation supported
// verification types (like integrity, authenticity, etc.) and return the
// successful signature verification outcome. The blob is read using blobReader and
// upon successful verification, it returns the descriptor of the blob.
// For more details on signature verification, see
// https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#signature-verification
func VerifyBlob(ctx context.Context, blobVerifier BlobVerifier, blobReader io.Reader, signature []byte, verifyBlobOpts VerifyBlobOptions) (ocispec.Descriptor, *VerificationOutcome, error) {
if blobVerifier == nil {
return ocispec.Descriptor{}, nil, errors.New("blobVerifier cannot be nil")
}
if blobReader == nil {
return ocispec.Descriptor{}, nil, errors.New("blobReader cannot be nil")
}
if len(signature) == 0 {
return ocispec.Descriptor{}, nil, errors.New("signature cannot be nil or empty")
}
if err := validateContentMediaType(verifyBlobOpts.ContentMediaType); err != nil {
return ocispec.Descriptor{}, nil, err
}
if err := validateSigMediaType(verifyBlobOpts.SignatureMediaType); err != nil {
return ocispec.Descriptor{}, nil, err
}
getDescFunc := getDescriptorFunc(ctx, blobReader, verifyBlobOpts.ContentMediaType, verifyBlobOpts.UserMetadata)
vo, err := blobVerifier.VerifyBlob(ctx, getDescFunc, signature, verifyBlobOpts.BlobVerifierVerifyOptions)
if err != nil {
return ocispec.Descriptor{}, nil, err
}
var desc ocispec.Descriptor
if err = json.Unmarshal(vo.EnvelopeContent.Payload.Content, &desc); err != nil {
return ocispec.Descriptor{}, nil, err
}
return desc, vo, nil
}
// Verify performs signature verification on each of the notation supported // Verify performs signature verification on each of the notation supported
// verification types (like integrity, authenticity, etc.) and return the // verification types (like integrity, authenticity, etc.) and return the
// successful signature verification outcome. // successful signature verification outcome.
@ -481,10 +357,10 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
return ocispec.Descriptor{}, nil, err return ocispec.Descriptor{}, nil, err
} }
if skip { if skip {
logger.Infoln("Verification skipped for", verifyOpts.ArtifactReference) logger.Infoln("Signature verification skipped for", verifyOpts.ArtifactReference)
return ocispec.Descriptor{}, []*VerificationOutcome{{VerificationLevel: verificationLevel}}, nil return ocispec.Descriptor{}, []*VerificationOutcome{{VerificationLevel: verificationLevel}}, nil
} }
logger.Info("Check over. Trust policy is not configured to skip signature verification") logger.Info("Check over. The signature verification level is not set to 'skip' in the trust policy.")
} }
// get artifact descriptor // get artifact descriptor
@ -502,7 +378,7 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
} }
if ref.ValidateReferenceAsDigest() != nil { if ref.ValidateReferenceAsDigest() != nil {
// artifactRef is not a digest reference // artifactRef is not a digest reference
logger.Infof("Resolved artifact tag `%s` to digest `%s` before verification", ref.Reference, artifactDescriptor.Digest.String()) logger.Infof("Resolved artifact tag `%s` to digest `%v` before verification", ref.Reference, artifactDescriptor.Digest)
logger.Warn("The resolved digest may not point to the same signed artifact, since tags are mutable") logger.Warn("The resolved digest may not point to the same signed artifact, since tags are mutable")
} else if ref.Reference != artifactDescriptor.Digest.String() { } else if ref.Reference != artifactDescriptor.Digest.String() {
return ocispec.Descriptor{}, nil, ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("user input digest %s does not match the resolved digest %s", ref.Reference, artifactDescriptor.Digest.String())} return ocispec.Descriptor{}, nil, ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("user input digest %s does not match the resolved digest %s", ref.Reference, artifactDescriptor.Digest.String())}
@ -611,31 +487,6 @@ func generateAnnotations(signerInfo *signature.SignerInfo, annotations map[strin
return annotations, nil return annotations, nil
} }
func getDescriptorFunc(ctx context.Context, reader io.Reader, contentMediaType string, userMetadata map[string]string) BlobDescriptorGenerator {
return func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
digester := hashAlgo.Digester()
bytes, err := io.Copy(digester.Hash(), reader)
if err != nil {
return ocispec.Descriptor{}, err
}
targetDesc := ocispec.Descriptor{
MediaType: contentMediaType,
Digest: digester.Digest(),
Size: bytes,
}
return addUserMetadataToDescriptor(ctx, targetDesc, userMetadata)
}
}
func validateContentMediaType(contentMediaType string) error {
if contentMediaType != "" {
if _, _, err := mime.ParseMediaType(contentMediaType); err != nil {
return fmt.Errorf("invalid content media-type %q: %v", contentMediaType, err)
}
}
return nil
}
func validateSigMediaType(sigMediaType string) error { func validateSigMediaType(sigMediaType string) error {
if !(sigMediaType == jws.MediaTypeEnvelope || sigMediaType == cose.MediaTypeEnvelope) { if !(sigMediaType == jws.MediaTypeEnvelope || sigMediaType == cose.MediaTypeEnvelope) {
return fmt.Errorf("invalid signature media-type %q", sigMediaType) return fmt.Errorf("invalid signature media-type %q", sigMediaType)

View File

@ -18,11 +18,9 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"time" "time"
@ -37,7 +35,6 @@ import (
"github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/registry" "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -68,85 +65,6 @@ func TestSignSuccess(t *testing.T) {
} }
} }
func TestSignBlobSuccess(t *testing.T) {
reader := strings.NewReader("some content")
testCases := []struct {
name string
dur time.Duration
mtype string
agent string
pConfig map[string]string
metadata map[string]string
}{
{"expiryInHours", 24 * time.Hour, "video/mp4", "", nil, nil},
{"oneSecondExpiry", 1 * time.Second, "video/mp4", "", nil, nil},
{"zeroExpiry", 0, "video/mp4", "", nil, nil},
{"validContentType", 1 * time.Second, "video/mp4", "", nil, nil},
{"emptyContentType", 1 * time.Second, "video/mp4", "someDummyAgent", map[string]string{"hi": "hello"}, map[string]string{"bye": "tata"}},
}
for _, tc := range testCases {
t.Run(tc.name, func(b *testing.T) {
opts := SignBlobOptions{
SignerSignOptions: SignerSignOptions{
SignatureMediaType: jws.MediaTypeEnvelope,
ExpiryDuration: tc.dur,
PluginConfig: tc.pConfig,
SigningAgent: tc.agent,
},
UserMetadata: expectedMetadata,
ContentMediaType: tc.mtype,
}
_, _, err := SignBlob(context.Background(), &dummySigner{}, reader, opts)
if err != nil {
b.Fatalf("Sign failed with error: %v", err)
}
})
}
}
func TestSignBlobError(t *testing.T) {
reader := strings.NewReader("some content")
testCases := []struct {
name string
signer BlobSigner
dur time.Duration
rdr io.Reader
sigMType string
ctMType string
errMsg string
}{
{"negativeExpiry", &dummySigner{}, -1 * time.Second, nil, "video/mp4", jws.MediaTypeEnvelope, "expiry duration cannot be a negative value"},
{"milliSecExpiry", &dummySigner{}, 1 * time.Millisecond, nil, "video/mp4", jws.MediaTypeEnvelope, "expiry duration supports minimum granularity of seconds"},
{"invalidContentMediaType", &dummySigner{}, 1 * time.Second, reader, "video/mp4/zoping", jws.MediaTypeEnvelope, "invalid content media-type \"video/mp4/zoping\": mime: unexpected content after media subtype"},
{"emptyContentMediaType", &dummySigner{}, 1 * time.Second, reader, "", jws.MediaTypeEnvelope, "content media-type cannot be empty"},
{"invalidSignatureMediaType", &dummySigner{}, 1 * time.Second, reader, "", "", "content media-type cannot be empty"},
{"nilReader", &dummySigner{}, 1 * time.Second, nil, "video/mp4", jws.MediaTypeEnvelope, "blobReader cannot be nil"},
{"nilSigner", nil, 1 * time.Second, reader, "video/mp4", jws.MediaTypeEnvelope, "signer cannot be nil"},
{"signerError", &dummySigner{fail: true}, 1 * time.Second, reader, "video/mp4", jws.MediaTypeEnvelope, "expected SignBlob failure"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
opts := SignBlobOptions{
SignerSignOptions: SignerSignOptions{
SignatureMediaType: jws.MediaTypeEnvelope,
ExpiryDuration: tc.dur,
PluginConfig: nil,
},
ContentMediaType: tc.sigMType,
}
_, _, err := SignBlob(context.Background(), tc.signer, tc.rdr, opts)
if err == nil {
t.Fatalf("expected error but didnt found")
}
if err.Error() != tc.errMsg {
t.Fatalf("expected err message to be '%s' but found '%s'", tc.errMsg, err.Error())
}
})
}
}
func TestSignSuccessWithUserMetadata(t *testing.T) { func TestSignSuccessWithUserMetadata(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
opts := SignOptions{} opts := SignOptions{}
@ -307,8 +225,7 @@ func TestSignOptsUnknownMediaType(t *testing.T) {
func TestRegistryResolveError(t *testing.T) { func TestRegistryResolveError(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "network error" errorMessage := "network error"
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -326,8 +243,7 @@ func TestRegistryResolveError(t *testing.T) {
func TestVerifyEmptyReference(t *testing.T) { func TestVerifyEmptyReference(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "reference is missing digest or tag" errorMessage := "reference is missing digest or tag"
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -343,7 +259,7 @@ func TestVerifyEmptyReference(t *testing.T) {
func TestVerifyTagReferenceFailed(t *testing.T) { func TestVerifyTagReferenceFailed(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := "invalid reference: invalid repository \"UPPERCASE/test\"" errorMessage := "invalid reference: invalid repository \"UPPERCASE/test\""
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -360,7 +276,7 @@ func TestVerifyDigestNotMatchResolve(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
repo.MissMatchDigest = true repo.MissMatchDigest = true
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest) errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest)
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -384,7 +300,7 @@ func TestSignDigestNotMatchResolve(t *testing.T) {
} }
errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest) errorMessage := fmt.Sprintf("user input digest %s does not match the resolved digest %s", mock.SampleDigest, mock.ZeroDigest)
expectedErr := fmt.Errorf(errorMessage) expectedErr := errors.New(errorMessage)
_, err := Sign(context.Background(), &dummySigner{}, repo, signOpts) _, err := Sign(context.Background(), &dummySigner{}, repo, signOpts)
if err == nil || err.Error() != errorMessage { if err == nil || err.Error() != errorMessage {
@ -395,7 +311,7 @@ func TestSignDigestNotMatchResolve(t *testing.T) {
func TestSkippedSignatureVerification(t *testing.T) { func TestSkippedSignatureVerification(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelSkip} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelSkip, false}
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50} opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
_, outcomes, err := Verify(context.Background(), &verifier, repo, opts) _, outcomes, err := Verify(context.Background(), &verifier, repo, opts)
@ -408,7 +324,7 @@ func TestSkippedSignatureVerification(t *testing.T) {
func TestRegistryNoSignatureManifests(t *testing.T) { func TestRegistryNoSignatureManifests(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := fmt.Sprintf("no signature is associated with %q, make sure the artifact was signed successfully", mock.SampleArtifactUri) errorMessage := fmt.Sprintf("no signature is associated with %q, make sure the artifact was signed successfully", mock.SampleArtifactUri)
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -425,7 +341,7 @@ func TestRegistryNoSignatureManifests(t *testing.T) {
func TestRegistryFetchSignatureBlobError(t *testing.T) { func TestRegistryFetchSignatureBlobError(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
errorMessage := fmt.Sprintf("unable to retrieve digital signature with digest %q associated with %q from the Repository, error : network error", mock.SampleDigest, mock.SampleArtifactUri) errorMessage := fmt.Sprintf("unable to retrieve digital signature with digest %q associated with %q from the Repository, error : network error", mock.SampleDigest, mock.SampleArtifactUri)
expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage} expectedErr := ErrorSignatureRetrievalFailed{Msg: errorMessage}
@ -442,21 +358,35 @@ func TestRegistryFetchSignatureBlobError(t *testing.T) {
func TestVerifyValid(t *testing.T) { func TestVerifyValid(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
// mock the repository // mock the repository
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50} opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
_, _, err := Verify(context.Background(), &verifier, repo, opts) _, _, err := Verify(context.Background(), &verifier, repo, opts)
if err != nil { if err != nil {
t.Fatalf("SignaureMediaTypeMismatch expected: %v got: %v", nil, err) t.Fatalf("expected nil error, but got: %v", err)
}
}
func TestVerifySkip(t *testing.T) {
repo := mock.NewRepository()
policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, true}
// mock the repository
opts := VerifyOptions{ArtifactReference: mock.SampleArtifactUri, MaxSignatureAttempts: 50}
_, _, err := Verify(context.Background(), &verifier, repo, opts)
if err != nil {
t.Fatalf("expected nil error, but got: %v", err)
} }
} }
func TestMaxSignatureAttemptsMissing(t *testing.T) { func TestMaxSignatureAttemptsMissing(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
expectedErr := ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("verifyOptions.MaxSignatureAttempts expects a positive number, got %d", 0)} expectedErr := ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("verifyOptions.MaxSignatureAttempts expects a positive number, got %d", 0)}
// mock the repository // mock the repository
@ -472,7 +402,7 @@ func TestExceededMaxSignatureAttempts(t *testing.T) {
repo := mock.NewRepository() repo := mock.NewRepository()
repo.ExceededNumOfSignatures = true repo.ExceededNumOfSignatures = true
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
expectedErr := ErrorVerificationFailed{Msg: fmt.Sprintf("signature evaluation stopped. The configured limit of %d signatures to verify per artifact exceeded", 1)} expectedErr := ErrorVerificationFailed{Msg: fmt.Sprintf("signature evaluation stopped. The configured limit of %d signatures to verify per artifact exceeded", 1)}
@ -489,7 +419,7 @@ func TestVerifyFailed(t *testing.T) {
t.Run("verification error", func(t *testing.T) { t.Run("verification error", func(t *testing.T) {
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
repo := mock.NewRepository() repo := mock.NewRepository()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, true, *trustpolicy.LevelStrict, false}
expectedErr := ErrorVerificationFailed{} expectedErr := ErrorVerificationFailed{}
// mock the repository // mock the repository
@ -516,7 +446,7 @@ func TestVerifyFailed(t *testing.T) {
t.Run("repo is nil", func(t *testing.T) { t.Run("repo is nil", func(t *testing.T) {
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
expectedErr := errors.New("repo cannot be nil") expectedErr := errors.New("repo cannot be nil")
// mock the repository // mock the repository
@ -529,63 +459,6 @@ func TestVerifyFailed(t *testing.T) {
}) })
} }
func TestVerifyBlobError(t *testing.T) {
reader := strings.NewReader("some content")
sig := []byte("signature")
testCases := []struct {
name string
verifier BlobVerifier
sig []byte
rdr io.Reader
ctMType string
sigMType string
errMsg string
}{
{"nilVerifier", nil, sig, reader, "video/mp4", jws.MediaTypeEnvelope, "blobVerifier cannot be nil"},
{"verifierError", &dummyVerifier{FailVerify: true}, sig, reader, "video/mp4", jws.MediaTypeEnvelope, "failed verify"},
{"nilSignature", &dummyVerifier{}, nil, reader, "video/mp4", jws.MediaTypeEnvelope, "signature cannot be nil or empty"},
{"emptySignature", &dummyVerifier{}, []byte{}, reader, "video/mp4", jws.MediaTypeEnvelope, "signature cannot be nil or empty"},
{"nilReader", &dummyVerifier{}, sig, nil, "video/mp4", jws.MediaTypeEnvelope, "blobReader cannot be nil"},
{"invalidContentType", &dummyVerifier{}, sig, reader, "video/mp4/zoping", jws.MediaTypeEnvelope, "invalid content media-type \"video/mp4/zoping\": mime: unexpected content after media subtype"},
{"invalidSigType", &dummyVerifier{}, sig, reader, "video/mp4", "hola!", "invalid signature media-type \"hola!\""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
opts := VerifyBlobOptions{
BlobVerifierVerifyOptions: BlobVerifierVerifyOptions{
SignatureMediaType: tc.sigMType,
UserMetadata: nil,
TrustPolicyName: "",
},
ContentMediaType: tc.ctMType,
}
_, _, err := VerifyBlob(context.Background(), tc.verifier, tc.rdr, tc.sig, opts)
if err == nil {
t.Fatalf("expected error but didnt found")
}
if err.Error() != tc.errMsg {
t.Fatalf("expected err message to be '%s' but found '%s'", tc.errMsg, err.Error())
}
})
}
}
func TestVerifyBlobValid(t *testing.T) {
opts := VerifyBlobOptions{
BlobVerifierVerifyOptions: BlobVerifierVerifyOptions{
SignatureMediaType: jws.MediaTypeEnvelope,
UserMetadata: nil,
TrustPolicyName: "",
},
}
_, _, err := VerifyBlob(context.Background(), &dummyVerifier{}, strings.NewReader("some content"), []byte("signature"), opts)
if err != nil {
t.Fatalf("SignaureMediaTypeMismatch expected: %v got: %v", nil, err)
}
}
func dummyPolicyDocument() (policyDoc trustpolicy.Document) { func dummyPolicyDocument() (policyDoc trustpolicy.Document) {
policyDoc = trustpolicy.Document{ policyDoc = trustpolicy.Document{
Version: "1.0", Version: "1.0",
@ -605,10 +478,7 @@ func dummyPolicyStatement() (policyStatement trustpolicy.TrustPolicy) {
return return
} }
type dummySigner struct{}
type dummySigner struct {
fail bool
}
func (s *dummySigner) Sign(_ context.Context, _ ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) { func (s *dummySigner) Sign(_ context.Context, _ ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
return []byte("ABC"), &signature.SignerInfo{ return []byte("ABC"), &signature.SignerInfo{
@ -618,23 +488,6 @@ func (s *dummySigner) Sign(_ context.Context, _ ocispec.Descriptor, _ SignerSign
}, nil }, nil
} }
func (s *dummySigner) SignBlob(_ context.Context, descGenFunc BlobDescriptorGenerator, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
if s.fail {
return nil, nil, errors.New("expected SignBlob failure")
}
_, err := descGenFunc(digest.SHA384)
if err != nil {
return nil, nil, err
}
return []byte("ABC"), &signature.SignerInfo{
SignedAttributes: signature.SignedAttributes{
SigningTime: time.Now(),
},
}, nil
}
type verifyMetadataSigner struct{} type verifyMetadataSigner struct{}
func (s *verifyMetadataSigner) Sign(_ context.Context, desc ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) { func (s *verifyMetadataSigner) Sign(_ context.Context, desc ocispec.Descriptor, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
@ -651,10 +504,11 @@ func (s *verifyMetadataSigner) Sign(_ context.Context, desc ocispec.Descriptor,
} }
type dummyVerifier struct { type dummyVerifier struct {
TrustPolicyDoc *trustpolicy.OCIDocument TrustPolicyDoc *trustpolicy.Document
PluginManager plugin.Manager PluginManager plugin.Manager
FailVerify bool FailVerify bool
VerificationLevel trustpolicy.VerificationLevel VerificationLevel trustpolicy.VerificationLevel
SkipVerification bool
} }
func (v *dummyVerifier) Verify(_ context.Context, _ ocispec.Descriptor, _ []byte, _ VerifierVerifyOptions) (*VerificationOutcome, error) { func (v *dummyVerifier) Verify(_ context.Context, _ ocispec.Descriptor, _ []byte, _ VerifierVerifyOptions) (*VerificationOutcome, error) {
@ -668,20 +522,11 @@ func (v *dummyVerifier) Verify(_ context.Context, _ ocispec.Descriptor, _ []byte
return outcome, nil return outcome, nil
} }
func (v *dummyVerifier) VerifyBlob(_ context.Context, _ BlobDescriptorGenerator, _ []byte, _ BlobVerifierVerifyOptions) (*VerificationOutcome, error) { func (v *dummyVerifier) SkipVerify(_ context.Context, _ VerifierVerifyOptions) (bool, *trustpolicy.VerificationLevel, error) {
if v.FailVerify { if v.SkipVerification {
return nil, errors.New("failed verify") return true, nil, nil
} }
return false, nil, nil
return &VerificationOutcome{
VerificationResults: []*ValidationResult{},
VerificationLevel: &v.VerificationLevel,
EnvelopeContent: &signature.EnvelopeContent{
Payload: signature.Payload{
Content: []byte("{}"),
},
},
}, nil
} }
var ( var (
@ -744,7 +589,7 @@ func TestLocalContent(t *testing.T) {
MaxSignatureAttempts: math.MaxInt64, MaxSignatureAttempts: math.MaxInt64,
} }
policyDocument := dummyPolicyDocument() policyDocument := dummyPolicyDocument()
verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict} verifier := dummyVerifier{&policyDocument, mock.PluginManager{}, false, *trustpolicy.LevelStrict, false}
// verify signatures inside the OCI layout folder // verify signatures inside the OCI layout folder
_, _, err = Verify(context.Background(), &verifier, repo, verifyOpts) _, _, err = Verify(context.Background(), &verifier, repo, verifyOpts)
if err != nil { if err != nil {

View File

@ -27,30 +27,38 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/notaryproject/notation-go/internal/io"
"github.com/notaryproject/notation-go/internal/slices" "github.com/notaryproject/notation-go/internal/slices"
"github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/plugin/proto"
"github.com/notaryproject/notation-plugin-framework-go/plugin" "github.com/notaryproject/notation-plugin-framework-go/plugin"
) )
// maxPluginOutputSize is the maximum size of the plugin output.
const maxPluginOutputSize = 64 * 1024 * 1024 // 64 MiB
var executor commander = &execCommander{} // for unit test var executor commander = &execCommander{} // for unit test
// GenericPlugin is the base requirement to be a plugin. // GenericPlugin is the base requirement to be a plugin.
//
// Deprecated: GenericPlugin exists for historical compatibility and should not be used. // Deprecated: GenericPlugin exists for historical compatibility and should not be used.
// To access GenericPlugin, use the notation-plugin-framework-go's plugin.GenericPlugin type. // To access GenericPlugin, use the notation-plugin-framework-go's plugin.GenericPlugin type.
type GenericPlugin = plugin.GenericPlugin type GenericPlugin = plugin.GenericPlugin
// SignPlugin defines the required methods to be a SignPlugin. // SignPlugin defines the required methods to be a SignPlugin.
//
// Deprecated: SignPlugin exists for historical compatibility and should not be used. // Deprecated: SignPlugin exists for historical compatibility and should not be used.
// To access SignPlugin, use the notation-plugin-framework-go's plugin.SignPlugin type. // To access SignPlugin, use the notation-plugin-framework-go's plugin.SignPlugin type.
type SignPlugin = plugin.SignPlugin type SignPlugin = plugin.SignPlugin
// VerifyPlugin defines the required method to be a VerifyPlugin. // VerifyPlugin defines the required method to be a VerifyPlugin.
//
// Deprecated: VerifyPlugin exists for historical compatibility and should not be used. // Deprecated: VerifyPlugin exists for historical compatibility and should not be used.
// To access VerifyPlugin, use the notation-plugin-framework-go's plugin.VerifyPlugin type. // To access VerifyPlugin, use the notation-plugin-framework-go's plugin.VerifyPlugin type.
type VerifyPlugin = plugin.VerifyPlugin type VerifyPlugin = plugin.VerifyPlugin
// Plugin defines required methods to be a Plugin. // Plugin defines required methods to be a Plugin.
//
// Deprecated: Plugin exists for historical compatibility and should not be used. // Deprecated: Plugin exists for historical compatibility and should not be used.
// To access Plugin, use the notation-plugin-framework-go's plugin.Plugin type. // To access Plugin, use the notation-plugin-framework-go's plugin.Plugin type.
type Plugin = plugin.Plugin type Plugin = plugin.Plugin
@ -218,10 +226,15 @@ func (c execCommander) Output(ctx context.Context, name string, command plugin.C
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(ctx, name, string(command)) cmd := exec.CommandContext(ctx, name, string(command))
cmd.Stdin = bytes.NewReader(req) cmd.Stdin = bytes.NewReader(req)
cmd.Stderr = &stderr // The limit writer will be handled by the caller in run() by comparing the
cmd.Stdout = &stdout // bytes written with the expected length of the bytes.
cmd.Stderr = io.LimitWriter(&stderr, maxPluginOutputSize)
cmd.Stdout = io.LimitWriter(&stdout, maxPluginOutputSize)
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, stderr.Bytes(), fmt.Errorf("'%s %s' command execution timeout: %w", name, string(command), err)
}
return nil, stderr.Bytes(), err return nil, stderr.Bytes(), err
} }
return stdout.Bytes(), nil, nil return stdout.Bytes(), nil, nil

View File

@ -19,9 +19,11 @@ import (
"errors" "errors"
"os" "os"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"time"
"github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/plugin/proto"
) )
@ -181,7 +183,7 @@ func TestValidateMetadata(t *testing.T) {
} }
} }
func TestNewCLIPlugin_PathError(t *testing.T) { func TestNewCLIPlugin_Error(t *testing.T) {
ctx := context.Background() ctx := context.Background()
t.Run("plugin directory exists without executable.", func(t *testing.T) { t.Run("plugin directory exists without executable.", func(t *testing.T) {
p, err := NewCLIPlugin(ctx, "emptyplugin", "./testdata/plugins/emptyplugin/notation-emptyplugin") p, err := NewCLIPlugin(ctx, "emptyplugin", "./testdata/plugins/emptyplugin/notation-emptyplugin")
@ -203,6 +205,25 @@ func TestNewCLIPlugin_PathError(t *testing.T) {
t.Errorf("NewCLIPlugin() plugin = %v, want nil", p) t.Errorf("NewCLIPlugin() plugin = %v, want nil", p)
} }
}) })
t.Run("plugin timeout error", func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on Windows")
}
expectedErrMsg := "'sleep 2' command execution timeout: signal: killed"
ctxWithTimout, cancel := context.WithTimeout(ctx, 10 * time.Millisecond)
defer cancel()
var twoSeconds proto.Command
twoSeconds = "2"
_, _, err := execCommander{}.Output(ctxWithTimout, "sleep", twoSeconds, nil);
if err == nil {
t.Errorf("execCommander{}.Output() expected error = %v, got nil", expectedErrMsg)
}
if err.Error() != expectedErrMsg {
t.Errorf("execCommander{}.Output() error = %v, want %v", err, expectedErrMsg)
}
})
} }
func TestNewCLIPlugin_ValidError(t *testing.T) { func TestNewCLIPlugin_ValidError(t *testing.T) {

View File

@ -15,7 +15,6 @@ package signer
import ( import (
"context" "context"
"crypto"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"errors" "errors"
@ -30,7 +29,6 @@ import (
"github.com/notaryproject/notation-go/log" "github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/plugin/proto"
"github.com/notaryproject/notation-plugin-framework-go/plugin" "github.com/notaryproject/notation-plugin-framework-go/plugin"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -43,15 +41,10 @@ type PluginSigner struct {
manifestAnnotations map[string]string manifestAnnotations map[string]string
} }
var algorithms = map[crypto.Hash]digest.Algorithm{
crypto.SHA256: digest.SHA256,
crypto.SHA384: digest.SHA384,
crypto.SHA512: digest.SHA512,
}
// NewFromPlugin creates a notation.Signer that signs artifacts and generates // NewFromPlugin creates a notation.Signer that signs artifacts and generates
// signatures by delegating the one or more operations to the named plugin, // signatures by delegating the one or more operations to the named plugin,
// as defined in https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#signing-interfaces. // as defined in https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#signing-interfaces.
//
// Deprecated: NewFromPlugin function exists for historical compatibility and should not be used. // Deprecated: NewFromPlugin function exists for historical compatibility and should not be used.
// To create PluginSigner, use NewPluginSigner() function. // To create PluginSigner, use NewPluginSigner() function.
func NewFromPlugin(plugin plugin.SignPlugin, keyID string, pluginConfig map[string]string) (notation.Signer, error) { func NewFromPlugin(plugin plugin.SignPlugin, keyID string, pluginConfig map[string]string) (notation.Signer, error) {
@ -116,38 +109,6 @@ func (s *PluginSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts n
return nil, nil, fmt.Errorf("plugin does not have signing capabilities") return nil, nil, fmt.Errorf("plugin does not have signing capabilities")
} }
// SignBlob signs the arbitrary data and returns the marshalled envelope.
func (s *PluginSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
logger := log.GetLogger(ctx)
mergedConfig := s.mergeConfig(opts.PluginConfig)
logger.Debug("Invoking plugin's get-plugin-metadata command")
metadata, err := s.plugin.GetMetadata(ctx, &plugin.GetMetadataRequest{PluginConfig: mergedConfig})
if err != nil {
return nil, nil, err
}
logger.Debug("Invoking plugin's describe-key command")
ks, err := s.getKeySpec(ctx, mergedConfig)
if err != nil {
return nil, nil, err
}
// get descriptor to sign
desc, err := getDescriptor(ks, descGenFunc)
if err != nil {
return nil, nil, err
}
logger.Debugf("Using plugin %v with capabilities %v to sign blob using descriptor %+v", metadata.Name, metadata.Capabilities, desc)
if metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
return s.generateSignature(ctx, desc, opts, ks, metadata, mergedConfig)
} else if metadata.HasCapability(plugin.CapabilityEnvelopeGenerator) {
return s.generateSignatureEnvelope(ctx, desc, opts)
}
return nil, nil, fmt.Errorf("plugin does not have signing capabilities")
}
func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string) (signature.KeySpec, error) { func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string) (signature.KeySpec, error) {
logger := log.GetLogger(ctx) logger := log.GetLogger(ctx)
logger.Debug("Invoking plugin's describe-key command") logger.Debug("Invoking plugin's describe-key command")

View File

@ -32,7 +32,6 @@ import (
"github.com/notaryproject/notation-go/internal/envelope" "github.com/notaryproject/notation-go/internal/envelope"
"github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/plugin/proto"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -71,16 +70,6 @@ type mockPlugin struct {
keySpec signature.KeySpec keySpec signature.KeySpec
} }
func getDescriptorFunc(throwError bool) func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
return func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
if throwError {
return ocispec.Descriptor{}, errors.New("")
}
return validSignDescriptor, nil
}
}
func newMockPlugin(key crypto.PrivateKey, certs []*x509.Certificate, keySpec signature.KeySpec) *mockPlugin { func newMockPlugin(key crypto.PrivateKey, certs []*x509.Certificate, keySpec signature.KeySpec) *mockPlugin {
return &mockPlugin{ return &mockPlugin{
key: key, key: key,
@ -336,22 +325,6 @@ func TestPluginSigner_Sign_Valid(t *testing.T) {
} }
} }
func TestPluginSigner_SignBlob_Valid(t *testing.T) {
for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
for _, keyCert := range keyCertPairCollections {
t.Run(fmt.Sprintf("external plugin,envelopeType=%v_keySpec=%v", envelopeType, keyCert.keySpecName), func(t *testing.T) {
keySpec, _ := proto.DecodeKeySpec(proto.KeySpec(keyCert.keySpecName))
pluginSigner := PluginSigner{
plugin: newMockPlugin(keyCert.key, keyCert.certs, keySpec),
}
validSignOpts.SignatureMediaType = envelopeType
data, signerInfo, err := pluginSigner.SignBlob(context.Background(), getDescriptorFunc(false), validSignOpts)
basicSignTest(t, &pluginSigner, envelopeType, data, signerInfo, err)
})
}
}
}
func TestPluginSigner_SignEnvelope_RunFailed(t *testing.T) { func TestPluginSigner_SignEnvelope_RunFailed(t *testing.T) {
for _, envelopeType := range signature.RegisteredEnvelopeTypes() { for _, envelopeType := range signature.RegisteredEnvelopeTypes() {
t.Run(fmt.Sprintf("envelopeType=%v", envelopeType), func(t *testing.T) { t.Run(fmt.Sprintf("envelopeType=%v", envelopeType), func(t *testing.T) {

View File

@ -34,7 +34,7 @@ import (
) )
// signingAgent is the unprotected header field used by signature. // signingAgent is the unprotected header field used by signature.
const signingAgent = "notation-go/1.3.0+unreleased" const signingAgent = "notation-go/1.3.1"
// GenericSigner implements notation.Signer and embeds signature.Signer // GenericSigner implements notation.Signer and embeds signature.Signer
type GenericSigner struct { type GenericSigner struct {
@ -42,6 +42,7 @@ type GenericSigner struct {
} }
// New returns a builtinSigner given key and cert chain // New returns a builtinSigner given key and cert chain
//
// Deprecated: New function exists for historical compatibility and should not be used. // Deprecated: New function exists for historical compatibility and should not be used.
// To create GenericSigner, use NewGenericSigner() function. // To create GenericSigner, use NewGenericSigner() function.
func New(key crypto.PrivateKey, certChain []*x509.Certificate) (notation.Signer, error) { func New(key crypto.PrivateKey, certChain []*x509.Certificate) (notation.Signer, error) {
@ -106,7 +107,6 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err) return nil, nil, fmt.Errorf("envelope payload can't be marshalled: %w", err)
} }
var signingAgentId string var signingAgentId string
if opts.SigningAgent != "" { if opts.SigningAgent != "" {
signingAgentId = opts.SigningAgent signingAgentId = opts.SigningAgent
@ -130,6 +130,7 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
SigningAgent: signingAgentId, SigningAgent: signingAgentId,
Timestamper: opts.Timestamper, Timestamper: opts.Timestamper,
TSARootCAs: opts.TSARootCAs, TSARootCAs: opts.TSARootCAs,
TSARevocationValidator: opts.TSARevocationValidator,
} }
// Add expiry only if ExpiryDuration is not zero // Add expiry only if ExpiryDuration is not zero
@ -143,6 +144,12 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
logger.Debugf(" Expiry: %v", signReq.Expiry) logger.Debugf(" Expiry: %v", signReq.Expiry)
logger.Debugf(" SigningScheme: %v", signReq.SigningScheme) logger.Debugf(" SigningScheme: %v", signReq.SigningScheme)
logger.Debugf(" SigningAgent: %v", signReq.SigningAgent) logger.Debugf(" SigningAgent: %v", signReq.SigningAgent)
if signReq.Timestamper != nil {
logger.Debug("Enabled timestamping")
if signReq.TSARevocationValidator != nil {
logger.Debug("Enabled timestamping certificate chain revocation check")
}
}
// Add ctx to the SignRequest // Add ctx to the SignRequest
signReq = signReq.WithContext(ctx) signReq = signReq.WithContext(ctx)
@ -152,12 +159,10 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sig, err := sigEnv.Sign(signReq) sig, err := sigEnv.Sign(signReq)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
envContent, err := sigEnv.Verify() envContent, err := sigEnv.Verify()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("generated signature failed verification: %v", err) return nil, nil, fmt.Errorf("generated signature failed verification: %v", err)
@ -167,30 +172,3 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts
} }
return sig, &envContent.SignerInfo, nil return sig, &envContent.SignerInfo, nil
} }
// SignBlob signs the descriptor returned by blobGen and returns the marshalled envelope
func (s *GenericSigner) SignBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
logger := log.GetLogger(ctx)
logger.Debugf("Generic blob signing for signature media type %v", opts.SignatureMediaType)
ks, err := s.signer.KeySpec()
if err != nil {
return nil, nil, err
}
desc, err := getDescriptor(ks, descGenFunc)
if err != nil {
return nil, nil, err
}
return s.Sign(ctx, desc, opts)
}
func getDescriptor(ks signature.KeySpec, descGenFunc notation.BlobDescriptorGenerator) (ocispec.Descriptor, error) {
digestAlg, ok := algorithms[ks.SignatureAlgorithm().Hash()]
if !ok {
return ocispec.Descriptor{}, fmt.Errorf("unknown hashing algo %v", ks.SignatureAlgorithm().Hash())
}
return descGenFunc(digestAlg)
}

View File

@ -30,6 +30,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/notaryproject/notation-core-go/revocation"
"github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature"
_ "github.com/notaryproject/notation-core-go/signature/cose" _ "github.com/notaryproject/notation-core-go/signature/cose"
_ "github.com/notaryproject/notation-core-go/signature/jws" _ "github.com/notaryproject/notation-core-go/signature/jws"
@ -257,30 +259,26 @@ func TestSignWithTimestamping(t *testing.T) {
if err == nil || err.Error() != expectedErrMsg { if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err) t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
} }
}
func TestSignBlobWithCertChain(t *testing.T) { // timestamping with unknown authority
// sign with key desc, sOpts = generateSigningContent()
for _, envelopeType := range signature.RegisteredEnvelopeTypes() { sOpts.SignatureMediaType = envelopeType
for _, keyCert := range keyCertPairCollections { sOpts.Timestamper, err = tspclient.NewHTTPTimestamper(nil, rfc3161URL)
t.Run(fmt.Sprintf("envelopeType=%v_keySpec=%v", envelopeType, keyCert.keySpecName), func(t *testing.T) {
s, err := NewGenericSigner(keyCert.key, keyCert.certs)
if err != nil { if err != nil {
t.Fatalf("NewSigner() error = %v", err) t.Fatal(err)
} }
sOpts.TSARootCAs = x509.NewCertPool()
sOpts := notation.SignerSignOptions{ tsaRevocationValidator, err := revocation.NewWithOptions(revocation.Options{
SignatureMediaType: envelopeType, CertChainPurpose: purpose.Timestamping,
}
sig, _, err := s.SignBlob(context.Background(), getDescriptorFunc(false), sOpts)
if err != nil {
t.Fatalf("Sign() error = %v", err)
}
// basic verification
basicVerification(t, sig, envelopeType, keyCert.certs[len(keyCert.certs)-1], nil)
}) })
if err != nil {
t.Fatal(err)
} }
sOpts.TSARevocationValidator = tsaRevocationValidator
_, _, err = s.Sign(ctx, desc, sOpts)
expectedErrMsg = "timestamp: failed to verify signed token: cms verification failure: x509: certificate signed by unknown authority"
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
} }
} }

View File

@ -110,11 +110,11 @@ func (c *FileCache) Get(ctx context.Context, url string) (*corecrl.Bundle, error
// check expiry // check expiry
if err := checkExpiry(ctx, bundle.BaseCRL.NextUpdate); err != nil { if err := checkExpiry(ctx, bundle.BaseCRL.NextUpdate); err != nil {
return nil, err return nil, fmt.Errorf("check BaseCRL expiry failed: %w", err)
} }
if bundle.DeltaCRL != nil { if bundle.DeltaCRL != nil {
if err := checkExpiry(ctx, bundle.DeltaCRL.NextUpdate); err != nil { if err := checkExpiry(ctx, bundle.DeltaCRL.NextUpdate); err != nil {
return nil, err return nil, fmt.Errorf("check DeltaCRL expiry failed: %w", err)
} }
} }
@ -144,7 +144,7 @@ func (c *FileCache) Set(ctx context.Context, url string, bundle *corecrl.Bundle)
if err != nil { if err != nil {
return fmt.Errorf("failed to store crl bundle in file cache: %w", err) return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
} }
if err := file.WriteFile(filepath.Join(c.root, c.fileName(url)), contentBytes); err != nil { if err := file.WriteFile(c.root, filepath.Join(c.root, c.fileName(url)), contentBytes); err != nil {
return fmt.Errorf("failed to store crl bundle in file cache: %w", err) return fmt.Errorf("failed to store crl bundle in file cache: %w", err)
} }
return nil return nil

View File

@ -270,7 +270,7 @@ func TestGetFailed(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = cache.Get(ctx, "expiredKey") _, err = cache.Get(ctx, "expiredKey")
expectedErrMsg := "crl bundle retrieved from file cache does not contain valid NextUpdate" expectedErrMsg := "check BaseCRL expiry failed: crl bundle retrieved from file cache does not contain valid NextUpdate"
if err == nil || err.Error() != expectedErrMsg { if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %v", expectedErrMsg, err) t.Fatalf("expected %s, but got %v", expectedErrMsg, err)
} }

View File

@ -60,7 +60,7 @@ func TestLoadX509TrustStore(t *testing.T) {
// load "ca" and "signingAuthority" trust store // load "ca" and "signingAuthority" trust store
caStore := "ca:valid-trust-store" caStore := "ca:valid-trust-store"
signingAuthorityStore := "signingAuthority:valid-trust-store" signingAuthorityStore := "signingAuthority:valid-trust-store"
dummyPolicy := dummyOCIPolicyDocument().TrustPolicies[0] dummyPolicy := dummyPolicyDocument().TrustPolicies[0]
dummyPolicy.TrustStores = []string{caStore, signingAuthorityStore} dummyPolicy.TrustStores = []string{caStore, signingAuthorityStore}
dir.UserConfigDir = "testdata" dir.UserConfigDir = "testdata"
x509truststore := truststore.NewX509TrustStore(dir.ConfigFS()) x509truststore := truststore.NewX509TrustStore(dir.ConfigFS())
@ -138,10 +138,10 @@ func getArtifactDigestFromReference(artifactReference string) (string, error) {
return artifactReference[i+1:], nil return artifactReference[i+1:], nil
} }
func dummyOCIPolicyDocument() (policyDoc trustpolicy.OCIDocument) { func dummyPolicyDocument() (policyDoc trustpolicy.Document) {
return trustpolicy.OCIDocument{ return trustpolicy.Document{
Version: "1.0", Version: "1.0",
TrustPolicies: []trustpolicy.OCITrustPolicy{ TrustPolicies: []trustpolicy.TrustPolicy{
{ {
Name: "test-statement-name", Name: "test-statement-name",
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
@ -153,15 +153,11 @@ func dummyOCIPolicyDocument() (policyDoc trustpolicy.OCIDocument) {
} }
} }
func dummyBlobPolicyDocument() (policyDoc trustpolicy.BlobDocument) { func dummyInvalidPolicyDocument() (policyDoc trustpolicy.Document) {
return trustpolicy.BlobDocument{ return trustpolicy.Document{
Version: "1.0", TrustPolicies: []trustpolicy.TrustPolicy{
TrustPolicies: []trustpolicy.BlobTrustPolicy{
{ {
Name: "blob-test-statement-name", Name: "invalid",
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"},
TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"},
}, },
}, },
} }

View File

@ -216,7 +216,7 @@ func TestAuthenticTimestamp(t *testing.T) {
VerificationLevel: trustpolicy.LevelStrict, VerificationLevel: trustpolicy.LevelStrict,
} }
authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestampingValidator, outcome) authenticTimestampResult := verifyAuthenticTimestamp(context.Background(), dummyTrustPolicy.Name, dummyTrustPolicy.TrustStores, dummyTrustPolicy.SignatureVerification, trustStore, revocationTimestampingValidator, outcome)
expectedErrMsg := "failed to parse timestamp countersignature with error: unexpected content type: 1.2.840.113549.1.7.1" expectedErrMsg := "failed to parse timestamp countersignature with error: unexpected content type: 1.2.840.113549.1.7.1. Expected to be id-ct-TSTInfo (1.2.840.113549.1.9.16.1.4)"
if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg { if err := authenticTimestampResult.Error; err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err) t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
} }

View File

@ -1,151 +0,0 @@
// 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 trustpolicy
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/notaryproject/notation-go/dir"
set "github.com/notaryproject/notation-go/internal/container"
"github.com/notaryproject/notation-go/internal/slices"
)
// BlobDocument represents a trustpolicy.blob.json document
type BlobDocument struct {
// Version of the policy document
Version string `json:"version"`
// TrustPolicies include each policy statement
TrustPolicies []BlobTrustPolicy `json:"trustPolicies"`
}
// BlobTrustPolicy represents a policy statement in the blob policy document
type BlobTrustPolicy struct {
// Name of the policy statement
Name string `json:"name"`
// SignatureVerification setting for this policy statement
SignatureVerification SignatureVerification `json:"signatureVerification"`
// TrustStores this policy statement uses
TrustStores []string `json:"trustStores"`
// TrustedIdentities this policy statement pins
TrustedIdentities []string `json:"trustedIdentities"`
// GlobalPolicy defines if policy statement is global or not
GlobalPolicy bool `json:"globalPolicy,omitempty"`
}
var supportedBlobPolicyVersions = []string{"1.0"}
// LoadBlobDocument loads a trust policy document from a local file system
func LoadBlobDocument() (*BlobDocument, error) {
var doc BlobDocument
err := getDocument(dir.PathBlobTrustPolicy, &doc)
return &doc, err
}
// Validate validates a policy document according to its version's rule set.
// if any rule is violated, returns an error
func (policyDoc *BlobDocument) Validate() error {
// sanity check
if policyDoc == nil {
return errors.New("blob trust policy document cannot be nil")
}
// Validate Version
if policyDoc.Version == "" {
return errors.New("blob trust policy has empty version, version must be specified")
}
if !slices.Contains(supportedBlobPolicyVersions, policyDoc.Version) {
return fmt.Errorf("blob trust policy document uses unsupported version %q", policyDoc.Version)
}
// Validate the policy according to 1.0 rules
if len(policyDoc.TrustPolicies) == 0 {
return errors.New("blob trust policy document can not have zero trust policy statements")
}
policyNames := set.New[string]()
var foundGlobalPolicy bool
for _, statement := range policyDoc.TrustPolicies {
// Verify unique policy statement names across the policy document
if policyNames.Contains(statement.Name) {
return fmt.Errorf("multiple blob trust policy statements use the same name %q, statement names must be unique", statement.Name)
}
if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
return fmt.Errorf("blob trust policy: %w", err)
}
if statement.GlobalPolicy {
if foundGlobalPolicy {
return errors.New("multiple blob trust policy statements have globalPolicy set to true. Only one trust policy statement can be marked as global policy")
}
// verificationLevel is skip
if reflect.DeepEqual(statement.SignatureVerification.VerificationLevel, LevelSkip) {
return errors.New("global blob trust policy statement cannot have verification level set to skip")
}
foundGlobalPolicy = true
}
policyNames.Add(statement.Name)
}
return nil
}
// GetApplicableTrustPolicy returns a pointer to the deep copied TrustPolicy for given policy name
// see https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#blob-trust-policy
func (policyDoc *BlobDocument) GetApplicableTrustPolicy(policyName string) (*BlobTrustPolicy, error) {
if strings.TrimSpace(policyName) == "" {
return nil, errors.New("policy name cannot be empty")
}
for _, policyStatement := range policyDoc.TrustPolicies {
// exact match
if policyStatement.Name == policyName {
return (&policyStatement).clone(), nil
}
}
return nil, fmt.Errorf("no applicable blob trust policy with name %q", policyName)
}
// GetGlobalTrustPolicy returns a pointer to the deep copy of the TrustPolicy that is marked as global policy
// see https://github.com/notaryproject/notaryproject/blob/v1.1.0/specs/trust-store-trust-policy.md#blob-trust-policy
func (policyDoc *BlobDocument) GetGlobalTrustPolicy() (*BlobTrustPolicy, error) {
for _, policyStatement := range policyDoc.TrustPolicies {
if policyStatement.GlobalPolicy {
return (&policyStatement).clone(), nil
}
}
return nil, fmt.Errorf("no global blob trust policy")
}
// clone returns a pointer to the deeply copied TrustPolicy
func (t *BlobTrustPolicy) clone() *BlobTrustPolicy {
return &BlobTrustPolicy{
Name: t.Name,
SignatureVerification: t.SignatureVerification,
TrustedIdentities: append([]string(nil), t.TrustedIdentities...),
TrustStores: append([]string(nil), t.TrustStores...),
GlobalPolicy: t.GlobalPolicy,
}
}

View File

@ -1,168 +0,0 @@
// 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 trustpolicy
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/notaryproject/notation-go/dir"
)
func TestLoadBlobDocument(t *testing.T) {
tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot
path := filepath.Join(tempRoot, "trustpolicy.blob.json")
policyJson, _ := json.Marshal(dummyBlobPolicyDocument())
if err := os.WriteFile(path, policyJson, 0600); err != nil {
t.Fatalf("TestLoadBlobDocument write policy file failed. Error: %v", err)
}
t.Cleanup(func() { os.RemoveAll(tempRoot) })
if _, err := LoadBlobDocument(); err != nil {
t.Fatalf("LoadBlobDocument() should not throw error for an existing policy file. Error: %v", err)
}
}
func TestValidate_BlobDocument(t *testing.T) {
policyDoc := dummyBlobPolicyDocument()
if err := policyDoc.Validate(); err != nil {
t.Fatalf("Validate() returned error: %v", err)
}
}
func TestValidate_BlobDocument_Error(t *testing.T) {
// Sanity check
var nilPolicyDoc *BlobDocument
err := nilPolicyDoc.Validate()
if err == nil || err.Error() != "blob trust policy document cannot be nil" {
t.Fatalf("nil policyDoc should return error")
}
// empty Version
policyDoc := dummyBlobPolicyDocument()
policyDoc.Version = ""
err = policyDoc.Validate()
if err == nil || err.Error() != "blob trust policy has empty version, version must be specified" {
t.Fatalf("empty version should return error")
}
// Invalid Version
policyDoc = dummyBlobPolicyDocument()
policyDoc.Version = "invalid"
err = policyDoc.Validate()
if err == nil || err.Error() != "blob trust policy document uses unsupported version \"invalid\"" {
t.Fatalf("invalid version should return error")
}
// No Policy Statements
policyDoc = dummyBlobPolicyDocument()
policyDoc.TrustPolicies = nil
err = policyDoc.Validate()
if err == nil || err.Error() != "blob trust policy document can not have zero trust policy statements" {
t.Fatalf("zero policy statements should return error")
}
// No Policy Statement Name
policyDoc = dummyBlobPolicyDocument()
policyDoc.TrustPolicies[0].Name = ""
err = policyDoc.Validate()
if err == nil || err.Error() != "blob trust policy: a trust policy statement is missing a name, every statement requires a name" {
t.Fatalf("policy statement with no name should return an error")
}
// multiple global rust policy
policyDoc = dummyBlobPolicyDocument()
policyStatement1 := policyDoc.TrustPolicies[0].clone()
policyStatement1.GlobalPolicy = true
policyStatement2 := policyDoc.TrustPolicies[0].clone()
policyStatement2.Name = "test-statement-name-2"
policyStatement2.GlobalPolicy = true
policyDoc.TrustPolicies = []BlobTrustPolicy{*policyStatement1, *policyStatement2}
err = policyDoc.Validate()
if err == nil || err.Error() != "multiple blob trust policy statements have globalPolicy set to true. Only one trust policy statement can be marked as global policy" {
t.Error(err)
t.Fatalf("multiple global blob policy should return error")
}
// Policy Document with duplicate policy statement names
policyDoc = dummyBlobPolicyDocument()
policyStatement1 = policyDoc.TrustPolicies[0].clone()
policyStatement2 = policyDoc.TrustPolicies[0].clone()
policyDoc.TrustPolicies = []BlobTrustPolicy{*policyStatement1, *policyStatement2}
err = policyDoc.Validate()
if err == nil || err.Error() != "multiple blob trust policy statements use the same name \"test-statement-name\", statement names must be unique" {
t.Fatalf("policy statements with same name should return error")
}
}
func TestGetApplicableTrustPolicy(t *testing.T) {
policyDoc := dummyBlobPolicyDocument()
policyStatement := policyDoc.TrustPolicies[0].clone()
policyStatement1 := policyStatement.clone()
policyStatement1.Name = "test-statement-name-1"
policyStatement1.GlobalPolicy = true
policyStatement2 := policyStatement.clone()
policyStatement2.Name = "test-statement-name-2"
policyDoc.TrustPolicies = []BlobTrustPolicy{*policyStatement, *policyStatement1, *policyStatement2}
validateGetApplicableTrustPolicy(t, policyDoc, "test-statement-name-2", policyStatement2)
validateGetApplicableTrustPolicy(t, policyDoc, "test-statement-name", policyStatement)
}
func TestGetApplicableTrustPolicy_Error(t *testing.T) {
policyDoc := dummyBlobPolicyDocument()
t.Run("empty policy name", func(t *testing.T) {
_, err := policyDoc.GetApplicableTrustPolicy("")
if err == nil || err.Error() != "policy name cannot be empty" {
t.Fatalf("GetApplicableTrustPolicy() returned error: %v", err)
}
})
t.Run("non existent policy name", func(t *testing.T) {
_, err := policyDoc.GetApplicableTrustPolicy("blaah")
if err == nil || err.Error() != "no applicable blob trust policy with name \"blaah\"" {
t.Fatalf("GetApplicableTrustPolicy() returned error: %v", err)
}
})
}
func TestGetGlobalTrustPolicy(t *testing.T) {
policyDoc := dummyBlobPolicyDocument()
policyDoc.TrustPolicies[0].GlobalPolicy = true
policy, err := policyDoc.GetGlobalTrustPolicy()
if err != nil {
t.Fatalf("GetGlobalTrustPolicy() returned error: %v", err)
}
if !reflect.DeepEqual(*policy, policyDoc.TrustPolicies[0]) {
t.Fatalf("GetGlobalTrustPolicy() returned unexpected policy")
}
}
func validateGetApplicableTrustPolicy(t *testing.T, policyDoc BlobDocument, policyName string, expectedPolicy *BlobTrustPolicy) {
policy, err := policyDoc.GetApplicableTrustPolicy(policyName)
if err != nil {
t.Fatalf("GetApplicableTrustPolicy() returned error: %v", err)
}
if reflect.DeepEqual(policy, *expectedPolicy) {
t.Fatalf("GetApplicableTrustPolicy() returned unexpected policy for %s", policyName)
}
}

View File

@ -25,20 +25,23 @@ import (
"github.com/notaryproject/notation-go/internal/trustpolicy" "github.com/notaryproject/notation-go/internal/trustpolicy"
) )
// OCIDocument represents a trustPolicy.json document for OCI artifacts // Document represents a trustpolicy.json document
type OCIDocument struct { type Document struct {
// Version of the policy document // Version of the policy document
Version string `json:"version"` Version string `json:"version"`
// TrustPolicies include each policy statement // TrustPolicies include each policy statement
TrustPolicies []OCITrustPolicy `json:"trustPolicies"` TrustPolicies []TrustPolicy `json:"trustPolicies"`
} }
// OCITrustPolicy represents a policy statement in the policy document for OCI artifacts // TrustPolicy represents a policy statement in the policy document
type OCITrustPolicy struct { type TrustPolicy struct {
// Name of the policy statement // Name of the policy statement
Name string `json:"name"` Name string `json:"name"`
// RegistryScopes that this policy statement affects
RegistryScopes []string `json:"registryScopes"`
// SignatureVerification setting for this policy statement // SignatureVerification setting for this policy statement
SignatureVerification SignatureVerification `json:"signatureVerification"` SignatureVerification SignatureVerification `json:"signatureVerification"`
@ -47,80 +50,49 @@ type OCITrustPolicy struct {
// TrustedIdentities this policy statement pins // TrustedIdentities this policy statement pins
TrustedIdentities []string `json:"trustedIdentities"` TrustedIdentities []string `json:"trustedIdentities"`
// RegistryScopes that this policy statement affects
RegistryScopes []string `json:"registryScopes"`
} }
// Document represents a trustPolicy.json document var supportedPolicyVersions = []string{"1.0"}
// Deprecated: Document exists for historical compatibility and should not be used.
// To create OCI Document, use OCIDocument.
type Document = OCIDocument
// TrustPolicy represents a policy statement in the policy document // LoadDocument retrieves a trust policy document from the local file system.
// Deprecated: TrustPolicy exists for historical compatibility and should not be used. func LoadDocument() (*Document, error) {
// To create OCI TrustPolicy, use OCITrustPolicy. var doc Document
type TrustPolicy = OCITrustPolicy
// LoadDocument loads a trust policy document from a local file system
// Deprecated: LoadDocument function exists for historical compatibility and should not be used.
// To load OCI Document, use LoadOCIDocument function.
var LoadDocument = LoadOCIDocument
var supportedOCIPolicyVersions = []string{"1.0"}
// LoadOCIDocument retrieves a trust policy document from the local file system.
// It attempts to read from dir.PathOCITrustPolicy first; if not found, it tries dir.PathTrustPolicy.
// If both dir.PathOCITrustPolicy and dir.PathTrustPolicy exist, dir.PathOCITrustPolicy will be read.
func LoadOCIDocument() (*OCIDocument, error) {
var doc OCIDocument
// attempt to load the document from dir.PathOCITrustPolicy
if err := getDocument(dir.PathOCITrustPolicy, &doc); err != nil {
// if the document is not found at the first path, try the second path
if errors.As(err, &errPolicyNotExist{}) {
if err := getDocument(dir.PathTrustPolicy, &doc); err != nil { if err := getDocument(dir.PathTrustPolicy, &doc); err != nil {
return nil, err return nil, err
} }
return &doc, nil
}
// if an error occurred other than the document not found, return it
return nil, err
}
return &doc, nil return &doc, nil
} }
// Validate validates a policy document according to its version's rule set. // Validate validates a policy document according to its version's rule set.
// if any rule is violated, returns an error // if any rule is violated, returns an error
func (policyDoc *OCIDocument) Validate() error { func (policyDoc *Document) Validate() error {
// sanity check // sanity check
if policyDoc == nil { if policyDoc == nil {
return errors.New("oci trust policy document cannot be nil") return errors.New("trust policy document cannot be nil")
} }
// Validate Version // Validate Version
if policyDoc.Version == "" { if policyDoc.Version == "" {
return errors.New("oci trust policy document has empty version, version must be specified") return errors.New("trust policy document has empty version, version must be specified")
} }
if !slices.Contains(supportedOCIPolicyVersions, policyDoc.Version) { if !slices.Contains(supportedPolicyVersions, policyDoc.Version) {
return fmt.Errorf("oci trust policy document uses unsupported version %q", policyDoc.Version) return fmt.Errorf("trust policy document uses unsupported version %q", policyDoc.Version)
} }
// Validate the policy according to 1.0 rules // Validate the policy according to 1.0 rules
if len(policyDoc.TrustPolicies) == 0 { if len(policyDoc.TrustPolicies) == 0 {
return errors.New("oci trust policy document can not have zero trust policy statements") return errors.New("trust policy document can not have zero trust policy statements")
} }
policyNames := set.New[string]() policyNames := set.New[string]()
for _, statement := range policyDoc.TrustPolicies { for _, statement := range policyDoc.TrustPolicies {
// Verify unique policy statement names across the policy document // Verify unique policy statement names across the policy document
if policyNames.Contains(statement.Name) { if policyNames.Contains(statement.Name) {
return fmt.Errorf("multiple oci trust policy statements use the same name %q, statement names must be unique", statement.Name) return fmt.Errorf("multiple trust policy statements use the same name %q, statement names must be unique", statement.Name)
} }
if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil { if err := validatePolicyCore(statement.Name, statement.SignatureVerification, statement.TrustStores, statement.TrustedIdentities); err != nil {
return fmt.Errorf("oci trust policy: %w", err) return fmt.Errorf("trust policy: %w", err)
} }
policyNames.Add(statement.Name) policyNames.Add(statement.Name)
@ -138,14 +110,14 @@ func (policyDoc *OCIDocument) Validate() error {
// statement that applies to the given registry scope. If no applicable trust // statement that applies to the given registry scope. If no applicable trust
// policy is found, returns an error // policy is found, returns an error
// see https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri // see https://github.com/notaryproject/notaryproject/blob/v1.0.0/specs/trust-store-trust-policy.md#selecting-a-trust-policy-based-on-artifact-uri
func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string) (*OCITrustPolicy, error) { func (policyDoc *Document) GetApplicableTrustPolicy(artifactReference string) (*TrustPolicy, error) {
artifactPath, err := getArtifactPathFromReference(artifactReference) artifactPath, err := getArtifactPathFromReference(artifactReference)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var wildcardPolicy *OCITrustPolicy var wildcardPolicy *TrustPolicy
var applicablePolicy *OCITrustPolicy var applicablePolicy *TrustPolicy
for _, policyStatement := range policyDoc.TrustPolicies { for _, policyStatement := range policyDoc.TrustPolicies {
if slices.Contains(policyStatement.RegistryScopes, trustpolicy.Wildcard) { if slices.Contains(policyStatement.RegistryScopes, trustpolicy.Wildcard) {
// we need to deep copy because we can't use the loop variable // we need to deep copy because we can't use the loop variable
@ -163,13 +135,13 @@ func (policyDoc *OCIDocument) GetApplicableTrustPolicy(artifactReference string)
} else if wildcardPolicy != nil { } else if wildcardPolicy != nil {
return wildcardPolicy, nil return wildcardPolicy, nil
} else { } else {
return nil, fmt.Errorf("artifact %q has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: %s", artifactReference, trustPolicyLink) return nil, fmt.Errorf("artifact %q has no applicable trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: %s", artifactReference, trustPolicyLink)
} }
} }
// clone returns a pointer to the deeply copied TrustPolicy // clone returns a pointer to the deeply copied TrustPolicy
func (t *OCITrustPolicy) clone() *OCITrustPolicy { func (t *TrustPolicy) clone() *TrustPolicy {
return &OCITrustPolicy{ return &TrustPolicy{
Name: t.Name, Name: t.Name,
SignatureVerification: t.SignatureVerification, SignatureVerification: t.SignatureVerification,
TrustedIdentities: append([]string(nil), t.TrustedIdentities...), TrustedIdentities: append([]string(nil), t.TrustedIdentities...),
@ -180,15 +152,15 @@ func (t *OCITrustPolicy) clone() *OCITrustPolicy {
// validateRegistryScopes validates if the policy document is following the // validateRegistryScopes validates if the policy document is following the
// Notary Project spec rules for registry scopes // Notary Project spec rules for registry scopes
func validateRegistryScopes(policyDoc *OCIDocument) error { func validateRegistryScopes(policyDoc *Document) error {
registryScopeCount := make(map[string]int) registryScopeCount := make(map[string]int)
for _, statement := range policyDoc.TrustPolicies { for _, statement := range policyDoc.TrustPolicies {
// Verify registry scopes are valid // Verify registry scopes are valid
if len(statement.RegistryScopes) == 0 { if len(statement.RegistryScopes) == 0 {
return fmt.Errorf("oci trust policy statement %q has zero registry scopes, it must specify registry scopes with at least one value", statement.Name) return fmt.Errorf("trust policy statement %q has zero registry scopes, it must specify registry scopes with at least one value", statement.Name)
} }
if len(statement.RegistryScopes) > 1 && slices.Contains(statement.RegistryScopes, trustpolicy.Wildcard) { if len(statement.RegistryScopes) > 1 && slices.Contains(statement.RegistryScopes, trustpolicy.Wildcard) {
return fmt.Errorf("oci trust policy statement %q uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values", statement.Name) return fmt.Errorf("trust policy statement %q uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values", statement.Name)
} }
for _, scope := range statement.RegistryScopes { for _, scope := range statement.RegistryScopes {
if scope != trustpolicy.Wildcard { if scope != trustpolicy.Wildcard {
@ -203,7 +175,7 @@ func validateRegistryScopes(policyDoc *OCIDocument) error {
// Verify one policy statement per registry scope // Verify one policy statement per registry scope
for key := range registryScopeCount { for key := range registryScopeCount {
if registryScopeCount[key] > 1 { if registryScopeCount[key] > 1 {
return fmt.Errorf("registry scope %q is present in multiple oci trust policy statements, one registry scope value can only be associated with one statement", key) return fmt.Errorf("registry scope %q is present in multiple trust policy statements, one registry scope value can only be associated with one statement", key)
} }
} }

View File

@ -23,47 +23,32 @@ import (
"github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/dir"
) )
func TestLoadOCIDocumentFromOldFileLocation(t *testing.T) { func TestLoadDocumentFromFileLocation(t *testing.T) {
tempRoot := t.TempDir() tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot dir.UserConfigDir = tempRoot
path := filepath.Join(tempRoot, "trustpolicy.json") path := filepath.Join(tempRoot, "trustpolicy.json")
policyJson, _ := json.Marshal(dummyOCIPolicyDocument()) policyJson, _ := json.Marshal(dummyPolicyDocument())
if err := os.WriteFile(path, policyJson, 0600); err != nil { if err := os.WriteFile(path, policyJson, 0600); err != nil {
t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err) t.Fatalf("TestLoadDocument write policy file failed. Error: %v", err)
} }
t.Cleanup(func() { os.RemoveAll(tempRoot) }) t.Cleanup(func() { os.RemoveAll(tempRoot) })
if _, err := LoadOCIDocument(); err != nil { if _, err := LoadDocument(); err != nil {
t.Fatalf("LoadOCIDocument() should not throw error for an existing policy file. Error: %v", err) t.Fatalf("LoadDocument() should not throw error for an existing policy file. Error: %v", err)
} }
} }
func TestLoadOCIDocumentFromNewFileLocation(t *testing.T) { func TestLoadDocumentError(t *testing.T) {
tempRoot := t.TempDir() tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot dir.UserConfigDir = tempRoot
path := filepath.Join(tempRoot, "trustpolicy.oci.json") if _, err := LoadDocument(); err == nil {
policyJson, _ := json.Marshal(dummyOCIPolicyDocument()) t.Fatalf("LoadDocument() should throw error if trust policy is not found")
if err := os.WriteFile(path, policyJson, 0600); err != nil {
t.Fatalf("TestLoadOCIDocument write policy file failed. Error: %v", err)
}
t.Cleanup(func() { os.RemoveAll(tempRoot) })
if _, err := LoadOCIDocument(); err != nil {
t.Fatalf("LoadOCIDocument() should not throw error for an existing policy file. Error: %v", err)
}
}
func TestLoadOCIDocumentError(t *testing.T) {
tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot
if _, err := LoadOCIDocument(); err == nil {
t.Fatalf("LoadOCIDocument() should throw error if OCI trust policy is not found")
} }
} }
// TestApplicableTrustPolicy tests filtering policies against registry scopes // TestApplicableTrustPolicy tests filtering policies against registry scopes
func TestApplicableTrustPolicy(t *testing.T) { func TestApplicableTrustPolicy(t *testing.T) {
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
policyStatement := policyDoc.TrustPolicies[0] policyStatement := policyDoc.TrustPolicies[0]
policyStatement.Name = "test-statement-name-1" policyStatement.Name = "test-statement-name-1"
@ -72,7 +57,7 @@ func TestApplicableTrustPolicy(t *testing.T) {
policyStatement.RegistryScopes = []string{registryScope} policyStatement.RegistryScopes = []string{registryScope}
policyStatement.SignatureVerification = SignatureVerification{VerificationLevel: "strict"} policyStatement.SignatureVerification = SignatureVerification{VerificationLevel: "strict"}
policyDoc.TrustPolicies = []OCITrustPolicy{ policyDoc.TrustPolicies = []TrustPolicy{
policyStatement, policyStatement,
} }
// existing Registry Scope // existing Registry Scope
@ -83,12 +68,12 @@ func TestApplicableTrustPolicy(t *testing.T) {
// non-existing Registry Scope // non-existing Registry Scope
policy, err = (&policyDoc).GetApplicableTrustPolicy("non.existing.scope/repo@sha256:hash") policy, err = (&policyDoc).GetApplicableTrustPolicy("non.existing.scope/repo@sha256:hash")
if policy != nil || err == nil || err.Error() != "artifact \"non.existing.scope/repo@sha256:hash\" has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" { if policy != nil || err == nil || err.Error() != "artifact \"non.existing.scope/repo@sha256:hash\" has no applicable trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy" {
t.Fatalf("GetApplicableTrustPolicy() should return nil for non existing registry scope") t.Fatalf("GetApplicableTrustPolicy() should return nil for non existing registry scope")
} }
// wildcard registry scope // wildcard registry scope
wildcardStatement := OCITrustPolicy{ wildcardStatement := TrustPolicy{
Name: "test-statement-name-2", Name: "test-statement-name-2",
SignatureVerification: SignatureVerification{VerificationLevel: "skip"}, SignatureVerification: SignatureVerification{VerificationLevel: "skip"},
TrustStores: []string{}, TrustStores: []string{},
@ -96,7 +81,7 @@ func TestApplicableTrustPolicy(t *testing.T) {
RegistryScopes: []string{"*"}, RegistryScopes: []string{"*"},
} }
policyDoc.TrustPolicies = []OCITrustPolicy{ policyDoc.TrustPolicies = []TrustPolicy{
policyStatement, policyStatement,
wildcardStatement, wildcardStatement,
} }
@ -110,130 +95,130 @@ func TestApplicableTrustPolicy(t *testing.T) {
// and tests various validations on policy elements // and tests various validations on policy elements
func TestValidateInvalidPolicyDocument(t *testing.T) { func TestValidateInvalidPolicyDocument(t *testing.T) {
// Sanity check // Sanity check
var nilPolicyDoc *OCIDocument var nilPolicyDoc *Document
err := nilPolicyDoc.Validate() err := nilPolicyDoc.Validate()
if err == nil || err.Error() != "oci trust policy document cannot be nil" { if err == nil || err.Error() != "trust policy document cannot be nil" {
t.Fatalf("nil policyDoc should return error") t.Fatalf("nil policyDoc should return error")
} }
// Invalid Version // Invalid Version
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
policyDoc.Version = "invalid" policyDoc.Version = "invalid"
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy document uses unsupported version \"invalid\"" { if err == nil || err.Error() != "trust policy document uses unsupported version \"invalid\"" {
t.Fatalf("invalid version should return error") t.Fatalf("invalid version should return error")
} }
// No Policy Statements // No Policy Statements
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies = nil policyDoc.TrustPolicies = nil
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy document can not have zero trust policy statements" { if err == nil || err.Error() != "trust policy document can not have zero trust policy statements" {
t.Fatalf("zero policy statements should return error") t.Fatalf("zero policy statements should return error")
} }
// No Policy Statement Name // No Policy Statement Name
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].Name = "" policyDoc.TrustPolicies[0].Name = ""
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: a trust policy statement is missing a name, every statement requires a name" { if err == nil || err.Error() != "trust policy: a trust policy statement is missing a name, every statement requires a name" {
t.Fatalf("policy statement with no name should return an error") t.Fatalf("policy statement with no name should return an error")
} }
// No Registry Scopes // No Registry Scopes
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].RegistryScopes = nil policyDoc.TrustPolicies[0].RegistryScopes = nil
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy statement \"test-statement-name\" has zero registry scopes, it must specify registry scopes with at least one value" { if err == nil || err.Error() != "trust policy statement \"test-statement-name\" has zero registry scopes, it must specify registry scopes with at least one value" {
t.Fatalf("policy statement with registry scopes should return error") t.Fatalf("policy statement with registry scopes should return error")
} }
// Multiple policy statements with same registry scope // Multiple policy statements with same registry scope
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyStatement1 := policyDoc.TrustPolicies[0].clone() policyStatement1 := policyDoc.TrustPolicies[0].clone()
policyStatement2 := policyDoc.TrustPolicies[0].clone() policyStatement2 := policyDoc.TrustPolicies[0].clone()
policyStatement2.Name = "test-statement-name-2" policyStatement2.Name = "test-statement-name-2"
policyDoc.TrustPolicies = []OCITrustPolicy{*policyStatement1, *policyStatement2} policyDoc.TrustPolicies = []TrustPolicy{*policyStatement1, *policyStatement2}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "registry scope \"registry.acme-rockets.io/software/net-monitor\" is present in multiple oci trust policy statements, one registry scope value can only be associated with one statement" { if err == nil || err.Error() != "registry scope \"registry.acme-rockets.io/software/net-monitor\" is present in multiple trust policy statements, one registry scope value can only be associated with one statement" {
t.Fatalf("Policy statements with same registry scope should return error %q", err) t.Fatalf("Policy statements with same registry scope should return error %q", err)
} }
// Registry scopes with a wildcard // Registry scopes with a wildcard
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].RegistryScopes = []string{"*", "registry.acme-rockets.io/software/net-monitor"} policyDoc.TrustPolicies[0].RegistryScopes = []string{"*", "registry.acme-rockets.io/software/net-monitor"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy statement \"test-statement-name\" uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values" { if err == nil || err.Error() != "trust policy statement \"test-statement-name\" uses wildcard registry scope '*', a wildcard scope cannot be used in conjunction with other scope values" {
t.Fatalf("policy statement with more than a wildcard registry scope should return error") t.Fatalf("policy statement with more than a wildcard registry scope should return error")
} }
// Invalid SignatureVerification // Invalid SignatureVerification
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "invalid"} policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "invalid"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: invalid signature verification level \"invalid\"" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: invalid signature verification level \"invalid\"" {
t.Fatalf("policy statement with invalid SignatureVerification should return error") t.Fatalf("policy statement with invalid SignatureVerification should return error")
} }
// Invalid SignatureVerification VerifyTimestamp // Invalid SignatureVerification VerifyTimestamp
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification.VerifyTimestamp = "invalid" 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\"" expectedErrMsg := "trust policy: trust policy statement \"test-statement-name\" has invalid signatureVerification: verifyTimestamp must be \"always\" or \"afterCertExpiry\", but got \"invalid\""
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != expectedErrMsg { if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err) t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
} }
// strict SignatureVerification should have a trust store // strict SignatureVerification should have a trust store
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustStores = []string{} policyDoc.TrustPolicies[0].TrustStores = []string{}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" {
t.Fatalf("strict SignatureVerification should have a trust store") t.Fatalf("strict SignatureVerification should have a trust store")
} }
// strict SignatureVerification should have trusted identities // strict SignatureVerification should have trusted identities
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustedIdentities = []string{} policyDoc.TrustPolicies[0].TrustedIdentities = []string{}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" is either missing trust stores or trusted identities, both must be specified" {
t.Fatalf("strict SignatureVerification should have trusted identities") t.Fatalf("strict SignatureVerification should have trusted identities")
} }
// skip SignatureVerification should not have trust store or trusted identities // skip SignatureVerification should not have trust store or trusted identities
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"} policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" is set to skip signature verification but configured with trust stores and/or trusted identities, remove them if signature verification needs to be skipped" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" is set to skip signature verification but configured with trust stores and/or trusted identities, remove them if signature verification needs to be skipped" {
t.Fatalf("strict SignatureVerification should have trusted identities") t.Fatalf("strict SignatureVerification should have trusted identities")
} }
// Empty Trusted Identity should throw error // Empty Trusted Identity should throw error
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustedIdentities = []string{""} policyDoc.TrustPolicies[0].TrustedIdentities = []string{""}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has an empty trusted identity" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has an empty trusted identity" {
t.Fatalf("policy statement with empty trusted identity should return error") t.Fatalf("policy statement with empty trusted identity should return error")
} }
// Trusted Identity without separator should throw error // Trusted Identity without separator should throw error
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"} policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator" {
t.Fatalf("policy statement with trusted identity missing separator should return error") t.Fatalf("policy statement with trusted identity missing separator should return error")
} }
// Empty Trusted Identity value should throw error // Empty Trusted Identity value should throw error
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"} policyDoc.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value" {
t.Fatalf("policy statement with trusted identity missing identity value should return error") t.Fatalf("policy statement with trusted identity missing identity value should return error")
} }
// trust store/trusted identities are optional for skip SignatureVerification // trust store/trusted identities are optional for skip SignatureVerification
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"} policyDoc.TrustPolicies[0].SignatureVerification = SignatureVerification{VerificationLevel: "skip"}
policyDoc.TrustPolicies[0].TrustStores = []string{} policyDoc.TrustPolicies[0].TrustStores = []string{}
policyDoc.TrustPolicies[0].TrustedIdentities = []string{} policyDoc.TrustPolicies[0].TrustedIdentities = []string{}
@ -243,52 +228,52 @@ func TestValidateInvalidPolicyDocument(t *testing.T) {
} }
// Trust Store missing separator // Trust Store missing separator
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustStores = []string{"ca"} policyDoc.TrustPolicies[0].TrustStores = []string{"ca"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" has malformed trust store value \"ca\". The required format is <TrustStoreType>:<TrustStoreName>" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" has malformed trust store value \"ca\". The required format is <TrustStoreType>:<TrustStoreName>" {
t.Fatalf("policy statement with trust store missing separator should return error") t.Fatalf("policy statement with trust store missing separator should return error")
} }
// Invalid Trust Store type // Invalid Trust Store type
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustStores = []string{"invalid:test-trust-store"} policyDoc.TrustPolicies[0].TrustStores = []string{"invalid:test-trust-store"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store type \"invalid\" in trust store value \"invalid:test-trust-store\"" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store type \"invalid\" in trust store value \"invalid:test-trust-store\"" {
t.Fatalf("policy statement with invalid trust store type should return error") t.Fatalf("policy statement with invalid trust store type should return error")
} }
// Empty Named Store // Empty Named Store
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustStores = []string{"ca:"} policyDoc.TrustPolicies[0].TrustStores = []string{"ca:"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store name \"\" in trust store value \"ca:\". Named store name needs to follow [a-zA-Z0-9_.-]+ format" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" uses an unsupported trust store name \"\" in trust store value \"ca:\". Named store name needs to follow [a-zA-Z0-9_.-]+ format" {
t.Fatalf("policy statement with trust store missing named store should return error") t.Fatalf("policy statement with trust store missing named store should return error")
} }
// trusted identities with a wildcard // trusted identities with a wildcard
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyDoc.TrustPolicies[0].TrustedIdentities = []string{"*", "test-identity"} policyDoc.TrustPolicies[0].TrustedIdentities = []string{"*", "test-identity"}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "oci trust policy: trust policy statement \"test-statement-name\" uses a wildcard trusted identity '*', a wildcard identity cannot be used in conjunction with other values" { if err == nil || err.Error() != "trust policy: trust policy statement \"test-statement-name\" uses a wildcard trusted identity '*', a wildcard identity cannot be used in conjunction with other values" {
t.Fatalf("policy statement with more than a wildcard trusted identity should return error") t.Fatalf("policy statement with more than a wildcard trusted identity should return error")
} }
// Policy Document with duplicate policy statement names // Policy Document with duplicate policy statement names
policyDoc = dummyOCIPolicyDocument() policyDoc = dummyPolicyDocument()
policyStatement1 = policyDoc.TrustPolicies[0].clone() policyStatement1 = policyDoc.TrustPolicies[0].clone()
policyStatement2 = policyDoc.TrustPolicies[0].clone() policyStatement2 = policyDoc.TrustPolicies[0].clone()
policyStatement2.RegistryScopes = []string{"registry.acme-rockets.io/software/legacy/metrics"} policyStatement2.RegistryScopes = []string{"registry.acme-rockets.io/software/legacy/metrics"}
policyDoc.TrustPolicies = []OCITrustPolicy{*policyStatement1, *policyStatement2} policyDoc.TrustPolicies = []TrustPolicy{*policyStatement1, *policyStatement2}
err = policyDoc.Validate() err = policyDoc.Validate()
if err == nil || err.Error() != "multiple oci trust policy statements use the same name \"test-statement-name\", statement names must be unique" { if err == nil || err.Error() != "multiple trust policy statements use the same name \"test-statement-name\", statement names must be unique" {
t.Fatalf("policy statements with same name should return error") t.Fatalf("policy statements with same name should return error")
} }
} }
// TestValidRegistryScopes tests valid scopes are accepted // TestValidRegistryScopes tests valid scopes are accepted
func TestValidRegistryScopes(t *testing.T) { func TestValidRegistryScopes(t *testing.T) {
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
validScopes := []string{ validScopes := []string{
"*", "example.com/rep", "example.com:8080/rep/rep2", "example.com/rep/subrep/subsub", "*", "example.com/rep", "example.com:8080/rep/rep2", "example.com/rep/subrep/subsub",
"10.10.10.10:8080/rep/rep2", "domain/rep", "domain:1234/rep", "10.10.10.10:8080/rep/rep2", "domain/rep", "domain:1234/rep",
@ -305,7 +290,7 @@ func TestValidRegistryScopes(t *testing.T) {
// TestInvalidRegistryScopes tests invalid scopes are rejected // TestInvalidRegistryScopes tests invalid scopes are rejected
func TestInvalidRegistryScopes(t *testing.T) { func TestInvalidRegistryScopes(t *testing.T) {
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
invalidScopes := []string{ invalidScopes := []string{
"", "1:1", "a,b", "abcd", "1111", "1,2", "example.com/rep:tag", "", "1:1", "a,b", "abcd", "1111", "1,2", "example.com/rep:tag",
"example.com/rep/subrep/sub:latest", "example.com", "rep/rep2:latest", "example.com/rep/subrep/sub:latest", "example.com", "rep/rep2:latest",
@ -333,7 +318,7 @@ func TestInvalidRegistryScopes(t *testing.T) {
// TestValidateValidPolicyDocument tests a happy policy document // TestValidateValidPolicyDocument tests a happy policy document
func TestValidateValidPolicyDocument(t *testing.T) { func TestValidateValidPolicyDocument(t *testing.T) {
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
policyStatement1 := policyDoc.TrustPolicies[0].clone() policyStatement1 := policyDoc.TrustPolicies[0].clone()
@ -376,7 +361,7 @@ func TestValidateValidPolicyDocument(t *testing.T) {
policyStatement8.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor8"} policyStatement8.RegistryScopes = []string{"registry.acme-rockets.io/software/net-monitor8"}
policyStatement8.SignatureVerification.VerifyTimestamp = OptionAfterCertExpiry policyStatement8.SignatureVerification.VerifyTimestamp = OptionAfterCertExpiry
policyDoc.TrustPolicies = []OCITrustPolicy{ policyDoc.TrustPolicies = []TrustPolicy{
*policyStatement1, *policyStatement1,
*policyStatement2, *policyStatement2,
*policyStatement3, *policyStatement3,

View File

@ -26,10 +26,10 @@ import (
"github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/dir"
) )
func dummyOCIPolicyDocument() OCIDocument { func dummyPolicyDocument() Document {
return OCIDocument{ return Document{
Version: "1.0", Version: "1.0",
TrustPolicies: []OCITrustPolicy{ TrustPolicies: []TrustPolicy{
{ {
Name: "test-statement-name", Name: "test-statement-name",
RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"}, RegistryScopes: []string{"registry.acme-rockets.io/software/net-monitor"},
@ -41,20 +41,6 @@ func dummyOCIPolicyDocument() OCIDocument {
} }
} }
func dummyBlobPolicyDocument() BlobDocument {
return BlobDocument{
Version: "1.0",
TrustPolicies: []BlobTrustPolicy{
{
Name: "test-statement-name",
SignatureVerification: SignatureVerification{VerificationLevel: "strict"},
TrustStores: []string{"ca:valid-trust-store", "signingAuthority:valid-trust-store"},
TrustedIdentities: []string{"x509.subject:CN=Notation Test Root,O=Notary,L=Seattle,ST=WA,C=US"},
},
},
}
}
// create testcase for validatePolicyCore method // create testcase for validatePolicyCore method
func TestValidatePolicyCore(t *testing.T) { func TestValidatePolicyCore(t *testing.T) {
policyName := "test-statement-name" policyName := "test-statement-name"
@ -306,22 +292,16 @@ func TestGetDocument(t *testing.T) {
t.Skip("skipping test on Windows") t.Skip("skipping test on Windows")
} }
dir.UserConfigDir = "/" dir.UserConfigDir = "/"
var ociDoc OCIDocument var doc Document
var blobDoc BlobDocument
tests := []struct { tests := []struct {
name string name string
expectedDocument any expectedDocument any
actualDocument any actualDocument any
}{ }{
{ {
name: "valid OCI policy file", name: "valid policy file",
expectedDocument: dummyOCIPolicyDocument(), expectedDocument: dummyPolicyDocument(),
actualDocument: &ociDoc, actualDocument: &doc,
},
{
name: "valid Blob policy file",
expectedDocument: dummyBlobPolicyDocument(),
actualDocument: &blobDoc,
}, },
} }
@ -345,7 +325,7 @@ func TestGetDocument(t *testing.T) {
func TestGetDocumentErrors(t *testing.T) { func TestGetDocumentErrors(t *testing.T) {
dir.UserConfigDir = "/" dir.UserConfigDir = "/"
t.Run("non-existing policy file", func(t *testing.T) { t.Run("non-existing policy file", func(t *testing.T) {
var doc OCIDocument var doc Document
if err := getDocument("blaah", &doc); err == nil || err.Error() != fmt.Sprintf("trust policy is not present. To create a trust policy, see: %s", trustPolicyLink) { if err := getDocument("blaah", &doc); err == nil || err.Error() != fmt.Sprintf("trust policy is not present. To create a trust policy, see: %s", trustPolicyLink) {
t.Fatalf("getDocument() should throw error for non existent policy") t.Fatalf("getDocument() should throw error for non existent policy")
} }
@ -362,7 +342,7 @@ func TestGetDocumentErrors(t *testing.T) {
} }
t.Cleanup(func() { os.RemoveAll(tempRoot) }) t.Cleanup(func() { os.RemoveAll(tempRoot) })
var doc OCIDocument var doc Document
if err := getDocument(path, &doc); err == nil || err.Error() != fmt.Sprintf("malformed trust policy. To create a trust policy, see: %s", trustPolicyLink) { if err := getDocument(path, &doc); err == nil || err.Error() != fmt.Sprintf("malformed trust policy. To create a trust policy, see: %s", trustPolicyLink) {
t.Fatalf("getDocument() should throw error for invalid policy file. Error: %v", err) t.Fatalf("getDocument() should throw error for invalid policy file. Error: %v", err)
} }
@ -379,7 +359,7 @@ func TestGetDocumentErrors(t *testing.T) {
t.Fatalf("creation of invalid permission policy file failed. Error: %v", err) t.Fatalf("creation of invalid permission policy file failed. Error: %v", err)
} }
expectedErrMsg := fmt.Sprintf("unable to read trust policy due to file permissions, please verify the permissions of %s", path) expectedErrMsg := fmt.Sprintf("unable to read trust policy due to file permissions, please verify the permissions of %s", path)
var doc OCIDocument var doc Document
if err := getDocument(path, &doc); err == nil || err.Error() != expectedErrMsg { if err := getDocument(path, &doc); err == nil || err.Error() != expectedErrMsg {
t.Errorf("getDocument() should throw error for a policy file with bad permissions. "+ t.Errorf("getDocument() should throw error for a policy file with bad permissions. "+
"Expected error: '%v'qq but found '%v'", expectedErrMsg, err.Error()) "Expected error: '%v'qq but found '%v'", expectedErrMsg, err.Error())
@ -400,7 +380,7 @@ func TestGetDocumentErrors(t *testing.T) {
if err := os.Symlink(path, symlinkPath); err != nil { if err := os.Symlink(path, symlinkPath); err != nil {
t.Fatalf("creation of symlink for policy file failed. Error: %v", err) t.Fatalf("creation of symlink for policy file failed. Error: %v", err)
} }
var doc OCIDocument var doc Document
if err := getDocument(symlinkPath, &doc); err == nil || !strings.HasPrefix(err.Error(), "trust policy is not a regular file (symlinks are not supported)") { if err := getDocument(symlinkPath, &doc); err == nil || !strings.HasPrefix(err.Error(), "trust policy is not a regular file (symlinks are not supported)") {
t.Fatalf("getDocument() should throw error for a symlink policy file. Error: %v", err) t.Fatalf("getDocument() should throw error for a symlink policy file. Error: %v", err)
} }

View File

@ -15,6 +15,7 @@
package truststore package truststore
import ( import (
"bytes"
"context" "context"
"crypto/x509" "crypto/x509"
"errors" "errors"
@ -106,6 +107,14 @@ func (trustStore *x509TrustStore) GetCertificates(ctx context.Context, storeType
if err := ValidateCertificates(certs); err != nil { if err := ValidateCertificates(certs); err != nil {
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("failed to validate the trusted certificate %s in trust store %s of type %s", certFileName, namedStore, storeType)} return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("failed to validate the trusted certificate %s in trust store %s of type %s", certFileName, namedStore, storeType)}
} }
// we require TSA certificates in trust store to be root CA certificates
if storeType == TypeTSA {
for _, cert := range certs {
if err := isRootCACertificate(cert); err != nil {
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("trusted certificate %s in trust store %s of type %s is invalid: %v", certFileName, namedStore, storeType, err.Error())}
}
}
}
certificates = append(certificates, certs...) certificates = append(certificates, certs...)
} }
if len(certificates) < 1 { if len(certificates) < 1 {
@ -137,3 +146,14 @@ func ValidateCertificates(certs []*x509.Certificate) error {
func isValidStoreType(storeType Type) bool { func isValidStoreType(storeType Type) bool {
return slices.Contains(Types, storeType) return slices.Contains(Types, storeType)
} }
// isRootCACertificate returns nil if cert is a root CA certificate
func isRootCACertificate(cert *x509.Certificate) error {
if err := cert.CheckSignatureFrom(cert); err != nil {
return fmt.Errorf("certificate with subject %q is not a root CA certificate: %w", cert.Subject, err)
}
if !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
return fmt.Errorf("certificate with subject %q is not a root CA certificate: issuer (%s) and subject (%s) are not the same", cert.Subject, cert.Issuer, cert.Subject)
}
return nil
}

View File

@ -98,3 +98,31 @@ func TestValidateCertsWithLeafCert(t *testing.T) {
t.Fatalf("leaf cert in a trust store should return error %q, got: %v", expectedErr, err) t.Fatalf("leaf cert in a trust store should return error %q, got: %v", expectedErr, err)
} }
} }
func TestGetCertFromValidTsaTrustStore(t *testing.T) {
// testing ../testdata/truststore/x509/tsa/test-nonCA/globalsignRoot.cer
_, err := trustStore.GetCertificates(context.Background(), "tsa", "test-timestamp")
if err != nil {
t.Fatalf("expected nil error, but got %s", err)
}
}
func TestGetCertFromInvalidTsaTrustStore(t *testing.T) {
t.Run("non CA certificate", func(t *testing.T) {
// testing ../testdata/truststore/x509/tsa/test-nonCA/wabbit-networks.io
expectedErrMsg := `trusted certificate wabbit-networks.io.crt in trust store test-nonCA of type tsa is invalid: certificate with subject "CN=wabbit-networks.io,O=Notary,L=Seattle,ST=WA,C=US" is not a root CA certificate: x509: invalid signature: parent certificate cannot sign this kind of certificate`
_, err := trustStore.GetCertificates(context.Background(), "tsa", "test-nonCA")
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error: %s, but got %s", expectedErrMsg, err)
}
})
t.Run("not self-issued", func(t *testing.T) {
//testing ../testdata/truststore/x509/tsa/test-nonSelfIssued/nonSelfIssued.crt
expectedErrMsg := `trusted certificate nonSelfIssued.crt in trust store test-nonSelfIssued of type tsa is invalid: certificate with subject "CN=Notation Test Revokable RSA Chain Cert 2,O=Notary,L=Seattle,ST=WA,C=US" is not a root CA certificate: issuer (CN=Notation Test Revokable RSA Chain Cert Root,O=Notary,L=Seattle,ST=WA,C=US) and subject (CN=Notation Test Revokable RSA Chain Cert 2,O=Notary,L=Seattle,ST=WA,C=US) are not the same`
_, err := trustStore.GetCertificates(context.Background(), "tsa", "test-nonSelfIssued")
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected error: %s, but got %s", expectedErrMsg, err)
}
})
}

View File

@ -16,7 +16,6 @@ package verifier
import ( import (
"context" "context"
"crypto"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"errors" "errors"
@ -47,20 +46,12 @@ import (
"github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation-go/verifier/truststore"
pluginframework "github.com/notaryproject/notation-plugin-framework-go/plugin" pluginframework "github.com/notaryproject/notation-plugin-framework-go/plugin"
"github.com/notaryproject/tspclient-go" "github.com/notaryproject/tspclient-go"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
var algorithms = map[crypto.Hash]digest.Algorithm{ // verifier implements notation.Verifier and notation.verifySkipper
crypto.SHA256: digest.SHA256,
crypto.SHA384: digest.SHA384,
crypto.SHA512: digest.SHA512,
}
// verifier implements notation.Verifier, notation.BlobVerifier and notation.verifySkipper
type verifier struct { type verifier struct {
ociTrustPolicyDoc *trustpolicy.OCIDocument trustPolicyDoc *trustpolicy.Document
blobTrustPolicyDoc *trustpolicy.BlobDocument
trustStore truststore.X509TrustStore trustStore truststore.X509TrustStore
pluginManager plugin.Manager pluginManager plugin.Manager
revocationClient revocation.Revocation revocationClient revocation.Revocation
@ -69,7 +60,7 @@ type verifier struct {
} }
// VerifierOptions specifies additional parameters that can be set when using // VerifierOptions specifies additional parameters that can be set when using
// the NewVerifierWithOptions constructor // the NewWithOptions constructor
type VerifierOptions struct { type VerifierOptions struct {
// RevocationClient is an implementation of revocation.Revocation to use for // RevocationClient is an implementation of revocation.Revocation to use for
// verifying revocation of code signing certificate chain // verifying revocation of code signing certificate chain
@ -88,68 +79,40 @@ type VerifierOptions struct {
RevocationTimestampingValidator revocation.Validator RevocationTimestampingValidator revocation.Validator
} }
// NewOCIVerifierFromConfig returns a OCI verifier based on local file system // NewFromConfig returns a verifier based on local file system.
func NewOCIVerifierFromConfig() (*verifier, error) { func NewFromConfig() (notation.Verifier, error) {
// load trust policy // load trust policy
policyDocument, err := trustpolicy.LoadOCIDocument() policyDocument, err := trustpolicy.LoadDocument()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// load trust store // load trust store
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
return NewVerifier(policyDocument, nil, x509TrustStore, plugin.NewCLIManager(dir.PluginFS())) return New(policyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()))
} }
// NewBlobVerifierFromConfig returns a Blob verifier based on local file system // New creates a new verifier given trustPolicy, trustStore and pluginManager
func NewBlobVerifierFromConfig() (*verifier, error) { func New(trustPolicy *trustpolicy.Document, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) {
// load trust policy return NewWithOptions(trustPolicy, trustStore, pluginManager, VerifierOptions{})
policyDocument, err := trustpolicy.LoadBlobDocument()
if err != nil {
return nil, err
}
// load trust store
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
return NewVerifier(nil, policyDocument, x509TrustStore, plugin.NewCLIManager(dir.PluginFS()))
} }
// NewWithOptions creates a new verifier given ociTrustPolicy, trustStore, // NewWithOptions creates a new verifier given trustPolicy, trustStore,
// pluginManager, and VerifierOptions. // pluginManager, and verifierOptions
// func NewWithOptions(trustPolicy *trustpolicy.Document, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, verifierOptions VerifierOptions) (notation.Verifier, error) {
// Deprecated: NewWithOptions function exists for historical compatibility and should not be used.
// To create verifier, use NewVerifierWithOptions function.
func NewWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, opts VerifierOptions) (notation.Verifier, error) {
return NewVerifierWithOptions(ociTrustPolicy, nil, trustStore, pluginManager, opts)
}
// NewVerifier creates a new verifier given ociTrustPolicy, trustStore and pluginManager
func NewVerifier(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPolicy *trustpolicy.BlobDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (*verifier, error) {
return NewVerifierWithOptions(ociTrustPolicy, blobTrustPolicy, trustStore, pluginManager, VerifierOptions{})
}
// NewVerifierWithOptions creates a new verifier given ociTrustPolicy, blobTrustPolicy,
// trustStore, pluginManager, and verifierOptions
func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPolicy *trustpolicy.BlobDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager, verifierOptions VerifierOptions) (*verifier, error) {
if trustStore == nil { if trustStore == nil {
return nil, errors.New("trustStore cannot be nil") return nil, errors.New("trustStore cannot be nil")
} }
if ociTrustPolicy == nil && blobTrustPolicy == nil {
return nil, errors.New("ociTrustPolicy and blobTrustPolicy both cannot be nil") if trustPolicy == nil {
return nil, errors.New("trustPolicy cannot be nil")
} }
if ociTrustPolicy != nil {
if err := ociTrustPolicy.Validate(); err != nil { if err := trustPolicy.Validate(); err != nil {
return nil, err return nil, err
} }
}
if blobTrustPolicy != nil {
if err := blobTrustPolicy.Validate(); err != nil {
return nil, err
}
}
v := &verifier{ v := &verifier{
ociTrustPolicyDoc: ociTrustPolicy, trustPolicyDoc: trustPolicy,
blobTrustPolicyDoc: blobTrustPolicy,
trustStore: trustStore, trustStore: trustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
} }
@ -160,22 +123,6 @@ func NewVerifierWithOptions(ociTrustPolicy *trustpolicy.OCIDocument, blobTrustPo
return v, nil return v, nil
} }
// NewFromConfig returns a OCI verifier based on local file system.
//
// Deprecated: NewFromConfig function exists for historical compatibility and should not be used.
// To create an OCI verifier, use NewOCIVerifierFromConfig function.
func NewFromConfig() (notation.Verifier, error) {
return NewOCIVerifierFromConfig()
}
// New creates a new verifier given ociTrustPolicy, trustStore and pluginManager.
//
// Deprecated: New function exists for historical compatibility and should not be used.
// To create verifier, use NewVerifier function.
func New(ociTrustPolicy *trustpolicy.OCIDocument, trustStore truststore.X509TrustStore, pluginManager plugin.Manager) (notation.Verifier, error) {
return NewVerifier(ociTrustPolicy, nil, trustStore, pluginManager)
}
// setRevocation sets revocation validators of v // setRevocation sets revocation validators of v
func (v *verifier) setRevocation(verifierOptions VerifierOptions) error { func (v *verifier) setRevocation(verifierOptions VerifierOptions) error {
// timestamping validator // timestamping validator
@ -221,7 +168,7 @@ func (v *verifier) SkipVerify(ctx context.Context, opts notation.VerifierVerifyO
logger := log.GetLogger(ctx) logger := log.GetLogger(ctx)
logger.Debugf("Check verification level against artifact %v", opts.ArtifactReference) logger.Debugf("Check verification level against artifact %v", opts.ArtifactReference)
trustPolicy, err := v.ociTrustPolicyDoc.GetApplicableTrustPolicy(opts.ArtifactReference) trustPolicy, err := v.trustPolicyDoc.GetApplicableTrustPolicy(opts.ArtifactReference)
if err != nil { if err != nil {
return false, nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()} return false, nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
} }
@ -238,87 +185,6 @@ func (v *verifier) SkipVerify(ctx context.Context, opts notation.VerifierVerifyO
return false, verificationLevel, nil return false, verificationLevel, nil
} }
// VerifyBlob verifies the signature of given blob , and returns the outcome upon
// successful verification.
func (v *verifier) VerifyBlob(ctx context.Context, descGenFunc notation.BlobDescriptorGenerator, signature []byte, opts notation.BlobVerifierVerifyOptions) (*notation.VerificationOutcome, error) {
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")
}
var trustPolicy *trustpolicy.BlobTrustPolicy
var err error
if opts.TrustPolicyName == "" {
trustPolicy, err = v.blobTrustPolicyDoc.GetGlobalTrustPolicy()
} else {
trustPolicy, err = v.blobTrustPolicyDoc.GetApplicableTrustPolicy(opts.TrustPolicyName)
}
if err != nil {
return nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
}
logger.Infof("Trust policy configuration: %+v", trustPolicy)
// ignore the error since we already validated the policy document
verificationLevel, _ := trustPolicy.SignatureVerification.GetVerificationLevel()
outcome := &notation.VerificationOutcome{
RawSignature: signature,
VerificationLevel: verificationLevel,
}
// verificationLevel is skip
if reflect.DeepEqual(verificationLevel, trustpolicy.LevelSkip) {
logger.Debug("Skipping signature verification")
return outcome, nil
}
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
}
payload := &envelope.Payload{}
err = json.Unmarshal(outcome.EnvelopeContent.Payload.Content, payload)
if err != nil {
logger.Error("Failed to unmarshal the payload content in the signature blob to envelope.Payload")
outcome.Error = err
return outcome, err
}
cryptoHash := outcome.EnvelopeContent.SignerInfo.SignatureAlgorithm.Hash()
digestAlgo, ok := algorithms[cryptoHash]
if !ok {
logger.Error("Unsupported hashing algorithm: %v", cryptoHash)
err := fmt.Errorf("unsupported hashing algorithm: %v", cryptoHash)
outcome.Error = err
return outcome, err
}
desc, err := descGenFunc(digestAlgo)
if err != nil {
errMsg := fmt.Sprintf("failed to generate descriptor for given artifact. Error: %s", err)
logger.Error(errMsg)
descErr := errors.New(errMsg)
outcome.Error = descErr
return outcome, descErr
}
if desc.Digest != payload.TargetArtifact.Digest || desc.Size != payload.TargetArtifact.Size ||
(desc.MediaType != "" && desc.MediaType != payload.TargetArtifact.MediaType) {
logger.Infof("payload present in the signature: %+v", payload.TargetArtifact)
logger.Infof("payload derived from the blob: %+v", desc)
outcome.Error = errors.New("integrity check failed. signature does not match the given blob")
}
if len(opts.UserMetadata) > 0 {
err := verifyUserMetadata(logger, payload, opts.UserMetadata)
if err != nil {
outcome.Error = err
}
}
return outcome, outcome.Error
}
// Verify verifies the signature associated the target OCI // Verify verifies the signature associated the target OCI
// artifact with manifest descriptor `desc`, and returns the outcome upon // artifact with manifest descriptor `desc`, and returns the outcome upon
// successful verification. // successful verification.
@ -331,11 +197,11 @@ func (v *verifier) Verify(ctx context.Context, desc ocispec.Descriptor, signatur
logger := log.GetLogger(ctx) logger := log.GetLogger(ctx)
logger.Debugf("Verify signature against artifact %v referenced as %s in signature media type %v", desc.Digest, artifactRef, envelopeMediaType) logger.Debugf("Verify signature against artifact %v referenced as %s in signature media type %v", desc.Digest, artifactRef, envelopeMediaType)
if v.ociTrustPolicyDoc == nil { if v.trustPolicyDoc == nil {
return nil, errors.New("ociTrustPolicyDoc is nil") return nil, errors.New("trustPolicyDoc is nil")
} }
trustPolicy, err := v.ociTrustPolicyDoc.GetApplicableTrustPolicy(artifactRef) trustPolicy, err := v.trustPolicyDoc.GetApplicableTrustPolicy(artifactRef)
if err != nil { if err != nil {
return nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()} return nil, notation.ErrorNoApplicableTrustPolicy{Msg: err.Error()}
} }
@ -407,7 +273,7 @@ func (v *verifier) processSignature(ctx context.Context, sigBlob []byte, envelop
var installedPlugin pluginframework.VerifyPlugin var installedPlugin pluginframework.VerifyPlugin
if verificationPluginName != "" { if verificationPluginName != "" {
logger.Debugf("Finding verification plugin %s", verificationPluginName) logger.Debugf("Finding verification plugin %q", verificationPluginName)
verificationPluginMinVersion, err := getVerificationPluginMinVersion(&outcome.EnvelopeContent.SignerInfo) verificationPluginMinVersion, err := getVerificationPluginMinVersion(&outcome.EnvelopeContent.SignerInfo)
if err != nil && err != errExtendedAttributeNotExist { if err != nil && err != errExtendedAttributeNotExist {
return notation.ErrorVerificationInconclusive{Msg: fmt.Sprintf("error while getting plugin minimum version, error: %s", err)} return notation.ErrorVerificationInconclusive{Msg: fmt.Sprintf("error while getting plugin minimum version, error: %s", err)}
@ -812,7 +678,7 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
certResult := certResults[i] certResult := certResults[i]
if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL { if certResult.RevocationMethod == revocationresult.RevocationMethodOCSPFallbackCRL {
// log the fallback warning // log the fallback warning
logger.Warnf("OCSP check failed with unknown error and fallback to CRL check for certificate #%d in chain with subject %v", (i + 1), cert.Subject.String()) logger.Warnf("OCSP check failed with unknown error and fallback to CRL check for certificate #%d in chain with subject %q", (i + 1), cert.Subject)
} }
for _, serverResult := range certResult.ServerResults { for _, serverResult := range certResult.ServerResults {
if serverResult.Error != nil { if serverResult.Error != nil {
@ -821,10 +687,10 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
// when the final revocation method is OCSPFallbackCRL, // when the final revocation method is OCSPFallbackCRL,
// the OCSP server results should not be logged as an error // the OCSP server results should not be logged as an error
// since the CRL revocation check can succeed. // since the CRL revocation check can succeed.
logger.Debugf("Certificate #%d in chain with subject %v encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject.String(), revocationresult.RevocationMethodOCSP, serverResult.Server, serverResult.Error) logger.Debugf("Certificate #%d in chain with subject %q encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject, revocationresult.RevocationMethodOCSP, serverResult.Server, serverResult.Error)
continue continue
} }
logger.Errorf("Certificate #%d in chain with subject %v encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject.String(), serverResult.RevocationMethod, serverResult.Server, serverResult.Error) logger.Errorf("Certificate #%d in chain with subject %q encountered an error for revocation method %s at URL %q: %v", (i + 1), cert.Subject, serverResult.RevocationMethod, serverResult.Server, serverResult.Error)
} }
} }
@ -838,6 +704,10 @@ func revocationFinalResult(certResults []*revocationresult.CertRevocationResult,
revokedCertSubject = problematicCertSubject revokedCertSubject = problematicCertSubject
} }
} }
if i < len(certResults)-1 && certResult.Result == revocationresult.ResultNonRevokable {
logger.Warnf("Certificate #%d in the chain with subject %q neither has an OCSP nor a CRL revocation method.", (i + 1), cert.Subject)
}
} }
if revokedFound { if revokedFound {
problematicCertSubject = revokedCertSubject problematicCertSubject = revokedCertSubject
@ -1018,7 +888,7 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
} }
// Performing timestamp verification // Performing timestamp verification
logger.Info("Performing timestamp verification...") logger.Debug("Performing timestamp verification...")
// 1. Timestamp countersignature MUST be present // 1. Timestamp countersignature MUST be present
logger.Debug("Checking timestamp countersignature existence...") logger.Debug("Checking timestamp countersignature existence...")
@ -1064,7 +934,7 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
if err := nx509.ValidateTimestampingCertChain(tsaCertChain); err != nil { if err := nx509.ValidateTimestampingCertChain(tsaCertChain); err != nil {
return fmt.Errorf("failed to validate the timestamping certificate chain with error: %w", err) return fmt.Errorf("failed to validate the timestamping certificate chain with error: %w", err)
} }
logger.Info("TSA identity is: ", tsaCertChain[0].Subject) logger.Debug("The subject of TSA signing certificate is: ", tsaCertChain[0].Subject)
// 4. Check the timestamp against the signing certificate chain // 4. Check the timestamp against the signing certificate chain
logger.Debug("Checking the timestamp against the signing certificate chain...") logger.Debug("Checking the timestamp against the signing certificate chain...")
@ -1076,6 +946,9 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
if !timestamp.BoundedBefore(cert.NotAfter) { 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)) return fmt.Errorf("timestamp can be after certificate %q validity period, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z))
} }
if timeOfVerification.After(cert.NotAfter) {
logger.Debugf("Certificate %q expired at %q, but timestamp is within certificate validity period", cert.Subject, cert.NotAfter.Format(time.RFC1123Z))
}
} }
// 5. Perform the timestamping certificate chain revocation check // 5. Perform the timestamping certificate chain revocation check
@ -1098,5 +971,6 @@ func verifyTimestamp(ctx context.Context, policyName string, trustStores []strin
} }
// success // success
logger.Debug("Timestamp verification: Success")
return nil return nil
} }

View File

@ -17,12 +17,12 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"reflect"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@ -32,10 +32,8 @@ import (
"github.com/notaryproject/notation-core-go/revocation" "github.com/notaryproject/notation-core-go/revocation"
"github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/revocation/result" "github.com/notaryproject/notation-core-go/revocation/result"
revocationresult "github.com/notaryproject/notation-core-go/revocation/result"
"github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature"
_ "github.com/notaryproject/notation-core-go/signature/cose" _ "github.com/notaryproject/notation-core-go/signature/cose"
"github.com/notaryproject/notation-core-go/signature/jws"
"github.com/notaryproject/notation-core-go/testhelper" "github.com/notaryproject/notation-core-go/testhelper"
corex509 "github.com/notaryproject/notation-core-go/x509" corex509 "github.com/notaryproject/notation-core-go/x509"
"github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go"
@ -47,45 +45,16 @@ import (
"github.com/notaryproject/notation-go/signer" "github.com/notaryproject/notation-go/signer"
"github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/notaryproject/notation-go/verifier/truststore" "github.com/notaryproject/notation-go/verifier/truststore"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
var testSig = `{"payload":"eyJ0YXJnZXRBcnRpZmFjdCI6eyJhbm5vdGF0aW9ucyI6eyJidWlsZElkIjoiMTAxIn0sImRpZ2VzdCI6InNoYTM4NDpiOGFiMjRkYWZiYTVjZjdlNGM4OWM1NjJmODExY2YxMDQ5M2Q0MjAzZGE5ODJkM2IxMzQ1ZjM2NmNhODYzZDljMmVkMzIzZGJkMGZiN2ZmODNhODAzMDJjZWZmYTVhNjEiLCJtZWRpYVR5cGUiOiJ2aWRlby9tcDQiLCJzaXplIjoxMn19","protected":"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDI0LTA0LTA0VDE1OjAzOjA2LTA3OjAwIn0","header":{"x5c":["MIIEbTCCAtWgAwIBAgICAK0wDQYJKoZIhvcNAQELBQAwZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3RhcnkxJTAjBgNVBAMTHE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwIBcNMjQwNDA0MjIwMzA1WhgPMjEyNDA0MDQyMjAzMDVaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90YXJ5MSUwIwYDVQQDExxOb3RhdGlvbiBFeGFtcGxlIHNlbGYtc2lnbmVkMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0dXD9UqzZcGlBlvPHO2uf+Sel/xwf/eOMS6Q30GV6JPeu9czLmyR0YMfC6P0N4zDzVYYZtQLkS5lalTMGX9A3yj9aXtXvtoYtLx2mF1CfdQJMcrT63wVVTWiPPe2JT8KHkkiACzVY6LTwc4s+DIAw9Gv21Uu6bFy4WWlGMp8UwTucR0JqaFoXzB6vxVRTkK8RRLM9Pj0hM5NwobpuZ+pc+ZS/7PhdvQHVzHeLLV9S7fHxw3n1c0ti8VUjSPSqCIEqOL3Eu/0pWMXB2A1xzn3RBfnzZMD3Tw3ksFgLMVzblhv41c6gr4cgjaS4wWwUvq9Xndd7Io8QNvxyiRDX5cHwQSEOmDfmegTIaLR0dKfvjY4ZJq8Y1DnaXU4RD6XeihtZykMlx7nTUyZZXpQ1akjh3VMzPykJ4mIknHh02zGRT9ZE8E1kYzRWhU/0MAzVrTTFHpric6jO459ouTnQXFjKwAcoD5+bNY6TuhC18iar7+l4BPPI1mFuqETnMfkkJQZAgMBAAGjJzAlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAYEAe5wyQPo+h1Yk2PkaA5aJKuU8azF2pTLfhQwAn/1XqPcmhNQuomOP0waoBsh+6sexfIDZaNuJ+zZUxqYHke/23+768SMiJCpuJfion3ak3Ka/IVNz48G0V+V+Vog+elkZzpdUQd30njLVcoQsihp0I/Gs3pnG2SeHmsdvYVuzycdYWTt5BFu4N8VWg4x4pfRMgDG7HGxRAacz2vTdqAx6rpWjO4xc0ZO8iUKjAeKHc7RuSx2dhUaRP9P8G8NBNtG6xNnbXIEjH6kP05srFRZ2jxm1an7sjsOpbBdIDztc0J+cb5yjBx7zo1OzWcmDUqMEXDR/WoygPzwhhHvWWvTqwVSEUvYnSaI6wxyHGxPFuX3+vCEZxU8NEGIuJtfYXWeo9cev5+PqjDgVu0uCWF53ZFsXNWbpff1qpG/CgrpFh3vN6uquMK9H5zaJBKr0GZFUsNRB1S8cUBgcjIZlWv3wrJQaOIFzF4RFO9dsYcG/b7ubdqSNGe4qfbsyuWf+1xsx"],"io.cncf.notary.signingAgent":"example signing agent"},"signature":"WMtF0u9GnQxJCpgrcxKZtNKNf3fvu2vnvOjd_2vQvjB4I9YKRYDQdr1q0AC0rU9b5aAGqP6Uh3jTbPkHHmOzGhXhRtidunfzOAeC6dPinR_RlnVMnVUY4cimZZG6Tg2tlgqGazgdzphnuZQpxUnK5mSInnWztXz_1-l_UJdPII49loJVE23hvWKDp8xOvMLftFXFlCYF9wE1ecTsYEAdrgB_XurFqbhhfeNcYie02aSMXfN0-ip9MHlIPhGrrOKLVm0w_S3nNBnuHHZ5lARgTm7tHtiNC0XxGCCk8qqteRZ4Vm2VM_UFMVOpdfh5KE_iTzmPCiHfNOJfgmvg5nysL1XUwGJ_KzCkPfY1Hq_4k73lia6RS6NSl1bSQ_s3uMBm3nx74WCmjK89RAihMIQ6s0PmUKQoWsIZ_5lWZ6uFW6LreoYyBFwvVVsSGSUx54-Gh76bwrt75va2VHpolSEXdhjcTK0KgscKLjU-LYDA_JD6AUaCi3WzMnpMSnO-9u_G"}` var policy = dummyPolicyDocument()
var trustedCert = `-----BEGIN CERTIFICATE----- var invalidPolicy = dummyInvalidPolicyDocument()
MIIEbTCCAtWgAwIBAgICAK0wDQYJKoZIhvcNAQELBQAwZDELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZOb3Rhcnkx
JTAjBgNVBAMTHE5vdGF0aW9uIEV4YW1wbGUgc2VsZi1zaWduZWQwIBcNMjQwNDA0
MjIwMzA1WhgPMjEyNDA0MDQyMjAzMDVaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJXQTEQMA4GA1UEBxMHU2VhdHRsZTEPMA0GA1UEChMGTm90YXJ5MSUwIwYDVQQD
ExxOb3RhdGlvbiBFeGFtcGxlIHNlbGYtc2lnbmVkMIIBojANBgkqhkiG9w0BAQEF
AAOCAY8AMIIBigKCAYEA0dXD9UqzZcGlBlvPHO2uf+Sel/xwf/eOMS6Q30GV6JPe
u9czLmyR0YMfC6P0N4zDzVYYZtQLkS5lalTMGX9A3yj9aXtXvtoYtLx2mF1CfdQJ
McrT63wVVTWiPPe2JT8KHkkiACzVY6LTwc4s+DIAw9Gv21Uu6bFy4WWlGMp8UwTu
cR0JqaFoXzB6vxVRTkK8RRLM9Pj0hM5NwobpuZ+pc+ZS/7PhdvQHVzHeLLV9S7fH
xw3n1c0ti8VUjSPSqCIEqOL3Eu/0pWMXB2A1xzn3RBfnzZMD3Tw3ksFgLMVzblhv
41c6gr4cgjaS4wWwUvq9Xndd7Io8QNvxyiRDX5cHwQSEOmDfmegTIaLR0dKfvjY4
ZJq8Y1DnaXU4RD6XeihtZykMlx7nTUyZZXpQ1akjh3VMzPykJ4mIknHh02zGRT9Z
E8E1kYzRWhU/0MAzVrTTFHpric6jO459ouTnQXFjKwAcoD5+bNY6TuhC18iar7+l
4BPPI1mFuqETnMfkkJQZAgMBAAGjJzAlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAYEAe5wyQPo+h1Yk2PkaA5aJ
KuU8azF2pTLfhQwAn/1XqPcmhNQuomOP0waoBsh+6sexfIDZaNuJ+zZUxqYHke/2
3+768SMiJCpuJfion3ak3Ka/IVNz48G0V+V+Vog+elkZzpdUQd30njLVcoQsihp0
I/Gs3pnG2SeHmsdvYVuzycdYWTt5BFu4N8VWg4x4pfRMgDG7HGxRAacz2vTdqAx6
rpWjO4xc0ZO8iUKjAeKHc7RuSx2dhUaRP9P8G8NBNtG6xNnbXIEjH6kP05srFRZ2
jxm1an7sjsOpbBdIDztc0J+cb5yjBx7zo1OzWcmDUqMEXDR/WoygPzwhhHvWWvTq
wVSEUvYnSaI6wxyHGxPFuX3+vCEZxU8NEGIuJtfYXWeo9cev5+PqjDgVu0uCWF53
ZFsXNWbpff1qpG/CgrpFh3vN6uquMK9H5zaJBKr0GZFUsNRB1S8cUBgcjIZlWv3w
rJQaOIFzF4RFO9dsYcG/b7ubdqSNGe4qfbsyuWf+1xsx
-----END CERTIFICATE-----`
var ociPolicy = dummyOCIPolicyDocument()
var blobPolicy = dummyBlobPolicyDocument()
var store = truststore.NewX509TrustStore(dir.ConfigFS()) var store = truststore.NewX509TrustStore(dir.ConfigFS())
var pm = mock.PluginManager{} var pm = mock.PluginManager{}
func TestNewVerifier_Error(t *testing.T) { func TestNewVerifier_Error(t *testing.T) {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
_, err := New(&policyDocument, nil, nil) _, err := New(&policyDocument, nil, nil)
expectedErr := errors.New("trustStore cannot be nil") expectedErr := errors.New("trustStore cannot be nil")
if err == nil || err.Error() != expectedErr.Error() { if err == nil || err.Error() != expectedErr.Error() {
@ -93,9 +62,30 @@ func TestNewVerifier_Error(t *testing.T) {
} }
} }
func TestNewFromConfig(t *testing.T) {
tempRoot := t.TempDir()
dir.UserConfigDir = tempRoot
expectedErrMsg := "trust policy is not present. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy"
_, err := NewFromConfig()
if err == nil || err.Error() != expectedErrMsg {
t.Fatalf("expected %s, but got %s", expectedErrMsg, err)
}
path := filepath.Join(tempRoot, "trustpolicy.json")
policyJson, _ := json.Marshal(dummyPolicyDocument())
if err := os.WriteFile(path, policyJson, 0600); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(tempRoot) })
_, err = NewFromConfig()
if err != nil {
t.Fatal(err)
}
}
func TestInvalidArtifactUriValidations(t *testing.T) { func TestInvalidArtifactUriValidations(t *testing.T) {
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &ociPolicy, trustPolicyDoc: &policy,
pluginManager: mock.PluginManager{}, pluginManager: mock.PluginManager{},
} }
@ -126,12 +116,12 @@ func TestInvalidArtifactUriValidations(t *testing.T) {
func TestErrorNoApplicableTrustPolicy_Error(t *testing.T) { func TestErrorNoApplicableTrustPolicy_Error(t *testing.T) {
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &ociPolicy, trustPolicyDoc: &policy,
pluginManager: mock.PluginManager{}, pluginManager: mock.PluginManager{},
} }
opts := notation.VerifierVerifyOptions{ArtifactReference: "non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333"} opts := notation.VerifierVerifyOptions{ArtifactReference: "non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333"}
_, err := verifier.Verify(context.Background(), ocispec.Descriptor{}, []byte{}, opts) _, err := verifier.Verify(context.Background(), ocispec.Descriptor{}, []byte{}, opts)
if !errors.Is(err, notation.ErrorNoApplicableTrustPolicy{Msg: "artifact \"non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333\" has no applicable oci trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy"}) { if !errors.Is(err, notation.ErrorNoApplicableTrustPolicy{Msg: "artifact \"non-existent-domain.com/repo@sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333\" has no applicable trust policy statement. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy"}) {
t.Fatalf("no applicable trust policy must throw error") t.Fatalf("no applicable trust policy must throw error")
} }
} }
@ -170,7 +160,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Unsupported Signature Envelope // Unsupported Signature Envelope
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
expectedErr := fmt.Errorf("unable to parse the digital signature, error : signature envelope format with media type \"application/unsupported+json\" is not supported") expectedErr := fmt.Errorf("unable to parse the digital signature, error : signature envelope format with media type \"application/unsupported+json\" is not supported")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
verificationType: trustpolicy.TypeIntegrity, verificationType: trustpolicy.TypeIntegrity,
@ -183,7 +173,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Integrity Success // Integrity Success
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
signatureBlob: validSigEnv, signatureBlob: validSigEnv,
verificationType: trustpolicy.TypeIntegrity, verificationType: trustpolicy.TypeIntegrity,
@ -195,7 +185,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Integrity Failure // Integrity Failure
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
expectedErr := fmt.Errorf("signature is invalid. Error: illegal base64 data at input byte 242") expectedErr := fmt.Errorf("signature is invalid. Error: illegal base64 data at input byte 242")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
signatureBlob: invalidSigEnv, signatureBlob: invalidSigEnv,
@ -209,7 +199,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Authenticity Success // Authenticity Success
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() // trust store is configured with the root certificate of the signature by default policyDocument := dummyPolicyDocument() // trust store is configured with the root certificate of the signature by default
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
signatureBlob: validSigEnv, signatureBlob: validSigEnv,
verificationType: trustpolicy.TypeAuthenticity, verificationType: trustpolicy.TypeAuthenticity,
@ -221,7 +211,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Authenticity Failure // Authenticity Failure
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority:valid-trust-store-2"} // trust store is not configured with the root certificate of the signature policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority:valid-trust-store-2"} // trust store is not configured with the root certificate of the signature
expectedErr := fmt.Errorf("signature is not produced by a trusted signer") expectedErr := fmt.Errorf("signature is not produced by a trusted signer")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
@ -236,7 +226,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Authenticity Failure with trust store missing separator // Authenticity Failure with trust store missing separator
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority"} policyDocument.TrustPolicies[0].TrustStores = []string{"ca:valid-trust-store-2", "signingAuthority"}
expectedErr := fmt.Errorf("error while loading the trust store, trust policy statement \"test-statement-name\" is missing separator in trust store value \"signingAuthority\". The required format is <TrustStoreType>:<TrustStoreName>") expectedErr := fmt.Errorf("error while loading the trust store, trust policy statement \"test-statement-name\" is missing separator in trust store value \"signingAuthority\". The required format is <TrustStoreType>:<TrustStoreName>")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
@ -251,7 +241,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// TrustedIdentity Failure // TrustedIdentity Failure
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:CN=LOL,O=DummyOrg,L=Hyderabad,ST=TG,C=IN"} // configure policy to not trust "CN=Notation Test Leaf Cert,O=Notary,L=Seattle,ST=WA,C=US" which is the subject of the signature's signing certificate policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:CN=LOL,O=DummyOrg,L=Hyderabad,ST=TG,C=IN"} // configure policy to not trust "CN=Notation Test Leaf Cert,O=Notary,L=Seattle,ST=WA,C=US" which is the subject of the signature's signing certificate
expectedErr := fmt.Errorf("signing certificate from the digital signature does not match the X.509 trusted identities [map[\"C\":\"IN\" \"CN\":\"LOL\" \"L\":\"Hyderabad\" \"O\":\"DummyOrg\" \"ST\":\"TG\"]] defined in the trust policy \"test-statement-name\"") expectedErr := fmt.Errorf("signing certificate from the digital signature does not match the X.509 trusted identities [map[\"C\":\"IN\" \"CN\":\"LOL\" \"L\":\"Hyderabad\" \"O\":\"DummyOrg\" \"ST\":\"TG\"]] defined in the trust policy \"test-statement-name\"")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
@ -266,7 +256,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// TrustedIdentity Failure without separator // TrustedIdentity Failure without separator
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"} policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject"}
expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator") expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject\" missing separator")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
@ -281,7 +271,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// TrustedIdentity Failure with empty value // TrustedIdentity Failure with empty value
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"} policyDocument.TrustPolicies[0].TrustedIdentities = []string{"x509.subject:"}
expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value") expectedErr := fmt.Errorf("trust policy statement \"test-statement-name\" has trusted identity \"x509.subject:\" without an identity value")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
@ -296,7 +286,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Expiry Success // Expiry Success
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
signatureBlob: validSigEnv, signatureBlob: validSigEnv,
verificationType: trustpolicy.TypeExpiry, verificationType: trustpolicy.TypeExpiry,
@ -308,7 +298,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
// Expiry Failure // Expiry Failure
for _, level := range verificationLevels { for _, level := range verificationLevels {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
expectedErr := fmt.Errorf("digital signature has expired on \"Fri, 29 Jul 2022 23:59:00 +0000\"") expectedErr := fmt.Errorf("digital signature has expired on \"Fri, 29 Jul 2022 23:59:00 +0000\"")
testCases = append(testCases, testCase{ testCases = append(testCases, testCase{
signatureBlob: expiredSigEnv, signatureBlob: expiredSigEnv,
@ -340,7 +330,7 @@ func assertNotationVerification(t *testing.T, scheme signature.SigningScheme) {
t.Fatalf("unexpected error while creating revocation object: %v", err) t.Fatalf("unexpected error while creating revocation object: %v", err)
} }
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &tt.policyDocument, trustPolicyDoc: &tt.policyDocument,
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()), trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -391,7 +381,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
t.Run("enforced revoked cert", func(t *testing.T) { t.Run("enforced revoked cert", func(t *testing.T) {
testedLevel := trustpolicy.LevelStrict testedLevel := trustpolicy.LevelStrict
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{ policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog, trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
@ -407,7 +397,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
dir.UserConfigDir = "testdata" dir.UserConfigDir = "testdata"
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &policyDoc, trustPolicyDoc: &policyDoc,
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()), trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -420,7 +410,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
}) })
t.Run("log revoked cert", func(t *testing.T) { t.Run("log revoked cert", func(t *testing.T) {
testedLevel := trustpolicy.LevelStrict testedLevel := trustpolicy.LevelStrict
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{ policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog, trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
@ -436,7 +426,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
dir.UserConfigDir = "testdata" dir.UserConfigDir = "testdata"
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &policyDoc, trustPolicyDoc: &policyDoc,
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()), trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -450,7 +440,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
}) })
t.Run("skip revoked cert", func(t *testing.T) { t.Run("skip revoked cert", func(t *testing.T) {
testedLevel := trustpolicy.LevelStrict testedLevel := trustpolicy.LevelStrict
policyDoc := dummyOCIPolicyDocument() policyDoc := dummyPolicyDocument()
policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name policyDoc.TrustPolicies[0].SignatureVerification.VerificationLevel = testedLevel.Name
policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{ policyDoc.TrustPolicies[0].SignatureVerification.Override = map[trustpolicy.ValidationType]trustpolicy.ValidationAction{
trustpolicy.TypeAuthenticity: trustpolicy.ActionLog, trustpolicy.TypeAuthenticity: trustpolicy.ActionLog,
@ -460,7 +450,7 @@ func TestVerifyRevocationEnvelope(t *testing.T) {
dir.UserConfigDir = "testdata" dir.UserConfigDir = "testdata"
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &policyDoc, trustPolicyDoc: &policyDoc,
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()), trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -712,86 +702,56 @@ func TestVerifyRevocation(t *testing.T) {
} }
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
if _, err := New(&ociPolicy, store, pm); err != nil { if _, err := New(&policy, store, pm); err != nil {
t.Fatalf("expected New constructor to succeed, but got %v", err) t.Fatalf("expected New constructor to succeed, but got %v", err)
} }
} }
func TestNewWithOptions(t *testing.T) { func TestNewWithOptions(t *testing.T) {
if _, err := NewWithOptions(&ociPolicy, store, pm, VerifierOptions{}); err != nil { if _, err := NewWithOptions(&policy, store, pm, VerifierOptions{}); err != nil {
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
} }
}
func TestNewVerifierWithOptions(t *testing.T) {
r, err := revocation.New(&http.Client{}) r, err := revocation.New(&http.Client{})
if err != nil { if err != nil {
t.Fatalf("unexpected error while creating revocation object: %v", err) t.Fatalf("unexpected error while creating revocation object: %v", err)
} }
opts := VerifierOptions{RevocationClient: r} opts := VerifierOptions{RevocationClient: r}
v, err := NewVerifierWithOptions(&ociPolicy, &blobPolicy, store, pm, opts) _, err = NewWithOptions(&policy, store, pm, opts)
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
}
if !(v.ociTrustPolicyDoc == &ociPolicy) {
t.Fatalf("expected ociTrustPolicyDoc %v, but got %v", v, v.ociTrustPolicyDoc)
}
if !(v.trustStore == store) {
t.Fatalf("expected trustStore %v, but got %v", store, v.trustStore)
}
if !reflect.DeepEqual(v.pluginManager, pm) {
t.Fatalf("expected pluginManager %v, but got %v", pm, v.pluginManager)
}
if v.revocationClient == nil {
t.Fatal("expected nonnil revocationClient")
}
if v.revocationCodeSigningValidator != nil {
t.Fatal("expected nil revocationCodeSigningValidator")
} }
_, err = NewVerifierWithOptions(nil, &blobPolicy, store, pm, opts) revocationCodeSigningValidator, err := revocation.NewWithOptions(revocation.Options{
if err != nil { OCSPHTTPClient: &http.Client{},
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) CertChainPurpose: purpose.CodeSigning,
} })
_, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts)
if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
}
opts.RevocationClient = nil
_, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts)
if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err)
}
csValidator, err := revocation.NewWithOptions(revocation.Options{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
opts = VerifierOptions{ revocationTimestampingValidator, err := revocation.NewWithOptions(revocation.Options{
RevocationCodeSigningValidator: csValidator, OCSPHTTPClient: &http.Client{},
} CertChainPurpose: purpose.Timestamping,
v, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts) })
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatal(err)
} }
if v.revocationCodeSigningValidator == nil { opts.RevocationCodeSigningValidator = revocationCodeSigningValidator
t.Fatal("expected v.revocationCodeSigningValidator to be non-nil") opts.RevocationTimestampingValidator = revocationTimestampingValidator
_, err = NewWithOptions(&policy, store, pm, opts)
if err != nil {
t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
} }
opts = VerifierOptions{} opts.RevocationClient = nil
v, err = NewVerifierWithOptions(&ociPolicy, nil, store, pm, opts) _, err = NewWithOptions(&policy, store, pm, opts)
if err != nil { if err != nil {
t.Fatalf("expected NewVerifierWithOptions constructor to succeed, but got %v", err) t.Fatalf("expected NewWithOptions constructor to succeed, but got %v", err)
}
if v.revocationCodeSigningValidator == nil {
t.Fatal("expected v.revocationCodeSigningValidator to be non-nil")
} }
} }
func TestNewVerifierWithOptionsError(t *testing.T) { func TestNewWithOptionsError(t *testing.T) {
r, err := revocation.New(&http.Client{}) r, err := revocation.New(&http.Client{})
if err != nil { if err != nil {
t.Fatalf("unexpected error while creating revocation object: %v", err) t.Fatalf("unexpected error while creating revocation object: %v", err)
@ -808,117 +768,23 @@ func TestNewVerifierWithOptionsError(t *testing.T) {
RevocationTimestampingValidator: rt, RevocationTimestampingValidator: rt,
} }
_, err = NewVerifierWithOptions(nil, nil, store, pm, opts) _, err = NewWithOptions(nil, store, pm, opts)
if err == nil || err.Error() != "ociTrustPolicy and blobTrustPolicy both cannot be nil" { expectedErrMsg := "trustPolicy cannot be nil"
t.Errorf("expected err but not found.") if err == nil || err.Error() != expectedErrMsg {
t.Errorf("expected %s, but got %s", expectedErrMsg, err)
} }
_, err = NewVerifierWithOptions(&ociPolicy, &blobPolicy, nil, pm, opts) _, err = NewWithOptions(&policy, nil, pm, opts)
if err == nil || err.Error() != "trustStore cannot be nil" { expectedErrMsg = "trustStore cannot be nil"
t.Errorf("expected err but not found.") if err == nil || err.Error() != expectedErrMsg {
} t.Errorf("expected %s, but got %s", expectedErrMsg, err)
}
func TestVerifyBlob(t *testing.T) {
policy := &trustpolicy.BlobDocument{
Version: "1.0",
TrustPolicies: []trustpolicy.BlobTrustPolicy{
{
Name: "blob-test-policy",
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
TrustStores: []string{"ca:dummy-ts"},
TrustedIdentities: []string{"*"},
},
},
}
v, err := NewVerifier(nil, policy, &testTrustStore{}, pm)
if err != nil {
t.Fatalf("unexpected error while creating verifier: %v", err)
} }
opts := notation.BlobVerifierVerifyOptions{ _, err = NewWithOptions(&invalidPolicy, store, pm, opts)
SignatureMediaType: jws.MediaTypeEnvelope, expectedErrMsg = "trust policy document has empty version, version must be specified"
TrustPolicyName: "blob-test-policy", if err == nil || err.Error() != expectedErrMsg {
t.Errorf("expected %s, but got %s", expectedErrMsg, err)
} }
descGenFunc := getTestDescGenFunc(false, "")
t.Run("without user defined metadata", func(t *testing.T) {
// verify with
if _, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts); err != nil {
t.Fatalf("VerifyBlob() returned unexpected error: %v", err)
}
})
t.Run("with user defined metadata", func(t *testing.T) {
opts.UserMetadata = map[string]string{"buildId": "101"}
if _, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts); err != nil {
t.Fatalf("VerifyBlob() with user metadata returned unexpected error: %v", err)
}
})
t.Run("trust policy set to skip", func(t *testing.T) {
policy.TrustPolicies[0].SignatureVerification = trustpolicy.SignatureVerification{VerificationLevel: "skip"}
opts.UserMetadata = map[string]string{"buildId": "101"}
if _, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts); err != nil {
t.Fatalf("VerifyBlob() with user metadata returned unexpected error: %v", err)
}
})
}
func TestVerifyBlob_Error(t *testing.T) {
policy := &trustpolicy.BlobDocument{
Version: "1.0",
TrustPolicies: []trustpolicy.BlobTrustPolicy{
{
Name: "blob-test-policy",
SignatureVerification: trustpolicy.SignatureVerification{VerificationLevel: "strict"},
TrustStores: []string{"ca:dummy-ts"},
TrustedIdentities: []string{"*"},
},
},
}
v, err := NewVerifier(nil, policy, &testTrustStore{}, pm)
if err != nil {
t.Fatalf("unexpected error while creating verifier: %v", err)
}
opts := notation.BlobVerifierVerifyOptions{
SignatureMediaType: jws.MediaTypeEnvelope,
TrustPolicyName: "blob-test-policy",
}
t.Run("BlobDescriptorGenerator returns error", func(t *testing.T) {
descGenFunc := getTestDescGenFunc(true, "")
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts)
if err == nil || err.Error() != "failed to generate descriptor for given artifact. Error: intentional test desc generation error" {
t.Errorf("VerifyBlob() didn't return error or didnt returned expected error: %v", err)
}
})
t.Run("descriptor mismatch returns error", func(t *testing.T) {
descGenFunc := getTestDescGenFunc(false, "sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a62")
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts)
if err == nil || err.Error() != "integrity check failed. signature does not match the given blob" {
t.Errorf("VerifyBlob() didn't return error or didnt returned expected error: %v", err)
}
})
t.Run("signature malformed returns error", func(t *testing.T) {
descGenFunc := getTestDescGenFunc(false, "")
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(""), opts)
if err == nil || err.Error() != "unable to parse the digital signature, error : unexpected end of JSON input" {
t.Errorf("VerifyBlob() didn't return error or didnt returned expected error: %v", err)
}
})
t.Run("user defined metadata mismatch returns error", func(t *testing.T) {
descGenFunc := getTestDescGenFunc(false, "")
opts.UserMetadata = map[string]string{"buildId": "zzz"}
_, err = v.VerifyBlob(context.Background(), descGenFunc, []byte(testSig), opts)
if err == nil || err.Error() != "unable to find specified metadata in the signature" {
t.Fatalf("VerifyBlob() with user metadata returned unexpected error: %v", err)
}
})
} }
func TestVerificationPluginInteractions(t *testing.T) { func TestVerificationPluginInteractions(t *testing.T) {
@ -934,7 +800,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
pluginSigEnv = mock.MockSaPluginSigEnv pluginSigEnv = mock.MockSaPluginSigEnv
} }
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
dir.UserConfigDir = "testdata" dir.UserConfigDir = "testdata"
x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS()) x509TrustStore := truststore.NewX509TrustStore(dir.ConfigFS())
@ -947,7 +813,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
t.Fatalf("unexpected error while creating revocation object: %v", err) t.Fatalf("unexpected error while creating revocation object: %v", err)
} }
v := verifier{ v := verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -963,7 +829,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
pluginManager.PluginCapabilities = []proto.Capability{proto.CapabilitySignatureGenerator} pluginManager.PluginCapabilities = []proto.Capability{proto.CapabilitySignatureGenerator}
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -987,7 +853,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1012,7 +878,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1036,7 +902,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1061,7 +927,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1088,7 +954,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1106,7 +972,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
pluginManager.PluginRunnerExecuteError = errors.New("revocation plugin should not be invoked when the trust policy skips revocation check") pluginManager.PluginRunnerExecuteError = errors.New("revocation plugin should not be invoked when the trust policy skips revocation check")
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1133,7 +999,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
pluginManager.PluginRunnerExecuteError = errors.New("invalid plugin response") pluginManager.PluginRunnerExecuteError = errors.New("invalid plugin response")
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1166,7 +1032,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1186,7 +1052,7 @@ func assertPluginVerification(scheme signature.SigningScheme, t *testing.T) {
} }
v = verifier{ v = verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1237,7 +1103,7 @@ func TestVerifyX509TrustedIdentities(t *testing.T) {
} }
func TestVerifyUserMetadata(t *testing.T) { func TestVerifyUserMetadata(t *testing.T) {
policyDocument := dummyOCIPolicyDocument() policyDocument := dummyPolicyDocument()
policyDocument.TrustPolicies[0].SignatureVerification.VerificationLevel = trustpolicy.LevelAudit.Name policyDocument.TrustPolicies[0].SignatureVerification.VerificationLevel = trustpolicy.LevelAudit.Name
pluginManager := mock.PluginManager{} pluginManager := mock.PluginManager{}
@ -1248,7 +1114,7 @@ func TestVerifyUserMetadata(t *testing.T) {
t.Fatalf("unexpected error while creating revocation object: %v", err) t.Fatalf("unexpected error while creating revocation object: %v", err)
} }
verifier := verifier{ verifier := verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: truststore.NewX509TrustStore(dir.ConfigFS()), trustStore: truststore.NewX509TrustStore(dir.ConfigFS()),
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1317,7 +1183,7 @@ func TestPluginVersionCompatibility(t *testing.T) {
t.Fatalf("unexpected error while creating revocation object: %v", err) t.Fatalf("unexpected error while creating revocation object: %v", err)
} }
v := verifier{ v := verifier{
ociTrustPolicyDoc: &policyDocument, trustPolicyDoc: &policyDocument,
trustStore: x509TrustStore, trustStore: x509TrustStore,
pluginManager: pluginManager, pluginManager: pluginManager,
revocationClient: revocationClient, revocationClient: revocationClient,
@ -1373,15 +1239,15 @@ func TestIsRequiredVerificationPluginVer(t *testing.T) {
} }
func TestRevocationFinalResult(t *testing.T) { func TestRevocationFinalResult(t *testing.T) {
certResult := []*revocationresult.CertRevocationResult{ certResult := []*result.CertRevocationResult{
{ {
// update leaf cert result in each sub-test // update leaf cert result in each sub-test
}, },
{ {
Result: revocationresult.ResultNonRevokable, Result: result.ResultNonRevokable,
ServerResults: []*revocationresult.ServerResult{ ServerResults: []*result.ServerResult{
{ {
Result: revocationresult.ResultNonRevokable, Result: result.ResultNonRevokable,
}, },
}, },
}, },
@ -1399,12 +1265,12 @@ func TestRevocationFinalResult(t *testing.T) {
}, },
} }
t.Run("OCSP error without fallback", func(t *testing.T) { t.Run("OCSP error without fallback", func(t *testing.T) {
certResult[0] = &revocationresult.CertRevocationResult{ certResult[0] = &result.CertRevocationResult{
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
ServerResults: []*revocationresult.ServerResult{ ServerResults: []*result.ServerResult{
{ {
Server: "http://ocsp.example.com", Server: "http://ocsp.example.com",
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
Error: errors.New("ocsp error"), Error: errors.New("ocsp error"),
RevocationMethod: result.RevocationMethodOCSP, RevocationMethod: result.RevocationMethodOCSP,
}, },
@ -1412,23 +1278,23 @@ func TestRevocationFinalResult(t *testing.T) {
} }
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard) finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
if finalResult != revocationresult.ResultUnknown || problematicCertSubject != "CN=leafCert" { if finalResult != result.ResultUnknown || problematicCertSubject != "CN=leafCert" {
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject) t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
} }
}) })
t.Run("OCSP error with fallback", func(t *testing.T) { t.Run("OCSP error with fallback", func(t *testing.T) {
certResult[0] = &revocationresult.CertRevocationResult{ certResult[0] = &result.CertRevocationResult{
Result: revocationresult.ResultOK, Result: result.ResultOK,
ServerResults: []*revocationresult.ServerResult{ ServerResults: []*result.ServerResult{
{ {
Server: "http://ocsp.example.com", Server: "http://ocsp.example.com",
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
Error: errors.New("ocsp error"), Error: errors.New("ocsp error"),
RevocationMethod: result.RevocationMethodOCSP, RevocationMethod: result.RevocationMethodOCSP,
}, },
{ {
Result: revocationresult.ResultOK, Result: result.ResultOK,
Server: "http://crl.example.com", Server: "http://crl.example.com",
RevocationMethod: result.RevocationMethodCRL, RevocationMethod: result.RevocationMethodCRL,
}, },
@ -1437,23 +1303,23 @@ func TestRevocationFinalResult(t *testing.T) {
} }
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard) finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
if finalResult != revocationresult.ResultOK || problematicCertSubject != "" { if finalResult != result.ResultOK || problematicCertSubject != "" {
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject) t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
} }
}) })
t.Run("OCSP error with fallback and CRL error", func(t *testing.T) { t.Run("OCSP error with fallback and CRL error", func(t *testing.T) {
certResult[0] = &revocationresult.CertRevocationResult{ certResult[0] = &result.CertRevocationResult{
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
ServerResults: []*revocationresult.ServerResult{ ServerResults: []*result.ServerResult{
{ {
Server: "http://ocsp.example.com", Server: "http://ocsp.example.com",
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
Error: errors.New("ocsp error"), Error: errors.New("ocsp error"),
RevocationMethod: result.RevocationMethodOCSP, RevocationMethod: result.RevocationMethodOCSP,
}, },
{ {
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
Error: errors.New("crl error"), Error: errors.New("crl error"),
RevocationMethod: result.RevocationMethodCRL, RevocationMethod: result.RevocationMethodCRL,
}, },
@ -1462,17 +1328,17 @@ func TestRevocationFinalResult(t *testing.T) {
} }
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard) finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
if finalResult != revocationresult.ResultUnknown || problematicCertSubject != "CN=leafCert" { if finalResult != result.ResultUnknown || problematicCertSubject != "CN=leafCert" {
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject) t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
} }
}) })
t.Run("revocation method unknown error(should never reach here)", func(t *testing.T) { t.Run("revocation method unknown error(should never reach here)", func(t *testing.T) {
certResult[0] = &revocationresult.CertRevocationResult{ certResult[0] = &result.CertRevocationResult{
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
ServerResults: []*revocationresult.ServerResult{ ServerResults: []*result.ServerResult{
{ {
Result: revocationresult.ResultUnknown, Result: result.ResultUnknown,
Error: errors.New("unknown error"), Error: errors.New("unknown error"),
RevocationMethod: result.RevocationMethodUnknown, RevocationMethod: result.RevocationMethodUnknown,
}, },
@ -1480,7 +1346,7 @@ func TestRevocationFinalResult(t *testing.T) {
} }
finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard) finalResult, problematicCertSubject := revocationFinalResult(certResult, certChain, log.Discard)
if finalResult != revocationresult.ResultUnknown || problematicCertSubject != "CN=leafCert" { if finalResult != result.ResultUnknown || problematicCertSubject != "CN=leafCert" {
t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject) t.Fatalf("unexpected final result: %v, problematic cert subject: %s", finalResult, problematicCertSubject)
} }
}) })
@ -1508,32 +1374,3 @@ func verifyResult(outcome *notation.VerificationOutcome, expectedResult notation
t.Fatalf("assertion failed. expected : %v got : %v", expectedErr, outcome.Error) t.Fatalf("assertion failed. expected : %v got : %v", expectedErr, outcome.Error)
} }
} }
// testTrustStore implements truststore.X509TrustStore and returns the trusted certificates for a given trust-store.
type testTrustStore struct{}
func (ts *testTrustStore) GetCertificates(_ context.Context, _ truststore.Type, _ string) ([]*x509.Certificate, error) {
block, _ := pem.Decode([]byte(trustedCert))
cert, _ := x509.ParseCertificate(block.Bytes)
return []*x509.Certificate{cert}, nil
}
func getTestDescGenFunc(returnErr bool, customDigest digest.Digest) notation.BlobDescriptorGenerator {
return func(digest.Algorithm) (ocispec.Descriptor, error) {
var err error = nil
if returnErr {
err = errors.New("intentional test desc generation error")
}
var expDigest digest.Digest = "sha384:b8ab24dafba5cf7e4c89c562f811cf10493d4203da982d3b1345f366ca863d9c2ed323dbd0fb7ff83a80302ceffa5a61"
if customDigest != "" {
expDigest = customDigest
}
return ocispec.Descriptor{
MediaType: "video/mp4",
Digest: expDigest,
Size: 12,
}, err
}
}