You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2019/06/20 17:42:14 UTC

[mynewt-artifact] 02/12: Support for the 2.0 mfgimage format

This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-artifact.git

commit 9dd213a93f66442563a7b113edcc935fd6c9b710
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Fri Dec 14 14:19:57 2018 -0800

    Support for the 2.0 mfgimage format
---
 image/create.go          | 101 +++++---------
 image/image.go           |   6 +
 image/key.go             |  19 ++-
 image/v1.go              |   2 +-
 manifest/manifest.go     |   3 +-
 manifest/mfg_manifest.go |  73 ++++++++++
 mfg/map_meta.go          | 153 ++++++++++++++++++++
 mfg/meta.go              | 352 +++++++++++++++++++++++++++++++++++++++++++++++
 mfg/mfg.go               | 113 +++++++++++++++
 mfg/paths.go             |  35 +++++
 10 files changed, 777 insertions(+), 80 deletions(-)

diff --git a/image/create.go b/image/create.go
index 5b28120..c3e8820 100644
--- a/image/create.go
+++ b/image/create.go
@@ -90,12 +90,12 @@ func generateEncTlv(cipherSecret []byte) (ImageTlv, error) {
 	}, nil
 }
 
-func generateSigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
+func generateSigRsa(key ImageSigKey, hash []byte) ([]byte, error) {
 	opts := rsa.PSSOptions{
 		SaltLength: rsa.PSSSaltLengthEqualsHash,
 	}
 	signature, err := rsa.SignPSS(
-		rand.Reader, key, crypto.SHA256, hash, &opts)
+		rand.Reader, key.Rsa, crypto.SHA256, hash, &opts)
 	if err != nil {
 		return nil, util.FmtNewtError("Failed to compute signature: %s", err)
 	}
@@ -103,24 +103,8 @@ func generateSigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
 	return signature, nil
 }
 
-func generateSigTlvRsa(key ImageSigKey, hash []byte) (ImageTlv, error) {
-	sig, err := generateSigRsa(key.Rsa, hash)
-	if err != nil {
-		return ImageTlv{}, err
-	}
-
-	return ImageTlv{
-		Header: ImageTlvHdr{
-			Type: key.sigTlvType(),
-			Pad:  0,
-			Len:  256, /* 2048 bits */
-		},
-		Data: sig,
-	}, nil
-}
-
-func generateSigEc(key *ecdsa.PrivateKey, hash []byte) ([]byte, error) {
-	r, s, err := ecdsa.Sign(rand.Reader, key, hash)
+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)
 	}
@@ -135,88 +119,65 @@ func generateSigEc(key *ecdsa.PrivateKey, hash []byte) ([]byte, error) {
 		return nil, util.FmtNewtError("Failed to construct signature: %s", err)
 	}
 
-	return signature, nil
-}
-
-func generateSigTlvEc(key ImageSigKey, hash []byte) (ImageTlv, error) {
-	sig, err := generateSigEc(key.Ec, hash)
-	if err != nil {
-		return ImageTlv{}, err
-	}
-
 	sigLen := key.sigLen()
-	if len(sig) > int(sigLen) {
-		return ImageTlv{}, util.FmtNewtError("Something is really wrong\n")
-	}
-
-	b := &bytes.Buffer{}
-
-	if _, err := b.Write(sig); err != nil {
-		return ImageTlv{},
-			util.FmtNewtError("Failed to append sig: %s", err.Error())
+	if len(signature) > int(sigLen) {
+		return nil, util.FmtNewtError("Something is really wrong\n")
 	}
 
-	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())
-	}
+	pad := make([]byte, int(sigLen)-len(signature))
+	signature = append(signature, pad...)
 
-	return ImageTlv{
-		Header: ImageTlvHdr{
-			Type: key.sigTlvType(),
-			Pad:  0,
-			Len:  sigLen,
-		},
-		Data: b.Bytes(),
-	}, nil
+	return signature, nil
 }
 
-func generateSigTlv(key ImageSigKey, hash []byte) (ImageTlv, error) {
+func generateSig(key ImageSigKey, hash []byte) ([]byte, error) {
 	key.assertValid()
 
 	if key.Rsa != nil {
-		return generateSigTlvRsa(key, hash)
+		return generateSigRsa(key, hash)
 	} else {
-		return generateSigTlvEc(key, hash)
+		return generateSigEc(key, hash)
 	}
 }
 
-func generateKeyHashTlv(key ImageSigKey) (ImageTlv, error) {
-	key.assertValid()
-
-	keyHash, err := key.sigKeyHash()
-	if err != nil {
-		return ImageTlv{}, util.FmtNewtError(
-			"Failed to compute hash of the public key: %s", err.Error())
-	}
-
+func BuildKeyHashTlv(keyBytes []byte) ImageTlv {
+	data := RawKeyHash(keyBytes)
 	return ImageTlv{
 		Header: ImageTlvHdr{
 			Type: IMAGE_TLV_KEYHASH,
 			Pad:  0,
-			Len:  uint16(len(keyHash)),
+			Len:  uint16(len(data)),
 		},
-		Data: keyHash,
-	}, nil
+		Data: data,
+	}
 }
 
-func GenerateSigTlvs(keys []ImageSigKey, hash []byte) ([]ImageTlv, error) {
+func BuildSigTlvs(keys []ImageSigKey, hash []byte) ([]ImageTlv, error) {
 	var tlvs []ImageTlv
 
 	for _, key := range keys {
 		key.assertValid()
 
-		tlv, err := generateKeyHashTlv(key)
+		// Key hash TLV.
+		pubKey, err := key.PubBytes()
 		if err != nil {
 			return nil, err
 		}
+		tlv := BuildKeyHashTlv(pubKey)
 		tlvs = append(tlvs, tlv)
 
-		tlv, err = generateSigTlv(key, hash)
+		// 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)
 	}
 
@@ -409,7 +370,7 @@ func (ic *ImageCreator) Create() (Image, error) {
 	}
 	ri.Tlvs = append(ri.Tlvs, tlv)
 
-	tlvs, err := GenerateSigTlvs(ic.SigKeys, hashBytes)
+	tlvs, err := BuildSigTlvs(ic.SigKeys, hashBytes)
 	if err != nil {
 		return ri, err
 	}
diff --git a/image/image.go b/image/image.go
index 244cfed..1ed093f 100644
--- a/image/image.go
+++ b/image/image.go
@@ -134,6 +134,12 @@ func ImageTlvTypeName(tlvType uint8) string {
 	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
diff --git a/image/key.go b/image/key.go
index 9141f6e..8345cd9 100644
--- a/image/key.go
+++ b/image/key.go
@@ -152,25 +152,30 @@ func (key *ImageSigKey) assertValid() {
 	}
 }
 
-func (key *ImageSigKey) sigKeyHash() ([]uint8, error) {
+func (key *ImageSigKey) PubBytes() ([]uint8, error) {
 	key.assertValid()
 
+	var pubkey []byte
+
 	if key.Rsa != nil {
-		pubkey, _ := asn1.Marshal(key.Rsa.PublicKey)
-		sum := sha256.Sum256(pubkey)
-		return sum[:4], 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)
-			sum := sha256.Sum256(pubkey)
-			return sum[:4], nil
+			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 {
diff --git a/image/v1.go b/image/v1.go
index bab86f6..5540d85 100644
--- a/image/v1.go
+++ b/image/v1.go
@@ -233,7 +233,7 @@ func generateV1SigTlvRsa(key ImageSigKey, hash []byte) (ImageTlv, error) {
 }
 
 func generateV1SigTlvEc(key ImageSigKey, hash []byte) (ImageTlv, error) {
-	sig, err := generateSigEc(key.Ec, hash)
+	sig, err := generateSigEc(key, hash)
 	if err != nil {
 		return ImageTlv{}, err
 	}
diff --git a/manifest/manifest.go b/manifest/manifest.go
index 62d8c06..3a3376a 100644
--- a/manifest/manifest.go
+++ b/manifest/manifest.go
@@ -5,7 +5,6 @@ import (
 	"io"
 	"io/ioutil"
 
-	"mynewt.apache.org/newt/artifact/flash"
 	"mynewt.apache.org/newt/util"
 )
 
@@ -57,7 +56,7 @@ type Manifest struct {
 	LoaderPkgs []*ManifestPkg    `json:"loader_pkgs,omitempty"`
 	TgtVars    []string          `json:"target"`
 	Repos      []*ManifestRepo   `json:"repos"`
-	FlashAreas []flash.FlashArea `json:"flash_map"`
+	Syscfg     map[string]string `json:"syscfg"`
 
 	PkgSizes       []*ManifestSizePkg `json:"pkgsz"`
 	LoaderPkgSizes []*ManifestSizePkg `json:"loader_pkgsz,omitempty"`
diff --git a/manifest/mfg_manifest.go b/manifest/mfg_manifest.go
new file mode 100644
index 0000000..25cf9b7
--- /dev/null
+++ b/manifest/mfg_manifest.go
@@ -0,0 +1,73 @@
+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"`
+	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/mfg/map_meta.go b/mfg/map_meta.go
new file mode 100644
index 0000000..b16c37e
--- /dev/null
+++ b/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/mfg/meta.go b/mfg/meta.go
new file mode 100644
index 0000000..4521a89
--- /dev/null
+++ b/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/mfg/mfg.go b/mfg/mfg.go
new file mode 100644
index 0000000..8e999ad
--- /dev/null
+++ b/mfg/mfg.go
@@ -0,0 +1,113 @@
+package mfg
+
+import (
+	"crypto/sha256"
+
+	"mynewt.apache.org/newt/util"
+)
+
+const MFG_IMG_FILENAME = "mfgimg.bin"
+const MFG_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
+}
+
+func (m *Mfg) bytesZeroedHash(eraseVal byte) ([]byte, error) {
+	binCopy := make([]byte, len(m.Bin))
+	copy(binCopy, m.Bin)
+
+	m.Meta.ClearHash()
+
+	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
+}
+
+// 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) Bytes(eraseVal byte) ([]byte, error) {
+	// First, write with zeroed hash.
+	bin, err := m.bytesZeroedHash(eraseVal)
+	if err != nil {
+		return nil, 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 nil, util.FmtNewtError(
+				"unexpected error: hash extends beyond end " +
+					"of manufacturing image")
+		}
+
+		copy(bin[hashOff:hashOff+META_HASH_SZ], tlv.Data)
+	}
+
+	return bin, nil
+}
diff --git a/mfg/paths.go b/mfg/paths.go
new file mode 100644
index 0000000..483aca2
--- /dev/null
+++ b/mfg/paths.go
@@ -0,0 +1,35 @@
+/**
+ * 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"
+	"path/filepath"
+)
+
+const MANIFEST_FILENAME = "manifest.json"
+const BOOT_DIR = "bootloader"
+const BOOT_MANIFEST_PATH = BOOT_DIR + "/manifest.json"
+const SECTION_BIN_DIR = "sections"
+
+func SectionBinPath(mfgPkgName string, sectionNum int) string {
+	return fmt.Sprintf("%s/%s-s%d.bin", SECTION_BIN_DIR,
+		filepath.Base(mfgPkgName), sectionNum)
+}