Skip to content

Commit

Permalink
Merge pull request #317 from mtrmac/openpgp-v3
Browse files Browse the repository at this point in the history
Support v3 signature packets in mechanism_openpgp
  • Loading branch information
mtrmac authored Jul 31, 2017
2 parents 6ee6294 + 978b2ba commit e6ced5e
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 68 deletions.
Binary file added signature/fixtures/corrupt.signature-v3
Binary file not shown.
Binary file added signature/fixtures/invalid-blob.signature-v3
Binary file not shown.
Binary file modified signature/fixtures/unknown-key.signature
Binary file not shown.
Binary file added signature/fixtures/unknown-key.signature-v3
Binary file not shown.
14 changes: 10 additions & 4 deletions signature/mechanism_openpgp.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,17 @@ func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents [
if md.SignedBy == nil {
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Invalid GPG signature: %#v", md.Signature)}
}
if md.Signature.SigLifetimeSecs != nil {
expiry := md.Signature.CreationTime.Add(time.Duration(*md.Signature.SigLifetimeSecs) * time.Second)
if time.Now().After(expiry) {
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Signature expired on %s", expiry)}
if md.Signature != nil {
if md.Signature.SigLifetimeSecs != nil {
expiry := md.Signature.CreationTime.Add(time.Duration(*md.Signature.SigLifetimeSecs) * time.Second)
if time.Now().After(expiry) {
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Signature expired on %s", expiry)}
}
}
} else if md.SignatureV3 == nil {
// Coverage: If md.SignedBy != nil, the final md.UnverifiedBody.Read() either sets one of md.Signature or md.SignatureV3,
// or sets md.SignatureError.
return nil, "", InvalidSignatureError{msg: "Unexpected openpgp.MessageDetails: neither Signature nor SignatureV3 is set"}
}

// Uppercase the fingerprint to be compatible with gpgme
Expand Down
158 changes: 94 additions & 64 deletions signature/mechanism_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ const (
testGPGHomeDirectory = "./fixtures"
)

// Many of the tests use two fixtures: V4 signature packets (*.signature), and V3 signature packets (*.signature-v3)

// fixtureVariants loads V3 and V4 signature fixture variants based on the v4 fixture path, and returns a map which makes it easy to test both.
func fixtureVariants(t *testing.T, v4Path string) map[string][]byte {
v4, err := ioutil.ReadFile(v4Path)
require.NoError(t, err)
v3Path := v4Path + "-v3"
v3, err := ioutil.ReadFile(v3Path)
require.NoError(t, err)
return map[string][]byte{v4Path: v4, v3Path: v3}
}

func TestSigningNotSupportedError(t *testing.T) {
// A stupid test just to keep code coverage
s := "test"
Expand All @@ -40,13 +52,14 @@ func TestNewGPGSigningMechanismInDirectory(t *testing.T) {

// Test that using the default directory (presumably in user’s home)
// cannot use TestKeyFingerprint.
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
signatures := fixtureVariants(t, "./fixtures/invalid-blob.signature")
mech, err = newGPGSigningMechanismInDirectory("")
require.NoError(t, err)
defer mech.Close()
_, _, err = mech.Verify(signature)
assert.Error(t, err)
for version, signature := range signatures {
_, _, err := mech.Verify(signature)
assert.Error(t, err, version)
}

// Similarly, using a newly created empty directory makes TestKeyFingerprint
// unavailable
Expand All @@ -56,8 +69,10 @@ func TestNewGPGSigningMechanismInDirectory(t *testing.T) {
mech, err = newGPGSigningMechanismInDirectory(emptyDir)
require.NoError(t, err)
defer mech.Close()
_, _, err = mech.Verify(signature)
assert.Error(t, err)
for version, signature := range signatures {
_, _, err := mech.Verify(signature)
assert.Error(t, err, version)
}

// If pubring.gpg is unreadable in the directory, either initializing
// the mechanism fails (with openpgp), or it succeeds (sadly, gpgme) and
Expand All @@ -71,16 +86,20 @@ func TestNewGPGSigningMechanismInDirectory(t *testing.T) {
mech, err = newGPGSigningMechanismInDirectory(unreadableDir)
if err == nil {
defer mech.Close()
_, _, err = mech.Verify(signature)
for version, signature := range signatures {
_, _, err := mech.Verify(signature)
assert.Error(t, err, version)
}
}
assert.Error(t, err)

// Setting the directory parameter to testGPGHomeDirectory makes the key available.
mech, err = newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
defer mech.Close()
_, _, err = mech.Verify(signature)
assert.NoError(t, err)
for version, signature := range signatures {
_, _, err := mech.Verify(signature)
assert.NoError(t, err, version)
}

// If we use the default directory mechanism, GNUPGHOME is respected.
origGNUPGHOME := os.Getenv("GNUPGHOME")
Expand All @@ -89,8 +108,10 @@ func TestNewGPGSigningMechanismInDirectory(t *testing.T) {
mech, err = newGPGSigningMechanismInDirectory("")
require.NoError(t, err)
defer mech.Close()
_, _, err = mech.Verify(signature)
assert.NoError(t, err)
for version, signature := range signatures {
_, _, err := mech.Verify(signature)
assert.NoError(t, err, version)
}
}

func TestNewEphemeralGPGSigningMechanism(t *testing.T) {
Expand All @@ -100,10 +121,11 @@ func TestNewEphemeralGPGSigningMechanism(t *testing.T) {
defer mech.Close()
assert.Empty(t, keyIdentities)
// Try validating a signature when the key is unknown.
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
content, signingFingerprint, err := mech.Verify(signature)
require.Error(t, err)
signatures := fixtureVariants(t, "./fixtures/invalid-blob.signature")
for version, signature := range signatures {
_, _, err := mech.Verify(signature)
require.Error(t, err, version)
}

// Successful import
keyBlob, err := ioutil.ReadFile("./fixtures/public-key.gpg")
Expand All @@ -113,10 +135,12 @@ func TestNewEphemeralGPGSigningMechanism(t *testing.T) {
defer mech.Close()
assert.Equal(t, []string{TestKeyFingerprint}, keyIdentities)
// After import, the signature should validate.
content, signingFingerprint, err = mech.Verify(signature)
require.NoError(t, err)
assert.Equal(t, []byte("This is not JSON\n"), content)
assert.Equal(t, TestKeyFingerprint, signingFingerprint)
for version, signature := range signatures {
content, signingFingerprint, err := mech.Verify(signature)
require.NoError(t, err, version)
assert.Equal(t, []byte("This is not JSON\n"), content, version)
assert.Equal(t, TestKeyFingerprint, signingFingerprint, version)
}

// Two keys: Read the binary-format pubring.gpg, and concatenate it twice.
// (Using two copies of public-key.gpg, in the ASCII-armored format, works with
Expand Down Expand Up @@ -176,10 +200,10 @@ func TestGPGSigningMechanismSign(t *testing.T) {
// The various GPG/GPGME failures cases are not obviously easy to reach.
}

func assertSigningError(t *testing.T, content []byte, fingerprint string, err error) {
assert.Error(t, err)
assert.Nil(t, content)
assert.Empty(t, fingerprint)
func assertSigningError(t *testing.T, content []byte, fingerprint string, err error, msgAndArgs ...interface{}) {
assert.Error(t, err, msgAndArgs...)
assert.Nil(t, content, msgAndArgs...)
assert.Empty(t, fingerprint, msgAndArgs...)
}

func TestGPGSigningMechanismVerify(t *testing.T) {
Expand All @@ -188,53 +212,56 @@ func TestGPGSigningMechanismVerify(t *testing.T) {
defer mech.Close()

// Successful verification
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
content, signingFingerprint, err := mech.Verify(signature)
require.NoError(t, err)
assert.Equal(t, []byte("This is not JSON\n"), content)
assert.Equal(t, TestKeyFingerprint, signingFingerprint)
signatures := fixtureVariants(t, "./fixtures/invalid-blob.signature")
for variant, signature := range signatures {
content, signingFingerprint, err := mech.Verify(signature)
require.NoError(t, err, variant)
assert.Equal(t, []byte("This is not JSON\n"), content, variant)
assert.Equal(t, TestKeyFingerprint, signingFingerprint, variant)
}

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

// Completely invalid signature.
content, signingFingerprint, err = mech.Verify([]byte{})
content, signingFingerprint, err := mech.Verify([]byte{})
assertSigningError(t, content, signingFingerprint, err)

content, signingFingerprint, err = mech.Verify([]byte("invalid signature"))
assertSigningError(t, content, signingFingerprint, err)

// Literal packet, not a signature
signature, err = ioutil.ReadFile("./fixtures/unsigned-literal.signature")
signature, err := ioutil.ReadFile("./fixtures/unsigned-literal.signature") // Not fixtureVariants, the “literal data” packet does not have V3/V4 versions.
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)

// Encrypted data, not a signature.
signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature")
signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature") // Not fixtureVariants, the “public-key encrypted session key” does not have V3/V4 versions.
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)

// FIXME? Is there a way to create a multi-signature so that gpgme_op_verify returns multiple signatures?

// Expired signature
signature, err = ioutil.ReadFile("./fixtures/expired.signature")
signature, err = ioutil.ReadFile("./fixtures/expired.signature") // Not fixtureVariants, V3 signature packets don’t support expiration.
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)

// Corrupt signature
signature, err = ioutil.ReadFile("./fixtures/corrupt.signature")
require.NoError(t, err)
content, signingFingerprint, err = mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err)
signatures = fixtureVariants(t, "./fixtures/corrupt.signature")
for version, signature := range signatures {
content, signingFingerprint, err := mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err, version)
}

// 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)
signatures = fixtureVariants(t, "./fixtures/unknown-key.signature")
for version, signature := range signatures {
content, signingFingerprint, err := mech.Verify(signature)
assertSigningError(t, content, signingFingerprint, err, version)
}

// The various GPG/GPGME failures cases are not obviously easy to reach.
}
Expand All @@ -245,12 +272,13 @@ func TestGPGSigningMechanismUntrustedSignatureContents(t *testing.T) {
defer mech.Close()

// A valid signature
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
content, shortKeyID, err := mech.UntrustedSignatureContents(signature)
require.NoError(t, err)
assert.Equal(t, []byte("This is not JSON\n"), content)
assert.Equal(t, TestKeyShortID, shortKeyID)
signatures := fixtureVariants(t, "./fixtures/invalid-blob.signature")
for version, signature := range signatures {
content, shortKeyID, err := mech.UntrustedSignatureContents(signature)
require.NoError(t, err, version)
assert.Equal(t, []byte("This is not JSON\n"), content, version)
assert.Equal(t, TestKeyShortID, shortKeyID, version)
}

// Completely invalid signature.
_, _, err = mech.UntrustedSignatureContents([]byte{})
Expand All @@ -260,38 +288,40 @@ func TestGPGSigningMechanismUntrustedSignatureContents(t *testing.T) {
assert.Error(t, err)

// Literal packet, not a signature
signature, err = ioutil.ReadFile("./fixtures/unsigned-literal.signature")
signature, err := ioutil.ReadFile("./fixtures/unsigned-literal.signature") // Not fixtureVariants, the “literal data” packet does not have V3/V4 versions.
require.NoError(t, err)
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
content, shortKeyID, err := mech.UntrustedSignatureContents(signature)
assert.Error(t, err)

// Encrypted data, not a signature.
signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature")
signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature") // Not fixtureVariants, the “public-key encrypted session key” does not have V3/V4 versions.
require.NoError(t, err)
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
assert.Error(t, err)

// Expired signature
signature, err = ioutil.ReadFile("./fixtures/expired.signature")
signature, err = ioutil.ReadFile("./fixtures/expired.signature") // Not fixtureVariants, V3 signature packets don’t support expiration.
require.NoError(t, err)
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
require.NoError(t, err)
assert.Equal(t, []byte("This signature is expired.\n"), content)
assert.Equal(t, TestKeyShortID, shortKeyID)

// Corrupt signature
signature, err = ioutil.ReadFile("./fixtures/corrupt.signature")
require.NoError(t, err)
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
require.NoError(t, err)
assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic ","timestamp":1458239713}}`), content)
assert.Equal(t, TestKeyShortID, shortKeyID)
signatures = fixtureVariants(t, "./fixtures/corrupt.signature")
for version, signature := range signatures {
content, shortKeyID, err := mech.UntrustedSignatureContents(signature)
require.NoError(t, err, version)
assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic ","timestamp":1458239713}}`), content, version)
assert.Equal(t, TestKeyShortID, shortKeyID, version)
}

// Valid signature with an unknown key
signature, err = ioutil.ReadFile("./fixtures/unknown-key.signature")
require.NoError(t, err)
content, shortKeyID, err = mech.UntrustedSignatureContents(signature)
require.NoError(t, err)
assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic 0.1.13-dev","timestamp":1464633474}}`), content)
assert.Equal(t, "E5476D1110D07803", shortKeyID)
signatures = fixtureVariants(t, "./fixtures/unknown-key.signature")
for version, signature := range signatures {
content, shortKeyID, err := mech.UntrustedSignatureContents(signature)
require.NoError(t, err, version)
assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic 0.1.13-dev","timestamp":1464633474}}`), content, version)
assert.Equal(t, "BB75E91990DF8F7E", shortKeyID, version)
}
}

0 comments on commit e6ced5e

Please sign in to comment.