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  }