container: add support for uploading to registries
Add a new generic container registry client via a new `container` package. Use this to create a command line utility as well as a new upload target for container registries. The code uses the github.com/containers/* project and packages to interact with container registires that is also used by skopeo, podman et al. One if the dependencies is `proglottis/gpgme` that is using cgo to bind libgpgme, so we have to add the corresponding devel package to the BuildRequires as well as installing it on CI. Checks will follow later via an integration test.
This commit is contained in:
parent
d136a075bc
commit
986f076276
955 changed files with 164203 additions and 2549 deletions
89
vendor/github.com/containers/image/v5/signature/docker.go
generated
vendored
Normal file
89
vendor/github.com/containers/image/v5/signature/docker.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// SignOptions includes optional parameters for signing container images.
|
||||
type SignOptions struct {
|
||||
// Passphare to use when signing with the key identity.
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
|
||||
// using mech and keyIdentity, and the specified options.
|
||||
func SignDockerManifestWithOptions(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string, options *SignOptions) ([]byte, error) {
|
||||
manifestDigest, err := manifest.Digest(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig := newUntrustedSignature(manifestDigest, dockerReference)
|
||||
|
||||
var passphrase string
|
||||
if options != nil {
|
||||
passphrase = options.Passphrase
|
||||
// The gpgme implementation can’t use passphrase with \n; reject it here for consistent behavior.
|
||||
if strings.Contains(passphrase, "\n") {
|
||||
return nil, errors.New("invalid passphrase: must not contain a line break")
|
||||
}
|
||||
}
|
||||
|
||||
return sig.sign(mech, keyIdentity, passphrase)
|
||||
}
|
||||
|
||||
// SignDockerManifest returns a signature for manifest as the specified dockerReference,
|
||||
// using mech and keyIdentity.
|
||||
func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, keyIdentity string) ([]byte, error) {
|
||||
return SignDockerManifestWithOptions(m, dockerReference, mech, keyIdentity, nil)
|
||||
}
|
||||
|
||||
// VerifyDockerManifestSignature checks that unverifiedSignature uses expectedKeyIdentity to sign unverifiedManifest as expectedDockerReference,
|
||||
// using mech.
|
||||
func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte,
|
||||
expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) {
|
||||
expectedRef, err := reference.ParseNormalizedNamed(expectedDockerReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := verifyAndExtractSignature(mech, unverifiedSignature, signatureAcceptanceRules{
|
||||
validateKeyIdentity: func(keyIdentity string) error {
|
||||
if keyIdentity != expectedKeyIdentity {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity)}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerReference: func(signedDockerReference string) error {
|
||||
signedRef, err := reference.ParseNormalizedNamed(signedDockerReference)
|
||||
if err != nil {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)}
|
||||
}
|
||||
if signedRef.String() != expectedRef.String() {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
|
||||
signedDockerReference, expectedDockerReference)}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error {
|
||||
matches, err := manifest.MatchesDigest(unverifiedManifest, signedDockerManifestDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matches {
|
||||
return InvalidSignatureError{msg: fmt.Sprintf("Signature for docker digest %q does not match", signedDockerManifestDigest)}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
88
vendor/github.com/containers/image/v5/signature/json.go
generated
vendored
Normal file
88
vendor/github.com/containers/image/v5/signature/json.go
generated
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// jsonFormatError is returned when JSON does not match expected format.
|
||||
type jsonFormatError string
|
||||
|
||||
func (err jsonFormatError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// The fieldResolver approach is useful for decoding the Policy.Transports map; using it for structs is a bit lazy,
|
||||
// we could use reflection to automate this. Later?
|
||||
func paranoidUnmarshalJSONObject(data []byte, fieldResolver func(string) interface{}) error {
|
||||
seenKeys := map[string]struct{}{}
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
return jsonFormatError(err.Error())
|
||||
}
|
||||
if t != json.Delim('{') {
|
||||
return jsonFormatError(fmt.Sprintf("JSON object expected, got \"%s\"", t))
|
||||
}
|
||||
for {
|
||||
t, err := dec.Token()
|
||||
if err != nil {
|
||||
return jsonFormatError(err.Error())
|
||||
}
|
||||
if t == json.Delim('}') {
|
||||
break
|
||||
}
|
||||
|
||||
key, ok := t.(string)
|
||||
if !ok {
|
||||
// Coverage: This should never happen, dec.Token() rejects non-string-literals in this state.
|
||||
return jsonFormatError(fmt.Sprintf("Key string literal expected, got \"%s\"", t))
|
||||
}
|
||||
if _, ok := seenKeys[key]; ok {
|
||||
return jsonFormatError(fmt.Sprintf("Duplicate key \"%s\"", key))
|
||||
}
|
||||
seenKeys[key] = struct{}{}
|
||||
|
||||
valuePtr := fieldResolver(key)
|
||||
if valuePtr == nil {
|
||||
return jsonFormatError(fmt.Sprintf("Unknown key \"%s\"", key))
|
||||
}
|
||||
// This works like json.Unmarshal, in particular it allows us to implement UnmarshalJSON to implement strict parsing of the field value.
|
||||
if err := dec.Decode(valuePtr); err != nil {
|
||||
return jsonFormatError(err.Error())
|
||||
}
|
||||
}
|
||||
if _, err := dec.Token(); err != io.EOF {
|
||||
return jsonFormatError("Unexpected data after JSON object")
|
||||
}
|
||||
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
|
||||
}
|
||||
97
vendor/github.com/containers/image/v5/signature/mechanism.go
generated
vendored
Normal file
97
vendor/github.com/containers/image/v5/signature/mechanism.go
generated
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
// This code is used only to parse the data in an explicitly-untrusted
|
||||
// code path, where cryptography is not relevant. For now, continue to
|
||||
// use this frozen deprecated implementation. When mechanism_openpgp.go
|
||||
// migrates to another implementation, this should migrate as well.
|
||||
//lint:ignore SA1019 See above
|
||||
"golang.org/x/crypto/openpgp" //nolint:staticcheck
|
||||
)
|
||||
|
||||
// SigningMechanism abstracts a way to sign binary blobs and verify their signatures.
|
||||
// Each mechanism should eventually be closed by calling Close().
|
||||
type SigningMechanism interface {
|
||||
// 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)
|
||||
// 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 corresponds to "Key ID" for OpenPGP keys)
|
||||
// is NOT the same as a "key identity" used in other calls to this interface, and
|
||||
// the values may have no recognizable relationship if the public key is not available.
|
||||
UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error)
|
||||
}
|
||||
|
||||
// signingMechanismWithPassphrase is an internal extension of SigningMechanism.
|
||||
type signingMechanismWithPassphrase interface {
|
||||
SigningMechanism
|
||||
|
||||
// Sign creates a (non-detached) signature of input using keyIdentity and passphrase.
|
||||
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
|
||||
SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error)
|
||||
}
|
||||
|
||||
// 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 for the user’s default
|
||||
// GPG configuration ($GNUPGHOME / ~/.gnupg)
|
||||
// The caller must call .Close() on the returned SigningMechanism.
|
||||
func NewGPGSigningMechanism() (SigningMechanism, error) {
|
||||
return newGPGSigningMechanismInDirectory("")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 corresponds to "Key ID" for OpenPGP keys)
|
||||
// is NOT the same as a "key identity" used in other calls to this interface, and
|
||||
// the values may have no recognizable relationship if the public key is not available.
|
||||
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 {
|
||||
return nil, "", err
|
||||
}
|
||||
if !md.IsSigned {
|
||||
return nil, "", errors.New("The input is not a signature")
|
||||
}
|
||||
content, err := io.ReadAll(md.UnverifiedBody)
|
||||
if err != nil {
|
||||
// Coverage: An error during reading the body can happen only if
|
||||
// 1) the message is encrypted, which is not our case (and we don’t give ReadMessage the key
|
||||
// to decrypt the contents anyway), or
|
||||
// 2) the message is signed AND we give ReadMessage a corresponding public key, which we don’t.
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Uppercase the key ID for minimal consistency with the gpgme-returned fingerprints
|
||||
// (but note that key ID is a suffix of the fingerprint only for V4 keys, not V3)!
|
||||
return content, strings.ToUpper(fmt.Sprintf("%016X", md.SignedByKeyId)), nil
|
||||
}
|
||||
202
vendor/github.com/containers/image/v5/signature/mechanism_gpgme.go
generated
vendored
Normal file
202
vendor/github.com/containers/image/v5/signature/mechanism_gpgme.go
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
//go:build !containers_image_openpgp
|
||||
// +build !containers_image_openpgp
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/proglottis/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) (signingMechanismWithPassphrase, 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) (signingMechanismWithPassphrase, []string, error) {
|
||||
dir, err := os.MkdirTemp("", "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 and passphrase.
|
||||
// Fails with a SigningNotSupportedError if the mechanism does not support signing.
|
||||
func (m *gpgmeSigningMechanism) SignWithPassphrase(input []byte, keyIdentity string, passphrase 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 passphrase != "" {
|
||||
// Callback to write the passphrase to the specified file descriptor.
|
||||
callback := func(uidHint string, prevWasBad bool, gpgmeFD *os.File) error {
|
||||
if prevWasBad {
|
||||
return errors.New("bad passphrase")
|
||||
}
|
||||
_, err := gpgmeFD.WriteString(passphrase + "\n")
|
||||
return err
|
||||
}
|
||||
if err := m.ctx.SetCallback(callback); err != nil {
|
||||
return nil, fmt.Errorf("setting gpgme passphrase callback: %w", err)
|
||||
}
|
||||
|
||||
// Loopback mode will use the callback instead of prompting the user.
|
||||
if err := m.ctx.SetPinEntryMode(gpgme.PinEntryLoopback); err != nil {
|
||||
return nil, fmt.Errorf("setting gpgme pinentry mode: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = m.ctx.Sign([]*gpgme.Key{key}, inputData, sigData, gpgme.SigModeNormal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sigBuffer.Bytes(), 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) {
|
||||
return m.SignWithPassphrase(input, keyIdentity, "")
|
||||
}
|
||||
|
||||
// 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 corresponds to "Key ID" for OpenPGP keys)
|
||||
// is NOT the same as a "key identity" used in other calls to 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)
|
||||
}
|
||||
173
vendor/github.com/containers/image/v5/signature/mechanism_openpgp.go
generated
vendored
Normal file
173
vendor/github.com/containers/image/v5/signature/mechanism_openpgp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
//go:build containers_image_openpgp
|
||||
// +build containers_image_openpgp
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
// This is a fallback code; the primary recommendation is to use the gpgme mechanism
|
||||
// implementation, which is out-of-process and more appropriate for handling long-term private key material
|
||||
// than any Go implementation.
|
||||
// For this verify-only fallback, we haven't reviewed any of the
|
||||
// existing alternatives to choose; so, for now, continue to
|
||||
// use this frozen deprecated implementation.
|
||||
//lint:ignore SA1019 See above
|
||||
"golang.org/x/crypto/openpgp" //nolint:staticcheck
|
||||
)
|
||||
|
||||
// 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) (signingMechanismWithPassphrase, error) {
|
||||
m := &openpgpSigningMechanism{
|
||||
keyring: openpgp.EntityList{},
|
||||
}
|
||||
|
||||
gpgHome := optionalDir
|
||||
if gpgHome == "" {
|
||||
gpgHome = os.Getenv("GNUPGHOME")
|
||||
if gpgHome == "" {
|
||||
gpgHome = path.Join(homedir.Get(), ".gnupg")
|
||||
}
|
||||
}
|
||||
|
||||
pubring, err := os.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) (signingMechanismWithPassphrase, []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) SignWithPassphrase(input []byte, keyIdentity string, passphrase string) ([]byte, error) {
|
||||
return nil, 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 m.SignWithPassphrase(input, keyIdentity, "")
|
||||
}
|
||||
|
||||
// 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 := io.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 != nil {
|
||||
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)}
|
||||
}
|
||||
}
|
||||
} else if md.SignatureV3 == nil {
|
||||
// Coverage: If md.SignedBy != nil, the final md.UnverifiedBody.Read() either sets one of md.Signature or md.SignatureV3,
|
||||
// or sets md.SignatureError.
|
||||
return nil, "", InvalidSignatureError{msg: "Unexpected openpgp.MessageDetails: neither Signature nor SignatureV3 is set"}
|
||||
}
|
||||
|
||||
// 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 corresponds to "Key ID" for OpenPGP keys)
|
||||
// is NOT the same as a "key identity" used in other calls to 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)
|
||||
}
|
||||
776
vendor/github.com/containers/image/v5/signature/policy_config.go
generated
vendored
Normal file
776
vendor/github.com/containers/image/v5/signature/policy_config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,776 @@
|
|||
// policy_config.go handles creation of policy objects, either by parsing JSON
|
||||
// or by programs building them programmatically.
|
||||
|
||||
// The New* constructors are intended to be a stable API. FIXME: after an independent review.
|
||||
|
||||
// Do not invoke the internals of the JSON marshaling/unmarshaling directly.
|
||||
|
||||
// We can't just blindly call json.Unmarshal because that would silently ignore
|
||||
// typos, and that would just not do for security policy.
|
||||
|
||||
// FIXME? This is by no means an user-friendly parser: No location information in error messages, no other context.
|
||||
// But at least it is not worse than blind json.Unmarshal()…
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// systemDefaultPolicyPath is the policy path used for DefaultPolicy().
|
||||
// You can override this at build time with
|
||||
// -ldflags '-X github.com/containers/image/v5/signature.systemDefaultPolicyPath=$your_path'
|
||||
var systemDefaultPolicyPath = builtinDefaultPolicyPath
|
||||
|
||||
// builtinDefaultPolicyPath is the policy path used for DefaultPolicy().
|
||||
// DO NOT change this, instead see systemDefaultPolicyPath above.
|
||||
const builtinDefaultPolicyPath = "/etc/containers/policy.json"
|
||||
|
||||
// userPolicyFile is the path to the per user policy path.
|
||||
var userPolicyFile = filepath.FromSlash(".config/containers/policy.json")
|
||||
|
||||
// InvalidPolicyFormatError is returned when parsing an invalid policy configuration.
|
||||
type InvalidPolicyFormatError string
|
||||
|
||||
func (err InvalidPolicyFormatError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
// DefaultPolicy returns the default policy of the system.
|
||||
// Most applications should be using this method to get the policy configured
|
||||
// by the system administrator.
|
||||
// sys should usually be nil, can be set to override the default.
|
||||
// NOTE: When this function returns an error, report it to the user and abort.
|
||||
// DO NOT hard-code fallback policies in your application.
|
||||
func DefaultPolicy(sys *types.SystemContext) (*Policy, error) {
|
||||
return NewPolicyFromFile(defaultPolicyPath(sys))
|
||||
}
|
||||
|
||||
// defaultPolicyPath returns a path to the default policy of the system.
|
||||
func defaultPolicyPath(sys *types.SystemContext) string {
|
||||
return defaultPolicyPathWithHomeDir(sys, homedir.Get())
|
||||
}
|
||||
|
||||
// defaultPolicyPathWithHomeDir is an internal implementation detail of defaultPolicyPath,
|
||||
// it exists only to allow testing it with an artificial home directory.
|
||||
func defaultPolicyPathWithHomeDir(sys *types.SystemContext, homeDir string) string {
|
||||
if sys != nil && sys.SignaturePolicyPath != "" {
|
||||
return sys.SignaturePolicyPath
|
||||
}
|
||||
userPolicyFilePath := filepath.Join(homeDir, userPolicyFile)
|
||||
if _, err := os.Stat(userPolicyFilePath); err == nil {
|
||||
return userPolicyFilePath
|
||||
}
|
||||
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
|
||||
}
|
||||
return systemDefaultPolicyPath
|
||||
}
|
||||
|
||||
// NewPolicyFromFile returns a policy configured in the specified file.
|
||||
func NewPolicyFromFile(fileName string) (*Policy, error) {
|
||||
contents, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy, err := NewPolicyFromBytes(contents)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid policy in %q", fileName)
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// NewPolicyFromBytes returns a policy parsed from the specified blob.
|
||||
// Use this function instead of calling json.Unmarshal directly.
|
||||
func NewPolicyFromBytes(data []byte) (*Policy, error) {
|
||||
p := Policy{}
|
||||
if err := json.Unmarshal(data, &p); err != nil {
|
||||
return nil, InvalidPolicyFormatError(err.Error())
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Compile-time check that Policy implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*Policy)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (p *Policy) UnmarshalJSON(data []byte) error {
|
||||
*p = Policy{}
|
||||
transports := policyTransportsMap{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "default":
|
||||
return &p.Default
|
||||
case "transports":
|
||||
return &transports
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Default == nil {
|
||||
return InvalidPolicyFormatError("Default policy is missing")
|
||||
}
|
||||
p.Transports = map[string]PolicyTransportScopes(transports)
|
||||
return nil
|
||||
}
|
||||
|
||||
// policyTransportsMap is a specialization of this map type for the strict JSON parsing semantics appropriate for the Policy.Transports member.
|
||||
type policyTransportsMap map[string]PolicyTransportScopes
|
||||
|
||||
// Compile-time check that policyTransportsMap implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*policyTransportsMap)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (m *policyTransportsMap) UnmarshalJSON(data []byte) error {
|
||||
// We can't unmarshal directly into map values because it is not possible to take an address of a map value.
|
||||
// So, use a temporary map of pointers-to-slices and convert.
|
||||
tmpMap := map[string]*PolicyTransportScopes{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
// transport can be nil
|
||||
transport := transports.Get(key)
|
||||
// paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||
if _, ok := tmpMap[key]; ok {
|
||||
return nil
|
||||
}
|
||||
ptsWithTransport := policyTransportScopesWithTransport{
|
||||
transport: transport,
|
||||
dest: &PolicyTransportScopes{}, // This allocates a new instance on each call.
|
||||
}
|
||||
tmpMap[key] = ptsWithTransport.dest
|
||||
return &ptsWithTransport
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, ptr := range tmpMap {
|
||||
(*m)[key] = *ptr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile-time check that PolicyTransportScopes "implements"" json.Unmarshaler.
|
||||
// we want to only use policyTransportScopesWithTransport
|
||||
var _ json.Unmarshaler = (*PolicyTransportScopes)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (m *PolicyTransportScopes) UnmarshalJSON(data []byte) error {
|
||||
return errors.New("Do not try to unmarshal PolicyTransportScopes directly")
|
||||
}
|
||||
|
||||
// policyTransportScopesWithTransport is a way to unmarshal a PolicyTransportScopes
|
||||
// while validating using a specific ImageTransport if not nil.
|
||||
type policyTransportScopesWithTransport struct {
|
||||
transport types.ImageTransport
|
||||
dest *PolicyTransportScopes
|
||||
}
|
||||
|
||||
// Compile-time check that policyTransportScopesWithTransport implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*policyTransportScopesWithTransport)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (m *policyTransportScopesWithTransport) UnmarshalJSON(data []byte) error {
|
||||
// We can't unmarshal directly into map values because it is not possible to take an address of a map value.
|
||||
// So, use a temporary map of pointers-to-slices and convert.
|
||||
tmpMap := map[string]*PolicyRequirements{}
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
// paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||
if _, ok := tmpMap[key]; ok {
|
||||
return nil
|
||||
}
|
||||
if key != "" && m.transport != nil {
|
||||
if err := m.transport.ValidatePolicyConfigurationScope(key); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
ptr := &PolicyRequirements{} // This allocates a new instance on each call.
|
||||
tmpMap[key] = ptr
|
||||
return ptr
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, ptr := range tmpMap {
|
||||
(*m.dest)[key] = *ptr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile-time check that PolicyRequirements implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*PolicyRequirements)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (m *PolicyRequirements) UnmarshalJSON(data []byte) error {
|
||||
reqJSONs := []json.RawMessage{}
|
||||
if err := json.Unmarshal(data, &reqJSONs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(reqJSONs) == 0 {
|
||||
return InvalidPolicyFormatError("List of verification policy requirements must not be empty")
|
||||
}
|
||||
res := make([]PolicyRequirement, len(reqJSONs))
|
||||
for i, reqJSON := range reqJSONs {
|
||||
req, err := newPolicyRequirementFromJSON(reqJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res[i] = req
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPolicyRequirementFromJSON parses JSON data into a PolicyRequirement implementation.
|
||||
func newPolicyRequirementFromJSON(data []byte) (PolicyRequirement, error) {
|
||||
var typeField prCommon
|
||||
if err := json.Unmarshal(data, &typeField); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res PolicyRequirement
|
||||
switch typeField.Type {
|
||||
case prTypeInsecureAcceptAnything:
|
||||
res = &prInsecureAcceptAnything{}
|
||||
case prTypeReject:
|
||||
res = &prReject{}
|
||||
case prTypeSignedBy:
|
||||
res = &prSignedBy{}
|
||||
case prTypeSignedBaseLayer:
|
||||
res = &prSignedBaseLayer{}
|
||||
default:
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("Unknown policy requirement type \"%s\"", typeField.Type))
|
||||
}
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// newPRInsecureAcceptAnything is NewPRInsecureAcceptAnything, except it returns the private type.
|
||||
func newPRInsecureAcceptAnything() *prInsecureAcceptAnything {
|
||||
return &prInsecureAcceptAnything{prCommon{Type: prTypeInsecureAcceptAnything}}
|
||||
}
|
||||
|
||||
// NewPRInsecureAcceptAnything returns a new "insecureAcceptAnything" PolicyRequirement.
|
||||
func NewPRInsecureAcceptAnything() PolicyRequirement {
|
||||
return newPRInsecureAcceptAnything()
|
||||
}
|
||||
|
||||
// Compile-time check that prInsecureAcceptAnything implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prInsecureAcceptAnything)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (pr *prInsecureAcceptAnything) UnmarshalJSON(data []byte) error {
|
||||
*pr = prInsecureAcceptAnything{}
|
||||
var tmp prInsecureAcceptAnything
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prTypeInsecureAcceptAnything {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
*pr = *newPRInsecureAcceptAnything()
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRReject is NewPRReject, except it returns the private type.
|
||||
func newPRReject() *prReject {
|
||||
return &prReject{prCommon{Type: prTypeReject}}
|
||||
}
|
||||
|
||||
// NewPRReject returns a new "reject" PolicyRequirement.
|
||||
func NewPRReject() PolicyRequirement {
|
||||
return newPRReject()
|
||||
}
|
||||
|
||||
// Compile-time check that prReject implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prReject)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (pr *prReject) UnmarshalJSON(data []byte) error {
|
||||
*pr = prReject{}
|
||||
var tmp prReject
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prTypeReject {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
*pr = *newPRReject()
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRSignedBy returns a new prSignedBy if parameters are valid.
|
||||
func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
if !keyType.IsValid() {
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("invalid keyType \"%s\"", keyType))
|
||||
}
|
||||
if len(keyPath) > 0 && len(keyData) > 0 {
|
||||
return nil, InvalidPolicyFormatError("keyType and keyData cannot be used simultaneously")
|
||||
}
|
||||
if signedIdentity == nil {
|
||||
return nil, InvalidPolicyFormatError("signedIdentity not specified")
|
||||
}
|
||||
return &prSignedBy{
|
||||
prCommon: prCommon{Type: prTypeSignedBy},
|
||||
KeyType: keyType,
|
||||
KeyPath: keyPath,
|
||||
KeyData: keyData,
|
||||
SignedIdentity: signedIdentity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newPRSignedByKeyPath is NewPRSignedByKeyPath, except it returns the private type.
|
||||
func newPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
return newPRSignedBy(keyType, keyPath, nil, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSignedByKeyPath returns a new "signedBy" PolicyRequirement using a KeyPath
|
||||
func NewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||
return newPRSignedByKeyPath(keyType, keyPath, signedIdentity)
|
||||
}
|
||||
|
||||
// newPRSignedByKeyData is NewPRSignedByKeyData, except it returns the private type.
|
||||
func newPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||
return newPRSignedBy(keyType, "", keyData, signedIdentity)
|
||||
}
|
||||
|
||||
// NewPRSignedByKeyData returns a new "signedBy" PolicyRequirement using a KeyData
|
||||
func NewPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||
return newPRSignedByKeyData(keyType, keyData, signedIdentity)
|
||||
}
|
||||
|
||||
// Compile-time check that prSignedBy implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prSignedBy)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (pr *prSignedBy) UnmarshalJSON(data []byte) error {
|
||||
*pr = prSignedBy{}
|
||||
var tmp prSignedBy
|
||||
var gotKeyPath, gotKeyData = false, false
|
||||
var signedIdentity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||
switch key {
|
||||
case "type":
|
||||
return &tmp.Type
|
||||
case "keyType":
|
||||
return &tmp.KeyType
|
||||
case "keyPath":
|
||||
gotKeyPath = true
|
||||
return &tmp.KeyPath
|
||||
case "keyData":
|
||||
gotKeyData = true
|
||||
return &tmp.KeyData
|
||||
case "signedIdentity":
|
||||
return &signedIdentity
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prTypeSignedBy {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
if signedIdentity == nil {
|
||||
tmp.SignedIdentity = NewPRMMatchRepoDigestOrExact()
|
||||
} else {
|
||||
si, err := newPolicyReferenceMatchFromJSON(signedIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp.SignedIdentity = si
|
||||
}
|
||||
|
||||
var res *prSignedBy
|
||||
var err error
|
||||
switch {
|
||||
case gotKeyPath && gotKeyData:
|
||||
return InvalidPolicyFormatError("keyPath and keyData cannot be used simultaneously")
|
||||
case gotKeyPath && !gotKeyData:
|
||||
res, err = newPRSignedByKeyPath(tmp.KeyType, tmp.KeyPath, tmp.SignedIdentity)
|
||||
case !gotKeyPath && gotKeyData:
|
||||
res, err = newPRSignedByKeyData(tmp.KeyType, tmp.KeyData, tmp.SignedIdentity)
|
||||
case !gotKeyPath && !gotKeyData:
|
||||
return InvalidPolicyFormatError("At least one of keyPath and keyData mus be specified")
|
||||
default: // Coverage: This should never happen
|
||||
return errors.Errorf("Impossible keyPath/keyData presence combination!?")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*pr = *res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid returns true iff kt is a recognized value
|
||||
func (kt sbKeyType) IsValid() bool {
|
||||
switch kt {
|
||||
case SBKeyTypeGPGKeys, SBKeyTypeSignedByGPGKeys,
|
||||
SBKeyTypeX509Certificates, SBKeyTypeSignedByX509CAs:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Compile-time check that sbKeyType implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*sbKeyType)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (kt *sbKeyType) UnmarshalJSON(data []byte) error {
|
||||
*kt = sbKeyType("")
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if !sbKeyType(s).IsValid() {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unrecognized keyType value \"%s\"", s))
|
||||
}
|
||||
*kt = sbKeyType(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRSignedBaseLayer is NewPRSignedBaseLayer, except it returns the private type.
|
||||
func newPRSignedBaseLayer(baseLayerIdentity PolicyReferenceMatch) (*prSignedBaseLayer, error) {
|
||||
if baseLayerIdentity == nil {
|
||||
return nil, InvalidPolicyFormatError("baseLayerIdentity not specified")
|
||||
}
|
||||
return &prSignedBaseLayer{
|
||||
prCommon: prCommon{Type: prTypeSignedBaseLayer},
|
||||
BaseLayerIdentity: baseLayerIdentity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPRSignedBaseLayer returns a new "signedBaseLayer" PolicyRequirement.
|
||||
func NewPRSignedBaseLayer(baseLayerIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||
return newPRSignedBaseLayer(baseLayerIdentity)
|
||||
}
|
||||
|
||||
// Compile-time check that prSignedBaseLayer implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prSignedBaseLayer)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error {
|
||||
*pr = prSignedBaseLayer{}
|
||||
var tmp prSignedBaseLayer
|
||||
var baseLayerIdentity json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"baseLayerIdentity": &baseLayerIdentity,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prTypeSignedBaseLayer {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
bli, err := newPolicyReferenceMatchFromJSON(baseLayerIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := newPRSignedBaseLayer(bli)
|
||||
if err != nil {
|
||||
// Coverage: This should never happen, newPolicyReferenceMatchFromJSON has ensured bli is valid.
|
||||
return err
|
||||
}
|
||||
*pr = *res
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPolicyReferenceMatchFromJSON parses JSON data into a PolicyReferenceMatch implementation.
|
||||
func newPolicyReferenceMatchFromJSON(data []byte) (PolicyReferenceMatch, error) {
|
||||
var typeField prmCommon
|
||||
if err := json.Unmarshal(data, &typeField); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res PolicyReferenceMatch
|
||||
switch typeField.Type {
|
||||
case prmTypeMatchExact:
|
||||
res = &prmMatchExact{}
|
||||
case prmTypeMatchRepoDigestOrExact:
|
||||
res = &prmMatchRepoDigestOrExact{}
|
||||
case prmTypeMatchRepository:
|
||||
res = &prmMatchRepository{}
|
||||
case prmTypeExactReference:
|
||||
res = &prmExactReference{}
|
||||
case prmTypeExactRepository:
|
||||
res = &prmExactRepository{}
|
||||
case prmTypeRemapIdentity:
|
||||
res = &prmRemapIdentity{}
|
||||
default:
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("Unknown policy reference match type \"%s\"", typeField.Type))
|
||||
}
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// newPRMMatchExact is NewPRMMatchExact, except it returns the private type.
|
||||
func newPRMMatchExact() *prmMatchExact {
|
||||
return &prmMatchExact{prmCommon{Type: prmTypeMatchExact}}
|
||||
}
|
||||
|
||||
// NewPRMMatchExact returns a new "matchExact" PolicyReferenceMatch.
|
||||
func NewPRMMatchExact() PolicyReferenceMatch {
|
||||
return newPRMMatchExact()
|
||||
}
|
||||
|
||||
// Compile-time check that prmMatchExact implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prmMatchExact)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (prm *prmMatchExact) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchExact{}
|
||||
var tmp prmMatchExact
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prmTypeMatchExact {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
*prm = *newPRMMatchExact()
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRMMatchRepoDigestOrExact is NewPRMMatchRepoDigestOrExact, except it returns the private type.
|
||||
func newPRMMatchRepoDigestOrExact() *prmMatchRepoDigestOrExact {
|
||||
return &prmMatchRepoDigestOrExact{prmCommon{Type: prmTypeMatchRepoDigestOrExact}}
|
||||
}
|
||||
|
||||
// NewPRMMatchRepoDigestOrExact returns a new "matchRepoDigestOrExact" PolicyReferenceMatch.
|
||||
func NewPRMMatchRepoDigestOrExact() PolicyReferenceMatch {
|
||||
return newPRMMatchRepoDigestOrExact()
|
||||
}
|
||||
|
||||
// Compile-time check that prmMatchRepoDigestOrExact implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prmMatchRepoDigestOrExact)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (prm *prmMatchRepoDigestOrExact) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchRepoDigestOrExact{}
|
||||
var tmp prmMatchRepoDigestOrExact
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prmTypeMatchRepoDigestOrExact {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
*prm = *newPRMMatchRepoDigestOrExact()
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRMMatchRepository is NewPRMMatchRepository, except it returns the private type.
|
||||
func newPRMMatchRepository() *prmMatchRepository {
|
||||
return &prmMatchRepository{prmCommon{Type: prmTypeMatchRepository}}
|
||||
}
|
||||
|
||||
// NewPRMMatchRepository returns a new "matchRepository" PolicyReferenceMatch.
|
||||
func NewPRMMatchRepository() PolicyReferenceMatch {
|
||||
return newPRMMatchRepository()
|
||||
}
|
||||
|
||||
// Compile-time check that prmMatchRepository implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prmMatchRepository)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmMatchRepository{}
|
||||
var tmp prmMatchRepository
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prmTypeMatchRepository {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
*prm = *newPRMMatchRepository()
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRMExactReference is NewPRMExactReference, except it returns the private type.
|
||||
func newPRMExactReference(dockerReference string) (*prmExactReference, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(dockerReference)
|
||||
if err != nil {
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error()))
|
||||
}
|
||||
if reference.IsNameOnly(ref) {
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("dockerReference %s contains neither a tag nor digest", dockerReference))
|
||||
}
|
||||
return &prmExactReference{
|
||||
prmCommon: prmCommon{Type: prmTypeExactReference},
|
||||
DockerReference: dockerReference,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPRMExactReference returns a new "exactReference" PolicyReferenceMatch.
|
||||
func NewPRMExactReference(dockerReference string) (PolicyReferenceMatch, error) {
|
||||
return newPRMExactReference(dockerReference)
|
||||
}
|
||||
|
||||
// Compile-time check that prmExactReference implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prmExactReference)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (prm *prmExactReference) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmExactReference{}
|
||||
var tmp prmExactReference
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"dockerReference": &tmp.DockerReference,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prmTypeExactReference {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
|
||||
res, err := newPRMExactReference(tmp.DockerReference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*prm = *res
|
||||
return nil
|
||||
}
|
||||
|
||||
// newPRMExactRepository is NewPRMExactRepository, except it returns the private type.
|
||||
func newPRMExactRepository(dockerRepository string) (*prmExactRepository, error) {
|
||||
if _, err := reference.ParseNormalizedNamed(dockerRepository); err != nil {
|
||||
return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerRepository %s: %s", dockerRepository, err.Error()))
|
||||
}
|
||||
return &prmExactRepository{
|
||||
prmCommon: prmCommon{Type: prmTypeExactRepository},
|
||||
DockerRepository: dockerRepository,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPRMExactRepository returns a new "exactRepository" PolicyRepositoryMatch.
|
||||
func NewPRMExactRepository(dockerRepository string) (PolicyReferenceMatch, error) {
|
||||
return newPRMExactRepository(dockerRepository)
|
||||
}
|
||||
|
||||
// Compile-time check that prmExactRepository implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prmExactRepository)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (prm *prmExactRepository) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmExactRepository{}
|
||||
var tmp prmExactRepository
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"dockerRepository": &tmp.DockerRepository,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prmTypeExactRepository {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
|
||||
res, err := newPRMExactRepository(tmp.DockerRepository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*prm = *res
|
||||
return nil
|
||||
}
|
||||
|
||||
// Private objects for validateIdentityRemappingPrefix
|
||||
var (
|
||||
// remapIdentityDomainRegexp matches exactly a reference domain (name[:port])
|
||||
remapIdentityDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.String() + "$")
|
||||
// remapIdentityDomainPrefixRegexp matches a reference that starts with a domain;
|
||||
// we need this because reference.NameRegexp accepts short names with docker.io implied.
|
||||
remapIdentityDomainPrefixRegexp = regexp.MustCompile("^" + reference.DomainRegexp.String() + "/")
|
||||
// remapIdentityNameRegexp matches exactly a reference.Named name (possibly unnormalized)
|
||||
remapIdentityNameRegexp = regexp.MustCompile("^" + reference.NameRegexp.String() + "$")
|
||||
)
|
||||
|
||||
// validateIdentityRemappingPrefix returns an InvalidPolicyFormatError if s is detected to be invalid
|
||||
// for the Prefix or SignedPrefix values of prmRemapIdentity.
|
||||
// Note that it may not recognize _all_ invalid values.
|
||||
func validateIdentityRemappingPrefix(s string) error {
|
||||
if remapIdentityDomainRegexp.MatchString(s) ||
|
||||
(remapIdentityNameRegexp.MatchString(s) && remapIdentityDomainPrefixRegexp.MatchString(s)) {
|
||||
// FIXME? This does not reject "shortname" nor "ns/shortname", because docker/reference
|
||||
// does not provide an API for the short vs. long name logic.
|
||||
// It will either not match, or fail in the ParseNamed call of
|
||||
// prmRemapIdentity.remapReferencePrefix when trying to use such a prefix.
|
||||
return nil
|
||||
}
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("prefix %q is not valid", s))
|
||||
}
|
||||
|
||||
// newPRMRemapIdentity is NewPRMRemapIdentity, except it returns the private type.
|
||||
func newPRMRemapIdentity(prefix, signedPrefix string) (*prmRemapIdentity, error) {
|
||||
if err := validateIdentityRemappingPrefix(prefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateIdentityRemappingPrefix(signedPrefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &prmRemapIdentity{
|
||||
prmCommon: prmCommon{Type: prmTypeRemapIdentity},
|
||||
Prefix: prefix,
|
||||
SignedPrefix: signedPrefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPRMRemapIdentity returns a new "remapIdentity" PolicyRepositoryMatch.
|
||||
func NewPRMRemapIdentity(prefix, signedPrefix string) (PolicyReferenceMatch, error) {
|
||||
return newPRMRemapIdentity(prefix, signedPrefix)
|
||||
}
|
||||
|
||||
// Compile-time check that prmRemapIdentity implements json.Unmarshaler.
|
||||
var _ json.Unmarshaler = (*prmRemapIdentity)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (prm *prmRemapIdentity) UnmarshalJSON(data []byte) error {
|
||||
*prm = prmRemapIdentity{}
|
||||
var tmp prmRemapIdentity
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"type": &tmp.Type,
|
||||
"prefix": &tmp.Prefix,
|
||||
"signedPrefix": &tmp.SignedPrefix,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Type != prmTypeRemapIdentity {
|
||||
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||
}
|
||||
|
||||
res, err := newPRMRemapIdentity(tmp.Prefix, tmp.SignedPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*prm = *res
|
||||
return nil
|
||||
}
|
||||
288
vendor/github.com/containers/image/v5/signature/policy_eval.go
generated
vendored
Normal file
288
vendor/github.com/containers/image/v5/signature/policy_eval.go
generated
vendored
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// This defines the top-level policy evaluation API.
|
||||
// To the extent possible, the interface of the functions provided
|
||||
// here is intended to be completely unambiguous, and stable for users
|
||||
// to rely on.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PolicyRequirementError is an explanatory text for rejecting a signature or an image.
|
||||
type PolicyRequirementError string
|
||||
|
||||
func (err PolicyRequirementError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
// signatureAcceptanceResult is the principal value returned by isSignatureAuthorAccepted.
|
||||
type signatureAcceptanceResult string
|
||||
|
||||
const (
|
||||
sarAccepted signatureAcceptanceResult = "sarAccepted"
|
||||
sarRejected signatureAcceptanceResult = "sarRejected"
|
||||
sarUnknown signatureAcceptanceResult = "sarUnknown"
|
||||
)
|
||||
|
||||
// PolicyRequirement is a rule which must be satisfied by at least one of the signatures of an image.
|
||||
// The type is public, but its definition is private.
|
||||
type PolicyRequirement interface {
|
||||
// FIXME: For speed, we should support creating per-context state (not stored in the PolicyRequirement), to cache
|
||||
// costly initialization like creating temporary GPG home directories and reading files.
|
||||
// Setup() (someState, error)
|
||||
// Then, the operations below would be done on the someState object, not directly on a PolicyRequirement.
|
||||
|
||||
// isSignatureAuthorAccepted, given an image and a signature blob, returns:
|
||||
// - sarAccepted if the signature has been verified against the appropriate public key
|
||||
// (where "appropriate public key" may depend on the contents of the signature);
|
||||
// in that case a parsed Signature should be returned.
|
||||
// - sarRejected if the signature has not been verified;
|
||||
// in that case error must be non-nil, and should be an PolicyRequirementError if evaluation
|
||||
// succeeded but the result was rejection.
|
||||
// - sarUnknown if if this PolicyRequirement does not deal with signatures.
|
||||
// NOTE: sarUnknown should not be returned if this PolicyRequirement should make a decision but something failed.
|
||||
// Returning sarUnknown and a non-nil error value is invalid.
|
||||
// WARNING: This makes the signature contents acceptable for further processing,
|
||||
// but it does not necessarily mean that the contents of the signature are
|
||||
// consistent with local policy.
|
||||
// For example:
|
||||
// - Do not use a true value to determine whether to run
|
||||
// a container based on this image; use IsRunningImageAllowed instead.
|
||||
// - Just because a signature is accepted does not automatically mean the contents of the
|
||||
// signature are authorized to run code as root, or to affect system or cluster configuration.
|
||||
isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error)
|
||||
|
||||
// isRunningImageAllowed returns true if the requirement allows running an image.
|
||||
// If it returns false, err must be non-nil, and should be an PolicyRequirementError if evaluation
|
||||
// succeeded but the result was rejection.
|
||||
// WARNING: This validates signatures and the manifest, but does not download or validate the
|
||||
// layers. Users must validate that the layers match their expected digests.
|
||||
isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error)
|
||||
}
|
||||
|
||||
// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
|
||||
// The type is public, but its implementation is private.
|
||||
type PolicyReferenceMatch interface {
|
||||
// matchesDockerReference decides whether a specific image identity is accepted for an image
|
||||
// (or, usually, for the image's Reference().DockerReference()). Note that
|
||||
// image.Reference().DockerReference() may be nil.
|
||||
matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool
|
||||
}
|
||||
|
||||
// PolicyContext encapsulates a policy and possible cached state
|
||||
// for speeding up its evaluation.
|
||||
type PolicyContext struct {
|
||||
Policy *Policy
|
||||
state policyContextState // Internal consistency checking
|
||||
}
|
||||
|
||||
// policyContextState is used internally to verify the users are not misusing a PolicyContext.
|
||||
type policyContextState string
|
||||
|
||||
const (
|
||||
pcInitializing policyContextState = "Initializing"
|
||||
pcReady policyContextState = "Ready"
|
||||
pcInUse policyContextState = "InUse"
|
||||
pcDestroying policyContextState = "Destroying"
|
||||
pcDestroyed policyContextState = "Destroyed"
|
||||
)
|
||||
|
||||
// changeContextState changes pc.state, or fails if the state is unexpected
|
||||
func (pc *PolicyContext) changeState(expected, new policyContextState) error {
|
||||
if pc.state != expected {
|
||||
return errors.Errorf(`"Invalid PolicyContext state, expected "%s", found "%s"`, expected, pc.state)
|
||||
}
|
||||
pc.state = new
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPolicyContext sets up and initializes a context for the specified policy.
|
||||
// The policy must not be modified while the context exists. FIXME: make a deep copy?
|
||||
// If this function succeeds, the caller should call PolicyContext.Destroy() when done.
|
||||
func NewPolicyContext(policy *Policy) (*PolicyContext, error) {
|
||||
pc := &PolicyContext{Policy: policy, state: pcInitializing}
|
||||
// FIXME: initialize
|
||||
if err := pc.changeState(pcInitializing, pcReady); err != nil {
|
||||
// Huh?! This should never fail, we didn't give the pointer to anybody.
|
||||
// Just give up and leave unclean state around.
|
||||
return nil, err
|
||||
}
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
// Destroy should be called when the user of the context is done with it.
|
||||
func (pc *PolicyContext) Destroy() error {
|
||||
if err := pc.changeState(pcReady, pcDestroying); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: destroy
|
||||
return pc.changeState(pcDestroying, pcDestroyed)
|
||||
}
|
||||
|
||||
// policyIdentityLogName returns a string description of the image identity for policy purposes.
|
||||
// ONLY use this for log messages, not for any decisions!
|
||||
func policyIdentityLogName(ref types.ImageReference) string {
|
||||
return ref.Transport().Name() + ":" + ref.PolicyConfigurationIdentity()
|
||||
}
|
||||
|
||||
// requirementsForImageRef selects the appropriate requirements for ref.
|
||||
func (pc *PolicyContext) requirementsForImageRef(ref types.ImageReference) PolicyRequirements {
|
||||
// Do we have a PolicyTransportScopes for this transport?
|
||||
transportName := ref.Transport().Name()
|
||||
if transportScopes, ok := pc.Policy.Transports[transportName]; ok {
|
||||
// Look for a full match.
|
||||
identity := ref.PolicyConfigurationIdentity()
|
||||
if req, ok := transportScopes[identity]; ok {
|
||||
logrus.Debugf(` Using transport "%s" policy section %s`, transportName, identity)
|
||||
return req
|
||||
}
|
||||
|
||||
// Look for a match of the possible parent namespaces.
|
||||
for _, name := range ref.PolicyConfigurationNamespaces() {
|
||||
if req, ok := transportScopes[name]; ok {
|
||||
logrus.Debugf(` Using transport "%s" specific policy section %s`, transportName, name)
|
||||
return req
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a default match for the transport.
|
||||
if req, ok := transportScopes[""]; ok {
|
||||
logrus.Debugf(` Using transport "%s" policy section ""`, transportName)
|
||||
return req
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf(" Using default policy section")
|
||||
return pc.Policy.Default
|
||||
}
|
||||
|
||||
// GetSignaturesWithAcceptedAuthor returns those signatures from an image
|
||||
// for which the policy accepts the author (and which have been successfully
|
||||
// verified).
|
||||
// NOTE: This may legitimately return an empty list and no error, if the image
|
||||
// has no signatures or only invalid signatures.
|
||||
// WARNING: This makes the signature contents acceptable for further processing,
|
||||
// but it does not necessarily mean that the contents of the signature are
|
||||
// consistent with local policy.
|
||||
// For example:
|
||||
// - Do not use a an existence of an accepted signature to determine whether to run
|
||||
// a container based on this image; use IsRunningImageAllowed instead.
|
||||
// - Just because a signature is accepted does not automatically mean the contents of the
|
||||
// signature are authorized to run code as root, or to affect system or cluster configuration.
|
||||
func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(ctx context.Context, image types.UnparsedImage) (sigs []*Signature, finalErr error) {
|
||||
if err := pc.changeState(pcReady, pcInUse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := pc.changeState(pcInUse, pcReady); err != nil {
|
||||
sigs = nil
|
||||
finalErr = err
|
||||
}
|
||||
}()
|
||||
|
||||
logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", policyIdentityLogName(image.Reference()))
|
||||
reqs := pc.requirementsForImageRef(image.Reference())
|
||||
|
||||
// FIXME: rename Signatures to UnverifiedSignatures
|
||||
// FIXME: pass context.Context
|
||||
unverifiedSignatures, err := image.Signatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]*Signature, 0, len(unverifiedSignatures))
|
||||
for sigNumber, sig := range unverifiedSignatures {
|
||||
var acceptedSig *Signature // non-nil if accepted
|
||||
rejected := false
|
||||
// FIXME? Say more about the contents of the signature, i.e. parse it even before verification?!
|
||||
logrus.Debugf("Evaluating signature %d:", sigNumber)
|
||||
interpretingReqs:
|
||||
for reqNumber, req := range reqs {
|
||||
// FIXME: Log the requirement itself? For now, we use just the number.
|
||||
// FIXME: supply state
|
||||
switch res, as, err := req.isSignatureAuthorAccepted(ctx, image, sig); res {
|
||||
case sarAccepted:
|
||||
if as == nil { // Coverage: this should never happen
|
||||
logrus.Debugf(" Requirement %d: internal inconsistency: sarAccepted but no parsed contents", reqNumber)
|
||||
rejected = true
|
||||
break interpretingReqs
|
||||
}
|
||||
logrus.Debugf(" Requirement %d: signature accepted", reqNumber)
|
||||
if acceptedSig == nil {
|
||||
acceptedSig = as
|
||||
} else if *as != *acceptedSig { // Coverage: this should never happen
|
||||
// Huh?! Two ways of verifying the same signature blob resulted in two different parses of its already accepted contents?
|
||||
logrus.Debugf(" Requirement %d: internal inconsistency: sarAccepted but different parsed contents", reqNumber)
|
||||
rejected = true
|
||||
acceptedSig = nil
|
||||
break interpretingReqs
|
||||
}
|
||||
case sarRejected:
|
||||
logrus.Debugf(" Requirement %d: signature rejected: %s", reqNumber, err.Error())
|
||||
rejected = true
|
||||
break interpretingReqs
|
||||
case sarUnknown:
|
||||
if err != nil { // Coverage: this should never happen
|
||||
logrus.Debugf(" Requirement %d: internal inconsistency: sarUnknown but an error message %s", reqNumber, err.Error())
|
||||
rejected = true
|
||||
break interpretingReqs
|
||||
}
|
||||
logrus.Debugf(" Requirement %d: signature state unknown, continuing", reqNumber)
|
||||
default: // Coverage: this should never happen
|
||||
logrus.Debugf(" Requirement %d: internal inconsistency: unknown result %#v", reqNumber, string(res))
|
||||
rejected = true
|
||||
break interpretingReqs
|
||||
}
|
||||
}
|
||||
// This also handles the (invalid) case of empty reqs, by rejecting the signature.
|
||||
if acceptedSig != nil && !rejected {
|
||||
logrus.Debugf(" Overall: OK, signature accepted")
|
||||
res = append(res, acceptedSig)
|
||||
} else {
|
||||
logrus.Debugf(" Overall: Signature not accepted")
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// IsRunningImageAllowed returns true iff the policy allows running the image.
|
||||
// If it returns false, err must be non-nil, and should be an PolicyRequirementError if evaluation
|
||||
// succeeded but the result was rejection.
|
||||
// WARNING: This validates signatures and the manifest, but does not download or validate the
|
||||
// layers. Users must validate that the layers match their expected digests.
|
||||
func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (res bool, finalErr error) {
|
||||
if err := pc.changeState(pcReady, pcInUse); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err := pc.changeState(pcInUse, pcReady); err != nil {
|
||||
res = false
|
||||
finalErr = err
|
||||
}
|
||||
}()
|
||||
|
||||
logrus.Debugf("IsRunningImageAllowed for image %s", policyIdentityLogName(image.Reference()))
|
||||
reqs := pc.requirementsForImageRef(image.Reference())
|
||||
|
||||
if len(reqs) == 0 {
|
||||
return false, PolicyRequirementError("List of verification policy requirements must not be empty")
|
||||
}
|
||||
|
||||
for reqNumber, req := range reqs {
|
||||
// FIXME: supply state
|
||||
allowed, err := req.isRunningImageAllowed(ctx, image)
|
||||
if !allowed {
|
||||
logrus.Debugf("Requirement %d: denied, done", reqNumber)
|
||||
return false, err
|
||||
}
|
||||
logrus.Debugf(" Requirement %d: allowed", reqNumber)
|
||||
}
|
||||
// We have tested that len(reqs) != 0, so at least one req must have explicitly allowed this image.
|
||||
logrus.Debugf("Overall: allowed")
|
||||
return true, nil
|
||||
}
|
||||
20
vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go
generated
vendored
Normal file
20
vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Policy evaluation for prSignedBaseLayer.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (pr *prSignedBaseLayer) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
return sarUnknown, nil, nil
|
||||
}
|
||||
|
||||
func (pr *prSignedBaseLayer) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
// FIXME? Reject this at policy parsing time already?
|
||||
logrus.Errorf("signedBaseLayer not implemented yet!")
|
||||
return false, PolicyRequirementError("signedBaseLayer not implemented yet!")
|
||||
}
|
||||
130
vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go
generated
vendored
Normal file
130
vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go
generated
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// Policy evaluation for prSignedBy.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
switch pr.KeyType {
|
||||
case SBKeyTypeGPGKeys:
|
||||
case SBKeyTypeSignedByGPGKeys, SBKeyTypeX509Certificates, SBKeyTypeSignedByX509CAs:
|
||||
// FIXME? Reject this at policy parsing time already?
|
||||
return sarRejected, nil, errors.Errorf(`"Unimplemented "keyType" value "%s"`, string(pr.KeyType))
|
||||
default:
|
||||
// This should never happen, newPRSignedBy ensures KeyType.IsValid()
|
||||
return sarRejected, nil, errors.Errorf(`"Unknown "keyType" value "%s"`, string(pr.KeyType))
|
||||
}
|
||||
|
||||
if pr.KeyPath != "" && pr.KeyData != nil {
|
||||
return sarRejected, nil, errors.New(`Internal inconsistency: both "keyPath" and "keyData" specified`)
|
||||
}
|
||||
// FIXME: move this to per-context initialization
|
||||
var data []byte
|
||||
if pr.KeyData != nil {
|
||||
data = pr.KeyData
|
||||
} else {
|
||||
d, err := os.ReadFile(pr.KeyPath)
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
data = d
|
||||
}
|
||||
|
||||
// FIXME: move this to per-context initialization
|
||||
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")
|
||||
}
|
||||
|
||||
signature, err := verifyAndExtractSignature(mech, sig, signatureAcceptanceRules{
|
||||
validateKeyIdentity: func(keyIdentity string) error {
|
||||
for _, trustedIdentity := range trustedIdentities {
|
||||
if keyIdentity == trustedIdentity {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Coverage: We use a private GPG home directory and only import trusted keys, so this should
|
||||
// not be reachable.
|
||||
return PolicyRequirementError(fmt.Sprintf("Signature by key %s is not accepted", keyIdentity))
|
||||
},
|
||||
validateSignedDockerReference: func(ref string) error {
|
||||
if !pr.SignedIdentity.matchesDockerReference(image, ref) {
|
||||
return PolicyRequirementError(fmt.Sprintf("Signature for identity %s is not accepted", ref))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerManifestDigest: func(digest digest.Digest) error {
|
||||
m, _, err := image.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
digestMatches, err := manifest.MatchesDigest(m, digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !digestMatches {
|
||||
return PolicyRequirementError(fmt.Sprintf("Signature for digest %s does not match", digest))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return sarRejected, nil, err
|
||||
}
|
||||
|
||||
return sarAccepted, signature, nil
|
||||
}
|
||||
|
||||
func (pr *prSignedBy) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
// FIXME: pass context.Context
|
||||
sigs, err := image.Signatures(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var rejections []error
|
||||
for _, s := range sigs {
|
||||
var reason error
|
||||
switch res, _, err := pr.isSignatureAuthorAccepted(ctx, image, s); res {
|
||||
case sarAccepted:
|
||||
// One accepted signature is enough.
|
||||
return true, nil
|
||||
case sarRejected:
|
||||
reason = err
|
||||
case sarUnknown:
|
||||
// Huh?! This should not happen at all; treat it as any other invalid value.
|
||||
fallthrough
|
||||
default:
|
||||
reason = errors.Errorf(`Internal error: Unexpected signature verification result "%s"`, string(res))
|
||||
}
|
||||
rejections = append(rejections, reason)
|
||||
}
|
||||
var summary error
|
||||
switch len(rejections) {
|
||||
case 0:
|
||||
summary = PolicyRequirementError("A signature was required, but no signature exists")
|
||||
case 1:
|
||||
summary = rejections[0]
|
||||
default:
|
||||
var msgs []string
|
||||
for _, e := range rejections {
|
||||
msgs = append(msgs, e.Error())
|
||||
}
|
||||
summary = PolicyRequirementError(fmt.Sprintf("None of the signatures were accepted, reasons: %s",
|
||||
strings.Join(msgs, "; ")))
|
||||
}
|
||||
return false, summary
|
||||
}
|
||||
29
vendor/github.com/containers/image/v5/signature/policy_eval_simple.go
generated
vendored
Normal file
29
vendor/github.com/containers/image/v5/signature/policy_eval_simple.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Policy evaluation for the various simple PolicyRequirement types.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
// prInsecureAcceptAnything semantics: Every image is allowed to run,
|
||||
// but this does not consider the signature as verified.
|
||||
return sarUnknown, nil, nil
|
||||
}
|
||||
|
||||
func (pr *prInsecureAcceptAnything) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (pr *prReject) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
return sarRejected, nil, PolicyRequirementError(fmt.Sprintf("Any signatures for image %s are rejected by policy.", transports.ImageName(image.Reference())))
|
||||
}
|
||||
|
||||
func (pr *prReject) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
return false, PolicyRequirementError(fmt.Sprintf("Running image %s is rejected by policy.", transports.ImageName(image.Reference())))
|
||||
}
|
||||
154
vendor/github.com/containers/image/v5/signature/policy_reference_match.go
generated
vendored
Normal file
154
vendor/github.com/containers/image/v5/signature/policy_reference_match.go
generated
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// PolicyReferenceMatch implementations.
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
)
|
||||
|
||||
// parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images.
|
||||
func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (reference.Named, reference.Named, error) {
|
||||
r1 := image.Reference().DockerReference()
|
||||
if r1 == nil {
|
||||
return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity",
|
||||
transports.ImageName(image.Reference())))
|
||||
}
|
||||
r2, err := reference.ParseNormalizedNamed(s2)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return r1, r2, nil
|
||||
}
|
||||
|
||||
func (prm *prmMatchExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now.
|
||||
if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) {
|
||||
return false
|
||||
}
|
||||
return signature.String() == intended.String()
|
||||
}
|
||||
|
||||
// matchRepoDigestOrExactReferenceValues implements prmMatchRepoDigestOrExact.matchesDockerReference
|
||||
// using reference.Named values.
|
||||
func matchRepoDigestOrExactReferenceValues(intended, signature reference.Named) bool {
|
||||
// Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now.
|
||||
if reference.IsNameOnly(signature) {
|
||||
return false
|
||||
}
|
||||
switch intended.(type) {
|
||||
case reference.NamedTagged: // Includes the case when intended has both a tag and a digest.
|
||||
return signature.String() == intended.String()
|
||||
case reference.Canonical:
|
||||
// We don’t actually compare the manifest digest against the signature here; that happens prSignedBy.in UnparsedImage.Manifest.
|
||||
// Because UnparsedImage.Manifest verifies the intended.Digest() against the manifest, and prSignedBy verifies the signature digest against the manifest,
|
||||
// we know that signature digest matches intended.Digest() (but intended.Digest() and signature digest may use different algorithms)
|
||||
return signature.Name() == intended.Name()
|
||||
default: // !reference.IsNameOnly(intended)
|
||||
return false
|
||||
}
|
||||
}
|
||||
func (prm *prmMatchRepoDigestOrExact) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return matchRepoDigestOrExactReferenceValues(intended, signature)
|
||||
}
|
||||
|
||||
func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return signature.Name() == intended.Name()
|
||||
}
|
||||
|
||||
// parseDockerReferences converts two reference strings into parsed entities, failing on any error
|
||||
func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, error) {
|
||||
r1, err := reference.ParseNormalizedNamed(s1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r2, err := reference.ParseNormalizedNamed(s2)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return r1, r2, nil
|
||||
}
|
||||
|
||||
func (prm *prmExactReference) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseDockerReferences(prm.DockerReference, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// prm.DockerReference and signatureDockerReference should be exact; so, verify that now.
|
||||
if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) {
|
||||
return false
|
||||
}
|
||||
return signature.String() == intended.String()
|
||||
}
|
||||
|
||||
func (prm *prmExactRepository) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseDockerReferences(prm.DockerRepository, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return signature.Name() == intended.Name()
|
||||
}
|
||||
|
||||
// refMatchesPrefix returns true if ref matches prm.Prefix.
|
||||
func (prm *prmRemapIdentity) refMatchesPrefix(ref reference.Named) bool {
|
||||
name := ref.Name()
|
||||
switch {
|
||||
case len(name) < len(prm.Prefix):
|
||||
return false
|
||||
case len(name) == len(prm.Prefix):
|
||||
return name == prm.Prefix
|
||||
case len(name) > len(prm.Prefix):
|
||||
// We are matching only ref.Name(), not ref.String(), so the only separator we are
|
||||
// expecting is '/':
|
||||
// - '@' is only valid to separate a digest, i.e. not a part of ref.Name()
|
||||
// - similarly ':' to mark a tag would not be a part of ref.Name(); it can be a part of a
|
||||
// host:port domain syntax, but we don't treat that specially and require an exact match
|
||||
// of the domain.
|
||||
return strings.HasPrefix(name, prm.Prefix) && name[len(prm.Prefix)] == '/'
|
||||
default:
|
||||
panic("Internal error: impossible comparison outcome")
|
||||
}
|
||||
}
|
||||
|
||||
// remapReferencePrefix returns the result of remapping ref, if it matches prm.Prefix
|
||||
// or the original ref if it does not.
|
||||
func (prm *prmRemapIdentity) remapReferencePrefix(ref reference.Named) (reference.Named, error) {
|
||||
if !prm.refMatchesPrefix(ref) {
|
||||
return ref, nil
|
||||
}
|
||||
refString := ref.String()
|
||||
newNamedRef := strings.Replace(refString, prm.Prefix, prm.SignedPrefix, 1)
|
||||
newParsedRef, err := reference.ParseNamed(newNamedRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`error rewriting reference from "%s" to "%s": %v`, refString, newNamedRef, err)
|
||||
}
|
||||
return newParsedRef, nil
|
||||
}
|
||||
|
||||
func (prm *prmRemapIdentity) matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool {
|
||||
intended, signature, err := parseImageAndDockerReference(image, signatureDockerReference)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
intended, err = prm.remapReferencePrefix(intended)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return matchRepoDigestOrExactReferenceValues(intended, signature)
|
||||
}
|
||||
163
vendor/github.com/containers/image/v5/signature/policy_types.go
generated
vendored
Normal file
163
vendor/github.com/containers/image/v5/signature/policy_types.go
generated
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
||||
|
||||
// This defines types used to represent a signature verification policy in memory.
|
||||
// Do not use the private types directly; either parse a configuration file, or construct a Policy from PolicyRequirements
|
||||
// built using the constructor functions provided in policy_config.go.
|
||||
|
||||
package signature
|
||||
|
||||
// NOTE: Keep this in sync with docs/containers-policy.json.5.md!
|
||||
|
||||
// Policy defines requirements for considering a signature, or an image, valid.
|
||||
type Policy struct {
|
||||
// Default applies to any image which does not have a matching policy in Transports.
|
||||
// Note that this can happen even if a matching PolicyTransportScopes exists in Transports
|
||||
// if the image matches none of the scopes.
|
||||
Default PolicyRequirements `json:"default"`
|
||||
Transports map[string]PolicyTransportScopes `json:"transports"`
|
||||
}
|
||||
|
||||
// PolicyTransportScopes defines policies for images for a specific transport,
|
||||
// for various scopes, the map keys.
|
||||
// Scopes are defined by the transport (types.ImageReference.PolicyConfigurationIdentity etc.);
|
||||
// there is one scope precisely matching to a single image, and namespace scopes as prefixes
|
||||
// of the single-image scope. (e.g. hostname[/zero[/or[/more[/namespaces[/individualimage]]]]])
|
||||
// The empty scope, if exists, is considered a parent namespace of all other scopes.
|
||||
// Most specific scope wins, duplication is prohibited (hard failure).
|
||||
type PolicyTransportScopes map[string]PolicyRequirements
|
||||
|
||||
// PolicyRequirements is a set of requirements applying to a set of images; each of them must be satisfied (though perhaps each by a different signature).
|
||||
// Must not be empty, frequently will only contain a single element.
|
||||
type PolicyRequirements []PolicyRequirement
|
||||
|
||||
// PolicyRequirement is a rule which must be satisfied by at least one of the signatures of an image.
|
||||
// The type is public, but its definition is private.
|
||||
|
||||
// prCommon is the common type field in a JSON encoding of PolicyRequirement.
|
||||
type prCommon struct {
|
||||
Type prTypeIdentifier `json:"type"`
|
||||
}
|
||||
|
||||
// prTypeIdentifier is string designating a kind of a PolicyRequirement.
|
||||
type prTypeIdentifier string
|
||||
|
||||
const (
|
||||
prTypeInsecureAcceptAnything prTypeIdentifier = "insecureAcceptAnything"
|
||||
prTypeReject prTypeIdentifier = "reject"
|
||||
prTypeSignedBy prTypeIdentifier = "signedBy"
|
||||
prTypeSignedBaseLayer prTypeIdentifier = "signedBaseLayer"
|
||||
)
|
||||
|
||||
// prInsecureAcceptAnything is a PolicyRequirement with type = prTypeInsecureAcceptAnything:
|
||||
// every image is allowed to run.
|
||||
// Note that because PolicyRequirements are implicitly ANDed, this is necessary only if it is the only rule (to make the list non-empty and the policy explicit).
|
||||
// NOTE: This allows the image to run; it DOES NOT consider the signature verified (per IsSignatureAuthorAccepted).
|
||||
// FIXME? Better name?
|
||||
type prInsecureAcceptAnything struct {
|
||||
prCommon
|
||||
}
|
||||
|
||||
// prReject is a PolicyRequirement with type = prTypeReject: every image is rejected.
|
||||
type prReject struct {
|
||||
prCommon
|
||||
}
|
||||
|
||||
// prSignedBy is a PolicyRequirement with type = prTypeSignedBy: the image is signed by trusted keys for a specified identity
|
||||
type prSignedBy struct {
|
||||
prCommon
|
||||
|
||||
// KeyType specifies what kind of key reference KeyPath/KeyData is.
|
||||
// Acceptable values are “GPGKeys” | “signedByGPGKeys” “X.509Certificates” | “signedByX.509CAs”
|
||||
// FIXME: eventually also support GPGTOFU, X.509TOFU, with KeyPath only
|
||||
KeyType sbKeyType `json:"keyType"`
|
||||
|
||||
// KeyPath is a pathname to a local file containing the trusted key(s). Exactly one of KeyPath and KeyData must be specified.
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
// KeyData contains the trusted key(s), base64-encoded. Exactly one of KeyPath and KeyData must be specified.
|
||||
KeyData []byte `json:"keyData,omitempty"`
|
||||
|
||||
// SignedIdentity specifies what image identity the signature must be claiming about the image.
|
||||
// Defaults to "match-exact" if not specified.
|
||||
SignedIdentity PolicyReferenceMatch `json:"signedIdentity"`
|
||||
}
|
||||
|
||||
// sbKeyType are the allowed values for prSignedBy.KeyType
|
||||
type sbKeyType string
|
||||
|
||||
const (
|
||||
// SBKeyTypeGPGKeys refers to keys contained in a GPG keyring
|
||||
SBKeyTypeGPGKeys sbKeyType = "GPGKeys"
|
||||
// SBKeyTypeSignedByGPGKeys refers to keys signed by keys in a GPG keyring
|
||||
SBKeyTypeSignedByGPGKeys sbKeyType = "signedByGPGKeys"
|
||||
// SBKeyTypeX509Certificates refers to keys in a set of X.509 certificates
|
||||
// FIXME: PEM, DER?
|
||||
SBKeyTypeX509Certificates sbKeyType = "X509Certificates"
|
||||
// SBKeyTypeSignedByX509CAs refers to keys signed by one of the X.509 CAs
|
||||
// FIXME: PEM, DER?
|
||||
SBKeyTypeSignedByX509CAs sbKeyType = "signedByX509CAs"
|
||||
)
|
||||
|
||||
// prSignedBaseLayer is a PolicyRequirement with type = prSignedBaseLayer: the image has a specified, correctly signed, base image.
|
||||
type prSignedBaseLayer struct {
|
||||
prCommon
|
||||
// BaseLayerIdentity specifies the base image to look for. "match-exact" is rejected, "match-repository" is unlikely to be useful.
|
||||
BaseLayerIdentity PolicyReferenceMatch `json:"baseLayerIdentity"`
|
||||
}
|
||||
|
||||
// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
|
||||
// The type is public, but its implementation is private.
|
||||
|
||||
// prmCommon is the common type field in a JSON encoding of PolicyReferenceMatch.
|
||||
type prmCommon struct {
|
||||
Type prmTypeIdentifier `json:"type"`
|
||||
}
|
||||
|
||||
// prmTypeIdentifier is string designating a kind of a PolicyReferenceMatch.
|
||||
type prmTypeIdentifier string
|
||||
|
||||
const (
|
||||
prmTypeMatchExact prmTypeIdentifier = "matchExact"
|
||||
prmTypeMatchRepoDigestOrExact prmTypeIdentifier = "matchRepoDigestOrExact"
|
||||
prmTypeMatchRepository prmTypeIdentifier = "matchRepository"
|
||||
prmTypeExactReference prmTypeIdentifier = "exactReference"
|
||||
prmTypeExactRepository prmTypeIdentifier = "exactRepository"
|
||||
prmTypeRemapIdentity prmTypeIdentifier = "remapIdentity"
|
||||
)
|
||||
|
||||
// prmMatchExact is a PolicyReferenceMatch with type = prmMatchExact: the two references must match exactly.
|
||||
type prmMatchExact struct {
|
||||
prmCommon
|
||||
}
|
||||
|
||||
// prmMatchRepoDigestOrExact is a PolicyReferenceMatch with type = prmMatchExactOrDigest: the two references must match exactly,
|
||||
// except that digest references are also accepted if the repository name matches (regardless of tag/digest) and the signature applies to the referenced digest
|
||||
type prmMatchRepoDigestOrExact struct {
|
||||
prmCommon
|
||||
}
|
||||
|
||||
// prmMatchRepository is a PolicyReferenceMatch with type = prmMatchRepository: the two references must use the same repository, may differ in the tag.
|
||||
type prmMatchRepository struct {
|
||||
prmCommon
|
||||
}
|
||||
|
||||
// prmExactReference is a PolicyReferenceMatch with type = prmExactReference: matches a specified reference exactly.
|
||||
type prmExactReference struct {
|
||||
prmCommon
|
||||
DockerReference string `json:"dockerReference"`
|
||||
}
|
||||
|
||||
// prmExactRepository is a PolicyReferenceMatch with type = prmExactRepository: matches a specified repository, with any tag.
|
||||
type prmExactRepository struct {
|
||||
prmCommon
|
||||
DockerRepository string `json:"dockerRepository"`
|
||||
}
|
||||
|
||||
// prmRemapIdentity is a PolicyReferenceMatch with type = prmRemapIdentity: like prmMatchRepoDigestOrExact,
|
||||
// except that a namespace (at least a host:port, at most a single repository) is substituted before matching the two references.
|
||||
type prmRemapIdentity struct {
|
||||
prmCommon
|
||||
Prefix string `json:"prefix"`
|
||||
SignedPrefix string `json:"signedPrefix"`
|
||||
// Possibly let the users make a choice for tag/digest matching behavior
|
||||
// similar to prmMatchExact/prmMatchRepository?
|
||||
}
|
||||
287
vendor/github.com/containers/image/v5/signature/signature.go
generated
vendored
Normal file
287
vendor/github.com/containers/image/v5/signature/signature.go
generated
vendored
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
||||
|
||||
// NOTE: Keep this in sync with docs/atomic-signature.md and docs/atomic-signature-embedded.json!
|
||||
|
||||
package signature
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/version"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
signatureType = "atomic container signature"
|
||||
)
|
||||
|
||||
// InvalidSignatureError is returned when parsing an invalid signature.
|
||||
type InvalidSignatureError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err InvalidSignatureError) Error() string {
|
||||
return err.msg
|
||||
}
|
||||
|
||||
// Signature is a parsed content of a signature.
|
||||
// The only way to get this structure from a blob should be as a return value from a successful call to verifyAndExtractSignature below.
|
||||
type Signature struct {
|
||||
DockerManifestDigest digest.Digest
|
||||
DockerReference string // FIXME: more precise type?
|
||||
}
|
||||
|
||||
// untrustedSignature is a parsed content of a signature.
|
||||
type untrustedSignature struct {
|
||||
UntrustedDockerManifestDigest digest.Digest
|
||||
UntrustedDockerReference string // FIXME: more precise type?
|
||||
UntrustedCreatorID *string
|
||||
// This is intentionally an int64; the native JSON float64 type would allow to represent _some_ sub-second precision,
|
||||
// but not nearly enough (with current timestamp values, a single unit in the last place is on the order of hundreds of nanoseconds).
|
||||
// So, this is explicitly an int64, and we reject fractional values. If we did need more precise timestamps eventually,
|
||||
// we would add another field, UntrustedTimestampNS int64.
|
||||
UntrustedTimestamp *int64
|
||||
}
|
||||
|
||||
// UntrustedSignatureInformation is information available in an untrusted signature.
|
||||
// This may be useful when debugging signature verification failures,
|
||||
// or when managing a set of signatures on a single image.
|
||||
//
|
||||
// WARNING: Do not use the contents of this for ANY security decisions,
|
||||
// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
|
||||
// There is NO REASON to expect the values to be correct, or not intentionally misleading
|
||||
// (including things like “✅ Verified by $authority”)
|
||||
type UntrustedSignatureInformation struct {
|
||||
UntrustedDockerManifestDigest digest.Digest
|
||||
UntrustedDockerReference string // FIXME: more precise type?
|
||||
UntrustedCreatorID *string
|
||||
UntrustedTimestamp *time.Time
|
||||
UntrustedShortKeyIdentifier string
|
||||
}
|
||||
|
||||
// newUntrustedSignature returns an untrustedSignature object with
|
||||
// the specified primary contents and appropriate metadata.
|
||||
func newUntrustedSignature(dockerManifestDigest digest.Digest, dockerReference string) untrustedSignature {
|
||||
// Use intermediate variables for these values so that we can take their addresses.
|
||||
// Golang guarantees that they will have a new address on every execution.
|
||||
creatorID := "atomic " + version.Version
|
||||
timestamp := time.Now().Unix()
|
||||
return untrustedSignature{
|
||||
UntrustedDockerManifestDigest: dockerManifestDigest,
|
||||
UntrustedDockerReference: dockerReference,
|
||||
UntrustedCreatorID: &creatorID,
|
||||
UntrustedTimestamp: ×tamp,
|
||||
}
|
||||
}
|
||||
|
||||
// Compile-time check that untrustedSignature implements json.Marshaler
|
||||
var _ json.Marshaler = (*untrustedSignature)(nil)
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (s untrustedSignature) MarshalJSON() ([]byte, error) {
|
||||
if s.UntrustedDockerManifestDigest == "" || s.UntrustedDockerReference == "" {
|
||||
return nil, errors.New("Unexpected empty signature content")
|
||||
}
|
||||
critical := map[string]interface{}{
|
||||
"type": signatureType,
|
||||
"image": map[string]string{"docker-manifest-digest": s.UntrustedDockerManifestDigest.String()},
|
||||
"identity": map[string]string{"docker-reference": s.UntrustedDockerReference},
|
||||
}
|
||||
optional := map[string]interface{}{}
|
||||
if s.UntrustedCreatorID != nil {
|
||||
optional["creator"] = *s.UntrustedCreatorID
|
||||
}
|
||||
if s.UntrustedTimestamp != nil {
|
||||
optional["timestamp"] = *s.UntrustedTimestamp
|
||||
}
|
||||
signature := map[string]interface{}{
|
||||
"critical": critical,
|
||||
"optional": optional,
|
||||
}
|
||||
return json.Marshal(signature)
|
||||
}
|
||||
|
||||
// Compile-time check that untrustedSignature implements json.Unmarshaler
|
||||
var _ json.Unmarshaler = (*untrustedSignature)(nil)
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (s *untrustedSignature) UnmarshalJSON(data []byte) error {
|
||||
err := s.strictUnmarshalJSON(data)
|
||||
if err != nil {
|
||||
if formatErr, ok := err.(jsonFormatError); ok {
|
||||
err = InvalidSignatureError{msg: formatErr.Error()}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 critical, optional json.RawMessage
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(data, map[string]interface{}{
|
||||
"critical": &critical,
|
||||
"optional": &optional,
|
||||
}); 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 gotTimestamp {
|
||||
intTimestamp := int64(timestamp)
|
||||
if float64(intTimestamp) != timestamp {
|
||||
return InvalidSignatureError{msg: "Field optional.timestamp is not is not an integer"}
|
||||
}
|
||||
s.UntrustedTimestamp = &intTimestamp
|
||||
}
|
||||
|
||||
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)}
|
||||
}
|
||||
|
||||
var digestString string
|
||||
if err := paranoidUnmarshalJSONObjectExactFields(image, map[string]interface{}{
|
||||
"docker-manifest-digest": &digestString,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
s.UntrustedDockerManifestDigest = digest.Digest(digestString)
|
||||
|
||||
return paranoidUnmarshalJSONObjectExactFields(identity, map[string]interface{}{
|
||||
"docker-reference": &s.UntrustedDockerReference,
|
||||
})
|
||||
}
|
||||
|
||||
// Sign formats the signature and returns a blob signed using mech and keyIdentity
|
||||
// (If it seems surprising that this is a method on untrustedSignature, note that there
|
||||
// isn’t a good reason to think that a key used by the user is trusted by any component
|
||||
// of the system just because it is a private key — actually the presence of a private key
|
||||
// on the system increases the likelihood of an a successful attack on that private key
|
||||
// on that particular system.)
|
||||
func (s untrustedSignature) sign(mech SigningMechanism, keyIdentity string, passphrase string) ([]byte, error) {
|
||||
json, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if newMech, ok := mech.(signingMechanismWithPassphrase); ok {
|
||||
return newMech.SignWithPassphrase(json, keyIdentity, passphrase)
|
||||
}
|
||||
|
||||
if passphrase != "" {
|
||||
return nil, errors.New("signing mechanism does not support passphrases")
|
||||
}
|
||||
|
||||
return mech.Sign(json, keyIdentity)
|
||||
}
|
||||
|
||||
// signatureAcceptanceRules specifies how to decide whether an untrusted signature is acceptable.
|
||||
// We centralize the actual parsing and data extraction in verifyAndExtractSignature; this supplies
|
||||
// the policy. We use an object instead of supplying func parameters to verifyAndExtractSignature
|
||||
// because the functions have the same or similar types, so there is a risk of exchanging the functions;
|
||||
// named members of this struct are more explicit.
|
||||
type signatureAcceptanceRules struct {
|
||||
validateKeyIdentity func(string) error
|
||||
validateSignedDockerReference func(string) error
|
||||
validateSignedDockerManifestDigest func(digest.Digest) error
|
||||
}
|
||||
|
||||
// verifyAndExtractSignature verifies that unverifiedSignature has been signed, and that its principal components
|
||||
// match expected values, both as specified by rules, and returns it
|
||||
func verifyAndExtractSignature(mech SigningMechanism, unverifiedSignature []byte, rules signatureAcceptanceRules) (*Signature, error) {
|
||||
signed, keyIdentity, err := mech.Verify(unverifiedSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rules.validateKeyIdentity(keyIdentity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var unmatchedSignature untrustedSignature
|
||||
if err := json.Unmarshal(signed, &unmatchedSignature); err != nil {
|
||||
return nil, InvalidSignatureError{msg: err.Error()}
|
||||
}
|
||||
if err := rules.validateSignedDockerManifestDigest(unmatchedSignature.UntrustedDockerManifestDigest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rules.validateSignedDockerReference(unmatchedSignature.UntrustedDockerReference); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// signatureAcceptanceRules have accepted this value.
|
||||
return &Signature{
|
||||
DockerManifestDigest: unmatchedSignature.UntrustedDockerManifestDigest,
|
||||
DockerReference: unmatchedSignature.UntrustedDockerReference,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUntrustedSignatureInformationWithoutVerifying extracts information available in an untrusted signature,
|
||||
// WITHOUT doing any cryptographic verification.
|
||||
// This may be useful when debugging signature verification failures,
|
||||
// or when managing a set of signatures on a single image.
|
||||
//
|
||||
// WARNING: Do not use the contents of this for ANY security decisions,
|
||||
// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
|
||||
// There is NO REASON to expect the values to be correct, or not intentionally misleading
|
||||
// (including things like “✅ Verified by $authority”)
|
||||
func GetUntrustedSignatureInformationWithoutVerifying(untrustedSignatureBytes []byte) (*UntrustedSignatureInformation, error) {
|
||||
// NOTE: This should eventually do format autodetection.
|
||||
mech, _, err := NewEphemeralGPGSigningMechanism([]byte{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mech.Close()
|
||||
|
||||
untrustedContents, shortKeyIdentifier, err := mech.UntrustedSignatureContents(untrustedSignatureBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var untrustedDecodedContents untrustedSignature
|
||||
if err := json.Unmarshal(untrustedContents, &untrustedDecodedContents); err != nil {
|
||||
return nil, InvalidSignatureError{msg: err.Error()}
|
||||
}
|
||||
|
||||
var timestamp *time.Time // = nil
|
||||
if untrustedDecodedContents.UntrustedTimestamp != nil {
|
||||
ts := time.Unix(*untrustedDecodedContents.UntrustedTimestamp, 0)
|
||||
timestamp = &ts
|
||||
}
|
||||
return &UntrustedSignatureInformation{
|
||||
UntrustedDockerManifestDigest: untrustedDecodedContents.UntrustedDockerManifestDigest,
|
||||
UntrustedDockerReference: untrustedDecodedContents.UntrustedDockerReference,
|
||||
UntrustedCreatorID: untrustedDecodedContents.UntrustedCreatorID,
|
||||
UntrustedTimestamp: timestamp,
|
||||
UntrustedShortKeyIdentifier: shortKeyIdentifier,
|
||||
}, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue