1 // Copyright 2019, 2020 OCI Contributors 2 // Copyright 2017 Docker, Inc. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // https://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package digest 17 18 import ( 19 "fmt" 20 "hash" 21 "io" 22 "regexp" 23 "strings" 24 ) 25 26 // Digest allows simple protection of hex formatted digest strings, prefixed 27 // by their algorithm. Strings of type Digest have some guarantee of being in 28 // the correct format and it provides quick access to the components of a 29 // digest string. 30 // 31 // The following is an example of the contents of Digest types: 32 // 33 // sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc 34 // 35 // This allows to abstract the digest behind this type and work only in those 36 // terms. 37 type Digest string 38 39 // NewDigest returns a Digest from alg and a hash.Hash object. 40 func NewDigest(alg Algorithm, h hash.Hash) Digest { 41 return NewDigestFromBytes(alg, h.Sum(nil)) 42 } 43 44 // NewDigestFromBytes returns a new digest from the byte contents of p. 45 // Typically, this can come from hash.Hash.Sum(...) or xxx.SumXXX(...) 46 // functions. This is also useful for rebuilding digests from binary 47 // serializations. 48 func NewDigestFromBytes(alg Algorithm, p []byte) Digest { 49 return NewDigestFromEncoded(alg, alg.Encode(p)) 50 } 51 52 // NewDigestFromHex is deprecated. Please use NewDigestFromEncoded. 53 func NewDigestFromHex(alg, hex string) Digest { 54 return NewDigestFromEncoded(Algorithm(alg), hex) 55 } 56 57 // NewDigestFromEncoded returns a Digest from alg and the encoded digest. 58 func NewDigestFromEncoded(alg Algorithm, encoded string) Digest { 59 return Digest(fmt.Sprintf("%s:%s", alg, encoded)) 60 } 61 62 // DigestRegexp matches valid digest types. 63 var DigestRegexp = regexp.MustCompile(`[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+`) 64 65 // DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match. 66 var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`) 67 68 var ( 69 // ErrDigestInvalidFormat returned when digest format invalid. 70 ErrDigestInvalidFormat = fmt.Errorf("invalid checksum digest format") 71 72 // ErrDigestInvalidLength returned when digest has invalid length. 73 ErrDigestInvalidLength = fmt.Errorf("invalid checksum digest length") 74 75 // ErrDigestUnsupported returned when the digest algorithm is unsupported. 76 ErrDigestUnsupported = fmt.Errorf("unsupported digest algorithm") 77 ) 78 79 // Parse parses s and returns the validated digest object. An error will 80 // be returned if the format is invalid. 81 func Parse(s string) (Digest, error) { 82 d := Digest(s) 83 return d, d.Validate() 84 } 85 86 // FromReader consumes the content of rd until io.EOF, returning canonical digest. 87 func FromReader(rd io.Reader) (Digest, error) { 88 return Canonical.FromReader(rd) 89 } 90 91 // FromBytes digests the input and returns a Digest. 92 func FromBytes(p []byte) Digest { 93 return Canonical.FromBytes(p) 94 } 95 96 // FromString digests the input and returns a Digest. 97 func FromString(s string) Digest { 98 return Canonical.FromString(s) 99 } 100 101 // Validate checks that the contents of d is a valid digest, returning an 102 // error if not. 103 func (d Digest) Validate() error { 104 s := string(d) 105 i := strings.Index(s, ":") 106 if i <= 0 || i+1 == len(s) { 107 return ErrDigestInvalidFormat 108 } 109 algorithm, encoded := Algorithm(s[:i]), s[i+1:] 110 if !algorithm.Available() { 111 if !DigestRegexpAnchored.MatchString(s) { 112 return ErrDigestInvalidFormat 113 } 114 return ErrDigestUnsupported 115 } 116 return algorithm.Validate(encoded) 117 } 118 119 // Algorithm returns the algorithm portion of the digest. This will panic if 120 // the underlying digest is not in a valid format. 121 func (d Digest) Algorithm() Algorithm { 122 return Algorithm(d[:d.sepIndex()]) 123 } 124 125 // Verifier returns a writer object that can be used to verify a stream of 126 // content against the digest. If the digest is invalid, the method will panic. 127 func (d Digest) Verifier() Verifier { 128 return hashVerifier{ 129 hash: d.Algorithm().Hash(), 130 digest: d, 131 } 132 } 133 134 // Encoded returns the encoded portion of the digest. This will panic if the 135 // underlying digest is not in a valid format. 136 func (d Digest) Encoded() string { 137 return string(d[d.sepIndex()+1:]) 138 } 139 140 // Hex is deprecated. Please use Digest.Encoded. 141 func (d Digest) Hex() string { 142 return d.Encoded() 143 } 144 145 func (d Digest) String() string { 146 return string(d) 147 } 148 149 func (d Digest) sepIndex() int { 150 i := strings.Index(string(d), ":") 151 152 if i < 0 { 153 panic(fmt.Sprintf("no ':' separator in digest %q", d)) 154 } 155 156 return i 157 } 158