github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/boot/stboot/bootball.go (about)

     1  // Copyright 2018 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  package stboot
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/u-root/u-root/pkg/boot/jsonboot"
    18  	"github.com/u-root/u-root/pkg/uzip"
    19  )
    20  
    21  const (
    22  	signaturesDirName string = "signatures"
    23  	rootCertName      string = "root.cert"
    24  )
    25  
    26  // BootBall contains data to operate on the system transparency
    27  // bootball archive. There is an underlying temporary directory
    28  // representing the extracted archive.
    29  type BootBall struct {
    30  	Archive        string
    31  	dir            string
    32  	config         *Stconfig
    33  	numBootConfigs int
    34  	bootFiles      map[string][]string
    35  	RootCertPEM    []byte
    36  	signatures     map[string][]Signature
    37  	NumSignatures  int
    38  	hashes         map[string][]byte
    39  	Signer         Signer
    40  }
    41  
    42  // BootBallFromArchive constructs a BootBall zip file at archive
    43  func BootBallFromArchive(archive string) (*BootBall, error) {
    44  	var ball = new(BootBall)
    45  
    46  	dir, err := ioutil.TempDir("", "bootball")
    47  	if err != nil {
    48  		return ball, fmt.Errorf("BootBall: cannot create tmp dir: %v", err)
    49  	}
    50  
    51  	err = uzip.FromZip(archive, dir)
    52  	if err != nil {
    53  		return ball, fmt.Errorf("BootBall: cannot unzip %s: %v", archive, err)
    54  	}
    55  
    56  	cfg, err := getConfig(filepath.Join(dir, ConfigName))
    57  	if err != nil {
    58  		return ball, fmt.Errorf("BootBall: getting configuration faild: %v", err)
    59  	}
    60  
    61  	ball.Archive = archive
    62  	ball.dir = dir
    63  	ball.config = cfg
    64  
    65  	err = ball.init()
    66  	if err != nil {
    67  		return ball, err
    68  	}
    69  
    70  	return ball, nil
    71  }
    72  
    73  // BootBallFromConfig constructs a BootBall from a stconfig.json at configFile.
    74  // the underlying tmporary directory is created with standardized paths and an
    75  // updated copy of stconfig.json
    76  func BootBallFromConfig(configFile string) (*BootBall, error) {
    77  	var ball = new(BootBall)
    78  
    79  	archive := filepath.Join(filepath.Dir(configFile), BallName)
    80  
    81  	cfg, err := getConfig(configFile)
    82  	if err != nil {
    83  		return ball, fmt.Errorf("BootBall: getting configuration faild: %v", err)
    84  	}
    85  
    86  	dir, err := makeConfigDir(cfg, filepath.Dir(configFile))
    87  	if err != nil {
    88  		return ball, fmt.Errorf("BootBall: creating standard configuration directory faild: %v", err)
    89  	}
    90  
    91  	ball.Archive = archive
    92  	ball.dir = dir
    93  	ball.config = cfg
    94  
    95  	err = ball.init()
    96  	if err != nil {
    97  		return ball, err
    98  	}
    99  
   100  	return ball, nil
   101  }
   102  
   103  func (ball *BootBall) init() error {
   104  	certPEM, err := ioutil.ReadFile(filepath.Join(ball.dir, signaturesDirName, rootCertName))
   105  	if err != nil {
   106  		return fmt.Errorf("BootBall: reading root certificate faild: %v", err)
   107  	}
   108  
   109  	bootFiles, err := getBootFiles(ball.config, ball.dir)
   110  	if err != nil {
   111  		return fmt.Errorf("BootBall: getting boot files faild: %v", err)
   112  	}
   113  
   114  	ball.RootCertPEM = certPEM
   115  	ball.numBootConfigs = len(ball.config.BootConfigs)
   116  	ball.bootFiles = bootFiles
   117  	ball.Signer = Sha512PssSigner{}
   118  
   119  	err = ball.getSignatures()
   120  	if err != nil {
   121  		return fmt.Errorf("BootBall: getting signatures: %v", err)
   122  	}
   123  
   124  	var x int
   125  	for _, sigPool := range ball.signatures {
   126  		if x == 0 {
   127  			x = len(sigPool)
   128  			continue
   129  		}
   130  		if len(sigPool) != x {
   131  			return errors.New("BootBall: invalid map of signatures")
   132  		}
   133  	}
   134  	ball.NumSignatures = x
   135  	return nil
   136  }
   137  
   138  // Clean removes the underlying temporary directory.
   139  func (ball *BootBall) Clean() error {
   140  	err := os.RemoveAll(ball.dir)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	ball.dir = ""
   145  	return nil
   146  }
   147  
   148  // Pack archives the all contents of the underlying temporary
   149  // directory using zip.
   150  func (ball *BootBall) Pack() error {
   151  	if ball.Archive == "" || ball.dir == "" {
   152  		return errors.New("BootBall.Pacstandak: booball.archive and bootball.dir must be set")
   153  	}
   154  	return uzip.ToZip(ball.dir, ball.Archive)
   155  }
   156  
   157  // Dir returns the temporary directory associated with BootBall.
   158  func (ball *BootBall) Dir() string {
   159  	return ball.dir
   160  }
   161  
   162  // GetBootConfigByIndex returns the Bootconfig at index from the BootBall's configs arrey.
   163  func (ball *BootBall) GetBootConfigByIndex(index int) (*jsonboot.BootConfig, error) {
   164  	bc, err := ball.config.getBootConfig(index)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	bc.SetFilePathsPrefix(ball.dir)
   169  	return bc, nil
   170  }
   171  
   172  // Hash calculates hashes of all boot configurations in BootBall using the
   173  // BootBall.Signer's hash function
   174  func (ball *BootBall) Hash() error {
   175  	ball.hashes = make(map[string][]byte)
   176  	for key, files := range ball.bootFiles {
   177  		hash, herr := ball.Signer.Hash(files...)
   178  		if herr != nil {
   179  			return herr
   180  		}
   181  		ball.hashes[key] = hash
   182  	}
   183  	return nil
   184  }
   185  
   186  // Sign signes the hashes of all boot configurations in BootBall using the
   187  // BootBall.Signer's hash function with the provided privKeyFile. The signature
   188  // is stored along with the provided certFile inside the BootBall.
   189  func (ball *BootBall) Sign(privKeyFile, certFile string) error {
   190  	if _, err := os.Stat(privKeyFile); err != nil {
   191  		return err
   192  	}
   193  
   194  	buf, err := ioutil.ReadFile(certFile)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	cert, err := parseCertificate(buf)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	err = validateCertificate(cert, ball.RootCertPEM)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	log.Printf("Signing with: %s", privKeyFile)
   210  
   211  	if ball.hashes == nil {
   212  		err = ball.Hash()
   213  		if err != nil {
   214  			return err
   215  		}
   216  	}
   217  
   218  	for key, hash := range ball.hashes {
   219  		s, err := ball.Signer.Sign(privKeyFile, hash)
   220  		if err != nil {
   221  			return err
   222  		}
   223  		sig := Signature{
   224  			Bytes: s,
   225  			Cert:  cert}
   226  		ball.signatures[key] = append(ball.signatures[key], sig)
   227  		d := filepath.Join(ball.dir, signaturesDirName, key)
   228  		if err = writeSignature(d, certFile, sig); err != nil {
   229  			return err
   230  		}
   231  	}
   232  
   233  	ball.NumSignatures++
   234  	return nil
   235  }
   236  
   237  // VerifyBootconfigByID validates the certificates stored together with the
   238  // signatures of BootConfig id and verifies the signatures. The number of
   239  // valid signatures is returned.
   240  func (ball *BootBall) VerifyBootconfigByID(id string) (found, verified int, err error) {
   241  	if ball.hashes == nil {
   242  		err := ball.Hash()
   243  		if err != nil {
   244  			return 0, 0, err
   245  		}
   246  	}
   247  
   248  	found = 0
   249  	verified = 0
   250  	for _, sig := range ball.signatures[id] {
   251  		err := validateCertificate(sig.Cert, ball.RootCertPEM)
   252  		if err != nil {
   253  			return found, verified, err
   254  		}
   255  		found++
   256  		err = ball.Signer.Verify(sig, ball.hashes[id])
   257  		if err != nil {
   258  			log.Print(err)
   259  		}
   260  		verified++
   261  	}
   262  	return found, verified, nil
   263  }
   264  
   265  // getConfig returns a Stconfig struct from a JSON file at src
   266  func getConfig(src string) (*Stconfig, error) {
   267  	cfgBytes, err := ioutil.ReadFile(src)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	cfg, err := stconfigFromBytes(cfgBytes)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	if !(cfg.IsValid()) {
   276  		return nil, errors.New("invalid configuration")
   277  	}
   278  	return cfg, nil
   279  }
   280  
   281  // getBootFiles returns the file paths of all files of a u-root bootconfig
   282  // for all bootconfigs in cfg.BootConfigs. Prefix is added in front of each
   283  // file path. The map's keys are set to the respective bootconfig's name.
   284  // An error is returned in case one of the files does not exist.
   285  func getBootFiles(cfg *Stconfig, prefix string) (map[string][]string, error) {
   286  	bootFiles := make(map[string][]string)
   287  	for _, bc := range cfg.BootConfigs {
   288  		files := make([]string, 0)
   289  		for _, file := range bc.FileNames() {
   290  			file = filepath.Join(prefix, file)
   291  			if _, err := os.Stat(file); err != nil {
   292  				return nil, err
   293  			}
   294  			files = append(files, file)
   295  		}
   296  		bootFiles[bc.ID()] = files
   297  	}
   298  	return bootFiles, nil
   299  }
   300  
   301  // getSignatures initializes ball.signatures with the corresponding signatures
   302  // and certificates found in the signatures folder (stboot.signaturesDirName)
   303  // of ball's underlying tmpDir (ball.dir). An error is returned if one of the
   304  // files cannot be read or parsed.
   305  func (ball *BootBall) getSignatures() error {
   306  	ball.signatures = make(map[string][]Signature)
   307  	path := filepath.Join(ball.dir, signaturesDirName)
   308  
   309  	sigPool := make([]Signature, 0)
   310  	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   311  		ext := filepath.Ext(info.Name())
   312  
   313  		if !info.IsDir() && (ext == ".signature") {
   314  			sigBytes, err := ioutil.ReadFile(path)
   315  			if err != nil {
   316  				return err
   317  			}
   318  
   319  			certFile := strings.TrimSuffix(path, filepath.Ext(path)) + ".cert"
   320  			certBytes, err := ioutil.ReadFile(certFile)
   321  			if err != nil {
   322  				return err
   323  			}
   324  
   325  			cert, err := parseCertificate(certBytes)
   326  			if err != nil {
   327  				return err
   328  			}
   329  
   330  			sig := Signature{
   331  				Bytes: sigBytes,
   332  				Cert:  cert,
   333  			}
   334  			sigPool = append(sigPool, sig)
   335  			key := filepath.Base(filepath.Dir(path))
   336  			ball.signatures[key] = sigPool
   337  		}
   338  		return nil
   339  	})
   340  	if err != nil {
   341  		return err
   342  	}
   343  	return nil
   344  }
   345  
   346  // writeSignature writes the signature represented by sig to a file in
   347  // dir along with a copy of certFile. The filenames are composed of the
   348  // first piece of the public key of the certificate.
   349  func writeSignature(dir, certFile string, sig Signature) error {
   350  	err := os.MkdirAll(dir, os.ModePerm)
   351  	if err != nil {
   352  		return err
   353  	}
   354  
   355  	id := fmt.Sprintf("%x", sig.Cert.PublicKey)[2:18]
   356  	sigName := fmt.Sprintf("%s.signature", id)
   357  	sigPath := filepath.Join(dir, sigName)
   358  	err = ioutil.WriteFile(sigPath, sig.Bytes, 0644)
   359  	if err != nil {
   360  		return err
   361  	}
   362  
   363  	certName := fmt.Sprintf("%s.cert", id)
   364  	certPath := filepath.Join(dir, certName)
   365  	return copyFile(certFile, certPath)
   366  }
   367  
   368  // makeConfigDir copies the files named in cfg to well known directory tree
   369  // inside the bootball's underlying tmpDir since the files in user's cfg can
   370  // reside anywhere in the file system. The created tmpDir is returned.
   371  // If one of the files in cfg does not exist or copying fails an error is
   372  // returned.
   373  func makeConfigDir(cfg *Stconfig, origDir string) (string, error) {
   374  	dir, err := ioutil.TempDir(os.TempDir(), "bootball")
   375  	if err != nil {
   376  		return "", err
   377  	}
   378  
   379  	dstPath := filepath.Join(dir, signaturesDirName, rootCertName)
   380  	srcPath := filepath.Join(origDir, cfg.RootCertPath)
   381  	if err := copyFile(srcPath, dstPath); err != nil {
   382  		return "", err
   383  	}
   384  
   385  	for i, bc := range cfg.BootConfigs {
   386  		dirName := bc.ID()
   387  		for _, file := range bc.FileNames() {
   388  			fileName := filepath.Base(file)
   389  			dstPath := filepath.Join(dir, dirName, fileName)
   390  			srcPath := filepath.Join(origDir, file)
   391  			if err := copyFile(srcPath, dstPath); err != nil {
   392  				return "", err
   393  			}
   394  		}
   395  
   396  		bc.ChangeFilePaths(dirName)
   397  		cfg.BootConfigs[i] = bc
   398  	}
   399  
   400  	dstPath = filepath.Join(dir, ConfigName)
   401  	bytes, err := cfg.bytes()
   402  	if err != nil {
   403  		return "", err
   404  	}
   405  	err = ioutil.WriteFile(dstPath, bytes, os.ModePerm)
   406  	if err != nil {
   407  		return "", err
   408  	}
   409  
   410  	return dir, nil
   411  }
   412  
   413  // copyFiles copies the content of the file at src to dst. If dst does not
   414  // exist it is created. In case case src does not exist, creation of dst
   415  // or copying fails an error is returned
   416  func copyFile(src, dst string) error {
   417  	in, err := os.Open(src)
   418  	if err != nil {
   419  		return err
   420  	}
   421  	defer in.Close()
   422  
   423  	if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
   424  		return err
   425  	}
   426  
   427  	out, err := os.Create(dst)
   428  	if err != nil {
   429  		return err
   430  	}
   431  	defer out.Close()
   432  
   433  	if _, err = io.Copy(out, in); err != nil {
   434  		return err
   435  	}
   436  
   437  	return out.Sync()
   438  }