Merge pull request #26618 from mtrmac/sequoia

Add --sign-by-sq-fingerprint
This commit is contained in:
openshift-merge-bot[bot] 2025-10-21 11:06:38 +00:00 committed by GitHub
commit b3ec5cf29e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 394 additions and 20 deletions

View file

@ -95,6 +95,7 @@ validate-source_task:
image_name: "${FEDORA_CACHE_IMAGE_NAME}" # from stdenvars
env:
TEST_FLAVOR: validate-source
TEST_BUILD_TAGS: ""
# NOTE: The default way Cirrus-CI clones is *NOT* compatible with
# environment expectations in contrib/cirrus/lib.sh. Specifically
# the 'origin' remote must be defined, and all remote branches/tags
@ -151,11 +152,13 @@ build_task:
# Ref: https://cirrus-ci.org/guide/writing-tasks/#matrix-modification
- env: &stdenvars
DISTRO_NV: ${FEDORA_NAME}
TEST_BUILD_TAGS: ""
# Not used here, is used in other tasks
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
CTR_FQIN: ${FEDORA_CONTAINER_FQIN}
- env:
DISTRO_NV: ${PRIOR_FEDORA_NAME}
TEST_BUILD_TAGS: ""
VM_IMAGE_NAME: ${PRIOR_FEDORA_CACHE_IMAGE_NAME}
CTR_FQIN: ${PRIOR_FEDORA_CONTAINER_FQIN}
CI_DESIRED_DATABASE: boltdb
@ -163,11 +166,13 @@ build_task:
- env:
<<: *stdenvars
DISTRO_NV: ${RAWHIDE_NAME}
TEST_BUILD_TAGS: "containers_image_sequoia"
VM_IMAGE_NAME: ${RAWHIDE_CACHE_IMAGE_NAME}
CI_DESIRED_STORAGE: composefs
CTR_FQIN: ""
- env:
DISTRO_NV: ${DEBIAN_NAME}
TEST_BUILD_TAGS: ""
VM_IMAGE_NAME: ${DEBIAN_CACHE_IMAGE_NAME}
env:
TEST_FLAVOR: build
@ -209,6 +214,7 @@ build_aarch64_task:
VM_IMAGE_NAME: ${FEDORA_AARCH64_AMI}
CTR_FQIN: ${FEDORA_CONTAINER_FQIN}
TEST_FLAVOR: build
TEST_BUILD_TAGS: ""
clone_script: *full_clone
# TODO: Rename to "ci-sanity" and move into task that runs in parallel to build
prebuild_script: *prebuild
@ -236,6 +242,7 @@ alt_build_task:
env:
<<: *stdenvars
TEST_FLAVOR: "altbuild"
TEST_BUILD_TAGS: ""
gce_instance: *fastvm
matrix:
- env:
@ -399,6 +406,7 @@ bindings_task:
env:
<<: *stdenvars
TEST_FLAVOR: bindings
TEST_BUILD_TAGS: ""
# N/B: This script depends on ${DISTRO_NV} being defined for the task.
clone_script: &get_gosrc |
cd /tmp
@ -440,6 +448,7 @@ swagger_task:
GCPJSON: ENCRYPTED[927dc01e755eaddb4242b0845cf86c9098d1e3dffac38c70aefb1487fd8b4fe6dd6ae627b3bffafaba70e2c63172664e]
GCPNAME: ENCRYPTED[c145e9c16b6fb88d476944a454bf4c1ccc84bb4ecaca73bdd28bdacef0dfa7959ebc8171a27b2e4064d66093b2cdba49]
GCPPROJECT: 'libpod-218412'
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main
@ -533,6 +542,7 @@ docker-py_test_task:
<<: *stdenvars
TEST_FLAVOR: docker-py
TEST_ENVIRON: container
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main
@ -565,6 +575,7 @@ unit_test_task:
gce_instance: *standardvm
env:
TEST_FLAVOR: unit
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main
@ -627,6 +638,7 @@ apiv2_test_task:
env:
<<: *stdenvars
TEST_FLAVOR: apiv2
TEST_BUILD_TAGS: ""
matrix:
- env:
PRIV_NAME: root
@ -661,6 +673,7 @@ compose_test_task:
env:
<<: *stdenvars
TEST_FLAVOR: compose_v2
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main
@ -733,6 +746,7 @@ container_integration_test_task:
env:
TEST_FLAVOR: int
TEST_ENVIRON: container
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main
@ -777,6 +791,7 @@ podman_machine_task:
env:
EC2_INST_TYPE: "m5zn.metal" # Bare-metal instance is required
TEST_FLAVOR: "machine-linux"
TEST_BUILD_TAGS: ""
PRIV_NAME: "rootless" # intended use-case
DISTRO_NV: "${FEDORA_NAME}"
VM_IMAGE_NAME: "${FEDORA_AMI}"
@ -796,6 +811,7 @@ podman_machine_aarch64_task:
timeout_in: 30m
env:
TEST_FLAVOR: "machine-linux"
TEST_BUILD_TAGS: ""
EC2_INST_TYPE: c6g.metal
PRIV_NAME: "rootless" # intended use-case
DISTRO_NV: "${FEDORA_AARCH64_NAME}"
@ -951,6 +967,7 @@ local_system_test_aarch64_task: &local_system_test_task_aarch64
env:
<<: *stdenvars_aarch64
TEST_FLAVOR: sys
TEST_BUILD_TAGS: ""
DISTRO_NV: ${FEDORA_AARCH64_NAME}
clone_script: *get_gosrc_aarch64
setup_script: *setup
@ -1028,6 +1045,7 @@ farm_test_task:
env:
<<: *stdenvars
TEST_FLAVOR: farm
TEST_BUILD_TAGS: ""
PRIV_NAME: rootless
clone_script: *get_gosrc
setup_script: *setup
@ -1050,6 +1068,7 @@ buildah_bud_test_task:
env:
<<: *stdenvars
TEST_FLAVOR: bud
TEST_BUILD_TAGS: ""
matrix:
- env:
PODBIN_NAME: podman
@ -1087,6 +1106,7 @@ upgrade_test_task:
gce_instance: *standardvm
env:
TEST_FLAVOR: upgrade_test
TEST_BUILD_TAGS: ""
DISTRO_NV: ${FEDORA_NAME}
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
# Never force a DB, let the old version decide its default
@ -1235,6 +1255,7 @@ release_task:
env:
<<: *stdenvars
TEST_FLAVOR: release
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main
@ -1262,6 +1283,7 @@ release_test_task:
env:
<<: *stdenvars
TEST_FLAVOR: release
TEST_BUILD_TAGS: ""
clone_script: *get_gosrc
setup_script: *setup
main_script: *main

View file

@ -56,6 +56,7 @@ SYSTEMDDIR ?= ${LIBDIR}/systemd/system
USERSYSTEMDDIR ?= ${LIBDIR}/systemd/user
SYSTEMDGENERATORSDIR ?= ${LIBDIR}/systemd/system-generators
USERSYSTEMDGENERATORSDIR ?= ${LIBDIR}/systemd/user-generators
SEQUOIA_SONAME_DIR =
REMOTETAGS ?= remote exclude_graphdriver_btrfs containers_image_openpgp
BUILDTAGS ?= \
grpcnotrace \
@ -131,6 +132,7 @@ LDFLAGS_PODMAN ?= \
-X $(LIBPOD)/config._installPrefix=$(PREFIX) \
-X $(LIBPOD)/config._etcDir=$(ETCDIR) \
-X $(PROJECT)/v5/pkg/systemd/quadlet._binDir=$(BINDIR) \
-X go.podman.io/image/v5/signature/internal/sequoia.sequoiaLibraryDir='"$(SEQUOIA_SONAME_DIR)"' \
-X go.podman.io/common/pkg/config.additionalHelperBinariesDir=$(HELPER_BINARIES_DIR)\
$(EXTRA_LDFLAGS)
LDFLAGS_PODMAN_STATIC ?= \

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

@ -213,7 +213,8 @@ function _run_build() {
# Ensure always start from clean-slate with all vendor modules downloaded
showrun make clean
showrun make vendor
showrun make -j $(nproc) --output-sync=target podman-release # includes podman, podman-remote, and docs
# shellcheck disable=SC2154
showrun make -j $(nproc) --output-sync=target podman-release EXTRA_BUILDTAGS="$TEST_BUILD_TAGS" # includes podman, podman-remote, and docs
# There's no reason to validate-binaries across multiple linux platforms
# shellcheck disable=SC2154
@ -416,7 +417,7 @@ dotest() {
die "The CI test TMPDIR is not on a tmpfs mount, we need tmpfs to make the tests faster"
fi
showrun make ${localremote}${testsuite} PODMAN_SERVER_LOG=$PODMAN_SERVER_LOG \
showrun make ${localremote}${testsuite} PODMAN_SERVER_LOG=$PODMAN_SERVER_LOG EXTRA_BUILDTAGS="$TEST_BUILD_TAGS" \
|& logformatter
# FIXME: https://github.com/containers/podman/issues/22642

View file

@ -422,11 +422,11 @@ case "$TEST_FLAVOR" in
die "Refusing to config. host-test in container";
fi
remove_packaged_podman_files
make install PREFIX=/usr ETCDIR=/etc
make install PREFIX=/usr ETCDIR=/etc EXTRA_BUILDTAGS="$TEST_BUILD_TAGS"
elif [[ "$TEST_ENVIRON" == "container" ]]; then
if ((CONTAINER)); then
remove_packaged_podman_files
make install PREFIX=/usr ETCDIR=/etc
make install PREFIX=/usr ETCDIR=/etc EXTRA_BUILDTAGS="$TEST_BUILD_TAGS"
fi
else
die "Invalid value for \$TEST_ENVIRON=$TEST_ENVIRON"

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

@ -1481,8 +1481,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(`
{
@ -1511,11 +1511,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

@ -878,6 +878,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