mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00

To ensure manifest integrity when pulling by digest, this changeset ensures that not only the remote digest provided by the registry is verified but also that the digest provided on the command line is checked, as well. If this check fails, the pull is cancelled as with an error. Inspection also should that while layers were being verified against their digests, the error was being treated as tech preview image signing verification error. This, in fact, is not a tech preview and opens up the docker daemon to man in the middle attacks that can be avoided with the v2 registry protocol. As a matter of cleanliness, the digest package from the distribution project has been updated to latest version. There were some recent improvements in the digest package. Signed-off-by: Stephen J Day <stephen.day@docker.com>
150 lines
5 KiB
Go
150 lines
5 KiB
Go
package graph
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/distribution/digest"
|
|
"github.com/docker/docker/registry"
|
|
"github.com/docker/docker/trust"
|
|
"github.com/docker/libtrust"
|
|
)
|
|
|
|
// loadManifest loads a manifest from a byte array and verifies its content,
|
|
// returning the local digest, the manifest itself, whether or not it was
|
|
// verified. If ref is a digest, rather than a tag, this will be treated as
|
|
// the local digest. An error will be returned if the signature verification
|
|
// fails, local digest verification fails and, if provided, the remote digest
|
|
// verification fails. The boolean return will only be false without error on
|
|
// the failure of signatures trust check.
|
|
func (s *TagStore) loadManifest(manifestBytes []byte, ref string, remoteDigest digest.Digest) (digest.Digest, *registry.ManifestData, bool, error) {
|
|
sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures")
|
|
if err != nil {
|
|
return "", nil, false, fmt.Errorf("error parsing payload: %s", err)
|
|
}
|
|
|
|
keys, err := sig.Verify()
|
|
if err != nil {
|
|
return "", nil, false, fmt.Errorf("error verifying payload: %s", err)
|
|
}
|
|
|
|
payload, err := sig.Payload()
|
|
if err != nil {
|
|
return "", nil, false, fmt.Errorf("error retrieving payload: %s", err)
|
|
}
|
|
|
|
// TODO(stevvooe): It would be a lot better here to build up a stack of
|
|
// verifiers, then push the bytes one time for signatures and digests, but
|
|
// the manifests are typically small, so this optimization is not worth
|
|
// hacking this code without further refactoring.
|
|
|
|
var localDigest digest.Digest
|
|
|
|
// verify the local digest, if present in tag
|
|
if dgst, err := digest.ParseDigest(ref); err != nil {
|
|
logrus.Debugf("provided manifest reference %q is not a digest: %v", ref, err)
|
|
|
|
// we don't have a local digest, since we are working from a tag.
|
|
// Digest the payload and return that.
|
|
localDigest, err = digest.FromBytes(payload)
|
|
if err != nil {
|
|
// near impossible
|
|
logrus.Errorf("error calculating local digest during tag pull: %v", err)
|
|
return "", nil, false, err
|
|
}
|
|
} else {
|
|
// verify the manifest against local ref
|
|
verifier, err := digest.NewDigestVerifier(dgst)
|
|
if err != nil {
|
|
// There are not many ways this can go wrong: if it does, its
|
|
// fatal. Likley, the cause would be poor validation of the
|
|
// incoming reference.
|
|
return "", nil, false, fmt.Errorf("error creating verifier for local digest reference %q: %v", dgst, err)
|
|
}
|
|
|
|
if _, err := verifier.Write(payload); err != nil {
|
|
return "", nil, false, fmt.Errorf("error writing payload to local digest reference verifier: %v", err)
|
|
}
|
|
|
|
if !verifier.Verified() {
|
|
return "", nil, false, fmt.Errorf("verification against local digest reference %q failed", dgst)
|
|
}
|
|
|
|
localDigest = dgst
|
|
}
|
|
|
|
// verify against the remote digest, if available
|
|
if remoteDigest != "" {
|
|
if err := remoteDigest.Validate(); err != nil {
|
|
return "", nil, false, fmt.Errorf("error validating remote digest %q: %v", remoteDigest, err)
|
|
}
|
|
|
|
verifier, err := digest.NewDigestVerifier(remoteDigest)
|
|
if err != nil {
|
|
// There are not many ways this can go wrong: if it does, its
|
|
// fatal. Likley, the cause would be poor validation of the
|
|
// incoming reference.
|
|
return "", nil, false, fmt.Errorf("error creating verifier for remote digest %q: %v", remoteDigest, err)
|
|
}
|
|
|
|
if _, err := verifier.Write(payload); err != nil {
|
|
return "", nil, false, fmt.Errorf("error writing payload to remote digest verifier (verifier target %q): %v", remoteDigest, err)
|
|
}
|
|
|
|
if !verifier.Verified() {
|
|
return "", nil, false, fmt.Errorf("verification against remote digest %q failed", remoteDigest)
|
|
}
|
|
}
|
|
|
|
var manifest registry.ManifestData
|
|
if err := json.Unmarshal(payload, &manifest); err != nil {
|
|
return "", nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
|
|
}
|
|
if manifest.SchemaVersion != 1 {
|
|
return "", nil, false, fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion)
|
|
}
|
|
|
|
// validate the contents of the manifest
|
|
if err := validateManifest(&manifest); err != nil {
|
|
return "", nil, false, err
|
|
}
|
|
|
|
var verified bool
|
|
for _, key := range keys {
|
|
namespace := manifest.Name
|
|
if namespace[0] != '/' {
|
|
namespace = "/" + namespace
|
|
}
|
|
b, err := key.MarshalJSON()
|
|
if err != nil {
|
|
return "", nil, false, fmt.Errorf("error marshalling public key: %s", err)
|
|
}
|
|
// Check key has read/write permission (0x03)
|
|
v, err := s.trustService.CheckKey(namespace, b, 0x03)
|
|
if err != nil {
|
|
vErr, ok := err.(trust.NotVerifiedError)
|
|
if !ok {
|
|
return "", nil, false, fmt.Errorf("error running key check: %s", err)
|
|
}
|
|
logrus.Debugf("Key check result: %v", vErr)
|
|
}
|
|
verified = v
|
|
if verified {
|
|
logrus.Debug("Key check result: verified")
|
|
}
|
|
}
|
|
return localDigest, &manifest, verified, nil
|
|
}
|
|
|
|
func validateManifest(manifest *registry.ManifestData) error {
|
|
if len(manifest.FSLayers) != len(manifest.History) {
|
|
return fmt.Errorf("length of history not equal to number of layers")
|
|
}
|
|
|
|
if len(manifest.FSLayers) == 0 {
|
|
return fmt.Errorf("no FSLayers in manifest")
|
|
}
|
|
|
|
return nil
|
|
}
|