You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2019/01/04 18:21:06 UTC
[mynewt-newt] 01/17: "artifact" library
This is an automated email from the ASF dual-hosted git repository.
ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git
commit 1e5f83d231a283d5d50342b13d30f250e0ea55a2
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Tue Nov 20 17:41:07 2018 -0800
"artifact" library
This library subsumes functionality that is currently tied to the newt
tool: Parsing, manipulation, and writing of artifacts that aren't
described by a Mynewt project.
Specifically:
* `.img` file parsing and generation
* Manifest file parsing
---
artifact/flash/flash.go | 160 +++++++++++++
artifact/image/create.go | 427 +++++++++++++++++++++++++++++++++
artifact/image/encrypted.go | 196 +++++++++++++++
artifact/image/image.go | 544 ++++++++++++++++++++++++++++++++++++++++++
artifact/image/key.go | 294 +++++++++++++++++++++++
artifact/image/keys_test.go | 244 +++++++++++++++++++
artifact/image/v1.go | 487 +++++++++++++++++++++++++++++++++++++
artifact/manifest/manifest.go | 95 ++++++++
8 files changed, 2447 insertions(+)
diff --git a/artifact/flash/flash.go b/artifact/flash/flash.go
new file mode 100644
index 0000000..c37d2dd
--- /dev/null
+++ b/artifact/flash/flash.go
@@ -0,0 +1,160 @@
+/**
+ * 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 flash
+
+import (
+ "fmt"
+ "sort"
+)
+
+const FLASH_AREA_NAME_BOOTLOADER = "FLASH_AREA_BOOTLOADER"
+const FLASH_AREA_NAME_IMAGE_0 = "FLASH_AREA_IMAGE_0"
+const FLASH_AREA_NAME_IMAGE_1 = "FLASH_AREA_IMAGE_1"
+const FLASH_AREA_NAME_IMAGE_SCRATCH = "FLASH_AREA_IMAGE_SCRATCH"
+const AREA_USER_ID_MIN = 16
+
+var SYSTEM_AREA_NAME_ID_MAP = map[string]int{
+ FLASH_AREA_NAME_BOOTLOADER: 0,
+ FLASH_AREA_NAME_IMAGE_0: 1,
+ FLASH_AREA_NAME_IMAGE_1: 2,
+ FLASH_AREA_NAME_IMAGE_SCRATCH: 3,
+}
+
+type FlashArea struct {
+ Name string
+ Id int
+ Device int
+ Offset int
+ Size int
+}
+
+type areaOffSorter struct {
+ areas []FlashArea
+}
+
+func (s areaOffSorter) Len() int {
+ return len(s.areas)
+}
+func (s areaOffSorter) Swap(i, j int) {
+ s.areas[i], s.areas[j] = s.areas[j], s.areas[i]
+}
+func (s areaOffSorter) Less(i, j int) bool {
+ ai := s.areas[i]
+ aj := s.areas[j]
+
+ if ai.Device < aj.Device {
+ return true
+ }
+ if ai.Device > aj.Device {
+ return false
+ }
+ return ai.Offset < aj.Offset
+}
+
+func SortFlashAreasByDevOff(areas []FlashArea) []FlashArea {
+ sorter := areaOffSorter{
+ areas: make([]FlashArea, len(areas)),
+ }
+
+ for i, a := range areas {
+ sorter.areas[i] = a
+ }
+
+ sort.Sort(sorter)
+ return sorter.areas
+}
+
+func SortFlashAreasById(areas []FlashArea) []FlashArea {
+ idMap := make(map[int]FlashArea, len(areas))
+ ids := make([]int, 0, len(areas))
+ for _, area := range areas {
+ idMap[area.Id] = area
+ ids = append(ids, area.Id)
+ }
+ sort.Ints(ids)
+
+ sorted := make([]FlashArea, len(ids))
+ for i, id := range ids {
+ sorted[i] = idMap[id]
+ }
+
+ return sorted
+}
+
+func areasDistinct(a FlashArea, b FlashArea) bool {
+ var lo FlashArea
+ var hi FlashArea
+
+ if a.Offset < b.Offset {
+ lo = a
+ hi = b
+ } else {
+ lo = b
+ hi = a
+ }
+
+ return lo.Device != hi.Device || lo.Offset+lo.Size <= hi.Offset
+}
+
+// @return overlapping-areas, id-conflicts.
+func DetectErrors(areas []FlashArea) ([][]FlashArea, [][]FlashArea) {
+ var overlaps [][]FlashArea
+ var conflicts [][]FlashArea
+
+ for i := 0; i < len(areas)-1; i++ {
+ iarea := areas[i]
+ for j := i + 1; j < len(areas); j++ {
+ jarea := areas[j]
+
+ if !areasDistinct(iarea, jarea) {
+ overlaps = append(overlaps, []FlashArea{iarea, jarea})
+ }
+
+ if iarea.Id == jarea.Id {
+ conflicts = append(conflicts, []FlashArea{iarea, jarea})
+ }
+ }
+ }
+
+ return overlaps, conflicts
+}
+
+func ErrorText(overlaps [][]FlashArea, conflicts [][]FlashArea) string {
+ str := ""
+
+ if len(conflicts) > 0 {
+ str += "Conflicting flash area IDs detected:\n"
+
+ for _, pair := range conflicts {
+ str += fmt.Sprintf(" (%d) %s =/= %s\n",
+ pair[0].Id-AREA_USER_ID_MIN, pair[0].Name, pair[1].Name)
+ }
+ }
+
+ if len(overlaps) > 0 {
+ str += "Overlapping flash areas detected:\n"
+
+ for _, pair := range overlaps {
+ str += fmt.Sprintf(" %s =/= %s\n", pair[0].Name, pair[1].Name)
+ }
+ }
+
+ return str
+}
diff --git a/artifact/image/create.go b/artifact/image/create.go
new file mode 100644
index 0000000..5b28120
--- /dev/null
+++ b/artifact/image/create.go
@@ -0,0 +1,427 @@
+/**
+ * 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
+}
diff --git a/artifact/image/encrypted.go b/artifact/image/encrypted.go
new file mode 100644
index 0000000..0547e2f
--- /dev/null
+++ b/artifact/image/encrypted.go
@@ -0,0 +1,196 @@
+/**
+ * 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.
+ */
+
+// Decoder for PKCS#5 encrypted PKCS#8 private keys.
+package image
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "fmt"
+ "hash"
+
+ "golang.org/x/crypto/pbkdf2"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+var (
+ oidPbes2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
+ oidPbkdf2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
+ oidHmacWithSha1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 7}
+ oidHmacWithSha224 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 8}
+ oidHmacWithSha256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 9}
+ oidAes128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2}
+ oidAes256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
+)
+
+// We only support a narrow set of possible key types, namely the type
+// generated by either MCUboot's `imgtool.py` command, or using an
+// OpenSSL command such as:
+//
+// openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
+// -aes-256-cbc > keyfile.pem
+//
+// or similar for ECDSA. Specifically, the encryption must be done
+// with PBES2, and PBKDF2, and aes-256-cbc used as the cipher.
+type pkcs5 struct {
+ Algo pkix.AlgorithmIdentifier
+ Encrypted []byte
+}
+
+// The parameters when the algorithm in pkcs5 is oidPbes2
+type pbes2 struct {
+ KeyDerivationFunc pkix.AlgorithmIdentifier
+ EncryptionScheme pkix.AlgorithmIdentifier
+}
+
+// Salt is given as a choice, but we will only support the inlined
+// octet string.
+type pbkdf2Param struct {
+ Salt []byte
+ IterCount int
+ HashFunc pkix.AlgorithmIdentifier
+ // Optional and default values omitted, and unsupported.
+}
+
+type hashFunc func() hash.Hash
+
+func parseEncryptedPrivateKey(der []byte) (key interface{}, err error) {
+ var wrapper pkcs5
+ if _, err = asn1.Unmarshal(der, &wrapper); err != nil {
+ return nil, err
+ }
+ if !wrapper.Algo.Algorithm.Equal(oidPbes2) {
+ return nil, fmt.Errorf("pkcs5: Unknown PKCS#5 wrapper algorithm: %v", wrapper.Algo.Algorithm)
+ }
+
+ var pbparm pbes2
+ if _, err = asn1.Unmarshal(wrapper.Algo.Parameters.FullBytes, &pbparm); err != nil {
+ return nil, err
+ }
+ if !pbparm.KeyDerivationFunc.Algorithm.Equal(oidPbkdf2) {
+ return nil, fmt.Errorf("pkcs5: Unknown KDF: %v", pbparm.KeyDerivationFunc.Algorithm)
+ }
+
+ var kdfParam pbkdf2Param
+ if _, err = asn1.Unmarshal(pbparm.KeyDerivationFunc.Parameters.FullBytes, &kdfParam); err != nil {
+ return nil, err
+ }
+
+ var hashNew hashFunc
+ switch {
+ case kdfParam.HashFunc.Algorithm.Equal(oidHmacWithSha1):
+ hashNew = sha1.New
+ case kdfParam.HashFunc.Algorithm.Equal(oidHmacWithSha224):
+ hashNew = sha256.New224
+ case kdfParam.HashFunc.Algorithm.Equal(oidHmacWithSha256):
+ hashNew = sha256.New
+ default:
+ return nil, fmt.Errorf("pkcs5: Unsupported hash: %v", pbparm.EncryptionScheme.Algorithm)
+ }
+
+ // Get the encryption used.
+ size := 0
+ var iv []byte
+ switch {
+ case pbparm.EncryptionScheme.Algorithm.Equal(oidAes256CBC):
+ size = 32
+ if _, err = asn1.Unmarshal(pbparm.EncryptionScheme.Parameters.FullBytes, &iv); err != nil {
+ return nil, err
+ }
+ case pbparm.EncryptionScheme.Algorithm.Equal(oidAes128CBC):
+ size = 16
+ if _, err = asn1.Unmarshal(pbparm.EncryptionScheme.Parameters.FullBytes, &iv); err != nil {
+ return nil, err
+ }
+ default:
+ return nil, fmt.Errorf("pkcs5: Unsupported cipher: %v", pbparm.EncryptionScheme.Algorithm)
+ }
+
+ return unwrapPbes2Pbkdf2(&kdfParam, size, iv, hashNew, wrapper.Encrypted)
+}
+
+func unwrapPbes2Pbkdf2(param *pbkdf2Param, size int, iv []byte, hashNew hashFunc, encrypted []byte) (key interface{}, err error) {
+ pass, err := getPassword()
+ if err != nil {
+ return nil, err
+ }
+ cryptoKey := pbkdf2.Key(pass, param.Salt, param.IterCount, size, hashNew)
+
+ block, err := aes.NewCipher(cryptoKey)
+ if err != nil {
+ return nil, err
+ }
+ enc := cipher.NewCBCDecrypter(block, iv)
+
+ plain := make([]byte, len(encrypted))
+ enc.CryptBlocks(plain, encrypted)
+
+ plain, err = checkPkcs7Padding(plain)
+ if err != nil {
+ return nil, err
+ }
+
+ return x509.ParsePKCS8PrivateKey(plain)
+}
+
+// Verify that PKCS#7 padding is correct on this plaintext message.
+// Returns a new slice with the padding removed.
+func checkPkcs7Padding(buf []byte) ([]byte, error) {
+ if len(buf) < 16 {
+ return nil, fmt.Errorf("Invalid padded buffer")
+ }
+
+ padLen := int(buf[len(buf)-1])
+ if padLen < 1 || padLen > 16 {
+ return nil, fmt.Errorf("Invalid padded buffer")
+ }
+
+ if padLen > len(buf) {
+ return nil, fmt.Errorf("Invalid padded buffer")
+ }
+
+ for pos := len(buf) - padLen; pos < len(buf); pos++ {
+ if int(buf[pos]) != padLen {
+ return nil, fmt.Errorf("Invalid padded buffer")
+ }
+ }
+
+ return buf[:len(buf)-padLen], nil
+}
+
+// For testing, a key can be set here. If this is empty, the key will
+// be queried via prompt.
+var KeyPassword = []byte{}
+
+// Prompt the user for a password, unless we have stored one for
+// testing.
+func getPassword() ([]byte, error) {
+ if len(KeyPassword) != 0 {
+ return KeyPassword, nil
+ }
+
+ fmt.Printf("key password: ")
+ return terminal.ReadPassword(0)
+}
diff --git a/artifact/image/image.go b/artifact/image/image.go
new file mode 100644
index 0000000..244cfed
--- /dev/null
+++ b/artifact/image/image.go
@@ -0,0 +1,544 @@
+/**
+ * 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"
+ "encoding/binary"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "strconv"
+ "strings"
+
+ "mynewt.apache.org/newt/util"
+)
+
+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_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_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
+ Body []byte
+ Tlvs []ImageTlv
+}
+
+type ImageOffsets struct {
+ Header int
+ Body int
+ Trailer int
+ Tlvs []int
+ TotalSize int
+}
+
+func ImageTlvTypeName(tlvType uint8) string {
+ name, ok := imageTlvTypeNameMap[tlvType]
+ if !ok {
+ return "???"
+ }
+
+ return name
+}
+
+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 (h *ImageHdr) Map(offset int) map[string]interface{} {
+ return map[string]interface{}{
+ "Magic": h.Magic,
+ "HdrSz": h.HdrSz,
+ "ImgSz": h.ImgSz,
+ "Flags": h.Flags,
+ "Vers": h.Vers.String(),
+ "offset": offset,
+ }
+}
+
+func rawBodyMap(offset int) map[string]interface{} {
+ return map[string]interface{}{
+ "offset": offset,
+ }
+}
+
+func (t *ImageTrailer) Map(offset int) map[string]interface{} {
+ return map[string]interface{}{
+ "Magic": t.Magic,
+ "TlvTotLen": t.TlvTotLen,
+ "offset": offset,
+ }
+}
+
+func (t *ImageTlv) Map(offset int) map[string]interface{} {
+ return map[string]interface{}{
+ "Type": t.Header.Type,
+ "typestr": ImageTlvTypeName(t.Header.Type),
+ "Len": t.Header.Len,
+ "offset": offset,
+ "data": hex.EncodeToString(t.Data),
+ }
+}
+
+func (img *Image) Map() (map[string]interface{}, error) {
+ offs, err := img.Offsets()
+ if err != nil {
+ return nil, err
+ }
+
+ m := map[string]interface{}{}
+ m["header"] = img.Header.Map(offs.Header)
+ m["body"] = rawBodyMap(offs.Body)
+ trailer := img.Trailer()
+ m["trailer"] = trailer.Map(offs.Trailer)
+
+ tlvMaps := []map[string]interface{}{}
+ for i, tlv := range img.Tlvs {
+ tlvMaps = append(tlvMaps, tlv.Map(offs.Tlvs[i]))
+ }
+ m["tlvs"] = tlvMaps
+
+ return m, nil
+}
+
+func (img *Image) Json() (string, error) {
+ m, err := img.Map()
+ if err != nil {
+ return "", err
+ }
+
+ b, err := json.MarshalIndent(m, "", " ")
+ if err != nil {
+ return "", util.ChildNewtError(err)
+ }
+
+ return string(b), nil
+}
+
+func (tlv *ImageTlv) Write(w io.Writer) (int, error) {
+ totalSize := 0
+
+ err := binary.Write(w, binary.LittleEndian, &tlv.Header)
+ if err != nil {
+ return totalSize, util.ChildNewtError(err)
+ }
+ totalSize += IMAGE_TLV_SIZE
+
+ size, err := w.Write(tlv.Data)
+ if err != nil {
+ return totalSize, util.ChildNewtError(err)
+ }
+ totalSize += size
+
+ return totalSize, nil
+}
+
+func (i *Image) FindTlvs(tlvType uint8) []ImageTlv {
+ var tlvs []ImageTlv
+
+ for _, tlv := range i.Tlvs {
+ if tlv.Header.Type == tlvType {
+ tlvs = append(tlvs, tlv)
+ }
+ }
+
+ return tlvs
+}
+
+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, util.FmtNewtError("Image contains %d TLVs with type %d",
+ len(tlvs), tlvType)
+ }
+
+ return &tlvs[0], nil
+}
+
+func (i *Image) RemoveTlvsIf(pred func(tlv ImageTlv) bool) int {
+ numRmed := 0
+ for idx := 0; idx < len(i.Tlvs); {
+ tlv := i.Tlvs[idx]
+ if pred(tlv) {
+ i.Tlvs = append(i.Tlvs[:idx], i.Tlvs[idx+1:]...)
+ numRmed++
+ } else {
+ idx++
+ }
+ }
+
+ return numRmed
+}
+
+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
+}
+
+func (i *Image) Hash() ([]byte, error) {
+ tlv, err := i.FindUniqueTlv(IMAGE_TLV_SHA256)
+ if err != nil {
+ return nil, err
+ }
+
+ if tlv == nil {
+ return nil, util.FmtNewtError("Image does not contain hash TLV")
+ }
+
+ return tlv.Data, nil
+}
+
+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, util.ChildNewtError(err)
+ }
+ offset += IMAGE_HEADER_SIZE
+
+ offs.Body = offset
+ size, err := w.Write(i.Body)
+ if err != nil {
+ return offs, util.ChildNewtError(err)
+ }
+ offset += size
+
+ trailer := i.Trailer()
+ offs.Trailer = offset
+ err = binary.Write(w, binary.LittleEndian, &trailer)
+ if err != nil {
+ return offs, util.ChildNewtError(err)
+ }
+ 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, util.ChildNewtError(err)
+ }
+ offset += size
+ }
+
+ offs.TotalSize = offset
+
+ return offs, nil
+}
+
+func (i *Image) Offsets() (ImageOffsets, error) {
+ return i.WritePlusOffsets(ioutil.Discard)
+}
+
+func (i *Image) TotalSize() (int, error) {
+ offs, err := i.Offsets()
+ if err != nil {
+ return 0, err
+ }
+ return offs.TotalSize, nil
+}
+
+func (i *Image) Write(w io.Writer) (int, error) {
+ offs, err := i.WritePlusOffsets(w)
+ if err != nil {
+ return 0, err
+ }
+
+ return offs.TotalSize, nil
+}
+
+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 util.ChildNewtError(err)
+ }
+
+ if _, err := i.Write(f); err != nil {
+ return util.ChildNewtError(err)
+ }
+
+ return nil
+}
+
+func parseRawHeader(imgData []byte, offset int) (ImageHdr, int, error) {
+ var hdr ImageHdr
+
+ r := bytes.NewReader(imgData)
+ r.Seek(int64(offset), io.SeekStart)
+
+ if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil {
+ return hdr, 0, util.FmtNewtError(
+ "Error reading image header: %s", err.Error())
+ }
+
+ if hdr.Magic != IMAGE_MAGIC {
+ return hdr, 0, util.FmtNewtError(
+ "Image magic incorrect; expected 0x%08x, got 0x%08x",
+ IMAGE_MAGIC, hdr.Magic)
+ }
+
+ remLen := len(imgData) - offset
+ if remLen < int(hdr.HdrSz) {
+ return hdr, 0, util.FmtNewtError(
+ "Image header incomplete; expected %d bytes, got %d bytes",
+ hdr.HdrSz, remLen)
+ }
+
+ return hdr, int(hdr.HdrSz), nil
+}
+
+func parseRawBody(imgData []byte, hdr ImageHdr,
+ offset int) ([]byte, int, error) {
+
+ imgSz := int(hdr.ImgSz)
+ remLen := len(imgData) - offset
+
+ if remLen < imgSz {
+ return nil, 0, util.FmtNewtError(
+ "Image body incomplete; expected %d bytes, got %d bytes",
+ imgSz, remLen)
+ }
+
+ return imgData[offset : offset+imgSz], imgSz, nil
+}
+
+func parseRawTrailer(imgData []byte, offset int) (ImageTrailer, int, error) {
+ var trailer ImageTrailer
+
+ r := bytes.NewReader(imgData)
+ r.Seek(int64(offset), io.SeekStart)
+
+ if err := binary.Read(r, binary.LittleEndian, &trailer); err != nil {
+ return trailer, 0, util.FmtNewtError(
+ "Image contains invalid trailer at offset %d: %s",
+ offset, err.Error())
+ }
+
+ return trailer, IMAGE_TRAILER_SIZE, nil
+}
+
+func parseRawTlv(imgData []byte, offset int) (ImageTlv, int, error) {
+ tlv := ImageTlv{}
+
+ r := bytes.NewReader(imgData)
+ r.Seek(int64(offset), io.SeekStart)
+
+ if err := binary.Read(r, binary.LittleEndian, &tlv.Header); err != nil {
+ return tlv, 0, util.FmtNewtError(
+ "Image contains invalid TLV at offset %d: %s", offset, err.Error())
+ }
+
+ tlv.Data = make([]byte, tlv.Header.Len)
+ if _, err := r.Read(tlv.Data); err != nil {
+ return tlv, 0, util.FmtNewtError(
+ "Image contains invalid TLV at offset %d: %s", offset, err.Error())
+ }
+
+ return tlv, IMAGE_TLV_SIZE + int(tlv.Header.Len), nil
+}
+
+func ParseImage(imgData []byte) (Image, error) {
+ img := Image{}
+ offset := 0
+
+ hdr, size, err := parseRawHeader(imgData, offset)
+ if err != nil {
+ return img, err
+ }
+ offset += size
+
+ body, size, err := parseRawBody(imgData, hdr, offset)
+ if err != nil {
+ return img, err
+ }
+ offset += size
+
+ trailer, size, err := parseRawTrailer(imgData, offset)
+ if err != nil {
+ return img, err
+ }
+ offset += size
+
+ var tlvs []ImageTlv
+ tlvLen := IMAGE_TRAILER_SIZE
+ for offset < len(imgData) {
+ tlv, size, err := parseRawTlv(imgData, offset)
+ if err != nil {
+ return img, err
+ }
+
+ tlvs = append(tlvs, tlv)
+ offset += size
+
+ tlvLen += IMAGE_TLV_SIZE + int(tlv.Header.Len)
+ }
+
+ if int(trailer.TlvTotLen) != tlvLen {
+ return img, util.FmtNewtError(
+ "invalid image: trailer indicates TLV-length=%d; actual=%d",
+ trailer.TlvTotLen, tlvLen)
+ }
+
+ img.Header = hdr
+ img.Body = body
+ img.Tlvs = tlvs
+
+ return img, nil
+}
+
+func ReadImage(filename string) (Image, error) {
+ ri := Image{}
+
+ imgData, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return ri, util.ChildNewtError(err)
+ }
+
+ return ParseImage(imgData)
+}
diff --git a/artifact/image/key.go b/artifact/image/key.go
new file mode 100644
index 0000000..9141f6e
--- /dev/null
+++ b/artifact/image/key.go
@@ -0,0 +1,294 @@
+/**
+ * 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 (
+ "crypto/aes"
+ "crypto/ecdsa"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/asn1"
+ "encoding/base64"
+ "encoding/pem"
+ "io/ioutil"
+
+ keywrap "github.com/NickBall/go-aes-key-wrap"
+
+ "mynewt.apache.org/newt/util"
+)
+
+type ImageSigKey struct {
+ // Only one of these members is non-nil.
+ Rsa *rsa.PrivateKey
+ Ec *ecdsa.PrivateKey
+}
+
+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.FmtNewtError(
+ "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.FmtNewtError(
+ "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.FmtNewtError(
+ "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 ReadKey(filename string) (ImageSigKey, error) {
+ key := ImageSigKey{}
+
+ keyBytes, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return key, util.FmtNewtError("Error reading key file: %s", err)
+ }
+
+ privKey, err := ParsePrivateKey(keyBytes)
+ if err != nil {
+ return key, err
+ }
+
+ switch priv := privKey.(type) {
+ case *rsa.PrivateKey:
+ key.Rsa = priv
+ case *ecdsa.PrivateKey:
+ key.Ec = priv
+ default:
+ return key, util.NewNewtError("Unknown private key format")
+ }
+
+ return key, nil
+}
+
+func ReadKeys(filenames []string) ([]ImageSigKey, error) {
+ keys := make([]ImageSigKey, len(filenames))
+
+ for i, filename := range filenames {
+ key, err := ReadKey(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ keys[i] = key
+ }
+
+ return keys, nil
+}
+
+func (key *ImageSigKey) assertValid() {
+ if key.Rsa == nil && key.Ec == nil {
+ panic("invalid key; neither RSA nor ECC")
+ }
+
+ if key.Rsa != nil && key.Ec != nil {
+ panic("invalid key; neither RSA nor ECC")
+ }
+}
+
+func (key *ImageSigKey) sigKeyHash() ([]uint8, error) {
+ key.assertValid()
+
+ if key.Rsa != nil {
+ pubkey, _ := asn1.Marshal(key.Rsa.PublicKey)
+ sum := sha256.Sum256(pubkey)
+ return sum[:4], nil
+ } else {
+ switch key.Ec.Curve.Params().Name {
+ case "P-224":
+ fallthrough
+ case "P-256":
+ pubkey, _ := x509.MarshalPKIXPublicKey(&key.Ec.PublicKey)
+ sum := sha256.Sum256(pubkey)
+ return sum[:4], nil
+ default:
+ return nil, util.NewNewtError("Unsupported ECC curve")
+ }
+ }
+}
+
+func (key *ImageSigKey) sigLen() uint16 {
+ key.assertValid()
+
+ if key.Rsa != nil {
+ return 256
+ } else {
+ switch key.Ec.Curve.Params().Name {
+ case "P-224":
+ return 68
+ case "P-256":
+ return 72
+ default:
+ return 0
+ }
+ }
+}
+
+func (key *ImageSigKey) sigTlvType() uint8 {
+ key.assertValid()
+
+ if key.Rsa != nil {
+ return IMAGE_TLV_RSA2048
+ } else {
+ switch key.Ec.Curve.Params().Name {
+ case "P-224":
+ return IMAGE_TLV_ECDSA224
+ case "P-256":
+ return IMAGE_TLV_ECDSA256
+ default:
+ return 0
+ }
+ }
+}
+
+func parseEncKeyPem(keyBytes []byte, plainSecret []byte) ([]byte, error) {
+ b, _ := pem.Decode(keyBytes)
+ if b == nil {
+ return nil, nil
+ }
+
+ if b.Type != "PUBLIC KEY" && b.Type != "RSA PUBLIC KEY" {
+ return nil, util.NewNewtError("Invalid PEM file")
+ }
+
+ pub, err := x509.ParsePKIXPublicKey(b.Bytes)
+ if err != nil {
+ return nil, util.FmtNewtError(
+ "Error parsing pubkey file: %s", err.Error())
+ }
+
+ var pubk *rsa.PublicKey
+ switch pub.(type) {
+ case *rsa.PublicKey:
+ pubk = pub.(*rsa.PublicKey)
+ default:
+ return nil, util.FmtNewtError(
+ "Error parsing pubkey file: %s", err.Error())
+ }
+
+ rng := rand.Reader
+ cipherSecret, err := rsa.EncryptOAEP(
+ sha256.New(), rng, pubk, plainSecret, nil)
+ if err != nil {
+ return nil, util.FmtNewtError(
+ "Error from encryption: %s\n", err.Error())
+ }
+
+ return cipherSecret, nil
+}
+
+func parseEncKeyBase64(keyBytes []byte, plainSecret []byte) ([]byte, error) {
+ kek, err := base64.StdEncoding.DecodeString(string(keyBytes))
+ if err != nil {
+ return nil, util.FmtNewtError(
+ "Error decoding kek: %s", err.Error())
+ }
+ if len(kek) != 16 {
+ return nil, util.FmtNewtError(
+ "Unexpected key size: %d != 16", len(kek))
+ }
+
+ cipher, err := aes.NewCipher(kek)
+ if err != nil {
+ return nil, util.FmtNewtError(
+ "Error creating keywrap cipher: %s", err.Error())
+ }
+
+ cipherSecret, err := keywrap.Wrap(cipher, plainSecret)
+ if err != nil {
+ return nil, util.FmtNewtError("Error key-wrapping: %s", err.Error())
+ }
+
+ return cipherSecret, nil
+}
+
+func ReadEncKey(filename string, plainSecret []byte) ([]byte, error) {
+ keyBytes, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, util.FmtNewtError(
+ "Error reading pubkey file: %s", err.Error())
+ }
+
+ // Try reading as PEM (asymetric key).
+ cipherSecret, err := parseEncKeyPem(keyBytes, plainSecret)
+ if err != nil {
+ return nil, err
+ }
+ if cipherSecret != nil {
+ return cipherSecret, nil
+ }
+
+ // Not PEM; assume this is a base64 encoded symetric key
+ cipherSecret, err = parseEncKeyBase64(keyBytes, plainSecret)
+ if err != nil {
+ return nil, err
+ }
+
+ return cipherSecret, nil
+}
diff --git a/artifact/image/keys_test.go b/artifact/image/keys_test.go
new file mode 100644
index 0000000..577d9cd
--- /dev/null
+++ b/artifact/image/keys_test.go
@@ -0,0 +1,244 @@
+/**
+ * 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_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+ "testing"
+)
+
+func TestRSA(t *testing.T) {
+ signatureTest(t, rsaPkcs1Private)
+}
+
+func TestPlainRSAPKCS8(t *testing.T) {
+ signatureTest(t, rsaPkcs8Private)
+}
+
+func TestEcdsa(t *testing.T) {
+ signatureTest(t, ecdsaPrivate)
+}
+
+func TestPlainEcdsaPkcs8(t *testing.T) {
+ signatureTest(t, ecdsaPkcs8Private)
+}
+
+func TestEncryptedRSA(t *testing.T) {
+ image.KeyPassword = []byte("sample")
+ signatureTest(t, rsaEncryptedPrivate)
+ image.KeyPassword = []byte{}
+}
+
+func TestEncryptedEcdsa(t *testing.T) {
+ image.KeyPassword = []byte("sample")
+ signatureTest(t, ecdsaEncryptedPrivate)
+ image.KeyPassword = []byte{}
+}
+
+func signatureTest(t *testing.T, privateKey []byte) {
+ tmpdir, err := ioutil.TempDir("", "newttest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ // Create a source image. Format doesn't really matter that
+ // much, since the header will be placed on it by the image
+ // tool.
+
+ simpleName := path.Join(tmpdir, "simple.bin")
+ hashedName := path.Join(tmpdir, "simple-hashed.bin")
+ signedName := path.Join(tmpdir, "simple-signed.bin")
+ keyName := path.Join(tmpdir, "private.pem")
+
+ tmp := make([]byte, 256)
+ for i := 0; i < len(tmp); i++ {
+ tmp[i] = byte(i & 0xFF)
+ }
+ err = ioutil.WriteFile(simpleName, tmp, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ img, err := image.NewImage(simpleName, hashedName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ img.SetVersion("1.5")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ img.Generate(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Now try with a signature.
+ err = ioutil.WriteFile(keyName, privateKey, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ img, err = image.NewImage(simpleName, signedName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = img.SetSigningKey(keyName, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = img.SetVersion("1.6")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = img.Generate(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// An RSA private key in the old PKCS1 format.
+var rsaPkcs1Private = []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6q2Q/VoFf6U5xm35ynls+HDbHKwfIbBr27PtFJxlS9YT0xKJ
+bcZScPTVizTlft0wfp2TctX/vGd/Y/X3qo5ckRmz+lKUeHm46i4k6rtOBbhBz2id
+hwrO7/ylzwaf8lxn2dj/9ikoYQKFtBb/cKu8wyuvW3gs/ou51AVEF8aKTrl5Expy
+PrhSlh97er2zUmm8NAoo259I5yHK1SvR9kCw2gNXSDQLpFlK2WikdmEbIu0N+cvN
+WM4ONAhffkasznrEOoLPSI66RDrzYhi/Ks9t+N2buEOXao19fDRcSHgZLKT8e6W6
+uK7WxRiEzNbajzgDddbZFqWlcpE7sqPNHFBijwIDAQABAoIBAQDdXx7fLpTzROvM
+F5/C9GnrraGzWVYAlIgZ9o8Umzceo3GN8PV8fND1xq7Novc9he8h8QjPEbksg0Dz
+DWo0FBiTs3hIELAHOWNKXH7sggVmddp2iUvXwEVWsq/CK5CjsbExGXbSQR7a6+Mt
+72fEY+wq+0Fuel2PPETuEI2cE+gRuyspIcO7asmMvLRkxLi2EXU0s4JlqV9UfxKQ
+aqn0PHlRXa5SIzys3mVhXuoe45T50+VKX0DIfu/RuV8njNkkMx74DeEVvf5W4MJW
+vHrRBHoK6KoMrqiwafyPLW/Rh6fMYAdPrffMVuuThtG7Hp83VBVX1HxFhI4Jrf3S
+Hf63hmSZAoGBAO2R/vYBl57qgWEKiMQaU8rzctRbP0TtTsqNdISPMnHV1Tn/rNAU
+m0N7/6IBDb+IlndXDQwIsW/DTXhF2XJSu7n6GXua8B1LF+zuVWUsFfmE3+eLz7B8
+x8G/OkSnOTfRZCYWEoEvzhynn1dlADQ+x49I/XmKqccvAhY71glk6WULAoGBAPzi
+IYo9G+ktlNj9/3OciX7aTCiIIMDyPYtYS6wi59gwH9IswaicHYK4w2fDpTWKlzEE
+18dKF4puuI5GxnKCwHBiWxGhij063cZKKMqA64X41csK+mumux/PAb2gKbGSzzoF
+mSgkKXJ+sZ4ytlgsijEAHV85Sw7j+xy8A0qnCWMNAoGAeCDR7q1hcM8duucrvxWc
+90vg7bZyKLVimROsLneGR3+cAWbiiJlS5W3nFpE31XkItLHE/CfNKTl1i/KuAJwL
+JwBrMFBpSDa3k2v0rGL9fZ2N5rSQwapnC/ZZTWvNiAcOgB+7Ha4BqAWuke+VidWQ
+7Ug4O+Q882Y2xO1ezoNDbX8CgYBq228KyAm8PXuRObsw8iuTg9D8q5ETlwj0kcng
+IhvP2X4IxMrMYbOCompHtX9hIYADwaUgXCmYYHLyA+wlRSTmGFmdGKKefvppqLqV
+32YmhWBp3Oi2hoy5wzJcG4qis4OHZAg00xsEe464Z3tvxNpcHE1NCJuz3hglKzlE
+2VJ5HQKBgQDRisWDbdnOEp7LTXp3Aa33PF1Rx/pkFk4Wb+2Hk977O1OxsAin2cKM
+S5HCltHvON2sCmSQUIxMXXKaNPJiGL3UZJxWZDj38zSg0vO+msmemS1Yjt0xCpbO
+pkl0kvKb/NVlsY4w9kquvql+t9e1rUu9Ug28TKEsSjc9SFrcnVPoNA==
+-----END RSA PRIVATE KEY-----
+`)
+
+// An RSA private key in PKCS8 format, with no encryption.
+var rsaPkcs8Private = []byte(`-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC+FjuXqPSPucsQ
+adxY4nw+9kTgAdsXRIPxq4Q//wkfjEjYhDczN+/rafi0hApuRh7PN7VMGOsDGGR1
+edyertiLt3SfUHAZROIqZ0VAoKGtxgXmnC+s+mMujAv9Ssntbmbi5tNxDcltdWjA
+SdBn7tbIMVVofKaMMugyuXCglxebMm8yxtkSgUvE1E6zZERnteDJTPo8dBCiqkvU
+hf+vG9s1j9lNDMjrZ+d5CHIFmBxJ/WFa6m49lNBFb1Ba43bKdj6mkK05rZ4VWMXU
+evy3Z/UUgU4VPJpoB+GIKy82iOrtjiU7s/6aDkvZ2e+fgxKksN0pzFE9azeA73QS
+bamp28E/AgMBAAECggEBAJ78+4UDFOKt1JF66YkSjjcfRkZSZwyUCwP0oF3ik5/m
+dvtZws29KJevgAyEMDFBxv0srB/k65QgL84uSgATYB2kKRAjeE86VSyASeUfNXui
+GEdlNV8p4hEJo/GMP06uu7FmvU1e6a36uM20L3LuyoiQ8s29DJRQ8/ORNQmstlrg
+J32FZSjTF1mElGPSc1koxhWvl1hE7UGE9pxsSfdsvPNhCIWwAOnVnIv49xG8EWaK
+CkHhEVVdZW8IvO9GYR5U0BJcgzNmdNkS8HVQBIxZtboGAAuPI32EC7siDomKmCF6
+rEcs40f/J/RlK6lrTyKKfqWb4DPtRrOSh9cmjrFFZlECgYEA6mZIANLXJd7CINZ9
+fjotI+FxH8BDOZF7l8xTnOk1e3Me1ia7t2GMcIL+frfG/zMBiDFq0IQuUYScRK1v
+pAILjJKFiU6yY8vH6FZ3mXqiiag6RPa+q89DaUsO0uXRUjQvhtTd5Yy6r8Eac1ya
+y6XC5T5sCJ6HgaF3qlheap+5FkkCgYEAz5qSLShV5oekuj1R0fs+h/Yn7VW9Q0sj
+px8jOD4MWc8gPZ9gZe0UPTvofOLrM3eAetP4egSif99AE9iD8EbiBzAt16OX7EN8
+d7xNiIN922Ep3pubcD6f1vglaI7Thrca/p52g6kWPip6+PWFd1acU6u31Uj0Xvgz
+VFiafstF+0cCgYEAw2sOcJFXCZ2Tnyjzav85jwZu95ek9CPUNJQGyXSsQAWUGdok
++hf7q/mqDx9Maoqtpkv8z2bD7vZuCdvGjaee1U16wyS3GPhV69/ayjwxsi5slf5Y
+rIiZnPkUnMM5Jh2X2gMyFCSlp82ILdFwxIOn3tOR4gW411w0lfIilSYgevECgYA3
+JAgVZHREcdzH9seHrWLze+co+6/0cr26guO46YogRIp8s5tIF0tb5FCg8yijl+cR
+OMHzrs12h1aertCEfl9Ep4BVmUcd4uLpbqNtUfeY0FrtnIkRrCCKWYieF+mJC5No
+86/o0n1s752QCK51fxSwiJigVutJWkVP7uTCLr2cuwKBgQCJPWMcWmSuRlLOVWnO
+jPFoa02Bb83n8GrRpQkpkZZofHextwfo2dd1sZF72zghRsbdC6e0Zj1GrekJOYXO
+8AXmCpyKlXJU7iH5tPGSo68uFN05R6mINbTNmEIQBNTKv8UoKT+nHcTycFrVtarX
+A8EPW2xB86m+Bjq/GNyRgfbPMg==
+-----END PRIVATE KEY-----
+`)
+
+// An ECDSA key in the X.509 internal private key format.
+var ecdsaPrivate = []byte(`-----BEGIN EC PRIVATE KEY-----
+MGgCAQEEHF64kDx3pZyVvezbqYMIxlLbtuPQmI85k4GRy1mgBwYFK4EEACGhPAM6
+AASRtolOCTLQYkDefkIF02tUXR92MKHrbtH4WK/8bfTSFVkaygTPdJbpNthK2wae
+oX9ZeFHS1pcOfQ==
+-----END EC PRIVATE KEY-----
+`)
+
+// An ECDSA key in PKCS#8 format, no encryption.
+var ecdsaPkcs8Private = []byte(`-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgHKeDq4UU6M+c+pMm
+j0AQZlBs7f4r67668eDCUB8aDR2hRANCAATyZPzsx+xn9JtlxdspevTrYisiMTjl
+YuBJCrV1FZj2HkplEgO+ZIMuD7eRvyTEBS2bw6F1aCeKOMUmYVImAbpc
+-----END PRIVATE KEY-----
+`)
+
+// A password-protected RSA private key in PKCS#5/8 format. The
+// password for this key is "sample".
+var rsaEncryptedPrivate = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIRMifqJThk8kCAggA
+MB0GCWCGSAFlAwQBKgQQTMUBoFpzjJ5UNRnCIeqf4QSCBNDkQvXnUNmss8erKiDo
+Uqs2tf9ZD8MjDThLBmF/gV1dg1q6aDY+3fI2E4yLXJb2PmKcUq82YZ0FDeoCvJRJ
+BCurzM9slur5akpNBTFoFwtFsdHz7nKNS4MHUul22rGBnVFUUNTySmpjl/m+dxWO
+fa6tWpGTAr7tsCy9gF5PxpSw7NR/NpIL0PmpydHWhTs1tl2csqBqK6Tp014Kefi/
+pmmeb2eRl5cmprxW32rW2QBMtv4z91SsbnlVdz4r8txTG+3S4td9v9jD5kqcIiC2
+KQHrbH9y7okUk/ISsp9ANKPJt10fbYDxORiMK57XssXy1enGjpkIIrUGz1TMydkD
+USfwqkmPuIrrzOXnbxU4ef2wC/pA/h9Smby3WWYo8725/1kZyIediNDcgi/Qgrs4
+1VQAYzsD6duwyUNSo+tgmYVFGvZhsottus3fMWe/Ay1biJ6z6Vk8gqKWI1VV/REJ
+zK/I9hgKGxj2N2Ff6E/YkcwQenHWj/iDWLjvokyOBnPFNqzzM2Qqo1XFpzj4EO5D
+0WD4EzZYvUhk3lZZNydvXiuy8RrCVLLJMS08XgOqQaiFqqxj2hjRwv3nBesk7iA8
+5Tv8GMa5QkNrISCnp4/uGBh+v/CjwVRqPTcK3/mctPN2nLhI6H4pF4Y6apXkz1TN
+NMQqxaxmVVg8fyLaS4/xfUr8LAmiEtOwvs0XOhcqCTvvlsO4N+yec4VD4gmsTDY9
+/2b/+YwSlGMpA+GQQbg0FraaF8NyJRG1mSER6WiUGGM1cuKK44nzBbykQbZwzDSA
+kkhjDaadkhv/NPKAUR3sNy2GXVaNL/ItCpQUHRKKcIPp0HhdXsl0YebuwRlHjw/6
+UOdzNYe23e40X/Xl3vmOKRbzhLP/qO2DV21o0wI4ujF8Xu5h1h8s49HPp58G1ldy
+/hJ6durYKX8T5khiR2iXYewoy0YObuccV//Ov1/ySOp/x0/QuCl/swvs8Jf7awnu
+rpRrHPArpCvMmXmt5Y+TFYXFjkJGwsxTew5TcwBebBlIET2XNbo2pbz4WqJ3eVlK
+CNZVDEZ8mMrGT00FBi759Vfw9rhrnqXnLlNtJZ5VCXFUw8Tos302sLaQWXzHYyf8
+4awM8G9PSu5Q9lFcN9od4H95YrAAv/l8F+pcGgEKD8ZuzsgFIalqgx5wzmUMDcPM
+NKV5u9mtHjI92ru6NB8rGesM6sy6kBGvpotsDWawpV2SoCrkbyEkk+kXaGS+fsG7
+D2H37GfktN8R5Ktc0Uf/JJiNfDzq8lk1J4r7LBQlWUbhKbfGMYxt+7Xo0GsqAsLp
+PKSUwx+hTZb3BmW6s4Q6vivI1MdQbWVT1zh41StvfRSNlo70iOFxOM0lU1jjY989
+UKo+gcolddvZbMNwip0ILPO3dsa+he1jJ/gbo9qBHLy7plfsBLLakZP1Nu6xdlqQ
+TSSobaE8uxUMZk+wMWClA9AOZ1TcUr2yRV5GVj/bxG9ab+H37vF9F8vFE+jjJ7yN
+6pjdohm4gXeSVx7ON4SeZLsVwNYkCVYS89E81qLx1jP9F57+6IUGDZN5EMC0aJLT
+ny75MCCLT00KD7BFsb0KDLXxp++eu/L2hinorT3p6dXp/9mUoxmy6wJqEyqCFniZ
+N2GZN7+LDTIbHUxCijVWamU2DQ==
+-----END ENCRYPTED PRIVATE KEY-----
+`)
+
+// A password-protected ECDSA private key in PKCS#5/8 format. The
+// password for this key is "sample"
+var ecdsaEncryptedPrivate = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAjlKrDSKNg9QQICCAAw
+HQYJYIZIAWUDBAEqBBDliPNzQTNpdlppTcYpmuhWBIGQVhfWaVSzUvi/qIZLiZVn
+Nulfw5jDOlbn3UBX9kp/Z9Pro582Q0kjzLfm5UahvDINEJWxL4pc/28UnGQTBr0Q
+nSEg+RbqpuD099C38H0Gq/YkIM+RDG4aiQrkmzHXyVsHshIbG+z2LsLTIwmU69/Z
+v0nX6/hGErVR8YWcrOne086rCvfJVrxyO5+EUqrkLhEr
+-----END ENCRYPTED PRIVATE KEY-----
+`)
diff --git a/artifact/image/v1.go b/artifact/image/v1.go
new file mode 100644
index 0000000..bab86f6
--- /dev/null
+++ b/artifact/image/v1.go
@@ -0,0 +1,487 @@
+/**
+ * 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"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+
+ "mynewt.apache.org/newt/util"
+)
+
+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, util.FmtNewtError("Image does not contain hash TLV")
+ }
+ if len(tlvs) > 1 {
+ return nil, util.FmtNewtError("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, util.ChildNewtError(err)
+ }
+ offset += IMAGE_HEADER_SIZE
+
+ offs.Body = offset
+ size, err := w.Write(img.Body)
+ if err != nil {
+ return offs, util.ChildNewtError(err)
+ }
+ offset += size
+
+ for _, tlv := range img.Tlvs {
+ offs.Tlvs = append(offs.Tlvs, offset)
+ size, err := tlv.Write(w)
+ if err != nil {
+ return offs, util.ChildNewtError(err)
+ }
+ 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 (key *ImageSigKey) sigHdrTypeV1() (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, util.FmtNewtError("Unsupported ECC curve")
+ }
+ }
+}
+
+func (key *ImageSigKey) sigTlvTypeV1() 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, util.FmtNewtError("Failed to compute signature: %s", err)
+ }
+
+ return signature, nil
+}
+
+func generateV1SigTlvRsa(key ImageSigKey, hash []byte) (ImageTlv, error) {
+ sig, err := generateV1SigRsa(key.Rsa, hash)
+ if err != nil {
+ return ImageTlv{}, err
+ }
+
+ return ImageTlv{
+ Header: ImageTlvHdr{
+ Type: key.sigTlvTypeV1(),
+ Pad: 0,
+ Len: 256, /* 2048 bits */
+ },
+ Data: sig,
+ }, nil
+}
+
+func generateV1SigTlvEc(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.sigTlvTypeV1(),
+ Pad: 0,
+ Len: sigLen + uint16(len(pad)),
+ },
+ Data: b.Bytes(),
+ }, nil
+}
+
+func generateV1SigTlv(key ImageSigKey, 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 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) CreateV1() (ImageV1, error) {
+ ri := ImageV1{}
+
+ if len(ic.SigKeys) > 1 {
+ return ri, util.FmtNewtError(
+ "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, util.FmtNewtError("Image header must be at "+
+ "least %d bytes", IMAGE_HEADER_SIZE)
+ }
+
+ hdr.HdrSz = uint16(ic.HeaderSize)
+ }
+
+ if len(ic.SigKeys) > 0 {
+ keyFlag, err := ic.SigKeys[0].sigHdrTypeV1()
+ 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, 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)
+ }
+ }
+
+ hashBytes, err := calcHashV1(ic.InitialHash, hdr, ic.Body)
+ if err != nil {
+ return ri, err
+ }
+
+ util.StatusMessage(util.VERBOSITY_VERBOSE,
+ "Computed Hash for image as %s\n", hex.EncodeToString(hashBytes))
+
+ /*
+ * 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, util.FmtNewtError(
+ "Failed to read from image body: %s", err.Error())
+ }
+ if cnt == 0 {
+ break
+ }
+
+ if _, err = w.Write(dataBuf[0:cnt]); err != nil {
+ return ri, util.FmtNewtError(
+ "Failed to write to image body: %s", err.Error())
+ }
+ }
+ 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{}, 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 ImageV1{}, util.FmtNewtError(
+ "Random generation error: %s\n", err)
+ }
+
+ cipherSecret, err := ReadEncKey(opts.SrcEncKeyFilename, 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
+}
diff --git a/artifact/manifest/manifest.go b/artifact/manifest/manifest.go
new file mode 100644
index 0000000..62d8c06
--- /dev/null
+++ b/artifact/manifest/manifest.go
@@ -0,0 +1,95 @@
+package manifest
+
+import (
+ "encoding/json"
+ "io"
+ "io/ioutil"
+
+ "mynewt.apache.org/newt/artifact/flash"
+ "mynewt.apache.org/newt/util"
+)
+
+/*
+ * Data that's going to go to build manifest file
+ */
+type ManifestSizeArea struct {
+ Name string `json:"name"`
+ Size uint32 `json:"size"`
+}
+
+type ManifestSizeSym struct {
+ Name string `json:"name"`
+ Areas []*ManifestSizeArea `json:"areas"`
+}
+
+type ManifestSizeFile struct {
+ Name string `json:"name"`
+ Syms []*ManifestSizeSym `json:"sym"`
+}
+
+type ManifestSizePkg struct {
+ Name string `json:"name"`
+ Files []*ManifestSizeFile `json:"files"`
+}
+
+type ManifestPkg struct {
+ Name string `json:"name"`
+ Repo string `json:"repo"`
+}
+
+type ManifestRepo struct {
+ Name string `json:"name"`
+ Commit string `json:"commit"`
+ Dirty bool `json:"dirty,omitempty"`
+ URL string `json:"url,omitempty"`
+}
+
+type Manifest 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 []*ManifestPkg `json:"pkgs"`
+ LoaderPkgs []*ManifestPkg `json:"loader_pkgs,omitempty"`
+ TgtVars []string `json:"target"`
+ Repos []*ManifestRepo `json:"repos"`
+ FlashAreas []flash.FlashArea `json:"flash_map"`
+
+ PkgSizes []*ManifestSizePkg `json:"pkgsz"`
+ LoaderPkgSizes []*ManifestSizePkg `json:"loader_pkgsz,omitempty"`
+}
+
+func ReadManifest(path string) (Manifest, error) {
+ m := Manifest{}
+
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return m, util.ChildNewtError(err)
+ }
+
+ if err := json.Unmarshal(content, &m); err != nil {
+ return m, util.FmtNewtError(
+ "Failure decoding manifest with path \"%s\": %s",
+ path, err.Error())
+ }
+
+ return m, nil
+}
+
+func (m *Manifest) Write(w io.Writer) (int, error) {
+ buffer, err := json.MarshalIndent(m, "", " ")
+ if err != nil {
+ return 0, util.FmtNewtError("Cannot encode manifest: %s", err.Error())
+ }
+
+ cnt, err := w.Write(buffer)
+ if err != nil {
+ return 0, util.FmtNewtError("Cannot write manifest: %s", err.Error())
+ }
+
+ return cnt, nil
+}