-
Notifications
You must be signed in to change notification settings - Fork 0
/
manifest.go
178 lines (149 loc) · 5.85 KB
/
manifest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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
}