diff --git a/copy/copy.go b/copy/copy.go index 0501fb3c1..b616e566c 100644 --- a/copy/copy.go +++ b/copy/copy.go @@ -124,9 +124,10 @@ type ImageListSelection int // Options allows supplying non-default configuration modifying the behavior of CopyImage. 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(), - SignPassphrase string // Passphare to use when signing with the key ID from `SignBy`. + 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(), + SignPassphrase string // Passphare to use when signing with the key ID from `SignBy`. + SignIdentity reference.Named // Identify to use when signing, defaults to the docker reference of the destination ReportWriter io.Writer SourceCtx *types.SystemContext DestinationCtx *types.SystemContext @@ -574,7 +575,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur // Sign the manifest list. if options.SignBy != "" { - newSig, err := c.createSignature(manifestList, options.SignBy, options.SignPassphrase) + newSig, err := c.createSignature(manifestList, options.SignBy, options.SignPassphrase, options.SignIdentity) if err != nil { return nil, err } @@ -796,7 +797,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli } if options.SignBy != "" { - newSig, err := c.createSignature(manifestBytes, options.SignBy, options.SignPassphrase) + newSig, err := c.createSignature(manifestBytes, options.SignBy, options.SignPassphrase, options.SignIdentity) if err != nil { return nil, "", "", err } diff --git a/copy/sign.go b/copy/sign.go index 21a3facd7..aa42674bc 100644 --- a/copy/sign.go +++ b/copy/sign.go @@ -1,13 +1,14 @@ package copy import ( + "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports" "github.com/pkg/errors" ) // createSignature creates a new signature of manifest using keyIdentity. -func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string) ([]byte, error) { +func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string, identity reference.Named) ([]byte, error) { mech, err := signature.NewGPGSigningMechanism() if err != nil { return nil, errors.Wrap(err, "initializing GPG") @@ -17,13 +18,19 @@ func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase return nil, errors.Wrap(err, "Signing not supported") } - dockerReference := c.dest.Reference().DockerReference() - if dockerReference == nil { - return nil, errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(c.dest.Reference())) + if identity != nil { + if reference.IsNameOnly(identity) { + return nil, errors.Errorf("Sign identity must be a fully specified reference %s", identity) + } + } else { + identity = c.dest.Reference().DockerReference() + if identity == nil { + return nil, errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(c.dest.Reference())) + } } c.Printf("Signing manifest\n") - newSig, err := signature.SignDockerManifestWithOptions(manifest, dockerReference.String(), mech, keyIdentity, &signature.SignOptions{Passphrase: passphrase}) + newSig, err := signature.SignDockerManifestWithOptions(manifest, identity.String(), mech, keyIdentity, &signature.SignOptions{Passphrase: passphrase}) if err != nil { return nil, errors.Wrap(err, "creating signature") } diff --git a/copy/sign_test.go b/copy/sign_test.go index 0e74c7cde..1e9fc6738 100644 --- a/copy/sign_test.go +++ b/copy/sign_test.go @@ -12,6 +12,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/types" + "github.com/docker/distribution/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -49,7 +50,7 @@ func TestCreateSignature(t *testing.T) { dest: imagedestination.FromPublic(dirDest), reportWriter: ioutil.Discard, } - _, err = c.createSignature(manifestBlob, testKeyFingerprint, "") + _, err = c.createSignature(manifestBlob, testKeyFingerprint, "", nil) assert.Error(t, err) // Set up a docker: reference @@ -65,17 +66,35 @@ func TestCreateSignature(t *testing.T) { } // Signing with an unknown key fails - _, err = c.createSignature(manifestBlob, "this key does not exist", "") + _, err = c.createSignature(manifestBlob, "this key does not exist", "", nil) assert.Error(t, err) - // Success + // Can't sign without a full reference + ref, err := reference.ParseNamed("myregistry.io/myrepo") + require.NoError(t, err) + _, err = c.createSignature(manifestBlob, testKeyFingerprint, "", ref) + assert.Error(t, err) + + // Mechanism for verifying the signatures mech, err = signature.NewGPGSigningMechanism() require.NoError(t, err) defer mech.Close() - sig, err := c.createSignature(manifestBlob, testKeyFingerprint, "") + + // Signing without overriding the identity uses the docker reference + sig, err := c.createSignature(manifestBlob, testKeyFingerprint, "", nil) require.NoError(t, err) verified, err := signature.VerifyDockerManifestSignature(sig, manifestBlob, "docker.io/library/busybox:latest", mech, testKeyFingerprint) require.NoError(t, err) assert.Equal(t, "docker.io/library/busybox:latest", verified.DockerReference) assert.Equal(t, manifestDigest, verified.DockerManifestDigest) + + // Can override the identity with own + ref, err = reference.ParseNamed("myregistry.io/myrepo:mytag") + require.NoError(t, err) + sig, err = c.createSignature(manifestBlob, testKeyFingerprint, "", ref) + require.NoError(t, err) + verified, err = signature.VerifyDockerManifestSignature(sig, manifestBlob, "myregistry.io/myrepo:mytag", mech, testKeyFingerprint) + require.NoError(t, err) + assert.Equal(t, "myregistry.io/myrepo:mytag", verified.DockerReference) + assert.Equal(t, manifestDigest, verified.DockerManifestDigest) }