caee4a99c9
Vendor updated containers/image and containers/storage, along with any new dependencies they drag in, and updated versions of other dependencies that happen to get pulled in. github.com/coreos/go-systemd/daemon/SdNotify() now takes a boolean to control whether or not it unsets the NOTIFY_SOCKET variable from the calling process's environment. Adapt. github.com/opencontainers/runtime-tools/generate/Generator.AddProcessEnv() now takes the environment variable name and value as two arguments, not one. Adapt. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
193 lines
5.9 KiB
Go
193 lines
5.9 KiB
Go
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
|
|
|
package signature
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/containers/image/version"
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
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.
|
|
type Signature struct {
|
|
DockerManifestDigest digest.Digest
|
|
DockerReference string // FIXME: more precise type?
|
|
}
|
|
|
|
// Wrap signature to add to it some methods which we don't want to make public.
|
|
type privateSignature struct {
|
|
Signature
|
|
}
|
|
|
|
// Compile-time check that privateSignature implements json.Marshaler
|
|
var _ json.Marshaler = (*privateSignature)(nil)
|
|
|
|
// MarshalJSON implements the json.Marshaler interface.
|
|
func (s privateSignature) MarshalJSON() ([]byte, error) {
|
|
return s.marshalJSONWithVariables(time.Now().UTC().Unix(), "atomic "+version.Version)
|
|
}
|
|
|
|
// Implementation of MarshalJSON, with a caller-chosen values of the variable items to help testing.
|
|
func (s privateSignature) marshalJSONWithVariables(timestamp int64, creatorID string) ([]byte, error) {
|
|
if s.DockerManifestDigest == "" || s.DockerReference == "" {
|
|
return nil, errors.New("Unexpected empty signature content")
|
|
}
|
|
critical := map[string]interface{}{
|
|
"type": signatureType,
|
|
"image": map[string]string{"docker-manifest-digest": s.DockerManifestDigest.String()},
|
|
"identity": map[string]string{"docker-reference": s.DockerReference},
|
|
}
|
|
optional := map[string]interface{}{
|
|
"creator": creatorID,
|
|
"timestamp": timestamp,
|
|
}
|
|
signature := map[string]interface{}{
|
|
"critical": critical,
|
|
"optional": optional,
|
|
}
|
|
return json.Marshal(signature)
|
|
}
|
|
|
|
// Compile-time check that privateSignature implements json.Unmarshaler
|
|
var _ json.Unmarshaler = (*privateSignature)(nil)
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface
|
|
func (s *privateSignature) UnmarshalJSON(data []byte) error {
|
|
err := s.strictUnmarshalJSON(data)
|
|
if err != nil {
|
|
if _, ok := err.(jsonFormatError); ok {
|
|
err = InvalidSignatureError{msg: err.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 *privateSignature) strictUnmarshalJSON(data []byte) error {
|
|
var untyped interface{}
|
|
if err := json.Unmarshal(data, &untyped); err != nil {
|
|
return err
|
|
}
|
|
o, ok := untyped.(map[string]interface{})
|
|
if !ok {
|
|
return InvalidSignatureError{msg: "Invalid signature format"}
|
|
}
|
|
if err := validateExactMapKeys(o, "critical", "optional"); err != nil {
|
|
return err
|
|
}
|
|
|
|
c, err := mapField(o, "critical")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := validateExactMapKeys(c, "type", "image", "identity"); err != nil {
|
|
return err
|
|
}
|
|
|
|
optional, err := mapField(o, "optional")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = optional // We don't use anything from here for now.
|
|
|
|
t, err := stringField(c, "type")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if t != signatureType {
|
|
return InvalidSignatureError{msg: fmt.Sprintf("Unrecognized signature type %s", t)}
|
|
}
|
|
|
|
image, err := mapField(c, "image")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := validateExactMapKeys(image, "docker-manifest-digest"); err != nil {
|
|
return err
|
|
}
|
|
digestString, err := stringField(image, "docker-manifest-digest")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.DockerManifestDigest = digest.Digest(digestString)
|
|
|
|
identity, err := mapField(c, "identity")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := validateExactMapKeys(identity, "docker-reference"); err != nil {
|
|
return err
|
|
}
|
|
reference, err := stringField(identity, "docker-reference")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.DockerReference = reference
|
|
|
|
return nil
|
|
}
|
|
|
|
// Sign formats the signature and returns a blob signed using mech and keyIdentity
|
|
func (s privateSignature) sign(mech SigningMechanism, keyIdentity string) ([]byte, error) {
|
|
json, err := json.Marshal(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 all of the functions have the same type, 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 principial 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 privateSignature
|
|
if err := json.Unmarshal(signed, &unmatchedSignature); err != nil {
|
|
return nil, InvalidSignatureError{msg: err.Error()}
|
|
}
|
|
if err := rules.validateSignedDockerManifestDigest(unmatchedSignature.DockerManifestDigest); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rules.validateSignedDockerReference(unmatchedSignature.DockerReference); err != nil {
|
|
return nil, err
|
|
}
|
|
signature := unmatchedSignature.Signature // Policy OK.
|
|
return &signature, nil
|
|
}
|