From 9ececa14ba860c9934d3cd8d3e704e53e828e22b Mon Sep 17 00:00:00 2001 From: Josh Hawn <josh.hawn@docker.com> Date: Tue, 17 Mar 2015 23:45:30 -0700 Subject: [PATCH] Add verification of image manifest digests Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn) --- graph/manifest.go | 29 ++++++++++++++++++++++++++++- graph/pull.go | 7 +++++-- graph/push.go | 7 ++----- registry/session_v2.go | 30 +++++++++++++++++++++++++----- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/graph/manifest.go b/graph/manifest.go index 75c2d9060b..3b1d825576 100644 --- a/graph/manifest.go +++ b/graph/manifest.go @@ -6,8 +6,10 @@ import ( "fmt" log "github.com/Sirupsen/logrus" + "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/registry" + "github.com/docker/docker/utils" "github.com/docker/libtrust" ) @@ -16,7 +18,7 @@ import ( // contains no signatures by a trusted key for the name in the manifest, the // image is not considered verified. The parsed manifest object and a boolean // for whether the manifest is verified is returned. -func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte) (*registry.ManifestData, bool, error) { +func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) { sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures") if err != nil { return nil, false, fmt.Errorf("error parsing payload: %s", err) @@ -32,6 +34,31 @@ func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte) (*regi return nil, false, fmt.Errorf("error retrieving payload: %s", err) } + var manifestDigest digest.Digest + + if dgst != "" { + manifestDigest, err = digest.ParseDigest(dgst) + if err != nil { + return nil, false, fmt.Errorf("invalid manifest digest from registry: %s", err) + } + + dgstVerifier, err := digest.NewDigestVerifier(manifestDigest) + if err != nil { + return nil, false, fmt.Errorf("unable to verify manifest digest from registry: %s", err) + } + + dgstVerifier.Write(payload) + + if !dgstVerifier.Verified() { + computedDigest, _ := digest.FromBytes(payload) + return nil, false, fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", manifestDigest, computedDigest) + } + } + + if utils.DigestReference(ref) && ref != manifestDigest.String() { + return nil, false, fmt.Errorf("mismatching image manifest digest: got %q, expected %q", manifestDigest, ref) + } + var manifest registry.ManifestData if err := json.Unmarshal(payload, &manifest); err != nil { return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err) diff --git a/graph/pull.go b/graph/pull.go index db46e519b1..c01152a248 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -430,12 +430,15 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { log.Debugf("Pulling tag from V2 registry: %q", tag) + manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth) if err != nil { return false, err } - manifest, verified, err := s.loadManifest(eng, manifestBytes) + // loadManifest ensures that the manifest payload has the expected digest + // if the tag is a digest reference. + manifest, verified, err := s.loadManifest(eng, manifestBytes, manifestDigest, tag) if err != nil { return false, fmt.Errorf("error verifying manifest: %s", err) } @@ -605,7 +608,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri out.Write(sf.FormatStatus(utils.ImageReference(repoInfo.CanonicalName, tag), "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.")) } - if len(manifestDigest) > 0 { + if manifestDigest != "" { out.Write(sf.FormatStatus("", "Digest: %s", manifestDigest)) } diff --git a/graph/push.go b/graph/push.go index 7bc79dc99e..5a4f0d1de9 100644 --- a/graph/push.go +++ b/graph/push.go @@ -1,7 +1,6 @@ package graph import ( - "bytes" "crypto/sha256" "encoding/json" "errors" @@ -432,14 +431,12 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o log.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID()) // push the manifest - digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader(signedBody), auth) + digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, signedBody, mBytes, auth) if err != nil { return err } - if len(digest) > 0 { - out.Write(sf.FormatStatus("", "Digest: %s", digest)) - } + out.Write(sf.FormatStatus("", "Digest: %s", digest)) } return nil } diff --git a/registry/session_v2.go b/registry/session_v2.go index c5bee11bc6..ec628ad115 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -1,6 +1,7 @@ package registry import ( + "bytes" "encoding/json" "fmt" "io" @@ -8,6 +9,7 @@ import ( "strconv" log "github.com/Sirupsen/logrus" + "github.com/docker/distribution/digest" "github.com/docker/docker/registry/v2" "github.com/docker/docker/utils" ) @@ -95,11 +97,12 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) } - buf, err := ioutil.ReadAll(res.Body) + manifestBytes, err := ioutil.ReadAll(res.Body) if err != nil { return nil, "", fmt.Errorf("Error while reading the http response: %s", err) } - return buf, res.Header.Get(DockerDigestHeader), nil + + return manifestBytes, res.Header.Get(DockerDigestHeader), nil } // - Succeeded to head image blob (already exists) @@ -263,7 +266,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string } // Finally Push the (signed) manifest of the blobs we've just pushed -func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, manifestRdr io.Reader, auth *RequestAuthorization) (string, error) { +func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, signedManifest, rawManifest []byte, auth *RequestAuthorization) (digest.Digest, error) { routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) if err != nil { return "", err @@ -271,7 +274,7 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, ma method := "PUT" log.Debugf("[registry] Calling %q %s", method, routeURL) - req, err := r.reqFactory.NewRequest(method, routeURL, manifestRdr) + req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest)) if err != nil { return "", err } @@ -297,7 +300,24 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, ma return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) } - return res.Header.Get(DockerDigestHeader), nil + hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader)) + if err != nil { + return "", fmt.Errorf("invalid manifest digest from registry: %s", err) + } + + dgstVerifier, err := digest.NewDigestVerifier(hdrDigest) + if err != nil { + return "", fmt.Errorf("invalid manifest digest from registry: %s", err) + } + + dgstVerifier.Write(rawManifest) + + if !dgstVerifier.Verified() { + computedDigest, _ := digest.FromBytes(rawManifest) + return "", fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", hdrDigest, computedDigest) + } + + return hdrDigest, nil } type remoteTags struct {