1
0
mirror of https://github.com/containers/podman.git synced 2026-02-05 15:45:08 +01: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:
Miloslav Trmač
2025-07-11 17:54:50 +02:00
parent 2f005b67f4
commit 9e2850d0a8
25 changed files with 365 additions and 16 deletions

View File

@@ -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 shouldnt prompt ourselves if no passphrase was explicitly provided.
// With signBySequoiaFingerprint, we dont prompt for a passphrase (for now??): We dont 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
}

View 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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 doesnt
// expose that as an option. So, for now, modify /etc/directly, and skip testing sigstore if
// we dont 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 isnt writable: %s\n", string(output))
GinkgoWriter.Printf("Skipping sigstore tests because /etc/containers/registries.d isnt 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())
}
}
}
})

View File

@@ -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})

View File

Binary file not shown.

View File

38
test/e2e/testdata/sequoia-key.pub vendored Normal file
View 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-----

View File

@@ -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@

View 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")
}

View 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 cant use passphrase with \n; reject it here for consistent behavior.
// FIXME: We dont 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
}
}

View 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 systems default
keyFingerprint string
passphrase string // "" if not provided.
}
// NewSigner returns a signature.Signer which creates “simple signing” signatures using the users 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 cant 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()
}

View 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
View File

@@ -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