This closes #10.
Merge remote-tracking branch 'd3zd3z/imgtool'
* d3zd3z/imgtool:
imgtool: Add support for RSA keys
imgtool: Add support for P-224
imgtool: Create simple signing tool
diff --git a/imgtool/README.rst b/imgtool/README.rst
new file mode 100644
index 0000000..eb4ab1a
--- /dev/null
+++ b/imgtool/README.rst
@@ -0,0 +1,108 @@
+Image Manipulation Tool
+#######################
+
+Introduction
+============
+
+The *mcuboot* bootloader began as the bootloader for the Mynewt_
+project. This project's *newt* tool, in addition to configuration and
+compilation, also contains code to manipulate image headers and apply
+signatures.
+
+.. _Mynewt: https://mynewt.apache.org/
+
+In order to use *mcuboot* with other OSes (starting with Zephyr), we
+need a tool that doesn't depend on being run inside of a *Mynewt*
+project. This ``imgtool`` command is a small program that uses the
+*newt* tool's library as a small image manipulation tool
+
+Building It
+===========
+
+The *newt* tool is written in Go_, and as such, this imgtool is also.
+In order to build from source, you will need the Go toolchain
+installed. On most distributions, this can be accomplished by
+installing a ``go`` or ``golang`` package. The tool is driven by a
+program ``go``, and::
+
+ $ go version
+
+.. _Go: https://golang.org/
+
+should print out the version installed.
+
+Go prefers a fairly specific source tree layout, and it is easiest to
+work with if you keep this layout. To do this, you will need a
+directory to hold your go source. Once you've created this, the
+environment variable ``GOPATH`` needs to be set to point to this
+directory. Once this is done, you can get this code, and its
+necessary dependencies by running::
+
+ $ go get github.com/runtimeco/mcuboot/imgtool
+
+and the package can be built with::
+
+ $ go install github.com/runtimeco/mcuboot/imgtool
+
+This will place the build tool in ``$GOPATH/bin/imgtool``. Feel free
+to either place this directory in your path, or move the image to
+somewhere in your path.
+
+Usage
+=====
+
+Run ``imgtool`` either with no arguments, or with ``help`` to get more
+help. The tool is capable of generating the signing keys needed for
+the bootloader, extracting the public key to embed in the image, and
+signing images.
+
+Generating keys
+---------------
+
+Keys are generated with the ``keygen`` subcommand. The ``-k`` option
+allows you to specify a different file for the resulting key. The key
+is stored in *PEM* format.
+
+You must specify a ``--key-type`` parameter. Current valid entries
+are:
+
+.. list-table:: Key types
+ :header-rows: 1
+
+ * - Parameter
+ - Description
+ * - ecdsa-p224
+ - ECDSA with SHA256 and NIST P-224 (secp224r1)
+ * - ecdsa-p256
+ - ECDSA with SHA256 and NIST P-256 (secp256r1)
+
+Extracting the public key
+-------------------------
+
+In order to use a key other than the key distributed in the Zephyr
+directory, it needs to be extracted from the private key. The
+``getpub`` subcommand does this. The ``-k`` option can be used to
+specify a keyfile. The program will then output the public key to
+stdout. This should be written to a ``.cc`` file and included in the
+build. For Zephyr builds, this is in the file ``boot/zephyr/keys.c``
+in the main tree.
+
+Signing images
+--------------
+
+In order for the bootloader to be able to upgrade an image, it must
+have a header prefixed to it, and a signature placed at the end. The
+``sign`` subcommand can perform these operations.
+
+In order to use Zephyr images, you must set CONFIG_TEXT_SECTION_OFFSET
+in the main Zephyr program you want to build. The exact value depends
+on the particular target. The FRDM-K64F, 0x200 should be used.
+
+The ``sign`` command needs to be told this header offset as well, with
+the ``--header-size 0x200`` argument.
+
+The ``sign`` command is also able to mark images for upgrade. This is
+done with the ``--pad``, and ``--align`` arguments. The pad should be
+set to the size of the image slot (in bytes), and the alignment needs
+to be set to the write alignment of the flash device. For FRDM-K64F,
+the alignment is 8.
diff --git a/imgtool/imgtool.go b/imgtool/imgtool.go
new file mode 100644
index 0000000..b880244
--- /dev/null
+++ b/imgtool/imgtool.go
@@ -0,0 +1,347 @@
+// The image tool.
+//
+// A standalone tool to manipulate images.
+package main
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/asn1"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math/big"
+ "os"
+ "strings"
+ "text/template"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var keyFile string
+var keyType KeyGenerator
+
+func main() {
+ root := &cobra.Command{
+ Use: "imgtool command args ...",
+ Short: "Manipulate boot images",
+ Run: func(cmd *cobra.Command, args []string) {
+ cmd.Usage()
+ log.Fatal("Invalid usage")
+ },
+ }
+
+ fl := root.PersistentFlags()
+ fl.StringVarP(&keyFile, "key", "k", "root_ec.pem", "Keyfile to use")
+
+ keygen := &cobra.Command{
+ Use: "keygen",
+ Short: "Generate an ECDSA P-256 private key",
+ Run: doKeyGen,
+ }
+
+ fl = keygen.Flags()
+ fl.VarP(&keyType, "key-type", "t", "Type of key to generate")
+
+ root.AddCommand(keygen)
+
+ getpub := &cobra.Command{
+ Use: "getpub",
+ Short: "Extract the public key as C code",
+ Run: doGetPub,
+ }
+
+ root.AddCommand(getpub)
+
+ root.AddCommand(setupSign())
+
+ if err := root.Execute(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func doKeyGen(cmd *cobra.Command, args []string) {
+ if keyType.generate == nil {
+ cmd.Usage()
+ log.Fatal("Must specify key type with --key-type")
+ }
+
+ if len(args) != 0 {
+ cmd.Usage()
+ log.Fatal("Expecting no arguments to keygen")
+ }
+
+ priv509, err := keyType.generate()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fd, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fd.Close()
+
+ block := pem.Block{
+ Type: keyType.pemType,
+ Bytes: priv509,
+ }
+ err = pem.Encode(fd, &block)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+var keyGens map[string]*KeyGenerator
+
+type KeyGenerator struct {
+ name string
+ description string
+ pemType string
+ generate func() ([]byte, error)
+}
+
+func (g *KeyGenerator) Set(text string) error {
+ kg, ok := keyGens[text]
+ if !ok {
+ return errors.New("Unsupported key type")
+ }
+
+ *g = *kg
+ return nil
+}
+
+func (g *KeyGenerator) String() string {
+ return g.name
+}
+
+func (g *KeyGenerator) Type() string {
+ return "keytype"
+}
+
+func init() {
+ keyGens = make(map[string]*KeyGenerator)
+
+ kg := &KeyGenerator{
+ name: "ecdsa-p256",
+ description: "ECDSA with SHA256 and the NIST P-256 curve",
+ pemType: "EC PRIVATE KEY",
+ generate: genEcdsaP256,
+ }
+ keyGens[kg.name] = kg
+
+ kg = &KeyGenerator{
+ name: "ecdsa-p224",
+ description: "ECDSA with SHA256 and the NIST P-224 curve",
+ pemType: "EC PRIVATE KEY",
+ generate: genEcdsaP224,
+ }
+ keyGens[kg.name] = kg
+
+ kg = &KeyGenerator{
+ name: "rsa-2048",
+ description: "RSA 2048",
+ pemType: "RSA PRIVATE KEY",
+ generate: genRSA2048,
+ }
+ keyGens[kg.name] = kg
+}
+
+func genEcdsaP224() ([]byte, error) {
+ priv, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+
+ return x509.MarshalECPrivateKey(priv)
+}
+
+func genEcdsaP256() ([]byte, error) {
+ priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+
+ return x509.MarshalECPrivateKey(priv)
+}
+
+func genRSA2048() ([]byte, error) {
+ priv, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return nil, err
+ }
+
+ return x509.MarshalPKCS1PrivateKey(priv), nil
+}
+
+func doGetPub(cmd *cobra.Command, args []string) {
+ data, err := ioutil.ReadFile(keyFile)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ block, data := pem.Decode(data)
+
+ // openssl will sometimes generate this extra parameters
+ // fields at the top (although it is included in the private
+ // key as well). If we see this, just read the next block.
+ if block.Type == "EC PARAMETERS" {
+ block, data = pem.Decode(data)
+ }
+ // fmt.Printf("type=%q, headers=%v, data=\n%s", block.Type, block.Headers, hex.Dump(block.Bytes))
+
+ if block.Type == "EC PRIVATE KEY" {
+ dumpECPub(block)
+ } else if block.Type == "RSA PRIVATE KEY" {
+ dumpRSAPub(block)
+ } else {
+ log.Fatal("Only supports ECDSA and RSA keys")
+ }
+}
+
+func dumpECPub(block *pem.Block) {
+ privateKey, err := x509.ParseECPrivateKey(block.Bytes)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // fmt.Printf("priv: %+v\n", privateKey)
+
+ // Dump out the public key as a nice structure.
+ // fmt.Printf("x = %x\n", privateKey.X.Bytes())
+ // fmt.Printf("y = %x\n", privateKey.Y.Bytes())
+
+ // tdata := make(map[string]string)
+ // tdata["x"] = formatCData(privateKey.X.Bytes(), 2)
+ // tdata["y"] = formatCData(privateKey.Y.Bytes(), 2)
+ // err = ecKeyTemplate.Execute(os.Stdout, tdata)
+ // if err != nil {
+ // log.Fatal(err)
+ // }
+
+ // The public key needs the algorithm and curve parameters.
+ var curve []int
+ switch privateKey.Params().Name {
+ case "P-224":
+ curve = []int{1, 3, 132, 0, 33}
+ case "P-256":
+ curve = []int{1, 2, 840, 10045, 3, 1, 7}
+ default:
+ log.Fatal("Key uses unsupported curve: %q", privateKey.Params().Name)
+ }
+
+ // The public key is encoded uncompressed, as a concatenation
+ // of the bytes.
+ var bbuf bytes.Buffer
+ bbuf.WriteByte(0x04)
+ bbuf.Write(privateKey.X.Bytes())
+ bbuf.Write(privateKey.Y.Bytes())
+ pkeyBytes := bbuf.Bytes()
+
+ pkey := EcPublicKey{
+ Algorithm: AlgorithmId{
+ Algorithm: []int{1, 2, 840, 10045, 2, 1},
+ Curve: curve,
+ },
+ PubKey: asn1.BitString{
+ Bytes: pkeyBytes,
+ BitLength: len(pkeyBytes) * 8,
+ },
+ }
+ asnBytes, err := asn1.Marshal(pkey)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // fmt.Print(hex.Dump(asnBytes))
+ fmt.Printf(`/* Autogenerated, do not edit */
+
+const unsigned char ec_pub_key[] = {
+ %s };
+const unsigned int ec_pub_key_len = %d;
+`,
+ formatCData(asnBytes, 1), len(asnBytes))
+}
+
+func dumpRSAPub(block *pem.Block) {
+ privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ pubKey := RSAPublicKey{
+ N: privateKey.N,
+ E: privateKey.E,
+ }
+
+ asnBytes, err := asn1.Marshal(pubKey)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf(`/* Autogenerated, do not edit */
+
+const unsigned char rsa_pub_key[] = {
+ %s };
+const unsigned int ec_pub_key_len = %d;
+`,
+ formatCData(asnBytes, 1), len(asnBytes))
+}
+
+// ecPublicKey represents an ASN.1 Elliptic Curve Public Key structure
+type EcPublicKey struct {
+ Algorithm AlgorithmId
+ PubKey asn1.BitString
+}
+
+type RSAPublicKey struct {
+ N *big.Int
+ E int
+}
+
+type AlgorithmId struct {
+ Algorithm asn1.ObjectIdentifier
+ Curve asn1.ObjectIdentifier
+}
+
+// Format a byte slice as 'C' data, with the given indentation on
+// subsequent lines.
+func formatCData(data []byte, indent int) string {
+ buf := new(bytes.Buffer)
+
+ indText := strings.Repeat("\t", indent)
+
+ for i, b := range data {
+ if i%8 == 0 {
+ if i > 0 {
+ fmt.Fprintf(buf, "\n%s", indText)
+ }
+ } else {
+ fmt.Fprintf(buf, " ")
+ }
+ fmt.Fprintf(buf, "0x%02x,", b)
+
+ }
+
+ return buf.String()
+}
+
+var ecKeyTemplate = template.Must(template.New("eckey").Parse(`
+/* Autogenerated, do not edit. */
+
+#include <stdint.h>
+
+struct ec_key {
+ uint8_t x[32];
+ uint8_t y[32];
+};
+
+struct ec_key ec_pub_key = {
+ .x = { {{.x}} },
+ .y = { {{.y}} },
+};
+`))
diff --git a/imgtool/sign.go b/imgtool/sign.go
new file mode 100644
index 0000000..b53279e
--- /dev/null
+++ b/imgtool/sign.go
@@ -0,0 +1,147 @@
+package main
+
+import (
+ "errors"
+ "os"
+ "strconv"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/spf13/cobra"
+
+ "mynewt.apache.org/newt/newt/image"
+)
+
+var version string
+var headerSize uint
+var signAlign alignment = 1
+var padTo int64
+
+func setupSign() *cobra.Command {
+ sign := &cobra.Command{
+ Use: "sign infile outfile",
+ Short: "Sign image with key",
+ Run: doSign,
+ }
+
+ fl := sign.Flags()
+ fl.StringVarP(&version, "version", "v", "1.0", "Version to stamp image with")
+ fl.UintVarP(&headerSize, "header-size", "H", 0, "Header size to use")
+ fl.Var(&signAlign, "align", "Alignment of flash device")
+ fl.Int64Var(&padTo, "pad", 0, "Pad image to this many bytes, adding trailer magic")
+
+ return sign
+}
+
+type alignment uint
+
+func (a alignment) String() string {
+ return strconv.FormatUint(uint64(a), 10)
+}
+
+func (a alignment) Type() string {
+ return "alignment"
+}
+
+func (a *alignment) Set(text string) error {
+ value, err := strconv.ParseUint(text, 10, 8)
+ if err != nil {
+ return err
+ }
+
+ switch value {
+ case 1, 2, 4, 8:
+ *a = alignment(value)
+ return nil
+ default:
+ return errors.New("Invalid alignment, must be one of 1,2,4,8")
+ }
+}
+
+func doSign(cmd *cobra.Command, args []string) {
+ if len(args) != 2 {
+ cmd.Usage()
+ log.Fatal("Expecting infile and outfile arguments")
+ }
+
+ img, err := image.NewImage(args[0], args[1])
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = img.SetVersion(version)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = img.SetSigningKey(keyFile, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // For Zephyr, we want to both skip and use the header size.
+ img.SrcSkip = headerSize
+ img.HeaderSize = headerSize
+
+ err = img.Generate(nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if padTo > 0 {
+ err = padImage(args[1])
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
+// Pad the image to a given position, and write the image trailer.
+func padImage(name string) error {
+ f, err := os.OpenFile(name, os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ info, err := f.Stat()
+ if err != nil {
+ return err
+ }
+
+ var trailerSize int64
+ switch signAlign {
+ case 1:
+ trailerSize = 402
+ case 2:
+ trailerSize = 788
+ case 4:
+ trailerSize = 1560
+ case 8:
+ trailerSize = 3104
+ default:
+ panic("Invalid alignment")
+ }
+
+ if info.Size() > padTo-trailerSize {
+ return errors.New("Image is too large for specified padding")
+ }
+
+ _, err = f.WriteAt(bootMagic, padTo-trailerSize)
+ if err != nil {
+ return err
+ }
+
+ err = f.Truncate(padTo)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+var bootMagic = []byte{
+ 0x77, 0xc2, 0x95, 0xf3,
+ 0x60, 0xd2, 0xef, 0x7f,
+ 0x35, 0x52, 0x50, 0x0f,
+ 0x2c, 0xb6, 0x79, 0x80,
+}