mirror of
https://github.com/containers/podman.git
synced 2025-11-29 09:37:38 +08:00
Add --sign-by-sq-fingerprint to push operations
This adds a new feature that allows signing using Sequoia-backed keys. The existing options to sign using GPG-backed keys (and sigstore) remain unchanged, and continue to use the same backends as usual. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
This commit is contained in:
@@ -12,13 +12,15 @@ import (
|
|||||||
"go.podman.io/image/v5/pkg/cli"
|
"go.podman.io/image/v5/pkg/cli"
|
||||||
"go.podman.io/image/v5/pkg/cli/sigstore"
|
"go.podman.io/image/v5/pkg/cli/sigstore"
|
||||||
"go.podman.io/image/v5/signature/signer"
|
"go.podman.io/image/v5/signature/signer"
|
||||||
|
"go.podman.io/image/v5/signature/simplesequoia"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SigningCLIOnlyOptions contains signing-related CLI options.
|
// SigningCLIOnlyOptions contains signing-related CLI options.
|
||||||
// Some other options are defined in entities.ImagePushOptions.
|
// Some other options are defined in entities.ImagePushOptions.
|
||||||
type SigningCLIOnlyOptions struct {
|
type SigningCLIOnlyOptions struct {
|
||||||
signPassphraseFile string
|
signPassphraseFile string
|
||||||
signBySigstoreParamFile string
|
signBySequoiaFingerprint string
|
||||||
|
signBySigstoreParamFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefineSigningFlags(cmd *cobra.Command, cliOpts *SigningCLIOnlyOptions, pushOpts *entities.ImagePushOptions) {
|
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")
|
flags.StringVar(&pushOpts.SignBy, signByFlagName, "", "Add a signature at the destination using the specified key")
|
||||||
_ = cmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone)
|
_ = 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"
|
signBySigstoreFlagName := "sign-by-sigstore"
|
||||||
flags.StringVar(&cliOpts.signBySigstoreParamFile, signBySigstoreFlagName, "", "Sign the image using a sigstore parameter file at `PATH`")
|
flags.StringVar(&cliOpts.signBySigstoreParamFile, signBySigstoreFlagName, "", "Sign the image using a sigstore parameter file at `PATH`")
|
||||||
_ = cmd.RegisterFlagCompletionFunc(signBySigstoreFlagName, completion.AutocompleteDefault)
|
_ = cmd.RegisterFlagCompletionFunc(signBySigstoreFlagName, completion.AutocompleteDefault)
|
||||||
@@ -42,6 +48,7 @@ func DefineSigningFlags(cmd *cobra.Command, cliOpts *SigningCLIOnlyOptions, push
|
|||||||
|
|
||||||
if registry.IsRemote() {
|
if registry.IsRemote() {
|
||||||
_ = flags.MarkHidden(signByFlagName)
|
_ = flags.MarkHidden(signByFlagName)
|
||||||
|
_ = flags.MarkHidden(signBySequoiaFingerprintFlagName)
|
||||||
_ = flags.MarkHidden(signBySigstoreFlagName)
|
_ = flags.MarkHidden(signBySigstoreFlagName)
|
||||||
_ = flags.MarkHidden(signBySigstorePrivateKeyFlagName)
|
_ = flags.MarkHidden(signBySigstorePrivateKeyFlagName)
|
||||||
_ = flags.MarkHidden(signPassphraseFileFlagName)
|
_ = 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,
|
// 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.
|
// with independent passphrases, but that would make the CLI probably too confusing.
|
||||||
// For now, use the passphrase with either, but only one of them.
|
// For now, use the passphrase with either, but only one of them.
|
||||||
if cliOpts.signPassphraseFile != "" && pushOpts.SignBy != "" && pushOpts.SignBySigstorePrivateKeyFile != "" {
|
if cliOpts.signPassphraseFile != "" {
|
||||||
return nil, fmt.Errorf("only one of --sign-by and sign-by-sigstore-private-key can be used with --sign-passphrase-file")
|
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
|
var passphrase string
|
||||||
@@ -72,9 +91,16 @@ func PrepareSigning(pushOpts *entities.ImagePushOptions, cliOpts *SigningCLIOnly
|
|||||||
p := ssh.ReadPassphrase()
|
p := ssh.ReadPassphrase()
|
||||||
passphrase = string(p)
|
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.
|
} // 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.SignPassphrase = passphrase
|
||||||
pushOpts.SignSigstorePrivateKeyPassphrase = []byte(passphrase)
|
pushOpts.SignSigstorePrivateKeyPassphrase = []byte(passphrase)
|
||||||
cleanup := signingCleanup{}
|
cleanup := signingCleanup{}
|
||||||
|
succeeded := false
|
||||||
|
defer func() {
|
||||||
|
if !succeeded {
|
||||||
|
cleanup.cleanup()
|
||||||
|
}
|
||||||
|
}()
|
||||||
if cliOpts.signBySigstoreParamFile != "" {
|
if cliOpts.signBySigstoreParamFile != "" {
|
||||||
signer, err := sigstore.NewSignerFromParameterFile(cliOpts.signBySigstoreParamFile, &sigstore.Options{
|
signer, err := sigstore.NewSignerFromParameterFile(cliOpts.signBySigstoreParamFile, &sigstore.Options{
|
||||||
PrivateKeyPassphrasePrompt: cli.ReadPassphraseFile,
|
PrivateKeyPassphrasePrompt: cli.ReadPassphraseFile,
|
||||||
@@ -87,6 +113,21 @@ func PrepareSigning(pushOpts *entities.ImagePushOptions, cliOpts *SigningCLIOnly
|
|||||||
pushOpts.Signers = append(pushOpts.Signers, signer)
|
pushOpts.Signers = append(pushOpts.Signers, signer)
|
||||||
cleanup.signers = append(cleanup.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
|
return cleanup.cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
docs/source/markdown/options/sign-by-sq-fingerprint.md
Normal file
8
docs/source/markdown/options/sign-by-sq-fingerprint.md
Normal file
@@ -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)
|
||||||
@@ -4,4 +4,4 @@
|
|||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--sign-passphrase-file**=*path*
|
#### **--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.
|
||||||
|
|||||||
@@ -38,11 +38,12 @@ Add a “simple signing” signature at the destination using the specified key.
|
|||||||
|
|
||||||
@@option sign-by-sigstore
|
@@option sign-by-sigstore
|
||||||
|
|
||||||
|
|
||||||
#### **--sign-by-sigstore-private-key**=*path*
|
#### **--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)
|
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 sign-passphrase-file
|
||||||
|
|
||||||
@@option tls-verify
|
@@option tls-verify
|
||||||
|
|||||||
@@ -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)
|
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 sign-passphrase-file
|
||||||
|
|
||||||
@@option tls-verify
|
@@option tls-verify
|
||||||
|
|||||||
@@ -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)
|
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 sign-passphrase-file
|
||||||
|
|
||||||
@@option tls-verify
|
@@option tls-verify
|
||||||
|
|||||||
@@ -1309,8 +1309,8 @@ func (p *PodmanTestIntegration) removeNetwork(name string) {
|
|||||||
|
|
||||||
// generatePolicyFile generates a signature verification policy file.
|
// generatePolicyFile generates a signature verification policy file.
|
||||||
// it returns the policy file path.
|
// it returns the policy file path.
|
||||||
func generatePolicyFile(tempDir string, port int) string {
|
func generatePolicyFile(tempDir string, port int, sequoiaKeyPath string) string {
|
||||||
keyPath := filepath.Join(tempDir, "key.gpg")
|
gpgKeyPath := filepath.Join(tempDir, "key.gpg")
|
||||||
policyPath := filepath.Join(tempDir, "policy.json")
|
policyPath := filepath.Join(tempDir, "policy.json")
|
||||||
conf := fmt.Sprintf(`
|
conf := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
@@ -1339,11 +1339,18 @@ func generatePolicyFile(tempDir string, port int) string {
|
|||||||
"type": "sigstoreSigned",
|
"type": "sigstoreSigned",
|
||||||
"keyPath": "testdata/sigstore-key.pub"
|
"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)
|
writeConf([]byte(conf), policyPath)
|
||||||
return policyPath
|
return policyPath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -13,9 +13,13 @@ import (
|
|||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
. "github.com/onsi/gomega/gexec"
|
. "github.com/onsi/gomega/gexec"
|
||||||
|
"go.podman.io/image/v5/signature/simplesequoia"
|
||||||
"go.podman.io/storage/pkg/archive"
|
"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() {
|
var _ = Describe("Podman push", func() {
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@@ -235,22 +239,26 @@ var _ = Describe("Podman push", func() {
|
|||||||
Expect(push2).Should(ExitCleanly())
|
Expect(push2).Should(ExitCleanly())
|
||||||
|
|
||||||
if !IsRemote() { // Remote does not support signing
|
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
|
// 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
|
// expose that as an option. So, for now, modify /etc/directly, and skip testing sigstore if
|
||||||
// we don’t have permission to do so.
|
// 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"
|
systemRegistriesDAddition := "/etc/containers/registries.d/podman-test-only-temporary-addition.yaml"
|
||||||
cmd := exec.Command("cp", "testdata/sigstore-registries.d-fragment.yaml", systemRegistriesDAddition)
|
registriesDFragment, err := os.ReadFile("testdata/sigstore-registries.d-fragment.yaml")
|
||||||
output, err := cmd.CombinedOutput()
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
registriesDFragment = bytes.ReplaceAll(registriesDFragment, []byte("@lookasideDir@"), []byte(lookasideDir))
|
||||||
|
err = os.WriteFile(systemRegistriesDAddition, registriesDFragment, 0644)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
|
By("pushing and pulling with --sign-by-sigstore-private-key")
|
||||||
defer func() {
|
defer func() {
|
||||||
err := os.Remove(systemRegistriesDAddition)
|
err := os.Remove(systemRegistriesDAddition)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
}()
|
}()
|
||||||
// Generate a signature verification policy file
|
// Generate a signature verification policy file
|
||||||
policyPath := generatePolicyFile(podmanTest.TempDir, 5003)
|
policyPath := generatePolicyFile(podmanTest.TempDir, 5003, "testdata/sequoia-key.pub")
|
||||||
defer os.Remove(policyPath)
|
defer os.Remove(policyPath)
|
||||||
|
|
||||||
// Verify that the policy rejects unsigned images
|
// 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 = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5003/sigstore-signed-params"})
|
||||||
pull.WaitWithDefaultTimeout()
|
pull.WaitWithDefaultTimeout()
|
||||||
Expect(pull).Should(ExitCleanly())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ default-docker:
|
|||||||
|
|
||||||
if !IsRemote() {
|
if !IsRemote() {
|
||||||
// Generate a signature verification policy file
|
// Generate a signature verification policy file
|
||||||
policyPath := generatePolicyFile(podmanTest.TempDir, port)
|
policyPath := generatePolicyFile(podmanTest.TempDir, port, "testdata/sequoia-key.pub")
|
||||||
defer os.Remove(policyPath)
|
defer os.Remove(policyPath)
|
||||||
|
|
||||||
session = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, pushedImage})
|
session = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, pushedImage})
|
||||||
|
|||||||
0
test/e2e/testdata/data/keystore/keystore.cookie
vendored
Normal file
0
test/e2e/testdata/data/keystore/keystore.cookie
vendored
Normal file
BIN
test/e2e/testdata/data/keystore/softkeys/1F5825285B785E1DB13BF36D2D11A19ABA41C6AE.pgp
vendored
Normal file
BIN
test/e2e/testdata/data/keystore/softkeys/1F5825285B785E1DB13BF36D2D11A19ABA41C6AE.pgp
vendored
Normal file
Binary file not shown.
BIN
test/e2e/testdata/data/keystore/softkeys/50DDE898DF4E48755C8C2B7AF6F908B6FA48A229.pgp
vendored
Normal file
BIN
test/e2e/testdata/data/keystore/softkeys/50DDE898DF4E48755C8C2B7AF6F908B6FA48A229.pgp
vendored
Normal file
Binary file not shown.
BIN
test/e2e/testdata/data/pgp.cert.d/1f/5825285b785e1db13bf36d2d11a19aba41c6ae
vendored
Normal file
BIN
test/e2e/testdata/data/pgp.cert.d/1f/5825285b785e1db13bf36d2d11a19aba41c6ae
vendored
Normal file
Binary file not shown.
BIN
test/e2e/testdata/data/pgp.cert.d/4d/8bcd544b7573eefaad18c278473e5f255d10b8
vendored
Normal file
BIN
test/e2e/testdata/data/pgp.cert.d/4d/8bcd544b7573eefaad18c278473e5f255d10b8
vendored
Normal file
Binary file not shown.
BIN
test/e2e/testdata/data/pgp.cert.d/50/dde898df4e48755c8c2b7af6f908b6fa48a229
vendored
Normal file
BIN
test/e2e/testdata/data/pgp.cert.d/50/dde898df4e48755c8c2b7af6f908b6fa48a229
vendored
Normal file
Binary file not shown.
BIN
test/e2e/testdata/data/pgp.cert.d/68/de230c4a009f5ee5fbb27984642d0130b86046
vendored
Normal file
BIN
test/e2e/testdata/data/pgp.cert.d/68/de230c4a009f5ee5fbb27984642d0130b86046
vendored
Normal file
Binary file not shown.
BIN
test/e2e/testdata/data/pgp.cert.d/trust-root
vendored
Normal file
BIN
test/e2e/testdata/data/pgp.cert.d/trust-root
vendored
Normal file
Binary file not shown.
0
test/e2e/testdata/data/pgp.cert.d/writelock
vendored
Normal file
0
test/e2e/testdata/data/pgp.cert.d/writelock
vendored
Normal file
38
test/e2e/testdata/sequoia-key.pub
vendored
Normal file
38
test/e2e/testdata/sequoia-key.pub
vendored
Normal file
@@ -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-----
|
||||||
@@ -3,3 +3,5 @@ docker:
|
|||||||
use-sigstore-attachments: true
|
use-sigstore-attachments: true
|
||||||
localhost:5003/sigstore-signed-params:
|
localhost:5003/sigstore-signed-params:
|
||||||
use-sigstore-attachments: true
|
use-sigstore-attachments: true
|
||||||
|
localhost:5003/simple-sq-signed:
|
||||||
|
lookaside: file://@lookasideDir@
|
||||||
|
|||||||
52
vendor/go.podman.io/image/v5/signature/simplesequoia/mechanism.go
generated
vendored
Normal file
52
vendor/go.podman.io/image/v5/signature/simplesequoia/mechanism.go
generated
vendored
Normal file
@@ -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")
|
||||||
|
}
|
||||||
37
vendor/go.podman.io/image/v5/signature/simplesequoia/options.go
generated
vendored
Normal file
37
vendor/go.podman.io/image/v5/signature/simplesequoia/options.go
generated
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
88
vendor/go.podman.io/image/v5/signature/simplesequoia/signer.go
generated
vendored
Normal file
88
vendor/go.podman.io/image/v5/signature/simplesequoia/signer.go
generated
vendored
Normal file
@@ -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()
|
||||||
|
}
|
||||||
28
vendor/go.podman.io/image/v5/signature/simplesequoia/signer_stub.go
generated
vendored
Normal file
28
vendor/go.podman.io/image/v5/signature/simplesequoia/signer_stub.go
generated
vendored
Normal file
@@ -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")
|
||||||
|
}
|
||||||
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@@ -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/fulcio
|
||||||
go.podman.io/image/v5/signature/sigstore/internal
|
go.podman.io/image/v5/signature/sigstore/internal
|
||||||
go.podman.io/image/v5/signature/sigstore/rekor
|
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/signature/simplesigning
|
||||||
go.podman.io/image/v5/storage
|
go.podman.io/image/v5/storage
|
||||||
go.podman.io/image/v5/tarball
|
go.podman.io/image/v5/tarball
|
||||||
|
|||||||
Reference in New Issue
Block a user