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
33 changes: 25 additions & 8 deletions signature/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,33 @@ func SignDockerManifest(manifest []byte, dockerReference string, mech SigningMec
// using mech.
func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte,
expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
expectedManifestDigest, err := utils.ManifestDigest(unverifiedManifest)
if err != nil {
return nil, err
}
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, expectedKeyIdentity, expectedDockerReference)
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 nil
},
validateSignedDockerReference: func(signedDockerReference string) error {
if signedDockerReference != expectedDockerReference {
return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
signedDockerReference, expectedDockerReference)}
}
return nil
},
validateSignedDockerManifestDigest: func(signedDockerManifestDigest string) error {
matches, err := utils.ManifestMatchesDigest(unverifiedManifest, signedDockerManifestDigest)
if err != nil {
return err
}
if !matches {
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %s does not match", signedDockerManifestDigest, signedDockerManifestDigest)}
}
return nil
},
})
if err != nil {
return nil, err
}
if sig.DockerManifestDigest != expectedManifestDigest {
return nil, InvalidSignatureError{msg: fmt.Sprintf("Docker manifest digest %s does not match %s", sig.DockerManifestDigest, expectedManifestDigest)}
}
return sig, nil
}
5 changes: 5 additions & 0 deletions signature/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func TestVerifyDockerManifestSignature(t *testing.T) {
assert.Error(t, err)
assert.Nil(t, sig)

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

// Docker manifest digest mismatch
sig, err = VerifyDockerManifestSignature(signature, []byte("unexpected manifest"), TestImageSignatureReference, mech, TestKeyFingerprint)
assert.Error(t, err)
Expand Down
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-mixed/manifest.json
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-mixed/signature-1
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-mixed/signature-2
27 changes: 27 additions & 0 deletions signature/fixtures/dir-img-modified-manifest/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
],
"extra": "this manifest has been modified"
}
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-modified-manifest/signature-1
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-no-manifest/signature-1
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-unsigned/manifest.json
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-valid-2/manifest.json
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-valid-2/signature-1
Binary file added signature/fixtures/dir-img-valid-2/signature-2
Binary file not shown.
1 change: 1 addition & 0 deletions signature/fixtures/dir-img-valid/manifest.json
Binary file added signature/fixtures/dir-img-valid/signature-1
Binary file not shown.
Binary file added signature/fixtures/unknown-key.signature
Binary file not shown.
7 changes: 7 additions & 0 deletions signature/mechanism_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,12 @@ func TestGPGSigningMechanismVerify(t *testing.T) {
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)

// Valid signature with an unknown key
signature, err = ioutil.ReadFile("./fixtures/unknown-key.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)

// The various GPG/GPGME failures cases are not obviously easy to reach.
}
7 changes: 3 additions & 4 deletions signature/policy_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,9 @@ func (m *policySpecificMap) UnmarshalJSON(data []byte) error {
// So, use a temporary map of pointers-to-slices and convert.
tmpMap := map[string]*PolicyRequirements{}
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
// Check that the scope format is at least plausible.
if _, err := reference.ParseNamed(key); err != nil {
return nil // FIXME? This returns an "Unknown key" error instead of saying that the format is invalid.
}
// FIXME? We might want to validate the scope format.
// Note that reference.ParseNamed is unsuitable; it would understand "example.com" as
// "docker.io/library/example.com"
// paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
if _, ok := tmpMap[key]; ok {
return nil
Expand Down
7 changes: 2 additions & 5 deletions signature/policy_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func TestPolicyUnmarshalJSON(t *testing.T) {
xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("abc"), NewPRMMatchExact()),
},
Specific: map[string]PolicyRequirements{
"library/busybox": []PolicyRequirement{
"docker.io/library/busybox": []PolicyRequirement{
xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("def"), NewPRMMatchExact()),
},
"registry.access.redhat.com": []PolicyRequirement{
Expand Down Expand Up @@ -191,11 +191,8 @@ func TestPolicyUnmarshalJSON(t *testing.T) {
func(v mSI) { v["specific"] = []string{} },
// "default" is an invalid PolicyRequirements
func(v mSI) { v["default"] = PolicyRequirements{} },
// Invalid scope name in "specific". Uppercase is invalid in Docker reference components.
// Get valid PolicyRequirements by copying them from "library/buxybox".
func(v mSI) { x(v, "specific")["INVALIDUPPERCASE"] = x(v, "specific")["library/busybox"] },
// A field in "specific" is an invalid PolicyRequirements
func(v mSI) { x(v, "specific")["library/busybox"] = PolicyRequirements{} },
func(v mSI) { x(v, "specific")["docker.io/library/busybox"] = PolicyRequirements{} },
}
for _, fn := range breakFns {
err = tryUnmarshalModifiedPolicy(t, &p, validJSON, fn)
Expand Down
Loading