diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cf7fd900..b1a51456 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,14 +40,19 @@ jobs: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Setup Go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version-file: ./go.mod + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # v2.16.1 + uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # v2.16.1 + uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # v2.16.1 + uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 diff --git a/.gitignore b/.gitignore index dbdd02f6..bd0ab9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea .DS_Store +*~ /sigstore-go /tufdata /conformance diff --git a/cmd/conformance/main.go b/cmd/conformance/main.go index c78cb543..d6c40263 100644 --- a/cmd/conformance/main.go +++ b/cmd/conformance/main.go @@ -57,10 +57,16 @@ func getTrustedRoot() root.TrustedMaterial { if !ok { log.Fatal("unable to get path") } - - tufDir := path.Join(path.Dir(filename), "tufdata") - - trustedRootJSON, err = tuf.GetTrustedrootJSON("tuf-repo-cdn.sigstore.dev", tufDir) + opts := tuf.DefaultOptions() + opts.CachePath = path.Join(path.Dir(filename), "tufdata") + client, err := tuf.New(opts) + if err != nil { + log.Fatal(err) + } + trustedRootJSON, err = client.GetTarget("trusted_root.json") + if err != nil { + log.Fatal(err) + } } if err != nil { diff --git a/cmd/sigstore-go/main.go b/cmd/sigstore-go/main.go index d143563f..224186d6 100644 --- a/cmd/sigstore-go/main.go +++ b/cmd/sigstore-go/main.go @@ -47,7 +47,7 @@ var onlineTlog *bool var trustedPublicKey *string var trustedrootJSONpath *string var tufRootURL *string -var tufDirectory *string +var tufTrustedRoot *string func init() { artifact = flag.String("artifact", "", "Path to artifact to verify") @@ -63,7 +63,7 @@ func init() { trustedPublicKey = flag.String("publicKey", "", "Path to trusted public key") trustedrootJSONpath = flag.String("trustedrootJSONpath", "examples/trusted-root-public-good.json", "Path to trustedroot JSON file") tufRootURL = flag.String("tufRootURL", "", "URL of TUF root containing trusted root JSON file") - tufDirectory = flag.String("tufDirectory", "tufdata", "Directory to store TUF metadata") + tufTrustedRoot = flag.String("tufTrustedRoot", "", "Path to the trusted TUF root.json to bootstrap trust in the remote TUF repository") flag.Parse() if flag.NArg() == 0 { usage() @@ -120,20 +120,41 @@ func run() error { identityPolicies = append(identityPolicies, verify.WithCertificateIdentity(certID)) var trustedMaterial = make(root.TrustedMaterialCollection, 0) - var trustedrootJSON []byte + var trustedRootJSON []byte if *tufRootURL != "" { - trustedrootJSON, err = tuf.GetTrustedrootJSON(*tufRootURL, *tufDirectory) + opts := tuf.DefaultOptions() + opts.RepositoryBaseURL = *tufRootURL + + // Load the tuf root.json if provided, if not use public good + if *tufTrustedRoot != "" { + rb, err := os.ReadFile(*tufTrustedRoot) + if err != nil { + return fmt.Errorf("failed to read %s: %w", + *tufTrustedRoot, err) + } + opts.Root = rb + } + + client, err := tuf.New(opts) + if err != nil { + return err + } + trustedRootJSON, err = client.GetTarget("trusted_root.json") + if err != nil { + return err + } } else if *trustedrootJSONpath != "" { - trustedrootJSON, err = os.ReadFile(*trustedrootJSONpath) - } - if err != nil { - return err + trustedRootJSON, err = os.ReadFile(*trustedrootJSONpath) + if err != nil { + return fmt.Errorf("failed to read %s: %w", + *trustedrootJSONpath, err) + } } - if len(trustedrootJSON) > 0 { + if len(trustedRootJSON) > 0 { var trustedRoot *root.TrustedRoot - trustedRoot, err = root.NewTrustedRootFromJSON(trustedrootJSON) + trustedRoot, err = root.NewTrustedRootFromJSON(trustedRootJSON) if err != nil { return err } diff --git a/examples/oci-image-verification/go.mod b/examples/oci-image-verification/go.mod index 41876ce1..bc7c735b 100644 --- a/examples/oci-image-verification/go.mod +++ b/examples/oci-image-verification/go.mod @@ -2,6 +2,8 @@ module github.com/sigstore/sigstore-go/examples/oci-image-verification go 1.21 +replace github.com/sigstore/sigstore-go => ../../ + require ( github.com/google/go-containerregistry v0.19.0 github.com/sigstore/protobuf-specs v0.2.1 @@ -22,18 +24,18 @@ require ( github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.22.0 // indirect github.com/go-openapi/errors v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/loads v0.21.5 // indirect - github.com/go-openapi/runtime v0.26.2 // indirect - github.com/go-openapi/spec v0.20.13 // indirect + github.com/go-openapi/runtime v0.27.1 // indirect + github.com/go-openapi/spec v0.20.14 // indirect github.com/go-openapi/strfmt v0.22.0 // indirect - github.com/go-openapi/swag v0.22.7 // indirect - github.com/go-openapi/validate v0.22.3 // indirect + github.com/go-openapi/swag v0.22.9 // indirect + github.com/go-openapi/validate v0.22.6 // indirect github.com/google/certificate-transparency-go v1.1.7 // indirect github.com/google/uuid v1.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -60,7 +62,7 @@ require ( github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - github.com/sigstore/rekor v1.3.4 // indirect + github.com/sigstore/rekor v1.3.5 // indirect github.com/sigstore/timestamp-authority v1.2.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -71,28 +73,29 @@ require ( github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect + github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.3 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/klog/v2 v2.120.0 // indirect ) diff --git a/examples/oci-image-verification/go.sum b/examples/oci-image-verification/go.sum index edd2a7d1..877d5e8a 100644 --- a/examples/oci-image-verification/go.sum +++ b/examples/oci-image-verification/go.sum @@ -1,4 +1,4 @@ -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -9,53 +9,53 @@ cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18 h1:rd389Q26LMy03gG4anandGFC2LW/xvjga5GezeeaxQk= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18/go.mod h1:fgJuSBrJP5qZtKqaMJE0hmhS2tmRH+44IkfZvjtaf1M= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.49.4 h1:qiXsqEeLLhdLgUIyfr5ot+N/dGPWALmtM1SetRmbUlY= -github.com/aws/aws-sdk-go v1.49.4/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk= -github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o= -github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU= -github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY= +github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= +github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.6 h1:zzaFokMF7UVk22/Igtb93A1ReGP50uu99ldLWaEMfHc= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.6/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.9 h1:W9PbZAZAEcelhhjb7KuwUtf+Lbc+i7ByYJRuWLlnxyQ= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.9/go.mod h1:2tFmR7fQnOdQlM2ZCEPpFnBIQD1U8wmXmduBgZbOag0= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -105,10 +105,9 @@ github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyN github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.22.0 h1:wQ/d07nf78HNj4u+KiSY0sT234IAyePPbMgpUjUJQR0= @@ -121,22 +120,22 @@ github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdX github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/loads v0.21.5 h1:jDzF4dSoHw6ZFADCGltDb2lE4F6De7aWSpe+IcsRzT0= github.com/go-openapi/loads v0.21.5/go.mod h1:PxTsnFBoBe+z89riT+wYt3prmSBP6GDAQh2l9H1Flz8= -github.com/go-openapi/runtime v0.26.2 h1:elWyB9MacRzvIVgAZCBJmqTi7hBzU0hlKD4IvfX0Zl0= -github.com/go-openapi/runtime v0.26.2/go.mod h1:O034jyRZ557uJKzngbMDJXkcKJVzXJiymdSfgejrcRw= -github.com/go-openapi/spec v0.20.13 h1:XJDIN+dLH6vqXgafnl5SUIMnzaChQ6QTo0/UPMbkIaE= -github.com/go-openapi/spec v0.20.13/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/runtime v0.27.1 h1:ae53yaOoh+fx/X5Eaq8cRmavHgDma65XPZuvBqvJYto= +github.com/go-openapi/runtime v0.27.1/go.mod h1:fijeJEiEclyS8BRurYE1DE5TLb9/KZl6eAdbzjsrlLU= +github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= +github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= -github.com/go-openapi/validate v0.22.3 h1:KxG9mu5HBRYbecRb37KRCihvGGtND2aXziBAv0NNfyI= -github.com/go-openapi/validate v0.22.3/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wpypXfo= +github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -155,8 +154,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= -github.com/google/trillian v1.5.3 h1:3ioA5p09qz+U9/t2riklZtaQdZclaStp0/eQNfewNRg= -github.com/google/trillian v1.5.3/go.mod h1:p4tcg7eBr7aT6DxrAoILpc3uXNfcuAvZSnQKonVg+Eo= +github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= +github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= @@ -239,8 +238,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -275,20 +274,18 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/sigstore/protobuf-specs v0.2.1 h1:KIoM7E3C4uaK092q8YoSj/XSf9720f8dlsbYwwOmgEA= github.com/sigstore/protobuf-specs v0.2.1/go.mod h1:xPqQGnH/HllKuZ4VFPz/g+78epWM/NLRGl7Fuy45UdE= -github.com/sigstore/rekor v1.3.4 h1:RGIia1iOZU7fOiiP2UY/WFYhhp50S5aUm7YrM8aiA6E= -github.com/sigstore/rekor v1.3.4/go.mod h1:1GubPVO2yO+K0m0wt/3SHFqnilr/hWbsjSOe7Vzxrlg= +github.com/sigstore/rekor v1.3.5 h1:QoVXcS7NppKY+rpbEFVHr4evGDZBBSh65X0g8PXoUkQ= +github.com/sigstore/rekor v1.3.5/go.mod h1:CWqOk/fmnPwORQmm7SyDgB54GTJizqobbZ7yOP1lvw8= github.com/sigstore/sigstore v1.8.1 h1:mAVposMb14oplk2h/bayPmIVdzbq2IhCgy4g6R0ZSjo= github.com/sigstore/sigstore v1.8.1/go.mod h1:02SL1158BSj15bZyOFz7m+/nJzLZfFd9A8ab3Kz7w/E= -github.com/sigstore/sigstore-go v0.1.0 h1:jHzjZcZnrxtzi8wKTTYlLFNixp2H6H4tKsefHv5/Xro= -github.com/sigstore/sigstore-go v0.1.0/go.mod h1:E9vlpY082JIbdOnQxc+BZTk4DmnDfX/t0Gi2mp+s7GA= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.0 h1:nLaaOX85YjBKQOQHWY2UlDkbx+je8ozTEM+t1ySAb78= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.0/go.mod h1:fLxrKqPP9lIz/B3UBD4ZK6j6984eX2czu/0zxm99fkE= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.0 h1:Txd7Fjei2NVb/sjBNYybrl+FcZGptO6FXXH4pVNBQMs= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.0/go.mod h1:mZjoLdfxFzo61abWNQisk8BcUbGshTO5HCpPRjzuPUs= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.0 h1:vQKLGL2H3L6AWnTddmF4TPKKNAM6GX1CtLsvIhCtjOw= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.0/go.mod h1:eaY3HCZUSNzqfkGsvkHSCkBlTQIQ4Sym9po09fAJw5w= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.0 h1:PspwJqJtD4bo0Aboo2UBrvznNUK7ETjD270GD9WLI88= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.0/go.mod h1:8ta2z6+ZsN8o3EdxGgpSn6VCAkTqLztV0L4YnLCwrwU= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 h1:rEDdUefulkIQaMJyzLwtgPDLNXBIltBABiFYfb0YmgQ= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1/go.mod h1:RCdYCc1IxCYWzh2IdzdA6Yf7JIY0cMRqH08fpQYechw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 h1:DvRWG99QGWZC5mp42SEde2Xke/Q384Idnj2da7yB+Mk= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1/go.mod h1:s13mo3a0UCQS3+PAUUZfvKe48sMDMsHk2GE1b2YfPcU= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1 h1:lwdRsJv1UbBemuk7w5YfXAQilQxMoFevrzamdPbG0wY= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1/go.mod h1:2OaSQ80EcdyVRSQ3T4d1lsc6Scopblsiq8U2AEk5K1A= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1 h1:9Ki0qudKpc1FQdef7xHO2bkLyTuw+qNUpWRzjBEmF4c= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1/go.mod h1:nhIgyu4YwwNgalIwTGsoAzam16jjAn3ADRSWKbWPwGI= github.com/sigstore/timestamp-authority v1.2.1 h1:j9RmqSAdvKgSofeltPO4x7d+1M3AXaROBzUJ+AA7L5Q= github.com/sigstore/timestamp-authority v1.2.1/go.mod h1:Ce+vWWEf0QaKLY2u6mpwEJbmYXEVeOfUk4fQ69kE6ck= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -321,6 +318,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141 h1:SsiWxSpJ9AD71/vqiZVUjXW1Uusv1wlKn4zPKFNq25w= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141/go.mod h1:D7dcS4bZMmF3pXOgUo8Vs6GLYM9sdrFFd37JqiP3hN4= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= @@ -339,20 +338,20 @@ go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/ go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.step.sm/crypto v0.40.0 h1:356UwJSM4Nhg5b5AjjjLlBNkf92Vw3Gi2r3vbEv72oc= -go.step.sm/crypto v0.40.0/go.mod h1:gfQMeTQXykihbS8e2Tdn0jtd9HbsQ7vbt+kp7efLA7U= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.step.sm/crypto v0.42.1 h1:OmwHm3GJO8S4VGWL3k4+I+Q4P/F2s+j8msvTyGnh1Vg= +go.step.sm/crypto v0.42.1/go.mod h1:yNcTLFQBnYCA75fC5bklBoTAT7y0dRZsB1TkinB8JMs= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -373,14 +372,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -410,18 +409,18 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050= -google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc= +google.golang.org/api v0.159.0 h1:fVTj+7HHiUYz4JEZCHHoRIeQX7h5FMzrA2RF/DzDdbs= +google.golang.org/api v0.159.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg= -google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457 h1:KHBtwE+eQc3+NxpjmRFlQ3pJQ2FNnhhgB9xOV8kyBuU= +google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -431,15 +430,14 @@ gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4 gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= +k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= diff --git a/examples/oci-image-verification/main.go b/examples/oci-image-verification/main.go index 2c048795..6d18c78d 100644 --- a/examples/oci-image-verification/main.go +++ b/examples/oci-image-verification/main.go @@ -142,20 +142,29 @@ func run() error { } var trustedMaterial = make(root.TrustedMaterialCollection, 0) - var trustedrootJSON []byte + var trustedRootJSON []byte if *tufRootURL != "" { - trustedrootJSON, err = tuf.GetTrustedrootJSON(*tufRootURL, *tufDirectory) + opts := tuf.DefaultOptions() + opts.RepositoryBaseURL = *tufRootURL + client, err := tuf.New(opts) + if err != nil { + return err + } + trustedRootJSON, err = client.GetTarget("trusted_root.json") + if err != nil { + return err + } } else if *trustedrootJSONpath != "" { - trustedrootJSON, err = os.ReadFile(*trustedrootJSONpath) + trustedRootJSON, err = os.ReadFile(*trustedrootJSONpath) } if err != nil { return err } - if len(trustedrootJSON) > 0 { + if len(trustedRootJSON) > 0 { var trustedRoot *root.TrustedRoot - trustedRoot, err = root.NewTrustedRootFromJSON(trustedrootJSON) + trustedRoot, err = root.NewTrustedRootFromJSON(trustedRootJSON) if err != nil { return err } diff --git a/go.mod b/go.mod index 55ba93d0..8f018ed0 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,8 @@ require ( github.com/sigstore/sigstore v1.8.1 github.com/sigstore/timestamp-authority v1.2.1 github.com/stretchr/testify v1.8.4 - github.com/theupdateframework/go-tuf v0.7.0 + github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141 + golang.org/x/crypto v0.18.0 golang.org/x/mod v0.14.0 google.golang.org/protobuf v1.32.0 ) @@ -66,6 +67,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect @@ -74,7 +76,6 @@ require ( go.opentelemetry.io/otel/trace v1.22.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/sync v0.6.0 // indirect diff --git a/go.sum b/go.sum index 310e9da3..3286a499 100644 --- a/go.sum +++ b/go.sum @@ -272,6 +272,8 @@ github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1 h1:9Ki0qudKpc1F github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1/go.mod h1:nhIgyu4YwwNgalIwTGsoAzam16jjAn3ADRSWKbWPwGI= github.com/sigstore/timestamp-authority v1.2.1 h1:j9RmqSAdvKgSofeltPO4x7d+1M3AXaROBzUJ+AA7L5Q= github.com/sigstore/timestamp-authority v1.2.1/go.mod h1:Ce+vWWEf0QaKLY2u6mpwEJbmYXEVeOfUk4fQ69kE6ck= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -297,6 +299,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141 h1:SsiWxSpJ9AD71/vqiZVUjXW1Uusv1wlKn4zPKFNq25w= +github.com/theupdateframework/go-tuf/v2 v2.0.0-20240207172116-f5cf71290141/go.mod h1:D7dcS4bZMmF3pXOgUo8Vs6GLYM9sdrFFd37JqiP3hN4= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= diff --git a/pkg/root/trusted_root.go b/pkg/root/trusted_root.go index dac46549..16288a00 100644 --- a/pkg/root/trusted_root.go +++ b/pkg/root/trusted_root.go @@ -20,11 +20,14 @@ import ( "crypto/x509" "encoding/hex" "fmt" + "log" "os" + "sync" "time" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" prototrustroot "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1" + "github.com/sigstore/sigstore-go/pkg/tuf" "google.golang.org/protobuf/encoding/protojson" ) @@ -256,3 +259,102 @@ func NewTrustedRootProtobuf(rootJSON []byte) (*prototrustroot.TrustedRoot, error } return pbTrustedRoot, nil } + +// FetchTrustedRoot fetches the Sigstore trusted root from TUF and returns it. +func FetchTrustedRoot() (*TrustedRoot, error) { + return FetchTrustedRootWithOptions(tuf.DefaultOptions()) +} + +// FetchTrustedRootWithOptions fetches the trusted root from TUF with the given options and returns it. +func FetchTrustedRootWithOptions(opts *tuf.Options) (*TrustedRoot, error) { + client, err := tuf.New(opts) + if err != nil { + return nil, err + } + return GetTrustedRoot(client) +} + +// GetTrustedRoot returns the trusted root +func GetTrustedRoot(c *tuf.Client) (*TrustedRoot, error) { + jsonBytes, err := c.GetTarget("trusted_root.json") + if err != nil { + return nil, err + } + return NewTrustedRootFromJSON(jsonBytes) +} + +// LiveTrustedRoot is a wrapper around TrustedRoot that periodically +// refreshes the trusted root from TUF. This is needed for long-running +// processes to ensure that the trusted root does not expire. +type LiveTrustedRoot struct { + *TrustedRoot + mu sync.RWMutex +} + +// NewLiveTrustedRoot returns a LiveTrustedRoot that will periodically +// refresh the trusted root from TUF. +func NewLiveTrustedRoot(opts *tuf.Options) (*LiveTrustedRoot, error) { + client, err := tuf.New(opts) + if err != nil { + return nil, err + } + tr, err := GetTrustedRoot(client) + if err != nil { + return nil, err + } + ltr := &LiveTrustedRoot{ + TrustedRoot: tr, + mu: sync.RWMutex{}, + } + ticker := time.NewTicker(time.Hour * 24) + go func() { + for { + select { + case <-ticker.C: + client, err = tuf.New(opts) + if err != nil { + log.Printf("error creating TUF client: %v", err) + } + newTr, err := GetTrustedRoot(client) + if err != nil { + log.Printf("error fetching trusted root: %v", err) + continue + } + ltr.mu.Lock() + ltr.TrustedRoot = newTr + ltr.mu.Unlock() + } + } + }() + return ltr, nil +} + +func (l *LiveTrustedRoot) TSACertificateAuthorities() []CertificateAuthority { + l.mu.RLock() + defer l.mu.RUnlock() + return l.TrustedRoot.TSACertificateAuthorities() +} + +func (l *LiveTrustedRoot) FulcioCertificateAuthorities() []CertificateAuthority { + l.mu.RLock() + defer l.mu.RUnlock() + return l.TrustedRoot.FulcioCertificateAuthorities() +} + +func (l *LiveTrustedRoot) TlogAuthorities() map[string]*TlogAuthority { + l.mu.RLock() + defer l.mu.RUnlock() + return l.TrustedRoot.TlogAuthorities() +} + +func (l *LiveTrustedRoot) CTlogAuthorities() map[string]*TlogAuthority { + l.mu.RLock() + defer l.mu.RUnlock() + return l.TrustedRoot.CTlogAuthorities() +} + +func (l *LiveTrustedRoot) PublicKeyVerifier(keyID string) (TimeConstrainedVerifier, error) { + l.mu.RLock() + defer l.mu.RUnlock() + return l.TrustedRoot.PublicKeyVerifier(keyID) +} diff --git a/pkg/tuf/client.go b/pkg/tuf/client.go index cfdd7d4d..f655f540 100644 --- a/pkg/tuf/client.go +++ b/pkg/tuf/client.go @@ -15,137 +15,186 @@ package tuf import ( - "bytes" - "embed" - "encoding/json" "fmt" - "path" + "path/filepath" + "strings" + "time" - tufclient "github.com/theupdateframework/go-tuf/client" - filejsonstore "github.com/theupdateframework/go-tuf/client/filejsonstore" - tufdata "github.com/theupdateframework/go-tuf/data" - tufutil "github.com/theupdateframework/go-tuf/util" + "github.com/theupdateframework/go-tuf/v2/metadata/config" + "github.com/theupdateframework/go-tuf/v2/metadata/updater" ) -//go:embed repository -var embeddedRepos embed.FS - -const TrustedRootTUFPath = "trusted_root.json" -const RootTUFPath = "root.json" - -// Implementation of go-tuf/client.Destination interface -type Writer struct { - Bytes []byte -} - -func (w *Writer) Write(b []byte) (int, error) { - w.Bytes = append(w.Bytes, b...) - return len(b), nil +// Client is a Sigstore TUF client +type Client struct { + cfg *config.UpdaterConfig + up *updater.Updater + opts *Options } -func (w *Writer) Delete() error { - w = nil - return nil -} - -func GetTrustedrootJSON(tufRootURL, workPath string) (trustedrootJSON []byte, err error) { - // Ensure we have a RootTUFPath file for this TUF URL - tufPath := path.Join(workPath, tufRootURL) - - fileJSONStore, err := filejsonstore.NewFileJSONStore(tufPath) - if err != nil { - return nil, err +// New returns a new client with custom options +func New(opts *Options) (*Client, error) { + var c = Client{ + opts: opts, } + dir := filepath.Join(opts.CachePath, URLToPath(opts.RepositoryBaseURL)) + var err error - tufMetaMap, err := fileJSONStore.GetMeta() - if err != nil { - return nil, err + if c.cfg, err = config.New(opts.RepositoryBaseURL, opts.Root); err != nil { + return nil, fmt.Errorf("failed to create TUF client: %w", err) } - _, ok := tufMetaMap[RootTUFPath] - if !ok { - // There isn't a RootTUFPath for this TUF URL, so see if the library has one embedded - _, err = checkEmbedded(tufRootURL, fileJSONStore) + c.cfg.LocalMetadataDir = dir + c.cfg.LocalTargetsDir = filepath.Join(dir, "targets") + c.cfg.DisableLocalCache = c.opts.DisableLocalCache + c.cfg.PrefixTargetsWithHash = !c.opts.DisableConsistentSnapshot - if err != nil { - return nil, err - } + if c.cfg.DisableLocalCache { + c.opts.CachePath = "" + c.opts.CacheValidity = 0 + c.opts.ForceCache = false } - // Now that we have fileJSONStore, create a tufclient and check remote for updates - tufRemoteOptions := &tufclient.HTTPRemoteOptions{ - MetadataPath: "", - TargetsPath: "targets", - Retries: tufclient.DefaultHTTPRetries, + if opts.Fetcher != nil { + c.cfg.Fetcher = opts.Fetcher } - tufRemoteStore, err := tufclient.HTTPRemoteStore(fmt.Sprintf("https://%s", tufRootURL), tufRemoteOptions, nil) + // Upon client creation, we may not perform a full TUF update, + // based on the cache control configuration. Start with a local + // client (only reads content on disk) and then decide if we + // must perform a full TUF update. + tmpCfg := *c.cfg + // Create a temporary config for the first use where UnsafeLocalMode + // is true. This means that when we first initialize the client, + // we are guaranteed to only read the metadata on disk. + // Based on that metadata we take a decision if a full TUF + // refresh should be done or not. As so, the tmpCfg is only needed + // here and not in future invocations. + tmpCfg.UnsafeLocalMode = true + c.up, err = updater.New(&tmpCfg) if err != nil { return nil, err } - - tufClient := tufclient.NewClient(fileJSONStore, tufRemoteStore) - targetFiles, err := tufClient.Update() - if err != nil { + if err = c.loadMetadata(); err != nil { return nil, err } - // Now that we've updated, see if remote trustedroot metadata matches local disk - trustedrootMeta, ok := targetFiles[TrustedRootTUFPath] - if !ok { - return nil, fmt.Errorf("Unable to find %s via TUF", TrustedRootTUFPath) + return &c, nil +} + +// DefaultClient returns a Sigstore TUF client for the public good instance +func DefaultClient() (*Client, error) { + opts := DefaultOptions() + + return New(opts) +} + +// loadMetadata controls if the client actually should perform a TUF refresh. +// The TUF specification mandates so, but for certain Sigstore clients, it +// may be beneficial to rely on the cache, or in air-gapped deployments it +// it may not even be possible. +func (c *Client) loadMetadata() error { + // Load the metadata into memory and verify it + if err := c.up.Refresh(); err != nil { + // this is most likely due to the lack of metadata files + // on disk. Perform a full update and return. + return c.Refresh() } - trustedroot, ok := tufMetaMap[TrustedRootTUFPath] - if ok { - if ok, _ := validTarget(trustedrootMeta, trustedroot); ok { - return trustedroot, nil + if c.opts.ForceCache { + return nil + } else if c.opts.CacheValidity > 0 { + cfg, err := LoadConfig(c.configPath()) + if err != nil { + // Config may not exist, don't error + // create a new empty config + cfg = &Config{} } - } - // What's on disk didn't match, so download from TUF remote (and cache it to disk) - writer := &Writer{ - Bytes: make([]byte, 0), + cacheValidUntil := cfg.LastTimestamp.AddDate(0, 0, c.opts.CacheValidity) + if time.Now().Before(cacheValidUntil) { + // No need to update + return nil + } } - err = tufClient.Download(TrustedRootTUFPath, writer) + return c.Refresh() +} + +func (c *Client) configPath() string { + var p = filepath.Join( + c.opts.CachePath, + fmt.Sprintf("%s.json", URLToPath(c.opts.RepositoryBaseURL)), + ) + + return p +} + +// Refresh forces a refresh of the underlying TUF client. +// As the tuf client updater does not support multiple refreshes during +// its life-time, this will replace the TUF client updater with a new one. +func (c *Client) Refresh() error { + var err error + + c.up, err = updater.New(c.cfg) if err != nil { - return nil, err + return fmt.Errorf("failed to create tuf updater: %w", err) + } + err = c.up.Refresh() + if err != nil { + return fmt.Errorf("tuf refresh failed: %w", err) } - err = fileJSONStore.SetMeta(TrustedRootTUFPath, writer.Bytes) + // Update config with last update + cfg, err := LoadConfig(c.configPath()) if err != nil { - return nil, err + // Likely config file did not exit, create it + cfg = &Config{} } + cfg.LastTimestamp = time.Now() + // ignore error writing update config file + _ = cfg.Persist(c.configPath()) - return writer.Bytes, nil + return nil } -func checkEmbedded(tufRootURL string, fileJSONStore *filejsonstore.FileJSONStore) (json.RawMessage, error) { - embeddedRootPath := path.Join("repository", tufRootURL, RootTUFPath) +// GetTarget returns a target file from the TUF repository +func (c *Client) GetTarget(target string) ([]byte, error) { + // Set filepath to the empty string. When we get targets, + // we rely in the target info struct instead. + const filePath = "" + ti, err := c.up.GetTargetInfo(target) + if err != nil { + return nil, fmt.Errorf("getting info for target \"%s\": %w", target, err) + } - root, err := embeddedRepos.ReadFile(embeddedRootPath) + path, tb, err := c.up.FindCachedTarget(ti, filePath) if err != nil { - return nil, err + return nil, fmt.Errorf("getting target cache: %w", err) + } + if path != "" { + // Cached version found + return tb, nil } - err = fileJSONStore.SetMeta(RootTUFPath, root) + // Download of target is needed + // Ignore targetsBaseURL, set to empty string + const targetsBaseURL = "" + _, tb, err = c.up.DownloadTarget(ti, filePath, targetsBaseURL) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to download target file %s - %w", target, err) } - return root, nil + return tb, nil } -func validTarget(expected tufdata.TargetFileMeta, localTarget []byte) (bool, error) { - got, err := tufutil.GenerateTargetFileMeta( - bytes.NewReader(localTarget), - "sha256", "sha512") - if err != nil { - return false, err - } - if err = tufutil.TargetFileMetaEqual(got, expected); err != nil { - return false, err +// URLToPath converts a URL to a filename-compatible string +func URLToPath(url string) string { + // Strip scheme, replace slashes with dashes + // e.g. https://github.github.com/prod-tuf-root -> github.github.com-prod-tuf-root + fn := url + if len(fn) > 8 && fn[:8] == "https://" { + fn = fn[8:] } - return true, nil + fn = strings.ReplaceAll(fn, "/", "-") + return fn } diff --git a/pkg/tuf/client_test.go b/pkg/tuf/client_test.go new file mode 100644 index 00000000..89d09dbb --- /dev/null +++ b/pkg/tuf/client_test.go @@ -0,0 +1,448 @@ +// Copyright 2024 The Sigstore 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 tuf + +import ( + "crypto" + "crypto/sha256" + "net/url" + "regexp" + "strconv" + "strings" + "testing" + "time" + + "fmt" + "os" + "path/filepath" + + "github.com/sigstore/sigstore/pkg/signature" + "github.com/stretchr/testify/assert" + "github.com/theupdateframework/go-tuf/v2/metadata" + "github.com/theupdateframework/go-tuf/v2/metadata/repository" + "golang.org/x/crypto/ed25519" +) + +func TestNewOfflineClientFail(t *testing.T) { + var opt = DefaultOptions() + opt.WithForceCache().WithCachePath(t.TempDir()) + opt.WithRepositoryBaseURL("http://localhost:12345") + + // create a client, it should fail as it's set to forced cache, + // and there is no metadata on disk, and the repository url is + // invalid. + + c, err := New(opt) + assert.Nil(t, c) + assert.Error(t, err) +} + +func TestRefresh(t *testing.T) { + r := newTestRepo(t) + r.AddTarget("foo", []byte("foo version 1")) + rootJSON, err := r.roles.Root().ToBytes(false) + if err != nil { + t.Fatal(err) + } + + var opt = DefaultOptions(). + WithRepositoryBaseURL("https://testing.local"). + WithRoot(rootJSON). + WithCachePath(t.TempDir()). + WithFetcher(r). + WithDisableLocalCache() + c, err := New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + target, err := c.GetTarget("foo") + assert.NoError(t, err) + assert.NotNil(t, target) + assert.Equal(t, target, []byte("foo version 1")) + + r.AddTarget("foo", []byte("foo version 2")) + assert.NoError(t, c.Refresh()) + + target, err = c.GetTarget("foo") + assert.NoError(t, err) + assert.NotNil(t, target) + assert.Equal(t, target, []byte("foo version 2")) +} + +func TestInvalidRoot(t *testing.T) { + r := newTestRepo(t) + r2 := newTestRepo(t) + rootJSON, err := r.roles.Root().ToBytes(false) + if err != nil { + t.Fatal(err) + } + + // Create a client with a root that is not signed by the given repository fetcher + var opt = DefaultOptions(). + WithRepositoryBaseURL("https://testing.local"). + WithRoot(rootJSON). + WithCachePath(t.TempDir()). + WithFetcher(r2). + WithDisableLocalCache() + c, err := New(opt) + assert.Nil(t, c) + assert.Error(t, err) +} + +func TestInvalidRepositoryURL(t *testing.T) { + var opt = DefaultOptions(). + WithRepositoryBaseURL(string(byte(0x7f))). + WithCachePath(t.TempDir()) + c, err := New(opt) + assert.Nil(t, c) + assert.Error(t, err) +} + +func TestCache(t *testing.T) { + r := newTestRepo(t) + r.AddTarget("foo", []byte("foo version 1")) + rootJSON, err := r.roles.Root().ToBytes(false) + if err != nil { + t.Fatal(err) + } + + var opt = DefaultOptions(). + WithRepositoryBaseURL("https://testing.local"). + WithRoot(rootJSON). + WithCachePath(t.TempDir()). + WithFetcher(r). + WithCacheValidity(1) + + c, err := New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + target, err := c.GetTarget("foo") + assert.NoError(t, err) + assert.NotNil(t, target) + assert.Equal(t, target, []byte("foo version 1")) + + r.AddTarget("foo", []byte("foo version 2")) + + // Create new client with the same cache path + c, err = New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + target, err = c.GetTarget("foo") + assert.NoError(t, err) + assert.NotNil(t, target) + // Cache is still valid, so we should get the old version + assert.Equal(t, target, []byte("foo version 1")) + + // Set last updated time to 2 days ago, to trigger cache refresh + cfg, err := LoadConfig(c.configPath()) + if err != nil { + t.Fatal(err) + } + cfg.LastTimestamp = time.Now().Add(-48 * time.Hour) + err = cfg.Persist(c.configPath()) + if err != nil { + t.Fatal(err) + } + + // Create new client with the same cache path + c, err = New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + // Now we should get the new version + target, err = c.GetTarget("foo") + assert.NoError(t, err) + assert.Equal(t, target, []byte("foo version 2")) +} + +func TestExpiredTimestamp(t *testing.T) { + r := newTestRepo(t) + r.AddTarget("foo", []byte("foo version 1")) + rootJSON, err := r.roles.Root().ToBytes(false) + if err != nil { + t.Fatal(err) + } + + var opt = DefaultOptions(). + WithRepositoryBaseURL("https://testing.local"). + WithRoot(rootJSON). + WithCachePath(t.TempDir()). + WithFetcher(r). + WithCacheValidity(1) + + c, err := New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + target, err := c.GetTarget("foo") + assert.NoError(t, err) + assert.Equal(t, target, []byte("foo version 1")) + + r.AddTarget("foo", []byte("foo version 2")) + + opt.ForceCache = true + c, err = New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + target, err = c.GetTarget("foo") + assert.NoError(t, err) + // Using ForceCache, so we should get the old version + assert.Equal(t, target, []byte("foo version 1")) + + r.SetTimestamp(time.Now()) + + // Manually write timestamp to disk, as Refresh() will fail + err = r.roles.Timestamp().ToFile(filepath.Join(opt.CachePath, "testing.local", "timestamp.json"), false) + if err != nil { + t.Fatal(err) + } + + // Client creation should fail as the timestamp is expired and the repository has an expired timestamp + c, err = New(opt) + assert.Nil(t, c) + assert.Error(t, err) + + // Update repo with unexpired timestamp + r.SetTimestamp(time.Now().AddDate(0, 0, 1)) + + c, err = New(opt) + assert.NotNil(t, c) + assert.NoError(t, err) + + target, err = c.GetTarget("foo") + assert.NoError(t, err) + // Even though ForceCache is set, we should get the new version since the cached timestamp is expired + assert.Equal(t, target, []byte("foo version 2")) +} + +// repo represents repositoryType from +// github.com/theupdateframework/go-tuf/v2/metadata/repository, which is +// unexported. +type repo interface { + Root() *metadata.Metadata[metadata.RootType] + SetRoot(meta *metadata.Metadata[metadata.RootType]) + Snapshot() *metadata.Metadata[metadata.SnapshotType] + SetSnapshot(meta *metadata.Metadata[metadata.SnapshotType]) + Timestamp() *metadata.Metadata[metadata.TimestampType] + SetTimestamp(meta *metadata.Metadata[metadata.TimestampType]) + Targets(name string) *metadata.Metadata[metadata.TargetsType] + SetTargets(name string, meta *metadata.Metadata[metadata.TargetsType]) +} + +// testRepo is a basic implementation of a TUF repository for testing purposes. +// It does not support delegates, multiple signers, thresholds, or other +// advanced TUF features, but it is sufficient for testing the sigstore-go +// client. Those other features should be covered by the go-tuf tests. This is +// primarily intended to test the caching and fetching behavior of the client. +type testRepo struct { + keys map[string]ed25519.PrivateKey + roles repo + dir string + t *testing.T +} + +func newTestRepo(t *testing.T) *testRepo { + var err error + r := &testRepo{ + keys: make(map[string]ed25519.PrivateKey), + roles: repository.New(), + t: t, + } + tomorrow := time.Now().AddDate(0, 0, 1).UTC() + targets := metadata.Targets(tomorrow) + r.roles.SetTargets(metadata.TARGETS, targets) + r.dir, err = os.MkdirTemp("", "tuf-test-repo") + if err != nil { + t.Fatal(err) + } + err = os.Mkdir(filepath.Join(r.dir, metadata.TARGETS), 0700) + if err != nil { + t.Fatal(err) + } + snapshot := metadata.Snapshot(tomorrow) + r.roles.SetSnapshot(snapshot) + timestamp := metadata.Timestamp(tomorrow) + r.roles.SetTimestamp(timestamp) + root := metadata.Root(tomorrow) + r.roles.SetRoot(root) + + for _, name := range []string{metadata.TARGETS, metadata.SNAPSHOT, metadata.TIMESTAMP, metadata.ROOT} { + _, private, err := ed25519.GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + r.keys[name] = private + key, err := metadata.KeyFromPublicKey(private.Public()) + if err != nil { + t.Fatal(err) + } + err = r.roles.Root().Signed.AddKey(key, name) + if err != nil { + t.Fatal(err) + } + } + + for _, name := range metadata.TOP_LEVEL_ROLE_NAMES { + key := r.keys[name] + signer, err := signature.LoadSigner(key, crypto.Hash(0)) + if err != nil { + t.Fatal(err) + } + switch name { + case metadata.TARGETS: + _, err = r.roles.Targets(metadata.TARGETS).Sign(signer) + case metadata.SNAPSHOT: + _, err = r.roles.Snapshot().Sign(signer) + case metadata.TIMESTAMP: + _, err = r.roles.Timestamp().Sign(signer) + case metadata.ROOT: + _, err = r.roles.Root().Sign(signer) + } + if err != nil { + t.Fatal(err) + } + } + + return r +} + +// DownloadFile is a test implementation of the Fetcher interface, which the +// client may use to avoid making real HTTP requests. It returns the contents +// of the metadata files and target files in the test repository. +func (r *testRepo) DownloadFile(urlPath string, _ int64, _ time.Duration) ([]byte, error) { + u, err := url.Parse(urlPath) + if err != nil { + return []byte{}, err + } + + if strings.HasPrefix(u.Path, "/targets/") { + re := regexp.MustCompile(`/targets/[0-9a-f]{64}\.(.*)$`) + matches := re.FindStringSubmatch(u.Path) + if len(matches) != 2 { + return nil, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + targetFile, ok := r.roles.Targets(metadata.TARGETS).Signed.Targets[matches[1]] + if !ok { + return nil, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + data, err := os.ReadFile(targetFile.Path) + if err != nil { + return nil, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + return data, nil + } + if u.Path == "/timestamp.json" { + meta := r.roles.Timestamp() + return meta.ToBytes(false) + } + re := regexp.MustCompile(`/(\d+)\.(root|snapshot|targets)\.json$`) + matches := re.FindStringSubmatch(u.Path) + if len(matches) != 3 { + return nil, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + role := matches[2] + version, err := strconv.Atoi(matches[1]) + if err != nil { + return []byte{}, &metadata.ErrDownload{} + } + switch role { + case metadata.ROOT: + // TODO: handle all versions of signed root + meta := r.roles.Root() + if meta.Signed.Version != int64(version) { + return []byte{}, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + return meta.ToBytes(false) + case metadata.SNAPSHOT: + meta := r.roles.Snapshot() + if meta.Signed.Version != int64(version) { + return []byte{}, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + return meta.ToBytes(false) + case metadata.TARGETS: + meta := r.roles.Targets(metadata.TARGETS) + if meta.Signed.Version != int64(version) { + return []byte{}, &metadata.ErrDownloadHTTP{StatusCode: 404} + } + return meta.ToBytes(false) + } + + return []byte{}, nil +} + +// AddTarget adds a target file to the repository. It also creates a new +// snapshot and timestamp metadata file, and signs them with the appropriate +// key. +func (r *testRepo) AddTarget(name string, content []byte) { + targetHash := sha256.Sum256(content) + localPath := filepath.Join(r.dir, metadata.TARGETS, fmt.Sprintf("%x.%s", targetHash, name)) + err := os.WriteFile(localPath, content, 0600) + if err != nil { + r.t.Fatal(err) + } + targetFileInfo, err := metadata.TargetFile().FromFile(localPath, "sha256") + if err != nil { + r.t.Fatal(err) + } + r.roles.Targets(metadata.TARGETS).Signed.Targets[name] = targetFileInfo + r.roles.Targets("targets").Signed.Version++ + + r.roles.Snapshot().Signed.Meta["targets.json"] = metadata.MetaFile(r.roles.Targets(metadata.TARGETS).Signed.Version) + r.roles.Snapshot().Signed.Version++ + + r.roles.Timestamp().Signed.Meta["snapshot.json"] = metadata.MetaFile(r.roles.Snapshot().Signed.Version) + r.roles.Timestamp().Signed.Version++ + + for _, name := range []string{metadata.TARGETS, metadata.SNAPSHOT, metadata.TIMESTAMP} { + signer, err := signature.LoadSigner(r.keys[name], crypto.Hash(0)) + if err != nil { + r.t.Fatal(err) + } + switch name { + case metadata.TARGETS: + r.roles.Targets(metadata.TARGETS).ClearSignatures() + _, err = r.roles.Targets(metadata.TARGETS).Sign(signer) + case metadata.SNAPSHOT: + r.roles.Snapshot().ClearSignatures() + _, err = r.roles.Snapshot().Sign(signer) + case metadata.TIMESTAMP: + r.roles.Timestamp().ClearSignatures() + _, err = r.roles.Timestamp().Sign(signer) + } + if err != nil { + r.t.Fatal(err) + } + } +} + +// SetTimestamp sets the expiration date of the timestamp metadata file to the +// given date, and increments the version number. It then signs the metadata +// file with the appropriate key. +func (r *testRepo) SetTimestamp(date time.Time) { + r.roles.Timestamp().Signed.Expires = date + r.roles.Timestamp().Signed.Version++ + signer, err := signature.LoadSigner(r.keys[metadata.TIMESTAMP], crypto.Hash(0)) + if err != nil { + r.t.Fatal(err) + } + r.roles.Timestamp().ClearSignatures() + _, err = r.roles.Timestamp().Sign(signer) + if err != nil { + r.t.Fatal(err) + } +} diff --git a/pkg/tuf/config.go b/pkg/tuf/config.go new file mode 100644 index 00000000..3f5a81f1 --- /dev/null +++ b/pkg/tuf/config.go @@ -0,0 +1,54 @@ +// Copyright 2023 The Sigstore 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 tuf + +import ( + "encoding/json" + "fmt" + "os" + "time" +) + +type Config struct { + LastTimestamp time.Time `json:"lastTimestamp"` +} + +func LoadConfig(p string) (*Config, error) { + var c Config + + b, err := os.ReadFile(p) + if err != nil { + return nil, fmt.Errorf("failed to read config: %w", err) + } + err = json.Unmarshal(b, &c) + if err != nil { + return nil, fmt.Errorf("malformed config file: %w", err) + } + + return &c, nil +} + +func (c *Config) Persist(p string) error { + b, err := json.Marshal(c) + if err != nil { + return fmt.Errorf("failed to JSON marshal config: %w", err) + } + err = os.WriteFile(p, b, 0600) + if err != nil { + return fmt.Errorf("failed to write config: %w", err) + } + + return nil +} diff --git a/pkg/tuf/config_test.go b/pkg/tuf/config_test.go new file mode 100644 index 00000000..797a2739 --- /dev/null +++ b/pkg/tuf/config_test.go @@ -0,0 +1,48 @@ +// Copyright 2023 The Sigstore 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 tuf + +import ( + "path/filepath" + "testing" + "time" +) + +func TestConfig(t *testing.T) { + var p = filepath.Join(t.TempDir(), "cfg.json") + var ts = time.Now() + var c = Config{ + LastTimestamp: ts, + } + + err := c.Persist(p) + if err != nil { + t.Error(err.Error()) + } + + cp, err := LoadConfig(p) + if err != nil { + t.Error(err.Error()) + } + delta := ts.Sub(cp.LastTimestamp) + if delta < 0 { + delta = -delta + } + // make sure the delta is less than one second. During JSON + // serializion precision up to a second may be lost + if delta > time.Second { + t.Error("wrong date received after load") + } +} diff --git a/pkg/tuf/options.go b/pkg/tuf/options.go new file mode 100644 index 00000000..a790bd5c --- /dev/null +++ b/pkg/tuf/options.go @@ -0,0 +1,144 @@ +// Copyright 2023 The Sigstore 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 tuf + +import ( + "embed" + "math" + "os" + "path/filepath" + + "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" +) + +//go:embed repository +var embeddedRepo embed.FS + +const ( + DefaultMirror = "https://tuf-repo-cdn.sigstore.dev" + + // The following caching values can be used for the CacheValidity option + NoCache = 0 + MaxCache = math.MaxInt +) + +// Options represent the various options for a Sigstore TUF Client +type Options struct { + // CacheValidity period in days (default 0). Note that the client will + // always refresh the cache if the metadata is expired, so this is not an + // optimal control for air-gapped environments. Use const MaxCache to only + // update the cache when the metadata is expired. + CacheValidity int + // ForceCache controls if the cache should be used without update + // as long as the metadata is valid + ForceCache bool + // Root is the TUF trust anchor + Root []byte + // CachePath is the location on disk for TUF cache + // (default $HOME/.sigstore/tuf) + CachePath string + // RepositoryBaseURL is the TUF repository location URL + // (default https://tuf-repo-cdn.sigstore.dev) + RepositoryBaseURL string + // DisableLocalCache mode allows a client to work on a read-only + // files system if this is set, cache path is ignored. + DisableLocalCache bool + // DisableConsistentSnapshot + DisableConsistentSnapshot bool + // Fetcher is the metadata fetcher + Fetcher fetcher.Fetcher +} + +// WithCacheValidity sets the cache validity period in days +func (o *Options) WithCacheValidity(days int) *Options { + o.CacheValidity = days + return o +} + +// WithForceCache forces the client to use the cache without updating +func (o *Options) WithForceCache() *Options { + o.ForceCache = true + return o +} + +// WithRoot sets the TUF trust anchor +func (o *Options) WithRoot(root []byte) *Options { + o.Root = root + return o +} + +// WithCachePath sets the location on disk for TUF cache +func (o *Options) WithCachePath(path string) *Options { + o.CachePath = path + return o +} + +// WithRepositoryBaseURL sets the TUF repository location URL +func (o *Options) WithRepositoryBaseURL(url string) *Options { + o.RepositoryBaseURL = url + return o +} + +// WithDisableLocalCache sets the client to work on a read-only file system +func (o *Options) WithDisableLocalCache() *Options { + o.DisableLocalCache = true + return o +} + +// WithDisableConsistentSnapshot sets the client to disable consistent snapshot +func (o *Options) WithDisableConsistentSnapshot() *Options { + o.DisableConsistentSnapshot = true + return o +} + +// WithFetcher sets the metadata fetcher +func (o *Options) WithFetcher(f fetcher.Fetcher) *Options { + o.Fetcher = f + return o +} + +// DefaultOptions returns an options struct for the public good instance +func DefaultOptions() *Options { + var opts Options + var err error + + opts.Root = DefaultRoot() + home, err := os.UserHomeDir() + if err != nil { + // Fall back to using a TUF repository in the temp location + home = os.TempDir() + } + opts.CachePath = filepath.Join(home, ".sigstore", "root") + opts.RepositoryBaseURL = DefaultMirror + + return &opts +} + +// DefaultRoot returns the root.json for the public good instance +func DefaultRoot() []byte { + var p = filepath.Join("repository", "root.json") + + b, err := embeddedRepo.ReadFile(p) + if err != nil { + // This should never happen. + // ReadFile from an embedded FS will never fail as long as + // the path is correct. If it fails, it would mean + // that the binary is not assembled as it should, and there + // is no way to recover from that. + panic(err) + } + + return b +} diff --git a/pkg/tuf/repository/tuf-repo-cdn.sigstore.dev/root.json b/pkg/tuf/repository/root.json similarity index 87% rename from pkg/tuf/repository/tuf-repo-cdn.sigstore.dev/root.json rename to pkg/tuf/repository/root.json index c3ea9cb6..ff409163 100644 --- a/pkg/tuf/repository/tuf-repo-cdn.sigstore.dev/root.json +++ b/pkg/tuf/repository/root.json @@ -2,8 +2,8 @@ "signed": { "_type": "root", "spec_version": "1.0", - "version": 7, - "expires": "2023-10-04T13:08:11Z", + "version": 8, + "expires": "2024-03-26T04:38:55Z", "keys": { "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { "keytype": "ecdsa-sha2-nistp256", @@ -120,21 +120,21 @@ "consistent_snapshot": true }, "signatures": [ - { - "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", - "sig": "3046022100c0610c0055ce5c4a52d054d7322e7b514d55baf44423d63aa4daa077cc60fd1f022100a097f2803f090fb66c42ead915a2c46ebe7db53a32bf18f2188275cc936f8bdd" - }, { "keyid": "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", - "sig": "304502203134f0468810299d5493a867c40630b341296b92e59c29821311d353343bb3a4022100e667ae3d304e7e3da0894c7425f6b9ecd917106841280e5cf6f3496ad5f8f68e" + "sig": "3044022024b8036b374f7071723f3f2cb1979c42e5da1910f0b178835ad546e3c360836302207140ccd408afcf8720dd9bea7f00325768c3aa47c22d531c849c974fd50e45dd" }, { "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", - "sig": "3045022037fe5f45426f21eaaf4730d2136f2b1611d6379688f79b9d1e3f61719997135c022100b63b022d7b79d4694b96f416d88aa4d7b1a3bff8a01f4fb51e0f42137c7d2d06" + "sig": "3046022100dcb1a96ecbfc05768a3c73726a92d681da78eaec068a9a0cfe13a12db672e44b022100a0dae7bc2e6b953e215f57cc614eb71660b9461d6dc86264b0b74a4f2e1307e1" }, { "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", - "sig": "3044022007cc8fcc4940809f2751ad5b535f4c5f53f5b4952f5b5696b09668e743306ac1022006dfcdf94e94c92163eeb1b47796db62cedaa730aa13aa61b573fe23714730f2" + "sig": "3046022100c4708d94077cb3d6dd60ebd2dd66545e7afb0464ce2593a5f23f6e3604b9f21e022100992e969cd5069eab17439b2ba60743fe422877bc1a1c46e935a6d5cb47b3cfc6" + }, + { + "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", + "sig": "3045022051faa6b6fc373730b97c1a4cd92d03efd98b83d4c9c93bf4f404d1f88ea2eb18022100f71ac1cd73dcba950f4210b12f9a05b8140b0490247c5339191e842b868155b4" } ] } \ No newline at end of file diff --git a/pkg/verify/signature.go b/pkg/verify/signature.go index 7cc62358..da6ca341 100644 --- a/pkg/verify/signature.go +++ b/pkg/verify/signature.go @@ -45,6 +45,7 @@ func VerifySignature(sigContent SignatureContent, verificationContent Verificati } else if msg := sigContent.MessageSignatureContent(); msg != nil { return errors.New("artifact must be provided to verify message signature") } + // handle an invalid signature content message return fmt.Errorf("signature content has neither an envelope or a message") } @@ -63,6 +64,7 @@ func VerifySignatureWithArtifact(sigContent SignatureContent, verificationConten } else if msg := sigContent.MessageSignatureContent(); msg != nil { return verifyMessageSignature(verifier, msg, artifact) } + // handle an invalid signature content message return fmt.Errorf("signature content has neither an envelope or a message") } @@ -81,6 +83,7 @@ func VerifySignatureWithArtifactDigest(sigContent SignatureContent, verification } else if msg := sigContent.MessageSignatureContent(); msg != nil { return verifyMessageSignatureWithArtifactDigest(verifier, msg, artifactDigest) } + // handle an invalid signature content message return fmt.Errorf("signature content has neither an envelope or a message") } @@ -92,6 +95,7 @@ func getSignatureVerifier(verificationContent VerificationContent, tm root.Trust } else if pk, ok := verificationContent.HasPublicKey(); ok { return tm.PublicKeyVerifier(pk.Hint()) } + return nil, fmt.Errorf("no public key or certificate found") }