Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add second signing mechanism based on native 'openpgp' Go library #198

Closed
wants to merge 6 commits into from
Closed
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
9 changes: 3 additions & 6 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type Options struct {
RemoveSignatures bool // Remove any pre-existing signatures. SignBy will still add a new signature.
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(),
ReportWriter io.Writer
Mechanism types.SigningMechanism
SourceCtx *types.SystemContext
DestinationCtx *types.SystemContext
}
Expand Down Expand Up @@ -123,7 +124,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
}()

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

if options != nil && options.SignBy != "" {
mech, err := signature.NewGPGSigningMechanism()
if err != nil {
return errors.Wrap(err, "Error initializing GPG")
}
dockerReference := dest.Reference().DockerReference()
if dockerReference == nil {
return errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(dest.Reference()))
}

writeReport("Signing manifest\n")
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, options.SignBy)
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), options.Mechanism, options.SignBy)
if err != nil {
return errors.Wrap(err, "Error creating signature")
}
Expand Down
15 changes: 8 additions & 7 deletions signature/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import (
"fmt"

"github.com/containers/image/manifest"
"github.com/opencontainers/go-digest"
"github.com/containers/image/types"
digest "github.com/opencontainers/go-digest"
)

// SignDockerManifest returns a signature for manifest as the specified dockerReference,
// using mech and keyIdentity.
func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string) ([]byte, error) {
func SignDockerManifest(m []byte, dockerReference string, mech types.SigningMechanism, keyIdentity string) ([]byte, error) {
manifestDigest, err := manifest.Digest(m)
if err != nil {
return nil, err
Expand All @@ -28,18 +29,18 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism,
// VerifyDockerManifestSignature checks that unverifiedSignature uses expectedKeyIdentity to sign unverifiedManifest as expectedDockerReference,
// using mech.
func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte,
expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
expectedDockerReference string, mech types.SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
validateKeyIdentity: func(keyIdentity string) error {
if keyIdentity != expectedKeyIdentity {
return InvalidSignatureError{msg: fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity)}
return types.NewInvalidSignatureError(fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity))
}
return nil
},
validateSignedDockerReference: func(signedDockerReference string) error {
if signedDockerReference != expectedDockerReference {
return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
signedDockerReference, expectedDockerReference)}
return types.NewInvalidSignatureError(fmt.Sprintf("Docker reference %s does not match %s",
signedDockerReference, expectedDockerReference))
}
return nil
},
Expand All @@ -49,7 +50,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt
return err
}
if !matches {
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest)}
return types.NewInvalidSignatureError(fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest))
}
return nil
},
Expand Down
84 changes: 0 additions & 84 deletions signature/docker_test.go

This file was deleted.

File renamed without changes.
85 changes: 85 additions & 0 deletions signature/gpgme/docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package gpgme

import (
"io/ioutil"
"testing"

"github.com/containers/image/signature"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestSignDockerManifest(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
manifest, err := ioutil.ReadFile("../fixtures/image.manifest.json")
require.NoError(t, err)

// Successful signing
s, err := signature.SignDockerManifest(manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
require.NoError(t, err)

verified, err := signature.VerifyDockerManifestSignature(s, manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
assert.NoError(t, err)
assert.Equal(t, signature.TestImageSignatureReference, verified.DockerReference)
assert.Equal(t, signature.TestImageManifestDigest, verified.DockerManifestDigest)

// Error computing Docker manifest
invalidManifest, err := ioutil.ReadFile("../fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
_, err = signature.SignDockerManifest(invalidManifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
assert.Error(t, err)

// Error creating blob to sign
_, err = signature.SignDockerManifest(manifest, "", mech, signature.TestKeyFingerprint)
assert.Error(t, err)

// Error signing
_, err = signature.SignDockerManifest(manifest, signature.TestImageSignatureReference, mech, "this fingerprint doesn't exist")
assert.Error(t, err)
}

func TestVerifyDockerManifestSignature(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
manifest, err := ioutil.ReadFile("../fixtures/image.manifest.json")
require.NoError(t, err)
s, err := ioutil.ReadFile("../fixtures/image.signature")
require.NoError(t, err)

// Successful verification
sig, err := signature.VerifyDockerManifestSignature(s, manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
require.NoError(t, err)
assert.Equal(t, signature.TestImageSignatureReference, sig.DockerReference)
assert.Equal(t, signature.TestImageManifestDigest, sig.DockerManifestDigest)

// For extra paranoia, test that we return nil data on error.

// Error computing Docker manifest
invalidManifest, err := ioutil.ReadFile("../fixtures/v2s1-invalid-signatures.manifest.json")
require.NoError(t, err)
sig, err = signature.VerifyDockerManifestSignature(s, invalidManifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)

// Error verifying signature
corruptSignature, err := ioutil.ReadFile("../fixtures/corrupt.signature")
sig, err = signature.VerifyDockerManifestSignature(corruptSignature, manifest, signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)

// Key fingerprint mismatch
sig, err = signature.VerifyDockerManifestSignature(s, manifest, signature.TestImageSignatureReference, mech, "unexpected fingerprint")
assert.Error(t, err)
assert.Nil(t, sig)

// Docker reference mismatch
sig, err = signature.VerifyDockerManifestSignature(s, manifest, "example.com/doesnt/match", mech, signature.TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)

// Docker manifest digest mismatch
sig, err = signature.VerifyDockerManifestSignature(s, []byte("unexpected manifest"), signature.TestImageSignatureReference, mech, signature.TestKeyFingerprint)
assert.Error(t, err)
assert.Nil(t, sig)
}
27 changes: 6 additions & 21 deletions signature/mechanism.go → signature/gpgme/gpgme.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
// Note: Consider the API unstable until the code supports at least three different image formats or transports.

package signature
package gpgme

import (
"bytes"
"fmt"

"github.com/containers/image/types"
"github.com/mtrmac/gpgme"
)

// SigningMechanism abstracts a way to sign binary blobs and verify their signatures.
// FIXME: Eventually expand on keyIdentity (namespace them between mechanisms to
// eliminate ambiguities, support CA signatures and perhaps other key properties)
type SigningMechanism interface {
// ImportKeysFromBytes imports public keys from the supplied blob and returns their identities.
// The blob is assumed to have an appropriate format (the caller is expected to know which one).
// NOTE: This may modify long-term state (e.g. key storage in a directory underlying the mechanism).
ImportKeysFromBytes(blob []byte) ([]string, error)
// Sign creates a (non-detached) signature of input using keyidentity
Sign(input []byte, keyIdentity string) ([]byte, error)
// Verify parses unverifiedSignature and returns the content and the signer's identity
Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error)
}

// A GPG/OpenPGP signing mechanism.
type gpgSigningMechanism struct {
ctx *gpgme.Context
}

// NewGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism.
func NewGPGSigningMechanism() (SigningMechanism, error) {
func NewGPGSigningMechanism() (types.SigningMechanism, error) {
return newGPGSigningMechanismInDirectory("")
}

// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty.
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) {
func newGPGSigningMechanismInDirectory(optionalDir string) (types.SigningMechanism, error) {
ctx, err := gpgme.New()
if err != nil {
return nil, err
Expand Down Expand Up @@ -109,13 +94,13 @@ func (m gpgSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte
return nil, "", err
}
if len(sigs) != 1 {
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Unexpected GPG signature count %d", len(sigs))}
return nil, "", types.NewInvalidSignatureError(fmt.Sprintf("Unexpected GPG signature count %d", len(sigs)))
}
sig := sigs[0]
// This is sig.Summary == gpgme.SigSumValid except for key trust, which we handle ourselves
if sig.Status != nil || sig.Validity == gpgme.ValidityNever || sig.ValidityReason != nil || sig.WrongKeyUsage {
// FIXME: Better error reporting eventually
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Invalid GPG signature: %#v", sig)}
return nil, "", types.NewInvalidSignatureError(fmt.Sprintf("Invalid GPG signature: %#v", sig))
}
return signedBuffer.Bytes(), sig.Fingerprint, nil
}
Loading