| 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) { |
| payload, keys, err := unpackSignedManifest(manifestBytes) |
| if err != nil { |
| return "", nil, false, fmt.Errorf("error unpacking manifest: %v", 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 ref. ParseDigest will validate |
| // that the ref is a digest and verify against that if present. Otherwize |
| // (on error), we simply compute the localDigest and proceed. |
| if dgst, err := digest.ParseDigest(ref); err == nil { |
| // verify the manifest against local ref |
| if err := verifyDigest(dgst, payload); err != nil { |
| return "", nil, false, fmt.Errorf("verifying local digest: %v", err) |
| } |
| |
| localDigest = dgst |
| } else { |
| // We don't have a local digest, since we are working from a tag. |
| // Compute the digest of the payload and return that. |
| logrus.Debugf("provided manifest reference %q is not a digest: %v", ref, err) |
| 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 |
| } |
| } |
| |
| // verify against the remote digest, if available |
| if remoteDigest != "" { |
| if err := verifyDigest(remoteDigest, payload); err != nil { |
| return "", nil, false, fmt.Errorf("verifying remote digest: %v", err) |
| } |
| } |
| |
| var manifest registry.ManifestData |
| if err := json.Unmarshal(payload, &manifest); err != nil { |
| return "", nil, false, fmt.Errorf("error unmarshalling manifest: %s", err) |
| } |
| |
| // validate the contents of the manifest |
| if err := validateManifest(&manifest); err != nil { |
| return "", nil, false, err |
| } |
| |
| var verified bool |
| verified, err = s.verifyTrustedKeys(manifest.Name, keys) |
| if err != nil { |
| return "", nil, false, fmt.Errorf("error verifying trusted keys: %v", err) |
| } |
| |
| return localDigest, &manifest, verified, nil |
| } |
| |
| // unpackSignedManifest takes the raw, signed manifest bytes, unpacks the jws |
| // and returns the payload and public keys used to signed the manifest. |
| // Signatures are verified for authenticity but not against the trust store. |
| func unpackSignedManifest(p []byte) ([]byte, []libtrust.PublicKey, error) { |
| sig, err := libtrust.ParsePrettySignature(p, "signatures") |
| if err != nil { |
| return nil, nil, fmt.Errorf("error parsing payload: %s", err) |
| } |
| |
| keys, err := sig.Verify() |
| if err != nil { |
| return nil, nil, fmt.Errorf("error verifying payload: %s", err) |
| } |
| |
| payload, err := sig.Payload() |
| if err != nil { |
| return nil, nil, fmt.Errorf("error retrieving payload: %s", err) |
| } |
| |
| return payload, keys, nil |
| } |
| |
| // verifyTrustedKeys checks the keys provided against the trust store, |
| // ensuring that the provided keys are trusted for the namespace. The keys |
| // provided from this method must come from the signatures provided as part of |
| // the manifest JWS package, obtained from unpackSignedManifest or libtrust. |
| func (s *TagStore) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey) (verified bool, err error) { |
| if namespace[0] != '/' { |
| namespace = "/" + namespace |
| } |
| |
| for _, key := range keys { |
| b, err := key.MarshalJSON() |
| if err != nil { |
| return 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 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 |
| } |
| |
| // verifyDigest checks the contents of p against the provided digest. Note |
| // that for manifests, this is the signed payload and not the raw bytes with |
| // signatures. |
| func verifyDigest(dgst digest.Digest, p []byte) error { |
| if err := dgst.Validate(); err != nil { |
| return fmt.Errorf("error validating digest %q: %v", dgst, err) |
| } |
| |
| 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 fmt.Errorf("error creating verifier for digest %q: %v", dgst, err) |
| } |
| |
| if _, err := verifier.Write(p); err != nil { |
| return fmt.Errorf("error writing payload to digest verifier (verifier target %q): %v", dgst, err) |
| } |
| |
| if !verifier.Verified() { |
| return fmt.Errorf("verification against digest %q failed", dgst) |
| } |
| |
| return nil |
| } |
| |
| func validateManifest(manifest *registry.ManifestData) error { |
| if manifest.SchemaVersion != 1 { |
| return fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion) |
| } |
| |
| 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 |
| } |