-
Notifications
You must be signed in to change notification settings - Fork 369
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: Allow choosing between a gpgme and openpgp signature backend usi…
…ng a build tag The default is gpgme; a containers_image_openpgp build tag can be used to use openpgp instead. openpgp does not currently support signing, and is based on mfojtik's implementation (adding GPG home directory support, parsing of unarmored keys, and fixing ImportKeysFromBytes semantics). NOTE: The openpgp backend is not really fleshed out yet. Some of the mechanism_test.go tests may be better mechanism-specific, and openpgp definitely needs more tests e.g. for optionalDir. Signed-off-by: Miloslav Trmač <mitr@redhat.com>
- Loading branch information
Showing
7 changed files
with
254 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// +build !containers_image_openpgp | ||
|
||
package signature | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
|
||
"github.com/mtrmac/gpgme" | ||
) | ||
|
||
// A GPG/OpenPGP signing mechanism, implemented using gpgme. | ||
type gpgmeSigningMechanism struct { | ||
ctx *gpgme.Context | ||
} | ||
|
||
// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty. | ||
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) { | ||
ctx, err := gpgme.New() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err = ctx.SetProtocol(gpgme.ProtocolOpenPGP); err != nil { | ||
return nil, err | ||
} | ||
if optionalDir != "" { | ||
err := ctx.SetEngineInfo(gpgme.ProtocolOpenPGP, "", optionalDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
ctx.SetArmor(false) | ||
ctx.SetTextMode(false) | ||
return gpgmeSigningMechanism{ctx: ctx}, nil | ||
} | ||
|
||
// ImportKeysFromBytes implements SigningMechanism.ImportKeysFromBytes | ||
func (m gpgmeSigningMechanism) 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 | ||
} | ||
|
||
// SupportsSigning returns nil if the mechanism supports signing, or an error message. | ||
func (m gpgmeSigningMechanism) SupportsSigning() error { | ||
return nil | ||
} | ||
|
||
// Sign implements SigningMechanism.Sign | ||
func (m gpgmeSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) { | ||
key, err := m.ctx.GetKey(keyIdentity, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
inputData, err := gpgme.NewDataBytes(input) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var sigBuffer bytes.Buffer | ||
sigData, err := gpgme.NewDataWriter(&sigBuffer) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err = m.ctx.Sign([]*gpgme.Key{key}, inputData, sigData, gpgme.SigModeNormal); err != nil { | ||
return nil, err | ||
} | ||
return sigBuffer.Bytes(), nil | ||
} | ||
|
||
// Verify implements SigningMechanism.Verify | ||
func (m gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) { | ||
signedBuffer := bytes.Buffer{} | ||
signedData, err := gpgme.NewDataWriter(&signedBuffer) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
unverifiedSignatureData, err := gpgme.NewDataBytes(unverifiedSignature) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
_, sigs, err := m.ctx.Verify(unverifiedSignatureData, nil, signedData) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
if len(sigs) != 1 { | ||
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Unexpected GPG signature count %d", len(sigs))} | ||
} | ||
sig := sigs[0] | ||
// This is sig.Summary == gpgme.SigSumValid except for key trust, which we handle ourselves | ||
if sig.Status != nil || sig.Validity == gpgme.ValidityNever || sig.ValidityReason != nil || sig.WrongKeyUsage { | ||
// FIXME: Better error reporting eventually | ||
return nil, "", InvalidSignatureError{msg: fmt.Sprintf("Invalid GPG signature: %#v", sig)} | ||
} | ||
return signedBuffer.Bytes(), sig.Fingerprint, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// +build containers_image_openpgp | ||
|
||
package signature | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"strings" | ||
"time" | ||
|
||
"github.com/containers/storage/pkg/homedir" | ||
"golang.org/x/crypto/openpgp" | ||
) | ||
|
||
// A GPG/OpenPGP signing mechanism, implemented using x/crypto/openpgp. | ||
type openpgpSigningMechanism struct { | ||
keyring openpgp.EntityList | ||
} | ||
|
||
// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty. | ||
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) { | ||
m := &openpgpSigningMechanism{ | ||
keyring: openpgp.EntityList{}, | ||
} | ||
|
||
gpgHome := optionalDir | ||
if gpgHome == "" { | ||
gpgHome = os.Getenv("GNUPGHOME") | ||
if gpgHome == "" { | ||
gpgHome = path.Join(homedir.Get(), ".gnupg") | ||
} | ||
} | ||
|
||
pubring, err := ioutil.ReadFile(path.Join(gpgHome, "pubring.gpg")) | ||
if err != nil { | ||
if !os.IsNotExist(err) { | ||
return nil, err | ||
} | ||
} else { | ||
_, err := m.ImportKeysFromBytes(pubring) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return m, nil | ||
} | ||
|
||
func (m *openpgpSigningMechanism) ImportKeysFromBytes(blob []byte) ([]string, error) { | ||
keyring, err := openpgp.ReadKeyRing(bytes.NewReader(blob)) | ||
if err != nil { | ||
k, e2 := openpgp.ReadArmoredKeyRing(bytes.NewReader(blob)) | ||
if e2 != nil { | ||
return nil, err // The original error -- FIXME: is this better? | ||
} | ||
keyring = k | ||
} | ||
|
||
keyIdentities := []string{} | ||
for _, entity := range keyring { | ||
if entity.PrimaryKey == nil { | ||
continue | ||
} | ||
// Uppercase the fingerprint to be compatible with gpgme | ||
keyIdentities = append(keyIdentities, strings.ToUpper(fmt.Sprintf("%x", entity.PrimaryKey.Fingerprint))) | ||
m.keyring = append(m.keyring, entity) | ||
} | ||
return keyIdentities, nil | ||
} | ||
|
||
// SupportsSigning returns nil if the mechanism supports signing, or an error message. | ||
func (m *openpgpSigningMechanism) SupportsSigning() error { | ||
return errors.New("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag") | ||
} | ||
|
||
func (m *openpgpSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) { | ||
return nil, errors.New("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag") | ||
} | ||
|
||
func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) { | ||
if len(m.keyring) == 0 { | ||
return nil, "", errors.New("no public keys imported") | ||
} | ||
md, err := openpgp.ReadMessage(bytes.NewReader(unverifiedSignature), m.keyring, nil, nil) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
if !md.IsSigned { | ||
return nil, "", errors.New("not signed") | ||
} | ||
content, err := ioutil.ReadAll(md.UnverifiedBody) | ||
if err != nil { | ||
return nil, "", err | ||
} | ||
if md.SignatureError != nil { | ||
return nil, "", fmt.Errorf("signature error: %v", md.SignatureError) | ||
} | ||
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)} | ||
} | ||
} | ||
|
||
// Uppercase the fingerprint to be compatible with gpgme | ||
return content, strings.ToUpper(fmt.Sprintf("%x", md.SignedBy.PublicKey.Fingerprint)), nil | ||
} |
Oops, something went wrong.