From 2a09410c8846734515fce214a7f88f72050e57a4 Mon Sep 17 00:00:00 2001 From: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:49:39 -0500 Subject: [PATCH 1/3] Python sigstore implementation - Python sigstore implementation --- go.mod | 80 ++- go.sum | 365 ++++++++++++- internal/config/config.go | 149 ++++- internal/config/config_test.go | 265 +++++++++ internal/config/registry_sigstore_test.go | 58 ++ internal/runtimes/python/python.go | 403 ++------------ .../runtimes/python/python_sigstore_test.go | 198 +++++++ internal/runtimes/python/verifiers.go | 510 ++++++++++++++++++ .../sigstore/trusted-root-public-good.json | 114 ++++ internal/sigstore/verify.go | 76 +++ internal/sigstore/verify_test.go | 72 +++ runtime-registry.yaml | 22 +- 12 files changed, 1924 insertions(+), 388 deletions(-) create mode 100644 internal/config/registry_sigstore_test.go create mode 100644 internal/runtimes/python/python_sigstore_test.go create mode 100644 internal/runtimes/python/verifiers.go create mode 100644 internal/sigstore/trusted-root-public-good.json create mode 100644 internal/sigstore/verify.go create mode 100644 internal/sigstore/verify_test.go diff --git a/go.mod b/go.mod index 67cfa96..946d15c 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,14 @@ module github.com/clean-dependency-project/cdprun -go 1.24.0 +go 1.25.0 require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/ProtonMail/gopenpgp/v2 v2.8.3 github.com/google/go-github/v57 v57.0.0 + github.com/sigstore/sigstore-go v1.2.1 github.com/urfave/cli/v2 v2.27.6 - golang.org/x/text v0.31.0 + golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/sqlite v1.5.7 gorm.io/gorm v1.30.0 @@ -16,21 +17,80 @@ require ( require ( github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect + github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect + github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.25.2 // indirect + github.com/go-openapi/errors v0.22.7 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.6 // indirect + github.com/go-openapi/loads v0.23.3 // indirect + github.com/go-openapi/runtime v0.32.3 // indirect + github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect + github.com/go-openapi/spec v0.22.5 // indirect + github.com/go-openapi/strfmt v0.26.3 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect + github.com/go-openapi/validate v0.25.3 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/google/certificate-transparency-go v1.3.3 // indirect + github.com/google/go-containerregistry v0.21.6 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/in-toto/attestation v1.2.0 // indirect + github.com/in-toto/in-toto-golang v0.11.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/stretchr/testify v1.11.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/sigstore/protobuf-specs v0.5.1 // indirect + github.com/sigstore/rekor v1.5.2 // indirect + github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect + github.com/sigstore/sigstore v1.10.8 // indirect + github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect + github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef // indirect + github.com/transparency-dev/formats v0.1.1 // indirect + github.com/transparency-dev/merkle v0.0.2 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/sys v0.38.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + k8s.io/klog/v2 v2.140.0 // indirect ) diff --git a/go.sum b/go.sum index aa2c63a..b4e1663 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,35 @@ +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= +cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= +cloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE= +cloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= +cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY= +cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e h1:VsUbObBMxXlc23Eb9VeeJYE4jvTs87qa5RqSN2U5FJU= +filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e/go.mod h1:32qQ5yj3R24Eu03iWFWchdC3OB653wPvoepWejkefbY= +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.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 h1:MaKvxE6D0KkjOg6Wd9M00iqP5PR0kUxCfiezes4JweM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0/go.mod h1:i2h9fsTFKZorh8RdV2IcSUf/Qj98GlTkrTvUbX/s8as= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= @@ -6,87 +38,386 @@ github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ek github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/gopenpgp/v2 v2.8.3 h1:1jHlELwCR00qovx2B50DkL/FjYwt/P91RnlsqeOp2Hs= github.com/ProtonMail/gopenpgp/v2 v2.8.3/go.mod h1:LiuOTbnJit8w9ZzOoLscj0kmdALY7hfoCVh5Qlb0bcg= +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-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= +github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/kms v1.52.0 h1:QNtg+Mtj1zmepk568+UKBD5DFfqh+ESTUUqQT27JkQc= +github.com/aws/aws-sdk-go-v2/service/kms v1.52.0/go.mod h1:Y0+uxvxz6ib4KktRdK0V4X45Vcs/JyYoz8H71pO8xeI= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= +github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= +github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= +github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= +github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= +github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= +github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= +github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= +github.com/go-openapi/runtime v0.32.3 h1:J7Ycy5DJmhhP1By3NifhRUjnkXTrk21qbeqSULjwX8U= +github.com/go-openapi/runtime v0.32.3/go.mod h1:/WTQi0fa5DiGnnCXQKsTkSm15OzJp8Uz3H2t+67TBr4= +github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= +github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= +github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo= +github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74= +github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= +github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= +github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs= +github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +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.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= +github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.21.6 h1:T+yqQIlJXKrM98Om4DlW3GoWQAmhZuLMwoDOvVrtiUM= +github.com/google/go-containerregistry v0.21.6/go.mod h1:U7MMSBIJynke2MVQrQk19NP9k/uQsGz/h0amIFSHMbo= github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs= github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= +github.com/google/trillian v1.7.3/go.mod h1:qh8iy4x/GvnVXUBd5pK4oncuT1Y9vVYfibQVsR/WpKg= +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/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= +github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/in-toto/attestation v1.2.0 h1:aPRUZ3azbqD7yEBD5fP3TD8Dszf+YHo284SOcpahjQk= +github.com/in-toto/attestation v1.2.0/go.mod h1:r79G45gOmzPismgObLSL+rZTFxUgZLOQJI6LofTZgXk= +github.com/in-toto/in-toto-golang v0.11.0 h1:nfidMYBFx+E0lnmX5KUnN2Pdm8zdNKal1ayjJuzzRoA= +github.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMeKfVoWsWeFMDCMtw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b h1:ZGiXF8sz7PDk6RgkP+A/SFfUD0ZR/AgG6SpRNEDKZy8= +github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b/go.mod h1:hQmNrgofl+IY/8L+n20H6E6PWBBTokdsv+q49j0QhsU= +github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= +github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/letsencrypt/boulder v0.20260309.0 h1:kZynrxK3QfqLGx6hhoz+Rfs3hgltJs1p9Mp+4+VwnY0= +github.com/letsencrypt/boulder v0.20260309.0/go.mod h1:yG8lj8pNPZ8taq3oNdTpfBS+eC74IaEuiewqzVpXiWE= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +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/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +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.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= +github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= +github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= +github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= +github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= +github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U= +github.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= +github.com/sigstore/rekor v1.5.2 h1:k6pX4o1zFAzAvDbXiVIp5IHj1b0wcDaxsbsbNpuRO8o= +github.com/sigstore/rekor v1.5.2/go.mod h1:WkMnITBccOFauPkT6yte74tF5gC83pefKRGZvNOsbjI= +github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F6m3Bo/f59bZo5dv1sTIfUnQqVnepIdDV24KoDw= +github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= +github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= +github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= +github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= +github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8/go.mod h1:YiTpAsxoWXhF9KlLOVWCh7BckN5cYO8X01WufDq1ido= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8 h1:MxpAIMZVzn0Tpbarc9ax1I498oQBp7oYSMgoMSsOmKI= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8/go.mod h1:bnAUEkFNam6STvkVZhptVwWzWR5pS24CEtQ+lhxu7S0= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8 h1:1DGe4/clcdOnkz5MINEczWlmEvjUtZd+AjPPT/cBhQ8= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8/go.mod h1:6IDFhpgxtzqbnzrFkyegbj7RfWwKeRrb3/+xAD1Wp+Y= +github.com/sigstore/timestamp-authority/v2 v2.1.2 h1:7DDhnknLL4w8VwomyvW2W8qblOS9LDR8oihna+jc7Ls= +github.com/sigstore/timestamp-authority/v2 v2.1.2/go.mod h1:o6rAVZceFyejClIj/uStRNIemP16bVMZtbMmhk6pr0U= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +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.4.2-0.20260407074541-7e8f69f906ef h1:jJac5InhEfD0Z46/d5RayZjoavf/se7bPZpOgg8GLrM= +github.com/theupdateframework/go-tuf/v2 v2.4.2-0.20260407074541-7e8f69f906ef/go.mod h1:cLUSJ2cgR194lNWfp+TJT4P8PX7qGleCXdudqlCMtOE= +github.com/tink-crypto/tink-go-awskms/v3 v3.0.0 h1:XSohRhCkXAVI0iaCnWB/GS05TEmpnKurQmzaY1jzt3Y= +github.com/tink-crypto/tink-go-awskms/v3 v3.0.0/go.mod h1:+7MXsShLzVbSQ6dI0Pe4JuZM52jD1jQ1itAygd/MDsA= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= +github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0 h1:eXuNqgrcYelxU1MVikOJDP3wTS5lvihM4ntoAbAMfvs= +github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0/go.mod h1:3RhcxAqek6xUlRFmJifvU4CYLZN60KMQdIKqpZAZJG0= +github.com/tink-crypto/tink-go/v2 v2.6.0 h1:+KHNBHhWH33Vn+igZWcsgdEPUxKwBMEe0QC60t388v4= +github.com/tink-crypto/tink-go/v2 v2.6.0/go.mod h1:2WbBA6pfNsAfBwDCggboaHeB2X29wkU8XHtGwh2YIk8= +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/formats v0.1.1 h1:4bVHJc+KdBgpA1OJD1yjI+g0i5Z1graCppTMH8lWKJI= +github.com/transparency-dev/formats v0.1.1/go.mod h1:qtZ8goRuJ8FTBG9c9+Bj0rn2rUG7eG/AUTkr+Aw3jFw= +github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= +github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.step.sm/crypto v0.77.7 h1:6azC+pD678Vjju8yXnMDHCZJ+HzFaEmL3sCryiezTIA= +go.step.sm/crypto v0.77.7/go.mod h1:OW/2sEHwTtDKq70PvSQ5B0JGy/CrLyDKOiVy3YvZMTQ= +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= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= 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.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= 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.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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.280.0 h1:F4OfEHZhZh6a7uTufJAXXVd/2TQ8EjM4vZH+jX/vFYk= +google.golang.org/api v0.280.0/go.mod h1:oGKmPZRDoD3vdkf6MA7F4VNkR1rxCiuaPSkhsf3EolU= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -96,3 +427,9 @@ gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/internal/config/config.go b/internal/config/config.go index 5a78137..559edcd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -52,8 +52,8 @@ type GlobalConfig struct { DownloadTimeout string `yaml:"download_timeout"` AutoDownloadAllPlatforms bool `yaml:"auto_download_all_platforms"` Storage StorageConfig `yaml:"storage"` - IgnoreFile string `yaml:"ignore_file"` // Path to file listing platform-specific versions to ignore - UnsupportedFile string `yaml:"unsupported_file"` // Path to file listing unsupported runtime version prefixes + IgnoreFile string `yaml:"ignore_file"` // Path to file listing platform-specific versions to ignore + UnsupportedFile string `yaml:"unsupported_file"` // Path to file listing unsupported runtime version prefixes PackagingExecution PackagingExecutionConfig `yaml:"packaging_execution"` } @@ -165,9 +165,25 @@ type Verification struct { type VerificationMethods struct { Checksum ChecksumVerification `yaml:"checksum"` GPG GPGVerification `yaml:"gpg"` + Sigstore SigstoreVerification `yaml:"sigstore"` ClamAV ClamAVVerification `yaml:"clamav"` } +// SigstoreVerification represents upstream Sigstore bundle verification configuration. +// When enabled, it takes precedence over GPG for the runtime. +type SigstoreVerification struct { + Enabled bool `yaml:"enabled"` + BundleSuffix string `yaml:"bundle_suffix"` // defaults to ".sigstore" + Identities []SigstoreIdentityEntry `yaml:"identities"` +} + +// SigstoreIdentityEntry pins the expected signing identity for a release line. +type SigstoreIdentityEntry struct { + VersionPrefix string `yaml:"version_prefix"` // major.minor, e.g. "3.15" + CertIdentity string `yaml:"cert_identity"` + OIDCIssuer string `yaml:"oidc_issuer"` +} + // ChecksumVerification represents checksum verification configuration. type ChecksumVerification struct { Enabled bool `yaml:"enabled"` @@ -361,7 +377,7 @@ func (v *Verification) Validate() error { return ErrChecksumPatternRequired } } - if v.Methods.GPG.Enabled { + if v.Methods.GPG.Enabled && !v.Methods.Sigstore.Enabled { if v.Methods.GPG.SignaturePattern == "" { return ErrSignaturePatternRequired } @@ -372,9 +388,136 @@ func (v *Verification) Validate() error { return ErrClamAVImageRequired } } + if err := v.Methods.Sigstore.Validate(); err != nil { + return fmt.Errorf("sigstore: %w", err) + } return nil } +// ErrSigstoreIdentityRequired is returned when sigstore verification is enabled without identities. +var ErrSigstoreIdentityRequired = errors.New("at least one identity is required when sigstore is enabled") + +// ErrSigstoreIdentityIncomplete is returned when an identity entry is missing required fields. +var ErrSigstoreIdentityIncomplete = errors.New("sigstore identity entries require version_prefix, cert_identity, and oidc_issuer") + +// Validate validates sigstore verification configuration. +func (s *SigstoreVerification) Validate() error { + if !s.Enabled { + return nil + } + if len(s.Identities) == 0 { + return ErrSigstoreIdentityRequired + } + for _, id := range s.Identities { + if strings.TrimSpace(id.VersionPrefix) == "" || + strings.TrimSpace(id.CertIdentity) == "" || + strings.TrimSpace(id.OIDCIssuer) == "" { + return ErrSigstoreIdentityIncomplete + } + } + return nil +} + +// BundleSuffixOrDefault returns the configured bundle suffix or ".sigstore". +func (s *SigstoreVerification) BundleSuffixOrDefault() string { + if s.BundleSuffix == "" { + return ".sigstore" + } + return s.BundleSuffix +} + +// SigstoreEnabled reports whether sigstore verification is active for the runtime. +func (r *Runtime) SigstoreEnabled() bool { + return r.Verification.Enabled && r.Verification.Methods.Sigstore.Enabled +} + +// GPGEnabled reports whether GPG verification is active. Sigstore takes precedence, +// so GPG is considered disabled whenever sigstore is enabled. +func (r *Runtime) GPGEnabled() bool { + if r.SigstoreEnabled() { + return false + } + return r.Verification.Enabled && r.Verification.Methods.GPG.Enabled +} + +// ErrSigstoreVersionInvalid is returned when a version cannot be parsed to major.minor. +var ErrSigstoreVersionInvalid = errors.New("could not parse major.minor from version") + +// ResolveSigstoreIdentityEntry returns the identity entry whose version_prefix +// (major.minor) longest-matches the given release version. +// +// It returns (nil, nil) when no entry matches — this is the "ignore old +// releases" case: Sigstore verification is skipped for that version (ClamAV +// still runs). A non-nil error is returned only when the version cannot be +// parsed to a major.minor prefix. +func (r *Runtime) ResolveSigstoreIdentityEntry(version string) (*SigstoreIdentityEntry, error) { + if !r.SigstoreEnabled() { + return nil, nil + } + prefix, err := majorMinorPrefix(version) + if err != nil { + return nil, err + } + releaseParts := strings.Split(prefix, ".") + var best *SigstoreIdentityEntry + var bestParts []string + for i := range r.Verification.Methods.Sigstore.Identities { + entry := &r.Verification.Methods.Sigstore.Identities[i] + entryParts := strings.Split(strings.TrimSpace(entry.VersionPrefix), ".") + if !versionPrefixMatch(releaseParts, entryParts) { + continue + } + if best == nil || len(entryParts) > len(bestParts) { + best = entry + bestParts = entryParts + } + } + return best, nil +} + +// versionPrefixMatch reports whether an identity entry's version_prefix matches +// a release's major.minor components. Matching is component-wise so "3.1" does +// not match "3.14" (unlike a raw string prefix). The entry may specify fewer +// components than the release to act as a catch-all (e.g. "3" matches "3.14"). +func versionPrefixMatch(releaseParts, entryParts []string) bool { + if len(entryParts) == 0 || entryParts[0] == "" || len(entryParts) > len(releaseParts) { + return false + } + for i := range entryParts { + if entryParts[i] != releaseParts[i] { + return false + } + } + return true +} + +// majorMinorPrefix extracts the "major.minor" prefix from a version string. +// It tolerates pre-release suffixes (e.g. "3.15.0b3" -> "3.15") and missing +// patch components (e.g. "3.15" -> "3.15"). +func majorMinorPrefix(version string) (string, error) { + version = strings.TrimSpace(version) + if version == "" { + return "", ErrSigstoreVersionInvalid + } + // Trim a leading 'v' if present. + version = strings.TrimPrefix(version, "v") + // Drop pre-release/build suffix after the first non-numeric segment past the patch. + clean := version + for i := 0; i < len(clean); i++ { + c := clean[i] + if c == '.' || (c >= '0' && c <= '9') { + continue + } + clean = clean[:i] + break + } + parts := strings.Split(strings.TrimSuffix(clean, "."), ".") + if len(parts) < 2 || parts[0] == "" || parts[1] == "" { + return "", ErrSigstoreVersionInvalid + } + return parts[0] + "." + parts[1], nil +} + // GetEnabledRuntimes returns a map of enabled runtime configurations. func (c *Config) GetEnabledRuntimes() map[string]Runtime { enabled := make(map[string]Runtime) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 2ff11c1..5e12231 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1003,3 +1003,268 @@ func TestGetConfiguredPlatforms_UsingSupportedArchitectures(t *testing.T) { }) } } + +func TestSigstoreValidation(t *testing.T) { + tests := []struct { + name string + cfg SigstoreVerification + wantErr bool + }{ + { + name: "disabled is valid", + cfg: SigstoreVerification{Enabled: false}, + wantErr: false, + }, + { + name: "enabled without identities", + cfg: SigstoreVerification{Enabled: true}, + wantErr: true, + }, + { + name: "enabled with incomplete identity", + cfg: SigstoreVerification{Enabled: true, Identities: []SigstoreIdentityEntry{ + {VersionPrefix: "3.15", CertIdentity: "", OIDCIssuer: "https://github.com/login/oauth"}, + }}, + wantErr: true, + }, + { + name: "enabled with valid identities", + cfg: SigstoreVerification{Enabled: true, Identities: []SigstoreIdentityEntry{ + {VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate() + if tt.wantErr && err == nil { + t.Errorf("expected error, got nil") + } + if !tt.wantErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} + +func TestResolveSigstoreIdentityEntry(t *testing.T) { + runtime := Runtime{ + Enabled: true, + Verification: Verification{ + Enabled: true, + Methods: VerificationMethods{ + Sigstore: SigstoreVerification{ + Enabled: true, + BundleSuffix: ".sigstore", + Identities: []SigstoreIdentityEntry{ + {VersionPrefix: "3.7", CertIdentity: "nad@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + {VersionPrefix: "3.13", CertIdentity: "thomas@python.org", OIDCIssuer: "https://accounts.google.com"}, + {VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + }, + }, + }, + }, + } + + tests := []struct { + name string + version string + wantCert string + wantIssuer string + wantNil bool // no matching identity row -> skip (nil, nil) + wantErr bool // unparseable version -> error + }{ + {name: "3.15.0b3 -> hugo", version: "3.15.0b3", wantCert: "hugo@python.org", wantIssuer: "https://github.com/login/oauth"}, + {name: "v3.15.0 -> hugo", version: "v3.15.0", wantCert: "hugo@python.org", wantIssuer: "https://github.com/login/oauth"}, + {name: "3.13.1 -> thomas", version: "3.13.1", wantCert: "thomas@python.org", wantIssuer: "https://accounts.google.com"}, + {name: "3.7.0 -> nad", version: "3.7.0", wantCert: "nad@python.org", wantIssuer: "https://github.com/login/oauth"}, + {name: "3.15 -> hugo (no patch)", version: "3.15", wantCert: "hugo@python.org", wantIssuer: "https://github.com/login/oauth"}, + {name: "unknown prefix skips", version: "2.7.0", wantNil: true}, + {name: "empty version fails", version: "", wantErr: true}, + {name: "single component fails", version: "3", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + entry, err := runtime.ResolveSigstoreIdentityEntry(tt.version) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error, got entry=%v", entry) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tt.wantNil { + if entry != nil { + t.Fatalf("expected nil entry (skip), got %+v", entry) + } + return + } + if entry == nil { + t.Fatalf("expected non-nil entry, got nil") + } + if entry.CertIdentity != tt.wantCert { + t.Errorf("cert: want %q, got %q", tt.wantCert, entry.CertIdentity) + } + if entry.OIDCIssuer != tt.wantIssuer { + t.Errorf("issuer: want %q, got %q", tt.wantIssuer, entry.OIDCIssuer) + } + }) + } +} + +func TestResolveSigstoreIdentityEntry_ComponentMatchNotStringPrefix(t *testing.T) { + // A "3.1" row must NOT match release "3.14" via string prefix; matching is + // component-wise. Likewise "3.14" must not match "3.140". + runtime := Runtime{ + Enabled: true, + Verification: Verification{ + Enabled: true, + Methods: VerificationMethods{ + Sigstore: SigstoreVerification{ + Enabled: true, + BundleSuffix: ".sigstore", + Identities: []SigstoreIdentityEntry{ + {VersionPrefix: "3.1", CertIdentity: "impostor@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + {VersionPrefix: "3.14", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + }, + }, + }, + }, + } + + // 3.14.2 resolves to the 3.14 row, not the 3.1 row. + entry, err := runtime.ResolveSigstoreIdentityEntry("3.14.2") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if entry == nil || entry.CertIdentity != "hugo@python.org" { + t.Errorf("3.14.2 -> entry=%+v, want hugo@python.org", entry) + } + + // 3.10.0 must not match the 3.1 row (string prefix would wrongly match). + entry, err = runtime.ResolveSigstoreIdentityEntry("3.10.0") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if entry != nil { + t.Errorf("3.10.0 -> entry=%+v, want nil (no component-wise match)", entry) + } + + // A single-component "3" catch-all row matches any 3.x release. + majorOnly := Runtime{ + Enabled: true, + Verification: Verification{ + Enabled: true, + Methods: VerificationMethods{ + Sigstore: SigstoreVerification{ + Enabled: true, + Identities: []SigstoreIdentityEntry{ + {VersionPrefix: "3", CertIdentity: "catchall@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + }, + }, + }, + }, + } + entry, err = majorOnly.ResolveSigstoreIdentityEntry("3.14.2") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if entry == nil || entry.CertIdentity != "catchall@python.org" { + t.Errorf("3.14.2 with major-only row -> entry=%+v, want catchall@python.org", entry) + } +} + +func TestSigstoreOverridesGPG(t *testing.T) { + runtime := Runtime{ + Enabled: true, + Verification: Verification{ + Enabled: true, + Methods: VerificationMethods{ + GPG: GPGVerification{Enabled: true, SignaturePattern: ".asc"}, + Sigstore: SigstoreVerification{Enabled: true, Identities: []SigstoreIdentityEntry{{VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}}}, + }, + }, + } + if !runtime.SigstoreEnabled() { + t.Error("expected sigstore enabled") + } + if runtime.GPGEnabled() { + t.Error("expected GPG disabled when sigstore enabled") + } +} + +func TestResolveSigstoreIdentityEntry_SkipOldReleases(t *testing.T) { + rt := Runtime{ + Enabled: true, + Verification: Verification{ + Enabled: true, + Methods: VerificationMethods{ + Sigstore: SigstoreVerification{Enabled: true, Identities: []SigstoreIdentityEntry{ + {VersionPrefix: "3.14", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + {VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}, + }}, + }, + }, + } + + tests := []struct { + name string + version string + wantNil bool + wantCert string + wantIssuer string + wantErr bool + }{ + {name: "3.15.0b3 covered -> entry", version: "3.15.0b3", wantCert: "hugo@python.org", wantIssuer: "https://github.com/login/oauth"}, + {name: "3.14.2 covered -> entry", version: "3.14.2", wantCert: "hugo@python.org", wantIssuer: "https://github.com/login/oauth"}, + {name: "3.13 old release -> skip (nil)", version: "3.13.0", wantNil: true}, + {name: "2.7 old release -> skip (nil)", version: "2.7.0", wantNil: true}, + {name: "empty version -> parse error", version: "", wantErr: true}, + {name: "single component -> parse error", version: "3", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + entry, err := rt.ResolveSigstoreIdentityEntry(tt.version) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error, got entry=%v", entry) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tt.wantNil { + if entry != nil { + t.Errorf("expected nil entry (skip), got %+v", entry) + } + return + } + if entry == nil { + t.Fatal("expected non-nil entry, got nil") + } + if entry.CertIdentity != tt.wantCert { + t.Errorf("cert: want %q, got %q", tt.wantCert, entry.CertIdentity) + } + if entry.OIDCIssuer != tt.wantIssuer { + t.Errorf("issuer: want %q, got %q", tt.wantIssuer, entry.OIDCIssuer) + } + }) + } +} + +func TestResolveSigstoreIdentityEntry_DisabledReturnsNil(t *testing.T) { + rt := Runtime{Enabled: true, Verification: Verification{Enabled: true, Methods: VerificationMethods{ + Sigstore: SigstoreVerification{Enabled: false}, + }}} + entry, err := rt.ResolveSigstoreIdentityEntry("3.15.0") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if entry != nil { + t.Errorf("expected nil entry when sigstore disabled, got %+v", entry) + } +} diff --git a/internal/config/registry_sigstore_test.go b/internal/config/registry_sigstore_test.go new file mode 100644 index 0000000..5325bd8 --- /dev/null +++ b/internal/config/registry_sigstore_test.go @@ -0,0 +1,58 @@ +package config + +import "testing" + +func TestRegistry_PythonSigstore(t *testing.T) { + cfg, err := LoadConfig("../../runtime-registry.yaml") + if err != nil { + t.Fatalf("LoadConfig() error: %v", err) + } + p, ok := cfg.Runtimes["python"] + if !ok { + t.Fatal("python runtime not found") + } + if !p.SigstoreEnabled() { + t.Error("expected python sigstore enabled") + } + if p.GPGEnabled() { + t.Error("expected python GPG disabled when sigstore enabled") + } + // 3.15 is covered by the identity table. + entry, err := p.ResolveSigstoreIdentityEntry("3.15.0b3") + if err != nil { + t.Fatalf("resolve 3.15.0b3: %v", err) + } + if entry == nil || entry.CertIdentity != "hugo@python.org" || entry.OIDCIssuer != "https://github.com/login/oauth" { + t.Errorf("3.15.0b3 -> entry=%+v", entry) + } + // 3.14 is also covered. + entry, err = p.ResolveSigstoreIdentityEntry("3.14.0") + if err != nil { + t.Fatalf("resolve 3.14.0: %v", err) + } + if entry == nil || entry.CertIdentity != "hugo@python.org" || entry.OIDCIssuer != "https://github.com/login/oauth" { + t.Errorf("3.14.0 -> entry=%+v", entry) + } + // 3.13 is intentionally NOT covered (old release) -> entry resolver skips. + entry, err = p.ResolveSigstoreIdentityEntry("3.13.0") + if err != nil { + t.Fatalf("resolve entry 3.13.0: %v", err) + } + if entry != nil { + t.Errorf("expected nil entry (skip) for old release 3.13.0, got %+v", entry) + } + // Only the 3.14–3.17 lines are configured. + wantLines := map[string]bool{"3.14": false, "3.15": false, "3.16": false, "3.17": false} + for _, id := range p.Verification.Methods.Sigstore.Identities { + if _, ok := wantLines[id.VersionPrefix]; ok { + wantLines[id.VersionPrefix] = true + } else { + t.Errorf("unexpected identity row for version_prefix %q", id.VersionPrefix) + } + } + for line, found := range wantLines { + if !found { + t.Errorf("expected identity row for %q not found", line) + } + } +} diff --git a/internal/runtimes/python/python.go b/internal/runtimes/python/python.go index b88c10f..7aff22a 100644 --- a/internal/runtimes/python/python.go +++ b/internal/runtimes/python/python.go @@ -5,19 +5,16 @@ package python import ( "context" - "embed" "encoding/json" "fmt" "log/slog" "net/http" - "os" "path/filepath" "strings" "time" "github.com/clean-dependency-project/cdprun/internal/config" "github.com/clean-dependency-project/cdprun/internal/endoflife" - "github.com/clean-dependency-project/cdprun/internal/gpg" "github.com/clean-dependency-project/cdprun/internal/platform" "github.com/clean-dependency-project/cdprun/internal/runtime" "github.com/clean-dependency-project/cdprun/internal/version" @@ -27,9 +24,6 @@ const ( Python = "python" ) -//go:embed keys -var embeddedKeysFS embed.FS - // PythonRelease represents a Python release from the endoflife API type PythonRelease struct { Cycle string `json:"cycle"` @@ -397,12 +391,11 @@ func (a *PythonAdapter) CreateDownloadTasks(version endoflife.VersionInfo, platf // shouldDownloadVerificationFiles determines if verification files should be downloaded based on config func (a *PythonAdapter) shouldDownloadVerificationFiles() bool { - // Enable GPG verification for Python - we have embedded keys available - if a.config != nil && a.config.Verification.Methods.GPG.Enabled { - return true + if a.config == nil { + return true // Default to enabled if no config specified } - // Default to enabled if no config specified (use embedded keys) - return true + // Sigstore takes precedence over GPG; either method needs sidecar files. + return a.config.SigstoreEnabled() || a.config.GPGEnabled() } // platformVersion pairs a platform with its specific version for verification tasks @@ -413,48 +406,49 @@ type platformVersion struct { // createVerificationTasksWithVersions creates download tasks for verification // files using the specific version recorded for each platform's main artifact. +// Sigstore takes precedence over GPG: when enabled, .sigstore sidecars are +// downloaded and .asc tasks are suppressed entirely. func (a *PythonAdapter) createVerificationTasksWithVersions(platformVersions []platformVersion, outputDir, userAgent string) []runtime.DownloadTask { - var tasks []runtime.DownloadTask - - // Get base URL from config or use default baseURL := "https://www.python.org/ftp/python" if a.config != nil && a.config.Download.BaseURL != "" { baseURL = a.config.Download.BaseURL } - // Add GPG signature files if GPG verification is enabled - if a.config == nil || a.config.Verification.Methods.GPG.Enabled { - for _, pv := range platformVersions { - versionBaseURL := fmt.Sprintf("%s/%s", baseURL, pv.version) - - // Get the main file URL to derive the signature URL - mainURL := a.constructDownloadURL(pv.version, pv.plat) - if mainURL == "" { - continue - } + switch { + case a.config != nil && a.config.SigstoreEnabled(): + return a.buildSidecarTasks(platformVersions, baseURL, outputDir, userAgent, + a.config.Verification.Methods.Sigstore.BundleSuffixOrDefault(), "sigstore") + case a.config == nil || a.config.GPGEnabled(): + return a.buildSidecarTasks(platformVersions, baseURL, outputDir, userAgent, ".asc", "signature") + } + return nil +} - // Extract filename and create signature URL - parts := strings.Split(mainURL, "/") - fileName := parts[len(parts)-1] - - // Python uses .asc files for GPG signatures - signatureFileName := fileName + ".asc" - signatureURL := versionBaseURL + "/" + signatureFileName - - signatureTask := runtime.DownloadTask{ - URL: signatureURL, - OutputPath: filepath.Join(outputDir, signatureFileName), - Platform: pv.plat, - Runtime: Python, - Version: pv.version, - FileType: "signature", - Headers: map[string]string{"User-Agent": userAgent}, - Optional: true, // Signature files are optional - } - tasks = append(tasks, signatureTask) +// buildSidecarTasks emits optional sidecar download tasks (one per main +// artifact) deriving each sidecar URL from the main artifact's URL plus suffix. +// Used for both Sigstore (.sigstore) and GPG (.asc) sidecars. +func (a *PythonAdapter) buildSidecarTasks(platformVersions []platformVersion, baseURL, outputDir, userAgent, suffix, fileType string) []runtime.DownloadTask { + tasks := make([]runtime.DownloadTask, 0, len(platformVersions)) + for _, pv := range platformVersions { + mainURL := a.constructDownloadURL(pv.version, pv.plat) + if mainURL == "" { + continue } - } + parts := strings.Split(mainURL, "/") + fileName := parts[len(parts)-1] + sidecarName := fileName + suffix + tasks = append(tasks, runtime.DownloadTask{ + URL: fmt.Sprintf("%s/%s/%s", baseURL, pv.version, sidecarName), + OutputPath: filepath.Join(outputDir, sidecarName), + Platform: pv.plat, + Runtime: Python, + Version: pv.version, + FileType: fileType, + Headers: map[string]string{"User-Agent": userAgent}, + Optional: true, // missing sidecars skip verification rather than failing + }) + } return tasks } @@ -510,7 +504,12 @@ func (a *PythonAdapter) ProcessDownloads(ctx context.Context, tasks []runtime.Do } // GetVerificationStrategy returns the verification strategy for Python downloads. +// Sigstore takes precedence over GPG: when sigstore is enabled, the Sigstore +// verifier is used and GPG is ignored entirely. func (a *PythonAdapter) GetVerificationStrategy() runtime.VerificationStrategy { + if a.config != nil && a.config.SigstoreEnabled() { + return NewPythonSigstoreVerifier(a.config, a.stdout) + } return NewPythonGPGVerifier(a.stdout) } @@ -719,319 +718,3 @@ func (a *PythonAdapter) validateVersionPolicy(version endoflife.VersionInfo) err // If version not found in policy, reject the download return fmt.Errorf("python version %s not found in policy file %s", version.Version, a.config.PolicyFile) } - -// NoOpVerifier is a no-operation verifier that always succeeds. -// Used when GPG key loading fails as a fallback. -type NoOpVerifier struct { - runtimeName string - logger *slog.Logger -} - -// Verify always returns nil (success) for no-op verification. -func (v *NoOpVerifier) Verify(ctx context.Context, result runtime.DownloadResult) error { - v.logger.Warn("no-op verification - GPG keys not available", - "runtime", v.runtimeName, - "file", result.LocalPath) - return nil -} - -// GetType returns the verification strategy type. -func (v *NoOpVerifier) GetType() string { - return "noop" -} - -// RequiresAdditionalFiles returns false since no-op verification doesn't need extra files. -func (v *NoOpVerifier) RequiresAdditionalFiles() bool { - return false -} - -// PythonGPGVerifier implements GPG signature verification for Python downloads -type PythonGPGVerifier struct { - keyRing gpg.KeyRing - logger *slog.Logger -} - -// NewPythonGPGVerifier creates a new GPG verifier for Python downloads using embedded keys -func NewPythonGPGVerifier(logger *slog.Logger) runtime.VerificationStrategy { - if logger == nil { - logger = slog.Default() - } - - verifier := &PythonGPGVerifier{ - logger: logger, - } - - // Load embedded keys using custom logic for numbered files - keyRing, err := loadPythonEmbeddedKeys(embeddedKeysFS, "keys", logger) - if err != nil { - logger.Error("Failed to load Python GPG keys from embedded filesystem", "error", err) - // Return a no-op verifier if key loading fails - return &NoOpVerifier{runtimeName: "python", logger: logger} - } - - verifier.keyRing = keyRing - logger.Info("Loaded Python GPG verification keys", "source", "embedded") - return verifier -} - -// loadPythonEmbeddedKeys loads GPG keys from embedded filesystem, handling numbered files -func loadPythonEmbeddedKeys(fs embed.FS, keysPath string, logger *slog.Logger) (gpg.KeyRing, error) { - files, err := fs.ReadDir(keysPath) - if err != nil { - return nil, fmt.Errorf("failed to read embedded keys directory: %w", err) - } - - keyRing := gpg.NewRealKeyRing() - keyCount := 0 - - for _, file := range files { - if file.IsDir() { - continue - } - - fileName := file.Name() - - // Skip .gitkeep files - if fileName == ".gitkeep" { - continue - } - - filePath := filepath.Join(keysPath, fileName) - keyData, err := fs.ReadFile(filePath) - if err != nil { - logger.Warn("Failed to read embedded key file", "file", fileName, "error", err) - continue - } - - // Check if this looks like a GPG key file - keyContent := string(keyData) - if !strings.Contains(keyContent, "BEGIN PGP PUBLIC KEY BLOCK") { - logger.Debug("Skipping non-GPG file", "file", fileName) - continue - } - - if len(keyData) > 100*1024*1024 { // 100MB max - logger.Warn("Embedded key file exceeds maximum allowed size", "file", fileName) - continue - } - - key, err := gpg.NewRealKey(keyContent) - if err != nil { - logger.Warn("Failed to parse embedded key", "file", fileName, "error", err) - continue - } - - // Validate key (check if revoked, etc.) - if key.IsRevoked() { - logger.Warn("Skipping revoked key", "file", fileName, "fingerprint", key.GetFingerprint()) - continue - } - - if err := keyRing.AddKey(key); err != nil { - logger.Warn("Failed to add key to keyring", "file", fileName, "error", err) - continue - } - - keyCount++ - logger.Debug("Loaded Python GPG key", "file", fileName, "fingerprint", key.GetFingerprint()) - } - - if keyCount == 0 { - return nil, fmt.Errorf("no valid GPG keys found in embedded directory") - } - - logger.Info("Successfully loaded Python GPG keys", "count", keyCount) - return keyRing, nil -} - -// Verify implements the VerificationStrategy interface -func (v *PythonGPGVerifier) Verify(ctx context.Context, result runtime.DownloadResult) error { - if v.keyRing == nil { - v.logger.Error("No GPG keyring available for verification", - "file", result.LocalPath, - "runtime", result.Runtime) - return fmt.Errorf("no GPG keyring available for verification") - } - - if result.Error != nil { - v.logger.Error("Cannot verify failed download", - "file", result.LocalPath, - "download_error", result.Error, - "runtime", result.Runtime) - return fmt.Errorf("cannot verify failed download: %w", result.Error) - } - - v.logger.Info("Starting Python GPG verification", - "file", result.LocalPath, - "url", result.URL, - "runtime", result.Runtime, - "version", result.Version, - "file_size", result.FileSize) - - // Determine file type based on filename - fileName := filepath.Base(result.LocalPath) - - // For signature files (.asc), these are detached signatures in Python's case - // We should skip verification of signature files themselves - if strings.HasSuffix(fileName, ".asc") { - v.logger.Debug("Detected signature file, skipping direct verification", - "file", result.LocalPath, - "runtime", result.Runtime, - "note", "Signature files are used to verify main files") - return nil // Don't verify signature files directly - } - - // For main files, look for corresponding signature files - v.logger.Debug("Detected main file, looking for signature file", - "file", result.LocalPath, - "runtime", result.Runtime) - return v.verifyMainFile(result) -} - -// GetType returns the verification strategy type -func (v *PythonGPGVerifier) GetType() string { - return "python-gpg" -} - -// RequiresAdditionalFiles returns true since we need signature files -func (v *PythonGPGVerifier) RequiresAdditionalFiles() bool { - return true -} - -// verifyMainFile verifies a main download file by looking for its signature -func (v *PythonGPGVerifier) verifyMainFile(result runtime.DownloadResult) error { - // Look for corresponding .asc signature file - ascFilePath := result.LocalPath + ".asc" - - v.logger.Debug("Looking for signature file", - "main_file", result.LocalPath, - "signature_file", ascFilePath) - - // Check if signature file exists - var gpgVerified bool - var verificationStatus string - - if _, err := os.Stat(ascFilePath); os.IsNotExist(err) { - v.logger.Warn("Signature file not found for main file, skipping GPG verification", - "main_file", result.LocalPath, - "signature_file", ascFilePath) - - // No signature file, so no GPG verification attempted - gpgVerified = false - verificationStatus = "signature_file_missing" - - // Create individual audit file for this download - if auditErr := v.createIndividualAuditFile(result, gpgVerified, verificationStatus); auditErr != nil { - v.logger.Error("Failed to create individual audit file", - "main_file", result.LocalPath, - "error", auditErr) - } - - return nil // Optional verification - don't fail if signature file is missing - } - - v.logger.Debug("Found signature file, performing detached signature verification", - "main_file", result.LocalPath, - "signature_file", ascFilePath) - - // Verify detached signature - err := gpg.VerifyDetachedSignature(v.keyRing, result.LocalPath, ascFilePath) - if err != nil { - v.logger.Error("GPG verification failed for main file", - "main_file", result.LocalPath, - "signature_file", ascFilePath, - "error", err) - - // GPG verification failed - gpgVerified = false - verificationStatus = "gpg_verification_failed" - - // Create individual audit file for this download - if auditErr := v.createIndividualAuditFile(result, gpgVerified, verificationStatus); auditErr != nil { - v.logger.Error("Failed to create individual audit file", - "main_file", result.LocalPath, - "error", auditErr) - } - - return fmt.Errorf("GPG verification failed for %s: %w", result.LocalPath, err) - } - - v.logger.Info("GPG verification successful for main file", - "main_file", result.LocalPath, - "signature_file", ascFilePath) - - // GPG verification succeeded - gpgVerified = true - verificationStatus = "success" - - // Create individual audit file for this download - if auditErr := v.createIndividualAuditFile(result, gpgVerified, verificationStatus); auditErr != nil { - v.logger.Error("Failed to create individual audit file", - "main_file", result.LocalPath, - "error", auditErr) - } - - return nil -} - -// createIndividualAuditFile creates an individual audit file for a downloaded Python file -func (v *PythonGPGVerifier) createIndividualAuditFile(result runtime.DownloadResult, gpgVerified bool, verificationStatus string) error { - // Reconstruct signature URL (educated guess based on main file URL) - // NOTE: Actual URL comes from Python download sources but we only have main file URL - var signatureFileURL string - urlIsReconstructed := true - - if result.URL != "" { - signatureFileURL = result.URL + ".asc" - } - - // Create audit record - auditRecord := map[string]interface{}{ - "timestamp": time.Now().UTC().Format(time.RFC3339), - "file_path": result.LocalPath, - "file_url": result.URL, - "runtime": "python", - "checksum_verified": false, // Python doesn't use checksum verification - "gpg_verified": gpgVerified, - "verification_status": verificationStatus, - } - - // Add GPG-specific details if signature file exists - signatureFile := result.LocalPath + ".asc" - if _, err := os.Stat(signatureFile); err == nil { - auditRecord["gpg_validation_method"] = "detached_signature_verification" - auditRecord["gpg_keyring_source"] = "embedded_python_keys" - auditRecord["signature_file"] = signatureFile - auditRecord["signature_file_url"] = signatureFileURL - auditRecord["signature_url_reconstructed"] = urlIsReconstructed - } - - // Get file info for additional metadata - if fileInfo, err := os.Stat(result.LocalPath); err == nil { - auditRecord["file_size"] = fileInfo.Size() - auditRecord["file_modified"] = fileInfo.ModTime().UTC().Format(time.RFC3339) - } - - // Marshal to JSON - jsonData, err := json.MarshalIndent(auditRecord, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal audit record to JSON: %w", err) - } - - // Create audit file path (same as main file but with .audit.json extension) - auditFilePath := result.LocalPath + ".audit.json" - - // Write audit file - if err := os.WriteFile(auditFilePath, jsonData, 0644); err != nil { - return fmt.Errorf("failed to write audit file %s: %w", auditFilePath, err) - } - - v.logger.Info("Created individual audit file for Python download", - "main_file", result.LocalPath, - "audit_file", auditFilePath, - "gpg_verified", gpgVerified, - "verification_status", verificationStatus, - "file_url", result.URL) - - return nil -} diff --git a/internal/runtimes/python/python_sigstore_test.go b/internal/runtimes/python/python_sigstore_test.go new file mode 100644 index 0000000..cd91f65 --- /dev/null +++ b/internal/runtimes/python/python_sigstore_test.go @@ -0,0 +1,198 @@ +package python + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/clean-dependency-project/cdprun/internal/config" + "github.com/clean-dependency-project/cdprun/internal/platform" + "github.com/clean-dependency-project/cdprun/internal/runtime" +) + +func sigstoreRuntime(identities ...config.SigstoreIdentityEntry) *config.Runtime { + return &config.Runtime{ + Enabled: true, + Verification: config.Verification{ + Enabled: true, + Methods: config.VerificationMethods{ + GPG: config.GPGVerification{Enabled: false}, + Sigstore: config.SigstoreVerification{Enabled: true, BundleSuffix: ".sigstore", Identities: identities}, + }, + }, + } +} + +func TestPythonSigstoreVerifier_SkipWhenBundleMissing(t *testing.T) { + dir := t.TempDir() + mainPath := filepath.Join(dir, "Python-3.15.0.tar.xz") + if err := os.WriteFile(mainPath, []byte("artifact-bytes"), 0644); err != nil { + t.Fatal(err) + } + + cfg := sigstoreRuntime(config.SigstoreIdentityEntry{ + VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth", + }) + v := NewPythonSigstoreVerifier(cfg, nil).(*PythonSigstoreVerifier) + + result := runtime.DownloadResult{ + LocalPath: mainPath, + Runtime: "python", + Version: "3.15.0", + Success: true, + } + if err := v.Verify(context.Background(), result); err != nil { + t.Fatalf("expected skip (nil error) when bundle missing, got: %v", err) + } + auditBytes, err := os.ReadFile(mainPath + ".audit.json") + if err != nil { + t.Fatalf("audit file not written: %v", err) + } + if !strings.Contains(string(auditBytes), `"sigstore_verified": false`) { + t.Errorf("audit should record sigstore_verified false, got: %s", auditBytes) + } + if !strings.Contains(string(auditBytes), `"bundle_missing"`) { + t.Errorf("audit should record bundle_missing status, got: %s", auditBytes) + } +} + +func TestPythonSigstoreVerifier_SkipsOldReleaseWithNoIdentity(t *testing.T) { + dir := t.TempDir() + mainPath := filepath.Join(dir, "Python-2.7.0.tar.xz") + if err := os.WriteFile(mainPath, []byte("artifact-bytes"), 0644); err != nil { + t.Fatal(err) + } + // A bundle sidecar exists, but config has no identity row for 2.7 (old + // release). Sigstore should SKIP rather than fail closed. + if err := os.WriteFile(mainPath+".sigstore", []byte("{}"), 0644); err != nil { + t.Fatal(err) + } + + cfg := sigstoreRuntime(config.SigstoreIdentityEntry{ + VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth", + }) + v := NewPythonSigstoreVerifier(cfg, nil).(*PythonSigstoreVerifier) + + result := runtime.DownloadResult{ + LocalPath: mainPath, + Runtime: "python", + Version: "2.7.0", + Success: true, + } + if err := v.Verify(context.Background(), result); err != nil { + t.Fatalf("expected skip (nil error) for old release with no identity, got: %v", err) + } + auditBytes, err := os.ReadFile(mainPath + ".audit.json") + if err != nil { + t.Fatalf("audit file not written: %v", err) + } + if !strings.Contains(string(auditBytes), `"identity_not_configured"`) { + t.Errorf("audit should record identity_not_configured status, got: %s", auditBytes) + } +} + +func TestPythonSigstoreVerifier_SkipsBundleSidecarItself(t *testing.T) { + dir := t.TempDir() + bundlePath := filepath.Join(dir, "Python-3.15.0.tar.xz.sigstore") + if err := os.WriteFile(bundlePath, []byte("{}"), 0644); err != nil { + t.Fatal(err) + } + cfg := sigstoreRuntime(config.SigstoreIdentityEntry{ + VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth", + }) + v := NewPythonSigstoreVerifier(cfg, nil).(*PythonSigstoreVerifier) + + result := runtime.DownloadResult{LocalPath: bundlePath, Runtime: "python", Version: "3.15.0", Success: true} + if err := v.Verify(context.Background(), result); err != nil { + t.Fatalf("expected nil when verifying bundle sidecar itself, got: %v", err) + } +} + +func TestPythonAdapter_GetVerificationStrategy_SigstorePrecedence(t *testing.T) { + // Sigstore enabled -> sigstore verifier. + a := &PythonAdapter{config: sigstoreRuntime(config.SigstoreIdentityEntry{ + VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth", + })} + if got := a.GetVerificationStrategy().GetType(); got != "python-sigstore" { + t.Errorf("expected python-sigstore, got %q", got) + } + + // Sigstore disabled, GPG enabled -> gpg verifier. + gpgCfg := &config.Runtime{Enabled: true, Verification: config.Verification{ + Enabled: true, + Methods: config.VerificationMethods{GPG: config.GPGVerification{Enabled: true, SignaturePattern: ".asc"}}, + }} + a2 := &PythonAdapter{config: gpgCfg} + if got := a2.GetVerificationStrategy().GetType(); got != "python-gpg" { + t.Errorf("expected python-gpg, got %q", got) + } + + // No config -> gpg verifier (legacy default). + a3 := &PythonAdapter{} + if got := a3.GetVerificationStrategy().GetType(); got != "python-gpg" { + t.Errorf("expected python-gpg default, got %q", got) + } +} + +func TestPythonAdapter_SigstoreDownloadTasks_NoAscWhenSigstoreOn(t *testing.T) { + cfg := &config.Runtime{ + Enabled: true, + Download: config.DownloadConfig{ + BaseURL: "https://www.python.org/ftp/python", + URLPattern: "{base_url}/{version}/python-{version}-{arch}.{ext}", + }, + Verification: config.Verification{ + Enabled: true, + Methods: config.VerificationMethods{ + GPG: config.GPGVerification{Enabled: true, SignaturePattern: ".asc"}, + Sigstore: config.SigstoreVerification{Enabled: true, BundleSuffix: ".sigstore", Identities: []config.SigstoreIdentityEntry{{VersionPrefix: "3.15", CertIdentity: "hugo@python.org", OIDCIssuer: "https://github.com/login/oauth"}}}, + }, + }, + } + a := &PythonAdapter{config: cfg, stdout: nil, stderr: nil} + + plat := platform.Platform{OS: "linux", Arch: "x64", FileExt: "tar.xz"} + tasks := a.createVerificationTasksWithVersions([]platformVersion{{plat: plat, version: "3.15.0"}}, "/tmp/out", "cdprun/1.0") + + if len(tasks) != 1 { + t.Fatalf("expected 1 sigstore task, got %d", len(tasks)) + } + if tasks[0].FileType != "sigstore" { + t.Errorf("expected sigstore file type, got %q", tasks[0].FileType) + } + if !strings.HasSuffix(tasks[0].URL, ".sigstore") { + t.Errorf("expected .sigstore URL, got %q", tasks[0].URL) + } +} + +func TestPythonAdapter_GPGDownloadTasks_WhenSigstoreOff(t *testing.T) { + cfg := &config.Runtime{ + Enabled: true, + Download: config.DownloadConfig{ + BaseURL: "https://www.python.org/ftp/python", + URLPattern: "{base_url}/{version}/python-{version}-{arch}.{ext}", + }, + Verification: config.Verification{ + Enabled: true, + Methods: config.VerificationMethods{ + GPG: config.GPGVerification{Enabled: true, SignaturePattern: ".asc"}, + }, + }, + } + a := &PythonAdapter{config: cfg} + + plat := platform.Platform{OS: "linux", Arch: "x64", FileExt: "tar.xz"} + tasks := a.createVerificationTasksWithVersions([]platformVersion{{plat: plat, version: "3.13.0"}}, "/tmp/out", "cdprun/1.0") + + if len(tasks) != 1 { + t.Fatalf("expected 1 gpg task, got %d", len(tasks)) + } + if tasks[0].FileType != "signature" { + t.Errorf("expected signature file type, got %q", tasks[0].FileType) + } + if !strings.HasSuffix(tasks[0].URL, ".asc") { + t.Errorf("expected .asc URL, got %q", tasks[0].URL) + } +} diff --git a/internal/runtimes/python/verifiers.go b/internal/runtimes/python/verifiers.go new file mode 100644 index 0000000..c269fac --- /dev/null +++ b/internal/runtimes/python/verifiers.go @@ -0,0 +1,510 @@ +package python + +import ( + "context" + "embed" + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" + "strings" + "time" + + "github.com/clean-dependency-project/cdprun/internal/config" + "github.com/clean-dependency-project/cdprun/internal/gpg" + "github.com/clean-dependency-project/cdprun/internal/runtime" + "github.com/clean-dependency-project/cdprun/internal/sigstore" +) + +//go:embed keys +var embeddedKeysFS embed.FS + +// NoOpVerifier is a no-operation verifier that always succeeds. +// Used when GPG key loading fails as a fallback. +type NoOpVerifier struct { + runtimeName string + logger *slog.Logger +} + +// Verify always returns nil (success) for no-op verification. +func (v *NoOpVerifier) Verify(ctx context.Context, result runtime.DownloadResult) error { + v.logger.Warn("no-op verification - GPG keys not available", + "runtime", v.runtimeName, + "file", result.LocalPath) + return nil +} + +// GetType returns the verification strategy type. +func (v *NoOpVerifier) GetType() string { + return "noop" +} + +// RequiresAdditionalFiles returns false since no-op verification doesn't need extra files. +func (v *NoOpVerifier) RequiresAdditionalFiles() bool { + return false +} + +// PythonGPGVerifier implements GPG signature verification for Python downloads +type PythonGPGVerifier struct { + keyRing gpg.KeyRing + logger *slog.Logger +} + +// NewPythonGPGVerifier creates a new GPG verifier for Python downloads using embedded keys +func NewPythonGPGVerifier(logger *slog.Logger) runtime.VerificationStrategy { + if logger == nil { + logger = slog.Default() + } + + verifier := &PythonGPGVerifier{ + logger: logger, + } + + // Load embedded keys using custom logic for numbered files + keyRing, err := loadPythonEmbeddedKeys(embeddedKeysFS, "keys", logger) + if err != nil { + logger.Error("Failed to load Python GPG keys from embedded filesystem", "error", err) + // Return a no-op verifier if key loading fails + return &NoOpVerifier{runtimeName: "python", logger: logger} + } + + verifier.keyRing = keyRing + logger.Info("Loaded Python GPG verification keys", "source", "embedded") + return verifier +} + +// loadPythonEmbeddedKeys loads GPG keys from embedded filesystem, handling numbered files +func loadPythonEmbeddedKeys(fs embed.FS, keysPath string, logger *slog.Logger) (gpg.KeyRing, error) { + files, err := fs.ReadDir(keysPath) + if err != nil { + return nil, fmt.Errorf("failed to read embedded keys directory: %w", err) + } + + keyRing := gpg.NewRealKeyRing() + keyCount := 0 + + for _, file := range files { + if file.IsDir() { + continue + } + + fileName := file.Name() + + // Skip .gitkeep files + if fileName == ".gitkeep" { + continue + } + + filePath := filepath.Join(keysPath, fileName) + keyData, err := fs.ReadFile(filePath) + if err != nil { + logger.Warn("Failed to read embedded key file", "file", fileName, "error", err) + continue + } + + // Check if this looks like a GPG key file + keyContent := string(keyData) + if !strings.Contains(keyContent, "BEGIN PGP PUBLIC KEY BLOCK") { + logger.Debug("Skipping non-GPG file", "file", fileName) + continue + } + + if len(keyData) > 100*1024*1024 { // 100MB max + logger.Warn("Embedded key file exceeds maximum allowed size", "file", fileName) + continue + } + + key, err := gpg.NewRealKey(keyContent) + if err != nil { + logger.Warn("Failed to parse embedded key", "file", fileName, "error", err) + continue + } + + // Validate key (check if revoked, etc.) + if key.IsRevoked() { + logger.Warn("Skipping revoked key", "file", fileName, "fingerprint", key.GetFingerprint()) + continue + } + + if err := keyRing.AddKey(key); err != nil { + logger.Warn("Failed to add key to keyring", "file", fileName, "error", err) + continue + } + + keyCount++ + logger.Debug("Loaded Python GPG key", "file", fileName, "fingerprint", key.GetFingerprint()) + } + + if keyCount == 0 { + return nil, fmt.Errorf("no valid GPG keys found in embedded directory") + } + + logger.Info("Successfully loaded Python GPG keys", "count", keyCount) + return keyRing, nil +} + +// Verify implements the VerificationStrategy interface +func (v *PythonGPGVerifier) Verify(ctx context.Context, result runtime.DownloadResult) error { + if v.keyRing == nil { + v.logger.Error("No GPG keyring available for verification", + "file", result.LocalPath, + "runtime", result.Runtime) + return fmt.Errorf("no GPG keyring available for verification") + } + + if result.Error != nil { + v.logger.Error("Cannot verify failed download", + "file", result.LocalPath, + "download_error", result.Error, + "runtime", result.Runtime) + return fmt.Errorf("cannot verify failed download: %w", result.Error) + } + + v.logger.Info("Starting Python GPG verification", + "file", result.LocalPath, + "url", result.URL, + "runtime", result.Runtime, + "version", result.Version, + "file_size", result.FileSize) + + // Determine file type based on filename + fileName := filepath.Base(result.LocalPath) + + // For signature files (.asc), these are detached signatures in Python's case + // We should skip verification of signature files themselves + if strings.HasSuffix(fileName, ".asc") { + v.logger.Debug("Detected signature file, skipping direct verification", + "file", result.LocalPath, + "runtime", result.Runtime, + "note", "Signature files are used to verify main files") + return nil // Don't verify signature files directly + } + + // For main files, look for corresponding signature files + v.logger.Debug("Detected main file, looking for signature file", + "file", result.LocalPath, + "runtime", result.Runtime) + return v.verifyMainFile(result) +} + +// GetType returns the verification strategy type +func (v *PythonGPGVerifier) GetType() string { + return "python-gpg" +} + +// RequiresAdditionalFiles returns true since we need signature files +func (v *PythonGPGVerifier) RequiresAdditionalFiles() bool { + return true +} + +// verifyMainFile verifies a main download file by looking for its signature +func (v *PythonGPGVerifier) verifyMainFile(result runtime.DownloadResult) error { + // Look for corresponding .asc signature file + ascFilePath := result.LocalPath + ".asc" + + v.logger.Debug("Looking for signature file", + "main_file", result.LocalPath, + "signature_file", ascFilePath) + + // Check if signature file exists + var gpgVerified bool + var verificationStatus string + + if _, err := os.Stat(ascFilePath); os.IsNotExist(err) { + v.logger.Warn("Signature file not found for main file, skipping GPG verification", + "main_file", result.LocalPath, + "signature_file", ascFilePath) + + // No signature file, so no GPG verification attempted + gpgVerified = false + verificationStatus = "signature_file_missing" + + // Create individual audit file for this download + if auditErr := v.createIndividualAuditFile(result, gpgVerified, verificationStatus); auditErr != nil { + v.logger.Error("Failed to create individual audit file", + "main_file", result.LocalPath, + "error", auditErr) + } + + return nil // Optional verification - don't fail if signature file is missing + } + + v.logger.Debug("Found signature file, performing detached signature verification", + "main_file", result.LocalPath, + "signature_file", ascFilePath) + + // Verify detached signature + err := gpg.VerifyDetachedSignature(v.keyRing, result.LocalPath, ascFilePath) + if err != nil { + v.logger.Error("GPG verification failed for main file", + "main_file", result.LocalPath, + "signature_file", ascFilePath, + "error", err) + + // GPG verification failed + gpgVerified = false + verificationStatus = "gpg_verification_failed" + + // Create individual audit file for this download + if auditErr := v.createIndividualAuditFile(result, gpgVerified, verificationStatus); auditErr != nil { + v.logger.Error("Failed to create individual audit file", + "main_file", result.LocalPath, + "error", auditErr) + } + + return fmt.Errorf("GPG verification failed for %s: %w", result.LocalPath, err) + } + + v.logger.Info("GPG verification successful for main file", + "main_file", result.LocalPath, + "signature_file", ascFilePath) + + // GPG verification succeeded + gpgVerified = true + verificationStatus = "success" + + // Create individual audit file for this download + if auditErr := v.createIndividualAuditFile(result, gpgVerified, verificationStatus); auditErr != nil { + v.logger.Error("Failed to create individual audit file", + "main_file", result.LocalPath, + "error", auditErr) + } + + return nil +} + +// createIndividualAuditFile creates an individual audit file for a downloaded Python file +func (v *PythonGPGVerifier) createIndividualAuditFile(result runtime.DownloadResult, gpgVerified bool, verificationStatus string) error { + // Reconstruct signature URL (educated guess based on main file URL) + // NOTE: Actual URL comes from Python download sources but we only have main file URL + var signatureFileURL string + urlIsReconstructed := true + + if result.URL != "" { + signatureFileURL = result.URL + ".asc" + } + + // Create audit record + auditRecord := map[string]interface{}{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + "file_path": result.LocalPath, + "file_url": result.URL, + "runtime": "python", + "checksum_verified": false, // Python doesn't use checksum verification + "gpg_verified": gpgVerified, + "verification_status": verificationStatus, + } + + // Add GPG-specific details if signature file exists + signatureFile := result.LocalPath + ".asc" + if _, err := os.Stat(signatureFile); err == nil { + auditRecord["gpg_validation_method"] = "detached_signature_verification" + auditRecord["gpg_keyring_source"] = "embedded_python_keys" + auditRecord["signature_file"] = signatureFile + auditRecord["signature_file_url"] = signatureFileURL + auditRecord["signature_url_reconstructed"] = urlIsReconstructed + } + + // Get file info for additional metadata + if fileInfo, err := os.Stat(result.LocalPath); err == nil { + auditRecord["file_size"] = fileInfo.Size() + auditRecord["file_modified"] = fileInfo.ModTime().UTC().Format(time.RFC3339) + } + + // Marshal to JSON + jsonData, err := json.MarshalIndent(auditRecord, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal audit record to JSON: %w", err) + } + + // Create audit file path (same as main file but with .audit.json extension) + auditFilePath := result.LocalPath + ".audit.json" + + // Write audit file + if err := os.WriteFile(auditFilePath, jsonData, 0644); err != nil { + return fmt.Errorf("failed to write audit file %s: %w", auditFilePath, err) + } + + v.logger.Info("Created individual audit file for Python download", + "main_file", result.LocalPath, + "audit_file", auditFilePath, + "gpg_verified", gpgVerified, + "verification_status", verificationStatus, + "file_url", result.URL) + + return nil +} + +// PythonSigstoreVerifier verifies upstream Sigstore bundles published by +// python.org against a pinned identity resolved from the runtime config. It is +// verify-only: cdprun never signs artifacts. Verification is skipped (ClamAV +// still runs) when the bundle sidecar is absent or when no identity row matches +// the version (an old release Sigstore intentionally ignores). Verification +// fails only when a configured version's bundle is present but does not match. +type PythonSigstoreVerifier struct { + config *config.Runtime + logger *slog.Logger +} + +// NewPythonSigstoreVerifier creates a Sigstore verifier bound to the runtime +// configuration, which supplies the bundle suffix and version-keyed identities. +func NewPythonSigstoreVerifier(cfg *config.Runtime, logger *slog.Logger) runtime.VerificationStrategy { + if logger == nil { + logger = slog.Default() + } + return &PythonSigstoreVerifier{config: cfg, logger: logger} +} + +// GetType returns the verification strategy type. +func (v *PythonSigstoreVerifier) GetType() string { + return "python-sigstore" +} + +// RequiresAdditionalFiles returns true since the .sigstore bundle sidecar must +// be downloaded alongside the main artifact. +func (v *PythonSigstoreVerifier) RequiresAdditionalFiles() bool { + return true +} + +// Verify implements the VerificationStrategy interface. +func (v *PythonSigstoreVerifier) Verify(ctx context.Context, result runtime.DownloadResult) error { + if result.Error != nil { + v.logger.Error("Cannot verify failed download", + "file", result.LocalPath, + "download_error", result.Error, + "runtime", result.Runtime) + return fmt.Errorf("cannot verify failed download: %w", result.Error) + } + + fileName := filepath.Base(result.LocalPath) + bundleSuffix := v.config.Verification.Methods.Sigstore.BundleSuffixOrDefault() + + // Skip verification of the bundle sidecar itself. + if strings.HasSuffix(fileName, bundleSuffix) { + v.logger.Debug("Detected sigstore bundle file, skipping direct verification", + "file", result.LocalPath) + return nil + } + + return v.verifyMainFile(result, bundleSuffix) +} + +// verifyMainFile verifies a main download file against its .sigstore bundle. +// Old releases (no matching identity row in config) are skipped rather than +// failed, so Sigstore only enforces the latest configured release lines. +func (v *PythonSigstoreVerifier) verifyMainFile(result runtime.DownloadResult, bundleSuffix string) error { + bundlePath := result.LocalPath + bundleSuffix + + entry, err := v.config.ResolveSigstoreIdentityEntry(result.Version) + if err != nil { + // Unparseable version is a real error, not an old-release skip. + v.logger.Error("Cannot resolve sigstore identity for version", + "main_file", result.LocalPath, + "version", result.Version, + "error", err) + if auditErr := v.writeAudit(result, false, "version_unparseable", "", "", bundlePath); auditErr != nil { + v.logger.Error("Failed to create audit file", + "main_file", result.LocalPath, "error", auditErr) + } + return fmt.Errorf("sigstore identity not resolvable for python %s: %w", result.Version, err) + } + if entry == nil { + // No identity row for this version: an old release Sigstore ignores. + v.logger.Info("No sigstore identity configured for version, skipping verification", + "main_file", result.LocalPath, + "version", result.Version) + if auditErr := v.writeAudit(result, false, "identity_not_configured", "", "", bundlePath); auditErr != nil { + v.logger.Error("Failed to create audit file", + "main_file", result.LocalPath, "error", auditErr) + } + return nil + } + + if _, err := os.Stat(bundlePath); os.IsNotExist(err) { + v.logger.Warn("Sigstore bundle not found for main file, skipping verification", + "main_file", result.LocalPath, + "bundle_file", bundlePath) + // Bundle is optional; skip crypto verification (ClamAV still runs). + if auditErr := v.writeAudit(result, false, "bundle_missing", entry.CertIdentity, entry.OIDCIssuer, bundlePath); auditErr != nil { + v.logger.Error("Failed to create audit file", + "main_file", result.LocalPath, "error", auditErr) + } + return nil + } + + certIdentity, oidcIssuer := entry.CertIdentity, entry.OIDCIssuer + v.logger.Info("Starting Python Sigstore verification", + "file", result.LocalPath, + "bundle_file", bundlePath, + "version", result.Version, + "cert_identity", certIdentity, + "oidc_issuer", oidcIssuer) + + if err := sigstore.VerifyBundle(result.LocalPath, bundlePath, oidcIssuer, certIdentity); err != nil { + v.logger.Error("Sigstore verification failed for main file", + "main_file", result.LocalPath, + "bundle_file", bundlePath, + "error", err) + if auditErr := v.writeAudit(result, false, "sigstore_verification_failed", certIdentity, oidcIssuer, bundlePath); auditErr != nil { + v.logger.Error("Failed to create audit file", + "main_file", result.LocalPath, "error", auditErr) + } + return fmt.Errorf("sigstore verification failed for %s: %w", result.LocalPath, err) + } + + v.logger.Info("Sigstore verification successful for main file", + "main_file", result.LocalPath, + "bundle_file", bundlePath) + + if auditErr := v.writeAudit(result, true, "success", certIdentity, oidcIssuer, bundlePath); auditErr != nil { + v.logger.Error("Failed to create audit file", + "main_file", result.LocalPath, "error", auditErr) + } + return nil +} + +// writeAudit writes a per-artifact .audit.json recording the Sigstore +// verification outcome. +func (v *PythonSigstoreVerifier) writeAudit(result runtime.DownloadResult, verified bool, status, certIdentity, oidcIssuer, bundlePath string) error { + auditRecord := map[string]interface{}{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + "file_path": result.LocalPath, + "file_url": result.URL, + "runtime": "python", + "checksum_verified": false, + "sigstore_verified": verified, + "verification_status": status, + "verification_type": "sigstore", + "bundle_file": bundlePath, + } + if certIdentity != "" { + auditRecord["cert_identity"] = certIdentity + } + if oidcIssuer != "" { + auditRecord["oidc_issuer"] = oidcIssuer + } + + if fileInfo, err := os.Stat(result.LocalPath); err == nil { + auditRecord["file_size"] = fileInfo.Size() + auditRecord["file_modified"] = fileInfo.ModTime().UTC().Format(time.RFC3339) + } + + jsonData, err := json.MarshalIndent(auditRecord, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal audit record to JSON: %w", err) + } + + auditFilePath := result.LocalPath + ".audit.json" + if err := os.WriteFile(auditFilePath, jsonData, 0644); err != nil { + return fmt.Errorf("failed to write audit file %s: %w", auditFilePath, err) + } + + v.logger.Info("Created individual audit file for Python download", + "main_file", result.LocalPath, + "audit_file", auditFilePath, + "sigstore_verified", verified, + "verification_status", status, + "file_url", result.URL) + return nil +} diff --git a/internal/sigstore/trusted-root-public-good.json b/internal/sigstore/trusted-root-public-good.json new file mode 100644 index 0000000..5ed6281 --- /dev/null +++ b/internal/sigstore/trusted-root-public-good.json @@ -0,0 +1,114 @@ +{ + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] +} diff --git a/internal/sigstore/verify.go b/internal/sigstore/verify.go new file mode 100644 index 0000000..c4afd2b --- /dev/null +++ b/internal/sigstore/verify.go @@ -0,0 +1,76 @@ +// Package sigstore provides upstream Sigstore bundle verification for downloaded +// runtime artifacts. It is verify-only: cdprun never signs or attests. +package sigstore + +import ( + _ "embed" + "fmt" + "os" + + "github.com/sigstore/sigstore-go/pkg/bundle" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/verify" +) + +//go:embed trusted-root-public-good.json +var trustedRootJSON []byte + +// VerifyBundle verifies that the artifact at artifactPath matches the Sigstore +// bundle at bundlePath, signed by the expected OIDC issuer and certificate +// identity (SAN). It uses the embedded public-good trusted root and the +// verification material embedded in the bundle (cert, transparency log entry, +// timestamps), so no network access to GitHub or TUF is required. +func VerifyBundle(artifactPath, bundlePath, oidcIssuer, certIdentity string) error { + artifactFile, err := os.Open(artifactPath) + if err != nil { + return fmt.Errorf("opening artifact %s: %w", artifactPath, err) + } + defer func() { _ = artifactFile.Close() }() + return verifyBundle(verify.WithArtifact(artifactFile), bundlePath, oidcIssuer, certIdentity) +} + +// VerifyBundleDigest verifies a Sigstore bundle against a precomputed artifact +// digest (hex-decoded bytes) and digest algorithm (e.g. "sha256"), pinned to the +// expected OIDC issuer and certificate identity. It is the offline-friendly +// counterpart to VerifyBundle: no artifact file is read, only the committed +// bundle and embedded trusted root are used. +func VerifyBundleDigest(artifactDigest []byte, algorithm, bundlePath, oidcIssuer, certIdentity string) error { + return verifyBundle(verify.WithArtifactDigest(algorithm, artifactDigest), bundlePath, oidcIssuer, certIdentity) +} + +// newVerifier builds a verifier over the embedded public-good trusted root with +// the same thresholds used by sigstore-go's verification example. +func newVerifier() (*verify.Verifier, error) { + trustedRoot, err := root.NewTrustedRootFromJSON(trustedRootJSON) + if err != nil { + return nil, fmt.Errorf("parsing trusted root: %w", err) + } + trustedMaterial := root.TrustedMaterialCollection{trustedRoot} + return verify.NewVerifier(trustedMaterial, + verify.WithSignedCertificateTimestamps(1), + verify.WithTransparencyLog(1), + verify.WithObserverTimestamps(1), + ) +} + +func verifyBundle(artifactPolicy verify.ArtifactPolicyOption, bundlePath, oidcIssuer, certIdentity string) error { + b, err := bundle.LoadJSONFromPath(bundlePath) + if err != nil { + return fmt.Errorf("loading sigstore bundle %s: %w", bundlePath, err) + } + verifier, err := newVerifier() + if err != nil { + return err + } + certID, err := verify.NewShortCertificateIdentity(oidcIssuer, "", certIdentity, "") + if err != nil { + return fmt.Errorf("building certificate identity: %w", err) + } + if _, err := verifier.Verify(b, verify.NewPolicy( + artifactPolicy, + verify.WithCertificateIdentity(certID), + )); err != nil { + return fmt.Errorf("sigstore verification failed: %w", err) + } + return nil +} diff --git a/internal/sigstore/verify_test.go b/internal/sigstore/verify_test.go new file mode 100644 index 0000000..d94d90d --- /dev/null +++ b/internal/sigstore/verify_test.go @@ -0,0 +1,72 @@ +package sigstore + +import ( + "testing" + + "github.com/sigstore/sigstore-go/pkg/bundle" +) + +const ( + fixtureBundlePath = "testdata/Python-3.15.0b3.tar.xz.sigstore" + fixtureCertID = "hugo@python.org" + fixtureOIDCIssuer = "https://github.com/login/oauth" +) + +// loadFixtureDigest extracts the message digest + algorithm embedded in the +// committed python.org bundle so the test can verify offline via digest without +// checking in the ~35MB artifact. +func loadFixtureDigest(t *testing.T) ([]byte, string) { + t.Helper() + b, err := bundle.LoadJSONFromPath(fixtureBundlePath) + if err != nil { + t.Fatalf("loading fixture bundle: %v", err) + } + content, err := b.SignatureContent() + if err != nil { + t.Fatalf("signature content: %v", err) + } + msg, ok := content.(interface { + Digest() []byte + DigestAlgorithm() string + }) + if !ok { + t.Fatalf("bundle SignatureContent %T does not expose a message digest", content) + } + return msg.Digest(), msg.DigestAlgorithm() +} + +func TestVerifyBundleDigest_Pass(t *testing.T) { + digest, alg := loadFixtureDigest(t) + if err := VerifyBundleDigest(digest, alg, fixtureBundlePath, fixtureOIDCIssuer, fixtureCertID); err != nil { + t.Fatalf("expected verification to pass, got: %v", err) + } +} + +func TestVerifyBundleDigest_TamperedDigestFails(t *testing.T) { + digest, alg := loadFixtureDigest(t) + if len(digest) == 0 { + t.Fatal("fixture digest is empty") + } + tampered := append([]byte(nil), digest...) + tampered[0] ^= 0xff + err := VerifyBundleDigest(tampered, alg, fixtureBundlePath, fixtureOIDCIssuer, fixtureCertID) + if err == nil { + t.Fatal("expected verification to fail for tampered digest, got nil") + } +} + +func TestVerifyBundleDigest_WrongIdentityFails(t *testing.T) { + digest, alg := loadFixtureDigest(t) + err := VerifyBundleDigest(digest, alg, fixtureBundlePath, fixtureOIDCIssuer, "impostor@python.org") + if err == nil { + t.Fatal("expected verification to fail for wrong identity, got nil") + } +} + +func TestVerifyBundleDigest_WrongIssuerFails(t *testing.T) { + digest, alg := loadFixtureDigest(t) + err := VerifyBundleDigest(digest, alg, fixtureBundlePath, "https://accounts.google.com", fixtureCertID) + if err == nil { + t.Fatal("expected verification to fail for wrong issuer, got nil") + } +} diff --git a/runtime-registry.yaml b/runtime-registry.yaml index 803b7f4..eb28cb0 100644 --- a/runtime-registry.yaml +++ b/runtime-registry.yaml @@ -51,8 +51,28 @@ runtimes: file_pattern: "" remote_checksum: false gpg: - enabled: true + enabled: false signature_pattern: ".asc" + sigstore: + enabled: true + bundle_suffix: ".sigstore" + # Only the latest Sigstore-signed release lines are covered. Older + # releases (3.13 and below) are intentionally ignored: if a .sigstore + # bundle is present but no identity row matches, verification is + # skipped (ClamAV still runs). Per python.org, 3.14+ is Sigstore-only. + identities: + - version_prefix: "3.17" + cert_identity: "savannah@python.org" + oidc_issuer: "https://github.com/login/oauth" + - version_prefix: "3.16" + cert_identity: "savannah@python.org" + oidc_issuer: "https://github.com/login/oauth" + - version_prefix: "3.15" + cert_identity: "hugo@python.org" + oidc_issuer: "https://github.com/login/oauth" + - version_prefix: "3.14" + cert_identity: "hugo@python.org" + oidc_issuer: "https://github.com/login/oauth" clamav: enabled: true image: "clamav/clamav-debian:latest" From 1124292f650f10ad882ac605e418ecc5cd708ed7 Mon Sep 17 00:00:00 2001 From: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:11:38 -0500 Subject: [PATCH 2/3] Include test sigstore --- internal/sigstore/testdata/Python-3.15.0b3.tar.xz.sigstore | 1 + 1 file changed, 1 insertion(+) create mode 100644 internal/sigstore/testdata/Python-3.15.0b3.tar.xz.sigstore diff --git a/internal/sigstore/testdata/Python-3.15.0b3.tar.xz.sigstore b/internal/sigstore/testdata/Python-3.15.0b3.tar.xz.sigstore new file mode 100644 index 0000000..919ffd0 --- /dev/null +++ b/internal/sigstore/testdata/Python-3.15.0b3.tar.xz.sigstore @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDJDCCAqmgAwIBAgIUZQc8HC3BVCV8oJD7+JSYknD7hPowCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjIzMTMzMTA0WhcNMjYwNjIzMTM0MTA0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMBfW6m5q1TX1g/9HQHZfDc/TSIxStahYmjRTSusJDlGLExH+ZiJg9rsWQ3NJXapI2IFL176UQLvoF2JXAiipFaOCAcgwggHEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU9sEAXij4s8GXLTCEYcXiTYKmkGIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHQYDVR0RAQH/BBMwEYEPaHVnb0BweXRob24ub3JnMCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDBSBgorBgEEAYO/MAEYBEQMQkNnY3hNekkwTWpJMUVpWm9kSFJ3Y3pvbE1rWWxNa1puYVhSb2RXSXVZMjl0SlRKR2JHOW5hVzRsTWtadllYVjBhQTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABnvStZHoAAAQDAEgwRgIhAKeQztx4T2Q7mrLnox/6r5XOLzRl3o96jm9MI9tN1ul5AiEAsgn9xxbGC7C92sD8iH7Fn1Du/haNggNd84cX4H5LFZswCgYIKoZIzj0EAwMDaQAwZgIxANdoTu6n7pAIksnDkAowk+yMs+20S661gpaBs9Vu6+ykGgNLNw1AUBftVHIVr30+FAIxAKY0RGzkqB+qR9tkWvKUZe8DiaQUXemzuppFZe9UgKQOnVE1WhrLERcrs887+W+j7w=="}, "tlogEntries": [{"logIndex": "1927037556", "logId": {"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1782221466", "inclusionPromise": {"signedEntryTimestamp": "MEYCIQCxre/xAuSN6ClcY6uUNU9GCsydKPDVPbt9iY1NMmnn4AIhAKj2DG+As/B6dlQVT0JgOrBTT/3BB4445c+gAkwv7kyK"}, "inclusionProof": {"logIndex": "1805133294", "rootHash": "2tVzL0ysJ8SsCQco/2LiJeOoCzHTUxH9ZYMpcvsZb08=", "treeSize": "1805133379", "hashes": ["l7suGfCnLfypvggTM7KjKSJv2nFWCIbeiyJOJ3zH/YQ=", "KF4k30EVBMdfZGlDfxw+bKCRHAR9UWZzrS8mJKW8jGU=", "IevYAq+VKg0Yw29/zejEY7xfOWtuzgm0i8QCXDj09+U=", "KVNAcRMLmD1JWca/jAoTLpcmox+ciS5xTAkLLD+9ENM=", "jS9kaPoaLqzmJcVgi7kLkeMphEKS/4qEutpCo8e122s=", "prR7iKnWxftJ/sbK8jI4+HkAcFWLEHUAN3lFJxjkivE=", "ACppXOGZYC/VqCkdVkJQ1nhbOP6CUXTY3bdf/u1HyEQ=", "HctR1fjEs1pDGxGHvfyhLBNNAxi/IB8Kux5WEm4V6Ps=", "hYn41smhFjVSYc/5yTk11fIRgxQ1GjvwsIDAaqX2Bgs=", "9q1IekJxYyR+kMudPimESHf3+W/qxT8AogA4SSdS+cs=", "kr83lMoZ5gCq4EmPdP5CqTvhq9i9+nQjeQXdzwSqmSM=", "v8zdK/ozmFIwpIQFNSxTqyji3j3x5z8eT5M6Qa8b/Kc=", "ZXrex+0eF5KisPWFrbV4dU4sABvVX5VCWS6IPJzJTfI=", "+SdULakto7HyVLdyxex2WHH+ActKh/STEFaWrp8/9Ak=", "OMfDh4k9YD0E+t4sjqJsh+vg8D1ueo1cC07exyxmr6s=", "+vHKm4wNwGJ46ceyZ8mePD7Vn1SediStNHCStRoTSWQ=", "5HwWjuGNDisetbiWM0cWIXM65TIM3EJlqk13ntCQlcU=", "NDIRlqxO0IaGr94q86MIU7VCAN3yEEgOHS4loya5lu4=", "daxmZaajRpZV+JxHiOYZhJBiSKN5ucqjh2WnGbHhirw=", "DOCeoSMovIvLExkhIvisow9AuNXgeWs4ECkyR6EcqYU="], "checkpoint": {"envelope": "rekor.sigstore.dev - 1193050959916656506\n1805133379\n2tVzL0ysJ8SsCQco/2LiJeOoCzHTUxH9ZYMpcvsZb08=\n\n\u2014 rekor.sigstore.dev wNI9ajBFAiBKtWYtMfGakAPby2E0Z3nlx25LkB/UDLvLsMyPfaee6QIhAMmBxCwiQ5uAi6ZmzLffjqtvbcxCihw+NI7ilRzzTOS/\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2YTkzNWFlMjM0YTY3ZTY1NDk4OTQzNzNiMGNmZWI4MzYxMTgyZDAzYjIxNDQyMzI4YWU5NTk4YWI3NDIyMTI3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRG5FQ2grQm5pdnR2b3kvdTFHdFZIR1JSS1hsdGZmQkRCSHZKTkFNK2QxQUFpRUEvRTdkSno3SGNoM0ozYTYyaXpRQUZYQzYxeVRxRHFyWG1DdzFyVllKQlZzPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUktSRU5EUVhGdFowRjNTVUpCWjBsVldsRmpPRWhETTBKV1ExWTRiMHBFTnl0S1UxbHJia1EzYUZCdmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDVxU1hwTlZFMTZUVlJCTUZkb1kwNU5hbGwzVG1wSmVrMVVUVEJOVkVFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZOUW1aWE5tMDFjVEZVV0RGbkx6bElVVWhhWmtSakwxUlRTWGhUZEdGb1dXMXFVbFFLVTNWelNrUnNSMHhGZUVncldtbEtaemx5YzFkUk0wNUtXR0Z3U1RKSlJrd3hOelpWVVV4MmIwWXlTbGhCYVdsd1JtRlBRMEZqWjNkblowaEZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlU1YzBWQkNsaHBhalJ6T0VkWVRGUkRSVmxqV0dsVVdVdHRhMGRKZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBoUldVUldVakJTUVZGSUwwSkNUWGRGV1VWUVlVaFdibUl3UW5kbFdGSnZZakkwZFdJelNtNU5RM2RIUTJselIwRlJVVUpuTnpoM1FWRkZSUXBJYldnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemx6WWpKa2NHSnBPWFpaV0ZZd1lVUkJkVUpuYjNKQ1owVkZRVmxQTDAxQlJVbENRMEZOQ2todGFEQmtTRUo2VDJrNGRsb3liREJoU0ZacFRHMU9kbUpUT1hOaU1tUndZbWs1ZGxsWVZqQmhSRUpUUW1kdmNrSm5SVVZCV1U4dlRVRkZXVUpGVVUwS1VXdE9ibGt6YUU1bGEydDNWRmR3U2sxVlZuQlhiVGxyVTBaS00xa3pjSFppUlRGeVYxZDRUbUV4Y0hWWlZtaFRZakpTV0ZOWVZscE5hbXd3VTJ4U1N3cFNNa3BJVDFjMWFGWjZVbk5VVjNSaFpHeHNXVlpxUW1oUlZFTkNhWGRaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamxDU0hOQlpWRkNNMEZPTURsTlIzSkhDbmg0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKdWRsTjBXa2h2UVVGQlVVUkJSV2QzVW1kSmFFRkxaVkVLZW5SNE5GUXlVVGR0Y2t4dWIzZ3ZObkkxV0U5TWVsSnNNMjg1Tm1wdE9VMUpPWFJPTVhWc05VRnBSVUZ6WjI0NWVIaGlSME0zUXpreWMwUTRhVWczUmdwdU1VUjFMMmhoVG1kblRtUTROR05ZTkVnMVRFWmFjM2REWjFsSlMyOWFTWHBxTUVWQmQwMUVZVkZCZDFwblNYaEJUbVJ2VkhVMmJqZHdRVWxyYzI1RUNtdEJiM2RySzNsTmN5c3lNRk0yTmpGbmNHRkNjemxXZFRZcmVXdEhaMDVNVG5jeFFWVkNablJXU0VsV2NqTXdLMFpCU1hoQlMxa3dVa2Q2YTNGQ0szRUtVamwwYTFkMlMxVmFaVGhFYVdGUlZWaGxiWHAxY0hCR1dtVTVWV2RMVVU5dVZrVXhWMmh5VEVWU1kzSnpPRGczSzFjcmFqZDNQVDBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In19fX0="}], "timestampVerificationData": {}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "apNa4jSmfmVJiUNzsM/rg2EYLQOyFEIyiulZirdCISc="}, "signature": "MEUCIDnECh+Bnivtvoy/u1GtVHGRRKXltffBDBHvJNAM+d1AAiEA/E7dJz7Hch3J3a62izQAFXC61yTqDqrXmCw1rVYJBVs="}} From e47e24b96535bf55b25553d9a72c103387d1a750 Mon Sep 17 00:00:00 2001 From: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:21:13 -0500 Subject: [PATCH 3/3] Update go version --- .github/workflows/build.yaml | 6 +++--- .github/workflows/ci.yml | 2 +- .github/workflows/run.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9c774fa..f5ad198 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' cache: true - name: golangci-lint @@ -36,7 +36,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' cache: true - name: Download dependencies @@ -59,7 +59,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' cache: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68cbc4a..661b804 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' - name: Install dependencies run: go mod download diff --git a/.github/workflows/run.yaml b/.github/workflows/run.yaml index cf03658..9aae08c 100644 --- a/.github/workflows/run.yaml +++ b/.github/workflows/run.yaml @@ -22,7 +22,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' cache: true - name: Build cdprun binary @@ -118,7 +118,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' cache: true - name: Build cdprun binary