diff --git a/storage/layer.go b/storage/layer.go index dc6b34228..2ad913147 100644 --- a/storage/layer.go +++ b/storage/layer.go @@ -53,9 +53,6 @@ type LayerUpload interface { } var ( - // ErrLayerUnknown returned when layer cannot be found. - ErrLayerUnknown = fmt.Errorf("unknown layer") - // ErrLayerExists returned when layer already exists ErrLayerExists = fmt.Errorf("layer exists") @@ -65,9 +62,6 @@ var ( // ErrLayerUploadUnknown returned when upload is not found. ErrLayerUploadUnknown = fmt.Errorf("layer upload unknown") - // ErrLayerInvalidDigest returned when tarsum check fails. - ErrLayerInvalidDigest = fmt.Errorf("invalid layer digest") - // ErrLayerInvalidLength returned when length check fails. ErrLayerInvalidLength = fmt.Errorf("invalid layer length") @@ -75,3 +69,21 @@ var ( // Layer or LayerUpload. ErrLayerClosed = fmt.Errorf("layer closed") ) + +// ErrUnknownLayer returned when layer cannot be found. +type ErrUnknownLayer struct { + FSLayer FSLayer +} + +func (err ErrUnknownLayer) Error() string { + return fmt.Sprintf("unknown layer %v", err.FSLayer.BlobSum) +} + +// ErrLayerInvalidDigest returned when tarsum check fails. +type ErrLayerInvalidDigest struct { + FSLayer FSLayer +} + +func (err ErrLayerInvalidDigest) Error() string { + return fmt.Sprintf("invalid digest for referenced layer: %v", err.FSLayer.BlobSum) +} diff --git a/storage/layer_test.go b/storage/layer_test.go index f04115e74..ba92d2deb 100644 --- a/storage/layer_test.go +++ b/storage/layer_test.go @@ -169,11 +169,13 @@ func TestSimpleLayerRead(t *testing.T) { t.Fatalf("error expected fetching unknown layer") } - if err != ErrLayerUnknown { - t.Fatalf("unexpected error fetching non-existent layer: %v", err) - } else { + switch err.(type) { + case ErrUnknownLayer: err = nil + default: + t.Fatalf("unexpected error fetching non-existent layer: %v", err) } + randomLayerDigest, err := writeTestLayer(driver, ls.pathMapper, imageName, dgst, randomLayerReader) if err != nil { t.Fatalf("unexpected error writing test layer: %v", err) diff --git a/storage/layerstore.go b/storage/layerstore.go index 2544ec4fb..d731a5b8b 100644 --- a/storage/layerstore.go +++ b/storage/layerstore.go @@ -19,7 +19,8 @@ func (ls *layerStore) Exists(name string, digest digest.Digest) (bool, error) { _, err := ls.Fetch(name, digest) if err != nil { - if err == ErrLayerUnknown { + switch err.(type) { + case ErrUnknownLayer: return false, nil } @@ -34,7 +35,7 @@ func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) { if err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError: - return nil, ErrLayerUnknown + return nil, ErrUnknownLayer{FSLayer{BlobSum: digest}} default: return nil, err } @@ -44,7 +45,7 @@ func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) { if err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError: - return nil, ErrLayerUnknown + return nil, ErrUnknownLayer{FSLayer{BlobSum: digest}} default: return nil, err } diff --git a/storage/layerupload.go b/storage/layerupload.go index e0336a0f9..de1a894ba 100644 --- a/storage/layerupload.go +++ b/storage/layerupload.go @@ -275,7 +275,7 @@ func (luc *layerUploadController) validateLayer(fp layerFile, size int64, dgst d } if !digestVerifier.Verified() { - return "", ErrLayerInvalidDigest + return "", ErrLayerInvalidDigest{FSLayer{BlobSum: dgst}} } return dgst, nil diff --git a/storage/manifest.go b/storage/manifest.go index 9921fbea6..8b288625b 100644 --- a/storage/manifest.go +++ b/storage/manifest.go @@ -1,23 +1,48 @@ package storage import ( + "crypto/x509" "encoding/json" "fmt" + "strings" "github.com/docker/libtrust" "github.com/docker/docker-registry/digest" ) -var ( - // ErrManifestUnknown is returned if the manifest is not known by the - // registry. - ErrManifestUnknown = fmt.Errorf("unknown manifest") +// ErrUnknownManifest is returned if the manifest is not known by the +// registry. +type ErrUnknownManifest struct { + Name string + Tag string +} - // ErrManifestUnverified is returned when the registry is unable to verify - // the manifest. - ErrManifestUnverified = fmt.Errorf("unverified manifest") -) +func (err ErrUnknownManifest) Error() string { + return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag) +} + +// ErrManifestUnverified is returned when the registry is unable to verify +// the manifest. +type ErrManifestUnverified struct{} + +func (ErrManifestUnverified) Error() string { + return fmt.Sprintf("unverified manifest") +} + +// ErrManifestVerification provides a type to collect errors encountered +// during manifest verification. Currently, it accepts errors of all types, +// but it may be narrowed to those involving manifest verification. +type ErrManifestVerification []error + +func (errs ErrManifestVerification) Error() string { + var parts []string + for _, err := range errs { + parts = append(parts, err.Error()) + } + + return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ",")) +} // Versioned provides a struct with just the manifest schemaVersion. Incoming // content with unknown schema version can be decoded against this struct to @@ -78,7 +103,37 @@ func (m *Manifest) Sign(pk libtrust.PrivateKey) (*SignedManifest, error) { }, nil } -// SignedManifest provides an envelope for +// SignWithChain signs the manifest with the given private key and x509 chain. +// The public key of the first element in the chain must be the public key +// corresponding with the sign key. +func (m *Manifest) SignWithChain(key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) { + p, err := json.Marshal(m) + if err != nil { + return nil, err + } + + js, err := libtrust.NewJSONSignature(p) + if err != nil { + return nil, err + } + + if err := js.SignWithChain(key, chain); err != nil { + return nil, err + } + + pretty, err := js.PrettySignature("signatures") + if err != nil { + return nil, err + } + + return &SignedManifest{ + Manifest: *m, + Raw: pretty, + }, nil +} + +// SignedManifest provides an envelope for a signed image manifest, including +// the format sensitive raw bytes. It contains fields to type SignedManifest struct { Manifest @@ -88,28 +143,51 @@ type SignedManifest struct { Raw []byte `json:"-"` } +// Verify verifies the signature of the signed manifest returning the public +// keys used during signing. +func (sm *SignedManifest) Verify() ([]libtrust.PublicKey, error) { + js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures") + if err != nil { + return nil, err + } + + return js.Verify() +} + +// VerifyChains verifies the signature of the signed manifest against the +// certificate pool returning the list of verified chains. Signatures without +// an x509 chain are not checked. +func (sm *SignedManifest) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) { + js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures") + if err != nil { + return nil, err + } + + return js.VerifyChains(ca) +} + // UnmarshalJSON populates a new ImageManifest struct from JSON data. -func (m *SignedManifest) UnmarshalJSON(b []byte) error { +func (sm *SignedManifest) UnmarshalJSON(b []byte) error { var manifest Manifest if err := json.Unmarshal(b, &manifest); err != nil { return err } - m.Manifest = manifest - m.Raw = b + sm.Manifest = manifest + sm.Raw = b return nil } // MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner // contents. -func (m *SignedManifest) MarshalJSON() ([]byte, error) { - if len(m.Raw) > 0 { - return m.Raw, nil +func (sm *SignedManifest) MarshalJSON() ([]byte, error) { + if len(sm.Raw) > 0 { + return sm.Raw, nil } // If the raw data is not available, just dump the inner content. - return json.Marshal(&m.Manifest) + return json.Marshal(&sm.Manifest) } // FSLayer is a container struct for BlobSums defined in an image manifest diff --git a/storage/manifest_test.go b/storage/manifest_test.go index c96c1dec8..e45179437 100644 --- a/storage/manifest_test.go +++ b/storage/manifest_test.go @@ -33,8 +33,13 @@ func TestManifestStorage(t *testing.T) { t.Fatalf("manifest should not exist") } - if _, err := ms.Get(name, tag); err != ErrManifestUnknown { - t.Fatalf("expected manifest unknown error: %v != %v", err, ErrManifestUnknown) + if _, err := ms.Get(name, tag); true { + switch err.(type) { + case ErrUnknownManifest: + break + default: + t.Fatalf("expected manifest unknown error: %#v", err) + } } manifest := Manifest{ diff --git a/storage/manifeststore.go b/storage/manifeststore.go index 1b76c8c03..707311b89 100644 --- a/storage/manifeststore.go +++ b/storage/manifeststore.go @@ -4,9 +4,8 @@ import ( "encoding/json" "fmt" - "github.com/docker/libtrust" - "github.com/docker/docker-registry/storagedriver" + "github.com/docker/libtrust" ) type manifestStore struct { @@ -45,7 +44,7 @@ func (ms *manifestStore) Get(name, tag string) (*SignedManifest, error) { if err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError: - return nil, ErrManifestUnknown + return nil, ErrUnknownManifest{Name: name, Tag: tag} default: return nil, err } @@ -73,13 +72,28 @@ func (ms *manifestStore) Put(name, tag string, manifest *SignedManifest) error { return err } - // TODO(stevvooe): Should we get manifest first? + // TODO(stevvooe): Should we get old manifest first? Perhaps, write, then + // move to ensure a valid manifest? return ms.driver.PutContent(p, manifest.Raw) } func (ms *manifestStore) Delete(name, tag string) error { - panic("not implemented") + p, err := ms.path(name, tag) + if err != nil { + return err + } + + if err := ms.driver.Delete(p); err != nil { + switch err := err.(type) { + case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError: + return ErrUnknownManifest{Name: name, Tag: tag} + default: + return err + } + } + + return nil } func (ms *manifestStore) path(name, tag string) (string, error) { @@ -90,6 +104,12 @@ func (ms *manifestStore) path(name, tag string) (string, error) { } func (ms *manifestStore) verifyManifest(name, tag string, manifest *SignedManifest) error { + // TODO(stevvooe): This verification is present here, but this needs to be + // lifted out of the storage infrastructure and moved into a package + // oriented towards defining verifiers and reporting them with + // granularity. + + var errs ErrManifestVerification if manifest.Name != name { return fmt.Errorf("name does not match manifest name") } @@ -98,37 +118,34 @@ func (ms *manifestStore) verifyManifest(name, tag string, manifest *SignedManife return fmt.Errorf("tag does not match manifest tag") } - var errs []error + // TODO(stevvooe): These pubkeys need to be checked with either Verify or + // VerifyWithChains. We need to define the exact source of the CA. + // Perhaps, its a configuration value injected into manifest store. + + if _, err := manifest.Verify(); err != nil { + switch err { + case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey: + errs = append(errs, ErrManifestUnverified{}) + default: + errs = append(errs, err) + } + } for _, fsLayer := range manifest.FSLayers { exists, err := ms.layerService.Exists(name, fsLayer.BlobSum) if err != nil { - // TODO(stevvooe): Need to store information about missing blob. errs = append(errs, err) } if !exists { - errs = append(errs, fmt.Errorf("missing layer %v", fsLayer.BlobSum)) + errs = append(errs, ErrUnknownLayer{FSLayer: fsLayer}) } } if len(errs) != 0 { // TODO(stevvooe): These need to be recoverable by a caller. - return fmt.Errorf("missing layers: %v", errs) + return errs } - js, err := libtrust.ParsePrettySignature(manifest.Raw, "signatures") - if err != nil { - return err - } - - _, err = js.Verify() // These pubkeys need to be checked. - if err != nil { - return err - } - - // TODO(sday): Pubkey checks need to go here. This where things get fancy. - // Perhaps, an injected service would reduce coupling here. - return nil }