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 }