github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/fit/vfit.go (about) 1 // Copyright 2021 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 // 5 // Performs signature checks on FDT images. 6 // Currently supports PGP and raw PKCS1v15 RSA signatures. 7 // 8 // Expected FDT Format: 9 // Node: images 10 // Node: image_name 11 // P: data 12 // Node: signature* 13 // P: value 14 // P: algo (ex. 'sha256,rsa4096', 'pgp') 15 // P: signer-name (Optional) 16 // P: key-name-hint (Optional) 17 18 package fit 19 20 import ( 21 "bytes" 22 "crypto" 23 "crypto/rsa" 24 "fmt" 25 "strings" 26 "unicode" 27 28 "github.com/ProtonMail/go-crypto/openpgp" 29 "github.com/mvdan/u-root-coreutils/pkg/dt" 30 "github.com/mvdan/u-root-coreutils/pkg/vfile" 31 ) 32 33 var algs = map[string]crypto.Hash{ 34 "MD4": crypto.MD4, 35 "MD5": crypto.MD5, 36 "SHA1": crypto.SHA1, 37 "SHA224": crypto.SHA224, 38 "SHA256": crypto.SHA256, 39 "SHA384": crypto.SHA384, 40 "SHA512": crypto.SHA512, 41 "RIPEMD160": crypto.RIPEMD160, 42 "SHA3_224": crypto.SHA3_224, 43 "SHA3_256": crypto.SHA3_256, 44 "SHA3_384": crypto.SHA3_384, 45 "SHA3_512": crypto.SHA3_512, 46 } 47 48 // Signature defines an extendable interface for verifying images using 49 // varying signing methods. 50 type Signature interface { 51 fmt.Stringer 52 // Warning: If the signature does not exist or does not match the keyring, 53 // both the file and a signature error will be returned. 54 // Returns a bytes.Reader to the original data array. 55 Verify([]byte, openpgp.KeyRing) (*bytes.Reader, error) 56 } 57 58 // PGPSignature implements a OpenPGP signature check. 59 type PGPSignature struct { 60 name string // Name of signature Node 61 value []byte 62 // Optional description fields 63 signer string 64 hint string 65 } 66 67 // RSASignature implements a PKCS1v15 signature check. 68 type RSASignature struct { 69 name string // Name of signature Node 70 hash crypto.Hash 71 value []byte 72 // Optional description fields 73 signer string 74 hint string 75 } 76 77 func (s PGPSignature) String() string { 78 return fmt.Sprintf("PGP Signature - name: %s, signer: '%s', hint: '%s'", s.name, s.signer, s.hint) 79 } 80 81 // Verify runs a PKCS1v15 check using the RSA keys extracted from the provided 82 // key ring. 83 // Warning: If the signature does not exist or does not match the keyring, 84 // both the file and a signature error will be returned. 85 func (s PGPSignature) Verify(b []byte, ring openpgp.KeyRing) (*bytes.Reader, error) { 86 r := bytes.NewReader(b) 87 if signer, err := openpgp.CheckDetachedSignature(ring, bytes.NewReader(b), bytes.NewReader(s.value), nil); err != nil { 88 return r, vfile.ErrUnsigned{Path: s.name, Err: err} 89 } else if signer == nil { 90 return r, vfile.ErrUnsigned{Path: s.name, Err: vfile.ErrWrongSigner{ring}} 91 } 92 return r, nil 93 } 94 95 // Verify runs a OpenPGP check using the PGP keys extracted from the provided 96 // key ring. 97 // Warning: If the signature does not exist or does not match the keyring, 98 // both the file and a signature error will be returned. 99 func (s RSASignature) Verify(b []byte, ring openpgp.KeyRing) (*bytes.Reader, error) { 100 r := bytes.NewReader(b) 101 keys, err := vfile.GetRSAKeysFromRing(ring) 102 if err != nil { 103 return r, err 104 } 105 106 hashed, err := vfile.CalculateHash(bytes.NewReader(b), s.hash.New()) 107 if err != nil { 108 return r, err 109 } 110 111 for _, key := range keys { 112 if err = rsa.VerifyPKCS1v15(key, s.hash, hashed, s.value); err == nil { 113 return r, nil 114 } 115 } 116 return r, vfile.ErrUnsigned{Err: vfile.ErrWrongSigner{ring}} 117 } 118 119 func (s RSASignature) String() string { 120 return fmt.Sprintf("RSA Signature - name: %s, signer: '%s', hint: '%s', hash: '%s'", s.name, s.signer, s.hint, s.hash) 121 } 122 123 // parseHash cleans and maps the first detected hash string into a crypto.Hash. 124 // Expected format: "sha256,rsa4096" or "sha1" 125 func parseHash(algo string) (crypto.Hash, error) { 126 cleaned := strings.TrimFunc(algo, func(r rune) bool { 127 return !unicode.IsGraphic(r) 128 }) 129 algoSplit := strings.Split(cleaned, ",") 130 if len(algoSplit) == 0 { 131 return 0, fmt.Errorf("Unrecognized hash algo: '%s'", cleaned) 132 } 133 for _, alg := range algoSplit { 134 if matched, ok := algs[strings.ToUpper(alg)]; ok { 135 return matched, nil 136 } 137 } 138 return 0, fmt.Errorf("Unrecognized hash algo: '%s'", cleaned) 139 } 140 141 // parseSignatures parses dt.Node to RSASignatures 142 // Nodes with missing required properties or invalid hash functions are skipped. 143 // An error is returned if no valid signatures are found 144 func parseSignatures(n ...*dt.Node) ([]Signature, error) { 145 var sigs []Signature 146 for _, node := range n { 147 v, ok := node.LookProperty("value") 148 if !ok { 149 fmt.Printf("Skipping signature %s: missing value node", node.Name) 150 continue 151 } 152 153 a, ok := node.LookProperty("algo") 154 if !ok { 155 fmt.Printf("Skipping signature %s: missing algo node", node.Name) 156 continue 157 } 158 159 var signer, hint string 160 if signerProp, ok := node.LookProperty("signer-name"); ok { 161 signer = string(signerProp.Value) 162 } 163 if hintProp, ok := node.LookProperty("key-name-hint"); ok { 164 hint = string(hintProp.Value) 165 } 166 167 // Perform a broad stroke check for algos. RSA is assumed a raw RSA signature 168 switch { 169 case strings.Contains(string(a.Value), "pgp"): 170 sigs = append(sigs, PGPSignature{value: v.Value, signer: signer, hint: hint}) 171 case strings.Contains(string(a.Value), "rsa"): 172 // Parse the hash function used for the signature. ex. 'sha256,rsa4096' 173 hf, err := parseHash(string(a.Value)) 174 if err != nil { 175 fmt.Printf("Skipping signature %s: %v", node.Name, err) 176 continue 177 } 178 sigs = append(sigs, RSASignature{value: v.Value, hash: hf, signer: signer, hint: hint}) 179 } 180 } 181 if len(sigs) == 0 { 182 return nil, fmt.Errorf("Failed to parse any valid Signatures") 183 } 184 return sigs, nil 185 } 186 187 // ReadSignedImage reads an image node from an FDT and verifies the 188 // content against a key set. Signature information is found in child nodes. 189 // 190 // WARNING! Unlike many Go functions, this may return both the file and an 191 // error. 192 // 193 // If the signature does not exist or does not match the keyring, both the file 194 // and a signature error will be returned. 195 func (i *Image) ReadSignedImage(image string, ring openpgp.KeyRing) (*bytes.Reader, error) { 196 iroot := i.Root.Root().Walk("images").Walk(image) 197 b, err := iroot.Property("data").AsBytes() 198 if err != nil { 199 return nil, err 200 } 201 202 br := bytes.NewReader(b) 203 sigNodes, err := iroot.FindAll(func(n *dt.Node) bool { 204 return strings.HasPrefix(strings.ToLower(n.Name), "signature") 205 }) 206 if err != nil { 207 return br, vfile.ErrUnsigned{Path: image, Err: fmt.Errorf("no signature nodes found")} 208 } 209 sigs, err := parseSignatures(sigNodes...) 210 if err != nil { 211 return br, vfile.ErrUnsigned{Path: image, Err: err} 212 } 213 214 for _, sig := range sigs { 215 v, err := sig.Verify(b, ring) 216 if err == nil { 217 return v, nil 218 } 219 fmt.Printf("Ignoring failed signature - %s: Failed with %v\n", sig, err) 220 } 221 222 return br, vfile.ErrUnsigned{Path: image, Err: vfile.ErrWrongSigner{i.KeyRing}} 223 }