mirror of
https://github.com/containers/skopeo.git
synced 2025-09-28 21:46:48 +00:00
No semantic change, only a reorganization: The utilities now return jsonFormatError instead of InvalidSignatureError, but their only caller maps it back.
182 lines
5.3 KiB
Go
182 lines
5.3 KiB
Go
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
|
|
|
package signature
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/projectatomic/skopeo/version"
|
|
)
|
|
|
|
const (
|
|
signatureType = "atomic container signature"
|
|
signatureCreatorID = "atomic " + version.Version
|
|
)
|
|
|
|
// 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 string // FIXME: more precise type?
|
|
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(), signatureCreatorID)
|
|
}
|
|
|
|
// 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},
|
|
"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
|
|
}
|
|
digest, err := stringField(image, "docker-manifest-digest")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.DockerManifestDigest = digest
|
|
|
|
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)
|
|
}
|
|
|
|
// verifyAndExtractSignature verifies that signature has been signed by expectedKeyIdentity
|
|
// using mech for expectedDockerReference, and returns it (without matching its contents to an image).
|
|
func verifyAndExtractSignature(mech SigningMechanism, unverifiedSignature []byte,
|
|
expectedKeyIdentity, expectedDockerReference string) (*Signature, error) {
|
|
signed, keyIdentity, err := mech.Verify(unverifiedSignature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if keyIdentity != expectedKeyIdentity {
|
|
return nil, InvalidSignatureError{msg: fmt.Sprintf("Signature by %s does not match expected fingerprint %s", keyIdentity, expectedKeyIdentity)}
|
|
}
|
|
|
|
var unmatchedSignature privateSignature
|
|
if err := json.Unmarshal(signed, &unmatchedSignature); err != nil {
|
|
return nil, InvalidSignatureError{msg: err.Error()}
|
|
}
|
|
|
|
if unmatchedSignature.DockerReference != expectedDockerReference {
|
|
return nil, InvalidSignatureError{msg: fmt.Sprintf("Docker reference %s does not match %s",
|
|
unmatchedSignature.DockerReference, expectedDockerReference)}
|
|
}
|
|
signature := unmatchedSignature.Signature // Policy OK.
|
|
return &signature, nil
|
|
}
|