blob: 7289f522188b9034a859f9498a426de2d8e6aeef [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 /* Image 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_NON_BOOTABLE = 0x00000002 /* non bootable image */
IMAGE_F_ENCRYPTED = 0x00000004 /* encrypted 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_ENC_RSA = 0x30
IMAGE_TLV_ENC_KEK = 0x31
)
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_ENC_RSA: "ENC_RSA",
IMAGE_TLV_ENC_KEK: "ENC_KEK",
}
type ImageVersion struct {
Major uint8
Minor uint8
Rev uint16
BuildNum uint32
}
type ImageHdr struct {
Magic uint32
Pad1 uint32
HdrSz uint16
Pad2 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
Tlvs []ImageTlv
}
type ImageOffsets struct {
Header int
Body int
Trailer int
Tlvs []int
TotalSize 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 ImageTlvTypeIsSig(tlvType uint8) bool {
return tlvType == IMAGE_TLV_RSA2048 ||
tlvType == IMAGE_TLV_RSA3072 ||
tlvType == IMAGE_TLV_ECDSA224 ||
tlvType == IMAGE_TLV_ECDSA256
}
func (ver ImageVersion) String() string {
return fmt.Sprintf("%d.%d.%d.%d",
ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
}
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
}
// FindTlvIndices searches an image for TLVs of the specified type and
// returns their indices.
func (i *Image) FindTlvIndices(tlvType uint8) []int {
var idxs []int
for i, tlv := range i.Tlvs {
if tlv.Header.Type == tlvType {
idxs = append(idxs, i)
}
}
return idxs
}
// FindTlvs retrieves all TLVs in an image's footer with the specified type.
func (i *Image) FindTlvs(tlvType uint8) []*ImageTlv {
var tlvs []*ImageTlv
idxs := i.FindTlvIndices(tlvType)
for _, idx := range idxs {
tlvs = append(tlvs, &i.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
})
}
// ImageTrailer constructs an image trailer 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.
func (i *Image) CalcHash() ([]byte, error) {
return calcHash(nil, i.Header, i.Pad, i.Body)
}
// 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
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 if ImageTlvTypeIsSig(t.Header.Type) {
if keyHashTlv == nil {
return nil, errors.Errorf(
"image contains signature tlv without preceding keyhash")
}
sigs = append(sigs, sec.Sig{
KeyHash: keyHashTlv.Data,
Data: t.Data,
})
keyHashTlv = nil
}
}
return sigs, nil
}