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 }