blob: bbc6b5a7b3f8a5c9e3b6260d13fe8f7bcaa90728 [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"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
"sort"
"strconv"
"strings"
keywrap "github.com/NickBall/go-aes-key-wrap"
log "github.com/Sirupsen/logrus"
"mynewt.apache.org/newt/newt/pkg"
"mynewt.apache.org/newt/util"
)
// Set this to enable RSA-PSS for RSA signatures, instead of PKCS#1
// v1.5. Eventually, this should be the default.
var UseRsaPss = false
// Use old image format
var UseV1 = false
// Public key file to encrypt image
var PubKeyFile = ""
type ImageVersion struct {
Major uint8
Minor uint8
Rev uint16
BuildNum uint32
}
type Image struct {
SourceBin string
SourceImg string
TargetImg string
Version ImageVersion
SigningRSA *rsa.PrivateKey
SigningEC *ecdsa.PrivateKey
KeyId uint8
Hash []byte
SrcSkip uint // Number of bytes to skip from the source image.
HeaderSize uint // If non-zero pad out the header to this size.
TotalSize uint // Total size, in bytes, of the generated .img file.
}
type ImageHdrV1 struct {
Magic uint32
TlvSz uint16
KeyId uint8
Pad1 uint8
HdrSz uint16
Pad2 uint16
ImgSz uint32
Flags uint32
Vers ImageVersion
Pad3 uint32
}
type ImageHdr struct {
Magic uint32
Pad1 uint32
HdrSz uint16
Pad2 uint16
ImgSz uint32
Flags uint32
Vers ImageVersion
Pad3 uint32
}
type ImageTlvInfo struct {
Magic uint16
TlvTotLen uint16
}
type ImageTrailerTlv struct {
Type uint8
Pad uint8
Len uint16
}
const (
IMAGEv1_MAGIC = 0x96f3b83c /* Image header magic */
IMAGE_MAGIC = 0x96f3b83d /* Image header magic */
IMAGE_TRAILER_MAGIC = 0x6907 /* Image tlv info magic */
)
const (
IMAGE_HEADER_SIZE = 32
)
/*
* Image header flags.
*/
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 */
IMAGE_F_PIC = 0x00000001
IMAGE_F_NON_BOOTABLE = 0x00000002 /* non bootable image */
IMAGE_F_ENCRYPTED = 0x00000004 /* encrypted image */
)
/*
* Image trailer TLV types.
*/
const (
IMAGEv1_TLV_SHA256 = 1
IMAGEv1_TLV_RSA2048 = 2
IMAGEv1_TLV_ECDSA224 = 3
IMAGEv1_TLV_ECDSA256 = 4
IMAGE_TLV_KEYHASH = 0x01
IMAGE_TLV_SHA256 = 0x10
IMAGE_TLV_RSA2048 = 0x20
IMAGE_TLV_ECDSA224 = 0x21
IMAGE_TLV_ECDSA256 = 0x22
IMAGE_TLV_ENC_RSA = 0x30
IMAGE_TLV_ENC_KEK = 0x31
)
/*
* Data that's going to go to build manifest file
*/
type ImageManifestSizeArea struct {
Name string `json:"name"`
Size uint32 `json:"size"`
}
type ImageManifestSizeSym struct {
Name string `json:"name"`
Areas []*ImageManifestSizeArea `json:"areas"`
}
type ImageManifestSizeFile struct {
Name string `json:"name"`
Syms []*ImageManifestSizeSym `json:"sym"`
}
type ImageManifestSizePkg struct {
Name string `json:"name"`
Files []*ImageManifestSizeFile `json:"files"`
}
type ImageManifestSizeCollector struct {
Pkgs []*ImageManifestSizePkg
}
type ImageManifest struct {
Name string `json:"name"`
Date string `json:"build_time"`
Version string `json:"build_version"`
BuildID string `json:"id"`
Image string `json:"image"`
ImageHash string `json:"image_hash"`
Loader string `json:"loader"`
LoaderHash string `json:"loader_hash"`
Pkgs []*ImageManifestPkg `json:"pkgs"`
LoaderPkgs []*ImageManifestPkg `json:"loader_pkgs,omitempty"`
TgtVars []string `json:"target"`
Repos []ImageManifestRepo `json:"repos"`
PkgSizes []*ImageManifestSizePkg `json:"pkgsz"`
LoaderPkgSizes []*ImageManifestSizePkg `json:"loader_pkgsz,omitempty"`
}
type ImageManifestPkg struct {
Name string `json:"name"`
Repo string `json:"repo"`
}
type ImageManifestRepo struct {
Name string `json:"name"`
Commit string `json:"commit"`
Dirty bool `json:"dirty,omitempty"`
URL string `json:"url,omitempty"`
}
type RepoManager struct {
repos map[string]ImageManifestRepo
}
type ECDSASig struct {
R *big.Int
S *big.Int
}
func ParseVersion(versStr string) (ImageVersion, error) {
var err error
var major uint64
var minor uint64
var rev uint64
var buildNum uint64
var ver ImageVersion
components := strings.Split(versStr, ".")
major, err = strconv.ParseUint(components[0], 10, 8)
if err != nil {
return ver, util.FmtNewtError("Invalid version string %s", versStr)
}
if len(components) > 1 {
minor, err = strconv.ParseUint(components[1], 10, 8)
if err != nil {
return ver, util.FmtNewtError("Invalid version string %s", versStr)
}
}
if len(components) > 2 {
rev, err = strconv.ParseUint(components[2], 10, 16)
if err != nil {
return ver, util.FmtNewtError("Invalid version string %s", versStr)
}
}
if len(components) > 3 {
buildNum, err = strconv.ParseUint(components[3], 10, 32)
if err != nil {
return ver, util.FmtNewtError("Invalid version string %s", versStr)
}
}
ver.Major = uint8(major)
ver.Minor = uint8(minor)
ver.Rev = uint16(rev)
ver.BuildNum = uint32(buildNum)
return ver, nil
}
func (ver ImageVersion) String() string {
return fmt.Sprintf("%d.%d.%d.%d",
ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
}
func NewImage(srcBinPath string, dstImgPath string) (*Image, error) {
image := &Image{}
image.SourceBin = srcBinPath
image.TargetImg = dstImgPath
return image, nil
}
func OldImage(imgPath string) (*Image, error) {
image := &Image{}
image.SourceImg = imgPath
return image, nil
}
func (image *Image) SetVersion(versStr string) error {
ver, err := ParseVersion(versStr)
if err != nil {
return err
}
log.Debugf("Assigning version number %d.%d.%d.%d\n",
ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
image.Version = ver
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, image.Version)
if err != nil {
fmt.Printf("Bombing out\n")
return nil
}
return nil
}
func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
var privKey interface{}
var err error
block, data := pem.Decode(keyBytes)
if block != nil && block.Type == "EC PARAMETERS" {
/*
* Openssl prepends an EC PARAMETERS block before the
* key itself. If we see this first, just skip it,
* and go on to the data block.
*/
block, _ = pem.Decode(data)
}
if block != nil && block.Type == "RSA PRIVATE KEY" {
/*
* ParsePKCS1PrivateKey returns an RSA private key from its ASN.1
* PKCS#1 DER encoded form.
*/
privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, util.NewNewtError(fmt.Sprintf("Private key parsing "+
"failed: %s", err))
}
}
if block != nil && block.Type == "EC PRIVATE KEY" {
/*
* ParseECPrivateKey returns a EC private key
*/
privKey, err = x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, util.NewNewtError(fmt.Sprintf("Private key parsing "+
"failed: %s", err))
}
}
if block != nil && block.Type == "PRIVATE KEY" {
// This indicates a PKCS#8 unencrypted private key.
// The particular type of key will be indicated within
// the key itself.
privKey, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, util.NewNewtError(fmt.Sprintf("Private key parsing "+
"failed: %s", err))
}
}
if block != nil && block.Type == "ENCRYPTED PRIVATE KEY" {
// This indicates a PKCS#8 key wrapped with PKCS#5
// encryption.
privKey, err = parseEncryptedPrivateKey(block.Bytes)
if err != nil {
return nil, util.FmtNewtError("Unable to decode encrypted private key: %s", err)
}
}
if privKey == nil {
return nil, util.NewNewtError("Unknown private key format, EC/RSA private " +
"key in PEM format only.")
}
return privKey, nil
}
func (image *Image) SetSigningKey(fileName string, keyId uint8) error {
keyBytes, err := ioutil.ReadFile(fileName)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error reading key file: %s", err))
}
image.KeyId = keyId
privKey, err := ParsePrivateKey(keyBytes)
if err != nil {
return err
}
switch priv := privKey.(type) {
case *rsa.PrivateKey:
image.SigningRSA = priv
case *ecdsa.PrivateKey:
image.SigningEC = priv
default:
return util.NewNewtError("Unknown private key format")
}
return nil
}
func (image *Image) sigHdrTypeV1() (uint32, error) {
if image.SigningRSA != nil {
if UseRsaPss {
return IMAGEv1_F_PKCS1_PSS_RSA2048_SHA256, nil
} else {
return IMAGEv1_F_PKCS15_RSA2048_SHA256, nil
}
} else if image.SigningEC != nil {
switch image.SigningEC.Curve.Params().Name {
case "P-224":
return IMAGEv1_F_ECDSA224_SHA256, nil
case "P-256":
return IMAGEv1_F_ECDSA256_SHA256, nil
default:
return 0, util.NewNewtError("Unsupported ECC curve")
}
} else {
return 0, nil
}
}
func (image *Image) sigKeyHash() ([]uint8, error) {
if image.SigningRSA != nil {
pubkey, _ := asn1.Marshal(image.SigningRSA.PublicKey)
sum := sha256.Sum256(pubkey)
return sum[:4], nil
} else if image.SigningEC != nil {
switch image.SigningEC.Curve.Params().Name {
case "P-224":
fallthrough
case "P-256":
pubkey, _ := x509.MarshalPKIXPublicKey(&image.SigningEC.PublicKey)
sum := sha256.Sum256(pubkey)
return sum[:4], nil
default:
return []uint8{}, util.NewNewtError("Unsupported ECC curve")
}
} else {
return []uint8{}, util.NewNewtError("No public key to hash")
}
}
func (image *Image) sigLen() uint16 {
if image.SigningRSA != nil {
return 256
} else if image.SigningEC != nil {
switch image.SigningEC.Curve.Params().Name {
case "P-224":
return 68
case "P-256":
return 72
default:
return 0
}
} else {
return 0
}
}
func (image *Image) sigTlvTypeV1() uint8 {
if image.SigningRSA != nil {
return IMAGEv1_TLV_RSA2048
} else if image.SigningEC != nil {
switch image.SigningEC.Curve.Params().Name {
case "P-224":
return IMAGEv1_TLV_ECDSA224
case "P-256":
return IMAGEv1_TLV_ECDSA256
default:
return 0
}
} else {
return 0
}
}
func (image *Image) sigTlvType() uint8 {
if image.SigningRSA != nil {
return IMAGE_TLV_RSA2048
} else if image.SigningEC != nil {
switch image.SigningEC.Curve.Params().Name {
case "P-224":
return IMAGE_TLV_ECDSA224
case "P-256":
return IMAGE_TLV_ECDSA256
default:
return 0
}
} else {
return 0
}
}
func (image *Image) ReSign() error {
srcImg, err := os.Open(image.SourceImg)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't open image file %s: %s",
image.SourceImg, err.Error()))
}
srcInfo, err := srcImg.Stat()
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't stat image file %s: %s",
image.SourceImg, err.Error()))
}
var hdr1 ImageHdrV1
var hdr2 ImageHdr
var hdrSz uint16
var imgSz uint32
err = binary.Read(srcImg, binary.LittleEndian, &hdr1)
if err == nil {
srcImg.Seek(0, 0)
err = binary.Read(srcImg, binary.LittleEndian, &hdr2)
}
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failing to access image %s: %s",
image.SourceImg, err.Error()))
}
if hdr1.Magic == IMAGEv1_MAGIC {
if uint32(srcInfo.Size()) !=
uint32(hdr1.HdrSz)+hdr1.ImgSz+uint32(hdr1.TlvSz) {
return util.NewNewtError(fmt.Sprintf("File %s is not an image\n",
image.SourceImg))
}
imgSz = hdr1.ImgSz
hdrSz = hdr1.HdrSz
image.Version = hdr1.Vers
log.Debugf("Resigning %s (ver %d.%d.%d.%d)", image.SourceImg,
hdr1.Vers.Major, hdr1.Vers.Minor, hdr1.Vers.Rev,
hdr1.Vers.BuildNum)
} else if hdr2.Magic == IMAGE_MAGIC {
if uint32(srcInfo.Size()) < uint32(hdr2.HdrSz)+hdr2.ImgSz {
return util.NewNewtError(fmt.Sprintf("File %s is not an image\n",
image.SourceImg))
}
imgSz = hdr2.ImgSz
hdrSz = hdr2.HdrSz
image.Version = hdr2.Vers
log.Debugf("Resigning %s (ver %d.%d.%d.%d)", image.SourceImg,
hdr2.Vers.Major, hdr2.Vers.Minor, hdr2.Vers.Rev,
hdr2.Vers.BuildNum)
} else {
return util.NewNewtError(fmt.Sprintf("File %s is not an image\n",
image.SourceImg))
}
srcImg.Seek(int64(hdrSz), 0)
tmpBin, err := ioutil.TempFile("", "")
if err != nil {
return util.NewNewtError(fmt.Sprintf("Creating temp file failed: %s",
err.Error()))
}
tmpBinName := tmpBin.Name()
defer os.Remove(tmpBinName)
log.Debugf("Extracting data from %s:%d-%d to %s\n",
image.SourceImg, int64(hdrSz), int64(hdrSz)+int64(imgSz), tmpBinName)
_, err = io.CopyN(tmpBin, srcImg, int64(imgSz))
srcImg.Close()
tmpBin.Close()
if err != nil {
return util.NewNewtError(fmt.Sprintf("Cannot copy to tmpfile %s: %s",
tmpBin.Name(), err.Error()))
}
image.SourceBin = tmpBinName
image.TargetImg = image.SourceImg
image.HeaderSize = uint(hdrSz)
return image.Generate(nil)
}
func (image *Image) generateV1(loader *Image) error {
binFile, err := os.Open(image.SourceBin)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't open app binary: %s",
err.Error()))
}
defer binFile.Close()
binInfo, err := binFile.Stat()
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't stat app binary %s: %s",
image.SourceBin, err.Error()))
}
imgFile, err := os.OpenFile(image.TargetImg,
os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't open target image %s: %s",
image.TargetImg, err.Error()))
}
defer imgFile.Close()
/*
* Compute hash while updating the file.
*/
hash := sha256.New()
if loader != nil {
err = binary.Write(hash, binary.LittleEndian, loader.Hash)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to seed hash: %s",
err.Error()))
}
}
/*
* First the header
*/
hdr := &ImageHdrV1{
Magic: IMAGEv1_MAGIC,
TlvSz: 0,
KeyId: 0,
Pad1: 0,
HdrSz: IMAGE_HEADER_SIZE,
Pad2: 0,
ImgSz: uint32(binInfo.Size()) - uint32(image.SrcSkip),
Flags: 0,
Vers: image.Version,
Pad3: 0,
}
hdr.Flags, err = image.sigHdrTypeV1()
if err != nil {
return err
}
if hdr.Flags != 0 {
/*
* Signature present
*/
hdr.TlvSz = 4 + image.sigLen()
hdr.KeyId = image.KeyId
}
hdr.TlvSz += 4 + 32
hdr.Flags |= IMAGEv1_F_SHA256
if loader != nil {
hdr.Flags |= IMAGEv1_F_NON_BOOTABLE
}
if image.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 image.HeaderSize < IMAGE_HEADER_SIZE {
return util.NewNewtError(fmt.Sprintf("Image header must be at least %d bytes", IMAGE_HEADER_SIZE))
}
hdr.HdrSz = uint16(image.HeaderSize)
}
err = binary.Write(imgFile, binary.LittleEndian, hdr)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image hdr: %s",
err.Error()))
}
err = binary.Write(hash, binary.LittleEndian, hdr)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to hash data: %s",
err.Error()))
}
if image.HeaderSize > IMAGE_HEADER_SIZE {
/*
* Pad the image (and hash) with zero bytes to fill
* out the buffer.
*/
buf := make([]byte, image.HeaderSize-IMAGE_HEADER_SIZE)
_, err = imgFile.Write(buf)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to write padding: %s",
err.Error()))
}
_, err = hash.Write(buf)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to hash padding: %s",
err.Error()))
}
}
/*
* Skip requested initial part of image.
*/
if image.SrcSkip > 0 {
buf := make([]byte, image.SrcSkip)
_, err = binFile.Read(buf)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to read from %s: %s",
image.SourceBin, err.Error()))
}
nonZero := false
for _, b := range buf {
if b != 0 {
nonZero = true
break
}
}
if nonZero {
log.Warnf("Skip requested of image %s, but image not preceeded "+
"by %d bytes of all zeros",
image.SourceBin, image.SrcSkip)
}
}
/*
* Followed by data.
*/
dataBuf := make([]byte, 1024)
for {
cnt, err := binFile.Read(dataBuf)
if err != nil && err != io.EOF {
return util.NewNewtError(fmt.Sprintf("Failed to read from %s: %s",
image.SourceBin, err.Error()))
}
if cnt == 0 {
break
}
_, err = imgFile.Write(dataBuf[0:cnt])
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to write to %s: %s",
image.TargetImg, err.Error()))
}
_, err = hash.Write(dataBuf[0:cnt])
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to hash data: %s",
err.Error()))
}
}
image.Hash = hash.Sum(nil)
/*
* Trailer with hash of the data
*/
tlv := &ImageTrailerTlv{
Type: IMAGEv1_TLV_SHA256,
Pad: 0,
Len: uint16(len(image.Hash)),
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(image.Hash)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append hash: %s",
err.Error()))
}
if image.SigningRSA != nil {
/*
* If signing key was set, generate TLV for that.
*/
tlv := &ImageTrailerTlv{
Type: IMAGEv1_TLV_RSA2048,
Pad: 0,
Len: 256, /* 2048 bits */
}
var signature []byte
if UseRsaPss {
opts := rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
}
signature, err = rsa.SignPSS(rand.Reader, image.SigningRSA,
crypto.SHA256, image.Hash, &opts)
} else {
signature, err = rsa.SignPKCS1v15(rand.Reader, image.SigningRSA,
crypto.SHA256, image.Hash)
}
if err != nil {
return util.NewNewtError(fmt.Sprintf(
"Failed to compute signature: %s", err))
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(signature)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append sig: %s",
err.Error()))
}
}
if image.SigningEC != nil {
r, s, err := ecdsa.Sign(rand.Reader, image.SigningEC, image.Hash)
if err != nil {
return util.NewNewtError(fmt.Sprintf(
"Failed to compute signature: %s", err))
}
sigLen := image.sigLen()
var ECDSA ECDSASig
ECDSA.R = r
ECDSA.S = s
signature, err := asn1.Marshal(ECDSA)
if err != nil {
return util.NewNewtError(fmt.Sprintf(
"Failed to construct signature: %s", err))
}
if len(signature) > int(sigLen) {
return util.NewNewtError(fmt.Sprintf(
"Something is really wrong\n"))
}
tlv := &ImageTrailerTlv{
Type: image.sigTlvTypeV1(),
Pad: 0,
Len: sigLen,
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(signature)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append sig: %s",
err.Error()))
}
pad := make([]byte, int(sigLen)-len(signature))
_, err = imgFile.Write(pad)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
}
util.StatusMessage(util.VERBOSITY_VERBOSE,
"Computed Hash for image %s as %s \n",
image.TargetImg, hex.EncodeToString(image.Hash))
// XXX: Replace "1" with io.SeekCurrent when go 1.7 becomes mainstream.
sz, err := imgFile.Seek(0, 1)
if err != nil {
return util.FmtNewtError("Failed to calculate file size of generated "+
"image %s: %s", image.TargetImg, err.Error())
}
image.TotalSize = uint(sz)
return nil
}
func (image *Image) generateV2(loader *Image) error {
binFile, err := os.Open(image.SourceBin)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't open app binary: %s",
err.Error()))
}
defer binFile.Close()
binInfo, err := binFile.Stat()
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't stat app binary %s: %s",
image.SourceBin, err.Error()))
}
imgFile, err := os.OpenFile(image.TargetImg,
os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Can't open target image %s: %s",
image.TargetImg, err.Error()))
}
defer imgFile.Close()
plainSecret := make([]byte, 16)
var cipherSecret []byte
var _type uint8
if PubKeyFile != "" {
_, err = rand.Read(plainSecret)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Random generation error: %s\n", err))
}
keyBytes, err := ioutil.ReadFile(PubKeyFile)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error reading pubkey file: %s", err))
}
// Try reading as PEM (asymetric key), if it fails, assume this is a
// base64 encoded symetric key
b, _ := pem.Decode(keyBytes)
if b == nil {
kek, err := base64.StdEncoding.DecodeString(string(keyBytes))
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error decoding kek: %s", err))
} else if len(kek) != 16 {
return util.NewNewtError(fmt.Sprintf("Unexpected key size: %d != 16", len(kek)))
}
cipher, err := aes.NewCipher(kek)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error creating keywrap cipher: %s", err))
}
cipherSecret, err = keywrap.Wrap(cipher, plainSecret)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error key-wrapping: %s", err))
}
} else if b.Type != "PUBLIC KEY" && b.Type != "RSA PUBLIC KEY" {
return util.NewNewtError("Invalid PEM file")
} else {
pub, err := x509.ParsePKIXPublicKey(b.Bytes)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error parsing pubkey file: %s", err))
}
var pubk *rsa.PublicKey
switch pub.(type) {
case *rsa.PublicKey:
pubk = pub.(*rsa.PublicKey)
default:
return util.NewNewtError(fmt.Sprintf("Error parsing pubkey file: %s", err))
}
rng := rand.Reader
cipherSecret, err = rsa.EncryptOAEP(sha256.New(), rng, pubk, plainSecret, nil)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Error from encryption: %s\n", err))
}
}
}
/*
* Compute hash while updating the file.
*/
hash := sha256.New()
if loader != nil {
err = binary.Write(hash, binary.LittleEndian, loader.Hash)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to seed hash: %s",
err.Error()))
}
}
/*
* First the header
*/
hdr := &ImageHdr{
Magic: IMAGE_MAGIC,
Pad1: 0,
HdrSz: IMAGE_HEADER_SIZE,
Pad2: 0,
ImgSz: uint32(binInfo.Size()) - uint32(image.SrcSkip),
Flags: 0,
Vers: image.Version,
Pad3: 0,
}
if loader != nil {
hdr.Flags |= IMAGE_F_NON_BOOTABLE
}
if cipherSecret != nil {
hdr.Flags |= IMAGE_F_ENCRYPTED
}
if image.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 image.HeaderSize < IMAGE_HEADER_SIZE {
return util.NewNewtError(fmt.Sprintf("Image header must be at "+
"least %d bytes", IMAGE_HEADER_SIZE))
}
hdr.HdrSz = uint16(image.HeaderSize)
}
err = binary.Write(imgFile, binary.LittleEndian, hdr)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image hdr: %s",
err.Error()))
}
err = binary.Write(hash, binary.LittleEndian, hdr)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to hash data: %s",
err.Error()))
}
if image.HeaderSize > IMAGE_HEADER_SIZE {
/*
* Pad the image (and hash) with zero bytes to fill
* out the buffer.
*/
buf := make([]byte, image.HeaderSize-IMAGE_HEADER_SIZE)
_, err = imgFile.Write(buf)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to write padding: %s",
err.Error()))
}
_, err = hash.Write(buf)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to hash padding: %s",
err.Error()))
}
}
/*
* Skip requested initial part of image.
*/
if image.SrcSkip > 0 {
buf := make([]byte, image.SrcSkip)
_, err = binFile.Read(buf)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to read from %s: %s",
image.SourceBin, err.Error()))
}
nonZero := false
for _, b := range buf {
if b != 0 {
nonZero = true
break
}
}
if nonZero {
log.Warnf("Skip requested of image %s, but image not preceeded by %d bytes of all zeros",
image.SourceBin, image.SrcSkip)
}
}
var stream cipher.Stream
if cipherSecret != nil {
block, err := aes.NewCipher(plainSecret)
if err != nil {
return 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)
for {
cnt, err := binFile.Read(dataBuf)
if err != nil && err != io.EOF {
return util.NewNewtError(fmt.Sprintf("Failed to read from %s: %s",
image.SourceBin, err.Error()))
}
if cnt == 0 {
break
}
_, err = hash.Write(dataBuf[0:cnt])
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to hash data: %s",
err.Error()))
}
if cipherSecret == nil {
_, err = imgFile.Write(dataBuf[0:cnt])
} else {
stream.XORKeyStream(encBuf, dataBuf[0:cnt])
_, err = imgFile.Write(encBuf[0:cnt])
}
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to write to %s: %s",
image.TargetImg, err.Error()))
}
}
image.Hash = hash.Sum(nil)
/*
* Write TLV info.
*/
tlvInfo := &ImageTlvInfo{
Magic: IMAGE_TRAILER_MAGIC,
TlvTotLen: 0,
}
tlvInfoOff, err := imgFile.Seek(0, 1)
if err != nil {
return util.FmtNewtError("Failed to calculate file size of generated "+
"image %s: %s", image.TargetImg, err.Error())
}
err = binary.Write(imgFile, binary.LittleEndian, tlvInfo)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image hdr: %s",
err.Error()))
}
/*
* Trailer with hash of the data
*/
tlv := &ImageTrailerTlv{
Type: IMAGE_TLV_SHA256,
Pad: 0,
Len: uint16(len(image.Hash)),
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(image.Hash)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append hash: %s",
err.Error()))
}
if image.SigningRSA != nil || image.SigningEC != nil {
keyHash, err := image.sigKeyHash()
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to compute hash " +
"of the public key"))
}
tlv = &ImageTrailerTlv{
Type: IMAGE_TLV_KEYHASH,
Pad: 0,
Len: uint16(len(keyHash)),
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serial image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(keyHash)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append "+
"key hash: %s", err.Error()))
}
}
if image.SigningRSA != nil {
/*
* If signing key was set, generate TLV for that.
*/
tlv := &ImageTrailerTlv{
Type: IMAGE_TLV_RSA2048,
Pad: 0,
Len: 256, /* 2048 bits */
}
var signature []byte
opts := rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
}
signature, err = rsa.SignPSS(rand.Reader, image.SigningRSA,
crypto.SHA256, image.Hash, &opts)
if err != nil {
return util.NewNewtError(fmt.Sprintf(
"Failed to compute signature: %s", err))
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(signature)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append sig: %s",
err.Error()))
}
}
if image.SigningEC != nil {
r, s, err := ecdsa.Sign(rand.Reader, image.SigningEC, image.Hash)
if err != nil {
return util.NewNewtError(fmt.Sprintf(
"Failed to compute signature: %s", err))
}
sigLen := image.sigLen()
var ECDSA ECDSASig
ECDSA.R = r
ECDSA.S = s
signature, err := asn1.Marshal(ECDSA)
if err != nil {
return util.NewNewtError(fmt.Sprintf(
"Failed to construct signature: %s", err))
}
if len(signature) > int(sigLen) {
return util.NewNewtError(fmt.Sprintf(
"Something is really wrong\n"))
}
tlv := &ImageTrailerTlv{
Type: image.sigTlvType(),
Pad: 0,
Len: sigLen,
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(signature)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append sig: %s",
err.Error()))
}
pad := make([]byte, int(sigLen)-len(signature))
_, err = imgFile.Write(pad)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
}
if cipherSecret != nil {
if len(cipherSecret) == 256 {
_type = IMAGE_TLV_ENC_RSA
} else if len(cipherSecret) == 24 {
_type = IMAGE_TLV_ENC_KEK
} else {
return util.NewNewtError(fmt.Sprintf("Invalid enc TLV size "))
}
tlv := &ImageTrailerTlv{
Type: _type,
Pad: 0,
Len: uint16(len(cipherSecret)),
}
err = binary.Write(imgFile, binary.LittleEndian, tlv)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image "+
"trailer: %s", err.Error()))
}
_, err = imgFile.Write(cipherSecret)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to append encrypted key: %s",
err.Error()))
}
}
util.StatusMessage(util.VERBOSITY_VERBOSE,
"Computed Hash for image %s as %s \n",
image.TargetImg, hex.EncodeToString(image.Hash))
// XXX: Replace "1" with io.SeekCurrent when go 1.7 becomes mainstream.
sz, err := imgFile.Seek(0, 1)
if err != nil {
return util.FmtNewtError("Failed to calculate file size of generated "+
"image %s: %s", image.TargetImg, err.Error())
}
image.TotalSize = uint(sz)
tlvInfo.TlvTotLen = uint16(sz - tlvInfoOff)
/*
* Go back and write tlv info total length
*/
_, err = imgFile.Seek(tlvInfoOff, 0)
if err != nil {
return util.FmtNewtError("Failed to move to tlvInfo offset %d "+
"image: %s", int(tlvInfoOff), err.Error())
}
err = binary.Write(imgFile, binary.LittleEndian, tlvInfo)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Failed to serialize image hdr: %s",
err.Error()))
}
return nil
}
func (image *Image) Generate(loader *Image) error {
if UseV1 {
return image.generateV1(loader)
} else {
return image.generateV2(loader)
}
}
func CreateBuildId(app *Image, loader *Image) []byte {
return app.Hash
}
func NewRepoManager() *RepoManager {
return &RepoManager{
repos: make(map[string]ImageManifestRepo),
}
}
func (r *RepoManager) GetImageManifestPkg(
lpkg *pkg.LocalPackage) *ImageManifestPkg {
ip := &ImageManifestPkg{
Name: lpkg.Name(),
}
var path string
if lpkg.Repo().IsLocal() {
ip.Repo = lpkg.Repo().Name()
path = lpkg.BasePath()
} else {
ip.Repo = lpkg.Repo().Name()
path = lpkg.BasePath()
}
if _, present := r.repos[ip.Repo]; present {
return ip
}
repo := ImageManifestRepo{
Name: ip.Repo,
}
// Make sure we restore the current working dir to whatever it was when
// this function was called
cwd, err := os.Getwd()
if err != nil {
log.Debugf("Unable to determine current working directory: %v", err)
return ip
}
defer os.Chdir(cwd)
if err := os.Chdir(path); err != nil {
return ip
}
var res []byte
res, err = util.ShellCommand([]string{
"git",
"rev-parse",
"HEAD",
}, nil)
if err != nil {
log.Debugf("Unable to determine commit hash for %s: %v", path, err)
repo.Commit = "UNKNOWN"
} else {
repo.Commit = strings.TrimSpace(string(res))
res, err = util.ShellCommand([]string{
"git",
"status",
"--porcelain",
}, nil)
if err != nil {
log.Debugf("Unable to determine dirty state for %s: %v", path, err)
} else {
if len(res) > 0 {
repo.Dirty = true
}
}
res, err = util.ShellCommand([]string{
"git",
"config",
"--get",
"remote.origin.url",
}, nil)
if err != nil {
log.Debugf("Unable to determine URL for %s: %v", path, err)
} else {
repo.URL = strings.TrimSpace(string(res))
}
}
r.repos[ip.Repo] = repo
return ip
}
func (r *RepoManager) AllRepos() []ImageManifestRepo {
keys := make([]string, 0, len(r.repos))
for k := range r.repos {
keys = append(keys, k)
}
sort.Strings(keys)
repos := make([]ImageManifestRepo, 0, len(keys))
for _, key := range keys {
repos = append(repos, r.repos[key])
}
return repos
}
func NewImageManifestSizeCollector() *ImageManifestSizeCollector {
return &ImageManifestSizeCollector{}
}
func (c *ImageManifestSizeCollector) AddPkg(pkg string) *ImageManifestSizePkg {
p := &ImageManifestSizePkg{
Name: pkg,
}
c.Pkgs = append(c.Pkgs, p)
return p
}
func (c *ImageManifestSizePkg) AddSymbol(file string, sym string, area string,
symSz uint32) {
f := c.addFile(file)
s := f.addSym(sym)
s.addArea(area, symSz)
}
func (p *ImageManifestSizePkg) addFile(file string) *ImageManifestSizeFile {
for _, f := range p.Files {
if f.Name == file {
return f
}
}
f := &ImageManifestSizeFile{
Name: file,
}
p.Files = append(p.Files, f)
return f
}
func (f *ImageManifestSizeFile) addSym(sym string) *ImageManifestSizeSym {
s := &ImageManifestSizeSym{
Name: sym,
}
f.Syms = append(f.Syms, s)
return s
}
func (s *ImageManifestSizeSym) addArea(area string, areaSz uint32) {
a := &ImageManifestSizeArea{
Name: area,
Size: areaSz,
}
s.Areas = append(s.Areas, a)
}