github.com/quay/claircore@v1.5.28/digest.go (about)

     1  package claircore
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"crypto/sha512"
     7  	"database/sql/driver"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"hash"
    11  )
    12  
    13  const (
    14  	SHA256 = "sha256"
    15  	SHA512 = "sha512"
    16  )
    17  
    18  // Digest is a type representing the hash of some data.
    19  //
    20  // It's used throughout claircore packages as an attempt to remain independent
    21  // of a specific hashing algorithm.
    22  type Digest struct {
    23  	algo     string
    24  	checksum []byte
    25  	repr     string
    26  }
    27  
    28  // Checksum returns the checksum byte slice.
    29  func (d Digest) Checksum() []byte { return d.checksum }
    30  
    31  // Algorithm returns a string representation of the algorithm used for this
    32  // digest.
    33  func (d Digest) Algorithm() string { return d.algo }
    34  
    35  // Hash returns an instance of the hashing algorithm used for this Digest.
    36  func (d Digest) Hash() hash.Hash {
    37  	switch d.algo {
    38  	case "sha256":
    39  		return sha256.New()
    40  	case "sha512":
    41  		return sha512.New()
    42  	default:
    43  		panic("Hash() called on an invalid Digest")
    44  	}
    45  }
    46  
    47  func (d Digest) String() string {
    48  	return d.repr
    49  }
    50  
    51  // MarshalText implements encoding.TextMarshaler.
    52  func (d Digest) MarshalText() ([]byte, error) {
    53  	b := make([]byte, len(d.repr))
    54  	copy(b, d.repr)
    55  	return b, nil
    56  }
    57  
    58  // UnmarshalText implements encoding.TextUnmarshaler.
    59  func (d *Digest) UnmarshalText(t []byte) error {
    60  	i := bytes.IndexByte(t, ':')
    61  	if i == -1 {
    62  		return &DigestError{msg: "invalid digest format"}
    63  	}
    64  	d.algo = string(t[:i])
    65  	t = t[i+1:]
    66  	b := make([]byte, hex.DecodedLen(len(t)))
    67  	if _, err := hex.Decode(b, t); err != nil {
    68  		return &DigestError{
    69  			msg:   "unable to decode digest as hex",
    70  			inner: err,
    71  		}
    72  	}
    73  	return d.setChecksum(b)
    74  }
    75  
    76  // DigestError is the concrete type backing errors returned from Digest's
    77  // methods.
    78  type DigestError struct {
    79  	msg   string
    80  	inner error
    81  }
    82  
    83  // Error implements error.
    84  func (e *DigestError) Error() string {
    85  	return e.msg
    86  }
    87  
    88  // Unwrap enables errors.Unwrap.
    89  func (e *DigestError) Unwrap() error {
    90  	return e.inner
    91  }
    92  
    93  func (d *Digest) setChecksum(b []byte) error {
    94  	var sz int
    95  	switch d.algo {
    96  	case "sha256":
    97  		sz = sha256.Size
    98  	case "sha512":
    99  		sz = sha512.Size
   100  	default:
   101  		return &DigestError{msg: fmt.Sprintf("unknown algorthm %q", d.algo)}
   102  	}
   103  	if l := len(b); l != sz {
   104  		return &DigestError{msg: fmt.Sprintf("bad checksum length: %d", l)}
   105  	}
   106  
   107  	el := hex.EncodedLen(sz)
   108  	hl := len(d.algo) + 1
   109  	sb := make([]byte, hl+el)
   110  	copy(sb, d.algo)
   111  	sb[len(d.algo)] = ':'
   112  	hex.Encode(sb[hl:], b)
   113  
   114  	d.checksum = b
   115  	d.repr = string(sb)
   116  
   117  	return nil
   118  }
   119  
   120  // Scan implements sql.Scanner.
   121  func (d *Digest) Scan(i interface{}) error {
   122  	switch v := i.(type) {
   123  	case nil:
   124  		return nil
   125  	case string:
   126  		d.UnmarshalText([]byte(v))
   127  		return nil
   128  	default:
   129  		return &DigestError{msg: fmt.Sprintf("invalid digest type: %T", v)}
   130  	}
   131  }
   132  
   133  // Value implements driver.Valuer.
   134  func (d Digest) Value() (driver.Value, error) {
   135  	return d.repr, nil
   136  }
   137  
   138  // NewDigest constructs a Digest.
   139  func NewDigest(algo string, sum []byte) (Digest, error) {
   140  	d := Digest{
   141  		algo: algo,
   142  	}
   143  	return d, d.setChecksum(sum)
   144  }
   145  
   146  // ParseDigest constructs a Digest from a string, ensuring it's well-formed.
   147  func ParseDigest(digest string) (Digest, error) {
   148  	d := Digest{}
   149  	return d, d.UnmarshalText([]byte(digest))
   150  }
   151  
   152  // MustParseDigest works like ParseDigest but panics if the provided
   153  // string is not well-formed.
   154  func MustParseDigest(digest string) Digest {
   155  	d := Digest{}
   156  	err := d.UnmarshalText([]byte(digest))
   157  	if err != nil {
   158  		s := fmt.Sprintf("digest %s could not be parsed: %v", digest, err)
   159  		panic(s)
   160  	}
   161  	return d
   162  }