blob: 7defa3879dc186d8f845765d7743f22e29bb66a9 [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 (
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/apache/mynewt-artifact/errors"
"github.com/apache/mynewt-artifact/sec"
)
const (
IMAGE_MAGIC = 0x96f3b83d /* Image header magic */
IMAGE_TRAILER_MAGIC = 0x6907 /* TLV info magic */
IMAGE_PROT_TRAILER_MAGIC = 0x6908 /* Protected TLV info magic */
)
const (
IMAGE_HEADER_SIZE = 32
IMAGE_TRAILER_SIZE = 4
IMAGE_TLV_SIZE = 4 /* Plus `value` field. */
)
/*
* Image header flags.
*/
const (
IMAGE_F_PIC = 0x00000001
IMAGE_F_ENCRYPTED = 0x00000004 /* encrypted image */
IMAGE_F_NON_BOOTABLE = 0x00000010 /* non bootable image */
)
/*
* Image trailer TLV types.
*/
const (
IMAGE_TLV_KEYHASH = 0x01
IMAGE_TLV_SHA256 = 0x10
IMAGE_TLV_RSA2048 = 0x20
IMAGE_TLV_ECDSA224 = 0x21
IMAGE_TLV_ECDSA256 = 0x22
IMAGE_TLV_RSA3072 = 0x23
IMAGE_TLV_ED25519 = 0x24
IMAGE_TLV_ENC_RSA = 0x30
IMAGE_TLV_ENC_KEK = 0x31
IMAGE_TLV_ENC_EC256 = 0x32
IMAGE_TLV_AES_NONCE_LEGACY = 0x50
IMAGE_TLV_SECRET_ID_LEGACY = 0x60
IMAGE_TLV_AES_NONCE = 0xa1
IMAGE_TLV_SECRET_ID = 0xa2
IMAGE_TLV_SECTION = 0xa3
)
var imageTlvTypeNameMap = map[uint8]string{
IMAGE_TLV_KEYHASH: "KEYHASH",
IMAGE_TLV_SHA256: "SHA256",
IMAGE_TLV_RSA2048: "RSA2048",
IMAGE_TLV_ECDSA224: "ECDSA224",
IMAGE_TLV_ECDSA256: "ECDSA256",
IMAGE_TLV_RSA3072: "RSA3072",
IMAGE_TLV_ED25519: "ED25519",
IMAGE_TLV_ENC_RSA: "ENC_RSA",
IMAGE_TLV_ENC_KEK: "ENC_KEK",
IMAGE_TLV_ENC_EC256: "ENC_EC256",
IMAGE_TLV_AES_NONCE: "AES_NONCE",
IMAGE_TLV_SECRET_ID: "SEC_KEY_ID",
IMAGE_TLV_AES_NONCE_LEGACY: "AES_NONCE",
IMAGE_TLV_SECRET_ID_LEGACY: "SEC_KEY_ID",
IMAGE_TLV_SECTION: "SECTION",
}
var imageTlvTypeSigTypeMap = map[uint8]sec.SigType{
IMAGE_TLV_RSA2048: sec.SIG_TYPE_RSA2048,
IMAGE_TLV_ECDSA224: sec.SIG_TYPE_ECDSA224,
IMAGE_TLV_ECDSA256: sec.SIG_TYPE_ECDSA256,
IMAGE_TLV_RSA3072: sec.SIG_TYPE_RSA3072,
IMAGE_TLV_ED25519: sec.SIG_TYPE_ED25519,
}
type ImageVersion struct {
Major uint8
Minor uint8
Rev uint16
BuildNum uint32
}
type ImageHdr struct {
Magic uint32
Pad1 uint32
HdrSz uint16
ProtSz uint16
ImgSz uint32
Flags uint32
Vers ImageVersion
Pad3 uint32
}
type ImageTlvHdr struct {
Type uint8
Pad uint8
Len uint16
}
type ImageTlv struct {
Header ImageTlvHdr
Data []byte
}
type ImageTrailer struct {
Magic uint16
TlvTotLen uint16
}
type Image struct {
Header ImageHdr
Pad []byte
Body []byte
ProtTlvs []ImageTlv
Tlvs []ImageTlv
}
type ImageOffsets struct {
Header int
Body int
ProtTrailer int
Trailer int
ProtTlvs []int
Tlvs []int
TotalSize int
}
type Section struct {
Name string
Size int
Offset int
}
func ImageTlvTypeIsValid(tlvType uint8) bool {
_, ok := imageTlvTypeNameMap[tlvType]
return ok
}
func ImageTlvTypeName(tlvType uint8) string {
name, ok := imageTlvTypeNameMap[tlvType]
if !ok {
return "???"
}
return name
}
func ImageTlvTypeToSigType(tlvType uint8) (sec.SigType, bool) {
typ, ok := imageTlvTypeSigTypeMap[tlvType]
return typ, ok
}
func ImageTlvTypeIsSig(tlvType uint8) bool {
return tlvType == IMAGE_TLV_RSA2048 ||
tlvType == IMAGE_TLV_RSA3072 ||
tlvType == IMAGE_TLV_ECDSA224 ||
tlvType == IMAGE_TLV_ECDSA256 ||
tlvType == IMAGE_TLV_ED25519
}
func ImageTlvTypeIsSecret(tlvType uint8) bool {
return tlvType == IMAGE_TLV_ENC_RSA ||
tlvType == IMAGE_TLV_ENC_KEK ||
tlvType == IMAGE_TLV_ENC_EC256
}
func (ver ImageVersion) String() string {
return fmt.Sprintf("%d.%d.%d.%d",
ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
}
func (tlv *ImageTlv) Clone() ImageTlv {
return ImageTlv{
Header: tlv.Header,
Data: append([]byte(nil), tlv.Data...),
}
}
func (tlv *ImageTlv) Write(w io.Writer) (int, error) {
totalSize := 0
err := binary.Write(w, binary.LittleEndian, &tlv.Header)
if err != nil {
return totalSize, errors.Wrapf(err, "failed to write image TLV header")
}
totalSize += IMAGE_TLV_SIZE
size, err := w.Write(tlv.Data)
if err != nil {
return totalSize, errors.Wrapf(err, "failed to write image TLV data")
}
totalSize += size
return totalSize, nil
}
// Clone performs a deep copy of an image.
func (img *Image) Clone() Image {
dup := Image{
Header: img.Header,
Pad: append([]byte(nil), img.Pad...),
Body: append([]byte(nil), img.Body...),
ProtTlvs: make([]ImageTlv, len(img.ProtTlvs)),
Tlvs: make([]ImageTlv, len(img.Tlvs)),
}
for i, tlv := range img.ProtTlvs {
dup.ProtTlvs[i] = tlv.Clone()
}
for i, tlv := range img.Tlvs {
dup.Tlvs[i] = tlv.Clone()
}
return dup
}
// FindTlvIndicesIf searches an image for TLVs satisfying the given predicate
// and returns their indices.
func (img *Image) FindTlvIndicesIf(pred func(tlv ImageTlv) bool) []int {
var idxs []int
for i, tlv := range img.Tlvs {
if pred(tlv) {
idxs = append(idxs, i)
}
}
return idxs
}
// FindTlvIndices searches an image for TLVs of the specified type and
// returns their indices.
func (img *Image) FindTlvIndices(tlvType uint8) []int {
return img.FindTlvIndicesIf(func(tlv ImageTlv) bool {
return tlv.Header.Type == tlvType
})
}
// FindTlvIndices searches an image for TLVs satisfying the given predicate and
// returns them.
func (img *Image) FindTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv {
var tlvs []*ImageTlv
idxs := img.FindTlvIndicesIf(pred)
for _, idx := range idxs {
tlvs = append(tlvs, &img.Tlvs[idx])
}
return tlvs
}
// FindTlvs retrieves all TLVs in an image's footer with the specified type.
func (img *Image) FindTlvs(tlvType uint8) []*ImageTlv {
var tlvs []*ImageTlv
idxs := img.FindTlvIndices(tlvType)
for _, idx := range idxs {
tlvs = append(tlvs, &img.Tlvs[idx])
}
return tlvs
}
// FindUniqueTlv retrieves a TLV in an image's footer with the specified
// type. It returns an error if there is more than one TLV with this type.
func (i *Image) FindUniqueTlv(tlvType uint8) (*ImageTlv, error) {
tlvs := i.FindTlvs(tlvType)
if len(tlvs) == 0 {
return nil, nil
}
if len(tlvs) > 1 {
return nil, errors.Errorf("image contains %d TLVs with type %d",
len(tlvs), tlvType)
}
return tlvs[0], nil
}
// RemoveTlvsIf removes all TLVs from an image that satisfy the supplied
// predicate. It returns a slice of the removed TLVs.
func (i *Image) RemoveTlvsIf(pred func(tlv ImageTlv) bool) []ImageTlv {
rmed := []ImageTlv{}
for idx := 0; idx < len(i.Tlvs); {
tlv := i.Tlvs[idx]
if pred(tlv) {
rmed = append(rmed, tlv)
i.Tlvs = append(i.Tlvs[:idx], i.Tlvs[idx+1:]...)
} else {
idx++
}
}
return rmed
}
// RemoveTlvsWithType removes from an image all TLVs with the specified type.
// It returns a slice of the removed TLVs.
func (i *Image) RemoveTlvsWithType(tlvType uint8) []ImageTlv {
return i.RemoveTlvsIf(func(tlv ImageTlv) bool {
return tlv.Header.Type == tlvType
})
}
// FindProtTlvIndicesIf searches an image for TLVs satisfying the given
// predicate and returns their indices.
func (img *Image) FindProtTlvIndicesIf(pred func(tlv ImageTlv) bool) []int {
var idxs []int
for i, tlv := range img.ProtTlvs {
if pred(tlv) {
idxs = append(idxs, i)
}
}
return idxs
}
// FindProtTlvIndices searches an image for TLVs of the specified type and
// returns their indices.
func (img *Image) FindProtTlvIndices(tlvType uint8) []int {
return img.FindProtTlvIndicesIf(func(tlv ImageTlv) bool {
return tlv.Header.Type == tlvType
})
}
// FindTlvIndices searches an image for TLVs satisfying the given predicate and
// returns them.
func (img *Image) FindProtTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv {
var tlvs []*ImageTlv
idxs := img.FindProtTlvIndicesIf(pred)
for _, idx := range idxs {
tlvs = append(tlvs, &img.ProtTlvs[idx])
}
return tlvs
}
// FindProtTlvs retrieves all TLVs in an image's footer with the specified type.
func (img *Image) FindProtTlvs(tlvType uint8) []*ImageTlv {
var tlvs []*ImageTlv
idxs := img.FindProtTlvIndices(tlvType)
for _, idx := range idxs {
tlvs = append(tlvs, &img.ProtTlvs[idx])
}
return tlvs
}
// FindProtUniqueTlv retrieves a TLV in an image's footer with the specified
// type. It returns an error if there is more than one TLV with this type.
func (i *Image) FindProtUniqueTlv(tlvType uint8) (*ImageTlv, error) {
tlvs := i.FindProtTlvs(tlvType)
if len(tlvs) == 0 {
return nil, nil
}
if len(tlvs) > 1 {
return nil, errors.Errorf("image contains %d TLVs with type %d",
len(tlvs), tlvType)
}
return tlvs[0], nil
}
// RemoveProtTlvsIf removes all TLVs from an image that satisfy the supplied
// predicate. It returns a slice of the removed TLVs.
func (i *Image) RemoveProtTlvsIf(pred func(tlv ImageTlv) bool) []ImageTlv {
rmed := []ImageTlv{}
for idx := 0; idx < len(i.ProtTlvs); {
tlv := i.ProtTlvs[idx]
if pred(tlv) {
rmed = append(rmed, tlv)
i.ProtTlvs = append(i.ProtTlvs[:idx], i.ProtTlvs[idx+1:]...)
i.Header.ProtSz -= uint16(IMAGE_TLV_SIZE + len(tlv.Data))
} else {
idx++
}
}
if len(i.ProtTlvs) == 0 {
i.Header.ProtSz = 0
}
return rmed
}
// RemoveProtTlvsWithType removes from an image all TLVs with the specified
// type. It returns a slice of the removed TLVs.
func (i *Image) RemoveProtTlvsWithType(tlvType uint8) []ImageTlv {
return i.RemoveProtTlvsIf(func(tlv ImageTlv) bool {
return tlv.Header.Type == tlvType
})
}
func (i *Image) FindAllTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv {
regTlvs := i.FindTlvsIf(pred)
protTlvs := i.FindProtTlvsIf(pred)
return append(regTlvs, protTlvs...)
}
func (i *Image) FindAllTlvs(tlvType uint8) []*ImageTlv {
return i.FindAllTlvsIf(func(tlv ImageTlv) bool {
return tlv.Header.Type == tlvType
})
}
func (i *Image) FindAllUniqueTlv(tlvType uint8) (*ImageTlv, error) {
tlvs := i.FindAllTlvs(tlvType)
if len(tlvs) == 0 {
return nil, nil
}
if len(tlvs) > 1 {
return nil, errors.Errorf(
"image contains too many TLVs with type %d: have=%d want<=1",
tlvType, len(tlvs))
}
return tlvs[0], nil
}
// ProtTrailer constructs a protected ImageTrailer corresponding to the given
// image.
func (img *Image) ProtTrailer() ImageTrailer {
trailer := ImageTrailer{
Magic: IMAGE_PROT_TRAILER_MAGIC,
TlvTotLen: IMAGE_TRAILER_SIZE,
}
for _, tlv := range img.ProtTlvs {
trailer.TlvTotLen += IMAGE_TLV_SIZE + tlv.Header.Len
}
return trailer
}
// Trailer constructs an ImageTrailer corresponding to the given image.
func (img *Image) Trailer() ImageTrailer {
trailer := ImageTrailer{
Magic: IMAGE_TRAILER_MAGIC,
TlvTotLen: IMAGE_TRAILER_SIZE,
}
for _, tlv := range img.Tlvs {
trailer.TlvTotLen += IMAGE_TLV_SIZE + tlv.Header.Len
}
return trailer
}
// Hash retrieves the contents of an image's SHA256 TLV.
func (i *Image) Hash() ([]byte, error) {
tlv, err := i.FindUniqueTlv(IMAGE_TLV_SHA256)
if err != nil {
return nil, errors.Wrapf(err, "failed to retrieve image hash")
}
if tlv == nil {
return nil, errors.Errorf(
"failed to retrieve image hash: image does not contain hash TLV")
}
return tlv.Data, nil
}
// CalcHash calculates a SHA256 of the given image. initialHash should be nil
// for non-split-images.
func (i *Image) CalcHash(initialHash []byte) ([]byte, error) {
return calcHash(initialHash, i.Header, i.Pad, i.Body, i.ProtTlvs)
}
// WritePlusOffsets writes a binary image to the given writer. It returns
// the offsets of the image components that got written.
func (i *Image) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
offs := ImageOffsets{}
offset := 0
offs.Header = offset
err := binary.Write(w, binary.LittleEndian, &i.Header)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image header")
}
offset += IMAGE_HEADER_SIZE
err = binary.Write(w, binary.LittleEndian, i.Pad)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image padding")
}
offset += len(i.Pad)
offs.Body = offset
size, err := w.Write(i.Body)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image body")
}
offset += size
if i.Header.ProtSz > 0 {
protTrailer := i.ProtTrailer()
offs.ProtTrailer = offset
err = binary.Write(w, binary.LittleEndian, &protTrailer)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image trailer")
}
offset += IMAGE_TRAILER_SIZE
for _, tlv := range i.ProtTlvs {
offs.ProtTlvs = append(offs.ProtTlvs, offset)
size, err := tlv.Write(w)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image TLV")
}
offset += size
}
}
trailer := i.Trailer()
offs.Trailer = offset
err = binary.Write(w, binary.LittleEndian, &trailer)
if err != nil {
return offs, errors.Wrapf(err, "failed to write image trailer")
}
offset += IMAGE_TRAILER_SIZE
for _, tlv := range i.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
}
// Offsets returns the offsets of each of an image's components if it were
// serialized.
func (i *Image) Offsets() (ImageOffsets, error) {
return i.WritePlusOffsets(ioutil.Discard)
}
// TotalSize returns the size of the image if it were serialized, in bytes.
func (i *Image) TotalSize() (int, error) {
offs, err := i.Offsets()
if err != nil {
return 0, err
}
return offs.TotalSize, nil
}
// Write serializes and writes a Mynewt image.
func (i *Image) Write(w io.Writer) (int, error) {
offs, err := i.WritePlusOffsets(w)
if err != nil {
return 0, err
}
return offs.TotalSize, nil
}
// WriteToFile writes a Mynewt image to a file.
func (i *Image) WriteToFile(filename string) error {
f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return errors.Wrapf(err, "failed to open image destination file")
}
if _, err := i.Write(f); err != nil {
return errors.Wrapf(err, "failed to write image")
}
return nil
}
// CollectSigs returns a slice of all signatures present in an image's
// trailer.
func (img *Image) CollectSigs() ([]sec.Sig, error) {
var sigs []sec.Sig
var keyHashTlv *ImageTlv
for i, _ := range img.Tlvs {
t := &img.Tlvs[i]
if t.Header.Type == IMAGE_TLV_KEYHASH {
if keyHashTlv != nil {
return nil, errors.Errorf(
"image contains keyhash tlv without subsequent signature")
}
keyHashTlv = t
} else {
sigType, ok := ImageTlvTypeToSigType(t.Header.Type)
if ok {
if keyHashTlv == nil {
return nil, errors.Errorf(
"image contains signature tlv without preceding keyhash")
}
sigs = append(sigs, sec.Sig{
Type: sigType,
KeyHash: keyHashTlv.Data,
Data: t.Data,
})
keyHashTlv = nil
}
}
}
return sigs, nil
}
// CollectSecret finds the "secret" TLV in an image and returns its body. It
// returns nil if there is no "secret" TLV.
func (img *Image) CollectSecret() ([]byte, error) {
tlv, err := img.FindUniqueTlv(IMAGE_TLV_ENC_RSA)
if err != nil {
return nil, err
}
if tlv == nil {
return nil, nil
}
return tlv.Data, nil
}
// ExtractSecret finds the "secret" TLV in an image, removes it, and returns
// its body. It returns nil if there is no "secret" TLV.
func (img *Image) ExtractSecret() ([]byte, error) {
tlvs := img.RemoveTlvsWithType(IMAGE_TLV_ENC_RSA)
if len(tlvs) == 0 {
return nil, nil
}
if len(tlvs) > 1 {
return nil, errors.Errorf(
"image contains >1 ENC_RSA TLVs (%d)", len(tlvs))
}
return tlvs[0].Data, nil
}
// Encrypt encrypts an image body and adds a "secret" TLV. It does NOT set the
// "encrypted" flag in the image header.
func Encrypt(img Image, pubEncKey sec.PubEncKey) (Image, error) {
dup := img.Clone()
tlvp, err := dup.FindUniqueTlv(IMAGE_TLV_ENC_RSA)
if err != nil {
return dup, err
}
if tlvp != nil {
return dup, errors.Errorf("image already contains an ENC_RSA TLV")
}
plainSecret, err := GeneratePlainSecret()
if err != nil {
return dup, err
}
cipherSecret, err := pubEncKey.Encrypt(plainSecret)
if err != nil {
return dup, err
}
body, err := sec.EncryptAES(dup.Body, plainSecret, nil)
if err != nil {
return dup, err
}
dup.Body = body
tlv, err := GenerateEncTlv(cipherSecret)
if err != nil {
return dup, err
}
dup.Tlvs = append(dup.Tlvs, tlv)
return dup, nil
}
// Decrypt decrypts an image body and strips the "secret" TLV. It does NOT
// clear the "encrypted" flag in the image header.
func Decrypt(img Image, privEncKey sec.PrivEncKey) (Image, error) {
dup := img.Clone()
tlvs := dup.RemoveTlvsIf(func(tlv ImageTlv) bool {
return ImageTlvTypeIsSecret(tlv.Header.Type)
})
if len(tlvs) != 1 {
return dup, errors.Errorf(
"failed to decrypt image: wrong count of \"secret\" TLVs; "+
"have=%d want=1", len(tlvs))
}
cipherSecret := tlvs[0].Data
plainSecret, err := privEncKey.Decrypt(cipherSecret)
if err != nil {
return img, err
}
body, err := sec.EncryptAES(dup.Body, plainSecret, nil)
if err != nil {
return img, err
}
dup.Body = body
return dup, nil
}
// DecryptHw decrypts a hardware-encrypted image. It does NOT strip the
// "nonce" or "secret ID" protected TLVs.
func DecryptHw(img Image, secret []byte) (Image, error) {
dup := img.Clone()
tlvs := dup.FindProtTlvs(IMAGE_TLV_AES_NONCE)
if len(tlvs) != 1 {
return dup, errors.Errorf(
"failed to decrypt hw-encrypted image: "+
"wrong count of AES nonce TLVs; have=%d want=1", len(tlvs))
}
nonce := tlvs[0].Data
body, err := sec.EncryptAES(dup.Body, secret, nonce)
if err != nil {
return dup, err
}
dup.Body = body
return dup, nil
}
// DecryptHw decrypts a hardware-encrypted image and strips the "nonce" and
// "secret ID" protected TLVs.
func DecryptHwFull(img Image, secret []byte) (Image, error) {
var err error
img, err = DecryptHw(img, secret)
if err != nil {
return img, err
}
img.RemoveProtTlvsWithType(IMAGE_TLV_AES_NONCE)
img.RemoveProtTlvsWithType(IMAGE_TLV_SECRET_ID)
return img, nil
}
// IsEncrypted indicates whether an image's "encrypted" flag is set.
func (img *Image) IsEncrypted() bool {
return img.Header.Flags&IMAGE_F_ENCRYPTED != 0
}