-
Notifications
You must be signed in to change notification settings - Fork 369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow choosing between a gpgme and openpgp signature backend using a build tag #207
Changes from all commits
d19336d
f5768e7
8c17b7b
1a37631
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// +build !containers_image_openpgp | ||
|
||
package signature | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
|
||
"github.com/mtrmac/gpgme" | ||
) | ||
|
||
// A GPG/OpenPGP signing mechanism, implemented using gpgme. | ||
type gpgmeSigningMechanism struct { | ||
ctx *gpgme.Context | ||
ephemeralDir string // If not "", a directory to be removed on Close() | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. var _ SigningMechanism = gpgmeSigningMechanism{} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? |
||
// newGPGSigningMechanismInDirectory returns a new GPG/OpenPGP signing mechanism, using optionalDir if not empty. | ||
// The caller must call .Close() on the returned SigningMechanism. | ||
func newGPGSigningMechanismInDirectory(optionalDir string) (SigningMechanism, error) { | ||
ctx, err := newGPGMEContext(optionalDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &gpgmeSigningMechanism{ | ||
ctx: ctx, | ||
ephemeralDir: "", | ||
}, nil | ||
} | ||
|
||
// newEphemeralGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism which | ||
// recognizes _only_ public keys from the supplied blob, and returns the identities | ||
// of these keys. | ||
// The caller must call .Close() on the returned SigningMechanism. | ||
func newEphemeralGPGSigningMechanism(blob []byte) (SigningMechanism, []string, error) { | ||
dir, err := ioutil.TempDir("", "containers-ephemeral-gpg-") | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
removeDir := true | ||
defer func() { | ||
if removeDir { | ||
os.RemoveAll(dir) | ||
} | ||
}() | ||
ctx, err := newGPGMEContext(dir) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
mech := &gpgmeSigningMechanism{ | ||
ctx: ctx, | ||
ephemeralDir: dir, | ||
} | ||
keyIdentities, err := mech.importKeysFromBytes(blob) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
removeDir = false | ||
return mech, keyIdentities, nil | ||
} | ||
|
||
// newGPGMEContext returns a new *gpgme.Context, using optionalDir if not empty. | ||
func newGPGMEContext(optionalDir string) (*gpgme.Context, 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 ctx, nil | ||
} | ||
|
||
func (m *gpgmeSigningMechanism) Close() error { | ||
if m.ephemeralDir != "" { | ||
os.RemoveAll(m.ephemeralDir) // Ignore an error, if any | ||
} | ||
return nil | ||
} | ||
|
||
// 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); | ||
// but we do not make this public, it can only be used through newEphemeralGPGSigningMechanism. | ||
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 a SigningNotSupportedError. | ||
func (m *gpgmeSigningMechanism) SupportsSigning() error { | ||
return nil | ||
} | ||
|
||
// Sign creates a (non-detached) signature of input using keyIdentity. | ||
// Fails with a SigningNotSupportedError if the mechanism does not support signing. | ||
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 parses unverifiedSignature and returns the content and the signer's identity | ||
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 | ||
} | ||
|
||
// UntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION, | ||
// along with a short identifier of the key used for signing. | ||
// WARNING: The short key identifier (which correponds to "Key ID" for OpenPGP keys) | ||
// is NOT the same as a "key identity" used in other calls ot this interface, and | ||
// the values may have no recognizable relationship if the public key is not available. | ||
func (m gpgmeSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) { | ||
return gpgUntrustedSignatureContents(untrustedSignature) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry for nit-picking, this should be called
ErrSigningNotSupported
according to golang's standardsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pointer to the standards?
Both https://blog.golang.org/error-handling-and-go and https://golang.org/doc/effective_go.html#errors and e.g. types in https://golang.org/pkg/os/ use the
...Error
form.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forget this, https://github.com/golang/go/wiki/Errors#naming (I swapped types and variables)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum, interesting. (And confusing that both types and values are recommended.)