github.com/anchore/syft@v1.38.2/internal/file/digest.go (about)

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