blob: 5b2812020337dd8ba3cca88b87f73c1c0e194e31 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package image
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/asn1"
"encoding/binary"
"encoding/hex"
"io"
"io/ioutil"
"math/big"
"mynewt.apache.org/newt/util"
)
type ImageCreator struct {
Body []byte
Version ImageVersion
SigKeys []ImageSigKey
PlainSecret []byte
CipherSecret []byte
HeaderSize int
InitialHash []byte
Bootable bool
}
type ImageCreateOpts struct {
SrcBinFilename string
SrcEncKeyFilename string
Version ImageVersion
SigKeys []ImageSigKey
LoaderHash []byte
}
type ECDSASig struct {
R *big.Int
S *big.Int
}
func NewImageCreator() ImageCreator {
return ImageCreator{
HeaderSize: IMAGE_HEADER_SIZE,
Bootable: true,
}
}
func generateEncTlv(cipherSecret []byte) (ImageTlv, error) {
var encType uint8
if len(cipherSecret) == 256 {
encType = IMAGE_TLV_ENC_RSA
} else if len(cipherSecret) == 24 {
encType = IMAGE_TLV_ENC_KEK
} else {
return ImageTlv{}, util.FmtNewtError("Invalid enc TLV size ")
}
return ImageTlv{
Header: ImageTlvHdr{
Type: encType,
Pad: 0,
Len: uint16(len(cipherSecret)),
},
Data: cipherSecret,
}, nil
}
func generateSigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
opts := rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
}
signature, err := rsa.SignPSS(
rand.Reader, key, crypto.SHA256, hash, &opts)
if err != nil {
return nil, util.FmtNewtError("Failed to compute signature: %s", err)
}
return signature, nil
}
func generateSigTlvRsa(key ImageSigKey, hash []byte) (ImageTlv, error) {
sig, err := generateSigRsa(key.Rsa, hash)
if err != nil {
return ImageTlv{}, err
}
return ImageTlv{
Header: ImageTlvHdr{
Type: key.sigTlvType(),
Pad: 0,
Len: 256, /* 2048 bits */
},
Data: sig,
}, nil
}
func generateSigEc(key *ecdsa.PrivateKey, hash []byte) ([]byte, error) {
r, s, err := ecdsa.Sign(rand.Reader, key, hash)
if err != nil {
return nil, util.FmtNewtError("Failed to compute signature: %s", err)
}
ECDSA := ECDSASig{
R: r,
S: s,
}
signature, err := asn1.Marshal(ECDSA)
if err != nil {
return nil, util.FmtNewtError("Failed to construct signature: %s", err)
}
return signature, nil
}
func generateSigTlvEc(key ImageSigKey, hash []byte) (ImageTlv, error) {
sig, err := generateSigEc(key.Ec, hash)
if err != nil {
return ImageTlv{}, err
}
sigLen := key.sigLen()
if len(sig) > int(sigLen) {
return ImageTlv{}, util.FmtNewtError("Something is really wrong\n")
}
b := &bytes.Buffer{}
if _, err := b.Write(sig); err != nil {
return ImageTlv{},
util.FmtNewtError("Failed to append sig: %s", err.Error())
}
pad := make([]byte, int(sigLen)-len(sig))
if _, err := b.Write(pad); err != nil {
return ImageTlv{}, util.FmtNewtError(
"Failed to serialize image trailer: %s", err.Error())
}
return ImageTlv{
Header: ImageTlvHdr{
Type: key.sigTlvType(),
Pad: 0,
Len: sigLen,
},
Data: b.Bytes(),
}, nil
}
func generateSigTlv(key ImageSigKey, hash []byte) (ImageTlv, error) {
key.assertValid()
if key.Rsa != nil {
return generateSigTlvRsa(key, hash)
} else {
return generateSigTlvEc(key, hash)
}
}
func generateKeyHashTlv(key ImageSigKey) (ImageTlv, error) {
key.assertValid()
keyHash, err := key.sigKeyHash()
if err != nil {
return ImageTlv{}, util.FmtNewtError(
"Failed to compute hash of the public key: %s", err.Error())
}
return ImageTlv{
Header: ImageTlvHdr{
Type: IMAGE_TLV_KEYHASH,
Pad: 0,
Len: uint16(len(keyHash)),
},
Data: keyHash,
}, nil
}
func GenerateSigTlvs(keys []ImageSigKey, hash []byte) ([]ImageTlv, error) {
var tlvs []ImageTlv
for _, key := range keys {
key.assertValid()
tlv, err := generateKeyHashTlv(key)
if err != nil {
return nil, err
}
tlvs = append(tlvs, tlv)
tlv, err = generateSigTlv(key, hash)
if err != nil {
return nil, err
}
tlvs = append(tlvs, tlv)
}
return tlvs, nil
}
func GenerateImage(opts ImageCreateOpts) (Image, error) {
ic := NewImageCreator()
srcBin, err := ioutil.ReadFile(opts.SrcBinFilename)
if err != nil {
return Image{}, util.FmtNewtError(
"Can't read app binary: %s", err.Error())
}
ic.Body = srcBin
ic.Version = opts.Version
ic.SigKeys = opts.SigKeys
if opts.LoaderHash != nil {
ic.InitialHash = opts.LoaderHash
ic.Bootable = false
} else {
ic.Bootable = true
}
if opts.SrcEncKeyFilename != "" {
plainSecret := make([]byte, 16)
if _, err := rand.Read(plainSecret); err != nil {
return Image{}, util.FmtNewtError(
"Random generation error: %s\n", err)
}
cipherSecret, err := ReadEncKey(opts.SrcEncKeyFilename, plainSecret)
if err != nil {
return Image{}, err
}
ic.PlainSecret = plainSecret
ic.CipherSecret = cipherSecret
}
ri, err := ic.Create()
if err != nil {
return Image{}, err
}
return ri, nil
}
func calcHash(initialHash []byte, hdr ImageHdr,
plainBody []byte) ([]byte, error) {
hash := sha256.New()
add := func(itf interface{}) error {
b := &bytes.Buffer{}
if err := binary.Write(b, binary.LittleEndian, itf); err != nil {
return err
}
if err := binary.Write(hash, binary.LittleEndian, itf); err != nil {
return util.FmtNewtError("Failed to hash data: %s", err.Error())
}
return nil
}
if initialHash != nil {
if err := add(initialHash); err != nil {
return nil, err
}
}
if err := add(hdr); err != nil {
return nil, err
}
extra := hdr.HdrSz - IMAGE_HEADER_SIZE
if extra > 0 {
b := make([]byte, extra)
if err := add(b); err != nil {
return nil, err
}
}
if err := add(plainBody); err != nil {
return nil, err
}
return hash.Sum(nil), nil
}
func (ic *ImageCreator) Create() (Image, error) {
ri := Image{}
// First the header
hdr := ImageHdr{
Magic: IMAGE_MAGIC,
Pad1: 0,
HdrSz: IMAGE_HEADER_SIZE,
Pad2: 0,
ImgSz: uint32(len(ic.Body)),
Flags: 0,
Vers: ic.Version,
Pad3: 0,
}
if !ic.Bootable {
hdr.Flags |= IMAGE_F_NON_BOOTABLE
}
if ic.CipherSecret != nil {
hdr.Flags |= IMAGE_F_ENCRYPTED
}
if ic.HeaderSize != 0 {
// Pad the header out to the given size. There will
// just be zeros between the header and the start of
// the image when it is padded.
extra := ic.HeaderSize - IMAGE_HEADER_SIZE
if extra < 0 {
return ri, util.FmtNewtError("Image header must be at "+
"least %d bytes", IMAGE_HEADER_SIZE)
}
hdr.HdrSz = uint16(ic.HeaderSize)
for i := 0; i < extra; i++ {
ri.Body = append(ri.Body, 0)
}
}
ri.Header = hdr
hashBytes, err := calcHash(ic.InitialHash, hdr, ic.Body)
if err != nil {
return ri, err
}
var stream cipher.Stream
if ic.CipherSecret != nil {
block, err := aes.NewCipher(ic.PlainSecret)
if err != nil {
return ri, util.NewNewtError("Failed to create block cipher")
}
nonce := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
stream = cipher.NewCTR(block, nonce)
}
/*
* Followed by data.
*/
dataBuf := make([]byte, 16)
encBuf := make([]byte, 16)
r := bytes.NewReader(ic.Body)
w := bytes.Buffer{}
for {
cnt, err := r.Read(dataBuf)
if err != nil && err != io.EOF {
return ri, util.FmtNewtError(
"Failed to read from image body: %s", err.Error())
}
if cnt == 0 {
break
}
if ic.CipherSecret == nil {
_, err = w.Write(dataBuf[0:cnt])
} else {
stream.XORKeyStream(encBuf, dataBuf[0:cnt])
_, err = w.Write(encBuf[0:cnt])
}
if err != nil {
return ri, util.FmtNewtError(
"Failed to write to image body: %s", err.Error())
}
}
ri.Body = append(ri.Body, w.Bytes()...)
util.StatusMessage(util.VERBOSITY_VERBOSE,
"Computed Hash for image as %s\n", hex.EncodeToString(hashBytes))
// Hash TLV.
tlv := ImageTlv{
Header: ImageTlvHdr{
Type: IMAGE_TLV_SHA256,
Pad: 0,
Len: uint16(len(hashBytes)),
},
Data: hashBytes,
}
ri.Tlvs = append(ri.Tlvs, tlv)
tlvs, err := GenerateSigTlvs(ic.SigKeys, hashBytes)
if err != nil {
return ri, err
}
ri.Tlvs = append(ri.Tlvs, tlvs...)
if ic.CipherSecret != nil {
tlv, err := generateEncTlv(ic.CipherSecret)
if err != nil {
return ri, err
}
ri.Tlvs = append(ri.Tlvs, tlv)
}
return ri, nil
}