Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions signature/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package signature
import (
"errors"
"fmt"
"slices"
"strings"

"github.com/containers/image/v5/docker/reference"
Expand Down Expand Up @@ -64,15 +63,8 @@ func VerifyImageManifestSignatureUsingKeyIdentityList(unverifiedSignature, unver
if err != nil {
return nil, "", err
}
var matchedKeyIdentity string
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
validateKeyIdentity: func(keyIdentity string) error {
if !slices.Contains(expectedKeyIdentities, keyIdentity) {
return internal.NewInvalidSignatureError(fmt.Sprintf("Signature by %s does not match expected fingerprints %v", keyIdentity, expectedKeyIdentities))
}
matchedKeyIdentity = keyIdentity
return nil
},
sig, matchedKeyIdentity, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
acceptedKeyIdentities: expectedKeyIdentities,
validateSignedDockerReference: func(signedDockerReference string) error {
signedRef, err := reference.ParseNormalizedNamed(signedDockerReference)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-valid-subkey/manifest.json
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-valid-subkey/signature-1
58 changes: 58 additions & 0 deletions signature/fixtures/public-key-with-revoked-subkey.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)

mQGNBGiBSK8BDAC6debGjwAv5hBns4gmeY54Er/U3gFcLpa6Wn/a426T1+5PBfl9
Gdk9ZxCKv8SEG6rkSFPbG1CQ9abahWQXrjl7jxthG99DSAjLq6x/oR/9y/th8YDM
oFVzARMHNpSfP5mbM7GAfKoIMmcg9xRPtNNpRi3utm7P8Fhr1STK6YqazXf95DdU
EXvnbD2pFDQe3UJT6YYfTq3jjVNH3wJhELvriux9n7vCdep7v9te12DsYmxY8k/5
juBzJSLsH/VKCeWmZPa0cy4FsQZvFNYhOfUAUxIZ70hgfrb5BD5I6fR+xbWB8DJ8
u8B8u93pKVVJzIstUKVHbBU8OqAnfWpg07a6bpJNCIFiU8CTfs2ocCP15stUaaJP
tyyhMIH3tTZNk6o7oI1r4YS8p1ndBk1gFY9uxzLFgWRehsd0YWAYuEJgU5eVOc3S
0eqQivk15mqJktknvTThxoyR7ifeOpmTFlLfZHgrzrlWwd34az1fHwoyJ6U0kQbI
cxc4qq+1VJflSNkAEQEAAbQmYy9pbWFnZSB0ZXN0IGtleSB3aXRoIGEgUkVWT0tF
RCBzdWJrZXmJAbkEEwEIACMFAmiBSK8CGyEHCwkIBwMCAQYVCAIJCgsEFgIDAQIe
AQIXgAAKCRBrhJmvYziNY7B2C/0TY00nN0YKKclWuwlg/XX+NDD6mDWw4tUm0on6
zKZJ1tFTEIimXjsy9qbsi+wj6KMWoPozIG22+g/JZmot4llUSYbHLp8qi/egB7LM
ySMMq56BbfXVZCzDl5TuU+vaCozWlev4F1OXdJSoezEqnKfkcPZPGl6/X09VOlT2
GlGLzSB4hhQUm6now28VaPmz2k6pcjuTfpykUtFuzA+psCltEYYUfv2eZaWUChJo
naxRuq+3nmVZoHq5yMmjCr4COA4cvsKCL0bkXplXx8e7wqjc1jEi7xFfBwCZfLER
egMq+jKwNSXqPIeV+BCvlxhQRgCt80sgWpCbw6hdY33qtoYadnOJLPkcgwBRHoTc
kj4BuKcDPJ7fBzrGHErr2UGP+ZNDlkYY3ynle18XMh7UX2NhbWDhKmIwdQJ7KsM/
ibcSAjfJ4Mm42Tx458FOkUkGy/pfF9427aW0tr7ShFWozGLHNBmcIeJ4dk0sDD2f
uLRs4x0uyup8NuI7oS2YepHtZXe5AY0EaIFIrwEMALezb6rHjiNOfJMSNwNN6xx1
yUo6oDoV/cfh9KBTHTA9NChnfIqPt0sqi2PZXzyzJmd+wTTe+ROViz6HrIReWFpS
4TCouvWsBrJz9pn5Bjg2dJZcHRovd2BqAuIGM1DndqkV0DhjxcNhy2OP8tVajlJA
o+KtRJgioerF+Z278BfzTRXtI9V5L5D9Ak+3K0HJC0c2AwwCF+vLQzHGPWxHaM39
Z9rMJD9+91DPy7ZNdP5kGSTyL3S8TdDj7jaNQarjcSgoeA+QBW+az4/n+2P8HcaO
UqKpkUsfsFZipo1TvCuWkerbYcRP22yVW6j6SRjNzd/z6C1BNEJ+Kr7CZhbLc7IQ
/Jwv95ZMcLaT08Z9tcLqRlsEKCG8Q06asy1G0KOh89i7RjPoNON3mI795NAiJ/6g
hjUUksPLIrnaSkz6RnFayEfycLMMnXdy1Im3AYD4vULKdPrbk9YZB7N1olMBWuO8
GjOzI1EdjfRzaFQ08mzv7PlzmCcKHVCZtvUFjCnlXwARAQABiQGfBCgBCAAJBQJo
gUi3Ah0CAAoJEGuEma9jOI1jI5cL/jPFGFa5Ot70Ih8sh5MdVXnHgqypYpo0DlM0
a1sB1w/v1zLWgdQzjCXYAHgUPqyIixD2RkFccS7k/W2oK9vLqWXM/ES3jOZdGZ2f
OFoRmWtU3tOhP58W4lqpNd61lUqkN86DgFAG1OT3D0JW1lXjtEsMd1G3ijdo5ZhS
pjB14Jb67MTibENC4sIiV+N0EoHviTonajgqe5oT+BL10fYBlNLoh44mcaHiNFwd
FNugBUz5bBB9ax6ixADNxceORUnvsuuRj5L+/Z8152+RopVQlF94lb9bla5NnjVD
365mmDK7jOoqrZs/zANMzyd05BPLkLsW2RKIZvUFPp35KzL5RXZJ0nmRhMM02NlX
x9ktLc3MsgWbSf1NnKZrsh9iGxwv7EiaPsSA9gI7CpHI5NBvrAApq2wIvrSqK6Gz
FHzyFJq8bBivw09GVJvpSBY3jNpkTikel+aTBgIHDkq+MstqP9aa5rSlGdaEJfK8
I4WOdny7ASnAZKG7pbxOhjSefKd9HYkDPgQYAQgACQUCaIFIrwIbAgGpCRBrhJmv
YziNY8DdIAQZAQgABgUCaIFIrwAKCRA+8Pk6H2Apl2WyC/0WzEM8kSzdni2unynT
WDvpnhIfPgMaAG152+sPbFotZeLUpQYBZO/j/ecE0mGf4gQHBfRKac5L+sfphhbJ
vbCXQ0BQ6NdTmyJP+VPxD5AkbbEPFSa7Xz1Grmon6AfNyrZS3uvm8BuY4e+gmDJt
IXXofULC3WI7SAa5Betudjtu4eQgGgnvG2y5y+U1lS6GNm6Uvagn/RlJX1zTK16U
NJhew9ipN45u/B1L/7KdwvnRu748DGCPiZxEU2M2YHDeAvzGB2bszUuA9wh+lVBd
rATE11poKp5vaDnuQY/BJOASio2rpxfYYnYHXYfJNnptaERfy85BVZBOVTLr5TH7
/WiKMwNQLSFClsuiGc8vwQ8Iw9tmp8Pl5O7l1geLyTCfLbxbwIgsle1kE8jfcYzn
7/5vckaLF/44CrndSp31xbEYE+5KzXRHIYjF8KvaRq3nsNk5CGMuRdWNLIOkWFCZ
dgw9aKmw0bLL70DeHZmmjzrCcYOVLzNT6IBAsEMxLI91eB8MjAwAqI5txMVBGcIw
xi4/4E/eq8hvWLh0ENjCTf1LPx8PcBfImNgFPSLXJnRGr+ovOfJslYxxH3FzFl1n
uSY7rCZ4n6FWjoVE2uonh0IKVuFmWaIolKLSArV709NbucSSZ8YBIt55gpABj72I
cjPet4RM0oIal1m9/exrP0ePpmqz++yx+t5/sqHLhlASnRd13IqExp5x27uSP+8z
m6VRDxB4jJ2f2OamXjGWQR54USingwFJWgO6NPXOFvSrxBQYySAgSqFwpXVPdy7K
hs2iXLwUPCuWygCzcwYQe1PZG7uOVSWnydCRbrA/W+BrHo2/ZHeZamuyn+a9idOX
C2PIwuWWegNtZurAattWSeTML+YqvbwsriaZMLLyOaYTjNvB8ueiFi2iIJczDq9E
awpsDdxsdcmqEMqBXoON+8eAokh3CC2GfBdbRrySPRBk5qOSVtREjXxRbEm76C4P
w3CRx47n3m2O1iNrbb7B/1sMqT4LlVqZrTREeTzWNmc2EYlYANjo
=k/PT
-----END PGP PUBLIC KEY BLOCK-----
49 changes: 49 additions & 0 deletions signature/fixtures/public-key-with-subkey.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)

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
=1ZyP
-----END PGP PUBLIC KEY BLOCK-----
Binary file modified signature/fixtures/pubring.gpg
Binary file not shown.
96 changes: 96 additions & 0 deletions signature/fixtures/regenerate-keys.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#! /bin/bash

# NOTE: To generate v3 signatures, this MUST be run on a system with GPG < 2.1, e.g. a RHEL 7.
# WARNING: This lazily writes to $(pwd). It is best run on a short-term VM.

# This is only a fragment of the ideal script; as you regenerate any other keys, please work on improving it.

set -x

dest=$(pwd)/new-fixtures
mkdir -p "$dest"

function resign() {
local key_id=$1
local signature=$2
local other_opts=$3

(GNUPGHOME= gpg -d "signature/fixtures/$signature"; true) | gpg --sign --digest-algo SHA256 --default-key "$key_id" $other_opts - > "$dest/$signature"
}

export GNUPGHOME=$(mktemp -d -t regenerate-keys.XXXXXX)
echo "GNUPGHOME: $GNUPGHOME" # Don't set up trap(1) to delete it, to allow inspection / debugging.

# Key-Usage: auth is used because "cert" is implied, and the only one we want, but an empty value is not accepted
# by gpg.
cat >batch-input <<EOF
Key-Type: RSA
Key-Length: 3072
Key-Usage: auth
Subkey-Type: RSA
Subkey-Length: 3072
Subkey-Usage: sign
%no-protection
Name-Real: c/image test key with subkey
Expire-Date: 0
%commit
EOF
out=$(gpg --batch --gen-key --cert-digest-algo SHA256 < batch-input --status-fd 1 --with-colons)
echo "$out" | grep -v ' PROGRESS '

fingerprint=$(echo "$out" | awk '$2 == "KEY_CREATED" { print $4 }')
# Yes, --fingerprint is used twice, to include the subkey fingerprint.
subkey_fingerprint=$(gpg --list-keys --fingerprint --fingerprint --with-colon "$fingerprint" | awk -F ':' '$1 == "fpr" { fp = $10 } END { print fp }')
echo "TestKeyFingerprintPrimaryWithSubkey = \"$fingerprint\"" > fixtures_info
echo "TestKeyFingerprintSubkeyWithSubkey = \"$subkey_fingerprint\"" >> fixtures_info

resign $subkey_fingerprint subkey.signature
resign $subkey_fingerprint subkey.signature-v3 --force-v3-sigs
gpg --export --armor "$fingerprint" > $dest/public-key-with-subkey.gpg

# Key-Usage: auth is used because "cert" is implied, and the only one we want, but an empty value is not accepted
# by gpg.
cat >batch-input <<EOF
Key-Type: RSA
Key-Length: 3072
Key-Usage: auth
Subkey-Type: RSA
Subkey-Length: 3072
Subkey-Usage: sign
%no-protection
Name-Real: c/image test key with a REVOKED subkey
Expire-Date: 0
%commit
EOF
out=$(gpg --batch --gen-key --cert-digest-algo SHA256 < batch-input --status-fd 1 --with-colons)
echo "$out" | grep -v ' PROGRESS '

fingerprint=$(echo "$out" | awk '$2 == "KEY_CREATED" { print $4 }')
# Yes, --fingerprint is used twice, to include the subkey fingerprint.
subkey_fingerprint=$(gpg --list-keys --fingerprint --fingerprint --with-colon "$fingerprint" | awk -F ':' '$1 == "fpr" { fp = $10 } END { print fp }')
echo "TestKeyFingerprintPrimaryWithRevokedSubkey = \"$fingerprint\"" >> fixtures_info
echo "TestKeyFingerprintSubkeyWithRevokedSubkey = \"$subkey_fingerprint\"" >> fixtures_info

resign $subkey_fingerprint subkey-revoked.signature
resign $subkey_fingerprint subkey-revoked.signature-v3 --force-v3-sigs

# FIXME? Can this be fully automated? --batch alone doesn't work, --yes seems to be ignored.
# Answer "yes", "key is compromised" (NOT "no longer used", to break the subkey-revoked.signature* files created above),
# an empty message, and finally, "save"
gpg --yes --cert-digest-algo SHA256 --edit-key "$fingerprint" 'key 1' 'revkey'

gpg --export --armor "$fingerprint" > $dest/public-key-with-revoked-subkey.gpg




# EVENTUALLY, rebuild signature/fixtures/pubring.gpg from all keys (currently impossible because this script
# does not regenerate all keys that should be present there):
# GNUPGHOME=$dest gpg --import "$dest/public-key-with-subkey.gpg"

# === We are done. Show how the regenerated files differ.
for i in "$dest"/*; do
(echo "==== $i"; diff -u <(gpg --list-packets < "signature/fixtures/${i#$dest/}") <(gpg --list-packets < "$i")) |& less
done

cat fixtures_info
Binary file added signature/fixtures/subkey-revoked.signature
Binary file not shown.
Binary file added signature/fixtures/subkey-revoked.signature-v3
Binary file not shown.
Binary file added signature/fixtures/subkey.signature
Binary file not shown.
Binary file added signature/fixtures/subkey.signature-v3
Binary file not shown.
8 changes: 8 additions & 0 deletions signature/fixtures_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ const (
TestKeyShortID = "E932F44B23E8DD43"
// TestKeyFingerprintWithPassphrase is the fingerprint of the private key with passphrase in this directory.
TestKeyFingerprintWithPassphrase = "F2B501009F78B0B340221A12A3CD242DA6028093"
// TestKeyFingerprintPrimaryWithSubkey is the primary key fingerprint of signature/fixtures/public-key-with-subkey.gpg.
TestKeyFingerprintPrimaryWithSubkey = "B9F4CDB9FD8C475BFA340AC38D54A947EE396F36"
// TestKeyFingerprintSubkeyWithSubkey is the subkey fingerprint of signature/fixtures/public-key-with-subkey.gpg.
TestKeyFingerprintSubkeyWithSubkey = "57D1D95BBC53BA0EAFF0718CD75B3109B3BAAEAF"
// TestKeyFingerprintPrimaryWithRevokedSubkey is the primary key fingerprint of signature/fixtures/public-key-with-revoked-subkey.gpg.
TestKeyFingerprintPrimaryWithRevokedSubkey = "6D88C5A1993648A17B2BB0DC6B8499AF63388D63"
// TestKeyFingerprintSubkeyWithRevokedSubkey is the subkey fingerprint of signature/fixtures/public-key-with-revoked-subkey.gpg.
TestKeyFingerprintSubkeyWithRevokedSubkey = "4D33404E00B470050709F3233EF0F93A1F602997"
// TestPassphrase is the passphrase for TestKeyFingerprintWithPassphrase.
TestPassphrase = "WithPassphrase123"
)
Expand Down
15 changes: 14 additions & 1 deletion signature/mechanism.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ type SigningMechanism interface {
// Sign creates a (non-detached) signature of input using keyIdentity.
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
Sign(input []byte, keyIdentity string) ([]byte, error)
// Verify parses unverifiedSignature and returns the content and the signer's identity
// Verify parses unverifiedSignature and returns the content and the signer's identity.
// For mechanisms created using NewEphemeralGPGSigningMechanism, the returned key identity
// is expected to be one of the values returned by NewEphemeralGPGSigningMechanism,
// or the mechanism should implement signingMechanismWithVerificationIdentityLookup.
Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error)
// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION,
// along with a short identifier of the key used for signing.
Expand All @@ -46,6 +49,16 @@ type signingMechanismWithPassphrase interface {
SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error)
}

// signingMechanismWithVerificationIdentityLookup is an internal extension of SigningMechanism.
type signingMechanismWithVerificationIdentityLookup interface {
SigningMechanism
// keyIdentityForVerificationKeyIdentity re-checks the key identity returned by Verify
// if it doesn't match an identity returned by NewEphemeralGPGSigningMechanism, trying to match it.
// (To be more specific, for mechanisms which return a subkey fingerprint from Verify,
// this converts the subkey fingerprint into the corresponding primary key fingerprint.)
keyIdentityForVerificationKeyIdentity(keyIdentity string) (string, error)
}

// SigningNotSupportedError is returned when trying to sign using a mechanism which does not support that.
type SigningNotSupportedError string

Expand Down
23 changes: 22 additions & 1 deletion signature/mechanism_gpgme.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ func (m *gpgmeSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte,
return m.SignWithPassphrase(input, keyIdentity, "")
}

// Verify parses unverifiedSignature and returns the content and the signer's identity
// Verify parses unverifiedSignature and returns the content and the signer's identity.
// For mechanisms created using NewEphemeralGPGSigningMechanism, the returned key identity
// is expected to be one of the values returned by NewEphemeralGPGSigningMechanism,
// or the mechanism should implement signingMechanismWithVerificationIdentityLookup.
func (m *gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
signedBuffer := bytes.Buffer{}
signedData, err := gpgme.NewDataWriter(&signedBuffer)
Expand All @@ -196,6 +199,24 @@ func (m *gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []b
return signedBuffer.Bytes(), sig.Fingerprint, nil
}

// keyIdentityForVerificationKeyIdentity re-checks the key identity returned by Verify
// if it doesn't match an identity returned by NewEphemeralGPGSigningMechanism, trying to match it.
// (To be more specific, for mechanisms which return a subkey fingerprint from Verify,
// this converts the subkey fingerprint into the corresponding primary key fingerprint.)
func (m *gpgmeSigningMechanism) keyIdentityForVerificationKeyIdentity(keyIdentity string) (string, error) {
// In theory, if keyIdentity refers to a subkey, the same subkey could be attached to different primary keys;
// in that case, GetKey fails with “ambiguous name”.
// We _could_ handle that, by using KeyList* (GetKey is internally just a helper for KeyList*), but sharing
// a subkey that way is very unexpected, so, for now, prefer the much simpler implementation.
key, err := m.ctx.GetKey(keyIdentity, false)
if err != nil {
return "", err
}
// In theory this value could be nil if (gpg --list-keys --with-colons) misses a "pub:" line
// or a "fpr:" line, but gpg (in recent enough versions) prints that unconditionally. // codespell:ignore fpr
return key.Fingerprint(), nil
}

// 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)
Expand Down
2 changes: 2 additions & 0 deletions signature/mechanism_gpgme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ func TestMain(m *testing.M) {
os.Exit(code)
}

var _ signingMechanismWithVerificationIdentityLookup = &gpgmeSigningMechanism{}

func TestGPGMESigningMechanismClose(t *testing.T) {
// Closing an ephemeral mechanism removes the directory.
// (The non-ephemeral case is tested in the common TestGPGSigningMechanismClose)
Expand Down
7 changes: 5 additions & 2 deletions signature/mechanism_openpgp.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ func (m *openpgpSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte
return m.SignWithPassphrase(input, keyIdentity, "")
}

// Verify parses unverifiedSignature and returns the content and the signer's identity
// Verify parses unverifiedSignature and returns the content and the signer's identity.
// For mechanisms created using NewEphemeralGPGSigningMechanism, the returned key identity
// is expected to be one of the values returned by NewEphemeralGPGSigningMechanism,
// or the mechanism should implement signingMechanismWithVerificationIdentityLookup.
func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
md, err := openpgp.ReadMessage(bytes.NewReader(unverifiedSignature), m.keyring, nil, nil)
if err != nil {
Expand Down Expand Up @@ -166,7 +169,7 @@ func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents [
}

// Uppercase the fingerprint to be compatible with gpgme
return content, strings.ToUpper(fmt.Sprintf("%x", md.SignedBy.PublicKey.Fingerprint)), nil
return content, strings.ToUpper(fmt.Sprintf("%x", md.SignedBy.Entity.PrimaryKey.Fingerprint)), nil
}

// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION,
Expand Down
Loading