diff --git a/go.mod b/go.mod index 77e761de2..50e736d20 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/vcs v1.13.3 + github.com/ProtonMail/go-crypto v1.3.0 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/cyphar/filepath-securejoin v0.4.1 github.com/distribution/distribution/v3 v3.0.0 @@ -64,6 +65,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/cloudflare/circl v1.6.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/go.sum b/go.sum index 9fa40e4d4..6ea802640 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -49,6 +51,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= diff --git a/internal/plugin/signing_info.go b/internal/plugin/signing_info.go index 43d01c893..61ee9cd15 100644 --- a/internal/plugin/signing_info.go +++ b/internal/plugin/signing_info.go @@ -23,7 +23,7 @@ import ( "path/filepath" "strings" - "golang.org/x/crypto/openpgp/clearsign" //nolint + "github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint "helm.sh/helm/v4/pkg/helmpath" ) diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 3ffad2765..57af1ad42 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -25,9 +25,9 @@ import ( "os" "strings" - "golang.org/x/crypto/openpgp" //nolint - "golang.org/x/crypto/openpgp/clearsign" //nolint - "golang.org/x/crypto/openpgp/packet" //nolint + "github.com/ProtonMail/go-crypto/openpgp" //nolint + "github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint + "github.com/ProtonMail/go-crypto/openpgp/packet" //nolint "sigs.k8s.io/yaml" ) @@ -281,8 +281,9 @@ func (s *Signatory) Verify(archiveData, provData []byte, filename string) (*Veri func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) { return openpgp.CheckDetachedSignature( s.KeyRing, - bytes.NewBuffer(block.Bytes), + bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, + &defaultPGPConfig, ) } diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 4f2fc7298..1efa62819 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -24,7 +24,8 @@ import ( "strings" "testing" - pgperrors "golang.org/x/crypto/openpgp/errors" //nolint + pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" //nolint + "github.com/ProtonMail/go-crypto/openpgp/packet" //nolint "sigs.k8s.io/yaml" "helm.sh/helm/v4/pkg/chart/v2/loader" @@ -59,6 +60,9 @@ const ( // testTamperedSigBlock is a tampered copy of msgblock.yaml.asc testTamperedSigBlock = "testdata/msgblock.yaml.tampered" + // testMixedKeyring points to a keyring containing RSA and ed25519 keys. + testMixedKeyring = "testdata/helm-mixed-keyring.pub" + // testSumfile points to a SHA256 sum generated by an external tool. // We always want to validate against an external tool's representation to // verify that we haven't done something stupid. This file was generated @@ -266,6 +270,58 @@ func TestClearSign(t *testing.T) { } } +func TestMixedKeyringRSASigningAndVerification(t *testing.T) { + signer, err := NewFromFiles(testKeyfile, testMixedKeyring) + if err != nil { + t.Fatal(err) + } + + if signer.Entity == nil { + t.Fatal("expected signer entity to be loaded") + } + + if signer.Entity.PrivateKey == nil { + t.Fatal("expected signer private key to be loaded") + } + + if signer.Entity.PrivateKey.PubKeyAlgo != packet.PubKeyAlgoRSA { + t.Fatalf("expected RSA key but got %v", signer.Entity.PrivateKey.PubKeyAlgo) + } + + metadataBytes := loadChartMetadataForSigning(t, testChartfile) + + archiveData, err := os.ReadFile(testChartfile) + if err != nil { + t.Fatal(err) + } + + sig, err := signer.ClearSign(archiveData, filepath.Base(testChartfile), metadataBytes) + if err != nil { + t.Fatalf("failed to sign chart: %v", err) + } + + verification, err := signer.Verify(archiveData, []byte(sig), filepath.Base(testChartfile)) + if err != nil { + t.Fatalf("failed to verify chart signature: %v", err) + } + + if verification.SignedBy == nil { + t.Fatal("expected verification to include signer") + } + + if verification.SignedBy.PrimaryKey == nil { + t.Fatal("expected verification to include signer primary key") + } + + if verification.SignedBy.PrimaryKey.PubKeyAlgo != packet.PubKeyAlgoRSA { + t.Fatalf("expected verification to report RSA key but got %v", verification.SignedBy.PrimaryKey.PubKeyAlgo) + } + + if _, ok := verification.SignedBy.Identities[testKeyName]; !ok { + t.Fatalf("expected verification to be signed by %q", testKeyName) + } +} + // failSigner always fails to sign and returns an error type failSigner struct{} diff --git a/pkg/provenance/testdata/helm-mixed-keyring.pub b/pkg/provenance/testdata/helm-mixed-keyring.pub new file mode 100644 index 000000000..7985bd20f Binary files /dev/null and b/pkg/provenance/testdata/helm-mixed-keyring.pub differ