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 "crypto" 20 "fmt" 21 "hash" 22 "io" 23 "regexp" 24 ) 25 26 // Algorithm identifies and implementation of a digester by an identifier. 27 // Note the that this defines both the hash algorithm used and the string 28 // encoding. 29 type Algorithm string 30 31 // supported digest types 32 const ( 33 SHA256 Algorithm = "sha256" // sha256 with hex encoding (lower case only) 34 SHA384 Algorithm = "sha384" // sha384 with hex encoding (lower case only) 35 SHA512 Algorithm = "sha512" // sha512 with hex encoding (lower case only) 36 37 // Canonical is the primary digest algorithm used with the distribution 38 // project. Other digests may be used but this one is the primary storage 39 // digest. 40 Canonical = SHA256 41 ) 42 43 var ( 44 // TODO(stevvooe): Follow the pattern of the standard crypto package for 45 // registration of digests. Effectively, we are a registerable set and 46 // common symbol access. 47 48 // algorithms maps values to hash.Hash implementations. Other algorithms 49 // may be available but they cannot be calculated by the digest package. 50 algorithms = map[Algorithm]crypto.Hash{ 51 SHA256: crypto.SHA256, 52 SHA384: crypto.SHA384, 53 SHA512: crypto.SHA512, 54 } 55 56 // anchoredEncodedRegexps contains anchored regular expressions for hex-encoded digests. 57 // Note that /A-F/ disallowed. 58 anchoredEncodedRegexps = map[Algorithm]*regexp.Regexp{ 59 SHA256: regexp.MustCompile(`^[a-f0-9]{64}$`), 60 SHA384: regexp.MustCompile(`^[a-f0-9]{96}$`), 61 SHA512: regexp.MustCompile(`^[a-f0-9]{128}$`), 62 } 63 ) 64 65 // Available returns true if the digest type is available for use. If this 66 // returns false, Digester and Hash will return nil. 67 func (a Algorithm) Available() bool { 68 h, ok := algorithms[a] 69 if !ok { 70 return false 71 } 72 73 // check availability of the hash, as well 74 return h.Available() 75 } 76 77 func (a Algorithm) String() string { 78 return string(a) 79 } 80 81 // Size returns number of bytes returned by the hash. 82 func (a Algorithm) Size() int { 83 h, ok := algorithms[a] 84 if !ok { 85 return 0 86 } 87 return h.Size() 88 } 89 90 // Set implemented to allow use of Algorithm as a command line flag. 91 func (a *Algorithm) Set(value string) error { 92 if value == "" { 93 *a = Canonical 94 } else { 95 // just do a type conversion, support is queried with Available. 96 *a = Algorithm(value) 97 } 98 99 if !a.Available() { 100 return ErrDigestUnsupported 101 } 102 103 return nil 104 } 105 106 // Digester returns a new digester for the specified algorithm. If the algorithm 107 // does not have a digester implementation, nil will be returned. This can be 108 // checked by calling Available before calling Digester. 109 func (a Algorithm) Digester() Digester { 110 return &digester{ 111 alg: a, 112 hash: a.Hash(), 113 } 114 } 115 116 // Hash returns a new hash as used by the algorithm. If not available, the 117 // method will panic. Check Algorithm.Available() before calling. 118 func (a Algorithm) Hash() hash.Hash { 119 if !a.Available() { 120 // Empty algorithm string is invalid 121 if a == "" { 122 panic(fmt.Sprintf("empty digest algorithm, validate before calling Algorithm.Hash()")) 123 } 124 125 // NOTE(stevvooe): A missing hash is usually a programming error that 126 // must be resolved at compile time. We don't import in the digest 127 // package to allow users to choose their hash implementation (such as 128 // when using stevvooe/resumable or a hardware accelerated package). 129 // 130 // Applications that may want to resolve the hash at runtime should 131 // call Algorithm.Available before call Algorithm.Hash(). 132 panic(fmt.Sprintf("%v not available (make sure it is imported)", a)) 133 } 134 135 return algorithms[a].New() 136 } 137 138 // Encode encodes the raw bytes of a digest, typically from a hash.Hash, into 139 // the encoded portion of the digest. 140 func (a Algorithm) Encode(d []byte) string { 141 // TODO(stevvooe): Currently, all algorithms use a hex encoding. When we 142 // add support for back registration, we can modify this accordingly. 143 return fmt.Sprintf("%x", d) 144 } 145 146 // FromReader returns the digest of the reader using the algorithm. 147 func (a Algorithm) FromReader(rd io.Reader) (Digest, error) { 148 digester := a.Digester() 149 150 if _, err := io.Copy(digester.Hash(), rd); err != nil { 151 return "", err 152 } 153 154 return digester.Digest(), nil 155 } 156 157 // FromBytes digests the input and returns a Digest. 158 func (a Algorithm) FromBytes(p []byte) Digest { 159 digester := a.Digester() 160 161 if _, err := digester.Hash().Write(p); err != nil { 162 // Writes to a Hash should never fail. None of the existing 163 // hash implementations in the stdlib or hashes vendored 164 // here can return errors from Write. Having a panic in this 165 // condition instead of having FromBytes return an error value 166 // avoids unnecessary error handling paths in all callers. 167 panic("write to hash function returned error: " + err.Error()) 168 } 169 170 return digester.Digest() 171 } 172 173 // FromString digests the string input and returns a Digest. 174 func (a Algorithm) FromString(s string) Digest { 175 return a.FromBytes([]byte(s)) 176 } 177 178 // Validate validates the encoded portion string 179 func (a Algorithm) Validate(encoded string) error { 180 r, ok := anchoredEncodedRegexps[a] 181 if !ok { 182 return ErrDigestUnsupported 183 } 184 // Digests much always be hex-encoded, ensuring that their hex portion will 185 // always be size*2 186 if a.Size()*2 != len(encoded) { 187 return ErrDigestInvalidLength 188 } 189 if r.MatchString(encoded) { 190 return nil 191 } 192 return ErrDigestInvalidFormat 193 } 194