github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/pkg/bootconfig/zipconfig.go (about) 1 // Copyright 2017-2019 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 bootconfig 6 7 import ( 8 "archive/zip" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "log" 14 "os" 15 "path" 16 17 "github.com/u-root/u-root/pkg/crypto" 18 "golang.org/x/crypto/ed25519" 19 ) 20 21 // memoryZipReader is used to unpack a zip file from a byte sequence in memory. 22 type memoryZipReader struct { 23 Content []byte 24 } 25 26 func (r *memoryZipReader) ReadAt(p []byte, offset int64) (n int, err error) { 27 cLen := int64(len(r.Content)) 28 if offset > cLen { 29 return 0, io.EOF 30 } 31 if cLen-offset >= int64(len(p)) { 32 n = len(p) 33 err = nil 34 } else { 35 err = io.EOF 36 n = int(int64(cLen) - offset) 37 } 38 copy(p, r.Content[offset:int(offset)+n]) 39 return n, err 40 } 41 42 // FromZip tries to extract a boot configuration from a ZIP file after verifying 43 // its signature with the provided public key file. The signature is expected to 44 // be appended to the ZIP file and have fixed length `ed25519.SignatureSize` . 45 // The returned string argument is the temporary directory where the files were 46 // extracted, if successful. 47 // No decoder (e.g. JSON, ZIP) or other function parsing the input file is called 48 // before verifying the signature. 49 func FromZip(filename string, pubkeyfile *string) (*Manifest, string, error) { 50 // load the whole zip file in memory - we need it anyway for the signature 51 // matching. 52 // TODO refuse to read if too big? 53 data, err := ioutil.ReadFile(filename) 54 if err != nil { 55 return nil, "", err 56 } 57 crypto.TryMeasureData(crypto.BlobPCR, data, filename) 58 zipbytes := data 59 // Load the public key and, if a valid one is specified, match the 60 // signature. The signature is appended to the ZIP file, and can be present 61 // or not. A ZIP file is still valid if arbitrary content is appended after 62 // its end. 63 if pubkeyfile != nil { 64 zipbytes = data[:len(data)-ed25519.SignatureSize] 65 pubkey, err := crypto.LoadPublicKeyFromFile(*pubkeyfile) 66 if err != nil { 67 return nil, "", err 68 } 69 70 // Load the signature. 71 // The signature is appended to the zip file and has length 72 // `ed25519.SignatureSize`. We read these bytes from the end of the file and 73 // treat them as the attached signature. 74 signature := data[len(data)-ed25519.SignatureSize:] 75 if len(signature) != ed25519.SignatureSize { 76 return nil, "", fmt.Errorf("short read when reading signature: want %d bytes, got %d", ed25519.SignatureSize, len(signature)) 77 } 78 79 // Verify the signature against the public key and the zip file bytes 80 if ok := ed25519.Verify(pubkey, zipbytes, signature); !ok { 81 return nil, "", fmt.Errorf("invalid ed25519 signature for file %s", filename) 82 } 83 log.Printf("Signature is valid") 84 } else { 85 log.Printf("No public key specified, the ZIP file will be unpacked without verification") 86 } 87 88 // At this point the signature is valid. Unzip the file and decode the boot 89 // configuration. 90 r, err := zip.NewReader(&memoryZipReader{Content: zipbytes}, int64(len(zipbytes))) 91 if err != nil { 92 return nil, "", err 93 } 94 tempDir, err := ioutil.TempDir(os.TempDir(), "bootconfig") 95 if err != nil { 96 return nil, "", err 97 } 98 log.Printf("Created temporary directory %s", tempDir) 99 var manifest *Manifest 100 for _, f := range r.File { 101 destination := path.Join(tempDir, f.Name) 102 if len(f.Name) == 0 { 103 log.Printf("Warning: skipping zero-length file name (flags: %d, mode: %s)", f.Flags, f.Mode()) 104 continue 105 } 106 if f.Name[len(f.Name)-1] == '/' { 107 // it's a directory, create it 108 if err := os.MkdirAll(destination, os.ModeDir|os.FileMode(0700)); err != nil { 109 return nil, "", err 110 } 111 log.Printf("Extracted directory %s (flags: %d)", f.Name, f.Flags) 112 } else { 113 fd, err := f.Open() 114 if err != nil { 115 return nil, "", err 116 } 117 buf, err := ioutil.ReadAll(fd) 118 if err != nil { 119 return nil, "", err 120 } 121 if f.Name == "manifest.json" { 122 // make sure it's not a duplicate manifest within the ZIP file 123 // and inform the user otherwise 124 if manifest != nil { 125 log.Printf("Warning: duplicate manifest.json found, the last found wins") 126 } 127 // parse the Manifest containing the boot configurations 128 manifest, err = ManifestFromBytes(buf) 129 if err != nil { 130 return nil, "", err 131 } 132 } 133 if err := ioutil.WriteFile(destination, buf, f.Mode()); err != nil { 134 return nil, "", err 135 } 136 log.Printf("Extracted file %s (flags: %d, mode: %s)", f.Name, f.Flags, f.Mode()) 137 } 138 } 139 if manifest == nil { 140 return nil, "", errors.New("no manifest found") 141 } 142 return manifest, tempDir, nil 143 }