You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by GitBox <gi...@apache.org> on 2019/01/04 18:21:03 UTC

[GitHub] ccollins476ad closed pull request #254: 2.0 mfgimage format

ccollins476ad closed pull request #254: 2.0 mfgimage format
URL: https://github.com/apache/mynewt-newt/pull/254
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/artifact/flash/flash.go b/artifact/flash/flash.go
new file mode 100644
index 00000000..6b85814e
--- /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 `json:"name"`
+	Id     int    `json:"id"`
+	Device int    `json:"device"`
+	Offset int    `json:"offset"`
+	Size   int    `json:"size"`
+}
+
+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 00000000..76e701d0
--- /dev/null
+++ b/artifact/image/create.go
@@ -0,0 +1,395 @@
+/**
+ * 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 ImageSigKey, hash []byte) ([]byte, error) {
+	opts := rsa.PSSOptions{
+		SaltLength: rsa.PSSSaltLengthEqualsHash,
+	}
+	signature, err := rsa.SignPSS(
+		rand.Reader, key.Rsa, crypto.SHA256, hash, &opts)
+	if err != nil {
+		return nil, util.FmtNewtError("Failed to compute signature: %s", err)
+	}
+
+	return signature, nil
+}
+
+func GenerateSigEc(key ImageSigKey, hash []byte) ([]byte, error) {
+	r, s, err := ecdsa.Sign(rand.Reader, key.Ec, 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)
+	}
+
+	sigLen := key.sigLen()
+	if len(signature) > int(sigLen) {
+		return nil, util.FmtNewtError("Something is really wrong\n")
+	}
+
+	pad := make([]byte, int(sigLen)-len(signature))
+	signature = append(signature, pad...)
+
+	return signature, nil
+}
+
+func GenerateSig(key ImageSigKey, hash []byte) ([]byte, error) {
+	key.assertValid()
+
+	if key.Rsa != nil {
+		return GenerateSigRsa(key, hash)
+	} else {
+		return GenerateSigEc(key, hash)
+	}
+}
+
+func BuildKeyHashTlv(keyBytes []byte) ImageTlv {
+	data := RawKeyHash(keyBytes)
+	return ImageTlv{
+		Header: ImageTlvHdr{
+			Type: IMAGE_TLV_KEYHASH,
+			Pad:  0,
+			Len:  uint16(len(data)),
+		},
+		Data: data,
+	}
+}
+
+func BuildSigTlvs(keys []ImageSigKey, hash []byte) ([]ImageTlv, error) {
+	var tlvs []ImageTlv
+
+	for _, key := range keys {
+		key.assertValid()
+
+		// Key hash TLV.
+		pubKey, err := key.PubBytes()
+		if err != nil {
+			return nil, err
+		}
+		tlv := BuildKeyHashTlv(pubKey)
+		tlvs = append(tlvs, tlv)
+
+		// Signature TLV.
+		sig, err := GenerateSig(key, hash)
+		if err != nil {
+			return nil, err
+		}
+		tlv = ImageTlv{
+			Header: ImageTlvHdr{
+				Type: key.sigTlvType(),
+				Len:  uint16(len(sig)),
+			},
+			Data: sig,
+		}
+		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, err := GeneratePlainSecret()
+		if err != nil {
+			return Image{}, err
+		}
+
+		pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename)
+		if err != nil {
+			return Image{}, util.FmtNewtError(
+				"Error reading pubkey file: %s", err.Error())
+		}
+
+		cipherSecret, err := GenerateCipherSecret(pubKeBytes, 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, pad []byte,
+	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
+	}
+
+	if err := add(pad); 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 EncryptImageBody(imageBody []byte, secret []byte) ([]byte, error) {
+	block, err := aes.NewCipher(secret)
+	if err != nil {
+		return nil, util.NewNewtError("Failed to create block cipher")
+	}
+	nonce := make([]byte, 16)
+	stream := cipher.NewCTR(block, nonce)
+
+	dataBuf := make([]byte, 16)
+	encBuf := make([]byte, 16)
+	r := bytes.NewReader(imageBody)
+	w := bytes.Buffer{}
+	for {
+		cnt, err := r.Read(dataBuf)
+		if err != nil && err != io.EOF {
+			return nil, util.FmtNewtError(
+				"Failed to read from image body: %s", err.Error())
+		}
+		if cnt == 0 {
+			break
+		}
+
+		stream.XORKeyStream(encBuf, dataBuf[0:cnt])
+		if _, err = w.Write(encBuf[0:cnt]); err != nil {
+			return nil, util.FmtNewtError(
+				"Failed to write to image body: %s", err.Error())
+		}
+	}
+
+	return w.Bytes(), nil
+}
+
+func (ic *ImageCreator) Create() (Image, error) {
+	img := Image{}
+
+	// First the header
+	img.Header = 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 {
+		img.Header.Flags |= IMAGE_F_NON_BOOTABLE
+	}
+
+	if ic.CipherSecret != nil {
+		img.Header.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 img, util.FmtNewtError("Image header must be at "+
+				"least %d bytes", IMAGE_HEADER_SIZE)
+		}
+
+		img.Header.HdrSz = uint16(ic.HeaderSize)
+		img.Pad = make([]byte, extra)
+	}
+
+	hashBytes, err := calcHash(ic.InitialHash, img.Header, img.Pad, ic.Body)
+	if err != nil {
+		return img, err
+	}
+
+	// Followed by data.
+	if ic.CipherSecret != nil {
+		encBody, err := EncryptImageBody(ic.Body, ic.PlainSecret)
+		if err != nil {
+			return img, err
+		}
+		img.Body = append(img.Body, encBody...)
+	} else {
+		img.Body = append(img.Body, ic.Body...)
+	}
+
+	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,
+	}
+	img.Tlvs = append(img.Tlvs, tlv)
+
+	tlvs, err := BuildSigTlvs(ic.SigKeys, hashBytes)
+	if err != nil {
+		return img, err
+	}
+	img.Tlvs = append(img.Tlvs, tlvs...)
+
+	if ic.CipherSecret != nil {
+		tlv, err := GenerateEncTlv(ic.CipherSecret)
+		if err != nil {
+			return img, err
+		}
+		img.Tlvs = append(img.Tlvs, tlv)
+	}
+
+	return img, nil
+}
diff --git a/newt/image/encrypted.go b/artifact/image/encrypted.go
similarity index 100%
rename from newt/image/encrypted.go
rename to artifact/image/encrypted.go
diff --git a/artifact/image/image.go b/artifact/image/image.go
new file mode 100644
index 00000000..6bd00507
--- /dev/null
+++ b/artifact/image/image.go
@@ -0,0 +1,564 @@
+/**
+ * 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
+	Pad    []byte
+	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 ImageTlvTypeIsSig(tlvType uint8) bool {
+	return tlvType == IMAGE_TLV_RSA2048 ||
+		tlvType == IMAGE_TLV_ECDSA224 ||
+		tlvType == IMAGE_TLV_ECDSA256
+}
+
+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,
+		"hdr_sz":  h.HdrSz,
+		"img_sz":  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,
+		"tlv_tot_len": t.TlvTotLen,
+		"_offset":     offset,
+	}
+}
+
+func (t *ImageTlv) Map(offset int) map[string]interface{} {
+	return map[string]interface{}{
+		"type":     t.Header.Type,
+		"len":      t.Header.Len,
+		"data":     hex.EncodeToString(t.Data),
+		"_typestr": ImageTlvTypeName(t.Header.Type),
+		"_offset":  offset,
+	}
+}
+
+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) []ImageTlv {
+	rmed := []ImageTlv{}
+
+	for idx := 0; idx < len(i.Tlvs); {
+		tlv := i.Tlvs[idx]
+		if pred(tlv) {
+			rmed = append(rmed, tlv)
+			i.Tlvs = append(i.Tlvs[:idx], i.Tlvs[idx+1:]...)
+		} else {
+			idx++
+		}
+	}
+
+	return rmed
+}
+
+func (i *Image) RemoveTlvsWithType(tlvType uint8) []ImageTlv {
+	return i.RemoveTlvsIf(func(tlv ImageTlv) bool {
+		return tlv.Header.Type == tlvType
+	})
+}
+
+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
+
+	err = binary.Write(w, binary.LittleEndian, i.Pad)
+	if err != nil {
+		return offs, util.ChildNewtError(err)
+	}
+	offset += len(i.Pad)
+
+	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",
+			uint32(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 00000000..a343e2dd
--- /dev/null
+++ b/artifact/image/key.go
@@ -0,0 +1,341 @@
+/**
+ * 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/cipher"
+	"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) PubBytes() ([]uint8, error) {
+	key.assertValid()
+
+	var pubkey []byte
+
+	if key.Rsa != nil {
+		pubkey, _ = asn1.Marshal(key.Rsa.PublicKey)
+	} else {
+		switch key.Ec.Curve.Params().Name {
+		case "P-224":
+			fallthrough
+		case "P-256":
+			pubkey, _ = x509.MarshalPKIXPublicKey(&key.Ec.PublicKey)
+		default:
+			return nil, util.NewNewtError("Unsupported ECC curve")
+		}
+	}
+
+	return pubkey, nil
+}
+
+func RawKeyHash(pubKeyBytes []byte) []byte {
+	sum := sha256.Sum256(pubKeyBytes)
+	return sum[:4]
+}
+
+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 ParsePubKePem(keyBytes []byte) (*rsa.PublicKey, 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())
+	}
+
+	return pubk, nil
+}
+
+func ParsePrivKeDer(keyBytes []byte) (*rsa.PrivateKey, error) {
+	privKey, err := x509.ParsePKCS1PrivateKey(keyBytes)
+	if err != nil {
+		return nil, util.FmtNewtError(
+			"Error parsing private key file: %s", err.Error())
+	}
+
+	return privKey, nil
+}
+
+func EncryptSecretRsa(pubk *rsa.PublicKey, plainSecret []byte) ([]byte, 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 DecryptSecretRsa(privk *rsa.PrivateKey,
+	cipherSecret []byte) ([]byte, error) {
+
+	rng := rand.Reader
+	plainSecret, err := rsa.DecryptOAEP(
+		sha256.New(), rng, privk, cipherSecret, nil)
+	if err != nil {
+		return nil, util.FmtNewtError(
+			"Error from encryption: %s\n", err.Error())
+	}
+
+	return plainSecret, nil
+}
+
+func ParseKeBase64(keyBytes []byte) (cipher.Block, 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())
+	}
+
+	return cipher, nil
+}
+
+func encryptSecretAes(c cipher.Block, plainSecret []byte) ([]byte, error) {
+	cipherSecret, err := keywrap.Wrap(c, plainSecret)
+	if err != nil {
+		return nil, util.FmtNewtError("Error key-wrapping: %s", err.Error())
+	}
+
+	return cipherSecret, nil
+}
+
+func GeneratePlainSecret() ([]byte, error) {
+	plainSecret := make([]byte, 16)
+	if _, err := rand.Read(plainSecret); err != nil {
+		return nil, util.FmtNewtError(
+			"Random generation error: %s\n", err)
+	}
+
+	return plainSecret, nil
+}
+
+func GenerateCipherSecret(pubKeBytes []byte,
+	plainSecret []byte) ([]byte, error) {
+
+	// Try reading as PEM (asymetric key).
+	rsaPubKe, err := ParsePubKePem(pubKeBytes)
+	if err != nil {
+		return nil, err
+	}
+	if rsaPubKe != nil {
+		return EncryptSecretRsa(rsaPubKe, plainSecret)
+	}
+
+	// Not PEM; assume this is a base64 encoded symetric key
+	aesPubKe, err := ParseKeBase64(pubKeBytes)
+	if err != nil {
+		return nil, err
+	}
+	if aesPubKe != nil {
+		return encryptSecretAes(aesPubKe, plainSecret)
+	}
+
+	return nil, util.FmtNewtError("Invalid image-crypt key")
+}
diff --git a/newt/image/keys_test.go b/artifact/image/keys_test.go
similarity index 99%
rename from newt/image/keys_test.go
rename to artifact/image/keys_test.go
index 86124031..577d9cdd 100644
--- a/newt/image/keys_test.go
+++ b/artifact/image/keys_test.go
@@ -24,8 +24,6 @@ import (
 	"os"
 	"path"
 	"testing"
-
-	"mynewt.apache.org/newt/newt/image"
 )
 
 func TestRSA(t *testing.T) {
diff --git a/artifact/image/v1.go b/artifact/image/v1.go
new file mode 100644
index 00000000..0dc10a5a
--- /dev/null
+++ b/artifact/image/v1.go
@@ -0,0 +1,491 @@
+/**
+ * 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, 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, err := GeneratePlainSecret()
+		if err != nil {
+			return ImageV1{}, err
+		}
+
+		pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename)
+		if err != nil {
+			return ImageV1{}, util.FmtNewtError(
+				"Error reading pubkey file: %s", err.Error())
+		}
+		cipherSecret, err := GenerateCipherSecret(pubKeBytes, 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 00000000..3a3376ae
--- /dev/null
+++ b/artifact/manifest/manifest.go
@@ -0,0 +1,94 @@
+package manifest
+
+import (
+	"encoding/json"
+	"io"
+	"io/ioutil"
+
+	"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"`
+	Syscfg     map[string]string `json:"syscfg"`
+
+	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
+}
diff --git a/artifact/manifest/mfg_manifest.go b/artifact/manifest/mfg_manifest.go
new file mode 100644
index 00000000..bb27adc8
--- /dev/null
+++ b/artifact/manifest/mfg_manifest.go
@@ -0,0 +1,74 @@
+package manifest
+
+import (
+	"encoding/json"
+	"io/ioutil"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/util"
+)
+
+type MfgManifestTarget struct {
+	Name         string `json:"name"`
+	Offset       int    `json:"offset"`
+	BinPath      string `json:"bin_path,omitempty"`
+	ImagePath    string `json:"image_path,omitempty"`
+	ManifestPath string `json:"manifest_path"`
+}
+
+type MfgManifestMetaMmr struct {
+	Area      string `json:"area"`
+	Device    int    `json:"_device"`
+	EndOffset int    `json:"_end_offset"`
+}
+
+type MfgManifestMeta struct {
+	EndOffset int                  `json:"end_offset"`
+	Size      int                  `json:"size"`
+	Hash      bool                 `json:"hash_present"`
+	FlashMap  bool                 `json:"flash_map_present"`
+	Mmrs      []MfgManifestMetaMmr `json:"mmrs,omitempty"`
+	// XXX: refhash
+}
+
+type MfgManifest struct {
+	Name       string            `json:"name"`
+	BuildTime  string            `json:"build_time"`
+	Format     int               `json:"format"`
+	MfgHash    string            `json:"mfg_hash"`
+	Version    string            `json:"version"`
+	Device     int               `json:"device"`
+	BinPath    string            `json:"bin_path"`
+	Bsp        string            `json:"bsp"`
+	FlashAreas []flash.FlashArea `json:"flash_map"`
+
+	Targets []MfgManifestTarget `json:"targets"`
+	Meta    *MfgManifestMeta    `json:"meta,omitempty"`
+}
+
+func ReadMfgManifest(path string) (MfgManifest, error) {
+	m := MfgManifest{}
+
+	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 mfg manifest with path \"%s\": %s",
+			path, err.Error())
+	}
+
+	return m, nil
+}
+
+func (m *MfgManifest) MarshalJson() ([]byte, error) {
+	buffer, err := json.MarshalIndent(m, "", "  ")
+	if err != nil {
+		return nil, util.FmtNewtError(
+			"Cannot encode mfg manifest: %s", err.Error())
+	}
+
+	return buffer, nil
+}
diff --git a/artifact/mfg/map_meta.go b/artifact/mfg/map_meta.go
new file mode 100644
index 00000000..b16c37ef
--- /dev/null
+++ b/artifact/mfg/map_meta.go
@@ -0,0 +1,153 @@
+/**
+ * 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 mfg
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/hex"
+	"encoding/json"
+
+	"mynewt.apache.org/newt/util"
+)
+
+func (t *MetaTlv) bodyMap() (map[string]interface{}, error) {
+	r := bytes.NewReader(t.Data)
+
+	readBody := func(dst interface{}) error {
+		if err := binary.Read(r, binary.LittleEndian, dst); err != nil {
+			return util.FmtNewtError(
+				"Error parsing TLV data: %s", err.Error())
+		}
+		return nil
+	}
+
+	switch t.Header.Type {
+	case META_TLV_TYPE_HASH:
+		var body MetaTlvBodyHash
+		if err := readBody(&body); err != nil {
+			return nil, err
+		}
+		return body.Map(), nil
+
+	case META_TLV_TYPE_FLASH_AREA:
+		var body MetaTlvBodyFlashArea
+		if err := readBody(&body); err != nil {
+			return nil, err
+		}
+		return body.Map(), nil
+
+	case META_TLV_TYPE_MMR_REF:
+		var body MetaTlvBodyMmrRef
+		if err := readBody(&body); err != nil {
+			return nil, err
+		}
+		return body.Map(), nil
+
+	default:
+		return nil, util.FmtNewtError(
+			"Unknown meta TLV type: %d", t.Header.Type)
+	}
+}
+
+func (b *MetaTlvBodyFlashArea) Map() map[string]interface{} {
+	return map[string]interface{}{
+		"area":   b.Area,
+		"device": b.Device,
+		"offset": b.Offset,
+		"size":   b.Size,
+	}
+}
+
+func (b *MetaTlvBodyHash) Map() map[string]interface{} {
+	return map[string]interface{}{
+		"hash": hex.EncodeToString(b.Hash[:]),
+	}
+}
+
+func (b *MetaTlvBodyMmrRef) Map() map[string]interface{} {
+	return map[string]interface{}{
+		"area": b.Area,
+	}
+}
+
+func (t *MetaTlv) Map(offset int) map[string]interface{} {
+	hmap := map[string]interface{}{
+		"_type_name": MetaTlvTypeName(t.Header.Type),
+		"type":       t.Header.Type,
+		"size":       t.Header.Size,
+	}
+
+	var body interface{}
+
+	bmap, err := t.bodyMap()
+	if err != nil {
+		body = hex.EncodeToString(t.Data)
+	} else {
+		body = bmap
+	}
+
+	return map[string]interface{}{
+		"_offset": offset,
+		"header":  hmap,
+		"data":    body,
+	}
+}
+
+func (f *MetaFooter) Map(offset int) map[string]interface{} {
+	return map[string]interface{}{
+		"_offset": offset,
+		"size":    f.Size,
+		"magic":   f.Magic,
+		"version": f.Version,
+	}
+}
+
+func (m *Meta) Map(endOffset int) map[string]interface{} {
+	offsets := m.Offsets()
+	startOffset := endOffset - int(m.Footer.Size)
+
+	tlvs := []map[string]interface{}{}
+	for i, t := range m.Tlvs {
+		tlv := t.Map(startOffset + offsets.Tlvs[i])
+		tlvs = append(tlvs, tlv)
+	}
+
+	ftr := m.Footer.Map(startOffset + offsets.Footer)
+
+	return map[string]interface{}{
+		"_offset":     startOffset,
+		"_end_offset": endOffset,
+		"_size":       m.Footer.Size,
+		"tlvs":        tlvs,
+		"footer":      ftr,
+	}
+}
+
+func (m *Meta) Json(offset int) (string, error) {
+	mmap := m.Map(offset)
+
+	bin, err := json.MarshalIndent(mmap, "", "    ")
+	if err != nil {
+		return "", util.ChildNewtError(err)
+	}
+
+	return string(bin), nil
+}
diff --git a/artifact/mfg/meta.go b/artifact/mfg/meta.go
new file mode 100644
index 00000000..4521a890
--- /dev/null
+++ b/artifact/mfg/meta.go
@@ -0,0 +1,352 @@
+/**
+ * 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 mfg
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+	"io/ioutil"
+
+	"mynewt.apache.org/newt/util"
+)
+
+// The "manufacturing meta region" is located at the end of the boot loader
+// flash area.  This region has the following structure.
+//
+//  0                   1                   2                   3
+//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |Version (0x01) |                  0xff padding                 |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |   TLV type    |   TLV size    | TLV data ("TLV size" bytes)   ~
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               ~
+// ~                                                               ~
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |   TLV type    |   TLV size    | TLV data ("TLV size" bytes)   ~
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               ~
+// ~                                                               ~
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |   Region size                 |         0xff padding          |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                       Magic (0x3bb2a269)                      |
+// +-+-+-+-+-+--+-+-+-+-end of boot loader area+-+-+-+-+-+-+-+-+-+-+
+//
+// The number of TLVs is variable; two are shown above for illustrative
+// purposes.
+//
+// Fields:
+// <Header>
+// 1. Version: Manufacturing meta version number; always 0x01.
+//
+// <TLVs>
+// 2. TLV type: Indicates the type of data to follow.
+// 3. TLV size: The number of bytes of data to follow.
+// 4. TLV data: TLV-size bytes of data.
+//
+// <Footer>
+// 5. Region size: The size, in bytes, of the entire manufacturing meta region;
+//    includes header, TLVs, and footer.
+// 6. Magic: indicates the presence of the manufacturing meta region.
+
+const META_MAGIC = 0x3bb2a269
+const META_VERSION = 2
+const META_TLV_TYPE_HASH = 0x01
+const META_TLV_TYPE_FLASH_AREA = 0x02
+const META_TLV_TYPE_MMR_REF = 0x04
+
+const META_HASH_SZ = 32
+const META_FOOTER_SZ = 8
+const META_TLV_HEADER_SZ = 2
+const META_TLV_HASH_SZ = META_HASH_SZ
+const META_TLV_FLASH_AREA_SZ = 10
+const META_TLV_MMR_REF_SZ = 1
+
+type MetaFooter struct {
+	Size    uint16 // Includes header, TLVs, and footer.
+	Version uint8
+	Pad8    uint8  // 0xff
+	Magic   uint32 // META_MAGIC
+}
+
+type MetaTlvHeader struct {
+	Type uint8 // Indicates the type of data to follow.
+	Size uint8 // The number of bytes of data to follow.
+}
+
+type MetaTlvBodyFlashArea struct {
+	Area   uint8  // Unique value identifying this flash area.
+	Device uint8  // Indicates host flash device (aka section number).
+	Offset uint32 // The byte offset within the flash device.
+	Size   uint32 // Size, in bytes, of entire flash area.
+}
+
+type MetaTlvBodyHash struct {
+	Hash [META_HASH_SZ]byte
+}
+
+type MetaTlvBodyMmrRef struct {
+	Area uint8
+}
+
+type MetaTlv struct {
+	Header MetaTlvHeader
+	Data   []byte
+}
+
+type Meta struct {
+	Tlvs   []MetaTlv
+	Footer MetaFooter
+}
+
+type MetaOffsets struct {
+	Tlvs      []int
+	Footer    int
+	TotalSize int
+}
+
+var metaTlvTypeNameMap = map[uint8]string{
+	META_TLV_TYPE_HASH:       "hash",
+	META_TLV_TYPE_FLASH_AREA: "flash_area",
+	META_TLV_TYPE_MMR_REF:    "mmr_ref",
+}
+
+func MetaTlvTypeName(typ uint8) string {
+	name := metaTlvTypeNameMap[typ]
+	if name == "" {
+		name = "???"
+	}
+	return name
+}
+
+func writeElem(elem interface{}, w io.Writer) error {
+	/* XXX: Assume target platform uses little endian. */
+	if err := binary.Write(w, binary.LittleEndian, elem); err != nil {
+		return util.ChildNewtError(err)
+	}
+	return nil
+}
+
+func (tlv *MetaTlv) Write(w io.Writer) (int, error) {
+	sz := 0
+
+	if err := writeElem(tlv.Header, w); err != nil {
+		return sz, err
+	}
+	sz += META_TLV_HEADER_SZ
+
+	if err := writeElem(tlv.Data, w); err != nil {
+		return sz, err
+	}
+	sz += len(tlv.Data)
+
+	return sz, nil
+}
+
+func (meta *Meta) WritePlusOffsets(w io.Writer) (MetaOffsets, error) {
+	mo := MetaOffsets{}
+	sz := 0
+
+	for _, tlv := range meta.Tlvs {
+		tlvSz, err := tlv.Write(w)
+		if err != nil {
+			return mo, err
+		}
+		mo.Tlvs = append(mo.Tlvs, sz)
+		sz += tlvSz
+	}
+
+	if err := writeElem(meta.Footer, w); err != nil {
+		return mo, err
+	}
+	mo.Footer = sz
+	sz += META_FOOTER_SZ
+
+	mo.TotalSize = sz
+
+	return mo, nil
+}
+
+func (meta *Meta) Offsets() MetaOffsets {
+	mo, _ := meta.WritePlusOffsets(ioutil.Discard)
+	return mo
+}
+
+func (meta *Meta) Write(w io.Writer) (int, error) {
+	mo, err := meta.WritePlusOffsets(w)
+	if err != nil {
+		return 0, err
+	}
+
+	return mo.TotalSize, nil
+}
+
+func (meta *Meta) Size() int {
+	return meta.Offsets().TotalSize
+}
+
+func (meta *Meta) Bytes() ([]byte, error) {
+	b := &bytes.Buffer{}
+
+	_, err := meta.Write(b)
+	if err != nil {
+		return nil, err
+	}
+
+	return b.Bytes(), nil
+}
+
+func (meta *Meta) FindTlvIndices(typ uint8) []int {
+	indices := []int{}
+
+	for i, tlv := range meta.Tlvs {
+		if tlv.Header.Type == typ {
+			indices = append(indices, i)
+		}
+	}
+
+	return indices
+}
+
+func (meta *Meta) FindTlvs(typ uint8) []*MetaTlv {
+	indices := meta.FindTlvIndices(typ)
+
+	tlvs := []*MetaTlv{}
+	for _, index := range indices {
+		tlvs = append(tlvs, &meta.Tlvs[index])
+	}
+
+	return tlvs
+}
+
+func (meta *Meta) FindFirstTlv(typ uint8) *MetaTlv {
+	indices := meta.FindTlvIndices(typ)
+	if len(indices) == 0 {
+		return nil
+	}
+
+	return &meta.Tlvs[indices[0]]
+}
+
+func (meta *Meta) HashOffset() int {
+	mo := meta.Offsets()
+	indices := meta.FindTlvIndices(META_TLV_TYPE_HASH)
+	if len(indices) == 0 {
+		return -1
+	}
+
+	return META_TLV_HEADER_SZ + mo.Tlvs[indices[0]]
+}
+
+func (meta *Meta) ClearHash() {
+	tlv := meta.FindFirstTlv(META_TLV_TYPE_HASH)
+	if tlv != nil {
+		tlv.Data = make([]byte, META_HASH_SZ)
+	}
+}
+
+func (meta *Meta) Hash() []byte {
+	tlv := meta.FindFirstTlv(META_TLV_TYPE_HASH)
+	if tlv == nil {
+		return nil
+	}
+	return tlv.Data
+}
+
+func parseMetaTlv(bin []byte) (MetaTlv, int, error) {
+	r := bytes.NewReader(bin)
+
+	tlv := MetaTlv{}
+	if err := binary.Read(r, binary.LittleEndian, &tlv.Header); err != nil {
+		return tlv, 0, util.FmtNewtError(
+			"Error reading TLV header: %s", err.Error())
+	}
+
+	data := make([]byte, tlv.Header.Size)
+	sz, err := r.Read(data)
+	if err != nil {
+		return tlv, 0, util.FmtNewtError(
+			"Error reading %d bytes of TLV data: %s",
+			tlv.Header.Size, err.Error())
+	}
+	if sz != len(data) {
+		return tlv, 0, util.FmtNewtError(
+			"Error reading %d bytes of TLV data: incomplete read",
+			tlv.Header.Size)
+	}
+	tlv.Data = data
+
+	return tlv, META_TLV_HEADER_SZ + int(tlv.Header.Size), nil
+}
+
+func parseMetaFooter(bin []byte) (MetaFooter, int, error) {
+	r := bytes.NewReader(bin)
+
+	var ftr MetaFooter
+	if err := binary.Read(r, binary.LittleEndian, &ftr); err != nil {
+		return ftr, 0, util.FmtNewtError(
+			"Error reading meta footer: %s", err.Error())
+	}
+
+	if ftr.Magic != META_MAGIC {
+		return ftr, 0, util.FmtNewtError(
+			"Meta footer contains invalid magic; exp:0x%08x, got:0x%08x",
+			META_MAGIC, ftr.Magic)
+	}
+
+	return ftr, META_FOOTER_SZ, nil
+}
+
+func ParseMeta(bin []byte) (Meta, int, error) {
+	if len(bin) < META_FOOTER_SZ {
+		return Meta{}, 0, util.FmtNewtError(
+			"Binary too small to accommodate meta footer; "+
+				"bin-size=%d ftr-size=%d", len(bin), META_FOOTER_SZ)
+	}
+
+	ftr, _, err := parseMetaFooter(bin[len(bin)-META_FOOTER_SZ:])
+	if err != nil {
+		return Meta{}, 0, err
+	}
+
+	if int(ftr.Size) > len(bin) {
+		return Meta{}, 0, util.FmtNewtError(
+			"Binary too small to accommodate meta region; "+
+				"bin-size=%d meta-size=%d", len(bin), ftr.Size)
+	}
+
+	ftrOff := len(bin) - META_FOOTER_SZ
+	off := len(bin) - int(ftr.Size)
+
+	tlvs := []MetaTlv{}
+	for off < ftrOff {
+		tlv, sz, err := parseMetaTlv(bin[off:])
+		if err != nil {
+			return Meta{}, 0, err
+		}
+		tlvs = append(tlvs, tlv)
+		off += sz
+	}
+
+	return Meta{
+		Tlvs:   tlvs,
+		Footer: ftr,
+	}, off, nil
+}
diff --git a/artifact/mfg/mfg.go b/artifact/mfg/mfg.go
new file mode 100644
index 00000000..3e295232
--- /dev/null
+++ b/artifact/mfg/mfg.go
@@ -0,0 +1,131 @@
+package mfg
+
+import (
+	"crypto/sha256"
+
+	"mynewt.apache.org/newt/util"
+)
+
+const MFG_IMG_FILENAME = "mfgimg.bin"
+const MANIFEST_FILENAME = "manifest.json"
+
+type Mfg struct {
+	Bin  []byte
+	Meta *Meta
+
+	// Unused if Meta==nil.
+	MetaOff int
+}
+
+func Parse(data []byte, metaEndOff int, eraseVal byte) (Mfg, error) {
+	m := Mfg{
+		Bin: data,
+	}
+
+	if metaEndOff >= 0 {
+		meta, _, err := ParseMeta(data[:metaEndOff])
+		if err != nil {
+			return m, err
+		}
+		m.Meta = &meta
+		m.MetaOff = metaEndOff - int(meta.Footer.Size)
+	}
+
+	return m, nil
+}
+
+func StripPadding(b []byte, eraseVal byte) []byte {
+	var pad int
+	for pad = 0; pad < len(b); pad++ {
+		off := len(b) - pad - 1
+		if b[off] != eraseVal {
+			break
+		}
+	}
+
+	return b[:len(b)-pad]
+}
+
+func AddPadding(b []byte, eraseVal byte, padLen int) []byte {
+	for i := 0; i < padLen; i++ {
+		b = append(b, eraseVal)
+	}
+	return b
+}
+
+// Calculates the SHA256 hash, using the full manufacturing image as input.
+// Hash-calculation algorithm is as follows:
+// 1. Zero out the 32 bytes that will contain the hash.
+// 2. Apply SHA256 to the result.
+//
+// This function assumes that the 32 bytes of hash data have already been
+// zeroed.
+func CalcHash(bin []byte) []byte {
+	hash := sha256.Sum256(bin)
+	return hash[:]
+}
+
+func (m *Mfg) RecalcHash(eraseVal byte) error {
+	if m.Meta == nil || m.Meta.Hash() == nil {
+		return nil
+	}
+
+	// First, write with zeroed hash.
+	m.Meta.ClearHash()
+	bin, err := m.Bytes(eraseVal)
+	if err != nil {
+		return err
+	}
+
+	// Calculate hash and fill TLV.
+	tlv := m.Meta.FindFirstTlv(META_TLV_TYPE_HASH)
+	if tlv != nil {
+		hashData := CalcHash(bin)
+		copy(tlv.Data, hashData)
+
+		hashOff := m.MetaOff + m.Meta.HashOffset()
+		if hashOff+META_HASH_SZ > len(bin) {
+			return util.FmtNewtError(
+				"unexpected error: hash extends beyond end " +
+					"of manufacturing image")
+		}
+	}
+
+	return nil
+}
+
+func (m *Mfg) Hash() ([]byte, error) {
+	var hashBytes []byte
+	if m.Meta != nil {
+		hashBytes = m.Meta.Hash()
+	}
+	if hashBytes == nil {
+		// No hash TLV; calculate hash manually.
+		bin, err := m.Bytes(0xff)
+		if err != nil {
+			return nil, err
+		}
+		hashBytes = CalcHash(bin)
+	}
+
+	return hashBytes, nil
+}
+
+func (m *Mfg) Bytes(eraseVal byte) ([]byte, error) {
+	binCopy := make([]byte, len(m.Bin))
+	copy(binCopy, m.Bin)
+
+	metaBytes, err := m.Meta.Bytes()
+	if err != nil {
+		return nil, err
+	}
+
+	padLen := m.MetaOff + len(metaBytes) - len(binCopy)
+	if padLen > 0 {
+		binCopy = AddPadding(binCopy, eraseVal, padLen)
+	}
+
+	copy(binCopy[m.MetaOff:m.MetaOff+len(metaBytes)], metaBytes)
+
+	return binCopy, nil
+}
diff --git a/newt/mfg/read.go b/artifact/misc/misc.go
similarity index 63%
rename from newt/mfg/read.go
rename to artifact/misc/misc.go
index 04520dbb..2f685e17 100644
--- a/newt/mfg/read.go
+++ b/artifact/misc/misc.go
@@ -17,24 +17,12 @@
  * under the License.
  */
 
-package mfg
+package misc
 
 import (
-	"strings"
-
-	"mynewt.apache.org/newt/newt/builder"
+	"fmt"
 )
 
-// @return						mfg-image-path, error
-func (mi *MfgImage) Upload() (string, error) {
-	// For now, we always upload section 0 only.
-	section0Path := MfgSectionBinPath(mi.basePkg.Name(), 0)
-	baseName := strings.TrimSuffix(section0Path, ".bin")
-
-	envSettings := map[string]string{"MFG_IMAGE": "1"}
-	if err := builder.Load(baseName, mi.bsp, envSettings); err != nil {
-		return "", err
-	}
-
-	return section0Path, nil
+func HashString(hash []byte) string {
+	return fmt.Sprintf("%x", hash)
 }
diff --git a/larva/cli/image_cmds.go b/larva/cli/image_cmds.go
new file mode 100644
index 00000000..846c1377
--- /dev/null
+++ b/larva/cli/image_cmds.go
@@ -0,0 +1,596 @@
+/**
+ * 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 cli
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"sort"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/larva/lvimg"
+	"mynewt.apache.org/newt/util"
+)
+
+func tlvStr(tlv image.ImageTlv) string {
+	return fmt.Sprintf("%s,0x%02x",
+		image.ImageTlvTypeName(tlv.Header.Type),
+		tlv.Header.Type)
+}
+
+func readImage(filename string) (image.Image, error) {
+	img, err := image.ReadImage(filename)
+	if err != nil {
+		return img, err
+	}
+
+	log.Debugf("Successfully read image %s", filename)
+	return img, nil
+}
+
+func writeImage(img image.Image, filename string) error {
+	if err := lvimg.VerifyImage(img); err != nil {
+		return err
+	}
+
+	if err := img.WriteToFile(filename); err != nil {
+		return err
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT, "Wrote image %s\n", filename)
+	return nil
+}
+
+func parseTlvArgs(typeArg string, filenameArg string) (image.ImageTlv, error) {
+	tlvType, err := util.AtoiNoOct(typeArg)
+	if err != nil || tlvType < 0 {
+		return image.ImageTlv{}, util.FmtNewtError(
+			"Invalid TLV type integer: %s", typeArg)
+	}
+
+	data, err := ioutil.ReadFile(filenameArg)
+	if err != nil {
+		return image.ImageTlv{}, util.FmtNewtError(
+			"Error reading TLV data file: %s", err.Error())
+	}
+
+	return image.ImageTlv{
+		Header: image.ImageTlvHdr{
+			Type: uint8(tlvType),
+			Pad:  0,
+			Len:  uint16(len(data)),
+		},
+		Data: data,
+	}, nil
+}
+
+func runShowCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	img, err := readImage(args[0])
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	s, err := img.Json()
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+	fmt.Printf("%s\n", s)
+}
+
+func runBriefCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	img, err := readImage(args[0])
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	offsets, err := img.Offsets()
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	fmt.Printf("%8d| Header\n", offsets.Header)
+	fmt.Printf("%8d| Body\n", offsets.Body)
+	fmt.Printf("%8d| Trailer\n", offsets.Trailer)
+	for i, tlv := range img.Tlvs {
+		fmt.Printf("%8d| TLV%d: type=%s(%d)\n",
+			offsets.Tlvs[i], i, image.ImageTlvTypeName(tlv.Header.Type),
+			tlv.Header.Type)
+	}
+	fmt.Printf("Total=%d\n", offsets.TotalSize)
+}
+
+func runSignCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	inFilename := args[0]
+	outFilename, err := CalcOutFilename(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	keys, err := image.ReadKeys(args[1:])
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	hash, err := img.Hash()
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read hash from specified image: %s", err.Error()))
+	}
+
+	tlvs, err := image.BuildSigTlvs(keys, hash)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	img.Tlvs = append(img.Tlvs, tlvs...)
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runAddTlvsCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 3 {
+		LarvaUsage(cmd, nil)
+	}
+
+	inFilename := args[0]
+	outFilename, err := CalcOutFilename(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	tlvArgs := args[1:]
+	if len(tlvArgs)%2 != 0 {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Invalid argument count; each TLV requires two arguments"))
+	}
+
+	tlvs := []image.ImageTlv{}
+	for i := 0; i < len(tlvArgs); i += 2 {
+		tlv, err := parseTlvArgs(tlvArgs[i], tlvArgs[i+1])
+		if err != nil {
+			LarvaUsage(cmd, err)
+		}
+
+		tlvs = append(tlvs, tlv)
+	}
+
+	img.Tlvs = append(img.Tlvs, tlvs...)
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runRmtlvsCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	inFilename := args[0]
+	outFilename, err := CalcOutFilename(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	tlvIndices := []int{}
+	idxMap := map[int]struct{}{}
+	for _, arg := range args[1:] {
+		idx, err := util.AtoiNoOct(arg)
+		if err != nil {
+			LarvaUsage(cmd, util.FmtNewtError("Invalid TLV index: %s", arg))
+		}
+
+		if idx < 0 || idx >= len(img.Tlvs) {
+			LarvaUsage(nil, util.FmtNewtError(
+				"TLV index %s out of range; "+
+					"must be in range [0, %d] for this image",
+				arg, len(img.Tlvs)-1))
+		}
+
+		if _, ok := idxMap[idx]; ok {
+			LarvaUsage(nil, util.FmtNewtError(
+				"TLV index %d specified more than once", idx))
+		}
+		idxMap[idx] = struct{}{}
+
+		tlvIndices = append(tlvIndices, idx)
+	}
+
+	// Remove TLVs in reverse order to preserve index mapping.
+	sort.Sort(sort.Reverse(sort.IntSlice(tlvIndices)))
+	for _, idx := range tlvIndices {
+		tlv := img.Tlvs[idx]
+		util.StatusMessage(util.VERBOSITY_DEFAULT,
+			"Removing TLV%d: %s\n", idx, tlvStr(tlv))
+
+		img.Tlvs = append(img.Tlvs[0:idx], img.Tlvs[idx+1:]...)
+	}
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runRmsigsCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	inFilename := args[0]
+	outFilename, err := CalcOutFilename(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	cnt := img.RemoveTlvsIf(func(tlv image.ImageTlv) bool {
+		return tlv.Header.Type == image.IMAGE_TLV_KEYHASH ||
+			tlv.Header.Type == image.IMAGE_TLV_RSA2048 ||
+			tlv.Header.Type == image.IMAGE_TLV_ECDSA224 ||
+			tlv.Header.Type == image.IMAGE_TLV_ECDSA256
+	})
+
+	log.Debugf("Removed %d existing signatures", cnt)
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runHashableCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	if OptOutFilename == "" {
+		LarvaUsage(cmd, util.FmtNewtError("--outfile (-o) option required"))
+	}
+
+	inFilename := args[0]
+	outFilename := OptOutFilename
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	f, err := os.Create(outFilename)
+	if err != nil {
+		LarvaUsage(nil, util.ChildNewtError(err))
+	}
+	defer f.Close()
+
+	if err := binary.Write(f, binary.LittleEndian, &img.Header); err != nil {
+		LarvaUsage(nil, util.FmtNewtError(
+			"Error writing image header: %s", err.Error()))
+	}
+	_, err = f.Write(img.Body)
+	if err != nil {
+		LarvaUsage(nil, util.FmtNewtError(
+			"Error writing image body: %s", err.Error()))
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"Wrote hashable content to %s\n", outFilename)
+}
+
+func runAddsigCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 4 {
+		LarvaUsage(cmd, nil)
+	}
+
+	imgFilename := args[0]
+	keyFilename := args[1]
+	sigFilename := args[2]
+
+	sigType, err := util.AtoiNoOct(args[3])
+	if err != nil || sigType < 0 || sigType > 255 ||
+		!image.ImageTlvTypeIsSig(uint8(sigType)) {
+
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Invalid signature type: %s", args[3]))
+	}
+
+	outFilename, err := CalcOutFilename(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	keyData, err := ioutil.ReadFile(keyFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Error reading key file: %s", err.Error()))
+	}
+
+	sigData, err := ioutil.ReadFile(sigFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Error reading signature file: %s", err.Error()))
+	}
+
+	// ECDSA256 signatures need to be padded out to >=72 bytes.
+	if sigType == image.IMAGE_TLV_ECDSA256 {
+		sigData, err = lvimg.PadEcdsa256Sig(sigData)
+		if err != nil {
+			LarvaUsage(nil, err)
+		}
+	}
+
+	// Build and append key hash TLV.
+	keyHashTlv := image.BuildKeyHashTlv(keyData)
+	util.StatusMessage(util.VERBOSITY_DEFAULT, "Adding TLV%d (%s)\n",
+		len(img.Tlvs), tlvStr(keyHashTlv))
+	img.Tlvs = append(img.Tlvs, keyHashTlv)
+
+	// Build and append signature TLV.
+	sigTlv := image.ImageTlv{
+		Header: image.ImageTlvHdr{
+			Type: uint8(sigType),
+			Len:  uint16(len(sigData)),
+		},
+		Data: sigData,
+	}
+	util.StatusMessage(util.VERBOSITY_DEFAULT, "Adding TLV%d (%s)\n",
+		len(img.Tlvs), tlvStr(sigTlv))
+	img.Tlvs = append(img.Tlvs, sigTlv)
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runDecryptCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	imgFilename := args[0]
+	keyFilename := args[1]
+
+	outFilename, err := CalcOutFilename(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	keyBytes, err := ioutil.ReadFile(keyFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Error reading key file: %s", err.Error()))
+	}
+
+	img, err = lvimg.DecryptImage(img, keyBytes)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runEncryptCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	imgFilename := args[0]
+	keyFilename := args[1]
+
+	outFilename, err := CalcOutFilename(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	keyBytes, err := ioutil.ReadFile(keyFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Error reading key file: %s", err.Error()))
+	}
+
+	img, err = lvimg.EncryptImage(img, keyBytes)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func AddImageCommands(cmd *cobra.Command) {
+	imageCmd := &cobra.Command{
+		Use:   "image",
+		Short: "Shows and manipulates Mynewt image (.img) files",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Usage()
+		},
+	}
+	cmd.AddCommand(imageCmd)
+
+	showCmd := &cobra.Command{
+		Use:   "show <img-file>",
+		Short: "Displays JSON describing a Mynewt image file",
+		Run:   runShowCmd,
+	}
+	imageCmd.AddCommand(showCmd)
+
+	briefCmd := &cobra.Command{
+		Use:   "brief <img-file>",
+		Short: "Displays brief text description of a Mynewt image file",
+		Run:   runBriefCmd,
+	}
+	imageCmd.AddCommand(briefCmd)
+
+	signCmd := &cobra.Command{
+		Use:   "sign <img-file> <priv-key-pem> [priv-key-pem...]",
+		Short: "Appends signatures to a Mynewt image file",
+		Run:   runSignCmd,
+	}
+
+	signCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+		"File to write to")
+	signCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(signCmd)
+
+	addtlvsCmd := &cobra.Command{
+		Use: "addtlvs <img-file> <tlv-type> <data-filename> " +
+			"[tlv-type] [data-filename] [...]",
+		Short: "Adds the specified TLVs to a Mynewt image file",
+		Run:   runAddTlvsCmd,
+	}
+
+	addtlvsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+		"File to write to")
+	addtlvsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(addtlvsCmd)
+
+	rmtlvsCmd := &cobra.Command{
+		Use:   "rmtlvs <img-file> <tlv-index> [tlv-index] [...]",
+		Short: "Removes the specified TLVs from a Mynewt image file",
+		Run:   runRmtlvsCmd,
+	}
+
+	rmtlvsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+		"File to write to")
+	rmtlvsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(rmtlvsCmd)
+
+	rmsigsCmd := &cobra.Command{
+		Use:   "rmsigs",
+		Short: "Removes all signatures from a Mynewt image file",
+		Run:   runRmsigsCmd,
+	}
+
+	rmsigsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+		"File to write to")
+	rmsigsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(rmsigsCmd)
+
+	hashableCmd := &cobra.Command{
+		Use:   "hashable <img-file>",
+		Short: "Removes all signatures from a Mynewt image file",
+		Run:   runHashableCmd,
+	}
+
+	hashableCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+		"", "File to write to")
+
+	imageCmd.AddCommand(hashableCmd)
+
+	addsigCmd := &cobra.Command{
+		Use:   "addsig <image> <pub-key-der> <sig-der> <sig-tlv-type>",
+		Short: "Adds a signature to a Mynewt image file",
+		Run:   runAddsigCmd,
+	}
+
+	addsigCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+		"", "File to write to")
+	addsigCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(addsigCmd)
+
+	decryptCmd := &cobra.Command{
+		Use:   "decrypt <image> <priv-key-der>",
+		Short: "Decrypts an encrypted Mynewt image file",
+		Run:   runDecryptCmd,
+	}
+
+	decryptCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+		"", "File to write to")
+	decryptCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(decryptCmd)
+
+	encryptCmd := &cobra.Command{
+		Use:   "encrypt <image> <priv-key-der>",
+		Short: "Encrypts a Mynewt image file",
+		Run:   runEncryptCmd,
+	}
+
+	encryptCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+		"", "File to write to")
+	encryptCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(encryptCmd)
+}
diff --git a/larva/cli/mfg_cmds.go b/larva/cli/mfg_cmds.go
new file mode 100644
index 00000000..45c63726
--- /dev/null
+++ b/larva/cli/mfg_cmds.go
@@ -0,0 +1,418 @@
+/**
+ * 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 cli
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/artifact/manifest"
+	"mynewt.apache.org/newt/artifact/mfg"
+	"mynewt.apache.org/newt/artifact/misc"
+	"mynewt.apache.org/newt/larva/lvmfg"
+	"mynewt.apache.org/newt/util"
+)
+
+func readMfgBin(filename string) ([]byte, error) {
+	bin, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, util.FmtChildNewtError(err,
+			"Failed to read manufacturing image: %s", err.Error())
+	}
+
+	return bin, nil
+}
+
+func readManifest(mfgDir string) (manifest.MfgManifest, error) {
+	return manifest.ReadMfgManifest(mfgDir + "/" + mfg.MANIFEST_FILENAME)
+}
+
+func extractFlashAreas(mman manifest.MfgManifest) ([]flash.FlashArea, error) {
+	areas := flash.SortFlashAreasByDevOff(mman.FlashAreas)
+
+	if len(areas) == 0 {
+		LarvaUsage(nil, util.FmtNewtError(
+			"Boot loader manifest does not contain flash map"))
+	}
+
+	overlaps, conflicts := flash.DetectErrors(areas)
+	if len(overlaps) > 0 || len(conflicts) > 0 {
+		return nil, util.NewNewtError(flash.ErrorText(overlaps, conflicts))
+	}
+
+	if err := lvmfg.VerifyAreas(areas); err != nil {
+		return nil, err
+	}
+
+	log.Debugf("Successfully read flash areas: %+v", areas)
+	return areas, nil
+}
+
+func createNameBlobMap(binDir string,
+	areas []flash.FlashArea) (lvmfg.NameBlobMap, error) {
+
+	mm := lvmfg.NameBlobMap{}
+
+	for _, area := range areas {
+		filename := fmt.Sprintf("%s/%s.bin", binDir, area.Name)
+		bin, err := readMfgBin(filename)
+		if err != nil {
+			if !util.IsNotExist(err) {
+				return nil, util.ChildNewtError(err)
+			}
+		} else {
+			mm[area.Name] = bin
+		}
+	}
+
+	return mm, nil
+}
+
+func runMfgShowCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+	inFilename := args[0]
+
+	metaEndOff, err := util.AtoiNoOct(args[1])
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"invalid meta offset \"%s\"", args[1]))
+	}
+
+	bin, err := readMfgBin(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	m, err := mfg.Parse(bin, metaEndOff, 0xff)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if m.Meta == nil {
+		util.StatusMessage(util.VERBOSITY_DEFAULT,
+			"Manufacturing image %s does not contain an MMR\n", inFilename)
+	} else {
+		s, err := m.Meta.Json(metaEndOff)
+		if err != nil {
+			LarvaUsage(nil, err)
+		}
+		util.StatusMessage(util.VERBOSITY_DEFAULT,
+			"Manufacturing image %s contains an MMR with "+
+				"the following properties:\n%s\n", inFilename, s)
+	}
+}
+
+func runSplitCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	mfgDir := args[0]
+	outDir := args[1]
+
+	mm, err := readManifest(mfgDir)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	areas, err := extractFlashAreas(mm)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	binPath := fmt.Sprintf("%s/%s", mfgDir, mm.BinPath)
+	bin, err := readMfgBin(binPath)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read \"%s\": %s", binPath, err.Error()))
+	}
+
+	nbmap, err := lvmfg.Split(bin, mm.Device, areas, 0xff)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := os.Mkdir(outDir, os.ModePerm); err != nil {
+		LarvaUsage(nil, util.ChildNewtError(err))
+	}
+
+	for name, data := range nbmap {
+		filename := fmt.Sprintf("%s/%s.bin", outDir, name)
+		if err := WriteFile(data, filename); err != nil {
+			LarvaUsage(nil, err)
+		}
+	}
+
+	mfgDstDir := fmt.Sprintf("%s/mfg", outDir)
+	if err := CopyDir(mfgDir, mfgDstDir); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runJoinCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	splitDir := args[0]
+	outDir := args[1]
+
+	if util.NodeExist(outDir) {
+		LarvaUsage(nil, util.FmtNewtError(
+			"Destination \"%s\" already exists", outDir))
+	}
+
+	mm, err := readManifest(splitDir + "/mfg")
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+	areas, err := extractFlashAreas(mm)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	nbmap, err := createNameBlobMap(splitDir, areas)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	bin, err := lvmfg.Join(nbmap, 0xff, areas)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	m, err := mfg.Parse(bin, mm.Meta.EndOffset, 0xff)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	infos, err := ioutil.ReadDir(splitDir + "/mfg")
+	if err != nil {
+		LarvaUsage(nil, util.FmtNewtError(
+			"Error reading source mfg directory: %s", err.Error()))
+	}
+	for _, info := range infos {
+		if info.Name() != mfg.MFG_IMG_FILENAME {
+			src := splitDir + "/mfg/" + info.Name()
+			dst := outDir + "/" + info.Name()
+			if info.IsDir() {
+				err = CopyDir(src, dst)
+			} else {
+				err = CopyFile(src, dst)
+			}
+			if err != nil {
+				LarvaUsage(nil, err)
+			}
+		}
+	}
+
+	finalBin, err := m.Bytes(0xff)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	binPath := fmt.Sprintf("%s/%s", outDir, mfg.MFG_IMG_FILENAME)
+	if err := WriteFile(finalBin, binPath); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runSwapKeyCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 3 {
+		LarvaUsage(cmd, nil)
+	}
+
+	mfgimgFilename := args[0]
+	okeyFilename := args[1]
+	nkeyFilename := args[2]
+
+	outFilename, err := CalcOutFilename(mfgimgFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	bin, err := readMfgBin(mfgimgFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read mfgimg file: %s", err.Error()))
+	}
+
+	okey, err := ioutil.ReadFile(okeyFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read old key der: %s", err.Error()))
+	}
+
+	nkey, err := ioutil.ReadFile(nkeyFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read new key der: %s", err.Error()))
+	}
+
+	if err := lvmfg.ReplaceKey(bin, okey, nkey); err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := WriteFile(bin, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runRehashCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	mfgDir := args[0]
+
+	outDir, err := CalcOutFilename(mfgDir)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	// Read manifest and mfgimg.bin.
+	mman, err := readManifest(mfgDir)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	binPath := fmt.Sprintf("%s/%s", mfgDir, mman.BinPath)
+	bin, err := readMfgBin(binPath)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read \"%s\": %s", binPath, err.Error()))
+	}
+
+	// Calculate accurate hash.
+	metaOff := -1
+	if mman.Meta != nil {
+		metaOff = mman.Meta.EndOffset
+	}
+	m, err := mfg.Parse(bin, metaOff, 0xff)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := m.RecalcHash(0xff); err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	hash, err := m.Hash()
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	// Update manifest.
+	mman.MfgHash = misc.HashString(hash)
+
+	// Write new artifacts.
+	if outDir != mfgDir {
+		// Not an in-place operation; copy input directory.
+		if err := CopyDir(mfgDir, outDir); err != nil {
+			LarvaUsage(nil, err)
+		}
+		binPath = fmt.Sprintf("%s/%s", outDir, mman.BinPath)
+	}
+
+	newBin, err := m.Bytes(0xff)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+	if err := WriteFile(newBin, binPath); err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	json, err := mman.MarshalJson()
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	manPath := fmt.Sprintf("%s/%s", outDir, mfg.MANIFEST_FILENAME)
+	if err := WriteFile(json, manPath); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func AddMfgCommands(cmd *cobra.Command) {
+	mfgCmd := &cobra.Command{
+		Use:   "mfg",
+		Short: "Manipulates Mynewt manufacturing images",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Usage()
+		},
+	}
+	cmd.AddCommand(mfgCmd)
+
+	showCmd := &cobra.Command{
+		Use:   "show <mfgimg.bin> <meta-end-offset>",
+		Short: "Displays JSON describing a manufacturing image",
+		Run:   runMfgShowCmd,
+	}
+
+	mfgCmd.AddCommand(showCmd)
+
+	splitCmd := &cobra.Command{
+		Use:   "split <mfgimage-dir> <out-dir>",
+		Short: "Splits a Mynewt mfg section into several files",
+		Run:   runSplitCmd,
+	}
+
+	mfgCmd.AddCommand(splitCmd)
+
+	joinCmd := &cobra.Command{
+		Use:   "join <split-dir> <out-dir>",
+		Short: "Joins a split mfg section into a single file",
+		Run:   runJoinCmd,
+	}
+
+	mfgCmd.AddCommand(joinCmd)
+
+	swapKeyCmd := &cobra.Command{
+		Use:   "swapkey <mfgimg-bin> <cur-key-der> <new-key-der>",
+		Short: "Replaces a key in a manufacturing image",
+		Run:   runSwapKeyCmd,
+	}
+
+	swapKeyCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+		"", "File to write to")
+	swapKeyCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	mfgCmd.AddCommand(swapKeyCmd)
+
+	rehashCmd := &cobra.Command{
+		Use:   "rehash <mfgimage-dir>",
+		Short: "Replaces an outdated mfgimage hash with an accurate one",
+		Run:   runRehashCmd,
+	}
+	rehashCmd.PersistentFlags().StringVarP(&OptOutFilename, "outdir", "o",
+		"", "Directory to write to")
+	rehashCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input files")
+
+	mfgCmd.AddCommand(rehashCmd)
+}
diff --git a/larva/cli/util.go b/larva/cli/util.go
new file mode 100644
index 00000000..ce574e34
--- /dev/null
+++ b/larva/cli/util.go
@@ -0,0 +1,100 @@
+/**
+ * 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 cli
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/util"
+)
+
+var OptOutFilename string
+var OptInPlace bool
+
+func LarvaUsage(cmd *cobra.Command, err error) {
+	if err != nil {
+		sErr := err.(*util.NewtError)
+		log.Debugf("%s", sErr.StackTrace)
+		fmt.Fprintf(os.Stderr, "Error: %s\n", sErr.Text)
+	}
+
+	if cmd != nil {
+		fmt.Printf("%s - ", cmd.Name())
+		cmd.Help()
+	}
+	os.Exit(1)
+}
+
+func CalcOutFilename(inFilename string) (string, error) {
+	if OptOutFilename != "" {
+		if OptInPlace {
+			return "", util.FmtNewtError(
+				"Only one of --outfile (-o) or --inplace (-i) options allowed")
+		}
+
+		return OptOutFilename, nil
+	}
+
+	if !OptInPlace {
+		return "", util.FmtNewtError(
+			"--outfile (-o) or --inplace (-i) option required")
+	}
+
+	return inFilename, nil
+}
+
+func CopyDir(src string, dst string) error {
+	if err := util.CopyDir(src, dst); err != nil {
+		return util.FmtNewtError(
+			"Failed to copy directory \"%s\" to \"%s\": %s",
+			src, dst, err.Error())
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"Copied directory \"%s\" to \"%s\"\n", src, dst)
+	return nil
+}
+
+func CopyFile(src string, dst string) error {
+	if err := util.CopyFile(src, dst); err != nil {
+		return util.FmtNewtError(
+			"Failed to copy file \"%s\" to \"%s\": %s",
+			src, dst, err.Error())
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"Copied file \"%s\" to \"%s\"\n", src, dst)
+	return nil
+}
+
+func WriteFile(data []byte, filename string) error {
+	if err := ioutil.WriteFile(filename, data, os.ModePerm); err != nil {
+		return util.FmtNewtError(
+			"Failed to write file \"%s\": %s", filename, err.Error())
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT, "Wrote file \"%s\"\n", filename)
+	return nil
+}
diff --git a/larva/larva.go b/larva/larva.go
new file mode 100644
index 00000000..5d7a58cb
--- /dev/null
+++ b/larva/larva.go
@@ -0,0 +1,100 @@
+/**
+ * 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 main
+
+import (
+	"fmt"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/larva/cli"
+	"mynewt.apache.org/newt/util"
+)
+
+var LarvaLogLevel log.Level
+var larvaSilent bool
+var larvaQuiet bool
+var larvaVerbose bool
+var larvaVersion = "0.0.2"
+
+func main() {
+	larvaHelpText := ""
+	larvaHelpEx := ""
+
+	logLevelStr := ""
+	larvaCmd := &cobra.Command{
+		Use:     "larva",
+		Short:   "larva is a tool to help you compose and build your own OS",
+		Long:    larvaHelpText,
+		Example: larvaHelpEx,
+		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+			verbosity := util.VERBOSITY_DEFAULT
+			if larvaSilent {
+				verbosity = util.VERBOSITY_SILENT
+			} else if larvaQuiet {
+				verbosity = util.VERBOSITY_QUIET
+			} else if larvaVerbose {
+				verbosity = util.VERBOSITY_VERBOSE
+			}
+
+			logLevel, err := log.ParseLevel(logLevelStr)
+			if err != nil {
+				cli.LarvaUsage(nil, util.ChildNewtError(err))
+			}
+			LarvaLogLevel = logLevel
+
+			if err := util.Init(LarvaLogLevel, "", verbosity); err != nil {
+				cli.LarvaUsage(nil, err)
+			}
+		},
+
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Help()
+		},
+	}
+
+	larvaCmd.PersistentFlags().BoolVarP(&larvaVerbose, "verbose", "v", false,
+		"Enable verbose output when executing commands")
+	larvaCmd.PersistentFlags().BoolVarP(&larvaQuiet, "quiet", "q", false,
+		"Be quiet; only display error output")
+	larvaCmd.PersistentFlags().BoolVarP(&larvaSilent, "silent", "s", false,
+		"Be silent; don't output anything")
+	larvaCmd.PersistentFlags().StringVarP(&logLevelStr, "loglevel", "l",
+		"WARN", "Log level")
+
+	versHelpText := `Display the larva version number`
+	versHelpEx := "  larva version"
+	versCmd := &cobra.Command{
+		Use:     "version",
+		Short:   "Display the larva version number",
+		Long:    versHelpText,
+		Example: versHelpEx,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Printf("%s\n", larvaVersion)
+		},
+	}
+	larvaCmd.AddCommand(versCmd)
+
+	cli.AddImageCommands(larvaCmd)
+	cli.AddMfgCommands(larvaCmd)
+
+	larvaCmd.Execute()
+}
diff --git a/larva/lvimg/lvimg.go b/larva/lvimg/lvimg.go
new file mode 100644
index 00000000..8522199f
--- /dev/null
+++ b/larva/lvimg/lvimg.go
@@ -0,0 +1,188 @@
+/**
+ * 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 lvimg
+
+import (
+	"encoding/hex"
+	"fmt"
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/util"
+)
+
+func GetDupSigs(img image.Image) []string {
+	m := map[string]struct{}{}
+	var dups []string
+
+	for _, tlv := range img.Tlvs {
+		if tlv.Header.Type == image.IMAGE_TLV_KEYHASH {
+			h := hex.EncodeToString(tlv.Data)
+			if _, ok := m[h]; ok {
+				dups = append(dups, h)
+			} else {
+				m[h] = struct{}{}
+			}
+		}
+	}
+
+	return dups
+}
+
+func DetectInvalidSigTlvs(img image.Image) error {
+	var errStrs []string
+	addErr := func(format string, args ...interface{}) {
+		s := fmt.Sprintf(format, args...)
+		errStrs = append(errStrs, s)
+	}
+
+	prevIsHash := false
+	for i, tlv := range img.Tlvs {
+		curIsHash := tlv.Header.Type == image.IMAGE_TLV_KEYHASH
+		curIsSig := image.ImageTlvTypeIsSig(tlv.Header.Type)
+		isLast := i == len(img.Tlvs)-1
+
+		if prevIsHash && !curIsSig {
+			prevTlv := img.Tlvs[i-1]
+			addErr("TLV%d (%s) not immediately followed by signature TLV",
+				i-1, image.ImageTlvTypeName(prevTlv.Header.Type))
+		} else if curIsHash && isLast {
+			addErr("TLV%d (%s) not immediately followed by signature TLV",
+				i, image.ImageTlvTypeName(tlv.Header.Type))
+		} else if !prevIsHash && curIsSig {
+			addErr("TLV%d (%s) not immediately preceded by key hash TLV",
+				i, image.ImageTlvTypeName(tlv.Header.Type))
+		}
+
+		prevIsHash = curIsHash
+	}
+
+	if len(errStrs) > 0 {
+		return util.FmtNewtError("%s", strings.Join(errStrs, "\n"))
+	}
+
+	return nil
+}
+
+func VerifyImage(img image.Image) error {
+	if len(img.Tlvs) == 0 || img.Tlvs[0].Header.Type != image.IMAGE_TLV_SHA256 {
+		return util.FmtNewtError("First TLV must be SHA256")
+	}
+
+	if err := DetectInvalidSigTlvs(img); err != nil {
+		return err
+	}
+
+	if dups := GetDupSigs(img); len(dups) > 0 {
+		s := "Duplicate signatures detected:"
+		for _, d := range dups {
+			s += fmt.Sprintf("\n    %s", d)
+		}
+
+		return util.FmtNewtError("%s", s)
+	}
+
+	return nil
+}
+
+func PadEcdsa256Sig(sig []byte) ([]byte, error) {
+	if len(sig) < 70 {
+		return nil, util.FmtNewtError(
+			"Invalid ECDSA256 signature; length (%d) less than 70", len(sig))
+	}
+
+	if len(sig) < 72 {
+		sig = append(sig, []byte{0x00, 0x00}...)
+	}
+
+	return sig, nil
+}
+
+// XXX: Only RSA supported for now.
+func ExtractSecret(img *image.Image) ([]byte, error) {
+	tlvs := img.RemoveTlvsWithType(image.IMAGE_TLV_ENC_RSA)
+	if len(tlvs) != 1 {
+		return nil, util.FmtNewtError(
+			"Image contains invalid count of ENC_RSA TLVs: %d; must contain 1",
+			len(tlvs))
+	}
+
+	return tlvs[0].Data, nil
+}
+
+// XXX: Only RSA supported for now.
+func DecryptImage(img image.Image, privKeBytes []byte) (image.Image, error) {
+	cipherSecret, err := ExtractSecret(&img)
+	if err != nil {
+		return img, err
+	}
+
+	privKe, err := image.ParsePrivKeDer(privKeBytes)
+	if err != nil {
+		return img, err
+	}
+
+	plainSecret, err := image.DecryptSecretRsa(privKe, cipherSecret)
+	if err != nil {
+		return img, err
+	}
+
+	body, err := image.EncryptImageBody(img.Body, plainSecret)
+	if err != nil {
+		return img, err
+	}
+
+	img.Body = body
+	return img, nil
+}
+
+func EncryptImage(img image.Image, pubKeBytes []byte) (image.Image, error) {
+	tlvp, err := img.FindUniqueTlv(image.IMAGE_TLV_ENC_RSA)
+	if err != nil {
+		return img, err
+	}
+	if tlvp != nil {
+		return img, util.FmtNewtError("Image already contains an ENC_RSA TLV")
+	}
+
+	plainSecret, err := image.GeneratePlainSecret()
+	if err != nil {
+		return img, err
+	}
+
+	cipherSecret, err := image.GenerateCipherSecret(pubKeBytes, plainSecret)
+	if err != nil {
+		return img, err
+	}
+
+	body, err := image.EncryptImageBody(img.Body, plainSecret)
+	if err != nil {
+		return img, err
+	}
+	img.Body = body
+
+	tlv, err := image.GenerateEncTlv(cipherSecret)
+	if err != nil {
+		return img, err
+	}
+	img.Tlvs = append(img.Tlvs, tlv)
+
+	return img, nil
+}
diff --git a/larva/lvmfg/lvmfg.go b/larva/lvmfg/lvmfg.go
new file mode 100644
index 00000000..8bb6a22d
--- /dev/null
+++ b/larva/lvmfg/lvmfg.go
@@ -0,0 +1,203 @@
+/**
+ * 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 lvmfg
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/artifact/mfg"
+	"mynewt.apache.org/newt/util"
+)
+
+type NameBlobMap map[string][]byte
+
+func (to NameBlobMap) Union(from NameBlobMap) {
+	for k, v := range from {
+		to[k] = v
+	}
+}
+
+func errInvalidArea(areaName string, format string,
+	args ...interface{}) error {
+
+	suffix := fmt.Sprintf(format, args...)
+	return util.FmtNewtError("Invalid flash area \"%s\": %s", areaName, suffix)
+}
+
+func verifyArea(area flash.FlashArea, minOffset int) error {
+	if area.Offset < minOffset {
+		return errInvalidArea(area.Name, "invalid offset %d; expected >= %d",
+			area.Offset, minOffset)
+	}
+
+	if area.Size < 0 {
+		return errInvalidArea(area.Name, "invalid size %d", area.Size)
+	}
+
+	return nil
+}
+
+// `areas` must be sorted by device ID, then by offset.
+func VerifyAreas(areas []flash.FlashArea) error {
+	prevDevice := -1
+	off := 0
+	for _, area := range areas {
+		if area.Device != prevDevice {
+			off = 0
+		}
+
+		if err := verifyArea(area, off); err != nil {
+			return err
+		}
+		off += area.Size
+	}
+
+	return nil
+}
+
+func Split(mfgBin []byte, deviceNum int,
+	areas []flash.FlashArea, eraseVal byte) (NameBlobMap, error) {
+
+	mm := NameBlobMap{}
+
+	for _, area := range areas {
+		if _, ok := mm[area.Name]; ok {
+			return nil, util.FmtNewtError(
+				"two or more flash areas with same name: \"%s\"", area.Name)
+		}
+
+		if area.Device == deviceNum {
+			var areaBin []byte
+			if area.Offset < len(mfgBin) {
+				end := area.Offset + area.Size
+				overflow := end - len(mfgBin)
+				if overflow > 0 {
+					end -= overflow
+				}
+				areaBin = mfgBin[area.Offset:end]
+			}
+
+			mm[area.Name] = StripPadding(areaBin, eraseVal)
+		}
+	}
+
+	return mm, nil
+}
+
+// `areas` must be sorted by device ID, then by offset.
+func Join(mm NameBlobMap, eraseVal byte,
+	areas []flash.FlashArea) ([]byte, error) {
+
+	// Keep track of which areas we haven't seen yet.
+	unseen := map[string]struct{}{}
+	for name, _ := range mm {
+		unseen[name] = struct{}{}
+	}
+
+	joined := []byte{}
+	for _, area := range areas {
+		bin := mm[area.Name]
+
+		// Only include this area if it belongs to the mfg image we are
+		// joining.
+		if bin != nil {
+			delete(unseen, area.Name)
+
+			// Pad remainder of previous area in this section.
+			padSize := area.Offset - len(joined)
+			if padSize > 0 {
+				joined = mfg.AddPadding(joined, eraseVal, padSize)
+			}
+
+			// Append data to joined binary.
+			binstr := ""
+			if len(bin) >= 4 {
+				binstr = fmt.Sprintf("%x", bin[:4])
+			}
+			util.StatusMessage(util.VERBOSITY_DEFAULT,
+				"inserting %s (%s) at offset %d (0x%x)\n",
+				area.Name, binstr, len(joined), len(joined))
+			joined = append(joined, bin...)
+		}
+	}
+
+	// Ensure we processed every area in the map.
+	if len(unseen) > 0 {
+		names := []string{}
+		for name, _ := range unseen {
+			names = append(names, name)
+		}
+		sort.Strings(names)
+
+		return nil, util.FmtNewtError(
+			"unprocessed flash areas: %s", strings.Join(names, ", "))
+	}
+
+	// Strip padding from the end of the joined binary.
+	joined = StripPadding(joined, eraseVal)
+
+	return joined, nil
+}
+
+func ReplaceKey(sec0 []byte, okey []byte, nkey []byte) error {
+	if len(okey) != len(nkey) {
+		return util.FmtNewtError(
+			"key lengths differ (%d != %d)", len(okey), len(nkey))
+	}
+
+	if len(okey) > len(sec0) {
+		return util.FmtNewtError(
+			"key longer than flash section (%d > %d)", len(okey), len(sec0))
+	}
+
+	idx := bytes.Index(sec0, okey)
+	if idx == -1 {
+		return util.FmtNewtError("old key not present in flash section")
+	}
+
+	lastIdx := bytes.LastIndex(sec0, okey)
+	if idx != lastIdx {
+		return util.FmtNewtError(
+			"multiple instances of old key in flash section")
+	}
+
+	util.StatusMessage(util.VERBOSITY_VERBOSE,
+		"Replacing boot key at offset %d\n", idx)
+
+	copy(sec0[idx:idx+len(okey)], nkey)
+
+	return nil
+}
+
+func StripPadding(b []byte, eraseVal byte) []byte {
+	var pad int
+	for pad = 0; pad < len(b); pad++ {
+		off := len(b) - pad - 1
+		if b[off] != eraseVal {
+			break
+		}
+	}
+
+	return b[:len(b)-pad]
+}
diff --git a/newt/builder/build.go b/newt/builder/build.go
index 06b16415..399b6313 100644
--- a/newt/builder/build.go
+++ b/newt/builder/build.go
@@ -29,7 +29,6 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 
-	"mynewt.apache.org/newt/newt/image"
 	"mynewt.apache.org/newt/newt/interfaces"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/pkg"
@@ -92,7 +91,7 @@ func NewBuilder(
 	for api, rpkg := range apiMap {
 		bpkg := b.PkgMap[rpkg]
 		if bpkg == nil {
-			for _, rpkg := range b.sortedRpkgs() {
+			for _, rpkg := range b.SortedRpkgs() {
 				log.Debugf("    * %s", rpkg.Lpkg.Name())
 			}
 			return nil, util.FmtNewtError(
@@ -794,41 +793,6 @@ func (b *Builder) buildRomElf(common *symbol.SymbolMap) error {
 	return nil
 }
 
-func (b *Builder) CreateImage(version string,
-	keystrs []string, keyId uint8,
-	loaderImg *image.Image) (*image.Image, error) {
-
-	img, err := image.NewImage(b.AppBinPath(), b.AppImgPath())
-	if err != nil {
-		return nil, err
-	}
-
-	err = img.SetVersion(version)
-	if err != nil {
-		return nil, err
-	}
-
-	if len(keystrs) == 1 {
-		if err := img.SetKeyV1(keystrs[0], keyId); err != nil {
-			return nil, err
-		}
-	} else {
-		if err := img.SetKeys(keystrs); err != nil {
-			return nil, err
-		}
-	}
-
-	img.HeaderSize = uint(b.targetBuilder.target.HeaderSize)
-	if err := img.Generate(loaderImg); err != nil {
-		return nil, err
-	}
-
-	util.StatusMessage(util.VERBOSITY_DEFAULT,
-		"App image succesfully generated: %s\n", img.TargetImg)
-
-	return img, nil
-}
-
 // Deletes files that should never be reused for a subsequent build.  This
 // list includes:
 //     <app>.img
diff --git a/newt/builder/buildutil.go b/newt/builder/buildutil.go
index 113cbfde..260ef537 100644
--- a/newt/builder/buildutil.go
+++ b/newt/builder/buildutil.go
@@ -83,7 +83,7 @@ func (b *Builder) sortedBuildPackages() []*BuildPackage {
 	return sorter.bpkgs
 }
 
-func (b *Builder) sortedRpkgs() []*resolve.ResolvePackage {
+func (b *Builder) SortedRpkgs() []*resolve.ResolvePackage {
 	bpkgs := b.sortedBuildPackages()
 
 	rpkgs := make([]*resolve.ResolvePackage, len(bpkgs), len(bpkgs))
diff --git a/newt/builder/paths.go b/newt/builder/paths.go
index becba56a..17995fec 100644
--- a/newt/builder/paths.go
+++ b/newt/builder/paths.go
@@ -166,6 +166,10 @@ func (b *Builder) AppHexPath() string {
 		".hex"
 }
 
+func (b *Builder) AppMapPath() string {
+	return b.AppElfPath() + ".map"
+}
+
 func (b *Builder) AppBinPath() string {
 	return b.AppElfPath() + ".bin"
 }
diff --git a/newt/builder/size.go b/newt/builder/size.go
index 4753433c..8da6f1a8 100644
--- a/newt/builder/size.go
+++ b/newt/builder/size.go
@@ -28,9 +28,8 @@ import (
 	"strconv"
 	"strings"
 
-	"mynewt.apache.org/newt/newt/image"
-	"mynewt.apache.org/newt/util"
 	"mynewt.apache.org/newt/newt/interfaces"
+	"mynewt.apache.org/newt/util"
 )
 
 /*
@@ -420,58 +419,6 @@ func (b *Builder) FindPkgNameByArName(arName string) string {
 	return filepath.Base(arName)
 }
 
-func (b *Builder) PkgSizes() (*image.ImageManifestSizeCollector, error) {
-	if b.appPkg == nil {
-		return nil, util.NewNewtError("app package not specified for this target")
-	}
-
-	if b.targetBuilder.bspPkg.Arch == "sim" {
-		return nil, util.NewNewtError("'newt size' not supported for sim targets")
-	}
-	mapFile := b.AppElfPath() + ".map"
-
-	libs, err := ParseMapFileSizes(mapFile)
-	if err != nil {
-		return nil, err
-	}
-
-	/*
-	 * Order libraries by name.
-	 */
-	pkgSizes := make(PkgSizeArray, len(libs))
-	i := 0
-	for _, es := range libs {
-		pkgSizes[i] = es
-		i++
-	}
-	sort.Sort(pkgSizes)
-
-	c := image.NewImageManifestSizeCollector()
-	for _, es := range pkgSizes {
-		p := c.AddPkg(b.FindPkgNameByArName(es.Name))
-
-		/*
-		 * Order symbols by name.
-		 */
-		symbols := make(SymbolDataArray, len(es.Syms))
-		i := 0
-		for _, sym := range es.Syms {
-			symbols[i] = sym
-			i++
-		}
-		sort.Sort(symbols)
-		for _, sym := range symbols {
-			for area, areaSz := range sym.Sizes {
-				if areaSz != 0 {
-					p.AddSymbol(sym.ObjName, sym.Name, area, areaSz)
-				}
-			}
-		}
-	}
-
-	return c, nil
-}
-
 func (b *Builder) Size() error {
 	if b.appPkg == nil {
 		return util.NewNewtError("app package not specified for this target")
diff --git a/newt/builder/size_report.go b/newt/builder/size_report.go
index 0673fefd..d62f5b28 100644
--- a/newt/builder/size_report.go
+++ b/newt/builder/size_report.go
@@ -76,7 +76,6 @@ func runAddr2lineCommand(elfFilePath string, address string) ([]byte, error) {
 	return cmdOut, err
 }
 
-
 func loadSymbolsAndPaths(elfFilePath, pathToStrip string) (map[string]string,
 	error) {
 	symbolsPath := make(map[string]string)
diff --git a/newt/builder/targetbuild.go b/newt/builder/targetbuild.go
index 3f10dbb9..94fe8f95 100644
--- a/newt/builder/targetbuild.go
+++ b/newt/builder/targetbuild.go
@@ -24,22 +24,19 @@ import (
 	"crypto/ecdsa"
 	"crypto/rsa"
 	"crypto/x509"
-	"encoding/json"
 	"encoding/pem"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"sort"
 	"strings"
-	"time"
 
 	log "github.com/Sirupsen/logrus"
 
-	"mynewt.apache.org/newt/newt/flash"
-	"mynewt.apache.org/newt/newt/image"
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/newt/flashmap"
 	"mynewt.apache.org/newt/newt/interfaces"
-	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/pkg"
 	"mynewt.apache.org/newt/newt/project"
 	"mynewt.apache.org/newt/newt/resolve"
@@ -106,6 +103,10 @@ func NewTargetBuilder(target *target.Target) (*TargetBuilder, error) {
 	return NewTargetTester(target, nil)
 }
 
+func (t *TargetBuilder) BspPkg() *pkg.BspPackage {
+	return t.bspPkg
+}
+
 func (t *TargetBuilder) NewCompiler(dstDir string, buildProfile string) (
 	*toolchain.Compiler, error) {
 
@@ -288,7 +289,10 @@ func (t *TargetBuilder) validateAndWriteCfg() error {
 	}
 
 	// Generate flash map.
-	if err := t.bspPkg.FlashMap.EnsureWritten(srcDir, incDir,
+	if err := flashmap.EnsureFlashMapWritten(
+		t.bspPkg.FlashMap,
+		srcDir,
+		incDir,
 		pkg.ShortName(t.target.Package())); err != nil {
 
 		return err
@@ -504,11 +508,6 @@ func (t *TargetBuilder) Build() error {
 		return err
 	}
 
-	/* Create manifest. */
-	if err := t.createManifest(); err != nil {
-		return err
-	}
-
 	return nil
 }
 
@@ -522,11 +521,11 @@ func (t *TargetBuilder) RelinkLoader() (error, map[string]bool,
 
 	/* fetch symbols from the elf and from the libraries themselves */
 	log.Debugf("Loader packages:")
-	for _, rpkg := range t.LoaderBuilder.sortedRpkgs() {
+	for _, rpkg := range t.LoaderBuilder.SortedRpkgs() {
 		log.Debugf("    * %s", rpkg.Lpkg.Name())
 	}
 	log.Debugf("App packages:")
-	for _, rpkg := range t.AppBuilder.sortedRpkgs() {
+	for _, rpkg := range t.AppBuilder.SortedRpkgs() {
 		log.Debugf("    * %s", rpkg.Lpkg.Name())
 	}
 	err, appLibSym := t.AppBuilder.ExtractSymbolInfo()
@@ -651,137 +650,6 @@ func (t *TargetBuilder) InjectSetting(key string, value string) {
 	t.injectedSettings[key] = value
 }
 
-func readManifest(path string) (*image.ImageManifest, error) {
-	content, err := ioutil.ReadFile(path)
-	if err != nil {
-		return nil, util.ChildNewtError(err)
-	}
-
-	manifest := &image.ImageManifest{}
-	if err := json.Unmarshal(content, &manifest); err != nil {
-		return nil, util.FmtNewtError(
-			"Failure decoding manifest with path \"%s\": %s", err.Error())
-	}
-
-	return manifest, nil
-}
-
-func (t *TargetBuilder) createManifest() error {
-	manifest := &image.ImageManifest{
-		Date: time.Now().Format(time.RFC3339),
-		Name: t.GetTarget().FullName(),
-	}
-
-	rm := image.NewRepoManager()
-	for _, rpkg := range t.AppBuilder.sortedRpkgs() {
-		manifest.Pkgs = append(manifest.Pkgs,
-			rm.GetImageManifestPkg(rpkg.Lpkg))
-	}
-
-	if t.LoaderBuilder != nil {
-		for _, rpkg := range t.LoaderBuilder.sortedRpkgs() {
-			manifest.LoaderPkgs = append(manifest.LoaderPkgs,
-				rm.GetImageManifestPkg(rpkg.Lpkg))
-		}
-	}
-
-	manifest.Repos = rm.AllRepos()
-
-	vars := t.GetTarget().TargetY.AllSettingsAsStrings()
-	keys := make([]string, 0, len(vars))
-	for k := range vars {
-		keys = append(keys, k)
-	}
-	sort.Strings(keys)
-	for _, k := range keys {
-		manifest.TgtVars = append(manifest.TgtVars, k+"="+vars[k])
-	}
-	syscfgKV := t.GetTarget().Package().SyscfgY.GetValStringMapString(
-		"syscfg.vals", nil)
-	if len(syscfgKV) > 0 {
-		tgtSyscfg := fmt.Sprintf("target.syscfg=%s",
-			syscfg.KeyValueToStr(syscfgKV))
-		manifest.TgtVars = append(manifest.TgtVars, tgtSyscfg)
-	}
-
-	c, err := t.AppBuilder.PkgSizes()
-	if err == nil {
-		manifest.PkgSizes = c.Pkgs
-	}
-	if t.LoaderBuilder != nil {
-		c, err = t.LoaderBuilder.PkgSizes()
-		if err == nil {
-			manifest.LoaderPkgSizes = c.Pkgs
-		}
-	}
-	file, err := os.Create(t.AppBuilder.ManifestPath())
-	if err != nil {
-		return util.FmtNewtError("Cannot create manifest file %s: %s",
-			t.AppBuilder.ManifestPath(), err.Error())
-	}
-	defer file.Close()
-
-	buffer, err := json.MarshalIndent(manifest, "", "  ")
-	if err != nil {
-		return util.FmtNewtError("Cannot encode manifest: %s", err.Error())
-	}
-	_, err = file.Write(buffer)
-	if err != nil {
-		return util.FmtNewtError("Cannot write manifest file: %s",
-			err.Error())
-	}
-
-	return nil
-}
-
-// Reads an existing manifest file and augments it with image fields:
-//     * Image version
-//     * App image path
-//     * App image hash
-//     * Loader image path
-//     * Loader image hash
-//     * Build ID
-func (t *TargetBuilder) augmentManifest(
-	appImg *image.Image,
-	loaderImg *image.Image,
-	buildId []byte) error {
-
-	manifest, err := readManifest(t.AppBuilder.ManifestPath())
-	if err != nil {
-		return err
-	}
-
-	manifest.Version = appImg.Version.String()
-	manifest.ImageHash = fmt.Sprintf("%x", appImg.Hash)
-	manifest.Image = filepath.Base(appImg.TargetImg)
-
-	if loaderImg != nil {
-		manifest.Loader = filepath.Base(loaderImg.TargetImg)
-		manifest.LoaderHash = fmt.Sprintf("%x", loaderImg.Hash)
-	}
-
-	manifest.BuildID = fmt.Sprintf("%x", buildId)
-
-	file, err := os.Create(t.AppBuilder.ManifestPath())
-	if err != nil {
-		return util.FmtNewtError("Cannot create manifest file %s: %s",
-			t.AppBuilder.ManifestPath(), err.Error())
-	}
-	defer file.Close()
-
-	buffer, err := json.MarshalIndent(manifest, "", "  ")
-	if err != nil {
-		return util.FmtNewtError("Cannot encode manifest: %s", err.Error())
-	}
-	_, err = file.Write(buffer)
-	if err != nil {
-		return util.FmtNewtError("Cannot write manifest file: %s",
-			err.Error())
-	}
-
-	return nil
-}
-
 // Calculates the size of a single boot trailer.  This is the amount of flash
 // that must be reserved at the end of each image slot.
 func (t *TargetBuilder) bootTrailerSize() int {
@@ -835,7 +703,7 @@ func (t *TargetBuilder) bootTrailerSize() int {
 
 // Calculates the size of the largest image that can be written to each image
 // slot.
-func (t *TargetBuilder) maxImgSizes() []int {
+func (t *TargetBuilder) MaxImgSizes() []int {
 	sz0 := t.bspPkg.FlashMap.Areas[flash.FLASH_AREA_NAME_IMAGE_0].Size
 	sz1 := t.bspPkg.FlashMap.Areas[flash.FLASH_AREA_NAME_IMAGE_1].Size
 	trailerSz := t.bootTrailerSize()
@@ -846,119 +714,6 @@ func (t *TargetBuilder) maxImgSizes() []int {
 	}
 }
 
-// Verifies that each already-built image leaves enough room for a boot trailer
-// a the end of its slot.
-func (t *TargetBuilder) verifyImgSizes(li *image.Image, ai *image.Image) error {
-	maxSizes := t.maxImgSizes()
-
-	errLines := []string{}
-	if li != nil {
-		if overflow := int(li.TotalSize) - maxSizes[0]; overflow > 0 {
-			errLines = append(errLines,
-				fmt.Sprintf("loader overflows slot-0 by %d bytes "+
-					"(image=%d max=%d)",
-					overflow, li.TotalSize, maxSizes[0]))
-		}
-		if overflow := int(ai.TotalSize) - maxSizes[1]; overflow > 0 {
-			errLines = append(errLines,
-				fmt.Sprintf("app overflows slot-1 by %d bytes "+
-					"(image=%d max=%d)",
-					overflow, ai.TotalSize, maxSizes[1]))
-
-		}
-	} else {
-		if overflow := int(ai.TotalSize) - maxSizes[0]; overflow > 0 {
-			errLines = append(errLines,
-				fmt.Sprintf("app overflows slot-0 by %d bytes "+
-					"(image=%d max=%d)",
-					overflow, ai.TotalSize, maxSizes[0]))
-		}
-	}
-
-	if len(errLines) > 0 {
-		if !newtutil.NewtForce {
-			return util.NewNewtError(strings.Join(errLines, "; "))
-		} else {
-			for _, e := range errLines {
-				util.StatusMessage(util.VERBOSITY_QUIET,
-					"* Warning: %s (ignoring due to force flag)\n", e)
-			}
-		}
-	}
-
-	return nil
-}
-
-// @return                      app-image, loader-image, error
-func (t *TargetBuilder) CreateImages(version string,
-	keystrs []string, keyId uint8) (*image.Image, *image.Image, error) {
-
-	if err := t.Build(); err != nil {
-		return nil, nil, err
-	}
-
-	var err error
-	var appImg *image.Image
-	var loaderImg *image.Image
-
-	c, err := t.NewCompiler("", "")
-	if err != nil {
-		return nil, nil, err
-	}
-
-	if t.LoaderBuilder != nil {
-		loaderImg, err = t.LoaderBuilder.CreateImage(version, keystrs, keyId,
-			nil)
-		if err != nil {
-			return nil, nil, err
-		}
-		tgtArea := t.bspPkg.FlashMap.Areas[flash.FLASH_AREA_NAME_IMAGE_0]
-		log.Debugf("Convert %s -> %s at offset 0x%x",
-			t.LoaderBuilder.AppImgPath(),
-			t.LoaderBuilder.AppHexPath(),
-			tgtArea.Offset)
-		err = c.ConvertBinToHex(t.LoaderBuilder.AppImgPath(),
-			t.LoaderBuilder.AppHexPath(), tgtArea.Offset)
-		if err != nil {
-			log.Errorf("Can't convert to hexfile %s\n", err.Error())
-		}
-	}
-
-	appImg, err = t.AppBuilder.CreateImage(version, keystrs, keyId, loaderImg)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	flashTargetArea := ""
-	if t.LoaderBuilder == nil {
-		flashTargetArea = flash.FLASH_AREA_NAME_IMAGE_0
-	} else {
-		flashTargetArea = flash.FLASH_AREA_NAME_IMAGE_1
-	}
-	tgtArea := t.bspPkg.FlashMap.Areas[flashTargetArea]
-	if tgtArea.Name != "" {
-		log.Debugf("Convert %s -> %s at offset 0x%x",
-			t.AppBuilder.AppImgPath(),
-			t.AppBuilder.AppHexPath(),
-			tgtArea.Offset)
-		err = c.ConvertBinToHex(t.AppBuilder.AppImgPath(),
-			t.AppBuilder.AppHexPath(), tgtArea.Offset)
-		if err != nil {
-			log.Errorf("Can't convert to hexfile %s\n", err.Error())
-		}
-	}
-	buildId := image.CreateBuildId(appImg, loaderImg)
-	if err := t.augmentManifest(appImg, loaderImg, buildId); err != nil {
-		return nil, nil, err
-	}
-
-	if err := t.verifyImgSizes(loaderImg, appImg); err != nil {
-		return nil, nil, err
-	}
-
-	return appImg, loaderImg, nil
-}
-
 func (t *TargetBuilder) CreateDepGraph() (DepGraph, error) {
 	if err := t.ensureResolved(); err != nil {
 		return nil, err
diff --git a/newt/cli/build_cmds.go b/newt/cli/build_cmds.go
index d986510c..d7cb8fca 100644
--- a/newt/cli/build_cmds.go
+++ b/newt/cli/build_cmds.go
@@ -27,6 +27,8 @@ import (
 
 	"github.com/spf13/cobra"
 	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/imgprod"
+	"mynewt.apache.org/newt/newt/manifest"
 	"mynewt.apache.org/newt/newt/pkg"
 	"mynewt.apache.org/newt/newt/project"
 	"mynewt.apache.org/newt/newt/target"
@@ -160,6 +162,15 @@ func buildRunCmd(cmd *cobra.Command, args []string, printShellCmds bool, execute
 			NewtUsage(nil, err)
 		}
 
+		// Produce bare "imageless" manifest.
+		mopts, err := manifest.OptsForNonImage(b)
+		if err != nil {
+			NewtUsage(nil, err)
+		}
+		if err := imgprod.ProduceManifest(mopts); err != nil {
+			NewtUsage(nil, err)
+		}
+
 		util.StatusMessage(util.VERBOSITY_DEFAULT,
 			"Target successfully built: %s\n", t.Name())
 	}
diff --git a/newt/cli/image_cmds.go b/newt/cli/image_cmds.go
index 73459ed4..cf742ac1 100644
--- a/newt/cli/image_cmds.go
+++ b/newt/cli/image_cmds.go
@@ -23,38 +23,50 @@ import (
 	"strconv"
 
 	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/artifact/image"
 	"mynewt.apache.org/newt/newt/builder"
-	"mynewt.apache.org/newt/newt/image"
+	"mynewt.apache.org/newt/newt/imgprod"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/util"
 )
 
 var useV1 bool
 var useV2 bool
+var encKeyFilename string
 
-func parseKeyArgs(args []string) ([]string, uint8, error) {
+// @return                      keys, key ID, error
+func parseKeyArgs(args []string) ([]image.ImageSigKey, uint8, error) {
 	if len(args) == 0 {
 		return nil, 0, nil
 	}
 
-	if len(args) == 1 {
-		return args, 0, nil
-	}
+	var keyId uint8
+	var keyFilenames []string
 
-	if image.UseV1 {
-		keyId64, err := strconv.ParseUint(args[1], 10, 8)
+	if len(args) == 1 {
+		keyFilenames = append(keyFilenames, args[0])
+	} else if useV1 {
+		keyIdUint, err := strconv.ParseUint(args[1], 10, 8)
 		if err != nil {
 			return nil, 0, util.NewNewtError("Key ID must be between 0-255")
 		}
-		return args[:1], uint8(keyId64), nil
+		keyId = uint8(keyIdUint)
+		keyFilenames = args[:1]
+	} else {
+		keyId = 0
+		keyFilenames = args
 	}
 
-	return args, 0, nil
+	keys, err := image.ReadKeys(keyFilenames)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	return keys, keyId, nil
 }
 
 func createImageRunCmd(cmd *cobra.Command, args []string) {
-	var keyId uint8
-
 	if len(args) < 2 {
 		NewtUsage(cmd, util.NewNewtError("Must specify target and version"))
 	}
@@ -62,10 +74,9 @@ func createImageRunCmd(cmd *cobra.Command, args []string) {
 	if useV1 && useV2 {
 		NewtUsage(cmd, util.NewNewtError("Either -1, or -2, but not both"))
 	}
-	if useV2 {
-		image.UseV1 = false
-	} else {
-		image.UseV1 = true
+
+	if !useV2 {
+		useV1 = true
 	}
 
 	TryGetProject()
@@ -76,65 +87,30 @@ func createImageRunCmd(cmd *cobra.Command, args []string) {
 		NewtUsage(cmd, util.NewNewtError("Invalid target name: "+targetName))
 	}
 
-	version := args[1]
+	ver, err := image.ParseVersion(args[1])
+	if err != nil {
+		NewtUsage(cmd, err)
+	}
 
 	b, err := builder.NewTargetBuilder(t)
 	if err != nil {
 		NewtUsage(nil, err)
 	}
 
-	keystrs, keyId, err := parseKeyArgs(args[2:])
+	keys, _, err := parseKeyArgs(args[2:])
 	if err != nil {
 		NewtUsage(cmd, err)
 	}
 
-	if _, _, err := b.CreateImages(version, keystrs, keyId); err != nil {
+	if err := b.Build(); err != nil {
 		NewtUsage(nil, err)
 	}
-}
 
-func resignImageRunCmd(cmd *cobra.Command, args []string) {
-	var keyId uint8
-	var keystr string
-
-	if len(args) < 1 {
-		NewtUsage(cmd, util.NewNewtError("Must specify image to re-sign."))
-	}
-
-	if useV1 && useV2 {
-		NewtUsage(cmd, util.NewNewtError("Either -1, or -2, but not both"))
-	}
-	if useV2 {
-		image.UseV1 = false
+	if useV1 {
+		err = imgprod.ProduceAllV1(b, ver, keys, encKeyFilename)
 	} else {
-		image.UseV1 = true
-	}
-
-	imgName := args[0]
-	img, err := image.OldImage(imgName)
-	if err != nil {
-		NewtUsage(nil, err)
-		return
-	}
-
-	if len(args) > 1 {
-		if len(args) > 2 {
-			keyId64, err := strconv.ParseUint(args[2], 10, 8)
-			if err != nil {
-				NewtUsage(cmd,
-					util.NewNewtError("Key ID must be between 0-255"))
-			}
-			keyId = uint8(keyId64)
-		}
-		keystr = args[1]
-		err = img.SetKeyV1(keystr, keyId)
-		if err != nil {
-			NewtUsage(nil, err)
-			return
-		}
+		err = imgprod.ProduceAll(b, ver, keys, encKeyFilename)
 	}
-
-	err = img.ReSign()
 	if err != nil {
 		NewtUsage(nil, err)
 	}
@@ -185,44 +161,23 @@ func AddImageCommands(cmd *cobra.Command) {
 		"1", "1", false, "Use old image header format")
 	createImageCmd.PersistentFlags().BoolVarP(&useV2,
 		"2", "2", false, "Use new image header format")
-	createImageCmd.PersistentFlags().StringVarP(&image.PubKeyFile,
+	createImageCmd.PersistentFlags().StringVarP(&encKeyFilename,
 		"encrypt", "e", "", "Encrypt image using this public key")
 
 	cmd.AddCommand(createImageCmd)
 	AddTabCompleteFn(createImageCmd, targetList)
 
-	resignImageHelpText := "Sign/Re-sign an existing image file with the specified signing key.\nIf a signing key is not specified, the signing key in the current image\nis stripped.  "
-	resignImageHelpText += "A image header will be recreated!\n"
-	resignImageHelpText += "\nWarning: The image hash will change if you change key-id "
-	resignImageHelpText += "or the type of key used for signing.\n"
-	resignImageHelpText += "Default image format is version 1.\n"
-	resignImageHelpText += "RSA signature format by default for ver 1 image is PKCSv1.5\n"
-	resignImageHelpText += "RSA signature format for ver 2 image is RSA-PSS\n"
-
-	resignImageHelpEx := "  newt resign-image my_target1.img private.pem\n"
-	resignImageHelpEx += "  newt resign-image my_target1.img private.pem 5\n"
+	resignImageHelpText :=
+		"This command is obsolete; use the `larva` tool to resign images."
 
 	resignImageCmd := &cobra.Command{
-		Use:     "resign-image <image-file> [signing-key [key-id]]",
-		Short:   "Re-sign an image.",
-		Long:    resignImageHelpText,
-		Example: resignImageHelpEx,
-		Run:     resignImageRunCmd,
+		Use:   "resign-image",
+		Short: "Obsolete",
+		Long:  resignImageHelpText,
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Help()
+		},
 	}
 
-	resignImageCmd.PersistentFlags().BoolVarP(&newtutil.NewtForce,
-		"force", "f", false,
-		"Ignore flash overflow errors during image creation")
-	resignImageCmd.PersistentFlags().BoolVar(&image.UseRsaPss,
-		"rsa-pss", false,
-		"Use RSA-PSS instead of PKCS#1 v1.5 for RSA sig. "+
-			"Meaningful for version 1 image format.")
-	resignImageCmd.PersistentFlags().BoolVarP(&useV1,
-		"1", "1", false, "Use old image header format")
-	resignImageCmd.PersistentFlags().BoolVarP(&useV2,
-		"2", "2", false, "Use new image header format")
-	resignImageCmd.PersistentFlags().StringVarP(&image.PubKeyFile,
-		"encrypt", "e", "", "Encrypt image using this public key")
-
 	cmd.AddCommand(resignImageCmd)
 }
diff --git a/newt/cli/mfg_cmds.go b/newt/cli/mfg_cmds.go
index 1e35d4ee..78d5eb9a 100644
--- a/newt/cli/mfg_cmds.go
+++ b/newt/cli/mfg_cmds.go
@@ -20,9 +20,11 @@
 package cli
 
 import (
+	"fmt"
+
 	"github.com/spf13/cobra"
 
-	"mynewt.apache.org/newt/newt/image"
+	"mynewt.apache.org/newt/artifact/image"
 	"mynewt.apache.org/newt/newt/mfg"
 	"mynewt.apache.org/newt/newt/pkg"
 	"mynewt.apache.org/newt/util"
@@ -50,31 +52,34 @@ func ResolveMfgPkg(pkgName string) (*pkg.LocalPackage, error) {
 	return lpkg, nil
 }
 
-func mfgCreate(mi *mfg.MfgImage) {
-	pathStr := ""
-	for _, path := range mi.FromPaths() {
-		pathStr += "    * " + path + "\n"
+func mfgCreate(me mfg.MfgEmitter) {
+	srcPaths, dstPaths, err := me.Emit()
+	if err != nil {
+		NewtUsage(nil, err)
 	}
 
-	util.StatusMessage(util.VERBOSITY_DEFAULT,
-		"Creating a manufacturing image from the following files:\n%s\n",
-		pathStr)
+	srcStr := ""
+	dstStr := ""
 
-	outputPaths, err := mi.CreateMfgImage()
-	if err != nil {
-		NewtUsage(nil, err)
+	for _, p := range srcPaths {
+		srcStr += fmt.Sprintf("    %s\n", p)
 	}
 
-	pathStr = ""
-	for _, path := range outputPaths {
-		pathStr += "    * " + path + "\n"
+	for _, p := range dstPaths {
+		dstStr += fmt.Sprintf("    %s\n", p)
 	}
+
 	util.StatusMessage(util.VERBOSITY_DEFAULT,
-		"Generated the following files:\n%s", pathStr)
+		"Creating a manufacturing image from the following files:\n%s\n",
+		srcStr)
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"Generated the following files:\n%s\n",
+		dstStr)
 }
 
-func mfgLoad(mi *mfg.MfgImage) {
-	binPath, err := mi.Upload()
+func mfgLoad(basePkg *pkg.LocalPackage) {
+	binPath, err := mfg.Upload(basePkg)
 	if err != nil {
 		NewtUsage(nil, err)
 	}
@@ -101,13 +106,12 @@ func mfgCreateRunCmd(cmd *cobra.Command, args []string) {
 		NewtUsage(cmd, err)
 	}
 
-	mi, err := mfg.Load(lpkg)
+	me, err := mfg.LoadMfgEmitter(lpkg, ver)
 	if err != nil {
 		NewtUsage(nil, err)
 	}
 
-	mi.SetVersion(ver)
-	mfgCreate(mi)
+	mfgCreate(me)
 }
 
 func mfgLoadRunCmd(cmd *cobra.Command, args []string) {
@@ -121,12 +125,7 @@ func mfgLoadRunCmd(cmd *cobra.Command, args []string) {
 		NewtUsage(cmd, err)
 	}
 
-	mi, err := mfg.Load(lpkg)
-	if err != nil {
-		NewtUsage(nil, err)
-	}
-
-	mfgLoad(mi)
+	mfgLoad(lpkg)
 }
 
 func mfgDeployRunCmd(cmd *cobra.Command, args []string) {
@@ -149,15 +148,14 @@ func mfgDeployRunCmd(cmd *cobra.Command, args []string) {
 		}
 	}
 
-	mi, err := mfg.Load(lpkg)
+	me, err := mfg.LoadMfgEmitter(lpkg, ver)
 	if err != nil {
 		NewtUsage(nil, err)
 	}
 
-	mi.SetVersion(ver)
-	mfgCreate(mi)
+	mfgCreate(me)
 
-	mfgLoad(mi)
+	mfgLoad(lpkg)
 }
 
 func AddMfgCommands(cmd *cobra.Command) {
diff --git a/newt/cli/run_cmds.go b/newt/cli/run_cmds.go
index d3f3ed90..55b0b9be 100644
--- a/newt/cli/run_cmds.go
+++ b/newt/cli/run_cmds.go
@@ -23,7 +23,8 @@ import (
 	"fmt"
 	"github.com/spf13/cobra"
 
-	"mynewt.apache.org/newt/newt/image"
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/newt/imgprod"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/parse"
 	"mynewt.apache.org/newt/util"
@@ -37,10 +38,8 @@ func runRunCmd(cmd *cobra.Command, args []string) {
 	if useV1 && useV2 {
 		NewtUsage(cmd, util.NewNewtError("Either -1, or -2, but not both"))
 	}
-	if useV2 {
-		image.UseV1 = false
-	} else {
-		image.UseV1 = true
+	if !useV2 {
+		useV1 = true
 	}
 
 	TryGetProject()
@@ -60,9 +59,9 @@ func runRunCmd(cmd *cobra.Command, args []string) {
 			NewtUsage(nil, err)
 		}
 	} else {
-		var version string = ""
+		var verStr string
 		if len(args) > 1 {
-			version = args[1]
+			verStr = args[1]
 		} else {
 			// If user did not provide version number and the target is not a
 			// bootloader and doesn't run in the simulator, then ask the user
@@ -78,29 +77,38 @@ func runRunCmd(cmd *cobra.Command, args []string) {
 			if !parse.ValueIsTrue(settings["BOOT_LOADER"]) &&
 				!parse.ValueIsTrue(settings["BSP_SIMULATED"]) {
 
-				version = "0"
+				verStr = "0"
 				fmt.Println("Enter image version(default 0):")
-				fmt.Scanf("%s\n", &version)
+				fmt.Scanf("%s\n", &verStr)
 			}
 		}
+
 		if err := b.Build(); err != nil {
 			NewtUsage(nil, err)
 		}
 
-		if len(version) > 0 {
-			var keystrs []string
-			var keyId uint8
+		if len(verStr) > 0 {
+			ver, err := image.ParseVersion(verStr)
+			if err != nil {
+				NewtUsage(cmd, err)
+			}
+
+			var keys []image.ImageSigKey
 
 			if len(args) > 2 {
-				keystrs, keyId, err = parseKeyArgs(args[2:])
+				keys, _, err = parseKeyArgs(args[2:])
 				if err != nil {
 					NewtUsage(cmd, err)
 				}
 			}
 
-			_, _, err = b.CreateImages(version, keystrs, keyId)
+			if useV1 {
+				err = imgprod.ProduceAllV1(b, ver, keys, "")
+			} else {
+				err = imgprod.ProduceAll(b, ver, keys, "")
+			}
 			if err != nil {
-				NewtUsage(cmd, err)
+				NewtUsage(nil, err)
 			}
 		}
 
diff --git a/newt/flash/flash.go b/newt/flashmap/flashmap.go
similarity index 64%
rename from newt/flash/flash.go
rename to newt/flashmap/flashmap.go
index 97114c51..d044f420 100644
--- a/newt/flash/flash.go
+++ b/newt/flashmap/flashmap.go
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package flash
+package flashmap
 
 import (
 	"bytes"
@@ -26,30 +26,16 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"sort"
 	"strings"
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/spf13/cast"
 
+	"mynewt.apache.org/newt/artifact/flash"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/util"
 )
 
-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"
-
-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,
-}
-
-const AREA_USER_ID_MIN = 16
-
 const HEADER_PATH = "sysflash/sysflash.h"
 const C_VAR_NAME = "sysflash_map_dflt"
 const C_VAR_COMMENT = `/**
@@ -59,24 +45,16 @@ const C_VAR_COMMENT = `/**
  */
 `
 
-type FlashArea struct {
-	Name   string
-	Id     int
-	Device int
-	Offset int
-	Size   int
-}
-
 type FlashMap struct {
-	Areas       map[string]FlashArea
-	Overlaps    [][]FlashArea
-	IdConflicts [][]FlashArea
+	Areas       map[string]flash.FlashArea
+	Overlaps    [][]flash.FlashArea
+	IdConflicts [][]flash.FlashArea
 }
 
 func newFlashMap() FlashMap {
 	return FlashMap{
-		Areas:    map[string]FlashArea{},
-		Overlaps: [][]FlashArea{},
+		Areas:    map[string]flash.FlashArea{},
+		Overlaps: [][]flash.FlashArea{},
 	}
 }
 
@@ -104,9 +82,9 @@ func parseSize(val string) (int, error) {
 }
 
 func parseFlashArea(
-	name string, ymlFields map[string]interface{}) (FlashArea, error) {
+	name string, ymlFields map[string]interface{}) (flash.FlashArea, error) {
 
-	area := FlashArea{
+	area := flash.FlashArea{
 		Name: name,
 	}
 
@@ -116,7 +94,7 @@ func parseFlashArea(
 	sizePresent := false
 
 	var isSystem bool
-	area.Id, isSystem = SYSTEM_AREA_NAME_ID_MAP[name]
+	area.Id, isSystem = flash.SYSTEM_AREA_NAME_ID_MAP[name]
 
 	var err error
 
@@ -132,7 +110,7 @@ func parseFlashArea(
 			if err != nil {
 				return area, flashAreaErr(name, "invalid user id: %s", v)
 			}
-			area.Id = userId + AREA_USER_ID_MIN
+			area.Id = userId + flash.AREA_USER_ID_MIN
 			idPresent = true
 
 		case "device":
@@ -179,8 +157,8 @@ func parseFlashArea(
 	return area, nil
 }
 
-func (flashMap FlashMap) unSortedAreas() []FlashArea {
-	areas := make([]FlashArea, 0, len(flashMap.Areas))
+func (flashMap FlashMap) unSortedAreas() []flash.FlashArea {
+	areas := make([]flash.FlashArea, 0, len(flashMap.Areas))
 	for _, area := range flashMap.Areas {
 		areas = append(areas, area)
 	}
@@ -188,42 +166,14 @@ func (flashMap FlashMap) unSortedAreas() []FlashArea {
 	return areas
 }
 
-func (flashMap FlashMap) SortedAreas() []FlashArea {
-	idMap := make(map[int]FlashArea, len(flashMap.Areas))
-	ids := make([]int, 0, len(flashMap.Areas))
-	for _, area := range flashMap.Areas {
-		idMap[area.Id] = area
-		ids = append(ids, area.Id)
-	}
-	sort.Ints(ids)
-
-	areas := make([]FlashArea, len(ids))
-	for i, id := range ids {
-		areas[i] = idMap[id]
-	}
-
-	return areas
-}
-
-func (flashMap FlashMap) DeviceIds() []int {
-	deviceMap := map[int]struct{}{}
-
-	for _, area := range flashMap.Areas {
-		deviceMap[area.Device] = struct{}{}
-	}
-
-	devices := make([]int, 0, len(deviceMap))
-	for device, _ := range deviceMap {
-		devices = append(devices, device)
-	}
-	sort.Ints(devices)
-
-	return devices
+func (flashMap FlashMap) SortedAreas() []flash.FlashArea {
+	areas := flashMap.unSortedAreas()
+	return flash.SortFlashAreasById(areas)
 }
 
-func areasDistinct(a FlashArea, b FlashArea) bool {
-	var lo FlashArea
-	var hi FlashArea
+func areasDistinct(a flash.FlashArea, b flash.FlashArea) bool {
+	var lo flash.FlashArea
+	var hi flash.FlashArea
 
 	if a.Offset < b.Offset {
 		lo = a
@@ -237,50 +187,12 @@ func areasDistinct(a FlashArea, b FlashArea) bool {
 }
 
 func (flashMap *FlashMap) detectOverlaps() {
-	flashMap.Overlaps = [][]FlashArea{}
-
-	// Convert the map to a slice.
-	areas := flashMap.unSortedAreas()
-
-	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) {
-				flashMap.Overlaps = append(
-					flashMap.Overlaps, []FlashArea{iarea, jarea})
-			}
-
-			if iarea.Id == jarea.Id {
-				flashMap.IdConflicts = append(
-					flashMap.IdConflicts, []FlashArea{iarea, jarea})
-			}
-		}
-	}
+	flashMap.Overlaps, flashMap.IdConflicts =
+		flash.DetectErrors(flashMap.unSortedAreas())
 }
 
 func (flashMap FlashMap) ErrorText() string {
-	str := ""
-
-	if len(flashMap.IdConflicts) > 0 {
-		str += "Conflicting flash area IDs detected:\n"
-
-		for _, pair := range flashMap.IdConflicts {
-			str += fmt.Sprintf("    (%d) %s =/= %s\n",
-				pair[0].Id-AREA_USER_ID_MIN, pair[0].Name, pair[1].Name)
-		}
-	}
-
-	if len(flashMap.Overlaps) > 0 {
-		str += "Overlapping flash areas detected:\n"
-
-		for _, pair := range flashMap.Overlaps {
-			str += fmt.Sprintf("    %s =/= %s\n", pair[0].Name, pair[1].Name)
-		}
-	}
-
-	return str
+	return flash.ErrorText(flashMap.Overlaps, flashMap.IdConflicts)
 }
 
 func Read(ymlFlashMap map[string]interface{}) (FlashMap, error) {
@@ -312,16 +224,16 @@ func Read(ymlFlashMap map[string]interface{}) (FlashMap, error) {
 	return flashMap, nil
 }
 
-func (flashMap FlashMap) varDecl() string {
+func flashMapVarDecl(fm FlashMap) string {
 	return fmt.Sprintf("const struct flash_area %s[%d]", C_VAR_NAME,
-		len(flashMap.Areas))
+		len(fm.Areas))
 }
 
-func (area FlashArea) writeHeader(w io.Writer) {
+func writeFlashAreaHeader(w io.Writer, area flash.FlashArea) {
 	fmt.Fprintf(w, "#define %-40s %d\n", area.Name, area.Id)
 }
 
-func (flashMap FlashMap) writeHeader(w io.Writer) {
+func writeFlashMapHeader(w io.Writer, fm FlashMap) {
 	fmt.Fprintf(w, newtutil.GeneratedPreamble())
 
 	fmt.Fprintf(w, "#ifndef H_MYNEWT_SYSFLASH_\n")
@@ -330,11 +242,11 @@ func (flashMap FlashMap) writeHeader(w io.Writer) {
 	fmt.Fprintf(w, "#include \"flash_map/flash_map.h\"\n")
 	fmt.Fprintf(w, "\n")
 	fmt.Fprintf(w, "%s", C_VAR_COMMENT)
-	fmt.Fprintf(w, "extern %s;\n", flashMap.varDecl())
+	fmt.Fprintf(w, "extern %s;\n", flashMapVarDecl(fm))
 	fmt.Fprintf(w, "\n")
 
-	for _, area := range flashMap.SortedAreas() {
-		area.writeHeader(w)
+	for _, area := range fm.SortedAreas() {
+		writeFlashAreaHeader(w, area)
 	}
 
 	fmt.Fprintf(w, "\n#endif\n")
@@ -348,7 +260,7 @@ func sizeComment(size int) string {
 	return fmt.Sprintf(" /* %d kB */", size/1024)
 }
 
-func (area FlashArea) writeSrc(w io.Writer) {
+func writeFlashAreaSrc(w io.Writer, area flash.FlashArea) {
 	fmt.Fprintf(w, "    /* %s */\n", area.Name)
 	fmt.Fprintf(w, "    {\n")
 	fmt.Fprintf(w, "        .fa_id = %d,\n", area.Id)
@@ -359,23 +271,23 @@ func (area FlashArea) writeSrc(w io.Writer) {
 	fmt.Fprintf(w, "    },\n")
 }
 
-func (flashMap FlashMap) writeSrc(w io.Writer) {
+func writeFlashMapSrc(w io.Writer, fm FlashMap) {
 	fmt.Fprintf(w, newtutil.GeneratedPreamble())
 
 	fmt.Fprintf(w, "#include \"%s\"\n", HEADER_PATH)
 	fmt.Fprintf(w, "\n")
 	fmt.Fprintf(w, "%s", C_VAR_COMMENT)
-	fmt.Fprintf(w, "%s = {", flashMap.varDecl())
+	fmt.Fprintf(w, "%s = {", flashMapVarDecl(fm))
 
-	for _, area := range flashMap.SortedAreas() {
+	for _, area := range fm.SortedAreas() {
 		fmt.Fprintf(w, "\n")
-		area.writeSrc(w)
+		writeFlashAreaSrc(w, area)
 	}
 
 	fmt.Fprintf(w, "};\n")
 }
 
-func (flashMap FlashMap) ensureWrittenGen(path string, contents []byte) error {
+func ensureFlashMapWrittenGen(path string, contents []byte) error {
 	writeReqd, err := util.FileContentsChanged(path, contents)
 	if err != nil {
 		return err
@@ -398,12 +310,15 @@ func (flashMap FlashMap) ensureWrittenGen(path string, contents []byte) error {
 	return nil
 }
 
-func (flashMap FlashMap) EnsureWritten(
-	srcDir string, includeDir string, targetName string) error {
+func EnsureFlashMapWritten(
+	fm FlashMap,
+	srcDir string,
+	includeDir string,
+	targetName string) error {
 
 	buf := bytes.Buffer{}
-	flashMap.writeSrc(&buf)
-	if err := flashMap.ensureWrittenGen(
+	writeFlashMapSrc(&buf, fm)
+	if err := ensureFlashMapWrittenGen(
 		fmt.Sprintf("%s/%s-sysflash.c", srcDir, targetName),
 		buf.Bytes()); err != nil {
 
@@ -411,8 +326,8 @@ func (flashMap FlashMap) EnsureWritten(
 	}
 
 	buf = bytes.Buffer{}
-	flashMap.writeHeader(&buf)
-	if err := flashMap.ensureWrittenGen(
+	writeFlashMapHeader(&buf, fm)
+	if err := ensureFlashMapWrittenGen(
 		includeDir+"/"+HEADER_PATH, buf.Bytes()); err != nil {
 		return err
 	}
diff --git a/newt/image/image.go b/newt/image/image.go
deleted file mode 100644
index 278804e0..00000000
--- a/newt/image/image.go
+++ /dev/null
@@ -1,1463 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package image
-
-import (
-	"bytes"
-	"crypto"
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/ecdsa"
-	"crypto/rand"
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/x509"
-	"encoding/asn1"
-	"encoding/base64"
-	"encoding/binary"
-	"encoding/hex"
-	"encoding/pem"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"math/big"
-	"os"
-	"sort"
-	"strconv"
-	"strings"
-
-	keywrap "github.com/NickBall/go-aes-key-wrap"
-	log "github.com/Sirupsen/logrus"
-
-	"mynewt.apache.org/newt/newt/pkg"
-	"mynewt.apache.org/newt/util"
-)
-
-// Set this to enable RSA-PSS for RSA signatures, instead of PKCS#1
-// v1.5.  Eventually, this should be the default.
-var UseRsaPss = false
-
-// Use old image format
-var UseV1 = false
-
-// Public key file to encrypt image
-var PubKeyFile = ""
-
-type ImageVersion struct {
-	Major    uint8
-	Minor    uint8
-	Rev      uint16
-	BuildNum uint32
-}
-
-type ImageKey struct {
-	// Only one of these members is non-nil.
-	Rsa *rsa.PrivateKey
-	Ec  *ecdsa.PrivateKey
-}
-
-type Image struct {
-	SourceBin  string
-	SourceImg  string
-	TargetImg  string
-	Version    ImageVersion
-	Keys       []ImageKey
-	KeyId      uint8
-	Hash       []byte
-	SrcSkip    uint // Number of bytes to skip from the source image.
-	HeaderSize uint // If non-zero pad out the header to this size.
-	TotalSize  uint // Total size, in bytes, of the generated .img file.
-}
-
-type ImageHdrV1 struct {
-	Magic uint32
-	TlvSz uint16
-	KeyId uint8
-	Pad1  uint8
-	HdrSz uint16
-	Pad2  uint16
-	ImgSz uint32
-	Flags uint32
-	Vers  ImageVersion
-	Pad3  uint32
-}
-
-type ImageHdr struct {
-	Magic uint32
-	Pad1  uint32
-	HdrSz uint16
-	Pad2  uint16
-	ImgSz uint32
-	Flags uint32
-	Vers  ImageVersion
-	Pad3  uint32
-}
-
-type ImageTlvInfo struct {
-	Magic     uint16
-	TlvTotLen uint16
-}
-
-type ImageTrailerTlv struct {
-	Type uint8
-	Pad  uint8
-	Len  uint16
-}
-
-const (
-	IMAGEv1_MAGIC       = 0x96f3b83c /* Image header magic */
-	IMAGE_MAGIC         = 0x96f3b83d /* Image header magic */
-	IMAGE_TRAILER_MAGIC = 0x6907     /* Image tlv info magic */
-)
-
-const (
-	IMAGE_HEADER_SIZE = 32
-)
-
-/*
- * Image header flags.
- */
-const (
-	IMAGEv1_F_PIC                      = 0x00000001
-	IMAGEv1_F_SHA256                   = 0x00000002 /* Image contains hash TLV */
-	IMAGEv1_F_PKCS15_RSA2048_SHA256    = 0x00000004 /* PKCS15 w/RSA2048 and SHA256 */
-	IMAGEv1_F_ECDSA224_SHA256          = 0x00000008 /* ECDSA224 over SHA256 */
-	IMAGEv1_F_NON_BOOTABLE             = 0x00000010 /* non bootable image */
-	IMAGEv1_F_ECDSA256_SHA256          = 0x00000020 /* ECDSA256 over SHA256 */
-	IMAGEv1_F_PKCS1_PSS_RSA2048_SHA256 = 0x00000040 /* RSA-PSS w/RSA2048 and SHA256 */
-
-	IMAGE_F_PIC          = 0x00000001
-	IMAGE_F_NON_BOOTABLE = 0x00000002 /* non bootable image */
-	IMAGE_F_ENCRYPTED    = 0x00000004 /* encrypted image */
-)
-
-/*
- * Image trailer TLV types.
- */
-const (
-	IMAGEv1_TLV_SHA256   = 1
-	IMAGEv1_TLV_RSA2048  = 2
-	IMAGEv1_TLV_ECDSA224 = 3
-	IMAGEv1_TLV_ECDSA256 = 4
-
-	IMAGE_TLV_KEYHASH  = 0x01
-	IMAGE_TLV_SHA256   = 0x10
-	IMAGE_TLV_RSA2048  = 0x20
-	IMAGE_TLV_ECDSA224 = 0x21
-	IMAGE_TLV_ECDSA256 = 0x22
-	IMAGE_TLV_ENC_RSA  = 0x30
-	IMAGE_TLV_ENC_KEK  = 0x31
-)
-
-/*
- * Data that's going to go to build manifest file
- */
-type ImageManifestSizeArea struct {
-	Name string `json:"name"`
-	Size uint32 `json:"size"`
-}
-
-type ImageManifestSizeSym struct {
-	Name  string                   `json:"name"`
-	Areas []*ImageManifestSizeArea `json:"areas"`
-}
-
-type ImageManifestSizeFile struct {
-	Name string                  `json:"name"`
-	Syms []*ImageManifestSizeSym `json:"sym"`
-}
-
-type ImageManifestSizePkg struct {
-	Name  string                   `json:"name"`
-	Files []*ImageManifestSizeFile `json:"files"`
-}
-
-type ImageManifestSizeCollector struct {
-	Pkgs []*ImageManifestSizePkg
-}
-
-type ImageManifest struct {
-	Name       string              `json:"name"`
-	Date       string              `json:"build_time"`
-	Version    string              `json:"build_version"`
-	BuildID    string              `json:"id"`
-	Image      string              `json:"image"`
-	ImageHash  string              `json:"image_hash"`
-	Loader     string              `json:"loader"`
-	LoaderHash string              `json:"loader_hash"`
-	Pkgs       []*ImageManifestPkg `json:"pkgs"`
-	LoaderPkgs []*ImageManifestPkg `json:"loader_pkgs,omitempty"`
-	TgtVars    []string            `json:"target"`
-	Repos      []ImageManifestRepo `json:"repos"`
-
-	PkgSizes       []*ImageManifestSizePkg `json:"pkgsz"`
-	LoaderPkgSizes []*ImageManifestSizePkg `json:"loader_pkgsz,omitempty"`
-}
-
-type ImageManifestPkg struct {
-	Name string `json:"name"`
-	Repo string `json:"repo"`
-}
-
-type ImageManifestRepo struct {
-	Name   string `json:"name"`
-	Commit string `json:"commit"`
-	Dirty  bool   `json:"dirty,omitempty"`
-	URL    string `json:"url,omitempty"`
-}
-
-type RepoManager struct {
-	repos map[string]ImageManifestRepo
-}
-
-type ECDSASig struct {
-	R *big.Int
-	S *big.Int
-}
-
-func ParseVersion(versStr string) (ImageVersion, error) {
-	var err error
-	var major uint64
-	var minor uint64
-	var rev uint64
-	var buildNum uint64
-	var ver ImageVersion
-
-	components := strings.Split(versStr, ".")
-	major, err = strconv.ParseUint(components[0], 10, 8)
-	if err != nil {
-		return ver, util.FmtNewtError("Invalid version string %s", versStr)
-	}
-	if len(components) > 1 {
-		minor, err = strconv.ParseUint(components[1], 10, 8)
-		if err != nil {
-			return ver, util.FmtNewtError("Invalid version string %s", versStr)
-		}
-	}
-	if len(components) > 2 {
-		rev, err = strconv.ParseUint(components[2], 10, 16)
-		if err != nil {
-			return ver, util.FmtNewtError("Invalid version string %s", versStr)
-		}
-	}
-	if len(components) > 3 {
-		buildNum, err = strconv.ParseUint(components[3], 10, 32)
-		if err != nil {
-			return ver, util.FmtNewtError("Invalid version string %s", versStr)
-		}
-	}
-
-	ver.Major = uint8(major)
-	ver.Minor = uint8(minor)
-	ver.Rev = uint16(rev)
-	ver.BuildNum = uint32(buildNum)
-	return ver, nil
-}
-
-func (ver ImageVersion) String() string {
-	return fmt.Sprintf("%d.%d.%d.%d",
-		ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
-}
-
-func NewImage(srcBinPath string, dstImgPath string) (*Image, error) {
-	image := &Image{}
-
-	image.SourceBin = srcBinPath
-	image.TargetImg = dstImgPath
-	return image, nil
-}
-
-func OldImage(imgPath string) (*Image, error) {
-	image := &Image{}
-
-	image.SourceImg = imgPath
-	return image, nil
-}
-
-func (image *Image) SetVersion(versStr string) error {
-	ver, err := ParseVersion(versStr)
-	if err != nil {
-		return err
-	}
-
-	log.Debugf("Assigning version number %d.%d.%d.%d\n",
-		ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
-
-	image.Version = ver
-
-	buf := new(bytes.Buffer)
-	err = binary.Write(buf, binary.LittleEndian, image.Version)
-	if err != nil {
-		fmt.Printf("Bombing out\n")
-		return nil
-	}
-
-	return nil
-}
-
-func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
-	var privKey interface{}
-	var err error
-
-	block, data := pem.Decode(keyBytes)
-	if block != nil && block.Type == "EC PARAMETERS" {
-		/*
-		 * Openssl prepends an EC PARAMETERS block before the
-		 * key itself.  If we see this first, just skip it,
-		 * and go on to the data block.
-		 */
-		block, _ = pem.Decode(data)
-	}
-	if block != nil && block.Type == "RSA PRIVATE KEY" {
-		/*
-		 * ParsePKCS1PrivateKey returns an RSA private key from its ASN.1
-		 * PKCS#1 DER encoded form.
-		 */
-		privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
-		if err != nil {
-			return nil, util.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) (ImageKey, error) {
-	key := ImageKey{}
-
-	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 (key *ImageKey) 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 (image *Image) SetKeys(filenames []string) error {
-	for _, filename := range filenames {
-		key, err := readKey(filename)
-		if err != nil {
-			return err
-		}
-
-		image.Keys = append(image.Keys, key)
-	}
-
-	return nil
-}
-
-func (image *Image) SetKeyV1(filename string, keyId uint8) error {
-	key, err := readKey(filename)
-	if err != nil {
-		return err
-	}
-
-	image.KeyId = keyId
-	image.Keys = []ImageKey{key}
-
-	return nil
-}
-
-func (key *ImageKey) 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 *ImageKey) 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 *ImageKey) 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 *ImageKey) sigTlvType() uint8 {
-	key.assertValid()
-
-	if UseV1 {
-		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
-			}
-		}
-	} else {
-		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 (image *Image) ReSign() error {
-	srcImg, err := os.Open(image.SourceImg)
-	if err != nil {
-		return util.FmtNewtError("Can't open image file %s: %s",
-			image.SourceImg, err.Error())
-	}
-
-	srcInfo, err := srcImg.Stat()
-	if err != nil {
-		return util.FmtNewtError("Can't stat image file %s: %s",
-			image.SourceImg, err.Error())
-	}
-
-	var hdr1 ImageHdrV1
-	var hdr2 ImageHdr
-	var hdrSz uint16
-	var imgSz uint32
-
-	err = binary.Read(srcImg, binary.LittleEndian, &hdr1)
-	if err == nil {
-		srcImg.Seek(0, 0)
-		err = binary.Read(srcImg, binary.LittleEndian, &hdr2)
-	}
-	if err != nil {
-		return util.FmtNewtError("Failing to access image %s: %s",
-			image.SourceImg, err.Error())
-	}
-	if hdr1.Magic == IMAGEv1_MAGIC {
-		if uint32(srcInfo.Size()) !=
-			uint32(hdr1.HdrSz)+hdr1.ImgSz+uint32(hdr1.TlvSz) {
-
-			return util.FmtNewtError("File %s is not an image\n",
-				image.SourceImg)
-		}
-		imgSz = hdr1.ImgSz
-		hdrSz = hdr1.HdrSz
-		image.Version = hdr1.Vers
-
-		log.Debugf("Resigning %s (ver %d.%d.%d.%d)", image.SourceImg,
-			hdr1.Vers.Major, hdr1.Vers.Minor, hdr1.Vers.Rev,
-			hdr1.Vers.BuildNum)
-	} else if hdr2.Magic == IMAGE_MAGIC {
-		if uint32(srcInfo.Size()) < uint32(hdr2.HdrSz)+hdr2.ImgSz {
-			return util.FmtNewtError("File %s is not an image\n",
-				image.SourceImg)
-		}
-		imgSz = hdr2.ImgSz
-		hdrSz = hdr2.HdrSz
-		image.Version = hdr2.Vers
-
-		log.Debugf("Resigning %s (ver %d.%d.%d.%d)", image.SourceImg,
-			hdr2.Vers.Major, hdr2.Vers.Minor, hdr2.Vers.Rev,
-			hdr2.Vers.BuildNum)
-	} else {
-		return util.FmtNewtError("File %s is not an image\n",
-			image.SourceImg)
-	}
-	srcImg.Seek(int64(hdrSz), 0)
-
-	tmpBin, err := ioutil.TempFile("", "")
-	if err != nil {
-		return util.FmtNewtError("Creating temp file failed: %s",
-			err.Error())
-	}
-	tmpBinName := tmpBin.Name()
-	defer os.Remove(tmpBinName)
-
-	log.Debugf("Extracting data from %s:%d-%d to %s\n",
-		image.SourceImg, int64(hdrSz), int64(hdrSz)+int64(imgSz), tmpBinName)
-	_, err = io.CopyN(tmpBin, srcImg, int64(imgSz))
-	srcImg.Close()
-	tmpBin.Close()
-	if err != nil {
-		return util.FmtNewtError("Cannot copy to tmpfile %s: %s",
-			tmpBin.Name(), err.Error())
-	}
-
-	image.SourceBin = tmpBinName
-	image.TargetImg = image.SourceImg
-	image.HeaderSize = uint(hdrSz)
-
-	return image.Generate(nil)
-}
-
-func generateSigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
-	var signature []byte
-	var err error
-
-	if UseRsaPss || !UseV1 {
-		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 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 generateSigTlvRsa(key ImageKey, hash []byte) ([]byte, error) {
-	tlv := &ImageTrailerTlv{
-		Type: key.sigTlvType(),
-		Pad:  0,
-		Len:  256, /* 2048 bits */
-	}
-
-	sig, err := generateSigRsa(key.Rsa, hash)
-	if err != nil {
-		return nil, err
-	}
-
-	b := &bytes.Buffer{}
-
-	err = binary.Write(b, binary.LittleEndian, tlv)
-	if err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to serialize image trailer: %s", err.Error())
-	}
-	_, err = b.Write(sig)
-	if err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to append sig: %s", err.Error())
-	}
-
-	return b.Bytes(), nil
-}
-
-func generateSigTlvEc(key ImageKey, hash []byte) ([]byte, error) {
-	sig, err := generateSigEc(key.Ec, hash)
-	if err != nil {
-		return nil, err
-	}
-
-	sigLen := key.sigLen()
-	if len(sig) > int(sigLen) {
-		return nil, util.FmtNewtError("Something is really wrong\n")
-	}
-
-	tlv := &ImageTrailerTlv{
-		Type: key.sigTlvType(),
-		Pad:  0,
-		Len:  sigLen,
-	}
-
-	b := &bytes.Buffer{}
-
-	if err := binary.Write(b, binary.LittleEndian, tlv); err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to serialize image trailer: %s", err.Error())
-	}
-
-	if _, err := b.Write(sig); err != nil {
-		return nil, util.FmtNewtError("Failed to append sig: %s", err.Error())
-	}
-
-	pad := make([]byte, int(sigLen)-len(sig))
-	if _, err := b.Write(pad); err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to serialize image trailer: %s", err.Error())
-	}
-
-	return b.Bytes(), nil
-}
-
-func generateSigTlv(key ImageKey, hash []byte) ([]byte, error) {
-	key.assertValid()
-
-	if key.Rsa != nil {
-		return generateSigTlvRsa(key, hash)
-	} else {
-		return generateSigTlvEc(key, hash)
-	}
-}
-
-func generateKeyHashTlv(key ImageKey) ([]byte, error) {
-	key.assertValid()
-
-	keyHash, err := key.sigKeyHash()
-	if err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to compute hash of the public key: %s", err.Error())
-	}
-
-	tlv := &ImageTrailerTlv{
-		Type: IMAGE_TLV_KEYHASH,
-		Pad:  0,
-		Len:  uint16(len(keyHash)),
-	}
-
-	b := &bytes.Buffer{}
-
-	if err := binary.Write(b, binary.LittleEndian, tlv); err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to serial image trailer: %s", err.Error())
-	}
-
-	if _, err := b.Write(keyHash); err != nil {
-		return nil, util.FmtNewtError(
-			"Failed to append key hash: %s", err.Error())
-	}
-
-	return b.Bytes(), nil
-}
-
-func (image *Image) generateV1(loader *Image) error {
-	binFile, err := os.Open(image.SourceBin)
-	if err != nil {
-		return util.FmtNewtError("Can't open app binary: %s",
-			err.Error())
-	}
-	defer binFile.Close()
-
-	binInfo, err := binFile.Stat()
-	if err != nil {
-		return util.FmtNewtError("Can't stat app binary %s: %s",
-			image.SourceBin, err.Error())
-	}
-
-	imgFile, err := os.OpenFile(image.TargetImg,
-		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
-	if err != nil {
-		return util.FmtNewtError("Can't open target image %s: %s",
-			image.TargetImg, err.Error())
-	}
-	defer imgFile.Close()
-
-	/*
-	 * Compute hash while updating the file.
-	 */
-	hash := sha256.New()
-
-	if loader != nil {
-		err = binary.Write(hash, binary.LittleEndian, loader.Hash)
-		if err != nil {
-			return util.FmtNewtError("Failed to seed hash: %s", err.Error())
-		}
-	}
-
-	/*
-	 * First the header
-	 */
-	hdr := &ImageHdrV1{
-		Magic: IMAGEv1_MAGIC,
-		TlvSz: 0,
-		KeyId: 0,
-		Pad1:  0,
-		HdrSz: IMAGE_HEADER_SIZE,
-		Pad2:  0,
-		ImgSz: uint32(binInfo.Size()) - uint32(image.SrcSkip),
-		Flags: 0,
-		Vers:  image.Version,
-		Pad3:  0,
-	}
-
-	if len(image.Keys) > 0 {
-		hdr.Flags, err = image.Keys[0].sigHdrTypeV1()
-		if err != nil {
-			return err
-		}
-
-		hdr.TlvSz = 4 + image.Keys[0].sigLen()
-		hdr.KeyId = image.KeyId
-	}
-
-	hdr.TlvSz += 4 + 32
-	hdr.Flags |= IMAGEv1_F_SHA256
-
-	if loader != nil {
-		hdr.Flags |= IMAGEv1_F_NON_BOOTABLE
-	}
-
-	if image.HeaderSize != 0 {
-		/*
-		 * Pad the header out to the given size.  There will
-		 * just be zeros between the header and the start of
-		 * the image when it is padded.
-		 */
-		if image.HeaderSize < IMAGE_HEADER_SIZE {
-			return util.FmtNewtError(
-				"Image header must be at least %d bytes", IMAGE_HEADER_SIZE)
-		}
-
-		hdr.HdrSz = uint16(image.HeaderSize)
-	}
-
-	err = binary.Write(imgFile, binary.LittleEndian, hdr)
-	if err != nil {
-		return util.FmtNewtError("Failed to serialize image hdr: %s",
-			err.Error())
-	}
-	err = binary.Write(hash, binary.LittleEndian, hdr)
-	if err != nil {
-		return util.FmtNewtError("Failed to hash data: %s", err.Error())
-	}
-
-	if image.HeaderSize > IMAGE_HEADER_SIZE {
-		/*
-		 * Pad the image (and hash) with zero bytes to fill
-		 * out the buffer.
-		 */
-		buf := make([]byte, image.HeaderSize-IMAGE_HEADER_SIZE)
-
-		_, err = imgFile.Write(buf)
-		if err != nil {
-			return util.FmtNewtError(
-				"Failed to write padding: %s", err.Error())
-		}
-
-		_, err = hash.Write(buf)
-		if err != nil {
-			return util.FmtNewtError("Failed to hash padding: %s", err.Error())
-		}
-	}
-
-	/*
-	 * Skip requested initial part of image.
-	 */
-	if image.SrcSkip > 0 {
-		buf := make([]byte, image.SrcSkip)
-		_, err = binFile.Read(buf)
-		if err != nil {
-			return util.FmtNewtError(
-				"Failed to read from %s: %s", image.SourceBin, err.Error())
-		}
-
-		nonZero := false
-		for _, b := range buf {
-			if b != 0 {
-				nonZero = true
-				break
-			}
-		}
-		if nonZero {
-			log.Warnf("Skip requested of image %s, but image not preceeded "+
-				"by %d bytes of all zeros",
-				image.SourceBin, image.SrcSkip)
-		}
-	}
-
-	/*
-	 * Followed by data.
-	 */
-	dataBuf := make([]byte, 1024)
-	for {
-		cnt, err := binFile.Read(dataBuf)
-		if err != nil && err != io.EOF {
-			return util.FmtNewtError(
-				"Failed to read from %s: %s", image.SourceBin, err.Error())
-		}
-		if cnt == 0 {
-			break
-		}
-		_, err = imgFile.Write(dataBuf[0:cnt])
-		if err != nil {
-			return util.FmtNewtError(
-				"Failed to write to %s: %s", image.TargetImg, err.Error())
-		}
-		_, err = hash.Write(dataBuf[0:cnt])
-		if err != nil {
-			return util.FmtNewtError(
-				"Failed to hash data: %s", err.Error())
-		}
-	}
-
-	image.Hash = hash.Sum(nil)
-
-	/*
-	 * Trailer with hash of the data
-	 */
-	tlv := &ImageTrailerTlv{
-		Type: IMAGEv1_TLV_SHA256,
-		Pad:  0,
-		Len:  uint16(len(image.Hash)),
-	}
-	err = binary.Write(imgFile, binary.LittleEndian, tlv)
-	if err != nil {
-		return util.FmtNewtError(
-			"Failed to serialize image trailer: %s", err.Error())
-	}
-	_, err = imgFile.Write(image.Hash)
-	if err != nil {
-		return util.FmtNewtError(
-			"Failed to append hash: %s", err.Error())
-	}
-
-	if len(image.Keys) > 0 {
-		tlvBytes, err := generateSigTlv(image.Keys[0], image.Hash)
-		if err != nil {
-			return err
-		}
-		if _, err := imgFile.Write(tlvBytes); err != nil {
-			return util.FmtNewtError(
-				"Failed to append sig TLV: %s", err.Error())
-		}
-	}
-
-	util.StatusMessage(util.VERBOSITY_VERBOSE,
-		"Computed Hash for image %s as %s \n",
-		image.TargetImg, hex.EncodeToString(image.Hash))
-
-	// XXX: Replace "1" with io.SeekCurrent when go 1.7 becomes mainstream.
-	sz, err := imgFile.Seek(0, 1)
-	if err != nil {
-		return util.FmtNewtError("Failed to calculate file size of generated "+
-			"image %s: %s", image.TargetImg, err.Error())
-	}
-	image.TotalSize = uint(sz)
-
-	return nil
-}
-
-func (image *Image) generateV2(loader *Image) error {
-	binFile, err := os.Open(image.SourceBin)
-	if err != nil {
-		return util.FmtNewtError("Can't open app binary: %s", err.Error())
-	}
-	defer binFile.Close()
-
-	binInfo, err := binFile.Stat()
-	if err != nil {
-		return util.FmtNewtError("Can't stat app binary %s: %s",
-			image.SourceBin, err.Error())
-	}
-
-	imgFile, err := os.OpenFile(image.TargetImg,
-		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
-	if err != nil {
-		return util.FmtNewtError("Can't open target image %s: %s",
-			image.TargetImg, err.Error())
-	}
-	defer imgFile.Close()
-
-	plainSecret := make([]byte, 16)
-	var cipherSecret []byte
-	var _type uint8
-	if PubKeyFile != "" {
-		_, err = rand.Read(plainSecret)
-		if err != nil {
-			return util.FmtNewtError("Random generation error: %s\n", err)
-		}
-
-		keyBytes, err := ioutil.ReadFile(PubKeyFile)
-		if err != nil {
-			return util.FmtNewtError("Error reading pubkey file: %s", err)
-		}
-
-		// Try reading as PEM (asymetric key), if it fails, assume this is a
-		// base64 encoded symetric key
-		b, _ := pem.Decode(keyBytes)
-		if b == nil {
-			kek, err := base64.StdEncoding.DecodeString(string(keyBytes))
-			if err != nil {
-				return util.FmtNewtError("Error decoding kek: %s", err)
-			} else if len(kek) != 16 {
-				return util.FmtNewtError("Unexpected key size: %d != 16", len(kek))
-			}
-
-			cipher, err := aes.NewCipher(kek)
-			if err != nil {
-				return util.FmtNewtError("Error creating keywrap cipher: %s", err)
-			}
-
-			cipherSecret, err = keywrap.Wrap(cipher, plainSecret)
-			if err != nil {
-				return util.FmtNewtError("Error key-wrapping: %s", err)
-			}
-		} else if b.Type != "PUBLIC KEY" && b.Type != "RSA PUBLIC KEY" {
-			return util.NewNewtError("Invalid PEM file")
-		} else {
-			pub, err := x509.ParsePKIXPublicKey(b.Bytes)
-			if err != nil {
-				return util.FmtNewtError("Error parsing pubkey file: %s", err)
-			}
-
-			var pubk *rsa.PublicKey
-			switch pub.(type) {
-			case *rsa.PublicKey:
-				pubk = pub.(*rsa.PublicKey)
-			default:
-				return util.FmtNewtError("Error parsing pubkey file: %s", err)
-			}
-
-			rng := rand.Reader
-			cipherSecret, err = rsa.EncryptOAEP(sha256.New(), rng, pubk, plainSecret, nil)
-			if err != nil {
-				return util.FmtNewtError("Error from encryption: %s\n", err)
-			}
-		}
-	}
-
-	/*
-	 * Compute hash while updating the file.
-	 */
-	hash := sha256.New()
-
-	if loader != nil {
-		err = binary.Write(hash, binary.LittleEndian, loader.Hash)
-		if err != nil {
-			return util.FmtNewtError("Failed to seed hash: %s",
-				err.Error())
-		}
-	}
-
-	/*
-	 * First the header
-	 */
-	hdr := &ImageHdr{
-		Magic: IMAGE_MAGIC,
-		Pad1:  0,
-		HdrSz: IMAGE_HEADER_SIZE,
-		Pad2:  0,
-		ImgSz: uint32(binInfo.Size()) - uint32(image.SrcSkip),
-		Flags: 0,
-		Vers:  image.Version,
-		Pad3:  0,
-	}
-
-	if loader != nil {
-		hdr.Flags |= IMAGE_F_NON_BOOTABLE
-	}
-	if cipherSecret != nil {
-		hdr.Flags |= IMAGE_F_ENCRYPTED
-	}
-
-	if image.HeaderSize != 0 {
-		/*
-		 * Pad the header out to the given size.  There will
-		 * just be zeros between the header and the start of
-		 * the image when it is padded.
-		 */
-		if image.HeaderSize < IMAGE_HEADER_SIZE {
-			return util.FmtNewtError("Image header must be at "+
-				"least %d bytes", IMAGE_HEADER_SIZE)
-		}
-
-		hdr.HdrSz = uint16(image.HeaderSize)
-	}
-
-	err = binary.Write(imgFile, binary.LittleEndian, hdr)
-	if err != nil {
-		return util.FmtNewtError("Failed to serialize image hdr: %s",
-			err.Error())
-	}
-	err = binary.Write(hash, binary.LittleEndian, hdr)
-	if err != nil {
-		return util.FmtNewtError("Failed to hash data: %s",
-			err.Error())
-	}
-
-	if image.HeaderSize > IMAGE_HEADER_SIZE {
-		/*
-		 * Pad the image (and hash) with zero bytes to fill
-		 * out the buffer.
-		 */
-		buf := make([]byte, image.HeaderSize-IMAGE_HEADER_SIZE)
-
-		_, err = imgFile.Write(buf)
-		if err != nil {
-			return util.FmtNewtError("Failed to write padding: %s",
-				err.Error())
-		}
-
-		_, err = hash.Write(buf)
-		if err != nil {
-			return util.FmtNewtError("Failed to hash padding: %s",
-				err.Error())
-		}
-	}
-
-	/*
-	 * Skip requested initial part of image.
-	 */
-	if image.SrcSkip > 0 {
-		buf := make([]byte, image.SrcSkip)
-		_, err = binFile.Read(buf)
-		if err != nil {
-			return util.FmtNewtError("Failed to read from %s: %s",
-				image.SourceBin, err.Error())
-		}
-
-		nonZero := false
-		for _, b := range buf {
-			if b != 0 {
-				nonZero = true
-				break
-			}
-		}
-		if nonZero {
-			log.Warnf("Skip requested of image %s, but image not preceeded by %d bytes of all zeros",
-				image.SourceBin, image.SrcSkip)
-		}
-	}
-
-	var stream cipher.Stream
-	if cipherSecret != nil {
-		block, err := aes.NewCipher(plainSecret)
-		if err != nil {
-			return util.NewNewtError("Failed to create block cipher")
-		}
-		nonce := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
-		stream = cipher.NewCTR(block, nonce)
-	}
-
-	/*
-	 * Followed by data.
-	 */
-	dataBuf := make([]byte, 16)
-	encBuf := make([]byte, 16)
-	for {
-		cnt, err := binFile.Read(dataBuf)
-		if err != nil && err != io.EOF {
-			return util.FmtNewtError("Failed to read from %s: %s",
-				image.SourceBin, err.Error())
-		}
-		if cnt == 0 {
-			break
-		}
-		_, err = hash.Write(dataBuf[0:cnt])
-		if err != nil {
-			return util.FmtNewtError("Failed to hash data: %s",
-				err.Error())
-		}
-		if cipherSecret == nil {
-			_, err = imgFile.Write(dataBuf[0:cnt])
-		} else {
-			stream.XORKeyStream(encBuf, dataBuf[0:cnt])
-			_, err = imgFile.Write(encBuf[0:cnt])
-		}
-		if err != nil {
-			return util.FmtNewtError("Failed to write to %s: %s",
-				image.TargetImg, err.Error())
-		}
-	}
-
-	image.Hash = hash.Sum(nil)
-
-	/*
-	 * Write TLV info.
-	 */
-	tlvInfo := &ImageTlvInfo{
-		Magic:     IMAGE_TRAILER_MAGIC,
-		TlvTotLen: 0,
-	}
-	tlvInfoOff, err := imgFile.Seek(0, 1)
-	if err != nil {
-		return util.FmtNewtError("Failed to calculate file size of generated "+
-			"image %s: %s", image.TargetImg, err.Error())
-	}
-	err = binary.Write(imgFile, binary.LittleEndian, tlvInfo)
-	if err != nil {
-		return util.FmtNewtError("Failed to serialize image hdr: %s",
-			err.Error())
-	}
-
-	/*
-	 * Trailer with hash of the data
-	 */
-	tlv := &ImageTrailerTlv{
-		Type: IMAGE_TLV_SHA256,
-		Pad:  0,
-		Len:  uint16(len(image.Hash)),
-	}
-	err = binary.Write(imgFile, binary.LittleEndian, tlv)
-	if err != nil {
-		return util.FmtNewtError("Failed to serialize image "+
-			"trailer: %s", err.Error())
-	}
-	_, err = imgFile.Write(image.Hash)
-	if err != nil {
-		return util.FmtNewtError("Failed to append hash: %s",
-			err.Error())
-	}
-
-	for _, key := range image.Keys {
-		key.assertValid()
-
-		tlvBytes, err := generateKeyHashTlv(key)
-		if err != nil {
-			return err
-		}
-
-		if _, err = imgFile.Write(tlvBytes); err != nil {
-			return util.FmtNewtError(
-				"Failed to append key hash: %s", err.Error())
-		}
-
-		tlvBytes, err = generateSigTlv(key, image.Hash)
-		if err != nil {
-			return err
-		}
-
-		if _, err = imgFile.Write(tlvBytes); err != nil {
-			return util.FmtNewtError(
-				"Failed to append signature: %s", err.Error())
-		}
-	}
-
-	if cipherSecret != nil {
-		if len(cipherSecret) == 256 {
-			_type = IMAGE_TLV_ENC_RSA
-		} else if len(cipherSecret) == 24 {
-			_type = IMAGE_TLV_ENC_KEK
-		} else {
-			return util.FmtNewtError("Invalid enc TLV size ")
-		}
-		tlv := &ImageTrailerTlv{
-			Type: _type,
-			Pad:  0,
-			Len:  uint16(len(cipherSecret)),
-		}
-		if err := binary.Write(imgFile, binary.LittleEndian, tlv); err != nil {
-			return util.FmtNewtError(
-				"Failed to serialize cipher secret TLV: %s", err.Error())
-		}
-		if _, err := imgFile.Write(cipherSecret); err != nil {
-			return util.FmtNewtError("Failed to append encrypted key: %s",
-				err.Error())
-		}
-	}
-
-	util.StatusMessage(util.VERBOSITY_VERBOSE,
-		"Computed Hash for image %s as %s \n",
-		image.TargetImg, hex.EncodeToString(image.Hash))
-
-	// XXX: Replace "1" with io.SeekCurrent when go 1.7 becomes mainstream.
-	sz, err := imgFile.Seek(0, 1)
-	if err != nil {
-		return util.FmtNewtError("Failed to calculate file size of generated "+
-			"image %s: %s", image.TargetImg, err.Error())
-	}
-	image.TotalSize = uint(sz)
-
-	tlvInfo.TlvTotLen = uint16(sz - tlvInfoOff)
-
-	/*
-	 * Go back and write tlv info total length
-	 */
-	_, err = imgFile.Seek(tlvInfoOff, 0)
-	if err != nil {
-		return util.FmtNewtError("Failed to move to tlvInfo offset %d "+
-			"image: %s", int(tlvInfoOff), err.Error())
-	}
-	err = binary.Write(imgFile, binary.LittleEndian, tlvInfo)
-	if err != nil {
-		return util.FmtNewtError("Failed to serialize image hdr: %s",
-			err.Error())
-	}
-
-	return nil
-}
-
-func (image *Image) Generate(loader *Image) error {
-	if UseV1 {
-		return image.generateV1(loader)
-	} else {
-		return image.generateV2(loader)
-	}
-}
-
-func CreateBuildId(app *Image, loader *Image) []byte {
-	return app.Hash
-}
-
-func NewRepoManager() *RepoManager {
-	return &RepoManager{
-		repos: make(map[string]ImageManifestRepo),
-	}
-}
-
-func (r *RepoManager) GetImageManifestPkg(
-	lpkg *pkg.LocalPackage) *ImageManifestPkg {
-
-	ip := &ImageManifestPkg{
-		Name: lpkg.Name(),
-	}
-
-	var path string
-	if lpkg.Repo().IsLocal() {
-		ip.Repo = lpkg.Repo().Name()
-		path = lpkg.BasePath()
-	} else {
-		ip.Repo = lpkg.Repo().Name()
-		path = lpkg.BasePath()
-	}
-
-	if _, present := r.repos[ip.Repo]; present {
-		return ip
-	}
-
-	repo := ImageManifestRepo{
-		Name: ip.Repo,
-	}
-
-	// Make sure we restore the current working dir to whatever it was when
-	// this function was called
-	cwd, err := os.Getwd()
-	if err != nil {
-		log.Debugf("Unable to determine current working directory: %v", err)
-		return ip
-	}
-	defer os.Chdir(cwd)
-
-	if err := os.Chdir(path); err != nil {
-		return ip
-	}
-
-	var res []byte
-
-	res, err = util.ShellCommand([]string{
-		"git",
-		"rev-parse",
-		"HEAD",
-	}, nil)
-	if err != nil {
-		log.Debugf("Unable to determine commit hash for %s: %v", path, err)
-		repo.Commit = "UNKNOWN"
-	} else {
-		repo.Commit = strings.TrimSpace(string(res))
-		res, err = util.ShellCommand([]string{
-			"git",
-			"status",
-			"--porcelain",
-		}, nil)
-		if err != nil {
-			log.Debugf("Unable to determine dirty state for %s: %v", path, err)
-		} else {
-			if len(res) > 0 {
-				repo.Dirty = true
-			}
-		}
-		res, err = util.ShellCommand([]string{
-			"git",
-			"config",
-			"--get",
-			"remote.origin.url",
-		}, nil)
-		if err != nil {
-			log.Debugf("Unable to determine URL for %s: %v", path, err)
-		} else {
-			repo.URL = strings.TrimSpace(string(res))
-		}
-	}
-	r.repos[ip.Repo] = repo
-
-	return ip
-}
-
-func (r *RepoManager) AllRepos() []ImageManifestRepo {
-	keys := make([]string, 0, len(r.repos))
-	for k := range r.repos {
-		keys = append(keys, k)
-	}
-
-	sort.Strings(keys)
-
-	repos := make([]ImageManifestRepo, 0, len(keys))
-	for _, key := range keys {
-		repos = append(repos, r.repos[key])
-	}
-
-	return repos
-}
-
-func NewImageManifestSizeCollector() *ImageManifestSizeCollector {
-	return &ImageManifestSizeCollector{}
-}
-
-func (c *ImageManifestSizeCollector) AddPkg(pkg string) *ImageManifestSizePkg {
-	p := &ImageManifestSizePkg{
-		Name: pkg,
-	}
-	c.Pkgs = append(c.Pkgs, p)
-
-	return p
-}
-
-func (c *ImageManifestSizePkg) AddSymbol(file string, sym string, area string,
-	symSz uint32) {
-	f := c.addFile(file)
-	s := f.addSym(sym)
-	s.addArea(area, symSz)
-}
-
-func (p *ImageManifestSizePkg) addFile(file string) *ImageManifestSizeFile {
-	for _, f := range p.Files {
-		if f.Name == file {
-			return f
-		}
-	}
-	f := &ImageManifestSizeFile{
-		Name: file,
-	}
-	p.Files = append(p.Files, f)
-
-	return f
-}
-
-func (f *ImageManifestSizeFile) addSym(sym string) *ImageManifestSizeSym {
-	s := &ImageManifestSizeSym{
-		Name: sym,
-	}
-	f.Syms = append(f.Syms, s)
-
-	return s
-}
-
-func (s *ImageManifestSizeSym) addArea(area string, areaSz uint32) {
-	a := &ImageManifestSizeArea{
-		Name: area,
-		Size: areaSz,
-	}
-	s.Areas = append(s.Areas, a)
-}
diff --git a/newt/imgprod/imgprod.go b/newt/imgprod/imgprod.go
new file mode 100644
index 00000000..dc72ecaf
--- /dev/null
+++ b/newt/imgprod/imgprod.go
@@ -0,0 +1,284 @@
+/**
+ * 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.
+ */
+
+// imgprod - Image production.
+
+package imgprod
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/manifest"
+	"mynewt.apache.org/newt/newt/newtutil"
+	"mynewt.apache.org/newt/util"
+)
+
+type ImageProdOpts struct {
+	LoaderSrcFilename string
+	LoaderDstFilename string
+	AppSrcFilename    string
+	AppDstFilename    string
+	EncKeyFilename    string
+	Version           image.ImageVersion
+	SigKeys           []image.ImageSigKey
+}
+
+type ProducedImage struct {
+	Filename string
+	Image    image.Image
+	Hash     []byte
+	FileSize int
+}
+
+type ProducedImageSet struct {
+	Loader *ProducedImage
+	App    ProducedImage
+}
+
+func produceLoader(opts ImageProdOpts) (ProducedImage, error) {
+	pi := ProducedImage{}
+
+	igo := image.ImageCreateOpts{
+		SrcBinFilename:    opts.LoaderSrcFilename,
+		SrcEncKeyFilename: opts.EncKeyFilename,
+		Version:           opts.Version,
+		SigKeys:           opts.SigKeys,
+	}
+
+	ri, err := image.GenerateImage(igo)
+	if err != nil {
+		return pi, err
+	}
+
+	hash, err := ri.Hash()
+	if err != nil {
+		return pi, err
+	}
+
+	fileSize, err := ri.TotalSize()
+	if err != nil {
+		return pi, err
+	}
+
+	imgFile, err := os.OpenFile(opts.LoaderDstFilename,
+		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+	if err != nil {
+		return pi, util.FmtNewtError(
+			"Can't open target image %s: %s",
+			opts.LoaderDstFilename, err.Error())
+	}
+	defer imgFile.Close()
+
+	if _, err := ri.Write(imgFile); err != nil {
+		return pi, err
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"Loader image successfully generated: %s\n", opts.LoaderDstFilename)
+
+	pi.Filename = opts.LoaderDstFilename
+	pi.Image = ri
+	pi.Hash = hash
+	pi.FileSize = fileSize
+
+	return pi, nil
+}
+
+func produceApp(opts ImageProdOpts, loaderHash []byte) (ProducedImage, error) {
+	pi := ProducedImage{}
+
+	igo := image.ImageCreateOpts{
+		SrcBinFilename:    opts.AppSrcFilename,
+		SrcEncKeyFilename: opts.EncKeyFilename,
+		Version:           opts.Version,
+		SigKeys:           opts.SigKeys,
+		LoaderHash:        loaderHash,
+	}
+
+	ri, err := image.GenerateImage(igo)
+	if err != nil {
+		return pi, err
+	}
+
+	hash, err := ri.Hash()
+	if err != nil {
+		return pi, err
+	}
+
+	fileSize, err := ri.TotalSize()
+	if err != nil {
+		return pi, err
+	}
+
+	imgFile, err := os.OpenFile(opts.AppDstFilename,
+		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+	if err != nil {
+		return pi, util.FmtNewtError(
+			"Can't open target image %s: %s", opts.AppDstFilename, err.Error())
+	}
+	defer imgFile.Close()
+
+	if _, err := ri.Write(imgFile); err != nil {
+		return pi, err
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"App image successfully generated: %s\n", opts.AppDstFilename)
+
+	pi.Filename = opts.AppDstFilename
+	pi.Image = ri
+	pi.Hash = hash
+	pi.FileSize = fileSize
+
+	return pi, nil
+}
+
+// Verifies that each already-built image leaves enough room for a boot trailer
+// a the end of its slot.
+func verifyImgSizes(pset ProducedImageSet, maxSizes []int) error {
+	errLines := []string{}
+	slot := 0
+
+	if pset.Loader != nil {
+		if overflow := int(pset.Loader.FileSize) - maxSizes[0]; overflow > 0 {
+			errLines = append(errLines,
+				fmt.Sprintf("loader overflows slot-0 by %d bytes "+
+					"(image=%d max=%d)",
+					overflow, pset.Loader.FileSize, maxSizes[0]))
+		}
+		slot++
+	}
+
+	if overflow := int(pset.App.FileSize) - maxSizes[slot]; overflow > 0 {
+		errLines = append(errLines,
+			fmt.Sprintf("app overflows slot-%d by %d bytes "+
+				"(image=%d max=%d)",
+				slot, overflow, pset.App.FileSize, maxSizes[slot]))
+
+	}
+
+	if len(errLines) > 0 {
+		if !newtutil.NewtForce {
+			return util.NewNewtError(strings.Join(errLines, "; "))
+		} else {
+			for _, e := range errLines {
+				util.StatusMessage(util.VERBOSITY_QUIET,
+					"* Warning: %s (ignoring due to force flag)\n", e)
+			}
+		}
+	}
+
+	return nil
+}
+
+func ProduceImages(opts ImageProdOpts) (ProducedImageSet, error) {
+	pset := ProducedImageSet{}
+
+	var loaderHash []byte
+	if opts.LoaderSrcFilename != "" {
+		pi, err := produceLoader(opts)
+		if err != nil {
+			return pset, err
+		}
+		loaderHash = pi.Hash
+
+		pset.Loader = &pi
+	}
+
+	pi, err := produceApp(opts, loaderHash)
+	if err != nil {
+		return pset, err
+	}
+	pset.App = pi
+
+	return pset, nil
+}
+
+func ProduceManifest(opts manifest.ManifestCreateOpts) error {
+	m, err := manifest.CreateManifest(opts)
+	if err != nil {
+		return err
+	}
+
+	file, err := os.Create(opts.TgtBldr.AppBuilder.ManifestPath())
+	if err != nil {
+		return util.FmtNewtError("Cannot create manifest file %s: %s",
+			opts.TgtBldr.AppBuilder.ManifestPath(), err.Error())
+	}
+	defer file.Close()
+
+	if _, err := m.Write(file); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func OptsFromTgtBldr(b *builder.TargetBuilder, ver image.ImageVersion,
+	sigKeys []image.ImageSigKey, encKeyFilename string) ImageProdOpts {
+
+	opts := ImageProdOpts{
+		AppSrcFilename: b.AppBuilder.AppBinPath(),
+		AppDstFilename: b.AppBuilder.AppImgPath(),
+		EncKeyFilename: encKeyFilename,
+		Version:        ver,
+		SigKeys:        sigKeys,
+	}
+
+	if b.LoaderBuilder != nil {
+		opts.LoaderSrcFilename = b.LoaderBuilder.AppBinPath()
+		opts.LoaderDstFilename = b.LoaderBuilder.AppImgPath()
+	}
+
+	return opts
+}
+
+func ProduceAll(t *builder.TargetBuilder, ver image.ImageVersion,
+	sigKeys []image.ImageSigKey, encKeyFilename string) error {
+
+	popts := OptsFromTgtBldr(t, ver, sigKeys, encKeyFilename)
+	pset, err := ProduceImages(popts)
+	if err != nil {
+		return err
+	}
+
+	var loaderHash []byte
+	if pset.Loader != nil {
+		loaderHash = pset.Loader.Hash
+	}
+
+	mopts, err := manifest.OptsForImage(t, ver, pset.App.Hash, loaderHash)
+	if err != nil {
+		return err
+	}
+
+	if err := ProduceManifest(mopts); err != nil {
+		return err
+	}
+
+	if err := verifyImgSizes(pset, mopts.TgtBldr.MaxImgSizes()); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/newt/imgprod/v1.go b/newt/imgprod/v1.go
new file mode 100644
index 00000000..05f0a33d
--- /dev/null
+++ b/newt/imgprod/v1.go
@@ -0,0 +1,217 @@
+package imgprod
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/manifest"
+	"mynewt.apache.org/newt/newt/newtutil"
+	"mynewt.apache.org/newt/util"
+)
+
+type ProducedImageV1 struct {
+	Filename string
+	Image    image.ImageV1
+	Hash     []byte
+	FileSize int
+}
+
+type ProducedImageSetV1 struct {
+	Loader *ProducedImageV1
+	App    ProducedImageV1
+}
+
+func produceLoaderV1(opts ImageProdOpts) (ProducedImageV1, error) {
+	pi := ProducedImageV1{}
+
+	igo := image.ImageCreateOpts{
+		SrcBinFilename:    opts.LoaderSrcFilename,
+		SrcEncKeyFilename: opts.EncKeyFilename,
+		Version:           opts.Version,
+		SigKeys:           opts.SigKeys,
+	}
+
+	img, err := image.GenerateV1Image(igo)
+	if err != nil {
+		return pi, err
+	}
+
+	hash, err := img.Hash()
+	if err != nil {
+		return pi, err
+	}
+
+	fileSize, err := img.TotalSize()
+	if err != nil {
+		return pi, err
+	}
+
+	imgFile, err := os.OpenFile(opts.LoaderDstFilename,
+		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+	if err != nil {
+		return pi, util.FmtNewtError(
+			"Can't open target image %s: %s",
+			opts.LoaderDstFilename, err.Error())
+	}
+	defer imgFile.Close()
+
+	if _, err := img.Write(imgFile); err != nil {
+		return pi, err
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"V1 loader image successfully generated: %s\n", opts.LoaderDstFilename)
+
+	pi.Filename = opts.LoaderDstFilename
+	pi.Image = img
+	pi.Hash = hash
+	pi.FileSize = fileSize
+
+	return pi, nil
+}
+
+func produceAppV1(opts ImageProdOpts,
+	loaderHash []byte) (ProducedImageV1, error) {
+
+	pi := ProducedImageV1{}
+
+	igo := image.ImageCreateOpts{
+		SrcBinFilename:    opts.AppSrcFilename,
+		SrcEncKeyFilename: opts.EncKeyFilename,
+		Version:           opts.Version,
+		SigKeys:           opts.SigKeys,
+		LoaderHash:        loaderHash,
+	}
+
+	img, err := image.GenerateV1Image(igo)
+	if err != nil {
+		return pi, err
+	}
+
+	hash, err := img.Hash()
+	if err != nil {
+		return pi, err
+	}
+
+	fileSize, err := img.TotalSize()
+	if err != nil {
+		return pi, err
+	}
+
+	imgFile, err := os.OpenFile(opts.AppDstFilename,
+		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+	if err != nil {
+		return pi, util.FmtNewtError(
+			"Can't open target image %s: %s", opts.AppDstFilename, err.Error())
+	}
+	defer imgFile.Close()
+
+	if _, err := img.Write(imgFile); err != nil {
+		return pi, err
+	}
+
+	util.StatusMessage(util.VERBOSITY_DEFAULT,
+		"App image successfully generated: %s\n", opts.AppDstFilename)
+
+	pi.Filename = opts.AppDstFilename
+	pi.Image = img
+	pi.Hash = hash
+	pi.FileSize = fileSize
+
+	return pi, nil
+}
+
+// Verifies that each already-built image leaves enough room for a boot trailer
+// a the end of its slot.
+func verifyImgSizesV1(pset ProducedImageSetV1, maxSizes []int) error {
+	errLines := []string{}
+	slot := 0
+
+	if pset.Loader != nil {
+		if overflow := int(pset.Loader.FileSize) - maxSizes[0]; overflow > 0 {
+			errLines = append(errLines,
+				fmt.Sprintf("loader overflows slot-0 by %d bytes "+
+					"(image=%d max=%d)",
+					overflow, pset.Loader.FileSize, maxSizes[0]))
+		}
+		slot++
+	}
+
+	if overflow := int(pset.App.FileSize) - maxSizes[slot]; overflow > 0 {
+		errLines = append(errLines,
+			fmt.Sprintf("app overflows slot-%d by %d bytes "+
+				"(image=%d max=%d)",
+				slot, overflow, pset.App.FileSize, maxSizes[slot]))
+
+	}
+
+	if len(errLines) > 0 {
+		if !newtutil.NewtForce {
+			return util.NewNewtError(strings.Join(errLines, "; "))
+		} else {
+			for _, e := range errLines {
+				util.StatusMessage(util.VERBOSITY_QUIET,
+					"* Warning: %s (ignoring due to force flag)\n", e)
+			}
+		}
+	}
+
+	return nil
+}
+
+func ProduceImagesV1(opts ImageProdOpts) (ProducedImageSetV1, error) {
+	pset := ProducedImageSetV1{}
+
+	var loaderHash []byte
+	if opts.LoaderSrcFilename != "" {
+		pi, err := produceLoaderV1(opts)
+		if err != nil {
+			return pset, err
+		}
+		loaderHash = pi.Hash
+
+		pset.Loader = &pi
+	}
+
+	pi, err := produceAppV1(opts, loaderHash)
+	if err != nil {
+		return pset, err
+	}
+	pset.App = pi
+
+	return pset, nil
+}
+
+func ProduceAllV1(t *builder.TargetBuilder, ver image.ImageVersion,
+	sigKeys []image.ImageSigKey, encKeyFilename string) error {
+
+	popts := OptsFromTgtBldr(t, ver, sigKeys, encKeyFilename)
+	pset, err := ProduceImagesV1(popts)
+	if err != nil {
+		return err
+	}
+
+	mopts := manifest.ManifestCreateOpts{
+		TgtBldr: t,
+		AppHash: pset.App.Hash,
+		Version: ver,
+		BuildID: fmt.Sprintf("%x", pset.App.Hash),
+	}
+
+	if pset.Loader != nil {
+		mopts.LoaderHash = pset.Loader.Hash
+	}
+
+	if err := ProduceManifest(mopts); err != nil {
+		return err
+	}
+
+	if err := verifyImgSizesV1(pset, mopts.TgtBldr.MaxImgSizes()); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/newt/manifest/manifest.go b/newt/manifest/manifest.go
new file mode 100644
index 00000000..c2b52431
--- /dev/null
+++ b/newt/manifest/manifest.go
@@ -0,0 +1,334 @@
+/**
+ * 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.
+ */
+
+// imgprod - Manifest generation.
+
+package manifest
+
+import (
+	"fmt"
+	"os"
+	"sort"
+	"strings"
+	"time"
+
+	log "github.com/Sirupsen/logrus"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/artifact/manifest"
+	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/pkg"
+	"mynewt.apache.org/newt/newt/syscfg"
+	"mynewt.apache.org/newt/util"
+)
+
+type ManifestSizeCollector struct {
+	Pkgs []*manifest.ManifestSizePkg
+}
+
+type ManifestCreateOpts struct {
+	TgtBldr    *builder.TargetBuilder
+	LoaderHash []byte
+	AppHash    []byte
+	Version    image.ImageVersion
+	BuildID    string
+	Syscfg     map[string]string
+}
+
+type RepoManager struct {
+	repos map[string]manifest.ManifestRepo
+}
+
+func NewRepoManager() *RepoManager {
+	return &RepoManager{
+		repos: make(map[string]manifest.ManifestRepo),
+	}
+}
+
+func (r *RepoManager) AllRepos() []*manifest.ManifestRepo {
+	keys := make([]string, 0, len(r.repos))
+	for k := range r.repos {
+		keys = append(keys, k)
+	}
+
+	sort.Strings(keys)
+
+	repos := make([]*manifest.ManifestRepo, 0, len(keys))
+	for _, key := range keys {
+		r := r.repos[key]
+		repos = append(repos, &r)
+	}
+
+	return repos
+}
+
+func (c *ManifestSizeCollector) AddPkg(pkg string) *manifest.ManifestSizePkg {
+	p := &manifest.ManifestSizePkg{
+		Name: pkg,
+	}
+	c.Pkgs = append(c.Pkgs, p)
+
+	return p
+}
+
+func AddSymbol(p *manifest.ManifestSizePkg, file string, sym string, area string,
+	symSz uint32) {
+
+	f := addFile(p, file)
+	s := addSym(f, sym)
+	addArea(s, area, symSz)
+}
+
+func addFile(p *manifest.ManifestSizePkg, file string) *manifest.ManifestSizeFile {
+	for _, f := range p.Files {
+		if f.Name == file {
+			return f
+		}
+	}
+	f := &manifest.ManifestSizeFile{
+		Name: file,
+	}
+	p.Files = append(p.Files, f)
+
+	return f
+}
+
+func addSym(f *manifest.ManifestSizeFile, sym string) *manifest.ManifestSizeSym {
+	s := &manifest.ManifestSizeSym{
+		Name: sym,
+	}
+	f.Syms = append(f.Syms, s)
+
+	return s
+}
+
+func addArea(s *manifest.ManifestSizeSym, area string, areaSz uint32) {
+	a := &manifest.ManifestSizeArea{
+		Name: area,
+		Size: areaSz,
+	}
+	s.Areas = append(s.Areas, a)
+}
+
+func (r *RepoManager) GetManifestPkg(
+	lpkg *pkg.LocalPackage) *manifest.ManifestPkg {
+
+	ip := &manifest.ManifestPkg{
+		Name: lpkg.FullName(),
+	}
+
+	var path string
+	if lpkg.Repo().IsLocal() {
+		ip.Repo = lpkg.Repo().Name()
+		path = lpkg.BasePath()
+	} else {
+		ip.Repo = lpkg.Repo().Name()
+		path = lpkg.BasePath()
+	}
+
+	if _, present := r.repos[ip.Repo]; present {
+		return ip
+	}
+
+	repo := manifest.ManifestRepo{
+		Name: ip.Repo,
+	}
+
+	// Make sure we restore the current working dir to whatever it was when
+	// this function was called
+	cwd, err := os.Getwd()
+	if err != nil {
+		log.Debugf("Unable to determine current working directory: %v", err)
+		return ip
+	}
+	defer os.Chdir(cwd)
+
+	if err := os.Chdir(path); err != nil {
+		return ip
+	}
+
+	var res []byte
+
+	res, err = util.ShellCommand([]string{
+		"git",
+		"rev-parse",
+		"HEAD",
+	}, nil)
+	if err != nil {
+		log.Debugf("Unable to determine commit hash for %s: %v", path, err)
+		repo.Commit = "UNKNOWN"
+	} else {
+		repo.Commit = strings.TrimSpace(string(res))
+		res, err = util.ShellCommand([]string{
+			"git",
+			"status",
+			"--porcelain",
+		}, nil)
+		if err != nil {
+			log.Debugf("Unable to determine dirty state for %s: %v", path, err)
+		} else {
+			if len(res) > 0 {
+				repo.Dirty = true
+			}
+		}
+		res, err = util.ShellCommand([]string{
+			"git",
+			"config",
+			"--get",
+			"remote.origin.url",
+		}, nil)
+		if err != nil {
+			log.Debugf("Unable to determine URL for %s: %v", path, err)
+		} else {
+			repo.URL = strings.TrimSpace(string(res))
+		}
+	}
+	r.repos[ip.Repo] = repo
+
+	return ip
+}
+
+func ManifestPkgSizes(b *builder.Builder) (ManifestSizeCollector, error) {
+	msc := ManifestSizeCollector{}
+
+	libs, err := builder.ParseMapFileSizes(b.AppMapPath())
+	if err != nil {
+		return msc, err
+	}
+
+	// Order libraries by name.
+	pkgSizes := make(builder.PkgSizeArray, len(libs))
+	i := 0
+	for _, es := range libs {
+		pkgSizes[i] = es
+		i++
+	}
+	sort.Sort(pkgSizes)
+
+	for _, es := range pkgSizes {
+		p := msc.AddPkg(b.FindPkgNameByArName(es.Name))
+
+		// Order symbols by name.
+		symbols := make(builder.SymbolDataArray, len(es.Syms))
+		i := 0
+		for _, sym := range es.Syms {
+			symbols[i] = sym
+			i++
+		}
+		sort.Sort(symbols)
+		for _, sym := range symbols {
+			for area, areaSz := range sym.Sizes {
+				if areaSz != 0 {
+					AddSymbol(p, sym.ObjName, sym.Name, area, areaSz)
+				}
+			}
+		}
+	}
+
+	return msc, nil
+}
+
+func OptsForNonImage(t *builder.TargetBuilder) (ManifestCreateOpts, error) {
+	res, err := t.Resolve()
+	if err != nil {
+		return ManifestCreateOpts{}, err
+	}
+
+	return ManifestCreateOpts{
+		TgtBldr: t,
+		Syscfg:  res.Cfg.SettingValues(),
+	}, nil
+}
+
+func OptsForImage(t *builder.TargetBuilder, ver image.ImageVersion,
+	appHash []byte, loaderHash []byte) (ManifestCreateOpts, error) {
+
+	res, err := t.Resolve()
+	if err != nil {
+		return ManifestCreateOpts{}, err
+	}
+
+	return ManifestCreateOpts{
+		TgtBldr:    t,
+		AppHash:    appHash,
+		LoaderHash: loaderHash,
+		Version:    ver,
+		BuildID:    fmt.Sprintf("%x", appHash),
+		Syscfg:     res.Cfg.SettingValues(),
+	}, nil
+}
+
+func CreateManifest(opts ManifestCreateOpts) (manifest.Manifest, error) {
+	t := opts.TgtBldr
+
+	m := manifest.Manifest{
+		Name:      t.GetTarget().FullName(),
+		Date:      time.Now().Format(time.RFC3339),
+		Version:   opts.Version.String(),
+		BuildID:   opts.BuildID,
+		Image:     t.AppBuilder.AppImgPath(),
+		ImageHash: fmt.Sprintf("%x", opts.AppHash),
+		Syscfg:    opts.Syscfg,
+	}
+
+	rm := NewRepoManager()
+	for _, rpkg := range t.AppBuilder.SortedRpkgs() {
+		m.Pkgs = append(m.Pkgs, rm.GetManifestPkg(rpkg.Lpkg))
+	}
+
+	m.Repos = rm.AllRepos()
+
+	vars := t.GetTarget().TargetY.AllSettingsAsStrings()
+	keys := make([]string, 0, len(vars))
+	for k := range vars {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		m.TgtVars = append(m.TgtVars, k+"="+vars[k])
+	}
+	syscfgKV := t.GetTarget().Package().SyscfgY.GetValStringMapString(
+		"syscfg.vals", nil)
+	if len(syscfgKV) > 0 {
+		tgtSyscfg := fmt.Sprintf("target.syscfg=%s",
+			syscfg.KeyValueToStr(syscfgKV))
+		m.TgtVars = append(m.TgtVars, tgtSyscfg)
+	}
+
+	c, err := ManifestPkgSizes(t.AppBuilder)
+	if err == nil {
+		m.PkgSizes = c.Pkgs
+	}
+
+	if t.LoaderBuilder != nil {
+		m.Loader = t.LoaderBuilder.AppImgPath()
+		m.LoaderHash = fmt.Sprintf("%x", opts.LoaderHash)
+
+		for _, rpkg := range t.LoaderBuilder.SortedRpkgs() {
+			m.LoaderPkgs = append(m.LoaderPkgs, rm.GetManifestPkg(rpkg.Lpkg))
+		}
+
+		c, err = ManifestPkgSizes(t.LoaderBuilder)
+		if err == nil {
+			m.LoaderPkgSizes = c.Pkgs
+		}
+	}
+
+	return m, nil
+}
diff --git a/newt/mfg/README.md b/newt/mfg/README.md
new file mode 100644
index 00000000..52dc5f05
--- /dev/null
+++ b/newt/mfg/README.md
@@ -0,0 +1,85 @@
+<!--
+#
+# 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.
+#
+-->
+
+# mfg
+
+### Definitions:
+
+| Term | Long Name | Meaning |
+| ---- | --------- | ------- |
+| Flashdev | Flash device | A single piece of flash hardware.  E.g., "internal flash", or "external SPI flash". |
+| Mfgimage | Manufacturing image | A file with the entire contents of a single flashdev. At manufacturing time, a separate mfgimage is typically written to each of the device's flashdevs. |
+| MMR | Manufacturing Meta Region | A chunk of read-only data included in an mfgimage. Contains identifying information for the mfgimage and other data that stays with the device until end of life. |
+
+### Design
+
+#### artifact/mfg
+
+The `artifact` library defines the `Mfg` type.  An `Mfg` is a barebones representation of an mfgimage.  It contains a raw binary of everything except the MMR, and a slice of MMR TLVs.  An `Mfg` is be converted to a flat byte slice with the following sequence:
+
+1. Calculate the SHA256 and add it to the MMR (`Mfg#CalcHash()`)
+2. Extract the byte-slice representation (`Mfg#Bytes()`)
+
+An `Mfg` can be parsed from a byte slice using the `Parse()` function.
+
+#### newt/mfg
+
+##### High level
+
+The newt tool creates mfgimages from:
+
+1. An mfg definition (including an `mfg.yml` file).
+2. Build artifacts for each target specified in the `mfg.yml` file.
+
+Therefore, mfgimage creation typically goes something like this:
+
+1. Build boot loader: `newt build <...>`
+2. Create [signed, encrypted] images: `newt create-image -2 <...> 1.2.3.4`
+3. Build mfgimage: `newt mfg create <...>`
+
+An mfgimage created by newt consists of:
+
+1. The binary flashdev contents.
+2. A `manifest.json` file describing the mfgimage.
+3. Build artifacts that were used as inputs.
+
+##### Details
+
+Newt performs a sequence of data structure transformations to produce the outputs listed above.  In the sequence depicted below, objects are enclosed in [brackets], steps are enclosed in (parentheses).
+
+```
+          (decode)          (build)          (emit)
+[MfgDecoder] --> [MfgBuilder] --> [MfgEmitter] --> [OUTPUT]
+```
+
+The steps are described below.
+
+###### 1. Decode
+
+Newt parses and verifies the `mfg.yml` file.
+
+###### 2. Build
+
+Newt uses the output of the decode step to determine which targets are included in the mfgimage.  It ensures the targets have been built and that they all share the same BSP.  Finally, it produces an `artifact/Mfg` object from the necessary binary files.
+
+###### 3. Emit
+
+Newt produces the manifest and writes all the mfgimage files to disk.
diff --git a/newt/mfg/build.go b/newt/mfg/build.go
new file mode 100644
index 00000000..a3f72918
--- /dev/null
+++ b/newt/mfg/build.go
@@ -0,0 +1,564 @@
+/**
+ * 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 mfg
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/artifact/manifest"
+	"mynewt.apache.org/newt/artifact/mfg"
+	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/flashmap"
+	"mynewt.apache.org/newt/newt/parse"
+	"mynewt.apache.org/newt/newt/pkg"
+	"mynewt.apache.org/newt/newt/project"
+	"mynewt.apache.org/newt/newt/target"
+	"mynewt.apache.org/newt/util"
+)
+
+type MfgBuildTarget struct {
+	Target  *target.Target
+	Area    flash.FlashArea
+	Offset  int
+	IsBoot  bool
+	BinPath string
+}
+
+type MfgBuildRaw struct {
+	Filename string
+	Offset   int
+	Area     flash.FlashArea
+}
+
+type MfgBuildMetaMmr struct {
+	Area flash.FlashArea
+}
+
+type MfgBuildMeta struct {
+	Area     flash.FlashArea
+	Hash     bool
+	FlashMap bool
+	Mmrs     []MfgBuildMetaMmr
+}
+
+// Can be used to construct an Mfg object.
+type MfgBuilder struct {
+	BasePkg *pkg.LocalPackage
+	Bsp     *pkg.BspPackage
+	Targets []MfgBuildTarget
+	Raws    []MfgBuildRaw
+	Meta    *MfgBuildMeta
+}
+
+// Searches the provided flash map for the named area.
+func lookUpArea(fm flashmap.FlashMap, name string) (flash.FlashArea, error) {
+	area, ok := fm.Areas[name]
+	if !ok {
+		return flash.FlashArea{}, util.FmtNewtError(
+			"reference to undefined flash area \"%s\"", name)
+	}
+
+	return area, nil
+}
+
+// Searches the project for the target corresponding to the specified decoded
+// entry (read from `mfg.yml`).
+func lookUpTarget(dt DecodedTarget) (*target.Target, error) {
+	t := target.GetTargets()[dt.Name]
+	if t == nil {
+		return nil, util.FmtNewtError(
+			"target entry references undefined target \"%s\"", dt.Name)
+	}
+
+	return t, nil
+}
+
+func normalizeOffset(offset int, length int,
+	area flash.FlashArea) (int, error) {
+
+	areaEnd := area.Offset + area.Size
+	if offset == OFFSET_END {
+		if length > area.Size {
+			return 0, util.FmtNewtError(
+				"segment is too large to fit in flash area \"%s\"; "+
+					"segment=%d, area=%d", area.Name, length, area.Size)
+		}
+		return areaEnd - length, nil
+	}
+
+	if offset+length > area.Size {
+		return 0, util.FmtNewtError(
+			"segment extends beyond end of flash area \"%s\"; "+
+				"offset=%d len=%d area_len=%d",
+			area.Name, offset, length, area.Size)
+	}
+
+	return area.Offset + offset, nil
+}
+
+func calcBsp(dm DecodedMfg,
+	basePkg *pkg.LocalPackage) (*pkg.BspPackage, error) {
+
+	var bspLpkg *pkg.LocalPackage
+	bspMap := map[*pkg.LocalPackage]struct{}{}
+	for _, dt := range dm.Targets {
+		t, err := lookUpTarget(dt)
+		if err != nil {
+			return nil, err
+		}
+
+		bspLpkg = t.Bsp()
+		bspMap[bspLpkg] = struct{}{}
+	}
+
+	if dm.Bsp != "" {
+		var err error
+		bspLpkg, err = project.GetProject().ResolvePackage(
+			basePkg.Repo(), dm.Bsp)
+		if err != nil {
+			return nil, util.FmtNewtError(
+				"failed to resolve BSP package: %s", err.Error())
+		}
+		bspMap[bspLpkg] = struct{}{}
+	}
+
+	if len(bspMap) == 0 {
+		return nil, util.FmtNewtError("at least one target required")
+	}
+
+	if len(bspMap) > 1 {
+		return nil, util.FmtNewtError("multiple BSPs detected")
+	}
+
+	bsp, err := pkg.NewBspPackage(bspLpkg)
+	if err != nil {
+		return nil, util.FmtNewtError(err.Error())
+	}
+
+	return bsp, nil
+}
+
+func (raw *MfgBuildRaw) ToPart(entryIdx int) (Part, error) {
+	data, err := ioutil.ReadFile(raw.Filename)
+	if err != nil {
+		return Part{}, util.ChildNewtError(err)
+	}
+
+	off, err := normalizeOffset(raw.Offset, len(data), raw.Area)
+	if err != nil {
+		return Part{}, err
+	}
+
+	return Part{
+		Name:   fmt.Sprintf("raw-%d (%s)", entryIdx, raw.Filename),
+		Offset: off,
+		Data:   data,
+	}, nil
+}
+
+func (mt *MfgBuildTarget) ToPart() (Part, error) {
+	data, err := ioutil.ReadFile(mt.BinPath)
+	if err != nil {
+		return Part{}, util.ChildNewtError(err)
+	}
+
+	off, err := normalizeOffset(mt.Offset, len(data), mt.Area)
+	if err != nil {
+		return Part{}, err
+	}
+
+	return Part{
+		Name:   fmt.Sprintf("%s (%s)", mt.Area.Name, filepath.Base(mt.BinPath)),
+		Offset: off,
+		Data:   data,
+	}, nil
+}
+
+func newMfgBuildTarget(dt DecodedTarget,
+	fm flashmap.FlashMap) (MfgBuildTarget, error) {
+
+	t, err := lookUpTarget(dt)
+	if err != nil {
+		return MfgBuildTarget{}, err
+	}
+
+	area, err := lookUpArea(fm, dt.Area)
+	if err != nil {
+		return MfgBuildTarget{}, err
+	}
+
+	mpath := builder.ManifestPath(dt.Name, builder.BUILD_NAME_APP,
+		t.App().Name())
+	man, err := manifest.ReadManifest(mpath)
+	if err != nil {
+		return MfgBuildTarget{}, util.FmtNewtError("%s", err.Error())
+	}
+
+	isBoot := parse.ValueIsTrue(man.Syscfg["BOOT_LOADER"])
+
+	return MfgBuildTarget{
+		Target:  t,
+		Area:    area,
+		Offset:  dt.Offset,
+		IsBoot:  isBoot,
+		BinPath: targetSrcBinPath(t, isBoot),
+	}, nil
+}
+
+func newMfgBuildRaw(dr DecodedRaw,
+	fm flashmap.FlashMap, basePath string) (MfgBuildRaw, error) {
+
+	filename := dr.Filename
+	if !strings.HasPrefix(filename, "/") {
+		filename = basePath + "/" + filename
+	}
+
+	area, err := lookUpArea(fm, dr.Area)
+	if err != nil {
+		return MfgBuildRaw{}, err
+	}
+
+	return MfgBuildRaw{
+		Filename: filename,
+		Offset:   dr.Offset,
+		Area:     area,
+	}, nil
+}
+
+func newMfgBuildMeta(dm DecodedMeta,
+	fm flashmap.FlashMap) (MfgBuildMeta, error) {
+
+	area, ok := fm.Areas[dm.Area]
+	if !ok {
+		return MfgBuildMeta{}, util.FmtNewtError(
+			"meta region specifies unrecognized flash area: \"%s\"", dm.Area)
+	}
+
+	var mmrs []MfgBuildMetaMmr
+	for _, dmmr := range dm.Mmrs {
+		area, err := lookUpArea(fm, dmmr.Area)
+		if err != nil {
+			return MfgBuildMeta{}, err
+		}
+		mmr := MfgBuildMetaMmr{
+			Area: area,
+		}
+		mmrs = append(mmrs, mmr)
+	}
+
+	return MfgBuildMeta{
+		Area:     area,
+		Hash:     dm.Hash,
+		FlashMap: dm.FlashMap,
+		Mmrs:     mmrs,
+	}, nil
+}
+
+func (mb *MfgBuilder) parts() ([]Part, error) {
+	parts := []Part{}
+
+	// Create parts from the raw entries.
+	for i, raw := range mb.Raws {
+		part, err := raw.ToPart(i)
+		if err != nil {
+			return nil, err
+		}
+		parts = append(parts, part)
+	}
+
+	// Create parts from the target entries.
+	for _, t := range mb.Targets {
+		part, err := t.ToPart()
+		if err != nil {
+			return nil, err
+		}
+		parts = append(parts, part)
+	}
+
+	// Sort by offset.
+	return SortParts(parts), nil
+}
+
+func (mb *MfgBuilder) detectOverlaps() error {
+	type overlap struct {
+		p1 Part
+		p2 Part
+	}
+
+	overlaps := []overlap{}
+
+	parts, err := mb.parts()
+	if err != nil {
+		return err
+	}
+
+	for i, p1 := range parts[:len(parts)-1] {
+		p1end := p1.Offset + len(p1.Data)
+		for _, p2 := range parts[i+1:] {
+			// Parts are sorted by offset, so only one comparison is
+			// necessary to detect overlap.
+			if p2.Offset < p1end {
+				overlaps = append(overlaps, overlap{
+					p1: p1,
+					p2: p2,
+				})
+			}
+		}
+	}
+
+	if len(overlaps) > 0 {
+		str := "flash overlaps detected:"
+		for _, overlap := range overlaps {
+
+			p1end := overlap.p1.Offset + len(overlap.p1.Data)
+			p2end := overlap.p2.Offset + len(overlap.p2.Data)
+			str += fmt.Sprintf("\n    * [%s] (%d - %d) <=> [%s] (%d - %d)",
+				overlap.p1.Name, overlap.p1.Offset, p1end,
+				overlap.p2.Name, overlap.p2.Offset, p2end)
+		}
+
+		return util.NewNewtError(str)
+	}
+
+	return nil
+}
+
+// Determines which flash device the manufacturing image is intended for.  It
+// is an error if the mfg definition specifies 0 or >1 devices.
+func (mb *MfgBuilder) calcDevice() (int, error) {
+	deviceMap := map[int]struct{}{}
+	for _, t := range mb.Targets {
+		deviceMap[t.Area.Device] = struct{}{}
+	}
+	for _, r := range mb.Raws {
+		deviceMap[r.Area.Device] = struct{}{}
+	}
+
+	devices := make([]int, 0, len(deviceMap))
+	for d, _ := range deviceMap {
+		devices = append(devices, d)
+	}
+	sort.Ints(devices)
+
+	if len(devices) == 0 {
+		return 0, util.FmtNewtError(
+			"manufacturing image definition does not indicate flash device")
+	}
+
+	if len(devices) > 1 {
+		return 0, util.FmtNewtError(
+			"multiple flash devices in use by single manufacturing image: %v",
+			devices)
+	}
+
+	return devices[0], nil
+}
+
+func newMfgBuilder(basePkg *pkg.LocalPackage, dm DecodedMfg,
+	ver image.ImageVersion) (MfgBuilder, error) {
+
+	mb := MfgBuilder{
+		BasePkg: basePkg,
+	}
+
+	bsp, err := calcBsp(dm, basePkg)
+	if err != nil {
+		return mb, err
+	}
+	mb.Bsp = bsp
+
+	for _, dt := range dm.Targets {
+		mbt, err := newMfgBuildTarget(dt, bsp.FlashMap)
+		if err != nil {
+			return mb, err
+		}
+		mb.Targets = append(mb.Targets, mbt)
+	}
+
+	for _, dr := range dm.Raws {
+		mbr, err := newMfgBuildRaw(dr, bsp.FlashMap, basePkg.BasePath())
+		if err != nil {
+			return mb, err
+		}
+		mb.Raws = append(mb.Raws, mbr)
+	}
+
+	if dm.Meta != nil {
+		meta, err := newMfgBuildMeta(*dm.Meta, mb.Bsp.FlashMap)
+		if err != nil {
+			return mb, err
+		}
+		mb.Meta = &meta
+	}
+
+	if _, err := mb.calcDevice(); err != nil {
+		return mb, err
+	}
+
+	if err := mb.detectOverlaps(); err != nil {
+		return mb, err
+	}
+
+	return mb, nil
+}
+
+// Creates a zeroed-out hash MMR TLV.  The hash's original value must be zero
+// for the actual hash to be calculated later.  After the actual value is
+// calculated, it replaces the zeros in the TLV.
+func newZeroHashTlv() mfg.MetaTlv {
+	return mfg.MetaTlv{
+		Header: mfg.MetaTlvHeader{
+			Type: mfg.META_TLV_TYPE_HASH,
+			Size: mfg.META_TLV_HASH_SZ,
+		},
+		Data: make([]byte, mfg.META_HASH_SZ),
+	}
+}
+
+// Creates a flash area MMR TLV.
+func newFlashAreaTlv(area flash.FlashArea) (mfg.MetaTlv, error) {
+	tlv := mfg.MetaTlv{
+		Header: mfg.MetaTlvHeader{
+			Type: mfg.META_TLV_TYPE_FLASH_AREA,
+			Size: mfg.META_TLV_FLASH_AREA_SZ,
+		},
+	}
+
+	body := mfg.MetaTlvBodyFlashArea{
+		Area:   uint8(area.Id),
+		Device: uint8(area.Device),
+		Offset: uint32(area.Offset),
+		Size:   uint32(area.Size),
+	}
+
+	b := &bytes.Buffer{}
+	if err := binary.Write(b, binary.LittleEndian, body); err != nil {
+		return tlv, util.ChildNewtError(err)
+	}
+
+	tlv.Data = b.Bytes()
+
+	return tlv, nil
+}
+
+// Creates an MMR ref TLV.
+func newMmrRefTlv(area flash.FlashArea) (mfg.MetaTlv, error) {
+	tlv := mfg.MetaTlv{
+		Header: mfg.MetaTlvHeader{
+			Type: mfg.META_TLV_TYPE_MMR_REF,
+			Size: mfg.META_TLV_MMR_REF_SZ,
+		},
+	}
+
+	body := mfg.MetaTlvBodyMmrRef{
+		Area: uint8(area.Id),
+	}
+
+	b := &bytes.Buffer{}
+	if err := binary.Write(b, binary.LittleEndian, body); err != nil {
+		return tlv, util.ChildNewtError(err)
+	}
+
+	tlv.Data = b.Bytes()
+
+	return tlv, nil
+}
+
+// Builds a manufacturing meta region.
+func (mb *MfgBuilder) buildMeta() (mfg.Meta, error) {
+	meta := mfg.Meta{
+		Footer: mfg.MetaFooter{
+			Size:    0, // Filled in later.
+			Version: mfg.META_VERSION,
+			Pad8:    0xff,
+			Magic:   mfg.META_MAGIC,
+		},
+	}
+
+	// Hash TLV.
+	if mb.Meta.Hash {
+		meta.Tlvs = append(meta.Tlvs, newZeroHashTlv())
+	}
+
+	// Flash map TLVs.
+	if mb.Meta.FlashMap {
+		for _, area := range mb.Bsp.FlashMap.SortedAreas() {
+			tlv, err := newFlashAreaTlv(area)
+			if err != nil {
+				return meta, err
+			}
+
+			meta.Tlvs = append(meta.Tlvs, tlv)
+		}
+	}
+
+	// MMR ref TLVs.
+	for _, mmr := range mb.Meta.Mmrs {
+		tlv, err := newMmrRefTlv(mmr.Area)
+		if err != nil {
+			return meta, err
+		}
+
+		meta.Tlvs = append(meta.Tlvs, tlv)
+	}
+
+	// Fill in region size in footer now that we know the value.
+	meta.Footer.Size = uint16(meta.Size())
+
+	return meta, nil
+}
+
+// Builds a manufacturing image.
+func (mb *MfgBuilder) Build() (mfg.Mfg, error) {
+	parts, err := mb.parts()
+	if err != nil {
+		return mfg.Mfg{}, err
+	}
+
+	bin, err := PartsBytes(parts)
+	if err != nil {
+		return mfg.Mfg{}, err
+	}
+
+	var metaOff int
+	var metap *mfg.Meta
+	if mb.Meta != nil {
+		meta, err := mb.buildMeta()
+		if err != nil {
+			return mfg.Mfg{}, err
+		}
+		metap = &meta
+		metaOff = mb.Meta.Area.Offset + mb.Meta.Area.Size - meta.Size()
+	}
+
+	return mfg.Mfg{
+		Bin:     bin,
+		Meta:    metap,
+		MetaOff: metaOff,
+	}, nil
+}
diff --git a/newt/mfg/create.go b/newt/mfg/create.go
deleted file mode 100644
index 2ee9f070..00000000
--- a/newt/mfg/create.go
+++ /dev/null
@@ -1,535 +0,0 @@
-/**
- * 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 mfg
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"sort"
-	"time"
-
-	"mynewt.apache.org/newt/newt/builder"
-	"mynewt.apache.org/newt/newt/flash"
-	"mynewt.apache.org/newt/newt/pkg"
-	"mynewt.apache.org/newt/newt/target"
-	"mynewt.apache.org/newt/util"
-)
-
-type mfgManifest struct {
-	BuildTime   string `json:"build_time"`
-	MfgHash     string `json:"mfg_hash"`
-	Version     string `json:"version"`
-	MetaSection int    `json:"meta_section"`
-	MetaOffset  int    `json:"meta_offset"`
-}
-
-type mfgSection struct {
-	offset     int
-	blob       []byte
-}
-
-type createState struct {
-	// {0:[section0], 1:[section1], ...}
-	dsMap      map[int]mfgSection
-	metaOffset int
-	hashOffset int
-	hash       []byte
-}
-
-func insertPartIntoBlob(section mfgSection, part mfgPart) {
-	partEnd := part.offset + len(part.data)
-
-	if len(section.blob) < partEnd {
-		panic("internal error; mfg blob too small")
-	}
-
-	copy(section.blob[part.offset:partEnd], part.data)
-}
-
-func (mi *MfgImage) partFromImage(
-	imgPath string, flashAreaName string) (mfgPart, error) {
-
-	part := mfgPart{
-		// Boot loader and images always go in device 0.
-		device: 0,
-	}
-
-	area, ok := mi.bsp.FlashMap.Areas[flashAreaName]
-	if !ok {
-		return part, util.FmtNewtError(
-			"Image at \"%s\" requires undefined flash area \"%s\"",
-			imgPath, flashAreaName)
-	}
-
-	part.name = fmt.Sprintf("%s (%s)", flashAreaName, filepath.Base(imgPath))
-	part.offset = area.Offset
-
-	var err error
-
-	part.data, err = ioutil.ReadFile(imgPath)
-	if err != nil {
-		return part, util.ChildNewtError(err)
-	}
-
-	overflow := len(part.data) - area.Size
-	if overflow > 0 {
-		return part, util.FmtNewtError(
-			"Image \"%s\" is too large to fit in flash area \"%s\"; "+
-				"image-size=%d flash-area-size=%d overflow=%d",
-			imgPath, flashAreaName, len(part.data), area.Size, overflow)
-	}
-
-	// If an image slot is used, the entire flash area is unwritable.  This
-	// restriction comes from the boot loader's need to write status at the end
-	// of an area.  Pad out part with unwriten flash (0xff).  This probably
-	// isn't terribly efficient...
-	for i := 0; i < -overflow; i++ {
-		part.data = append(part.data, 0xff)
-	}
-
-	return part, nil
-}
-
-func partFromRawEntry(entry MfgRawEntry, entryIdx int) mfgPart {
-	return mfgPart{
-		name:   fmt.Sprintf("entry-%d (%s)", entryIdx, entry.filename),
-		offset: entry.offset,
-		data:   entry.data,
-	}
-}
-
-func (mi *MfgImage) targetParts() ([]mfgPart, error) {
-	parts := []mfgPart{}
-
-	bootPath := mi.dstBootBinPath()
-	if bootPath != "" {
-		bootPart, err := mi.partFromImage(
-			bootPath, flash.FLASH_AREA_NAME_BOOTLOADER)
-		if err != nil {
-			return nil, err
-		}
-
-		parts = append(parts, bootPart)
-	}
-
-	for i := 0; i < 2; i++ {
-		imgPath := mi.dstImgPath(i)
-		if imgPath != "" {
-			areaName, err := areaNameFromImgIdx(i)
-			if err != nil {
-				return nil, err
-			}
-
-			part, err := mi.partFromImage(imgPath, areaName)
-			if err != nil {
-				return nil, err
-			}
-			parts = append(parts, part)
-		}
-	}
-
-	return parts, nil
-}
-
-func sectionSize(parts []mfgPart) (int, int) {
-	greatest := 0
-	lowest := 0
-	if len(parts) > 0 {
-		lowest = parts[0].offset
-	}
-	for _, part := range parts {
-		lowest = util.IntMin(lowest, part.offset)
-	}
-	for _, part := range parts {
-		end := part.offset + len(part.data)
-		greatest = util.IntMax(greatest, end)
-	}
-
-	return lowest, greatest
-}
-
-func sectionFromParts(parts []mfgPart) mfgSection {
-	offset, sectionSize := sectionSize(parts)
-	blob := make([]byte, sectionSize)
-
-	section := mfgSection{
-		offset: offset,
-		blob:   blob,
-	}
-
-	// Initialize section 0's data as unwritten flash (0xff).
-	for i, _ := range blob {
-		blob[i] = 0xff
-	}
-
-	for _, part := range parts {
-		insertPartIntoBlob(section, part)
-	}
-
-	return section
-}
-
-func (mi *MfgImage) devicePartMap() (map[int][]mfgPart, error) {
-	dpMap := map[int][]mfgPart{}
-
-	// Create parts from the raw entries.
-	for i, entry := range mi.rawEntries {
-		part := partFromRawEntry(entry, i)
-		dpMap[entry.device] = append(dpMap[entry.device], part)
-	}
-
-	// Insert the boot loader and image parts into section 0.
-	targetParts, err := mi.targetParts()
-	if err != nil {
-		return nil, err
-	}
-	dpMap[0] = append(dpMap[0], targetParts...)
-
-	// Sort each part slice by offset.
-	for device, _ := range dpMap {
-		sortParts(dpMap[device])
-	}
-
-	return dpMap, nil
-}
-
-func (mi *MfgImage) deviceSectionMap() (map[int]mfgSection, error) {
-	dpMap, err := mi.devicePartMap()
-	if err != nil {
-		return nil, err
-	}
-
-	// Convert each part slice into a section.
-	dsMap := map[int]mfgSection{}
-	for device, parts := range dpMap {
-		dsMap[device] = sectionFromParts(parts)
-	}
-
-	return dsMap, nil
-}
-
-func (mi *MfgImage) createSections() (createState, error) {
-	cs := createState{}
-
-	var err error
-
-	if err := mi.detectOverlaps(); err != nil {
-		return cs, err
-	}
-
-	cs.dsMap, err = mi.deviceSectionMap()
-	if err != nil {
-		return cs, err
-	}
-
-	if len(cs.dsMap) < 1 {
-		panic("Invalid state; no section 0")
-	}
-
-	cs.metaOffset, cs.hashOffset, err = insertMeta(cs.dsMap[0].blob,
-		mi.bsp.FlashMap)
-	if err != nil {
-		return cs, err
-	}
-
-	// Calculate manufacturing hash.
-	devices := make([]int, 0, len(cs.dsMap))
-	for device, _ := range cs.dsMap {
-		devices = append(devices, device)
-	}
-	sort.Ints(devices)
-
-	sections := make([][]byte, len(devices))
-	for i, device := range devices {
-		sections[i] = cs.dsMap[device].blob
-	}
-	cs.hash = calcMetaHash(sections)
-	copy(cs.dsMap[0].blob[cs.hashOffset:cs.hashOffset+META_HASH_SZ], cs.hash)
-
-	return cs, nil
-}
-
-func areaNameFromImgIdx(imgIdx int) (string, error) {
-	switch imgIdx {
-	case 0:
-		return flash.FLASH_AREA_NAME_IMAGE_0, nil
-	case 1:
-		return flash.FLASH_AREA_NAME_IMAGE_1, nil
-	default:
-		return "", util.FmtNewtError("invalid image index: %d", imgIdx)
-	}
-}
-
-func bootLoaderFromPaths(t *target.Target) []string {
-	return []string{
-		/* boot.elf */
-		builder.AppElfPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name()),
-
-		/* boot.elf.bin */
-		builder.AppBinPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name()),
-
-		/* manifest.json */
-		builder.ManifestPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name()),
-	}
-}
-
-func loaderFromPaths(t *target.Target) []string {
-	if t.LoaderName == "" {
-		return nil
-	}
-
-	return []string{
-		/* <loader>.elf */
-		builder.AppElfPath(t.Name(), builder.BUILD_NAME_LOADER,
-			t.Loader().Name()),
-
-		/* <app>.img */
-		builder.AppImgPath(t.Name(), builder.BUILD_NAME_LOADER,
-			t.Loader().Name()),
-	}
-}
-
-func appFromPaths(t *target.Target) []string {
-	return []string{
-		/* <app>.elf */
-		builder.AppElfPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name()),
-
-		/* <app>.img */
-		builder.AppImgPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name()),
-
-		/* manifest.json */
-		builder.ManifestPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name()),
-	}
-}
-
-func imageFromPaths(t *target.Target) []string {
-	paths := loaderFromPaths(t)
-	paths = append(paths, appFromPaths(t)...)
-	return paths
-}
-
-func (mi *MfgImage) copyBinFile(srcPath string, dstDir string) error {
-	dstPath := dstDir + "/" + filepath.Base(srcPath)
-
-	util.StatusMessage(util.VERBOSITY_VERBOSE, "copying file %s --> %s\n",
-		srcPath, dstPath)
-
-	if err := util.CopyFile(srcPath, dstPath); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (mi *MfgImage) copyBinFiles() error {
-	dstPath := MfgBinDir(mi.basePkg.Name())
-	if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
-		return util.ChildNewtError(err)
-	}
-
-	bootPaths := bootLoaderFromPaths(mi.boot)
-	for _, path := range bootPaths {
-		dstDir := MfgBootDir(mi.basePkg.Name())
-		if err := mi.copyBinFile(path, dstDir); err != nil {
-			return err
-		}
-	}
-
-	for i, imgTarget := range mi.images {
-		imgPaths := imageFromPaths(imgTarget)
-		dstDir := MfgImageBinDir(mi.basePkg.Name(), i)
-		for _, path := range imgPaths {
-			if err := mi.copyBinFile(path, dstDir); err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
-func (mi *MfgImage) dstBootBinPath() string {
-	if mi.boot == nil {
-		return ""
-	}
-
-	return fmt.Sprintf("%s/%s.elf.bin",
-		MfgBootDir(mi.basePkg.Name()),
-		pkg.ShortName(mi.boot.App()))
-}
-
-func (mi *MfgImage) dstImgPath(slotIdx int) string {
-	var pack *pkg.LocalPackage
-	var imgIdx int
-
-	if len(mi.images) >= 1 {
-		switch slotIdx {
-		case 0:
-			if mi.images[0].LoaderName != "" {
-				pack = mi.images[0].Loader()
-			} else {
-				pack = mi.images[0].App()
-			}
-			imgIdx = 0
-
-		case 1:
-			if mi.images[0].LoaderName != "" {
-				pack = mi.images[0].App()
-				imgIdx = 0
-			} else {
-				if len(mi.images) >= 2 {
-					pack = mi.images[1].App()
-				}
-				imgIdx = 1
-			}
-
-		default:
-			panic(fmt.Sprintf("invalid image index: %d", imgIdx))
-		}
-	}
-
-	if pack == nil {
-		return ""
-	}
-
-	return fmt.Sprintf("%s/%s.img",
-		MfgImageBinDir(mi.basePkg.Name(), imgIdx), pkg.ShortName(pack))
-}
-
-// Returns a slice containing the path of each file required to build the
-// manufacturing image.
-func (mi *MfgImage) FromPaths() []string {
-	paths := []string{}
-
-	if mi.boot != nil {
-		paths = append(paths, bootLoaderFromPaths(mi.boot)...)
-	}
-	if len(mi.images) >= 1 {
-		paths = append(paths, imageFromPaths(mi.images[0])...)
-	}
-	if len(mi.images) >= 2 {
-		paths = append(paths, imageFromPaths(mi.images[1])...)
-	}
-
-	for _, raw := range mi.rawEntries {
-		paths = append(paths, raw.filename)
-	}
-
-	return paths
-}
-
-func (mi *MfgImage) build() (createState, error) {
-	if err := mi.copyBinFiles(); err != nil {
-		return createState{}, err
-	}
-
-	cs, err := mi.createSections()
-	if err != nil {
-		return cs, err
-	}
-
-	return cs, nil
-}
-
-func (mi *MfgImage) createManifest(cs createState) ([]byte, error) {
-	manifest := mfgManifest{
-		BuildTime:   time.Now().Format(time.RFC3339),
-		Version:     mi.version.String(),
-		MfgHash:     fmt.Sprintf("%x", cs.hash),
-		MetaSection: 0,
-		MetaOffset:  cs.metaOffset,
-	}
-	buffer, err := json.MarshalIndent(manifest, "", "  ")
-	if err != nil {
-		return nil, util.FmtNewtError("Failed to encode mfg manifest: %s",
-			err.Error())
-	}
-
-	return buffer, nil
-}
-
-func appendNonEmptyStr(dst []string, src string) []string {
-	if src != "" {
-		dst = append(dst, src)
-	}
-
-	return dst
-}
-
-func (mi *MfgImage) ToPaths() []string {
-	paths := []string{}
-
-	paths = appendNonEmptyStr(paths, mi.BootBinPath())
-	paths = appendNonEmptyStr(paths, mi.BootElfPath())
-	paths = appendNonEmptyStr(paths, mi.BootManifestPath())
-
-	for i := 0; i < len(mi.images); i++ {
-		paths = appendNonEmptyStr(paths, mi.LoaderImgPath(i))
-		paths = appendNonEmptyStr(paths, mi.LoaderElfPath(i))
-		paths = appendNonEmptyStr(paths, mi.AppImgPath(i))
-		paths = appendNonEmptyStr(paths, mi.AppElfPath(i))
-		paths = appendNonEmptyStr(paths, mi.ImageManifestPath(i))
-	}
-
-	paths = append(paths, mi.SectionBinPaths()...)
-	paths = append(paths, mi.SectionHexPaths()...)
-	paths = append(paths, mi.ManifestPath())
-
-	return paths
-}
-
-// @return                      [paths-of-artifacts], error
-func (mi *MfgImage) CreateMfgImage() ([]string, error) {
-	cs, err := mi.build()
-	if err != nil {
-		return nil, err
-	}
-
-	sectionDir := MfgSectionBinDir(mi.basePkg.Name())
-	if err := os.MkdirAll(sectionDir, 0755); err != nil {
-		return nil, util.ChildNewtError(err)
-	}
-
-	for device, section := range cs.dsMap {
-		sectionPath := MfgSectionBinPath(mi.basePkg.Name(), device)
-		err := ioutil.WriteFile(sectionPath, section.blob[section.offset:], 0644)
-		if err != nil {
-			return nil, util.ChildNewtError(err)
-		}
-		hexPath := MfgSectionHexPath(mi.basePkg.Name(), device)
-		mi.compiler.ConvertBinToHex(sectionPath, hexPath, section.offset)
-	}
-
-	manifest, err := mi.createManifest(cs)
-	if err != nil {
-		return nil, err
-	}
-
-	manifestPath := mi.ManifestPath()
-	if err := ioutil.WriteFile(manifestPath, manifest, 0644); err != nil {
-		return nil, util.FmtNewtError("Failed to write mfg manifest file: %s",
-			err.Error())
-	}
-
-	return mi.ToPaths(), nil
-}
diff --git a/newt/mfg/decode.go b/newt/mfg/decode.go
new file mode 100644
index 00000000..802e88d6
--- /dev/null
+++ b/newt/mfg/decode.go
@@ -0,0 +1,310 @@
+/**
+ * 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 contains functionality for loading mfg definitions from `mfg.yml`
+// files.
+
+package mfg
+
+import (
+	"github.com/spf13/cast"
+
+	"mynewt.apache.org/newt/newt/ycfg"
+	"mynewt.apache.org/newt/util"
+)
+
+// Indicates that an element is located at the end of a flash area.
+const OFFSET_END = -1
+
+type DecodedTarget struct {
+	Name   string
+	Area   string
+	Offset int
+}
+
+type DecodedRaw struct {
+	Filename string
+	Area     string
+	Offset   int
+}
+
+type DecodedMmrRef struct {
+	Area string
+}
+
+type DecodedMeta struct {
+	Area     string
+	Hash     bool
+	FlashMap bool
+	Mmrs     []DecodedMmrRef
+}
+
+type DecodedMfg struct {
+	Targets []DecodedTarget
+	Raws    []DecodedRaw
+	Meta    *DecodedMeta
+
+	// Only required if no targets present.
+	Bsp string
+}
+
+func decodeOffsetStr(offsetStr string) (int, error) {
+	if offsetStr == "end" {
+		return OFFSET_END, nil
+	}
+
+	offsetInt, err := cast.ToIntE(offsetStr)
+	if err != nil {
+		return 0, util.FmtNewtError("invalid offset value: \"%s\"", offsetStr)
+	}
+
+	return offsetInt, nil
+}
+
+func decodeBool(kv map[string]interface{}, key string) (*bool, error) {
+	var bp *bool
+
+	val := kv[key]
+	if val != nil {
+		b, err := cast.ToBoolE(val)
+		if err != nil {
+			return nil, util.FmtNewtError(
+				"invalid `%s` value \"%v\"; "+
+					"value must be either \"true\" or \"false\"", key, val)
+		}
+
+		bp = &b
+	}
+
+	return bp, nil
+}
+
+func decodeBoolDflt(kv map[string]interface{}, key string,
+	dflt bool) (bool, error) {
+
+	bp, err := decodeBool(kv, key)
+	if err != nil {
+		return false, err
+	}
+
+	if bp == nil {
+		return dflt, nil
+	} else {
+		return *bp, nil
+	}
+}
+
+func decodeTarget(yamlTarget interface{}) (DecodedTarget, error) {
+	dt := DecodedTarget{}
+
+	kv, err := cast.ToStringMapE(yamlTarget)
+	if err != nil {
+		return dt, util.FmtNewtError(
+			"mfg contains invalid `mfg.targets` map: %s", err.Error())
+	}
+
+	nameVal := kv["name"]
+	if nameVal == nil {
+		return dt, util.FmtNewtError(
+			"mfg target entry missing required field \"name\"")
+	}
+	dt.Name = cast.ToString(nameVal)
+
+	areaVal := kv["area"]
+	if areaVal == nil {
+		return dt, util.FmtNewtError(
+			"target entry \"%s\" missing required field \"area\"", dt.Name)
+	}
+	dt.Area = cast.ToString(areaVal)
+
+	offsetVal := kv["offset"]
+	if offsetVal == nil {
+		return dt, util.FmtNewtError(
+			"target entry \"%s\" missing required field \"offset\"", dt.Name)
+	}
+	offsetStr := cast.ToString(offsetVal)
+	offsetInt, err := decodeOffsetStr(offsetStr)
+	if err != nil {
+		return dt, util.FmtNewtError(
+			"in target entry \"%s\": %s", dt.Name, err.Error())
+	}
+	dt.Offset = offsetInt
+
+	return dt, nil
+}
+
+func decodeRaw(yamlRaw interface{}, entryIdx int) (DecodedRaw, error) {
+	dr := DecodedRaw{}
+
+	kv, err := cast.ToStringMapE(yamlRaw)
+	if err != nil {
+		return dr, util.FmtNewtError(
+			"mfg contains invalid `mfg.raw` map: %s", err.Error())
+	}
+
+	areaVal := kv["area"]
+	if areaVal == nil {
+		return dr, util.FmtNewtError(
+			"raw entry missing required field \"area\"")
+	}
+	dr.Area = cast.ToString(areaVal)
+
+	offsetVal := kv["offset"]
+	if offsetVal == nil {
+		return dr, util.FmtNewtError(
+			"mfg raw entry missing required field \"offset\"")
+	}
+	offsetStr := cast.ToString(offsetVal)
+	offsetInt, err := decodeOffsetStr(offsetStr)
+	if err != nil {
+		return dr, util.FmtNewtError(
+			"in raw entry %d: %s", entryIdx, err.Error())
+	}
+	dr.Offset = offsetInt
+
+	filenameVal := kv["name"]
+	if filenameVal == nil {
+		return dr, util.FmtNewtError(
+			"mfg raw entry missing required field \"filename\"")
+	}
+	dr.Filename = cast.ToString(filenameVal)
+
+	return dr, nil
+}
+
+func decodeMmr(yamlMmr interface{}) (DecodedMmrRef, error) {
+	dm := DecodedMmrRef{}
+
+	kv, err := cast.ToStringMapE(yamlMmr)
+	if err != nil {
+		return dm, util.FmtNewtError(
+			"mfg meta contains invalid `mmrs` sequence: %s", err.Error())
+	}
+
+	areaVal := kv["area"]
+	if areaVal == nil {
+		return dm, util.FmtNewtError(
+			"mmr entry missing required field \"area\"")
+	}
+	dm.Area = cast.ToString(areaVal)
+
+	return dm, nil
+}
+
+func decodeMmrs(yamlMmrs interface{}) ([]DecodedMmrRef, error) {
+	yamlSlice, err := cast.ToSliceE(yamlMmrs)
+	if err != nil {
+		return nil, util.FmtNewtError(
+			"mfg meta contains invalid `mmrs` sequence: %s", err.Error())
+	}
+
+	mmrs := []DecodedMmrRef{}
+	for _, yamlMmr := range yamlSlice {
+		mmr, err := decodeMmr(yamlMmr)
+		if err != nil {
+			return nil, err
+		}
+		mmrs = append(mmrs, mmr)
+	}
+
+	return mmrs, nil
+}
+
+func decodeMeta(
+	kv map[string]interface{}) (DecodedMeta, error) {
+
+	dm := DecodedMeta{}
+
+	areaVal := kv["area"]
+	if areaVal == nil {
+		return dm, util.FmtNewtError(
+			"meta map missing required field \"area\"")
+	}
+	dm.Area = cast.ToString(areaVal)
+
+	hash, err := decodeBoolDflt(kv, "hash", false)
+	if err != nil {
+		return dm, err
+	}
+	dm.Hash = hash
+
+	fm, err := decodeBoolDflt(kv, "flash_map", false)
+	if err != nil {
+		return dm, err
+	}
+	dm.FlashMap = fm
+
+	yamlMmrs := kv["mmrs"]
+	if yamlMmrs != nil {
+		mmrs, err := decodeMmrs(yamlMmrs)
+		if err != nil {
+			return dm, err
+		}
+		dm.Mmrs = mmrs
+	}
+
+	return dm, nil
+}
+
+func decodeMfg(yc ycfg.YCfg) (DecodedMfg, error) {
+	dm := DecodedMfg{}
+
+	yamlTargets := yc.GetValSlice("mfg.targets", nil)
+	if yamlTargets != nil {
+		for _, yamlTarget := range yamlTargets {
+			t, err := decodeTarget(yamlTarget)
+			if err != nil {
+				return dm, err
+			}
+
+			dm.Targets = append(dm.Targets, t)
+		}
+	}
+
+	dm.Bsp = yc.GetValString("mfg.bsp", nil)
+
+	if len(dm.Targets) == 0 && dm.Bsp == "" {
+		return dm, util.FmtNewtError(
+			"\"mfg.bsp\" field required for mfg images without any targets")
+	}
+
+	itf := yc.GetValSlice("mfg.raw", nil)
+	slice := cast.ToSlice(itf)
+	if slice != nil {
+		for i, yamlRaw := range slice {
+			raw, err := decodeRaw(yamlRaw, i)
+			if err != nil {
+				return dm, err
+			}
+
+			dm.Raws = append(dm.Raws, raw)
+		}
+	}
+
+	yamlMeta := yc.GetValStringMap("mfg.meta", nil)
+	if yamlMeta != nil {
+		meta, err := decodeMeta(yamlMeta)
+		if err != nil {
+			return dm, err
+		}
+		dm.Meta = &meta
+	}
+
+	return dm, nil
+}
diff --git a/newt/mfg/emit.go b/newt/mfg/emit.go
new file mode 100644
index 00000000..3974fda2
--- /dev/null
+++ b/newt/mfg/emit.go
@@ -0,0 +1,343 @@
+/**
+ * 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 mfg
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"time"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/artifact/manifest"
+	"mynewt.apache.org/newt/artifact/mfg"
+	"mynewt.apache.org/newt/artifact/misc"
+	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/flashmap"
+	"mynewt.apache.org/newt/newt/target"
+	"mynewt.apache.org/newt/util"
+)
+
+// Current manufacturing image binary format version.
+const MANIFEST_FORMAT = 2
+
+// Represents a file copy operation.
+type CpEntry struct {
+	From string
+	To   string
+}
+
+type MfgEmitTarget struct {
+	Name         string
+	Offset       int
+	IsBoot       bool
+	BinPath      string
+	ElfPath      string
+	ManifestPath string
+}
+
+type MfgEmitRaw struct {
+	Filename string
+	Offset   int
+}
+
+type MfgEmitMetaMmr struct {
+	Area flash.FlashArea
+}
+
+type MfgEmitMeta struct {
+	Offset   int
+	Hash     bool
+	FlashMap bool
+	Mmrs     []MfgEmitMetaMmr
+}
+
+type MfgEmitter struct {
+	Name    string
+	Ver     image.ImageVersion
+	Targets []MfgEmitTarget
+	Raws    []MfgEmitRaw
+	Meta    *MfgEmitMeta
+
+	Mfg      mfg.Mfg
+	Device   int
+	FlashMap flashmap.FlashMap
+	BspName  string
+}
+
+// Calculates the source path of a target's binary.  Boot loader targets use
+// `.bin` files; image targets use `.img`.
+func targetSrcBinPath(t *target.Target, isBoot bool) string {
+	if isBoot {
+		return builder.AppBinPath(t.Name(), builder.BUILD_NAME_APP,
+			t.App().Name())
+	} else {
+		return builder.AppImgPath(t.Name(), builder.BUILD_NAME_APP,
+			t.App().Name())
+	}
+}
+
+// Calculates the source path of a target's `.elf` file.
+func targetSrcElfPath(t *target.Target) string {
+	return builder.AppElfPath(t.Name(), builder.BUILD_NAME_APP, t.App().Name())
+}
+
+// Calculates the source path of a target's manifest file.
+func targetSrcManifestPath(t *target.Target) string {
+	return builder.ManifestPath(t.Name(), builder.BUILD_NAME_APP,
+		t.App().Name())
+}
+
+func newMfgEmitTarget(bt MfgBuildTarget) (MfgEmitTarget, error) {
+	return MfgEmitTarget{
+		Name:    bt.Target.FullName(),
+		Offset:  bt.Area.Offset + bt.Offset,
+		IsBoot:  bt.IsBoot,
+		BinPath: targetSrcBinPath(bt.Target, bt.IsBoot),
+		ElfPath: targetSrcElfPath(bt.Target),
+		ManifestPath: builder.ManifestPath(bt.Target.Name(),
+			builder.BUILD_NAME_APP, bt.Target.App().Name()),
+	}, nil
+}
+
+func newMfgEmitRaw(br MfgBuildRaw) MfgEmitRaw {
+	return MfgEmitRaw{
+		Filename: br.Filename,
+		Offset:   br.Area.Offset + br.Offset,
+	}
+}
+
+func newMfgEmitMeta(bm MfgBuildMeta, metaOff int) MfgEmitMeta {
+	mmrs := []MfgEmitMetaMmr{}
+	for _, bmmr := range bm.Mmrs {
+		mmr := MfgEmitMetaMmr{
+			Area: bmmr.Area,
+		}
+		mmrs = append(mmrs, mmr)
+	}
+
+	return MfgEmitMeta{
+		Offset:   bm.Area.Offset + metaOff,
+		Hash:     bm.Hash,
+		FlashMap: bm.FlashMap,
+		Mmrs:     mmrs,
+	}
+}
+
+// NewMfgEmitter creates an mfg emitter from an mfg builder.
+func NewMfgEmitter(mb MfgBuilder, name string, ver image.ImageVersion,
+	device int) (MfgEmitter, error) {
+
+	me := MfgEmitter{
+		Name:     name,
+		Ver:      ver,
+		Device:   device,
+		FlashMap: mb.Bsp.FlashMap,
+		BspName:  mb.Bsp.FullName(),
+	}
+
+	m, err := mb.Build()
+	if err != nil {
+		return me, err
+	}
+	me.Mfg = m
+
+	for _, bt := range mb.Targets {
+		et, err := newMfgEmitTarget(bt)
+		if err != nil {
+			return me, err
+		}
+
+		me.Targets = append(me.Targets, et)
+	}
+
+	for _, br := range mb.Raws {
+		et := newMfgEmitRaw(br)
+		me.Raws = append(me.Raws, et)
+	}
+
+	if mb.Meta != nil {
+		mm := newMfgEmitMeta(*mb.Meta, me.Mfg.MetaOff)
+		me.Meta = &mm
+	}
+
+	return me, nil
+}
+
+// Calculates the necessary file copy operations for emitting an mfg image.
+func (me *MfgEmitter) calcCpEntries() []CpEntry {
+	entries := []CpEntry{}
+	for i, mt := range me.Targets {
+		var binTo string
+		if mt.IsBoot {
+			binTo = MfgTargetBinPath(i)
+		} else {
+			binTo = MfgTargetImgPath(i)
+		}
+
+		entry := CpEntry{
+			From: mt.BinPath,
+			To:   MfgBinDir(me.Name) + "/" + binTo,
+		}
+		entries = append(entries, entry)
+
+		entry = CpEntry{
+			From: mt.ElfPath,
+			To: MfgBinDir(me.Name) + "/" +
+				MfgTargetElfPath(i),
+		}
+		entries = append(entries, entry)
+
+		entry = CpEntry{
+			From: mt.ManifestPath,
+			To: MfgBinDir(me.Name) + "/" +
+				MfgTargetManifestPath(i),
+		}
+		entries = append(entries, entry)
+	}
+
+	return entries
+}
+
+func copyBinFiles(entries []CpEntry) error {
+	for _, entry := range entries {
+		if err := os.MkdirAll(filepath.Dir(entry.To), 0755); err != nil {
+			return util.ChildNewtError(err)
+		}
+
+		util.StatusMessage(util.VERBOSITY_VERBOSE, "copying file %s --> %s\n",
+			entry.From, entry.To)
+
+		if err := util.CopyFile(entry.From, entry.To); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// emitManifest generates an mfg manifest.
+func (me *MfgEmitter) emitManifest() ([]byte, error) {
+	hashBytes, err := me.Mfg.Hash()
+	if err != nil {
+		return nil, err
+	}
+
+	mm := manifest.MfgManifest{
+		Name:       me.Name,
+		BuildTime:  time.Now().Format(time.RFC3339),
+		Format:     MANIFEST_FORMAT,
+		MfgHash:    misc.HashString(hashBytes),
+		Version:    me.Ver.String(),
+		Device:     me.Device,
+		BinPath:    mfg.MFG_IMG_FILENAME,
+		FlashAreas: me.FlashMap.SortedAreas(),
+		Bsp:        me.BspName,
+	}
+
+	for i, t := range me.Targets {
+		mmt := manifest.MfgManifestTarget{
+			Name:         t.Name,
+			ManifestPath: MfgTargetManifestPath(i),
+			Offset:       t.Offset,
+		}
+
+		if t.IsBoot {
+			mmt.BinPath = MfgTargetBinPath(i)
+		} else {
+			mmt.ImagePath = MfgTargetImgPath(i)
+		}
+
+		mm.Targets = append(mm.Targets, mmt)
+	}
+
+	if me.Meta != nil {
+		mmm := manifest.MfgManifestMeta{
+			EndOffset: me.Mfg.MetaOff + int(me.Mfg.Meta.Footer.Size),
+			Size:      int(me.Mfg.Meta.Footer.Size),
+		}
+
+		mmm.Hash = me.Meta.Hash
+		mmm.FlashMap = me.Meta.FlashMap
+
+		for _, mmr := range me.Meta.Mmrs {
+			mmm.Mmrs = append(mmm.Mmrs, manifest.MfgManifestMetaMmr{
+				Area:      mmr.Area.Name,
+				Device:    mmr.Area.Device,
+				EndOffset: mmr.Area.Offset + mmr.Area.Size,
+			})
+		}
+
+		mm.Meta = &mmm
+	}
+
+	return mm.MarshalJson()
+}
+
+// @return                      [source-paths], [dest-paths], error
+func (me *MfgEmitter) Emit() ([]string, []string, error) {
+	if err := me.Mfg.RecalcHash(0xff); err != nil {
+		return nil, nil, err
+	}
+
+	mbin, err := me.Mfg.Bytes(0xff)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cpEntries := me.calcCpEntries()
+	if err := copyBinFiles(cpEntries); err != nil {
+		return nil, nil, err
+	}
+
+	// Write mfgimg.bin
+	binPath := MfgBinPath(me.Name)
+	if err := os.MkdirAll(filepath.Dir(binPath), 0755); err != nil {
+		return nil, nil, util.ChildNewtError(err)
+	}
+	if err := ioutil.WriteFile(binPath, mbin, 0644); err != nil {
+		return nil, nil, err
+	}
+
+	// Write manifest.
+	manifest, err := me.emitManifest()
+	if err != nil {
+		return nil, nil, err
+	}
+
+	manifestPath := MfgManifestPath(me.Name)
+	if err := ioutil.WriteFile(manifestPath, manifest, 0644); err != nil {
+		return nil, nil, util.FmtNewtError(
+			"Failed to write mfg manifest file: %s", err.Error())
+	}
+
+	srcPaths := []string{}
+	dstPaths := []string{
+		binPath,
+		manifestPath,
+	}
+	for _, entry := range cpEntries {
+		srcPaths = append(srcPaths, entry.From)
+		dstPaths = append(dstPaths, entry.To)
+	}
+
+	return srcPaths, dstPaths, nil
+}
diff --git a/newt/mfg/load.go b/newt/mfg/load.go
deleted file mode 100644
index e17635b1..00000000
--- a/newt/mfg/load.go
+++ /dev/null
@@ -1,317 +0,0 @@
-/**
- * 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 mfg
-
-import (
-	"fmt"
-	"io/ioutil"
-	"sort"
-	"strconv"
-	"strings"
-
-	"github.com/spf13/cast"
-
-	"mynewt.apache.org/newt/newt/newtutil"
-	"mynewt.apache.org/newt/newt/pkg"
-	"mynewt.apache.org/newt/newt/project"
-	"mynewt.apache.org/newt/newt/target"
-	"mynewt.apache.org/newt/newt/toolchain"
-	"mynewt.apache.org/newt/util"
-)
-
-const MFG_YAML_FILENAME string = "mfg.yml"
-
-type partSorter struct {
-	parts []mfgPart
-}
-
-func (s partSorter) Len() int {
-	return len(s.parts)
-}
-func (s partSorter) Swap(i, j int) {
-	s.parts[i], s.parts[j] = s.parts[j], s.parts[i]
-}
-func (s partSorter) Less(i, j int) bool {
-	return s.parts[i].offset < s.parts[j].offset
-}
-
-func sortParts(parts []mfgPart) []mfgPart {
-	sorter := partSorter{
-		parts: parts,
-	}
-
-	sort.Sort(sorter)
-	return sorter.parts
-}
-
-func (mi *MfgImage) loadError(
-	msg string, args ...interface{}) *util.NewtError {
-
-	return util.FmtNewtError("Error in %s mfg: %s", mi.basePkg.Name(),
-		fmt.Sprintf(msg, args...))
-
-}
-
-func (mi *MfgImage) loadTarget(targetName string) (
-	*target.Target, error) {
-
-	tgt := target.GetTargets()[targetName]
-	if tgt == nil {
-		return nil, mi.loadError("cannot resolve referenced target \"%s\"",
-			targetName)
-	}
-
-	return tgt, nil
-}
-
-func (mi *MfgImage) loadRawEntry(
-	entryIdx int, rawEntry map[string]string) (MfgRawEntry, error) {
-
-	raw := MfgRawEntry{}
-
-	var err error
-
-	deviceStr := rawEntry["device"]
-	if deviceStr == "" {
-		return raw, mi.loadError(
-			"raw entry %d missing required \"device\" field", entryIdx)
-	}
-
-	raw.device, err = util.AtoiNoOct(deviceStr)
-	if err != nil {
-		return raw, mi.loadError(
-			"raw entry %d contains invalid offset: %s", entryIdx, deviceStr)
-	}
-
-	offsetStr := rawEntry["offset"]
-	if offsetStr == "" {
-		return raw, mi.loadError(
-			"raw entry %d missing required \"offset\" field", entryIdx)
-	}
-
-	raw.offset, err = util.AtoiNoOct(offsetStr)
-	if err != nil {
-		return raw, mi.loadError(
-			"raw entry %d contains invalid offset: %s", entryIdx, offsetStr)
-	}
-
-	raw.filename = rawEntry["file"]
-	if raw.filename == "" {
-		return raw, mi.loadError(
-			"raw entry %d missing required \"file\" field", entryIdx)
-	}
-
-	if !strings.HasPrefix(raw.filename, "/") {
-		raw.filename = mi.basePkg.BasePath() + "/" + raw.filename
-	}
-
-	raw.data, err = ioutil.ReadFile(raw.filename)
-	if err != nil {
-		return raw, mi.loadError(
-			"error loading file for raw entry %d; filename=%s: %s",
-			entryIdx, raw.filename, err.Error())
-	}
-
-	return raw, nil
-}
-
-func (mi *MfgImage) detectInvalidDevices() error {
-	sectionIds := mi.sectionIds()
-	deviceIds := mi.bsp.FlashMap.DeviceIds()
-
-	deviceMap := map[int]struct{}{}
-	for _, device := range deviceIds {
-		deviceMap[device] = struct{}{}
-	}
-
-	invalidIds := []int{}
-	for _, sectionId := range sectionIds {
-		if _, ok := deviceMap[sectionId]; !ok {
-			invalidIds = append(invalidIds, sectionId)
-		}
-	}
-
-	if len(invalidIds) == 0 {
-		return nil
-	}
-
-	listStr := ""
-	for i, id := range invalidIds {
-		if i != 0 {
-			listStr += ", "
-		}
-		listStr += strconv.Itoa(id)
-	}
-
-	return util.FmtNewtError(
-		"image specifies flash devices that are not present in the BSP's "+
-			"flash map: %s", listStr)
-}
-
-func (mi *MfgImage) detectOverlaps() error {
-	type overlap struct {
-		part0 mfgPart
-		part1 mfgPart
-	}
-
-	overlaps := []overlap{}
-
-	dpMap, err := mi.devicePartMap()
-	if err != nil {
-		return err
-	}
-
-	// Iterate flash devices in order.
-	devices := make([]int, 0, len(dpMap))
-	for device, _ := range dpMap {
-		devices = append(devices, device)
-	}
-	sort.Ints(devices)
-
-	for _, device := range devices {
-		parts := dpMap[device]
-		for i, part0 := range parts[:len(parts)-1] {
-			part0End := part0.offset + len(part0.data)
-			for _, part1 := range parts[i+1:] {
-				// Parts are sorted by offset, so only one comparison is
-				// necessary to detect overlap.
-				if part1.offset < part0End {
-					overlaps = append(overlaps, overlap{
-						part0: part0,
-						part1: part1,
-					})
-				}
-			}
-		}
-	}
-
-	if len(overlaps) > 0 {
-		str := "flash overlaps detected:"
-		for _, overlap := range overlaps {
-
-			part0End := overlap.part0.offset + len(overlap.part0.data)
-			part1End := overlap.part1.offset + len(overlap.part1.data)
-			str += fmt.Sprintf("\n    * s%d [%s] (%d - %d) <=> [%s] (%d - %d)",
-				overlap.part0.device,
-				overlap.part0.name, overlap.part0.offset, part0End,
-				overlap.part1.name, overlap.part1.offset, part1End)
-		}
-
-		return util.NewNewtError(str)
-	}
-
-	return nil
-}
-
-func Load(basePkg *pkg.LocalPackage) (*MfgImage, error) {
-	v, err := newtutil.ReadConfig(basePkg.BasePath(),
-		strings.TrimSuffix(MFG_YAML_FILENAME, ".yml"))
-	if err != nil {
-		return nil, err
-	}
-
-	mi := &MfgImage{
-		basePkg: basePkg,
-	}
-
-	bootName := v.GetValString("mfg.bootloader", nil)
-	if bootName == "" {
-		return nil, mi.loadError("mfg.bootloader field required")
-	}
-	mi.boot, err = mi.loadTarget(bootName)
-	if err != nil {
-		return nil, err
-	}
-
-	imgNames := v.GetValStringSlice("mfg.images", nil)
-	if imgNames != nil {
-		for _, imgName := range imgNames {
-			imgTarget, err := mi.loadTarget(imgName)
-			if err != nil {
-				return nil, err
-			}
-
-			mi.images = append(mi.images, imgTarget)
-		}
-	}
-
-	if len(mi.images) > 2 {
-		return nil, mi.loadError("too many images (%d); maximum is 2",
-			len(mi.images))
-	}
-
-	itf := v.GetFirstVal("mfg.raw", nil)
-	slice := cast.ToSlice(itf)
-	if slice != nil {
-		for i, entryItf := range slice {
-			yamlEntry := cast.ToStringMapString(entryItf)
-			entry, err := mi.loadRawEntry(i, yamlEntry)
-			if err != nil {
-				return nil, err
-			}
-
-			mi.rawEntries = append(mi.rawEntries, entry)
-		}
-	}
-
-	proj := project.GetProject()
-
-	bspLpkg, err := proj.ResolvePackage(mi.basePkg.Repo(),
-		mi.boot.BspName)
-	if err != nil {
-		return nil, mi.loadError(
-			"could not resolve boot loader BSP package: %s",
-			mi.boot.BspName)
-	}
-	mi.bsp, err = pkg.NewBspPackage(bspLpkg)
-	if err != nil {
-		return nil, mi.loadError(err.Error())
-	}
-
-	compilerPkg, err := proj.ResolvePackage(mi.bsp.Repo(), mi.bsp.CompilerName)
-	if err != nil {
-		return nil, mi.loadError(err.Error())
-	}
-	mi.compiler, err = toolchain.NewCompiler(compilerPkg.BasePath(), "",
-		target.DEFAULT_BUILD_PROFILE)
-	if err != nil {
-		return nil, mi.loadError(err.Error())
-	}
-
-	for _, imgTarget := range mi.images {
-		if len(mi.images) > 1 && imgTarget.LoaderName != "" {
-			return nil, mi.loadError("only one image allowed in "+
-				"split image mode (%s is a split build)", imgTarget.Name())
-		}
-
-		if imgTarget.Bsp() != mi.bsp.LocalPackage {
-			return nil, mi.loadError(
-				"image target \"%s\" specified conflicting BSP; "+
-					"boot loader uses %s, image uses %s",
-				imgTarget.Name(), mi.bsp.Name(), imgTarget.BspName)
-		}
-	}
-
-	if err := mi.detectInvalidDevices(); err != nil {
-		return nil, err
-	}
-
-	return mi, nil
-}
diff --git a/newt/mfg/meta.go b/newt/mfg/meta.go
deleted file mode 100644
index 87aabe0f..00000000
--- a/newt/mfg/meta.go
+++ /dev/null
@@ -1,247 +0,0 @@
-/**
- * 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 mfg
-
-import (
-	"bytes"
-	"crypto/sha256"
-	"encoding/binary"
-
-	"mynewt.apache.org/newt/newt/flash"
-	"mynewt.apache.org/newt/util"
-)
-
-// The "manufacturing meta region" is located at the end of the boot loader
-// flash area.  This region has the following structure.
-//
-//  0                   1                   2                   3
-//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-// |Version (0x01) |                  0xff padding                 |
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-// |   TLV type    |   TLV size    | TLV data ("TLV size" bytes)   ~
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               ~
-// ~                                                               ~
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-// |   TLV type    |   TLV size    | TLV data ("TLV size" bytes)   ~
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               ~
-// ~                                                               ~
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-// |   Region size                 |         0xff padding          |
-// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-// |                       Magic (0x3bb2a269)                      |
-// +-+-+-+-+-+--+-+-+-+-end of boot loader area+-+-+-+-+-+-+-+-+-+-+
-//
-// The number of TLVs is variable; two are shown above for illustrative
-// purposes.
-//
-// Fields:
-// <Header>
-// 1. Version: Manufacturing meta version number; always 0x01.
-//
-// <TLVs>
-// 2. TLV type: Indicates the type of data to follow.
-// 3. TLV size: The number of bytes of data to follow.
-// 4. TLV data: TLV-size bytes of data.
-//
-// <Footer>
-// 5. Region size: The size, in bytes, of the entire manufacturing meta region;
-//    includes header, TLVs, and footer.
-// 6. Magic: indicates the presence of the manufacturing meta region.
-
-const META_MAGIC = 0x3bb2a269
-const META_VERSION = 1
-const META_TLV_CODE_HASH = 0x01
-const META_TLV_CODE_FLASH_AREA = 0x02
-
-const META_HASH_SZ = 32
-const META_FOOTER_SZ = 8
-const META_TLV_HASH_SZ = META_HASH_SZ
-const META_TLV_FLASH_AREA_SZ = 12
-
-type metaHeader struct {
-	version uint8  // 1
-	pad8    uint8  // 0xff
-	pad16   uint16 // 0xffff
-}
-
-type metaFooter struct {
-	size  uint16 // Includes header, TLVs, and footer.
-	pad16 uint16 // 0xffff
-	magic uint32 // META_MAGIC
-}
-
-type metaTlvHeader struct {
-	typ  uint8 // Indicates the type of data to follow.
-	size uint8 // The number of bytes of data to follow.
-}
-
-type metaTlvFlashArea struct {
-	header   metaTlvHeader
-	areaId   uint8  // Unique value identifying this flash area.
-	deviceId uint8  // Indicates host flash device (aka section number).
-	pad16    uint16 // 0xffff
-	offset   uint32 // The byte offset within the flash device.
-	size     uint32 // Size, in bytes, of entire flash area.
-}
-
-type metaTlvHash struct {
-	header metaTlvHeader
-	hash   [META_HASH_SZ]byte
-}
-
-func writeElem(elem interface{}, buf *bytes.Buffer) error {
-	/* XXX: Assume target platform uses little endian. */
-	if err := binary.Write(buf, binary.LittleEndian, elem); err != nil {
-		return util.ChildNewtError(err)
-	}
-	return nil
-}
-
-func writeHeader(buf *bytes.Buffer) error {
-	hdr := metaHeader{
-		version: META_VERSION,
-		pad8:    0xff,
-		pad16:   0xffff,
-	}
-	return writeElem(hdr, buf)
-}
-
-func writeFooter(buf *bytes.Buffer) error {
-	ftr := metaFooter{
-		size:  uint16(buf.Len() + META_FOOTER_SZ),
-		pad16: 0xffff,
-		magic: META_MAGIC,
-	}
-	return writeElem(ftr, buf)
-}
-
-func writeTlvHeader(typ uint8, size uint8, buf *bytes.Buffer) error {
-	tlvHdr := metaTlvHeader{
-		typ:  typ,
-		size: size,
-	}
-	return writeElem(tlvHdr, buf)
-}
-
-// Writes a single entry of the flash map TLV.
-func writeFlashMapEntry(area flash.FlashArea, buf *bytes.Buffer) error {
-	tlv := metaTlvFlashArea{
-		header: metaTlvHeader{
-			typ:  META_TLV_CODE_FLASH_AREA,
-			size: META_TLV_FLASH_AREA_SZ,
-		},
-		areaId:   uint8(area.Id),
-		deviceId: uint8(area.Device),
-		pad16:    0xffff,
-		offset:   uint32(area.Offset),
-		size:     uint32(area.Size),
-	}
-	return writeElem(tlv, buf)
-}
-
-// Writes a zeroed-out hash TLV.  The hash's original value must be zero for
-// the actual hash to be calculated later.  After the actual value is
-// calculated, it replaces the zeros in the TLV.
-func writeZeroHash(buf *bytes.Buffer) error {
-	tlv := metaTlvHash{
-		header: metaTlvHeader{
-			typ:  META_TLV_CODE_HASH,
-			size: META_TLV_HASH_SZ,
-		},
-		hash: [META_HASH_SZ]byte{},
-	}
-	return writeElem(tlv, buf)
-}
-
-// @return						meta-offset, hash-offset, error
-func insertMeta(section0Data []byte, flashMap flash.FlashMap) (
-	int, int, error) {
-
-	buf := &bytes.Buffer{}
-
-	if err := writeHeader(buf); err != nil {
-		return 0, 0, err
-	}
-
-	for _, area := range flashMap.SortedAreas() {
-		if err := writeFlashMapEntry(area, buf); err != nil {
-			return 0, 0, err
-		}
-	}
-
-	if err := writeZeroHash(buf); err != nil {
-		return 0, 0, err
-	}
-	hashSubOff := buf.Len() - META_HASH_SZ
-
-	if err := writeFooter(buf); err != nil {
-		return 0, 0, err
-	}
-
-	// The meta region gets placed at the very end of the boot loader slot.
-	bootArea, ok := flashMap.Areas[flash.FLASH_AREA_NAME_BOOTLOADER]
-	if !ok {
-		return 0, 0,
-			util.NewNewtError("Required boot loader flash area missing")
-	}
-
-	if bootArea.Size < buf.Len() {
-		return 0, 0, util.FmtNewtError(
-			"Boot loader flash area too small to accommodate meta region; "+
-				"boot=%d meta=%d", bootArea.Size, buf.Len())
-	}
-
-	metaOff := bootArea.Offset + bootArea.Size - buf.Len()
-	for i := metaOff; i < bootArea.Size; i++ {
-		if section0Data[i] != 0xff {
-			return 0, 0, util.FmtNewtError(
-				"Boot loader extends into meta region; "+
-					"meta region starts at offset %d", metaOff)
-		}
-	}
-
-	// Copy the meta region into the manufacturing image.  The meta hash is
-	// still zeroed.
-	copy(section0Data[metaOff:], buf.Bytes())
-
-	return metaOff, metaOff + hashSubOff, nil
-}
-
-// Calculates the SHA256 hash, using the full manufacturing image as input.
-// Hash-calculation algorithm is as follows:
-// 1. Concatenate sections in ascending order of index.
-// 2. Zero out the 32 bytes that will contain the hash.
-// 3. Apply SHA256 to the result.
-//
-// This function assumes that the 32 bytes of hash data have already been
-// zeroed.
-func calcMetaHash(sections [][]byte) []byte {
-	// Concatenate all sections.
-	blob := []byte{}
-	for _, section := range sections {
-		blob = append(blob, section...)
-	}
-
-	// Calculate hash.
-	hash := sha256.Sum256(blob)
-
-	return hash[:]
-}
diff --git a/newt/mfg/mfg.go b/newt/mfg/mfg.go
deleted file mode 100644
index 51d6e6d9..00000000
--- a/newt/mfg/mfg.go
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * 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 mfg
-
-import (
-	"sort"
-
-	"mynewt.apache.org/newt/newt/image"
-	"mynewt.apache.org/newt/newt/pkg"
-	"mynewt.apache.org/newt/newt/target"
-	"mynewt.apache.org/newt/newt/toolchain"
-)
-
-type MfgRawEntry struct {
-	device   int
-	offset   int
-	filename string
-	data     []byte
-}
-
-// A chunk of data in the manufacturing image.  Can be a firmware image or a
-// raw entry (contents of a data file).
-type mfgPart struct {
-	device int
-	offset int
-	data   []byte
-	name   string
-}
-
-type MfgImage struct {
-	basePkg *pkg.LocalPackage
-
-	bsp      *pkg.BspPackage
-	compiler *toolchain.Compiler
-
-	boot       *target.Target
-	images     []*target.Target
-	rawEntries []MfgRawEntry
-
-	version image.ImageVersion
-}
-
-func (mi *MfgImage) SetVersion(ver image.ImageVersion) {
-	mi.version = ver
-}
-
-func (mi *MfgImage) imgApps(imageIdx int) (
-	app *pkg.LocalPackage, loader *pkg.LocalPackage) {
-
-	if imageIdx >= len(mi.images) {
-		return
-	}
-
-	t := mi.images[imageIdx]
-	app = t.App()
-	loader = t.Loader()
-	return
-}
-
-func (mi *MfgImage) sectionIds() []int {
-	idMap := map[int]struct{}{}
-
-	// The bootloader and images always go in section 0.
-	idMap[0] = struct{}{}
-
-	for _, entry := range mi.rawEntries {
-		idMap[entry.device] = struct{}{}
-	}
-
-	ids := make([]int, 0, len(idMap))
-	for id, _ := range idMap {
-		ids = append(ids, id)
-	}
-	sort.Ints(ids)
-
-	return ids
-}
-
-func (mi *MfgImage) NumImages() int {
-	return len(mi.images)
-}
diff --git a/newt/mfg/misc.go b/newt/mfg/misc.go
new file mode 100644
index 00000000..73d911d8
--- /dev/null
+++ b/newt/mfg/misc.go
@@ -0,0 +1,92 @@
+/**
+ * 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 mfg
+
+import (
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/newt/builder"
+	"mynewt.apache.org/newt/newt/newtutil"
+	"mynewt.apache.org/newt/newt/pkg"
+)
+
+func loadDecodedMfg(basePath string) (DecodedMfg, error) {
+	yc, err := newtutil.ReadConfig(basePath,
+		strings.TrimSuffix(YAML_FILENAME, ".yml"))
+	if err != nil {
+		return DecodedMfg{}, err
+	}
+
+	dm, err := decodeMfg(yc)
+	if err != nil {
+		return DecodedMfg{}, err
+	}
+
+	return dm, nil
+}
+
+func LoadMfgEmitter(basePkg *pkg.LocalPackage,
+	ver image.ImageVersion) (MfgEmitter, error) {
+
+	dm, err := loadDecodedMfg(basePkg.BasePath())
+	if err != nil {
+		return MfgEmitter{}, err
+	}
+
+	mb, err := newMfgBuilder(basePkg, dm, ver)
+	if err != nil {
+		return MfgEmitter{}, err
+	}
+
+	device, err := mb.calcDevice()
+	if err != nil {
+		return MfgEmitter{}, err
+	}
+
+	me, err := NewMfgEmitter(mb, basePkg.Name(), ver, device)
+	if err != nil {
+		return MfgEmitter{}, err
+	}
+
+	return me, nil
+}
+
+func Upload(basePkg *pkg.LocalPackage) (string, error) {
+	dm, err := loadDecodedMfg(basePkg.BasePath())
+	if err != nil {
+		return "", err
+	}
+
+	mb, err := newMfgBuilder(basePkg, dm, image.ImageVersion{})
+	if err != nil {
+		return "", err
+	}
+
+	envSettings := map[string]string{"MFG_IMAGE": "1"}
+	binPath := MfgBinPath(basePkg.Name())
+	basePath := strings.TrimSuffix(binPath, ".bin")
+
+	if err := builder.Load(basePath, mb.Bsp, envSettings); err != nil {
+		return "", err
+	}
+
+	return binPath, nil
+}
diff --git a/newt/mfg/part.go b/newt/mfg/part.go
new file mode 100644
index 00000000..ca84d294
--- /dev/null
+++ b/newt/mfg/part.go
@@ -0,0 +1,96 @@
+/**
+ * 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 mfg
+
+import (
+	"bytes"
+	"io"
+	"sort"
+
+	"mynewt.apache.org/newt/util"
+)
+
+// A chunk of data in the manufacturing image.  Can be a firmware image or a
+// raw entry (contents of a data file).
+type Part struct {
+	Name   string
+	Offset int
+	Data   []byte
+}
+
+type partSorter struct {
+	parts []Part
+}
+
+func (s partSorter) Len() int {
+	return len(s.parts)
+}
+func (s partSorter) Swap(i, j int) {
+	s.parts[i], s.parts[j] = s.parts[j], s.parts[i]
+}
+func (s partSorter) Less(i, j int) bool {
+	return s.parts[i].Offset < s.parts[j].Offset
+}
+
+func SortParts(parts []Part) []Part {
+	sorter := partSorter{
+		parts: parts,
+	}
+
+	sort.Sort(sorter)
+	return sorter.parts
+}
+
+func WriteParts(parts []Part, w io.Writer, eraseVal byte) (int, error) {
+	off := 0
+	for _, p := range parts {
+		if p.Offset < off {
+			return off, util.FmtNewtError(
+				"Invalid mfg parts: out of order")
+		}
+
+		// Pad the previous block up to the current offset.
+		for off < p.Offset {
+			if _, err := w.Write([]byte{eraseVal}); err != nil {
+				return off, util.ChildNewtError(err)
+			}
+			off++
+		}
+
+		// Write the current block's data.
+		size, err := w.Write(p.Data)
+		if err != nil {
+			return off, util.ChildNewtError(err)
+		}
+		off += size
+	}
+
+	// Note: the final block does not get padded.
+
+	return off, nil
+}
+
+func PartsBytes(parts []Part) ([]byte, error) {
+	b := &bytes.Buffer{}
+	if _, err := WriteParts(parts, b, 0xff); err != nil {
+		return nil, err
+	}
+	return b.Bytes(), nil
+}
diff --git a/newt/mfg/paths.go b/newt/mfg/paths.go
index 04cf448c..8c70b94c 100644
--- a/newt/mfg/paths.go
+++ b/newt/mfg/paths.go
@@ -21,162 +21,42 @@ package mfg
 
 import (
 	"fmt"
-	"path/filepath"
-	"strconv"
 
+	"mynewt.apache.org/newt/artifact/mfg"
 	"mynewt.apache.org/newt/newt/builder"
-	"mynewt.apache.org/newt/newt/pkg"
 )
 
+// Filename containing a manufacturing image definition.
+const YAML_FILENAME string = "mfg.yml"
+
 func MfgBinDir(mfgPkgName string) string {
 	return builder.BinRoot() + "/" + mfgPkgName
 }
 
-func MfgBootDir(mfgPkgName string) string {
-	return MfgBinDir(mfgPkgName) + "/bootloader"
-}
-
-func MfgBootBinPath(mfgPkgName string, appName string) string {
-	return MfgBootDir(mfgPkgName) + "/" + appName + ".elf.bin"
-}
-
-func MfgBootElfPath(mfgPkgName string, appName string) string {
-	return MfgBootDir(mfgPkgName) + "/" + appName + ".elf"
-}
-
-func MfgBootManifestPath(mfgPkgName string, appName string) string {
-	return MfgBootDir(mfgPkgName) + "/manifest.json"
-}
-
-// Image indices start at 0.
-func MfgImageBinDir(mfgPkgName string, imageIdx int) string {
-	return MfgBinDir(mfgPkgName) + "/image" + strconv.Itoa(imageIdx)
-}
-
-func MfgImageImgPath(mfgPkgName string, imageIdx int,
-	appName string) string {
-
-	return MfgImageBinDir(mfgPkgName, imageIdx) + "/" + appName + ".img"
-}
-
-func MfgImageElfPath(mfgPkgName string, imageIdx int,
-	appName string) string {
-
-	return MfgImageBinDir(mfgPkgName, imageIdx) + "/" + appName + ".elf"
-}
-
-func MfgImageManifestPath(mfgPkgName string, imageIdx int) string {
-	return MfgImageBinDir(mfgPkgName, imageIdx) + "/manifest.json"
-}
-
-func MfgSectionBinDir(mfgPkgName string) string {
-	return MfgBinDir(mfgPkgName) + "/sections"
-}
-
-func MfgSectionBinPath(mfgPkgName string, sectionNum int) string {
-	return fmt.Sprintf("%s/%s-s%d.bin", MfgSectionBinDir(mfgPkgName),
-		filepath.Base(mfgPkgName), sectionNum)
-}
-
-func MfgSectionHexPath(mfgPkgName string, sectionNum int) string {
-	return fmt.Sprintf("%s/%s-s%d.hex", MfgSectionBinDir(mfgPkgName),
-		filepath.Base(mfgPkgName), sectionNum)
+func MfgBinPath(mfgPkgName string) string {
+	return MfgBinDir(mfgPkgName) + "/" + mfg.MFG_IMG_FILENAME
 }
 
 func MfgManifestPath(mfgPkgName string) string {
-	return MfgBinDir(mfgPkgName) + "/manifest.json"
+	return MfgBinDir(mfgPkgName) + "/" + mfg.MANIFEST_FILENAME
 }
 
-func (mi *MfgImage) ManifestPath() string {
-	return MfgManifestPath(mi.basePkg.Name())
+func MfgTargetDir(targetNum int) string {
+	return fmt.Sprintf("targets/%d", targetNum)
 }
 
-func (mi *MfgImage) BootBinPath() string {
-	if mi.boot == nil {
-		return ""
-	}
-
-	return MfgBootBinPath(mi.basePkg.Name(),
-		pkg.ShortName(mi.boot.App()))
-}
-
-func (mi *MfgImage) BootElfPath() string {
-	if mi.boot == nil {
-		return ""
-	}
-
-	return MfgBootElfPath(mi.basePkg.Name(), pkg.ShortName(mi.boot.App()))
-}
-
-func (mi *MfgImage) BootManifestPath() string {
-	if mi.boot == nil {
-		return ""
-	}
-
-	return MfgBootManifestPath(mi.basePkg.Name(),
-		pkg.ShortName(mi.boot.App()))
-}
-
-func (mi *MfgImage) AppImgPath(imageIdx int) string {
-	app, _ := mi.imgApps(imageIdx)
-	if app == nil {
-		return ""
-	}
-
-	return MfgImageImgPath(mi.basePkg.Name(), imageIdx, pkg.ShortName(app))
-}
-
-func (mi *MfgImage) AppElfPath(imageIdx int) string {
-	app, _ := mi.imgApps(imageIdx)
-	if app == nil {
-		return ""
-	}
-
-	return MfgImageElfPath(mi.basePkg.Name(), imageIdx, pkg.ShortName(app))
+func MfgTargetBinPath(targetNum int) string {
+	return fmt.Sprintf("%s/binary.bin", MfgTargetDir(targetNum))
 }
 
-func (mi *MfgImage) LoaderImgPath(imageIdx int) string {
-	_, loader := mi.imgApps(imageIdx)
-	if loader == nil {
-		return ""
-	}
-
-	return MfgImageImgPath(mi.basePkg.Name(), imageIdx, pkg.ShortName(loader))
+func MfgTargetImgPath(targetNum int) string {
+	return fmt.Sprintf("%s/image.img", MfgTargetDir(targetNum))
 }
 
-func (mi *MfgImage) LoaderElfPath(imageIdx int) string {
-	_, loader := mi.imgApps(imageIdx)
-	if loader == nil {
-		return ""
-	}
-
-	return MfgImageElfPath(mi.basePkg.Name(), imageIdx, pkg.ShortName(loader))
+func MfgTargetElfPath(targetNum int) string {
+	return fmt.Sprintf("%s/elf.elf", MfgTargetDir(targetNum))
 }
 
-func (mi *MfgImage) ImageManifestPath(imageIdx int) string {
-	if imageIdx >= len(mi.images) {
-		return ""
-	}
-
-	return MfgImageManifestPath(mi.basePkg.Name(), imageIdx)
-}
-
-func (mi *MfgImage) SectionBinPaths() []string {
-	sectionIds := mi.sectionIds()
-
-	paths := make([]string, len(sectionIds))
-	for i, sectionId := range sectionIds {
-		paths[i] = MfgSectionBinPath(mi.basePkg.Name(), sectionId)
-	}
-	return paths
-}
-
-func (mi *MfgImage) SectionHexPaths() []string {
-	sectionIds := mi.sectionIds()
-
-	paths := make([]string, len(sectionIds))
-	for i, sectionId := range sectionIds {
-		paths[i] = MfgSectionHexPath(mi.basePkg.Name(), sectionId)
-	}
-	return paths
+func MfgTargetManifestPath(targetNum int) string {
+	return fmt.Sprintf("%s/manifest.json", MfgTargetDir(targetNum))
 }
diff --git a/newt/pkg/bsp_package.go b/newt/pkg/bsp_package.go
index 14065931..81196ff5 100644
--- a/newt/pkg/bsp_package.go
+++ b/newt/pkg/bsp_package.go
@@ -24,7 +24,7 @@ import (
 	"runtime"
 	"strings"
 
-	"mynewt.apache.org/newt/newt/flash"
+	"mynewt.apache.org/newt/newt/flashmap"
 	"mynewt.apache.org/newt/newt/interfaces"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/ycfg"
@@ -41,7 +41,7 @@ type BspPackage struct {
 	Part2LinkerScripts []string /* scripts to link app to second partition */
 	DownloadScript     string
 	DebugScript        string
-	FlashMap           flash.FlashMap
+	FlashMap           flashmap.FlashMap
 	BspV               ycfg.YCfg
 }
 
@@ -162,7 +162,7 @@ func (bsp *BspPackage) Reload(settings map[string]string) error {
 		return util.NewNewtError("BSP does not specify a flash map " +
 			"(bsp.flash_map)")
 	}
-	bsp.FlashMap, err = flash.Read(ymlFlashMap)
+	bsp.FlashMap, err = flashmap.Read(ymlFlashMap)
 	if err != nil {
 		return err
 	}
diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go
index c9ccb42d..45944c68 100644
--- a/newt/resolve/resolve.go
+++ b/newt/resolve/resolve.go
@@ -26,7 +26,7 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 
-	"mynewt.apache.org/newt/newt/flash"
+	"mynewt.apache.org/newt/newt/flashmap"
 	"mynewt.apache.org/newt/newt/logcfg"
 	"mynewt.apache.org/newt/newt/parse"
 	"mynewt.apache.org/newt/newt/pkg"
@@ -61,7 +61,7 @@ type Resolver struct {
 	pkgMap           map[*pkg.LocalPackage]*ResolvePackage
 	seedPkgs         []*pkg.LocalPackage
 	injectedSettings map[string]string
-	flashMap         flash.FlashMap
+	flashMap         flashmap.FlashMap
 	cfg              syscfg.Cfg
 	lcfg             logcfg.LCfg
 	sysinitCfg       sysinit.SysinitCfg
@@ -131,7 +131,7 @@ type Resolution struct {
 func newResolver(
 	seedPkgs []*pkg.LocalPackage,
 	injectedSettings map[string]string,
-	flashMap flash.FlashMap) *Resolver {
+	flashMap flashmap.FlashMap) *Resolver {
 
 	r := &Resolver{
 		apis:             map[string]resolveApi{},
@@ -800,7 +800,7 @@ func ResolveFull(
 	loaderSeeds []*pkg.LocalPackage,
 	appSeeds []*pkg.LocalPackage,
 	injectedSettings map[string]string,
-	flashMap flash.FlashMap) (*Resolution, error) {
+	flashMap flashmap.FlashMap) (*Resolution, error) {
 
 	// First, calculate syscfg and determine which package provides each
 	// required API.  Syscfg and APIs are project-wide; that is, they are
diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go
index 8a81a68d..30109377 100644
--- a/newt/syscfg/syscfg.go
+++ b/newt/syscfg/syscfg.go
@@ -34,7 +34,7 @@ import (
 	log "github.com/Sirupsen/logrus"
 	"github.com/spf13/cast"
 
-	"mynewt.apache.org/newt/newt/flash"
+	"mynewt.apache.org/newt/newt/flashmap"
 	"mynewt.apache.org/newt/newt/interfaces"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/parse"
@@ -738,7 +738,7 @@ func (cfg *Cfg) detectPriorityViolations() {
 }
 
 // Detects all flash conflict errors in the syscfg and records them internally.
-func (cfg *Cfg) detectFlashConflicts(flashMap flash.FlashMap) {
+func (cfg *Cfg) detectFlashConflicts(flashMap flashmap.FlashMap) {
 	entries := cfg.settingsOfType(CFG_SETTING_TYPE_FLASH_OWNER)
 
 	areaEntryMap := map[string][]CfgEntry{}
@@ -1072,7 +1072,7 @@ func (cfg *Cfg) detectAmbiguities() {
 
 // Detects and records errors in the build's syscfg.  This should only be
 // called after APIs are resolved to avoid false positives.
-func (cfg *Cfg) DetectErrors(flashMap flash.FlashMap) {
+func (cfg *Cfg) DetectErrors(flashMap flashmap.FlashMap) {
 	cfg.detectAmbiguities()
 	cfg.detectViolations()
 	cfg.detectPriorityViolations()
@@ -1081,7 +1081,7 @@ func (cfg *Cfg) DetectErrors(flashMap flash.FlashMap) {
 
 func Read(lpkgs []*pkg.LocalPackage, apis []string,
 	injectedSettings map[string]string, settings map[string]string,
-	flashMap flash.FlashMap) (Cfg, error) {
+	flashMap flashmap.FlashMap) (Cfg, error) {
 
 	cfg := NewCfg()
 	for k, v := range injectedSettings {
diff --git a/util/util.go b/util/util.go
index fa9098b2..b1d7973e 100644
--- a/util/util.go
+++ b/util/util.go
@@ -103,6 +103,14 @@ func ChildNewtError(parent error) *NewtError {
 	return newtErr
 }
 
+func FmtChildNewtError(parent error, format string,
+	args ...interface{}) *NewtError {
+
+	ne := ChildNewtError(parent)
+	ne.Text = fmt.Sprintf(format, args...)
+	return ne
+}
+
 // Print Silent, Quiet and Verbose aware status messages to stdout.
 func WriteMessage(f *os.File, level int, message string,
 	args ...interface{}) {


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services