From aee0abb5d2de9cd9f59d9b208140164db803cf58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 24 May 2016 18:44:20 +0200 Subject: [PATCH] Add SigningMechanism.ImportKeysFromBytes This will be needed for verification against specified public keys. Also rerun hack/vendor.sh to pick up import support from github.com/mtrmac/gpgme . --- signature/fixtures/public-key.gpg | 19 +++++++ signature/mechanism.go | 23 +++++++++ signature/mechanism_test.go | 41 +++++++++++++++ vendor/github.com/mtrmac/gpgme/gpgme.go | 66 +++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 signature/fixtures/public-key.gpg diff --git a/signature/fixtures/public-key.gpg b/signature/fixtures/public-key.gpg new file mode 100644 index 00000000..46901d58 --- /dev/null +++ b/signature/fixtures/public-key.gpg @@ -0,0 +1,19 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0EVurzqQEEAL3qkFq4K2URtSWVDYnQUNA9HdM9sqS2eAWfqUFMrkD5f+oN+LBL +tPyaE5GNLA0vXY7nHAM2TeM8ijZ/eMP17Raj64JL8GhCymL3wn2jNvb9XaF0R0s6 +H0IaRPPu45A3SnxLwm4Orc/9Z7/UxtYjKSg9xOaTiVPzJgaf5Vm4J4ApABEBAAG0 +EnNrb3BlbyB0ZXN0aW5nIGtleYi4BBMBAgAiBQJW6vOpAhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRDbcvIYi7RsyBbOBACgJFiKDlQ1UyvsNmGqJ7D0OpbS +1OppJlradKgZXyfahFswhFI+7ZREvELLHbinq3dBy5cLXRWzQKdJZNHknSN5Tjf2 +0ipVBQuqpcBo+dnKiG4zH6fhTri7yeTZksIDfsqlI6FXDOdKLUSnahagEBn4yU+x +jHPvZk5SuuZv56A45biNBFbq86kBBADIC/9CsAlOmRALuYUmkhcqEjuFwn3wKz2d +IBjzgvro7zcVNNCgxQfMEjcUsvEh5cx13G3QQHcwOKy3M6Bv6VMhfZjd+1P1el4P +0fJS8GFmhWRBknMN8jFsgyohQeouQ798RFFv94KszfStNnr/ae8oao5URmoUXSCa +/MdUxn0YKwARAQABiJ8EGAECAAkFAlbq86kCGwwACgkQ23LyGIu0bMjUywQAq0dn +lUpDNSoLTcpNWuVvHQ7c/qmnE4TyiSLiRiAywdEWA6gMiyhUUucuGsEhMFP1WX1k +UNwArZ6UG7BDOUsvngP7jKGNqyUOQrq1s/r8D+0MrJGOWErGLlfttO2WeoijECkI +5qm8cXzAra3Xf/Z3VjxYTKSnNu37LtZkakdTdYE= +=tJAt +-----END PGP PUBLIC KEY BLOCK----- diff --git a/signature/mechanism.go b/signature/mechanism.go index 715c5a11..196ad927 100644 --- a/signature/mechanism.go +++ b/signature/mechanism.go @@ -13,6 +13,10 @@ import ( // 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 @@ -49,6 +53,25 @@ func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, er return gpgSigningMechanism{ctx: ctx}, nil } +// ImportKeysFromBytes implements SigningMechanism.ImportKeysFromBytes +func (m gpgSigningMechanism) ImportKeysFromBytes(blob []byte) ([]string, error) { + inputData, err := gpgme.NewDataBytes(blob) + if err != nil { + return nil, err + } + res, err := m.ctx.Import(inputData) + if err != nil { + return nil, err + } + keyIdentities := []string{} + for _, i := range res.Imports { + if i.Result == nil { + keyIdentities = append(keyIdentities, i.Fingerprint) + } + } + return keyIdentities, nil +} + // Sign implements SigningMechanism.Sign func (m gpgSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) { key, err := m.ctx.GetKey(keyIdentity, true) diff --git a/signature/mechanism_test.go b/signature/mechanism_test.go index f9c69756..8617b6d2 100644 --- a/signature/mechanism_test.go +++ b/signature/mechanism_test.go @@ -1,7 +1,9 @@ package signature import ( + "bytes" "io/ioutil" + "os" "testing" "github.com/stretchr/testify/assert" @@ -25,6 +27,45 @@ func TestNewGPGSigningMechanismInDirectory(t *testing.T) { // The various GPG failure cases are not obviously easy to reach. } +func TestGPGSigningMechanismImportKeysFromBytes(t *testing.T) { + testDir, err := ioutil.TempDir("", "gpg-import-keys") + require.NoError(t, err) + defer os.RemoveAll(testDir) + + mech, err := newGPGSigningMechanismInDirectory(testDir) + require.NoError(t, err) + + // 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) + + // Successful import + keyBlob, err := ioutil.ReadFile("./fixtures/public-key.gpg") + require.NoError(t, err) + keyIdentities, err := mech.ImportKeysFromBytes(keyBlob) + require.NoError(t, err) + 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) + + // Two keys: just concatenate the valid input twice. + keyIdentities, err = mech.ImportKeysFromBytes(bytes.Join([][]byte{keyBlob, keyBlob}, nil)) + require.NoError(t, err) + assert.Equal(t, []string{TestKeyFingerprint, TestKeyFingerprint}, keyIdentities) + + // Invalid input: This is accepted anyway by GPG, just returns no keys. + keyIdentities, err = mech.ImportKeysFromBytes([]byte("This is invalid")) + require.NoError(t, err) + assert.Equal(t, []string{}, keyIdentities) + // The various GPG/GPGME failures cases are not obviously easy to reach. +} + func TestGPGSigningMechanismSign(t *testing.T) { mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) require.NoError(t, err) diff --git a/vendor/github.com/mtrmac/gpgme/gpgme.go b/vendor/github.com/mtrmac/gpgme/gpgme.go index 40d786d5..444456f2 100644 --- a/vendor/github.com/mtrmac/gpgme/gpgme.go +++ b/vendor/github.com/mtrmac/gpgme/gpgme.go @@ -472,6 +472,72 @@ func (c *Context) Sign(signers []*Key, plain, sig *Data, mode SigMode) error { return handleError(C.gpgme_op_sign(c.ctx, plain.dh, sig.dh, C.gpgme_sig_mode_t(mode))) } +// ImportStatusFlags describes the type of ImportStatus.Status. The C API in gpgme.h simply uses "unsigned". +type ImportStatusFlags uint + +const ( + ImportNew ImportStatusFlags = C.GPGME_IMPORT_NEW + ImportUID ImportStatusFlags = C.GPGME_IMPORT_UID + ImportSIG ImportStatusFlags = C.GPGME_IMPORT_SIG + ImportSubKey ImportStatusFlags = C.GPGME_IMPORT_SUBKEY + ImportSecret ImportStatusFlags = C.GPGME_IMPORT_SECRET +) + +type ImportStatus struct { + Fingerprint string + Result error + Status ImportStatusFlags +} + +type ImportResult struct { + Considered int + NoUserID int + Imported int + ImportedRSA int + Unchanged int + NewUserIDs int + NewSubKeys int + NewSignatures int + NewRevocations int + SecretRead int + SecretImported int + SecretUnchanged int + NotImported int + Imports []ImportStatus +} + +func (c *Context) Import(keyData *Data) (*ImportResult, error) { + err := handleError(C.gpgme_op_import(c.ctx, keyData.dh)) + if err != nil { + return nil, err + } + res := C.gpgme_op_import_result(c.ctx) + imports := []ImportStatus{} + for s := res.imports; s != nil; s = s.next { + imports = append(imports, ImportStatus{ + Fingerprint: C.GoString(s.fpr), + Result: handleError(s.result), + Status: ImportStatusFlags(s.status), + }) + } + return &ImportResult{ + Considered: int(res.considered), + NoUserID: int(res.no_user_id), + Imported: int(res.imported), + ImportedRSA: int(res.imported_rsa), + Unchanged: int(res.unchanged), + NewUserIDs: int(res.new_user_ids), + NewSubKeys: int(res.new_sub_keys), + NewSignatures: int(res.new_signatures), + NewRevocations: int(res.new_revocations), + SecretRead: int(res.secret_read), + SecretImported: int(res.secret_imported), + SecretUnchanged: int(res.secret_unchanged), + NotImported: int(res.not_imported), + Imports: imports, + }, nil +} + type Key struct { k C.gpgme_key_t }