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  }