github.com/usbarmory/armory-boot@v0.0.0-20240307133412-208c66a380b9/config/config.go (about)

     1  // https://github.com/usbarmory/armory-boot
     2  //
     3  // Copyright (c) WithSecure Corporation
     4  // https://foundry.withsecure.com
     5  //
     6  // Use of this source code is governed by the license
     7  // that can be found in the LICENSE file.
     8  
     9  // Package config provides parsing for the armory-boot configuration file
    10  // format.
    11  package config
    12  
    13  import (
    14  	"encoding/json"
    15  	"errors"
    16  	"fmt"
    17  	"log"
    18  
    19  	"github.com/usbarmory/armory-boot/disk"
    20  )
    21  
    22  // DefaultConfigPath is the default armory-boot configuration file path.
    23  const DefaultConfigPath = "/boot/armory-boot.conf"
    24  
    25  // DefaultSignaturePath is the default armory-boot configuration file signature
    26  // path.
    27  const DefaultSignaturePath = "/boot/armory-boot.conf.sig"
    28  
    29  // Config represents the armory-boot configuration.
    30  type Config struct {
    31  	// KernelPath is the path to a Linux kernel image.
    32  	KernelPath []string `json:"kernel"`
    33  
    34  	// DeviceTreeBlobPath is the path to a Linux DTB file.
    35  	DeviceTreeBlobPath []string `json:"dtb"`
    36  
    37  	// InitialRamDiskPath is the path to a Linux initrd file.
    38  	InitialRamDiskPath []string `json:"initrd"`
    39  
    40  	// CmdLine is the Linux kernel command-line parameters.
    41  	CmdLine string `json:"cmdline"`
    42  
    43  	// Unikernel is the path to an ELF unikernel image (e.g. TamaGo).
    44  	UnikernelPath []string `json:"unikernel"`
    45  
    46  	// ELF indicates whether the loaded kernel is a unikernel or not.
    47  	ELF bool
    48  
    49  	// JSON holds the configuration file contents
    50  	JSON []byte
    51  
    52  	kernel []byte
    53  	dtb    []byte
    54  	initrd []byte
    55  
    56  	kernelHash string
    57  	dtbHash    string
    58  	initrdHash string
    59  }
    60  
    61  func (c *Config) init(part *disk.Partition) (err error) {
    62  	var kernelPath string
    63  
    64  	if err = json.Unmarshal(c.JSON, &c); err != nil {
    65  		return
    66  	}
    67  
    68  	ul, kl := len(c.UnikernelPath), len(c.KernelPath)
    69  	isUnikernel, isKernel := ul > 0, kl > 0
    70  
    71  	if isUnikernel == isKernel {
    72  		return errors.New("must specify either unikernel or kernel")
    73  	}
    74  
    75  	switch {
    76  	case isKernel:
    77  		if kl != 2 {
    78  			return errors.New("invalid kernel parameter size")
    79  		}
    80  
    81  		if len(c.DeviceTreeBlobPath) != 2 {
    82  			return errors.New("invalid dtb parameter size")
    83  		}
    84  
    85  		if len(c.InitialRamDiskPath) > 0 {
    86  			if len(c.InitialRamDiskPath) != 2 {
    87  				return errors.New("invalid initrd parameter size")
    88  			}
    89  
    90  			if c.initrd, err = part.ReadAll(c.InitialRamDiskPath[0]); err != nil {
    91  				return
    92  			}
    93  
    94  			c.initrdHash = c.InitialRamDiskPath[1]
    95  		}
    96  
    97  		kernelPath = c.KernelPath[0]
    98  		c.kernelHash = c.KernelPath[1]
    99  
   100  		if c.dtb, err = part.ReadAll(c.DeviceTreeBlobPath[0]); err != nil {
   101  			return
   102  		}
   103  
   104  		c.dtbHash = c.DeviceTreeBlobPath[1]
   105  	case isUnikernel:
   106  		if ul != 2 {
   107  			return errors.New("invalid unikernel parameter size")
   108  		}
   109  
   110  		kernelPath = c.UnikernelPath[0]
   111  		c.kernelHash = c.UnikernelPath[1]
   112  	}
   113  
   114  	if c.kernel, err = part.ReadAll(kernelPath); err != nil {
   115  		return fmt.Errorf("invalid path %s, %v", kernelPath, err)
   116  	}
   117  
   118  	if err != nil {
   119  		return fmt.Errorf("invalid path %s, %v", c.DeviceTreeBlobPath[0], err)
   120  	}
   121  
   122  	if isUnikernel {
   123  		c.ELF = true
   124  	}
   125  
   126  	return
   127  }
   128  
   129  // Load reads an armory-boot configuration file, and optionally its signature,
   130  // from a disk partition. The public key argument is used for signature
   131  // authentication, a valid signature path must be present if a key is set.
   132  func Load(part *disk.Partition, configPath string, sigPath string, pubKey string) (c *Config, err error) {
   133  	log.Printf("armory-boot: loading configuration at %s\n", configPath)
   134  
   135  	c = &Config{}
   136  
   137  	if c.JSON, err = part.ReadAll(configPath); err != nil {
   138  		return
   139  	}
   140  
   141  	if len(pubKey) > 0 {
   142  		sig, err := part.ReadAll(sigPath)
   143  
   144  		if err != nil {
   145  			return nil, fmt.Errorf("invalid signature path, %v", err)
   146  		}
   147  
   148  		if err = Verify(c.JSON, sig, pubKey); err != nil {
   149  			return nil, err
   150  		}
   151  	}
   152  
   153  	defer func() {
   154  		if err != nil {
   155  			c.kernel = nil
   156  			c.dtb = nil
   157  			c.initrd = nil
   158  		}
   159  	}()
   160  
   161  	if err = c.init(part); err != nil {
   162  		return
   163  	}
   164  
   165  	if len(pubKey) == 0 {
   166  		return
   167  	}
   168  
   169  	if !CompareHash(c.kernel, c.kernelHash) {
   170  		err = errors.New("invalid kernel hash")
   171  		return
   172  	}
   173  
   174  	if len(c.dtb) > 0 && !CompareHash(c.dtb, c.dtbHash) {
   175  		err = errors.New("invalid dtb hash")
   176  		return
   177  	}
   178  
   179  	if len(c.initrd) > 0 && !CompareHash(c.initrd, c.initrdHash) {
   180  		err = errors.New("invalid initrd hash")
   181  		return
   182  	}
   183  
   184  	return
   185  }
   186  
   187  // Kernel returns the contents of the kernel image previously loaded by a
   188  // successful Load().
   189  func (c *Config) Kernel() []byte {
   190  	return c.kernel
   191  }
   192  
   193  // DeviceTreeBlob returns the contents of the dtb file previously loaded by a
   194  // successful Load().
   195  func (c *Config) DeviceTreeBlob() []byte {
   196  	return c.dtb
   197  }
   198  
   199  // InitialRamDisk returns the contents of the initrd image previously loaded by
   200  // a successful Load().
   201  func (c *Config) InitialRamDisk() []byte {
   202  	return c.initrd
   203  }