read image config from docker v2s1 manifests
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
bbe2dea0a6
commit
a0071de607
39 changed files with 1272 additions and 506 deletions
7
vendor/github.com/containers/image/signature/docker_test.go
generated
vendored
7
vendor/github.com/containers/image/signature/docker_test.go
generated
vendored
|
@ -11,6 +11,12 @@ import (
|
|||
func TestSignDockerManifest(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
|
||||
if err := mech.SupportsSigning(); err != nil {
|
||||
t.Skipf("Signing not supported: %v", err)
|
||||
}
|
||||
|
||||
manifest, err := ioutil.ReadFile("fixtures/image.manifest.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -41,6 +47,7 @@ func TestSignDockerManifest(t *testing.T) {
|
|||
func TestVerifyDockerManifestSignature(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
manifest, err := ioutil.ReadFile("fixtures/image.manifest.json")
|
||||
require.NoError(t, err)
|
||||
signature, err := ioutil.ReadFile("fixtures/image.signature")
|
||||
|
|
80
vendor/github.com/containers/image/signature/json.go
generated
vendored
80
vendor/github.com/containers/image/signature/json.go
generated
vendored
|
@ -14,64 +14,6 @@ func (err jsonFormatError) Error() string {
|
|||
return string(err)
|
||||
}
|
||||
|
||||
// validateExactMapKeys returns an error if the keys of m are not exactly expectedKeys, which must be pairwise distinct
|
||||
func validateExactMapKeys(m map[string]interface{}, expectedKeys ...string) error {
|
||||
if len(m) != len(expectedKeys) {
|
||||
return jsonFormatError("Unexpected keys in a JSON object")
|
||||
}
|
||||
|
||||
for _, k := range expectedKeys {
|
||||
if _, ok := m[k]; !ok {
|
||||
return jsonFormatError(fmt.Sprintf("Key %s missing in a JSON object", k))
|
||||
}
|
||||
}
|
||||
// Assuming expectedKeys are pairwise distinct, we know m contains len(expectedKeys) different values in expectedKeys.
|
||||
return nil
|
||||
}
|
||||
|
||||
// int64Field returns a member fieldName of m, if it is an int64, or an error.
|
||||
func int64Field(m map[string]interface{}, fieldName string) (int64, error) {
|
||||
untyped, ok := m[fieldName]
|
||||
if !ok {
|
||||
return -1, jsonFormatError(fmt.Sprintf("Field %s missing", fieldName))
|
||||
}
|
||||
f, ok := untyped.(float64)
|
||||
if !ok {
|
||||
return -1, jsonFormatError(fmt.Sprintf("Field %s is not a number", fieldName))
|
||||
}
|
||||
v := int64(f)
|
||||
if float64(v) != f {
|
||||
return -1, jsonFormatError(fmt.Sprintf("Field %s is not an integer", fieldName))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// mapField returns a member fieldName of m, if it is a JSON map, or an error.
|
||||
func mapField(m map[string]interface{}, fieldName string) (map[string]interface{}, error) {
|
||||
untyped, ok := m[fieldName]
|
||||
if !ok {
|
||||
return nil, jsonFormatError(fmt.Sprintf("Field %s missing", fieldName))
|
||||
}
|
||||
v, ok := untyped.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, jsonFormatError(fmt.Sprintf("Field %s is not a JSON object", fieldName))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// stringField returns a member fieldName of m, if it is a string, or an error.
|
||||
func stringField(m map[string]interface{}, fieldName string) (string, error) {
|
||||
untyped, ok := m[fieldName]
|
||||
if !ok {
|
||||
return "", jsonFormatError(fmt.Sprintf("Field %s missing", fieldName))
|
||||
}
|
||||
v, ok := untyped.(string)
|
||||
if !ok {
|
||||
return "", jsonFormatError(fmt.Sprintf("Field %s is not a string", fieldName))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// paranoidUnmarshalJSONObject unmarshals data as a JSON object, but failing on the slightest unexpected aspect
|
||||
// (including duplicated keys, unrecognized keys, and non-matching types). Uses fieldResolver to
|
||||
// determine the destination for a field value, which should return a pointer to the destination if valid, or nil if the key is rejected.
|
||||
|
@ -122,3 +64,25 @@ func paranoidUnmarshalJSONObject(data []byte, fieldResolver func(string) interfa
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// paranoidUnmarshalJSONObject unmarshals data as a JSON object, but failing on the slightest unexpected aspect
|
||||
// (including duplicated keys, unrecognized keys, and non-matching types). Each of the fields in exactFields
|
||||
// must be present exactly once, and none other fields are accepted.
|
||||
func paranoidUnmarshalJSONObjectExactFields(data []byte, exactFields map[string]interface{}) error {
|
||||
seenKeys := map[string]struct{}{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
if valuePtr, ok := exactFields[key]; ok {
|
||||
seenKeys[key] = struct{}{}
|
||||
return valuePtr
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for key := range exactFields {
|
||||
if _, ok := seenKeys[key]; !ok {
|
||||
return jsonFormatError(fmt.Sprintf(`Key "%s" missing in a JSON object`, key))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
129
vendor/github.com/containers/image/signature/json_test.go
generated
vendored
129
vendor/github.com/containers/image/signature/json_test.go
generated
vendored
|
@ -2,8 +2,6 @@ package signature
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -24,91 +22,6 @@ func x(m mSI, fields ...string) mSI {
|
|||
return m
|
||||
}
|
||||
|
||||
func TestValidateExactMapKeys(t *testing.T) {
|
||||
// Empty map and keys
|
||||
err := validateExactMapKeys(mSI{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Success
|
||||
err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "b", "a")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Extra map keys
|
||||
err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "a")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Extra expected keys
|
||||
err = validateExactMapKeys(mSI{"a": 1}, "b", "a")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Unexpected key values
|
||||
err = validateExactMapKeys(mSI{"a": 1}, "b")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestInt64Field(t *testing.T) {
|
||||
// Field not found
|
||||
_, err := int64Field(mSI{"a": "x"}, "b")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Field has a wrong type
|
||||
_, err = int64Field(mSI{"a": "string"}, "a")
|
||||
assert.Error(t, err)
|
||||
|
||||
for _, value := range []float64{
|
||||
0.5, // Fractional input
|
||||
math.Inf(1), // Infinity
|
||||
math.NaN(), // NaN
|
||||
} {
|
||||
_, err = int64Field(mSI{"a": value}, "a")
|
||||
assert.Error(t, err, fmt.Sprintf("%f", value))
|
||||
}
|
||||
|
||||
// Success
|
||||
// The float64 type has 53 bits of effective precision, so ±1FFFFFFFFFFFFF is the
|
||||
// range of integer values which can all be represented exactly (beyond that,
|
||||
// some are representable if they are divisible by a high enough power of 2,
|
||||
// but most are not).
|
||||
for _, value := range []int64{0, 1, -1, 0x1FFFFFFFFFFFFF, -0x1FFFFFFFFFFFFF} {
|
||||
testName := fmt.Sprintf("%d", value)
|
||||
v, err := int64Field(mSI{"a": float64(value), "b": nil}, "a")
|
||||
require.NoError(t, err, testName)
|
||||
assert.Equal(t, value, v, testName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapField(t *testing.T) {
|
||||
// Field not found
|
||||
_, err := mapField(mSI{"a": mSI{}}, "b")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Field has a wrong type
|
||||
_, err = mapField(mSI{"a": 1}, "a")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Success
|
||||
// FIXME? We can't use mSI as the type of child, that type apparently can't be converted to the raw map type.
|
||||
child := map[string]interface{}{"b": mSI{}}
|
||||
m, err := mapField(mSI{"a": child, "b": nil}, "a")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, child, m)
|
||||
}
|
||||
|
||||
func TestStringField(t *testing.T) {
|
||||
// Field not found
|
||||
_, err := stringField(mSI{"a": "x"}, "b")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Field has a wrong type
|
||||
_, err = stringField(mSI{"a": 1}, "a")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Success
|
||||
s, err := stringField(mSI{"a": "x", "b": nil}, "a")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "x", s)
|
||||
}
|
||||
|
||||
// implementsUnmarshalJSON is a minimalistic type used to detect that
|
||||
// paranoidUnmarshalJSONObject uses the json.Unmarshaler interface of resolved
|
||||
// pointers.
|
||||
|
@ -180,3 +93,45 @@ func TestParanoidUnmarshalJSONObject(t *testing.T) {
|
|||
assert.Error(t, err, input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParanoidUnmarshalJSONObjectExactFields(t *testing.T) {
|
||||
var stringValue string
|
||||
var float64Value float64
|
||||
var rawValue json.RawMessage
|
||||
var unmarshallCalled implementsUnmarshalJSON
|
||||
exactFields := map[string]interface{}{
|
||||
"string": &stringValue,
|
||||
"float64": &float64Value,
|
||||
"raw": &rawValue,
|
||||
"unmarshaller": &unmarshallCalled,
|
||||
}
|
||||
|
||||
// Empty object
|
||||
err := paranoidUnmarshalJSONObjectExactFields([]byte(`{}`), map[string]interface{}{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Success
|
||||
err = paranoidUnmarshalJSONObjectExactFields([]byte(`{"string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`), exactFields)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "a", stringValue)
|
||||
assert.Equal(t, 3.5, float64Value)
|
||||
assert.Equal(t, json.RawMessage(`{"a":"b"}`), rawValue)
|
||||
assert.Equal(t, implementsUnmarshalJSON(true), unmarshallCalled)
|
||||
|
||||
// Various kinds of invalid input
|
||||
for _, input := range []string{
|
||||
``, // Empty input
|
||||
`&`, // Entirely invalid JSON
|
||||
`1`, // Not an object
|
||||
`{&}`, // Invalid key JSON
|
||||
`{1:1}`, // Key not a string
|
||||
`{"string": "a", "string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`, // Duplicate key
|
||||
`{"string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true, "thisisunknown", 1}`, // Unknown key
|
||||
`{"string": &, "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`, // Invalid value JSON
|
||||
`{"string": 1, "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}`, // Type mismatch
|
||||
`{"string": "a", "float64": 3.5, "raw": {"a":"b"}, "unmarshaller": true}{}`, // Extra data after object
|
||||
} {
|
||||
err := paranoidUnmarshalJSONObjectExactFields([]byte(input), exactFields)
|
||||
assert.Error(t, err, input)
|
||||
}
|
||||
}
|
||||
|
|
120
vendor/github.com/containers/image/signature/mechanism.go
generated
vendored
120
vendor/github.com/containers/image/signature/mechanism.go
generated
vendored
|
@ -9,19 +9,20 @@ import (
|
|||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/mtrmac/gpgme"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
// SigningMechanism abstracts a way to sign binary blobs and verify their signatures.
|
||||
// Each mechanism should eventually be closed by calling Close().
|
||||
// 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
|
||||
// Close removes resources associated with the mechanism, if any.
|
||||
Close() error
|
||||
// SupportsSigning returns nil if the mechanism supports signing, or a SigningNotSupportedError.
|
||||
SupportsSigning() error
|
||||
// Sign creates a (non-detached) signature of input using keyIdentity.
|
||||
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
|
||||
Sign(input []byte, keyIdentity string) ([]byte, error)
|
||||
// Verify parses unverifiedSignature and returns the content and the signer's identity
|
||||
Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error)
|
||||
|
@ -33,109 +34,34 @@ type SigningMechanism interface {
|
|||
UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error)
|
||||
}
|
||||
|
||||
// A GPG/OpenPGP signing mechanism.
|
||||
type gpgSigningMechanism struct {
|
||||
ctx *gpgme.Context
|
||||
// SigningNotSupportedError is returned when trying to sign using a mechanism which does not support that.
|
||||
type SigningNotSupportedError string
|
||||
|
||||
func (err SigningNotSupportedError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
// NewGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism.
|
||||
// NewGPGSigningMechanism returns a new GPG/OpenPGP signing mechanism for the user’s default
|
||||
// GPG configuration ($GNUPGHOME / ~/.gnupg)
|
||||
// The caller must call .Close() on the returned SigningMechanism.
|
||||
func NewGPGSigningMechanism() (SigningMechanism, error) {
|
||||
return newGPGSigningMechanismInDirectory("")
|
||||
}
|
||||
|
||||
// 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 gpgSigningMechanism{ctx: ctx}, 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) {
|
||||
return newEphemeralGPGSigningMechanism(blob)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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 gpgSigningMechanism) 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,
|
||||
// gpgUntrustedSignatureContents 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 gpgSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
|
||||
func gpgUntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
|
||||
// This uses the Golang-native OpenPGP implementation instead of gpgme because we are not doing any cryptography.
|
||||
md, err := openpgp.ReadMessage(bytes.NewReader(untrustedSignature), openpgp.EntityList{}, nil, nil)
|
||||
if err != nil {
|
||||
|
|
175
vendor/github.com/containers/image/signature/mechanism_gpgme.go
generated
vendored
Normal file
175
vendor/github.com/containers/image/signature/mechanism_gpgme.go
generated
vendored
Normal file
|
@ -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()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
37
vendor/github.com/containers/image/signature/mechanism_gpgme_test.go
generated
vendored
Normal file
37
vendor/github.com/containers/image/signature/mechanism_gpgme_test.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
// +build !containers_image_openpgp
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGPGMESigningMechanismClose(t *testing.T) {
|
||||
// Closing an ephemeral mechanism removes the directory.
|
||||
// (The non-ephemeral case is tested in the common TestGPGSigningMechanismClose)
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
require.NoError(t, err)
|
||||
gpgMech, ok := mech.(*gpgmeSigningMechanism)
|
||||
require.True(t, ok)
|
||||
dir := gpgMech.ephemeralDir
|
||||
assert.NotEmpty(t, dir)
|
||||
_, err = os.Lstat(dir)
|
||||
require.NoError(t, err)
|
||||
err = mech.Close()
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Lstat(dir)
|
||||
require.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestGPGMESigningMechanismSupportsSigning(t *testing.T) {
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
err = mech.SupportsSigning()
|
||||
assert.NoError(t, err)
|
||||
}
|
153
vendor/github.com/containers/image/signature/mechanism_openpgp.go
generated
vendored
Normal file
153
vendor/github.com/containers/image/signature/mechanism_openpgp.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
|||
// +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.
|
||||
// The caller must call .Close() on the returned SigningMechanism.
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
m := &openpgpSigningMechanism{
|
||||
keyring: openpgp.EntityList{},
|
||||
}
|
||||
keyIdentities, err := m.importKeysFromBytes(blob)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return m, keyIdentities, nil
|
||||
}
|
||||
|
||||
func (m *openpgpSigningMechanism) Close() error {
|
||||
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).
|
||||
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 {
|
||||
// Coverage: This should never happen, openpgp.ReadEntity fails with a
|
||||
// openpgp.errors.StructuralError instead of returning an entity with this
|
||||
// field set to 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 a SigningNotSupportedError.
|
||||
func (m *openpgpSigningMechanism) SupportsSigning() error {
|
||||
return SigningNotSupportedError("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag")
|
||||
}
|
||||
|
||||
// Sign creates a (non-detached) signature of input using keyIdentity.
|
||||
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
|
||||
func (m *openpgpSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte, error) {
|
||||
return nil, SigningNotSupportedError("signing is not supported in github.com/containers/image built with the containers_image_openpgp build tag")
|
||||
}
|
||||
|
||||
// Verify parses unverifiedSignature and returns the content and the signer's identity
|
||||
func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
|
||||
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 {
|
||||
// Coverage: md.UnverifiedBody.Read only fails if the body is encrypted
|
||||
// (and possibly also signed, but it _must_ be encrypted) and the signing
|
||||
// “modification detection code” detects a mismatch. But in that case,
|
||||
// we would expect the signature verification to fail as well, and that is checked
|
||||
// first. Besides, we are not supplying any decryption keys, so we really
|
||||
// can never reach this “encrypted data MDC mismatch” path.
|
||||
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
|
||||
}
|
||||
|
||||
// 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 openpgpSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
|
||||
return gpgUntrustedSignatureContents(untrustedSignature)
|
||||
}
|
28
vendor/github.com/containers/image/signature/mechanism_openpgp_test.go
generated
vendored
Normal file
28
vendor/github.com/containers/image/signature/mechanism_openpgp_test.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// +build containers_image_openpgp
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOpenpgpSigningMechanismSupportsSigning(t *testing.T) {
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
err = mech.SupportsSigning()
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, SigningNotSupportedError(""), err)
|
||||
}
|
||||
|
||||
func TestOpenpgpSigningMechanismSign(t *testing.T) {
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
_, err = mech.Sign([]byte{}, TestKeyFingerprint)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, SigningNotSupportedError(""), err)
|
||||
}
|
128
vendor/github.com/containers/image/signature/mechanism_test.go
generated
vendored
128
vendor/github.com/containers/image/signature/mechanism_test.go
generated
vendored
|
@ -1,9 +1,12 @@
|
|||
package signature
|
||||
|
||||
// These tests are expected to pass unmodified for _both_ mechanism_gpgme.go and mechanism_openpgp.go.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -14,27 +17,88 @@ const (
|
|||
testGPGHomeDirectory = "./fixtures"
|
||||
)
|
||||
|
||||
func TestSigningNotSupportedError(t *testing.T) {
|
||||
// A stupid test just to keep code coverage
|
||||
s := "test"
|
||||
err := SigningNotSupportedError(s)
|
||||
assert.Equal(t, s, err.Error())
|
||||
}
|
||||
|
||||
func TestNewGPGSigningMechanism(t *testing.T) {
|
||||
// A dumb test just for code coverage. We test more with newGPGSigningMechanismInDirectory().
|
||||
_, err := NewGPGSigningMechanism()
|
||||
mech, err := NewGPGSigningMechanism()
|
||||
assert.NoError(t, err)
|
||||
mech.Close()
|
||||
}
|
||||
|
||||
func TestNewGPGSigningMechanismInDirectory(t *testing.T) {
|
||||
// A dumb test just for code coverage.
|
||||
_, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
assert.NoError(t, err)
|
||||
mech.Close()
|
||||
// The various GPG failure cases are not obviously easy to reach.
|
||||
|
||||
// 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)
|
||||
mech, err = newGPGSigningMechanismInDirectory("")
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
_, _, err = mech.Verify(signature)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Similarly, using a newly created empty directory makes TestKeyFingerprint
|
||||
// unavailable
|
||||
emptyDir, err := ioutil.TempDir("", "signing-empty-directory")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(emptyDir)
|
||||
mech, err = newGPGSigningMechanismInDirectory(emptyDir)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
_, _, err = mech.Verify(signature)
|
||||
assert.Error(t, err)
|
||||
|
||||
// If pubring.gpg is unreadable in the directory, either initializing
|
||||
// the mechanism fails (with openpgp), or it succeeds (sadly, gpgme) and
|
||||
// later verification fails.
|
||||
unreadableDir, err := ioutil.TempDir("", "signing-unreadable-directory")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(unreadableDir)
|
||||
f, err := os.OpenFile(filepath.Join(unreadableDir, "pubring.gpg"), os.O_RDONLY|os.O_CREATE, 0000)
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
mech, err = newGPGSigningMechanismInDirectory(unreadableDir)
|
||||
if err == nil {
|
||||
defer mech.Close()
|
||||
_, _, err = mech.Verify(signature)
|
||||
}
|
||||
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)
|
||||
|
||||
// If we use the default directory mechanism, GNUPGHOME is respected.
|
||||
origGNUPGHOME := os.Getenv("GNUPGHOME")
|
||||
defer os.Setenv("GNUPGHOME", origGNUPGHOME)
|
||||
os.Setenv("GNUPGHOME", testGPGHomeDirectory)
|
||||
mech, err = newGPGSigningMechanismInDirectory("")
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
_, _, err = mech.Verify(signature)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGPGSigningMechanismImportKeysFromBytes(t *testing.T) {
|
||||
testDir, err := ioutil.TempDir("", "gpg-import-keys")
|
||||
func TestNewEphemeralGPGSigningMechanism(t *testing.T) {
|
||||
// Empty input: This is accepted anyway by GPG, just returns no keys.
|
||||
mech, keyIdentities, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
mech, err := newGPGSigningMechanismInDirectory(testDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
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)
|
||||
|
@ -44,31 +108,57 @@ func TestGPGSigningMechanismImportKeysFromBytes(t *testing.T) {
|
|||
// Successful import
|
||||
keyBlob, err := ioutil.ReadFile("./fixtures/public-key.gpg")
|
||||
require.NoError(t, err)
|
||||
keyIdentities, err := mech.ImportKeysFromBytes(keyBlob)
|
||||
mech, keyIdentities, err = NewEphemeralGPGSigningMechanism(keyBlob)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
|
||||
// Two keys: just concatenate the valid input twice.
|
||||
keyIdentities, err = mech.ImportKeysFromBytes(bytes.Join([][]byte{keyBlob, keyBlob}, nil))
|
||||
// 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
|
||||
// gpgmeSigningMechanism but not openpgpSigningMechanism.)
|
||||
keyBlob, err = ioutil.ReadFile("./fixtures/pubring.gpg")
|
||||
require.NoError(t, err)
|
||||
mech, keyIdentities, err = NewEphemeralGPGSigningMechanism(bytes.Join([][]byte{keyBlob, keyBlob}, nil))
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
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)
|
||||
// Invalid input: This is, sadly, accepted anyway by GPG, just returns no keys.
|
||||
// For openpgpSigningMechanism we can detect this and fail.
|
||||
mech, keyIdentities, err = NewEphemeralGPGSigningMechanism([]byte("This is invalid"))
|
||||
assert.True(t, err != nil || len(keyIdentities) == 0)
|
||||
if err == nil {
|
||||
mech.Close()
|
||||
}
|
||||
assert.Empty(t, keyIdentities)
|
||||
// The various GPG/GPGME failures cases are not obviously easy to reach.
|
||||
}
|
||||
|
||||
func TestGPGSigningMechanismClose(t *testing.T) {
|
||||
// Closing a non-ephemeral mechanism does not remove anything in the directory.
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
err = mech.Close()
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Lstat(testGPGHomeDirectory)
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Lstat(filepath.Join(testGPGHomeDirectory, "pubring.gpg"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGPGSigningMechanismSign(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
|
||||
if err := mech.SupportsSigning(); err != nil {
|
||||
t.Skipf("Signing not supported: %v", err)
|
||||
}
|
||||
|
||||
// Successful signing
|
||||
content := []byte("content")
|
||||
|
@ -95,6 +185,7 @@ func assertSigningError(t *testing.T, content []byte, fingerprint string, err er
|
|||
func TestGPGSigningMechanismVerify(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
|
||||
// Successful verification
|
||||
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
|
||||
|
@ -149,8 +240,9 @@ func TestGPGSigningMechanismVerify(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGPGSigningMechanismUntrustedSignatureContents(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
|
||||
// A valid signature
|
||||
signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
|
||||
|
|
84
vendor/github.com/containers/image/signature/policy_config.go
generated
vendored
84
vendor/github.com/containers/image/signature/policy_config.go
generated
vendored
|
@ -255,13 +255,8 @@ var _ json.Unmarshaler = (*prInsecureAcceptAnything)(nil)
|
|||
func (pr *prInsecureAcceptAnything) UnmarshalJSON(data []byte) error {
|
||||
*pr = prInsecureAcceptAnything{}
|
||||
var tmp prInsecureAcceptAnything
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -290,13 +285,8 @@ var _ json.Unmarshaler = (*prReject)(nil)
|
|||
func (pr *prReject) UnmarshalJSON(data []byte) error {
|
||||
*pr = prReject{}
|
||||
var tmp prReject
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -465,15 +455,9 @@ func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error {
|
|||
*pr = prSignedBaseLayer{}
|
||||
var tmp prSignedBaseLayer
|
||||
var baseLayerIdentity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
case "baseLayerIdentity":
|
||||
return &baseLayerIdentity
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"baseLayerIdentity": &baseLayerIdentity,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -481,9 +465,6 @@ func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error {
|
|||
if tmp.Type != prTypeSignedBaseLayer {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
if baseLayerIdentity == nil {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("baseLayerIdentity not specified"))
|
||||
}
|
||||
bli, err := newPolicyReferenceMatchFromJSON(baseLayerIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -541,13 +522,8 @@ var _ json.Unmarshaler = (*prmMatchExact)(nil)
|
|||
func (prm *prmMatchExact) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchExact{}
|
||||
var tmp prmMatchExact
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -576,13 +552,8 @@ var _ json.Unmarshaler = (*prmMatchRepoDigestOrExact)(nil)
|
|||
func (prm *prmMatchRepoDigestOrExact) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchRepoDigestOrExact{}
|
||||
var tmp prmMatchRepoDigestOrExact
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -611,13 +582,8 @@ var _ json.Unmarshaler = (*prmMatchRepository)(nil)
|
|||
func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchRepository{}
|
||||
var tmp prmMatchRepository
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -656,15 +622,9 @@ var _ json.Unmarshaler = (*prmExactReference)(nil)
|
|||
func (prm *prmExactReference) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmExactReference{}
|
||||
var tmp prmExactReference
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
case "dockerReference":
|
||||
return &tmp.DockerReference
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"dockerReference": &tmp.DockerReference,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -704,15 +664,9 @@ var _ json.Unmarshaler = (*prmExactRepository)(nil)
|
|||
func (prm *prmExactRepository) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmExactRepository{}
|
||||
var tmp prmExactRepository
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
case "dockerRepository":
|
||||
return &tmp.DockerRepository
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"dockerRepository": &tmp.DockerRepository,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
4
vendor/github.com/containers/image/signature/policy_config_test.go
generated
vendored
4
vendor/github.com/containers/image/signature/policy_config_test.go
generated
vendored
|
@ -297,8 +297,8 @@ func TestPolicyUnmarshalJSON(t *testing.T) {
|
|||
|
||||
// Various allowed modifications to the policy
|
||||
allowedModificationFns := []func(mSI){
|
||||
// Delete the map of specific policies
|
||||
func(v mSI) { delete(v, "specific") },
|
||||
// Delete the map of transport-specific scopes
|
||||
func(v mSI) { delete(v, "transports") },
|
||||
// Use an empty map of transport-specific scopes
|
||||
func(v mSI) { v["transports"] = map[string]PolicyTransportScopes{} },
|
||||
}
|
||||
|
|
14
vendor/github.com/containers/image/signature/policy_eval_signedby.go
generated
vendored
14
vendor/github.com/containers/image/signature/policy_eval_signedby.go
generated
vendored
|
@ -5,7 +5,6 @@ package signature
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -42,20 +41,11 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(image types.UnparsedImage, sig [
|
|||
}
|
||||
|
||||
// FIXME: move this to per-context initialization
|
||||
dir, err := ioutil.TempDir("", "skopeo-signedBy-")
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
mech, err := newGPGSigningMechanismInDirectory(dir)
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
|
||||
trustedIdentities, err := mech.ImportKeysFromBytes(data)
|
||||
mech, trustedIdentities, err := NewEphemeralGPGSigningMechanism(data)
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
defer mech.Close()
|
||||
if len(trustedIdentities) == 0 {
|
||||
return sarRejected, nil, PolicyRequirementError("No public keys imported")
|
||||
}
|
||||
|
|
96
vendor/github.com/containers/image/signature/signature.go
generated
vendored
96
vendor/github.com/containers/image/signature/signature.go
generated
vendored
|
@ -120,78 +120,69 @@ func (s *untrustedSignature) UnmarshalJSON(data []byte) error {
|
|||
// strictUnmarshalJSON is UnmarshalJSON, except that it may return the internal jsonFormatError error type.
|
||||
// Splitting it into a separate function allows us to do the jsonFormatError → InvalidSignatureError in a single place, the caller.
|
||||
func (s *untrustedSignature) strictUnmarshalJSON(data []byte) error {
|
||||
var untyped interface{}
|
||||
if err := json.Unmarshal(data, &untyped); err != nil {
|
||||
return err
|
||||
}
|
||||
o, ok := untyped.(map[string]interface{})
|
||||
if !ok {
|
||||
return InvalidSignatureError{msg: "Invalid signature format"}
|
||||
}
|
||||
if err := validateExactMapKeys(o, "critical", "optional"); err != nil {
|
||||
var critical, optional json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"critical": &critical,
|
||||
"optional": &optional,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := mapField(o, "critical")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExactMapKeys(c, "type", "image", "identity"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
optional, err := mapField(o, "optional")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := optional["creator"]; ok {
|
||||
creatorID, err := stringField(optional, "creator")
|
||||
if err != nil {
|
||||
return err
|
||||
var creatorID string
|
||||
var timestamp float64
|
||||
var gotCreatorID, gotTimestamp = false, false
|
||||
if err := paranoidUnmarshalJSONObject(optional, func(key string) interface{} {
|
||||
switch key {
|
||||
case "creator":
|
||||
gotCreatorID = true
|
||||
return &creatorID
|
||||
case "timestamp":
|
||||
gotTimestamp = true
|
||||
return ×tamp
|
||||
default:
|
||||
var ignore interface{}
|
||||
return &ignore
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if gotCreatorID {
|
||||
s.UntrustedCreatorID = &creatorID
|
||||
}
|
||||
if _, ok := optional["timestamp"]; ok {
|
||||
timestamp, err := int64Field(optional, "timestamp")
|
||||
if err != nil {
|
||||
return err
|
||||
if gotTimestamp {
|
||||
intTimestamp := int64(timestamp)
|
||||
if float64(intTimestamp) != timestamp {
|
||||
return InvalidSignatureError{msg: "Field optional.timestamp is not is not an integer"}
|
||||
}
|
||||
s.UntrustedTimestamp = ×tamp
|
||||
s.UntrustedTimestamp = &intTimestamp
|
||||
}
|
||||
|
||||
t, err := stringField(c, "type")
|
||||
if err != nil {
|
||||
var t string
|
||||
var image, identity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(critical, map[string]interface{}{
|
||||
"type": &t,
|
||||
"image": &image,
|
||||
"identity": &identity,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if t != signatureType {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Unrecognized signature type %s", t)}
|
||||
}
|
||||
|
||||
image, err := mapField(c, "image")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExactMapKeys(image, "docker-manifest-digest"); err != nil {
|
||||
return err
|
||||
}
|
||||
digestString, err := stringField(image, "docker-manifest-digest")
|
||||
if err != nil {
|
||||
var digestString string
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(image, map[string]interface{}{
|
||||
"docker-manifest-digest": &digestString,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
||||
|
||||
identity, err := mapField(c, "identity")
|
||||
if err != nil {
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
||||
"docker-reference": &s.UntrustedDockerReference,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExactMapKeys(identity, "docker-reference"); err != nil {
|
||||
return err
|
||||
}
|
||||
reference, err := stringField(identity, "docker-reference")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedDockerReference = reference
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -261,10 +252,11 @@ func verifyAndExtractSignature(mech SigningMechanism, unverifiedSignature []byte
|
|||
// (including things like “✅ Verified by $authority”)
|
||||
func GetUntrustedSignatureInformationWithoutVerifying(untrustedSignatureBytes []byte) (*UntrustedSignatureInformation, error) {
|
||||
// NOTE: This should eventualy do format autodetection.
|
||||
mech, err := NewGPGSigningMechanism()
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mech.Close()
|
||||
|
||||
untrustedContents, shortKeyIdentifier, err := mech.UntrustedSignatureContents(untrustedSignatureBytes)
|
||||
if err != nil {
|
||||
|
|
7
vendor/github.com/containers/image/signature/signature_test.go
generated
vendored
7
vendor/github.com/containers/image/signature/signature_test.go
generated
vendored
|
@ -153,6 +153,7 @@ func TestUnmarshalJSON(t *testing.T) {
|
|||
func(v mSI) { x(v, "optional")["creator"] = 1 },
|
||||
// Invalid "timestamp"
|
||||
func(v mSI) { x(v, "optional")["timestamp"] = "unexpected" },
|
||||
func(v mSI) { x(v, "optional")["timestamp"] = 0.5 }, // Fractional input
|
||||
}
|
||||
for _, fn := range breakFns {
|
||||
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
|
||||
|
@ -188,6 +189,11 @@ func TestUnmarshalJSON(t *testing.T) {
|
|||
func TestSign(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
|
||||
if err := mech.SupportsSigning(); err != nil {
|
||||
t.Skipf("Signing not supported: %v", err)
|
||||
}
|
||||
|
||||
sig := newUntrustedSignature("digest!@#", "reference#@!")
|
||||
|
||||
|
@ -232,6 +238,7 @@ func TestSign(t *testing.T) {
|
|||
func TestVerifyAndExtractSignature(t *testing.T) {
|
||||
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
|
||||
type triple struct {
|
||||
keyIdentity string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue