348 lines
6.9 KiB
Go
348 lines
6.9 KiB
Go
// 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}} },
|
|
};
|
|
`))
|