Skip to content

Commit 1823cf0

Browse files
committed
Implement second signing mechanism using Go native openpgp (only verification)
1 parent 514da4b commit 1823cf0

20 files changed

Lines changed: 461 additions & 235 deletions

copy/copy.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ type Options struct {
9090
RemoveSignatures bool // Remove any pre-existing signatures. SignBy will still add a new signature.
9191
SignBy string // If non-empty, asks for a signature to be added during the copy, and specifies a key ID, as accepted by signature.NewGPGSigningMechanism().SignDockerManifest(),
9292
ReportWriter io.Writer
93+
Mechanism types.SigningMechanism
9394
SourceCtx *types.SystemContext
9495
DestinationCtx *types.SystemContext
9596
}
@@ -123,7 +124,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
123124
}()
124125

125126
// Please keep this policy check BEFORE reading any other information about the image.
126-
if allowed, err := policyContext.IsRunningImageAllowed(unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
127+
if allowed, err := policyContext.IsRunningImageAllowed(options.Mechanism, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
127128
return errors.Wrap(err, "Source image rejected")
128129
}
129130
src, err := image.FromUnparsedImage(unparsedImage)
@@ -200,17 +201,13 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
200201
}
201202

202203
if options != nil && options.SignBy != "" {
203-
mech, err := signature.NewGPGSigningMechanism()
204-
if err != nil {
205-
return errors.Wrap(err, "Error initializing GPG")
206-
}
207204
dockerReference := dest.Reference().DockerReference()
208205
if dockerReference == nil {
209206
return errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(dest.Reference()))
210207
}
211208

212209
writeReport("Signing manifest\n")
213-
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, options.SignBy)
210+
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), options.Mechanism, options.SignBy)
214211
if err != nil {
215212
return errors.Wrap(err, "Error creating signature")
216213
}

signature/docker.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import (
66
"fmt"
77

88
"github.com/containers/image/manifest"
9+
"github.com/containers/image/types"
910
"github.com/docker/distribution/digest"
1011
)
1112

1213
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
1314
// using mech and keyIdentity.
14-
func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string) ([]byte, error) {
15+
func SignDockerManifest(m []byte, dockerReference string, mech types.SigningMechanism, keyIdentity string) ([]byte, error) {
1516
manifestDigest, err := manifest.Digest(m)
1617
if err != nil {
1718
return nil, err
@@ -28,18 +29,18 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism,
2829
// VerifyDockerManifestSignature checks that unverifiedSignature uses expectedKeyIdentity to sign unverifiedManifest as expectedDockerReference,
2930
// using mech.
3031
func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte,
31-
expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
32+
expectedDockerReference string, mech types.SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
3233
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
3334
validateKeyIdentity: func(keyIdentity string) error {
3435
if keyIdentity != expectedKeyIdentity {
35-
return InvalidSignatureError{msg: fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity)}
36+
return types.NewInvalidSignatureError(fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity))
3637
}
3738
return nil
3839
},
3940
validateSignedDockerReference: func(signedDockerReference string) error {
4041
if signedDockerReference != expectedDockerReference {
41-
return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
42-
signedDockerReference, expectedDockerReference)}
42+
return types.NewInvalidSignatureError(fmt.Sprintf("Docker reference %s does not match %s",
43+
signedDockerReference, expectedDockerReference))
4344
}
4445
return nil
4546
},
@@ -49,7 +50,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
4950
return err
5051
}
5152
if !matches {
52-
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest)}
53+
return types.NewInvalidSignatureError(fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest))
5354
}
5455
return nil
5556
},

signature/docker_test.go

Lines changed: 0 additions & 84 deletions
This file was deleted.

signature/gpgme/docker_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package gpgme
2+
3+
import (
4+
"io/ioutil"
5+
"testing"
6+
7+
"github.com/containers/image/signature"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestSignDockerManifest(t *testing.T) {
13+
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
14+
require.NoError(t, err)
15+
manifest, err := ioutil.ReadFile("../fixtures/image.manifest.json")
16+
require.NoError(t, err)
17+
18+
// Successful signing
19+
s, err := signature.SignDockerManifest(manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
20+
require.NoError(t, err)
21+
22+
verified, err := signature.VerifyDockerManifestSignature(s, manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
23+
assert.NoError(t, err)
24+
assert.Equal(t, signature.TestImageSignatureReference, verified.DockerReference)
25+
assert.Equal(t, signature.TestImageManifestDigest, verified.DockerManifestDigest)
26+
27+
// Error computing Docker manifest
28+
invalidManifest, err := ioutil.ReadFile("../fixtures/v2s1-invalid-signatures.manifest.json")
29+
require.NoError(t, err)
30+
_, err = signature.SignDockerManifest(invalidManifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
31+
assert.Error(t, err)
32+
33+
// Error creating blob to sign
34+
_, err = signature.SignDockerManifest(manifest, "", mech, signature.TestKeyFingerprint)
35+
assert.Error(t, err)
36+
37+
// Error signing
38+
_, err = signature.SignDockerManifest(manifest, signature.TestImageSignatureReference, mech, "this fingerprint doesn't exist")
39+
assert.Error(t, err)
40+
}
41+
42+
func TestVerifyDockerManifestSignature(t *testing.T) {
43+
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
44+
require.NoError(t, err)
45+
manifest, err := ioutil.ReadFile("../fixtures/image.manifest.json")
46+
require.NoError(t, err)
47+
s, err := ioutil.ReadFile("../fixtures/image.signature")
48+
require.NoError(t, err)
49+
50+
// Successful verification
51+
sig, err := signature.VerifyDockerManifestSignature(s, manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
52+
require.NoError(t, err)
53+
assert.Equal(t, signature.TestImageSignatureReference, sig.DockerReference)
54+
assert.Equal(t, signature.TestImageManifestDigest, sig.DockerManifestDigest)
55+
56+
// For extra paranoia, test that we return nil data on error.
57+
58+
// Error computing Docker manifest
59+
invalidManifest, err := ioutil.ReadFile("../fixtures/v2s1-invalid-signatures.manifest.json")
60+
require.NoError(t, err)
61+
sig, err = signature.VerifyDockerManifestSignature(s, invalidManifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
62+
assert.Error(t, err)
63+
assert.Nil(t, sig)
64+
65+
// Error verifying signature
66+
corruptSignature, err := ioutil.ReadFile("../fixtures/corrupt.signature")
67+
sig, err = signature.VerifyDockerManifestSignature(corruptSignature, manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
68+
assert.Error(t, err)
69+
assert.Nil(t, sig)
70+
71+
// Key fingerprint mismatch
72+
sig, err = signature.VerifyDockerManifestSignature(s, manifest, signature.TestImageSignatureReference, mech, "unexpected fingerprint")
73+
assert.Error(t, err)
74+
assert.Nil(t, sig)
75+
76+
// Docker reference mismatch
77+
sig, err = signature.VerifyDockerManifestSignature(s, manifest, "example.com/doesnt/match", mech, signature.TestKeyFingerprint)
78+
assert.Error(t, err)
79+
assert.Nil(t, sig)
80+
81+
// Docker manifest digest mismatch
82+
sig, err = signature.VerifyDockerManifestSignature(s, []byte("unexpected manifest"), signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
83+
assert.Error(t, err)
84+
assert.Nil(t, sig)
85+
}
Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,25 @@
1-
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
2-
3-
package signature
1+
package gpgme
42

53
import (
64
"bytes"
75
"fmt"
86

7+
"github.com/containers/image/types"
98
"github.com/mtrmac/gpgme"
109
)
1110

12-
// SigningMechanism abstracts a way to sign binary blobs and verify their signatures.
13-
// FIXME: Eventually expand on keyIdentity (namespace them between mechanisms to
14-
// eliminate ambiguities, support CA signatures and perhaps other key properties)
15-
type SigningMechanism interface {
16-
// ImportKeysFromBytes imports public keys from the supplied blob and returns their identities.
17-
// The blob is assumed to have an appropriate format (the caller is expected to know which one).
18-
// NOTE: This may modify long-term state (e.g. key storage in a directory underlying the mechanism).
19-
ImportKeysFromBytes(blob []byte) ([]string, error)
20-
// Sign creates a (non-detached) signature of input using keyidentity
21-
Sign(input []byte, keyIdentity string) ([]byte, error)
22-
// Verify parses unverifiedSignature and returns the content and the signer's identity
23-
Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error)
24-
}
25-
2611
// A GPG/OpenPGP signing mechanism.
2712
type gpgSigningMechanism struct {
2813
ctx *gpgme.Context
2914
}
3015

3116
// NewGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism.
32-
func NewGPGSigningMechanism() (SigningMechanism, error) {
17+
func NewGPGSigningMechanism() (types.SigningMechanism, error) {
3318
return newGPGSigningMechanismInDirectory("")
3419
}
3520

3621
// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty.
37-
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) {
22+
func newGPGSigningMechanismInDirectory(optionalDir string) (types.SigningMechanism, error) {
3823
ctx, err := gpgme.New()
3924
if err != nil {
4025
return nil, err
@@ -109,13 +94,13 @@ func (m gpgSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte
10994
return nil, "", err
11095
}
11196
if len(sigs) != 1 {
112-
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Unexpected GPG signature count %d", len(sigs))}
97+
return nil, "", types.NewInvalidSignatureError(fmt.Sprintf("Unexpected GPG signature count %d", len(sigs)))
11398
}
11499
sig := sigs[0]
115100
// This is sig.Summary == gpgme.SigSumValid except for key trust, which we handle ourselves
116101
if sig.Status != nil || sig.Validity == gpgme.ValidityNever || sig.ValidityReason != nil || sig.WrongKeyUsage {
117102
// FIXME: Better error reporting eventually
118-
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Invalid GPG signature: %#v", sig)}
103+
return nil, "", types.NewInvalidSignatureError(fmt.Sprintf("Invalid GPG signature: %#v", sig))
119104
}
120105
return signedBuffer.Bytes(), sig.Fingerprint, nil
121106
}

0 commit comments

Comments
 (0)