mirror of
https://github.com/containers/skopeo.git
synced 2025-07-18 08:41:41 +00:00
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 .
This commit is contained in:
parent
721a628f4a
commit
aee0abb5d2
19
signature/fixtures/public-key.gpg
Normal file
19
signature/fixtures/public-key.gpg
Normal file
@ -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-----
|
@ -13,6 +13,10 @@ import (
|
|||||||
// FIXME: Eventually expand on keyIdentity (namespace them between mechanisms to
|
// FIXME: Eventually expand on keyIdentity (namespace them between mechanisms to
|
||||||
// eliminate ambiguities, support CA signatures and perhaps other key properties)
|
// eliminate ambiguities, support CA signatures and perhaps other key properties)
|
||||||
type SigningMechanism interface {
|
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 creates a (non-detached) signature of input using keyidentity
|
||||||
Sign(input []byte, keyIdentity string) ([]byte, error)
|
Sign(input []byte, keyIdentity string) ([]byte, error)
|
||||||
// Verify parses unverifiedSignature and returns the content and the signer's identity
|
// 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
|
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
|
// Sign implements SigningMechanism.Sign
|
||||||
func (m gpgSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
|
func (m gpgSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
|
||||||
key, err := m.ctx.GetKey(keyIdentity, true)
|
key, err := m.ctx.GetKey(keyIdentity, true)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package signature
|
package signature
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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.
|
// 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) {
|
func TestGPGSigningMechanismSign(t *testing.T) {
|
||||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
66
vendor/github.com/mtrmac/gpgme/gpgme.go
generated
vendored
66
vendor/github.com/mtrmac/gpgme/gpgme.go
generated
vendored
@ -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)))
|
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 {
|
type Key struct {
|
||||||
k C.gpgme_key_t
|
k C.gpgme_key_t
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user