github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/file/digest.go (about) 1 package file 2 3 import ( 4 "crypto" 5 "fmt" 6 "hash" 7 "io" 8 "strings" 9 ) 10 11 type Digest struct { 12 Algorithm string `json:"algorithm"` 13 Value string `json:"value"` 14 } 15 16 func NewDigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, error) { 17 // create a set of hasher objects tied together with a single writer to feed content into 18 hashers := make([]hash.Hash, len(hashes)) 19 writers := make([]io.Writer, len(hashes)) 20 for idx, hashObj := range hashes { 21 hashers[idx] = hashObj.New() 22 writers[idx] = hashers[idx] 23 } 24 25 size, err := io.Copy(io.MultiWriter(writers...), closer) 26 if err != nil { 27 return nil, err 28 } 29 30 if size == 0 { 31 return make([]Digest, 0), nil 32 } 33 34 result := make([]Digest, len(hashes)) 35 // only capture digests when there is content. It is important to do this based on SIZE and not 36 // FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only 37 // file type but a body is still allowed. 38 for idx, hasher := range hashers { 39 result[idx] = Digest{ 40 Algorithm: DigestAlgorithmName(hashes[idx]), 41 Value: fmt.Sprintf("%+x", hasher.Sum(nil)), 42 } 43 } 44 45 return result, nil 46 } 47 48 func Hashers(names ...string) ([]crypto.Hash, error) { 49 supportedHashAlgorithms := make(map[string]crypto.Hash) 50 for _, h := range []crypto.Hash{ 51 crypto.MD5, 52 crypto.SHA1, 53 crypto.SHA256, 54 } { 55 supportedHashAlgorithms[DigestAlgorithmName(h)] = h 56 } 57 58 var hashers []crypto.Hash 59 for _, hashStr := range names { 60 hashObj, ok := supportedHashAlgorithms[CleanDigestAlgorithmName(hashStr)] 61 if !ok { 62 return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr) 63 } 64 hashers = append(hashers, hashObj) 65 } 66 return hashers, nil 67 } 68 69 func DigestAlgorithmName(hash crypto.Hash) string { 70 return CleanDigestAlgorithmName(hash.String()) 71 } 72 73 func CleanDigestAlgorithmName(name string) string { 74 lower := strings.ToLower(name) 75 return strings.ReplaceAll(lower, "-", "") 76 }