blob: 7f6bb22204f4087b9004a928edea616789f0573d [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.
*/
// This file implements parsing and generation of version-1 images. Much of
// this code duplicates the v2 code. The expectation is that this file will be
// removed when version 1 is oficially retired (soon).
package image
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/binary"
"io"
"io/ioutil"
"github.com/apache/mynewt-artifact/errors"
"github.com/apache/mynewt-artifact/sec"
)
const IMAGEv1_MAGIC = 0x96f3b83c /* Image header magic */
const (
IMAGEv1_F_PIC = 0x00000001
IMAGEv1_F_SHA256 = 0x00000002 /* Image contains hash TLV */
IMAGEv1_F_PKCS15_RSA2048_SHA256 = 0x00000004 /* PKCS15 w/RSA2048 and SHA256 */
IMAGEv1_F_ECDSA224_SHA256 = 0x00000008 /* ECDSA224 over SHA256 */
IMAGEv1_F_NON_BOOTABLE = 0x00000010 /* non bootable image */
IMAGEv1_F_ECDSA256_SHA256 = 0x00000020 /* ECDSA256 over SHA256 */
IMAGEv1_F_PKCS1_PSS_RSA2048_SHA256 = 0x00000040 /* RSA-PSS w/RSA2048 and SHA256 */
)
const (
IMAGEv1_TLV_SHA256 = 1
IMAGEv1_TLV_RSA2048 = 2
IMAGEv1_TLV_ECDSA224 = 3
IMAGEv1_TLV_ECDSA256 = 4
)
// Set this to enable RSA-PSS for RSA signatures, instead of PKCS#1
// v1.5. Eventually, this should be the default.
var UseRsaPss = false
type ImageHdrV1 struct {
Magic uint32
TlvSz uint16
KeyId uint8
Pad1 uint8
HdrSz uint16
Pad2 uint16
ImgSz uint32
Flags uint32
Vers ImageVersion
Pad3 uint32
}
type ImageV1 struct {
Header ImageHdrV1
Body []byte
Tlvs []ImageTlv
}
func (img *ImageV1) FindTlvs(tlvType uint8) []ImageTlv {
var tlvs []ImageTlv
for _, tlv := range img.Tlvs {
if tlv.Header.Type == tlvType {
tlvs = append(tlvs, tlv)
}
}
return tlvs
}
func (img *ImageV1) Hash() ([]byte, error) {
tlvs := img.FindTlvs(IMAGEv1_TLV_SHA256)
if len(tlvs) == 0 {
return nil, errors.Errorf("image does not contain hash TLV")
}
if len(tlvs) > 1 {
return nil, errors.Errorf("image contains %d hash TLVs", len(tlvs))
}
return tlvs[0].Data, nil
}
func (img *ImageV1) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
offs := ImageOffsets{}
offset := 0
offs.Header = offset
err := binary.Write(w, binary.LittleEndian, &img.Header)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image header")
}
offset += IMAGE_HEADER_SIZE
offs.Body = offset
size, err := w.Write(img.Body)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image body")
}
offset += size
for _, tlv := range img.Tlvs {
offs.Tlvs = append(offs.Tlvs, offset)
size, err := tlv.Write(w)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image TLV")
}
offset += size
}
offs.TotalSize = offset
return offs, nil
}
func (img *ImageV1) Offsets() (ImageOffsets, error) {
return img.WritePlusOffsets(ioutil.Discard)
}
func (img *ImageV1) TotalSize() (int, error) {
offs, err := img.Offsets()
if err != nil {
return 0, err
}
return offs.TotalSize, nil
}
func (img *ImageV1) Write(w io.Writer) (int, error) {
offs, err := img.WritePlusOffsets(w)
if err != nil {
return 0, err
}
return offs.TotalSize, nil
}
func sigHdrTypeV1(key sec.PrivSignKey) (uint32, error) {
key.AssertValid()
if key.Rsa != nil {
if UseRsaPss {
return IMAGEv1_F_PKCS1_PSS_RSA2048_SHA256, nil
} else {
return IMAGEv1_F_PKCS15_RSA2048_SHA256, nil
}
} else {
switch key.Ec.Curve.Params().Name {
case "P-224":
return IMAGEv1_F_ECDSA224_SHA256, nil
case "P-256":
return IMAGEv1_F_ECDSA256_SHA256, nil
default:
return 0, errors.Errorf("unsupported ECC curve")
}
}
}
func sigTlvTypeV1(key sec.PrivSignKey) uint8 {
key.AssertValid()
if key.Rsa != nil {
return IMAGEv1_TLV_RSA2048
} else {
switch key.Ec.Curve.Params().Name {
case "P-224":
return IMAGEv1_TLV_ECDSA224
case "P-256":
return IMAGEv1_TLV_ECDSA256
default:
return 0
}
}
}
func generateV1SigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
var signature []byte
var err error
if UseRsaPss {
opts := rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
}
signature, err = rsa.SignPSS(
rand.Reader, key, crypto.SHA256, hash, &opts)
} else {
signature, err = rsa.SignPKCS1v15(
rand.Reader, key, crypto.SHA256, hash)
}
if err != nil {
return nil, errors.Wrapf(err, "failed to compute signature")
}
return signature, nil
}
func generateV1SigTlvRsa(key sec.PrivSignKey, hash []byte) (ImageTlv, error) {
sig, err := generateV1SigRsa(key.Rsa, hash)
if err != nil {
return ImageTlv{}, err
}
return ImageTlv{
Header: ImageTlvHdr{
Type: sigTlvTypeV1(key),
Pad: 0,
Len: 256, /* 2048 bits */
},
Data: sig,
}, nil
}
func generateV1SigTlvEc(key sec.PrivSignKey, hash []byte) (ImageTlv, error) {
sig, err := GenerateSigEc(key, hash)
if err != nil {
return ImageTlv{}, err
}
sigLen := key.SigLen()
if len(sig) > int(sigLen) {
return ImageTlv{}, errors.Errorf("signature truncated")
}
b := &bytes.Buffer{}
if _, err := b.Write(sig); err != nil {
return ImageTlv{}, errors.Wrapf(err, "failed to append sig")
}
pad := make([]byte, int(sigLen)-len(sig))
if _, err := b.Write(pad); err != nil {
return ImageTlv{}, errors.Wrapf(err,
"failed to serialize image trailer")
}
return ImageTlv{
Header: ImageTlvHdr{
Type: sigTlvTypeV1(key),
Pad: 0,
Len: sigLen + uint16(len(pad)),
},
Data: b.Bytes(),
}, nil
}
func generateV1SigTlv(key sec.PrivSignKey, hash []byte) (ImageTlv, error) {
key.AssertValid()
if key.Rsa != nil {
return generateV1SigTlvRsa(key, hash)
} else {
return generateV1SigTlvEc(key, hash)
}
}
func calcHashV1(initialHash []byte, hdr ImageHdrV1,
plainBody []byte) ([]byte, error) {
hash := sha256.New()
add := func(itf interface{}) error {
if err := binary.Write(hash, binary.LittleEndian, itf); err != nil {
return errors.Wrapf(err, "failed to hash data")
}
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) CreateV1() (ImageV1, error) {
ri := ImageV1{}
if len(ic.SigKeys) > 1 {
return ri, errors.Errorf(
"v1 image format only allows one key, %d keys specified",
len(ic.SigKeys))
}
// First the header
hdr := ImageHdrV1{
Magic: IMAGEv1_MAGIC,
TlvSz: 0, // Filled in later.
KeyId: 0,
Pad1: 0,
HdrSz: IMAGE_HEADER_SIZE,
Pad2: 0,
ImgSz: uint32(len(ic.Body)),
Flags: IMAGEv1_F_SHA256,
Vers: ic.Version,
Pad3: 0,
}
if !ic.Bootable {
hdr.Flags |= IMAGEv1_F_NON_BOOTABLE
}
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.
*/
if ic.HeaderSize < IMAGE_HEADER_SIZE {
return ri, errors.Errorf(
"image header must be at least %d bytes", IMAGE_HEADER_SIZE)
}
hdr.HdrSz = uint16(ic.HeaderSize)
}
if len(ic.SigKeys) > 0 {
keyFlag, err := sigHdrTypeV1(ic.SigKeys[0])
if err != nil {
return ri, err
}
hdr.Flags |= keyFlag
hdr.TlvSz = 4 + ic.SigKeys[0].SigLen()
}
hdr.TlvSz += 4 + 32
if hdr.HdrSz > IMAGE_HEADER_SIZE {
// 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, errors.Errorf(
"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)
}
}
hashBytes, err := calcHashV1(ic.InitialHash, hdr, ic.Body)
if err != nil {
return ri, err
}
/*
* Followed by data.
*/
dataBuf := make([]byte, 1024)
r := bytes.NewReader(ic.Body)
w := bytes.Buffer{}
for {
cnt, err := r.Read(dataBuf)
if err != nil && err != io.EOF {
return ri, errors.Wrapf(err, "failed to read from image body")
}
if cnt == 0 {
break
}
if _, err = w.Write(dataBuf[0:cnt]); err != nil {
return ri, errors.Wrapf(err, "failed to write to image body")
}
}
ri.Body = w.Bytes()
// Hash TLV.
tlv := ImageTlv{
Header: ImageTlvHdr{
Type: IMAGEv1_TLV_SHA256,
Pad: 0,
Len: uint16(len(hashBytes)),
},
Data: hashBytes,
}
ri.Tlvs = append(ri.Tlvs, tlv)
if len(ic.SigKeys) > 0 {
tlv, err := generateV1SigTlv(ic.SigKeys[0], hashBytes)
if err != nil {
return ri, err
}
ri.Tlvs = append(ri.Tlvs, tlv)
}
offs, err := ri.Offsets()
if err != nil {
return ri, err
}
hdr.TlvSz = uint16(offs.TotalSize - offs.Tlvs[0])
ri.Header = hdr
return ri, nil
}
func GenerateV1Image(opts ImageCreateOpts) (ImageV1, error) {
ic := NewImageCreator()
srcBin, err := ioutil.ReadFile(opts.SrcBinFilename)
if err != nil {
return ImageV1{}, errors.Wrapf(err, "can't read app binary")
}
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, err := GeneratePlainSecret()
if err != nil {
return ImageV1{}, err
}
pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename)
if err != nil {
return ImageV1{}, errors.Wrapf(err, "error reading pubkey file")
}
pubKe, err := sec.ParsePubEncKey(pubKeBytes)
if err != nil {
return ImageV1{}, err
}
cipherSecret, err := pubKe.Encrypt(plainSecret)
if err != nil {
return ImageV1{}, err
}
ic.PlainSecret = plainSecret
ic.CipherSecret = cipherSecret
}
ri, err := ic.CreateV1()
if err != nil {
return ImageV1{}, err
}
return ri, nil
}