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  }