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 <ccoll...@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
+}

Reply via email to