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
+}