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 }