diff --git a/cmd/podman/common/sign.go b/cmd/podman/common/sign.go index 57d1ed617c..b9551a866e 100644 --- a/cmd/podman/common/sign.go +++ b/cmd/podman/common/sign.go @@ -12,13 +12,15 @@ import ( "go.podman.io/image/v5/pkg/cli" "go.podman.io/image/v5/pkg/cli/sigstore" "go.podman.io/image/v5/signature/signer" + "go.podman.io/image/v5/signature/simplesequoia" ) // SigningCLIOnlyOptions contains signing-related CLI options. // Some other options are defined in entities.ImagePushOptions. type SigningCLIOnlyOptions struct { - signPassphraseFile string - signBySigstoreParamFile string + signPassphraseFile string + signBySequoiaFingerprint string + signBySigstoreParamFile string } func DefineSigningFlags(cmd *cobra.Command, cliOpts *SigningCLIOnlyOptions, pushOpts *entities.ImagePushOptions) { @@ -28,6 +30,10 @@ func DefineSigningFlags(cmd *cobra.Command, cliOpts *SigningCLIOnlyOptions, push flags.StringVar(&pushOpts.SignBy, signByFlagName, "", "Add a signature at the destination using the specified key") _ = cmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + signBySequoiaFingerprintFlagName := "sign-by-sq-fingerprint" + flags.StringVar(&cliOpts.signBySequoiaFingerprint, signBySequoiaFingerprintFlagName, "", "Sign the image using a Sequoia-PGP key with the specified `FINGERPRINT`") + _ = cmd.RegisterFlagCompletionFunc(signBySequoiaFingerprintFlagName, completion.AutocompleteNone) + signBySigstoreFlagName := "sign-by-sigstore" flags.StringVar(&cliOpts.signBySigstoreParamFile, signBySigstoreFlagName, "", "Sign the image using a sigstore parameter file at `PATH`") _ = cmd.RegisterFlagCompletionFunc(signBySigstoreFlagName, completion.AutocompleteDefault) @@ -42,6 +48,7 @@ func DefineSigningFlags(cmd *cobra.Command, cliOpts *SigningCLIOnlyOptions, push if registry.IsRemote() { _ = flags.MarkHidden(signByFlagName) + _ = flags.MarkHidden(signBySequoiaFingerprintFlagName) _ = flags.MarkHidden(signBySigstoreFlagName) _ = flags.MarkHidden(signBySigstorePrivateKeyFlagName) _ = flags.MarkHidden(signPassphraseFileFlagName) @@ -57,8 +64,20 @@ func PrepareSigning(pushOpts *entities.ImagePushOptions, cliOpts *SigningCLIOnly // c/common/libimage.Image does allow creating both simple signing and sigstore signatures simultaneously, // with independent passphrases, but that would make the CLI probably too confusing. // For now, use the passphrase with either, but only one of them. - if cliOpts.signPassphraseFile != "" && pushOpts.SignBy != "" && pushOpts.SignBySigstorePrivateKeyFile != "" { - return nil, fmt.Errorf("only one of --sign-by and sign-by-sigstore-private-key can be used with --sign-passphrase-file") + if cliOpts.signPassphraseFile != "" { + count := 0 + if pushOpts.SignBy != "" { + count++ + } + if cliOpts.signBySequoiaFingerprint != "" { + count++ + } + if pushOpts.SignBySigstorePrivateKeyFile != "" { + count++ + } + if count > 1 { + return nil, fmt.Errorf("only one of --sign-by, --sign-by-sq-fingerprint and --sign-by-sigstore-private-key can be used with --sign-passphrase-file") + } } var passphrase string @@ -72,9 +91,16 @@ func PrepareSigning(pushOpts *entities.ImagePushOptions, cliOpts *SigningCLIOnly p := ssh.ReadPassphrase() passphrase = string(p) } // pushOpts.SignBy triggers a GPG-agent passphrase prompt, possibly using a more secure channel, so we usually shouldn’t prompt ourselves if no passphrase was explicitly provided. + // With signBySequoiaFingerprint, we don’t prompt for a passphrase (for now??): We don’t know whether the key requires a passphrase. pushOpts.SignPassphrase = passphrase pushOpts.SignSigstorePrivateKeyPassphrase = []byte(passphrase) cleanup := signingCleanup{} + succeeded := false + defer func() { + if !succeeded { + cleanup.cleanup() + } + }() if cliOpts.signBySigstoreParamFile != "" { signer, err := sigstore.NewSignerFromParameterFile(cliOpts.signBySigstoreParamFile, &sigstore.Options{ PrivateKeyPassphrasePrompt: cli.ReadPassphraseFile, @@ -87,6 +113,21 @@ func PrepareSigning(pushOpts *entities.ImagePushOptions, cliOpts *SigningCLIOnly pushOpts.Signers = append(pushOpts.Signers, signer) cleanup.signers = append(cleanup.signers, signer) } + if cliOpts.signBySequoiaFingerprint != "" { + opts := []simplesequoia.Option{ + simplesequoia.WithKeyFingerprint(cliOpts.signBySequoiaFingerprint), + } + if passphrase != "" { + opts = append(opts, simplesequoia.WithPassphrase(passphrase)) + } + signer, err := simplesequoia.NewSigner(opts...) + if err != nil { + return nil, fmt.Errorf("error using --sign-by-sq-fingerprint: %w", err) + } + pushOpts.Signers = append(pushOpts.Signers, signer) + cleanup.signers = append(cleanup.signers, signer) + } + succeeded = true return cleanup.cleanup, nil } diff --git a/docs/source/markdown/options/sign-by-sq-fingerprint.md b/docs/source/markdown/options/sign-by-sq-fingerprint.md new file mode 100644 index 0000000000..9f3ddf3788 --- /dev/null +++ b/docs/source/markdown/options/sign-by-sq-fingerprint.md @@ -0,0 +1,8 @@ +####> This option file is used in: +####> podman artifact push, manifest push, push +####> If file is edited, make sure the changes +####> are applicable to all of those. +#### **--sign-by-sq-fingerprint**=*fingerprint* + +Add a “simple signing” signature using a Sequoia-PGP key with the specified fingerprint. +(This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) diff --git a/docs/source/markdown/options/sign-passphrase-file.md b/docs/source/markdown/options/sign-passphrase-file.md index f25233ac1d..d30393a28e 100644 --- a/docs/source/markdown/options/sign-passphrase-file.md +++ b/docs/source/markdown/options/sign-passphrase-file.md @@ -4,4 +4,4 @@ ####> are applicable to all of those. #### **--sign-passphrase-file**=*path* -If signing the image (using either **--sign-by** or **--sign-by-sigstore-private-key**), read the passphrase to use from the specified path. +If signing the image (using **--sign-by**, **sign-by-sq-fingerprint** or **--sign-by-sigstore-private-key**), read the passphrase to use from the specified path. diff --git a/docs/source/markdown/podman-artifact-push.1.md.in b/docs/source/markdown/podman-artifact-push.1.md.in index a1dc7568dd..2157911ce9 100644 --- a/docs/source/markdown/podman-artifact-push.1.md.in +++ b/docs/source/markdown/podman-artifact-push.1.md.in @@ -38,11 +38,12 @@ Add a “simple signing” signature at the destination using the specified key. @@option sign-by-sigstore - #### **--sign-by-sigstore-private-key**=*path* Add a sigstore signature at the destination using a private key at the specified path. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +@@option sign-by-sq-fingerprint + @@option sign-passphrase-file @@option tls-verify diff --git a/docs/source/markdown/podman-manifest-push.1.md.in b/docs/source/markdown/podman-manifest-push.1.md.in index 47ced3c1b6..a4b2ba118e 100644 --- a/docs/source/markdown/podman-manifest-push.1.md.in +++ b/docs/source/markdown/podman-manifest-push.1.md.in @@ -70,6 +70,8 @@ Sign the pushed images with a “simple signing” signature using the specified Sign the pushed images with a sigstore signature using a private key at the specified path. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +@@option sign-by-sq-fingerprint + @@option sign-passphrase-file @@option tls-verify diff --git a/docs/source/markdown/podman-push.1.md.in b/docs/source/markdown/podman-push.1.md.in index 093182f048..dbcf61fd01 100644 --- a/docs/source/markdown/podman-push.1.md.in +++ b/docs/source/markdown/podman-push.1.md.in @@ -98,6 +98,8 @@ Add a “simple signing” signature at the destination using the specified key. Add a sigstore signature at the destination using a private key at the specified path. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +@@option sign-by-sq-fingerprint + @@option sign-passphrase-file @@option tls-verify diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 587b273284..e04c6b148e 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -1309,8 +1309,8 @@ func (p *PodmanTestIntegration) removeNetwork(name string) { // generatePolicyFile generates a signature verification policy file. // it returns the policy file path. -func generatePolicyFile(tempDir string, port int) string { - keyPath := filepath.Join(tempDir, "key.gpg") +func generatePolicyFile(tempDir string, port int, sequoiaKeyPath string) string { + gpgKeyPath := filepath.Join(tempDir, "key.gpg") policyPath := filepath.Join(tempDir, "policy.json") conf := fmt.Sprintf(` { @@ -1339,11 +1339,18 @@ func generatePolicyFile(tempDir string, port int) string { "type": "sigstoreSigned", "keyPath": "testdata/sigstore-key.pub" } + ], + "localhost:%[1]d/simple-sq-signed": [ + { + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "%[3]s" + } ] } } } -`, port, keyPath) +`, port, gpgKeyPath, sequoiaKeyPath) writeConf([]byte(conf), policyPath) return policyPath } diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index d9e37cc068..01fdbf3969 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -3,9 +3,9 @@ package integration import ( + "bytes" "fmt" "os" - "os/exec" "path/filepath" "strings" @@ -13,9 +13,13 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" + "go.podman.io/image/v5/signature/simplesequoia" "go.podman.io/storage/pkg/archive" ) +// testSequoiaKeyFingerprint is a fingerprint of a test Sequoia key in testdata. +const testSequoiaKeyFingerprint = "50DDE898DF4E48755C8C2B7AF6F908B6FA48A229" + var _ = Describe("Podman push", func() { BeforeEach(func() { @@ -235,22 +239,26 @@ var _ = Describe("Podman push", func() { Expect(push2).Should(ExitCleanly()) if !IsRemote() { // Remote does not support signing - By("pushing and pulling with --sign-by-sigstore-private-key") // Ideally, this should set SystemContext.RegistriesDirPath, but Podman currently doesn’t // expose that as an option. So, for now, modify /etc/directly, and skip testing sigstore if // we don’t have permission to do so. + lookasideDir, err := filepath.Abs(filepath.Join(podmanTest.TempDir, "test-lookaside")) + Expect(err).ToNot(HaveOccurred()) systemRegistriesDAddition := "/etc/containers/registries.d/podman-test-only-temporary-addition.yaml" - cmd := exec.Command("cp", "testdata/sigstore-registries.d-fragment.yaml", systemRegistriesDAddition) - output, err := cmd.CombinedOutput() + registriesDFragment, err := os.ReadFile("testdata/sigstore-registries.d-fragment.yaml") + Expect(err).ToNot(HaveOccurred()) + registriesDFragment = bytes.ReplaceAll(registriesDFragment, []byte("@lookasideDir@"), []byte(lookasideDir)) + err = os.WriteFile(systemRegistriesDAddition, registriesDFragment, 0644) if err != nil { - GinkgoWriter.Printf("Skipping sigstore tests because /etc/containers/registries.d isn’t writable: %s\n", string(output)) + GinkgoWriter.Printf("Skipping sigstore tests because /etc/containers/registries.d isn’t writable: %s\n", err) } else { + By("pushing and pulling with --sign-by-sigstore-private-key") defer func() { err := os.Remove(systemRegistriesDAddition) Expect(err).ToNot(HaveOccurred()) }() // Generate a signature verification policy file - policyPath := generatePolicyFile(podmanTest.TempDir, 5003) + policyPath := generatePolicyFile(podmanTest.TempDir, 5003, "testdata/sequoia-key.pub") defer os.Remove(policyPath) // Verify that the policy rejects unsigned images @@ -289,6 +297,40 @@ var _ = Describe("Podman push", func() { pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5003/sigstore-signed-params"}) pull.WaitWithDefaultTimeout() Expect(pull).Should(ExitCleanly()) + + signer, err := simplesequoia.NewSigner( + simplesequoia.WithSequoiaHome("testdata"), + simplesequoia.WithKeyFingerprint(testSequoiaKeyFingerprint), + ) + if err != nil { + GinkgoWriter.Printf("Skipping Sequoia tests because simplesequoia.NewSigner failed: %s\n", err) + } else { + signer.Close() + + By("pushing and pulling with --sign-by-sq-fingerprint") + absSequoiaHome, err := filepath.Abs("testdata") + Expect(err).ToNot(HaveOccurred()) + defer os.Unsetenv("SEQUOIA_HOME") + os.Setenv("SEQUOIA_HOME", absSequoiaHome) + + // Verify that the policy rejects unsigned images + push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5003/simple-sq-signed"}) + push.WaitWithDefaultTimeout() + Expect(push).Should(ExitCleanly()) + + pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5003/simple-sq-signed"}) + pull.WaitWithDefaultTimeout() + Expect(pull).To(ExitWithError(125, "A signature was required, but no signature exists")) + + // Sign an image, and verify it is accepted. + push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", "--sign-by-sq-fingerprint", testSequoiaKeyFingerprint, ALPINE, "localhost:5003/simple-sq-signed"}) + push.WaitWithDefaultTimeout() + Expect(push).Should(ExitCleanly()) + + pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5003/simple-sq-signed"}) + pull.WaitWithDefaultTimeout() + Expect(pull).Should(ExitCleanly()) + } } } }) diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index b4dbecc54c..0ad7101a60 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -189,7 +189,7 @@ default-docker: if !IsRemote() { // Generate a signature verification policy file - policyPath := generatePolicyFile(podmanTest.TempDir, port) + policyPath := generatePolicyFile(podmanTest.TempDir, port, "testdata/sequoia-key.pub") defer os.Remove(policyPath) session = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, pushedImage}) diff --git a/test/e2e/testdata/data/keystore/keystore.cookie b/test/e2e/testdata/data/keystore/keystore.cookie new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/testdata/data/keystore/softkeys/1F5825285B785E1DB13BF36D2D11A19ABA41C6AE.pgp b/test/e2e/testdata/data/keystore/softkeys/1F5825285B785E1DB13BF36D2D11A19ABA41C6AE.pgp new file mode 100644 index 0000000000..86462c6b64 Binary files /dev/null and b/test/e2e/testdata/data/keystore/softkeys/1F5825285B785E1DB13BF36D2D11A19ABA41C6AE.pgp differ diff --git a/test/e2e/testdata/data/keystore/softkeys/50DDE898DF4E48755C8C2B7AF6F908B6FA48A229.pgp b/test/e2e/testdata/data/keystore/softkeys/50DDE898DF4E48755C8C2B7AF6F908B6FA48A229.pgp new file mode 100644 index 0000000000..4c6ad86f0f Binary files /dev/null and b/test/e2e/testdata/data/keystore/softkeys/50DDE898DF4E48755C8C2B7AF6F908B6FA48A229.pgp differ diff --git a/test/e2e/testdata/data/pgp.cert.d/1f/5825285b785e1db13bf36d2d11a19aba41c6ae b/test/e2e/testdata/data/pgp.cert.d/1f/5825285b785e1db13bf36d2d11a19aba41c6ae new file mode 100644 index 0000000000..eb6dd1f4cc Binary files /dev/null and b/test/e2e/testdata/data/pgp.cert.d/1f/5825285b785e1db13bf36d2d11a19aba41c6ae differ diff --git a/test/e2e/testdata/data/pgp.cert.d/4d/8bcd544b7573eefaad18c278473e5f255d10b8 b/test/e2e/testdata/data/pgp.cert.d/4d/8bcd544b7573eefaad18c278473e5f255d10b8 new file mode 100644 index 0000000000..8dae8b8d8b Binary files /dev/null and b/test/e2e/testdata/data/pgp.cert.d/4d/8bcd544b7573eefaad18c278473e5f255d10b8 differ diff --git a/test/e2e/testdata/data/pgp.cert.d/50/dde898df4e48755c8c2b7af6f908b6fa48a229 b/test/e2e/testdata/data/pgp.cert.d/50/dde898df4e48755c8c2b7af6f908b6fa48a229 new file mode 100644 index 0000000000..b9fee9bb24 Binary files /dev/null and b/test/e2e/testdata/data/pgp.cert.d/50/dde898df4e48755c8c2b7af6f908b6fa48a229 differ diff --git a/test/e2e/testdata/data/pgp.cert.d/68/de230c4a009f5ee5fbb27984642d0130b86046 b/test/e2e/testdata/data/pgp.cert.d/68/de230c4a009f5ee5fbb27984642d0130b86046 new file mode 100644 index 0000000000..6a58a268c8 Binary files /dev/null and b/test/e2e/testdata/data/pgp.cert.d/68/de230c4a009f5ee5fbb27984642d0130b86046 differ diff --git a/test/e2e/testdata/data/pgp.cert.d/trust-root b/test/e2e/testdata/data/pgp.cert.d/trust-root new file mode 100644 index 0000000000..addf38a561 Binary files /dev/null and b/test/e2e/testdata/data/pgp.cert.d/trust-root differ diff --git a/test/e2e/testdata/data/pgp.cert.d/writelock b/test/e2e/testdata/data/pgp.cert.d/writelock new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/testdata/sequoia-key.pub b/test/e2e/testdata/sequoia-key.pub new file mode 100644 index 0000000000..394d47ec12 --- /dev/null +++ b/test/e2e/testdata/sequoia-key.pub @@ -0,0 +1,38 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEaGwFVhYJKwYBBAHaRw8BAQdAZzfnqEAgvE3RoCtPWEOc3Xp8oMURR0qjq+Ru +PHJrc6TCwAsEHxYKAH0FgmhsBVYDCwkHCRD2+Qi2+kiiKUcUAAAAAAAeACBzYWx0 +QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcEjRQtILaFnIhczxeUkcfW0KMHEZ30 +wTdJ1v1iHB7NKQMVCggCmwECHgkWIQRQ3eiY305IdVyMK3r2+Qi2+kiiKQAA86gA +/1ZkXWPHUxh3nQu/EL72ZeP9k/SLWkEuNKs6dJrmRud9AQCHbWwSUwKyt12EFVt/ +QvMFSQ95brUxsWLHgFMPpNfWAc0aU2tvcGVvIFNlcXVvaWEgdGVzdGluZyBrZXnC +wA4EExYKAIAFgmhsBVYDCwkHCRD2+Qi2+kiiKUcUAAAAAAAeACBzYWx0QG5vdGF0 +aW9ucy5zZXF1b2lhLXBncC5vcmctF7xuY06GUyedOGjd2iNKwab85gV64zEAGKgi +ExHRxgMVCggCmQECmwECHgkWIQRQ3eiY305IdVyMK3r2+Qi2+kiiKQAA3SEBAMe1 +y6rWaPjDpkeiDthLV1Umr6NsXVBv/IJTcP9RM4quAQCwmlsdQMddCsc+K3Y5KH88 +saIG0/MRZaPJdsd8vRGUCs4zBGhsBVYWCSsGAQQB2kcPAQEHQLN8yt/21QDMzcB4 +2bzFRg1LpkFZWECjkb2ty7Iju/aOwsC/BBgWCgExBYJobAVWCRD2+Qi2+kiiKUcU +AAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmce9QEurrtI24ys +vXssO/40rI5rlsNokEEFr7CVwVgWvAKbAr6gBBkWCgBvBYJobAVWCRB63Ra9Qdgp +tkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcBWCJsdUfj +oYpld4qcYBqjxsyScwpID2vkNlYMLmS+IhYhBKyZqvZ6WI3zgaapXHrdFr1B2Cm2 +AAAEZwEA/UhpNN1XElYx6Xq+JMKlXywoIgButkQy1+H2EcRBeHsBAM7lq8BXvRKz +bDjRlgxiIAYl77p7ihVQ5NYcuZcAlH0CFiEEUN3omN9OSHVcjCt69vkItvpIoikA +AJcwAP9D4spfb28k16w2cemrWAtAE1WUgV8V+OEpE7+gpV+17gEA+0Kzf7jBHgd3 +pBAWwttuRd8OHlZZzKs3f26z28I6mgLOMwRobAVWFgkrBgEEAdpHDwEBB0DPyS14 +jQk1mSWNmuYR4P9M5zOfU2mkhwaqx1l3OWTZD8LAvwQYFgoBMQWCaGwFVgkQ9vkI +tvpIoilHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn+wfK +FmPmtrsi0sY5zIq9KFmbrQyhXz/VZIw6K8D1zdECmyC+oAQZFgoAbwWCaGwFVgkQ +bwujLUxU69BHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn +xF3KXB4+dN9suOhCD2XkYlAWUJ4GVBVV2wAmdQAueyEWIQTv1sMw2eUTIMQmb7Zv +C6MtTFTr0AAA/LYA/iBkRh6dGbp76VzuuHVNUNgTqvXgz9FjizZGJKnVZctXAPwL +TlHxcH6XX96AuiCy9QAMUpm8ZvMu8TAgjgOrlFPKCBYhBFDd6JjfTkh1XIwrevb5 +CLb6SKIpAAA0rQD9HWbBeSoshjH6/k5ntZjOfIAha4/TLlBrMq2w+t4LWD0A/2q5 +DEbYh6PwMidDxXteyHWf4Qnr0vH8vip9d+WHbDYEzjgEaGwFVhIKKwYBBAGXVQEF +AQEHQLxXHw9STOAhb2PLEjrl3uQDwpaXIdigg67vId0jSstVAwEIB8LAAAQYFgoA +cgWCaGwFVgkQ9vkItvpIoilHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9p +YS1wZ3Aub3Jn8bvuQCv3uEYJtK6h5y5e4AY9lJtVXx3brexR5bmFCwcCmwwWIQRQ +3eiY305IdVyMK3r2+Qi2+kiiKQAAEzkA/Az97rdlp3hf97S6a5AxU8pTry4gKI63 +lwKtBAT+uF/pAP9lAziQRlNEa1sX6qCXrQqeA/aQ0nj9gRJ1Wvi1PMxWBA== +=7jmE +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test/e2e/testdata/sigstore-registries.d-fragment.yaml b/test/e2e/testdata/sigstore-registries.d-fragment.yaml index ecac462039..790ec41396 100644 --- a/test/e2e/testdata/sigstore-registries.d-fragment.yaml +++ b/test/e2e/testdata/sigstore-registries.d-fragment.yaml @@ -3,3 +3,5 @@ docker: use-sigstore-attachments: true localhost:5003/sigstore-signed-params: use-sigstore-attachments: true + localhost:5003/simple-sq-signed: + lookaside: file://@lookasideDir@ diff --git a/vendor/go.podman.io/image/v5/signature/simplesequoia/mechanism.go b/vendor/go.podman.io/image/v5/signature/simplesequoia/mechanism.go new file mode 100644 index 0000000000..9ec71fa733 --- /dev/null +++ b/vendor/go.podman.io/image/v5/signature/simplesequoia/mechanism.go @@ -0,0 +1,52 @@ +//go:build containers_image_sequoia + +package simplesequoia + +// This implements a signature.signingMechanismWithPassphrase that only supports signing. +// +// FIXME: Consider restructuring the simple signing signature creation code path +// not to require this indirection and all those unimplemented methods. + +import ( + "go.podman.io/image/v5/signature/internal/sequoia" +) + +// A GPG/OpenPGP signing mechanism, implemented using Sequoia. +type sequoiaSigningOnlyMechanism struct { + inner *sequoia.SigningMechanism +} + +func (m *sequoiaSigningOnlyMechanism) Close() error { + panic("Should never be called") +} + +// SupportsSigning returns nil if the mechanism supports signing, or a SigningNotSupportedError. +func (m *sequoiaSigningOnlyMechanism) SupportsSigning() error { + panic("Should never be called") +} + +// Sign creates a (non-detached) signature of input using keyIdentity and passphrase. +// Fails with a SigningNotSupportedError if the mechanism does not support signing. +func (m *sequoiaSigningOnlyMechanism) SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error) { + return m.inner.SignWithPassphrase(input, keyIdentity, passphrase) +} + +// Sign creates a (non-detached) signature of input using keyIdentity. +// Fails with a SigningNotSupportedError if the mechanism does not support signing. +func (m *sequoiaSigningOnlyMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) { + panic("Should never be called") +} + +// Verify parses unverifiedSignature and returns the content and the signer's identity +func (m *sequoiaSigningOnlyMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) { + panic("Should never be called") +} + +// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION, +// along with a short identifier of the key used for signing. +// WARNING: The short key identifier (which corresponds to "Key ID" for OpenPGP keys) +// is NOT the same as a "key identity" used in other calls to this interface, and +// the values may have no recognizable relationship if the public key is not available. +func (m *sequoiaSigningOnlyMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) { + panic("Should never be called") +} diff --git a/vendor/go.podman.io/image/v5/signature/simplesequoia/options.go b/vendor/go.podman.io/image/v5/signature/simplesequoia/options.go new file mode 100644 index 0000000000..9e3448d729 --- /dev/null +++ b/vendor/go.podman.io/image/v5/signature/simplesequoia/options.go @@ -0,0 +1,37 @@ +package simplesequoia + +import ( + "errors" + "strings" +) + +type Option func(*simpleSequoiaSigner) error + +// WithSequoiaHome returns an Option for NewSigner, specifying a Sequoia home directory to use. +func WithSequoiaHome(sequoiaHome string) Option { + return func(s *simpleSequoiaSigner) error { + s.sequoiaHome = sequoiaHome + return nil + } +} + +// WithKeyFingerprint returns an Option for NewSigner, specifying a key to sign with, using the provided Sequoia-PGP key fingerprint. +func WithKeyFingerprint(keyFingerprint string) Option { + return func(s *simpleSequoiaSigner) error { + s.keyFingerprint = keyFingerprint + return nil + } +} + +// WithPassphrase returns an Option for NewSigner, specifying a passphrase for the private key. +func WithPassphrase(passphrase string) Option { + return func(s *simpleSequoiaSigner) error { + // The gpgme implementation can’t use passphrase with \n; reject it here for consistent behavior. + // FIXME: We don’t need it in this API at all, but the "\n" check exists in the current call stack. That should go away. + if strings.Contains(passphrase, "\n") { + return errors.New("invalid passphrase: must not contain a line break") + } + s.passphrase = passphrase + return nil + } +} diff --git a/vendor/go.podman.io/image/v5/signature/simplesequoia/signer.go b/vendor/go.podman.io/image/v5/signature/simplesequoia/signer.go new file mode 100644 index 0000000000..f4f9b870ad --- /dev/null +++ b/vendor/go.podman.io/image/v5/signature/simplesequoia/signer.go @@ -0,0 +1,88 @@ +//go:build containers_image_sequoia + +package simplesequoia + +import ( + "context" + "errors" + "fmt" + + "go.podman.io/image/v5/docker/reference" + internalSig "go.podman.io/image/v5/internal/signature" + internalSigner "go.podman.io/image/v5/internal/signer" + "go.podman.io/image/v5/signature" + "go.podman.io/image/v5/signature/internal/sequoia" + "go.podman.io/image/v5/signature/signer" +) + +// simpleSequoiaSigner is a signer.SignerImplementation implementation for simple signing signatures using Sequoia. +type simpleSequoiaSigner struct { + mech *sequoia.SigningMechanism + sequoiaHome string // "" if using the system’s default + keyFingerprint string + passphrase string // "" if not provided. +} + +// NewSigner returns a signature.Signer which creates “simple signing” signatures using the user’s default +// Sequoia PGP configuration. +// +// The set of options must identify a key to sign with, probably using a WithKeyFingerprint. +// +// The caller must call Close() on the returned Signer. +func NewSigner(opts ...Option) (*signer.Signer, error) { + s := simpleSequoiaSigner{} + for _, o := range opts { + if err := o(&s); err != nil { + return nil, err + } + } + if s.keyFingerprint == "" { + return nil, errors.New("no key identity provided for simple signing") + } + + if err := sequoia.Init(); err != nil { + return nil, err // Coverage: This is impractical to test in-process, with the static go_sequoia_dlhandle. + } + mech, err := sequoia.NewMechanismFromDirectory(s.sequoiaHome) + if err != nil { + return nil, fmt.Errorf("initializing Sequoia: %w", err) + } + s.mech = mech + succeeded := false + defer func() { + if !succeeded { + s.mech.Close() // Coverage: This is currently unreachable. + } + }() + + // Ideally, we should look up (and unlock?) the key at this point already. FIXME: is that possible? Anyway, low-priority. + + succeeded = true + return internalSigner.NewSigner(&s), nil +} + +// ProgressMessage returns a human-readable sentence that makes sense to write before starting to create a single signature. +func (s *simpleSequoiaSigner) ProgressMessage() string { + return "Signing image using Sequoia-PGP simple signing" +} + +// SignImageManifest creates a new signature for manifest m as dockerReference. +func (s *simpleSequoiaSigner) SignImageManifest(ctx context.Context, m []byte, dockerReference reference.Named) (internalSig.Signature, error) { + if reference.IsNameOnly(dockerReference) { + return nil, fmt.Errorf("reference %s can’t be signed, it has neither a tag nor a digest", dockerReference.String()) + } + wrapped := sequoiaSigningOnlyMechanism{ + inner: s.mech, + } + simpleSig, err := signature.SignDockerManifestWithOptions(m, dockerReference.String(), &wrapped, s.keyFingerprint, &signature.SignOptions{ + Passphrase: s.passphrase, + }) + if err != nil { + return nil, err + } + return internalSig.SimpleSigningFromBlob(simpleSig), nil +} + +func (s *simpleSequoiaSigner) Close() error { + return s.mech.Close() +} diff --git a/vendor/go.podman.io/image/v5/signature/simplesequoia/signer_stub.go b/vendor/go.podman.io/image/v5/signature/simplesequoia/signer_stub.go new file mode 100644 index 0000000000..0abb6c057c --- /dev/null +++ b/vendor/go.podman.io/image/v5/signature/simplesequoia/signer_stub.go @@ -0,0 +1,28 @@ +//go:build !containers_image_sequoia + +package simplesequoia + +import ( + "errors" + + "go.podman.io/image/v5/signature/signer" +) + +// simpleSequoiaSigner is a signer.SignerImplementation implementation for simple signing signatures using Sequoia. +type simpleSequoiaSigner struct { + // This is not really used, we just keep the struct fields so that the With… Option functions can be compiled. + + sequoiaHome string // "" if using the system's default + keyFingerprint string + passphrase string // "" if not provided. +} + +// NewSigner returns a signature.Signer which creates "simple signing" signatures using the user's default +// Sequoia PGP configuration. +// +// The set of options must identify a key to sign with, probably using a WithKeyFingerprint. +// +// The caller must call Close() on the returned Signer. +func NewSigner(opts ...Option) (*signer.Signer, error) { + return nil, errors.New("Sequoia-PGP support is not enabled in this build") +} diff --git a/vendor/modules.txt b/vendor/modules.txt index cbf1019431..c278d2e73c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -875,6 +875,7 @@ go.podman.io/image/v5/signature/sigstore go.podman.io/image/v5/signature/sigstore/fulcio go.podman.io/image/v5/signature/sigstore/internal go.podman.io/image/v5/signature/sigstore/rekor +go.podman.io/image/v5/signature/simplesequoia go.podman.io/image/v5/signature/simplesigning go.podman.io/image/v5/storage go.podman.io/image/v5/tarball