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/21 21:25:39 UTC
[mynewt-artifact] 15/23: Add "verify" functions to API
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 c15c49efe5926ba4ad41fd902061ff23a1b76a06
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Thu Jun 20 14:07:44 2019 -0700
Add "verify" functions to API
After reading an image or mfgimage, the `Verify()` and
`VerifyManifest()` functions can be used to check the object's
integrity.
---
image/create.go | 35 ++++----
image/image.go | 121 +++++++++++++++++++---------
image/keys_test.go | 61 +-------------
image/v1.go | 57 ++++++-------
image/verify.go | 87 ++++++++++++++++++++
manifest/manifest.go | 25 ++++--
manifest/mfg_manifest.go | 95 +++++++++++++++++++---
mfg/map_meta.go | 15 ++--
mfg/meta.go | 87 ++++++++++++++++----
mfg/mfg.go | 128 +++++++++++++++++++++++++-----
mfg/verify.go | 202 +++++++++++++++++++++++++++++++++++++++++++++++
sec/encrypt.go | 10 +--
sec/key.go | 112 +++++++++++++++-----------
sec/sig.go | 55 +++++++++++++
14 files changed, 822 insertions(+), 268 deletions(-)
diff --git a/image/create.go b/image/create.go
index cbbc410..f51d77b 100644
--- a/image/create.go
+++ b/image/create.go
@@ -28,12 +28,11 @@ import (
"crypto/sha256"
"encoding/asn1"
"encoding/binary"
- "encoding/hex"
"io/ioutil"
"math/big"
- "mynewt.apache.org/newt/artifact/sec"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/sec"
)
type ImageCreator struct {
@@ -100,7 +99,7 @@ func GenerateEncTlv(cipherSecret []byte) (ImageTlv, error) {
} else if len(cipherSecret) == 24 {
encType = IMAGE_TLV_ENC_KEK
} else {
- return ImageTlv{}, util.FmtNewtError("Invalid enc TLV size ")
+ return ImageTlv{}, errors.Errorf("invalid enc TLV size: %d", len(cipherSecret))
}
return ImageTlv{
@@ -120,7 +119,7 @@ func GenerateSigRsa(key sec.SignKey, hash []byte) ([]byte, error) {
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 nil, errors.Wrapf(err, "failed to compute signature")
}
return signature, nil
@@ -129,7 +128,7 @@ func GenerateSigRsa(key sec.SignKey, hash []byte) ([]byte, error) {
func GenerateSigEc(key sec.SignKey, 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)
+ return nil, errors.Wrapf(err, "failed to compute signature")
}
ECDSA := ECDSASig{
@@ -139,12 +138,12 @@ func GenerateSigEc(key sec.SignKey, hash []byte) ([]byte, error) {
signature, err := asn1.Marshal(ECDSA)
if err != nil {
- return nil, util.FmtNewtError("Failed to construct signature: %s", err)
+ return nil, errors.Wrapf(err, "failed to construct signature")
}
sigLen := key.SigLen()
if len(signature) > int(sigLen) {
- return nil, util.FmtNewtError("Something is really wrong\n")
+ return nil, errors.Errorf("signature truncated")
}
pad := make([]byte, int(sigLen)-len(signature))
@@ -210,8 +209,7 @@ func BuildSigTlvs(keys []sec.SignKey, hash []byte) ([]ImageTlv, error) {
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 nil, errors.Wrapf(err, "random generation error")
}
return plainSecret, nil
@@ -238,7 +236,7 @@ func GenerateCipherSecret(pubKeBytes []byte,
return sec.EncryptSecretAes(aesPubKe, plainSecret)
}
- return nil, util.FmtNewtError("Invalid image-crypt key")
+ return nil, errors.Errorf("invalid image-crypt key")
}
func GenerateImage(opts ImageCreateOpts) (Image, error) {
@@ -246,8 +244,7 @@ func GenerateImage(opts ImageCreateOpts) (Image, error) {
srcBin, err := ioutil.ReadFile(opts.SrcBinFilename)
if err != nil {
- return Image{}, util.FmtNewtError(
- "Can't read app binary: %s", err.Error())
+ return Image{}, errors.Wrapf(err, "Can't read app binary")
}
ic.Body = srcBin
@@ -269,8 +266,7 @@ func GenerateImage(opts ImageCreateOpts) (Image, error) {
pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename)
if err != nil {
- return Image{}, util.FmtNewtError(
- "Error reading pubkey file: %s", err.Error())
+ return Image{}, errors.Wrapf(err, "error reading pubkey file")
}
cipherSecret, err := GenerateCipherSecret(pubKeBytes, plainSecret)
@@ -301,7 +297,7 @@ func calcHash(initialHash []byte, hdr ImageHdr, pad []byte,
return err
}
if err := binary.Write(hash, binary.LittleEndian, itf); err != nil {
- return util.FmtNewtError("Failed to hash data: %s", err.Error())
+ return errors.Wrapf(err, "failed to hash data")
}
return nil
@@ -364,8 +360,8 @@ func (ic *ImageCreator) Create() (Image, error) {
// 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)
+ return img, errors.Errorf(
+ "image header must be at least %d bytes", IMAGE_HEADER_SIZE)
}
img.Header.HdrSz = uint16(ic.HeaderSize)
@@ -388,9 +384,6 @@ func (ic *ImageCreator) Create() (Image, error) {
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{
diff --git a/image/image.go b/image/image.go
index 9855223..5c489c5 100644
--- a/image/image.go
+++ b/image/image.go
@@ -31,7 +31,8 @@ import (
"strconv"
"strings"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/sec"
)
const (
@@ -128,6 +129,11 @@ type ImageOffsets struct {
TotalSize int
}
+func ImageTlvTypeIsValid(tlvType uint8) bool {
+ _, ok := imageTlvTypeNameMap[tlvType]
+ return ok
+}
+
func ImageTlvTypeName(tlvType uint8) string {
name, ok := imageTlvTypeNameMap[tlvType]
if !ok {
@@ -155,24 +161,24 @@ func ParseVersion(versStr string) (ImageVersion, error) {
components := strings.Split(versStr, ".")
major, err = strconv.ParseUint(components[0], 10, 8)
if err != nil {
- return ver, util.FmtNewtError("Invalid version string %s", versStr)
+ return ver, errors.Errorf("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)
+ return ver, errors.Errorf("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)
+ return ver, errors.Errorf("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)
+ return ver, errors.Errorf("invalid version string %s", versStr)
}
}
@@ -252,7 +258,7 @@ func (img *Image) Json() (string, error) {
b, err := json.MarshalIndent(m, "", " ")
if err != nil {
- return "", util.ChildNewtError(err)
+ return "", errors.Wrapf(err, "failed to marshal image")
}
return string(b), nil
@@ -263,13 +269,13 @@ func (tlv *ImageTlv) Write(w io.Writer) (int, error) {
err := binary.Write(w, binary.LittleEndian, &tlv.Header)
if err != nil {
- return totalSize, util.ChildNewtError(err)
+ return totalSize, errors.Wrapf(err, "failed to write image TLV header")
}
totalSize += IMAGE_TLV_SIZE
size, err := w.Write(tlv.Data)
if err != nil {
- return totalSize, util.ChildNewtError(err)
+ return totalSize, errors.Wrapf(err, "failed to write image TLV data")
}
totalSize += size
@@ -294,7 +300,7 @@ func (i *Image) FindUniqueTlv(tlvType uint8) (*ImageTlv, error) {
return nil, nil
}
if len(tlvs) > 1 {
- return nil, util.FmtNewtError("Image contains %d TLVs with type %d",
+ return nil, errors.Errorf("image contains %d TLVs with type %d",
len(tlvs), tlvType)
}
@@ -338,11 +344,12 @@ func (img *Image) Trailer() ImageTrailer {
func (i *Image) Hash() ([]byte, error) {
tlv, err := i.FindUniqueTlv(IMAGE_TLV_SHA256)
if err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "failed to retrieve image hash")
}
if tlv == nil {
- return nil, util.FmtNewtError("Image does not contain hash TLV")
+ return nil, errors.Errorf(
+ "failed to retrieve image hash: image does not contain hash TLV")
}
return tlv.Data, nil
@@ -360,20 +367,20 @@ func (i *Image) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
err := binary.Write(w, binary.LittleEndian, &i.Header)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image header")
}
offset += IMAGE_HEADER_SIZE
err = binary.Write(w, binary.LittleEndian, i.Pad)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image padding")
}
offset += len(i.Pad)
offs.Body = offset
size, err := w.Write(i.Body)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image body")
}
offset += size
@@ -381,7 +388,7 @@ func (i *Image) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
offs.Trailer = offset
err = binary.Write(w, binary.LittleEndian, &trailer)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image trailer")
}
offset += IMAGE_TRAILER_SIZE
@@ -389,7 +396,7 @@ func (i *Image) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
offs.Tlvs = append(offs.Tlvs, offset)
size, err := tlv.Write(w)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image TLV")
}
offset += size
}
@@ -423,16 +430,47 @@ func (i *Image) Write(w io.Writer) (int, error) {
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)
+ return errors.Wrapf(err, "failed to open image destination file")
}
if _, err := i.Write(f); err != nil {
- return util.ChildNewtError(err)
+ return errors.Wrapf(err, "failed to write image")
}
return nil
}
+func (img *Image) CollectSigs() ([]sec.Sig, error) {
+ var sigs []sec.Sig
+
+ var keyHashTlv *ImageTlv
+ for i, _ := range img.Tlvs {
+ t := &img.Tlvs[i]
+
+ if t.Header.Type == IMAGE_TLV_KEYHASH {
+ if keyHashTlv != nil {
+ return nil, errors.Errorf(
+ "image contains keyhash tlv without subsequent signature")
+ }
+ keyHashTlv = t
+ } else if ImageTlvTypeIsSig(t.Header.Type) {
+ if keyHashTlv == nil {
+ return nil, errors.Errorf(
+ "image contains signature tlv without preceding keyhash")
+ }
+
+ sigs = append(sigs, sec.Sig{
+ KeyHash: keyHashTlv.Data,
+ Data: t.Data,
+ })
+
+ keyHashTlv = nil
+ }
+ }
+
+ return sigs, nil
+}
+
func parseRawHeader(imgData []byte, offset int) (ImageHdr, int, error) {
var hdr ImageHdr
@@ -440,20 +478,19 @@ func parseRawHeader(imgData []byte, offset int) (ImageHdr, int, error) {
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())
+ return hdr, 0, errors.Wrapf(err, "error reading image header")
}
if hdr.Magic != IMAGE_MAGIC {
- return hdr, 0, util.FmtNewtError(
- "Image magic incorrect; expected 0x%08x, got 0x%08x",
+ return hdr, 0, errors.Errorf(
+ "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",
+ return hdr, 0, errors.Errorf(
+ "image header incomplete; expected %d bytes, got %d bytes",
hdr.HdrSz, remLen)
}
@@ -467,8 +504,8 @@ func parseRawBody(imgData []byte, hdr ImageHdr,
remLen := len(imgData) - offset
if remLen < imgSz {
- return nil, 0, util.FmtNewtError(
- "Image body incomplete; expected %d bytes, got %d bytes",
+ return nil, 0, errors.Errorf(
+ "image body incomplete; expected %d bytes, got %d bytes",
imgSz, remLen)
}
@@ -482,9 +519,8 @@ func parseRawTrailer(imgData []byte, offset int) (ImageTrailer, int, error) {
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, 0, errors.Wrapf(err,
+ "image contains invalid trailer at offset %d", offset)
}
return trailer, IMAGE_TRAILER_SIZE, nil
@@ -497,14 +533,14 @@ func parseRawTlv(imgData []byte, offset int) (ImageTlv, int, error) {
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())
+ return tlv, 0, errors.Wrapf(err,
+ "image contains invalid TLV at offset %d", offset)
}
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, 0, errors.Wrapf(err,
+ "image contains invalid TLV at offset %d", offset)
}
return tlv, IMAGE_TLV_SIZE + int(tlv.Header.Len), nil
@@ -532,6 +568,15 @@ func ParseImage(imgData []byte) (Image, error) {
}
offset += size
+ totalLen := IMAGE_HEADER_SIZE + len(body) + int(trailer.TlvTotLen)
+ if len(imgData) < totalLen {
+ return img, errors.Errorf("image data truncated: have=%d want=%d",
+ len(imgData), totalLen)
+ }
+
+ // Trim excess data following image trailer.
+ imgData = imgData[:totalLen]
+
var tlvs []ImageTlv
tlvLen := IMAGE_TRAILER_SIZE
for offset < len(imgData) {
@@ -541,13 +586,17 @@ func ParseImage(imgData []byte) (Image, error) {
}
tlvs = append(tlvs, tlv)
+
offset += size
+ if offset > len(imgData) {
+ return img, errors.Errorf("TLVs extend beyond end of image")
+ }
- tlvLen += IMAGE_TLV_SIZE + int(tlv.Header.Len)
+ tlvLen += size
}
if int(trailer.TlvTotLen) != tlvLen {
- return img, util.FmtNewtError(
+ return img, errors.Errorf(
"invalid image: trailer indicates TLV-length=%d; actual=%d",
trailer.TlvTotLen, tlvLen)
}
@@ -564,7 +613,7 @@ func ReadImage(filename string) (Image, error) {
imgData, err := ioutil.ReadFile(filename)
if err != nil {
- return ri, util.ChildNewtError(err)
+ return ri, errors.Wrapf(err, "failed to read image from file")
}
return ParseImage(imgData)
diff --git a/image/keys_test.go b/image/keys_test.go
index 769848a..0119098 100644
--- a/image/keys_test.go
+++ b/image/keys_test.go
@@ -24,8 +24,8 @@ import (
"os"
"testing"
- "mynewt.apache.org/newt/artifact/image"
- "mynewt.apache.org/newt/artifact/sec"
+ "mynewt.apache.org/artifact/image"
+ "mynewt.apache.org/artifact/sec"
)
func TestRSA(t *testing.T) {
@@ -44,18 +44,6 @@ func TestPlainEcdsaPkcs8(t *testing.T) {
signatureTest(t, ecdsaPkcs8Private)
}
-func TestEncryptedRSA(t *testing.T) {
- sec.KeyPassword = []byte("sample")
- signatureTest(t, rsaEncryptedPrivate)
- sec.KeyPassword = []byte{}
-}
-
-func TestEncryptedEcdsa(t *testing.T) {
- sec.KeyPassword = []byte("sample")
- signatureTest(t, ecdsaEncryptedPrivate)
- sec.KeyPassword = []byte{}
-}
-
func signatureTest(t *testing.T, privateKey []byte) {
tmpdir, err := ioutil.TempDir("", "newttest")
if err != nil {
@@ -170,48 +158,3 @@ j0AQZlBs7f4r67668eDCUB8aDR2hRANCAATyZPzsx+xn9JtlxdspevTrYisiMTjl
YuBJCrV1FZj2HkplEgO+ZIMuD7eRvyTEBS2bw6F1aCeKOMUmYVImAbpc
-----END PRIVATE KEY-----
`)
-
-// A password-protected RSA private key in PKCS#5/8 format. The
-// password for this key is "sample".
-var rsaEncryptedPrivate = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
-MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIRMifqJThk8kCAggA
-MB0GCWCGSAFlAwQBKgQQTMUBoFpzjJ5UNRnCIeqf4QSCBNDkQvXnUNmss8erKiDo
-Uqs2tf9ZD8MjDThLBmF/gV1dg1q6aDY+3fI2E4yLXJb2PmKcUq82YZ0FDeoCvJRJ
-BCurzM9slur5akpNBTFoFwtFsdHz7nKNS4MHUul22rGBnVFUUNTySmpjl/m+dxWO
-fa6tWpGTAr7tsCy9gF5PxpSw7NR/NpIL0PmpydHWhTs1tl2csqBqK6Tp014Kefi/
-pmmeb2eRl5cmprxW32rW2QBMtv4z91SsbnlVdz4r8txTG+3S4td9v9jD5kqcIiC2
-KQHrbH9y7okUk/ISsp9ANKPJt10fbYDxORiMK57XssXy1enGjpkIIrUGz1TMydkD
-USfwqkmPuIrrzOXnbxU4ef2wC/pA/h9Smby3WWYo8725/1kZyIediNDcgi/Qgrs4
-1VQAYzsD6duwyUNSo+tgmYVFGvZhsottus3fMWe/Ay1biJ6z6Vk8gqKWI1VV/REJ
-zK/I9hgKGxj2N2Ff6E/YkcwQenHWj/iDWLjvokyOBnPFNqzzM2Qqo1XFpzj4EO5D
-0WD4EzZYvUhk3lZZNydvXiuy8RrCVLLJMS08XgOqQaiFqqxj2hjRwv3nBesk7iA8
-5Tv8GMa5QkNrISCnp4/uGBh+v/CjwVRqPTcK3/mctPN2nLhI6H4pF4Y6apXkz1TN
-NMQqxaxmVVg8fyLaS4/xfUr8LAmiEtOwvs0XOhcqCTvvlsO4N+yec4VD4gmsTDY9
-/2b/+YwSlGMpA+GQQbg0FraaF8NyJRG1mSER6WiUGGM1cuKK44nzBbykQbZwzDSA
-kkhjDaadkhv/NPKAUR3sNy2GXVaNL/ItCpQUHRKKcIPp0HhdXsl0YebuwRlHjw/6
-UOdzNYe23e40X/Xl3vmOKRbzhLP/qO2DV21o0wI4ujF8Xu5h1h8s49HPp58G1ldy
-/hJ6durYKX8T5khiR2iXYewoy0YObuccV//Ov1/ySOp/x0/QuCl/swvs8Jf7awnu
-rpRrHPArpCvMmXmt5Y+TFYXFjkJGwsxTew5TcwBebBlIET2XNbo2pbz4WqJ3eVlK
-CNZVDEZ8mMrGT00FBi759Vfw9rhrnqXnLlNtJZ5VCXFUw8Tos302sLaQWXzHYyf8
-4awM8G9PSu5Q9lFcN9od4H95YrAAv/l8F+pcGgEKD8ZuzsgFIalqgx5wzmUMDcPM
-NKV5u9mtHjI92ru6NB8rGesM6sy6kBGvpotsDWawpV2SoCrkbyEkk+kXaGS+fsG7
-D2H37GfktN8R5Ktc0Uf/JJiNfDzq8lk1J4r7LBQlWUbhKbfGMYxt+7Xo0GsqAsLp
-PKSUwx+hTZb3BmW6s4Q6vivI1MdQbWVT1zh41StvfRSNlo70iOFxOM0lU1jjY989
-UKo+gcolddvZbMNwip0ILPO3dsa+he1jJ/gbo9qBHLy7plfsBLLakZP1Nu6xdlqQ
-TSSobaE8uxUMZk+wMWClA9AOZ1TcUr2yRV5GVj/bxG9ab+H37vF9F8vFE+jjJ7yN
-6pjdohm4gXeSVx7ON4SeZLsVwNYkCVYS89E81qLx1jP9F57+6IUGDZN5EMC0aJLT
-ny75MCCLT00KD7BFsb0KDLXxp++eu/L2hinorT3p6dXp/9mUoxmy6wJqEyqCFniZ
-N2GZN7+LDTIbHUxCijVWamU2DQ==
------END ENCRYPTED PRIVATE KEY-----
-`)
-
-// A password-protected ECDSA private key in PKCS#5/8 format. The
-// password for this key is "sample"
-var ecdsaEncryptedPrivate = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
-MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAjlKrDSKNg9QQICCAAw
-HQYJYIZIAWUDBAEqBBDliPNzQTNpdlppTcYpmuhWBIGQVhfWaVSzUvi/qIZLiZVn
-Nulfw5jDOlbn3UBX9kp/Z9Pro582Q0kjzLfm5UahvDINEJWxL4pc/28UnGQTBr0Q
-nSEg+RbqpuD099C38H0Gq/YkIM+RDG4aiQrkmzHXyVsHshIbG+z2LsLTIwmU69/Z
-v0nX6/hGErVR8YWcrOne086rCvfJVrxyO5+EUqrkLhEr
------END ENCRYPTED PRIVATE KEY-----
-`)
diff --git a/image/v1.go b/image/v1.go
index 67a451f..0b82086 100644
--- a/image/v1.go
+++ b/image/v1.go
@@ -30,12 +30,11 @@ import (
"crypto/rsa"
"crypto/sha256"
"encoding/binary"
- "encoding/hex"
"io"
"io/ioutil"
- "mynewt.apache.org/newt/artifact/sec"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/sec"
)
const IMAGEv1_MAGIC = 0x96f3b83c /* Image header magic */
@@ -95,10 +94,10 @@ func (img *ImageV1) FindTlvs(tlvType uint8) []ImageTlv {
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")
+ return nil, errors.Errorf("image does not contain hash TLV")
}
if len(tlvs) > 1 {
- return nil, util.FmtNewtError("Image contains %d hash TLVs", len(tlvs))
+ return nil, errors.Errorf("image contains %d hash TLVs", len(tlvs))
}
return tlvs[0].Data, nil
@@ -112,14 +111,14 @@ func (img *ImageV1) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
err := binary.Write(w, binary.LittleEndian, &img.Header)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image header")
}
offset += IMAGE_HEADER_SIZE
offs.Body = offset
size, err := w.Write(img.Body)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image body")
}
offset += size
@@ -127,7 +126,7 @@ func (img *ImageV1) WritePlusOffsets(w io.Writer) (ImageOffsets, error) {
offs.Tlvs = append(offs.Tlvs, offset)
size, err := tlv.Write(w)
if err != nil {
- return offs, util.ChildNewtError(err)
+ return offs, errors.Wrapf(err, "failed to write image TLV")
}
offset += size
}
@@ -174,7 +173,7 @@ func sigHdrTypeV1(key sec.SignKey) (uint32, error) {
case "P-256":
return IMAGEv1_F_ECDSA256_SHA256, nil
default:
- return 0, util.FmtNewtError("Unsupported ECC curve")
+ return 0, errors.Errorf("unsupported ECC curve")
}
}
}
@@ -211,7 +210,7 @@ func generateV1SigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
rand.Reader, key, crypto.SHA256, hash)
}
if err != nil {
- return nil, util.FmtNewtError("Failed to compute signature: %s", err)
+ return nil, errors.Wrapf(err, "failed to compute signature")
}
return signature, nil
@@ -241,20 +240,19 @@ func generateV1SigTlvEc(key sec.SignKey, hash []byte) (ImageTlv, error) {
sigLen := key.SigLen()
if len(sig) > int(sigLen) {
- return ImageTlv{}, util.FmtNewtError("Something is really wrong\n")
+ return ImageTlv{}, errors.Errorf("signature truncated")
}
b := &bytes.Buffer{}
if _, err := b.Write(sig); err != nil {
- return ImageTlv{},
- util.FmtNewtError("Failed to append sig: %s", err.Error())
+ return ImageTlv{}, errors.Wrapf(err, "failed to append sig")
}
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{}, errors.Wrapf(err,
+ "failed to serialize image trailer")
}
return ImageTlv{
@@ -284,7 +282,7 @@ func calcHashV1(initialHash []byte, hdr ImageHdrV1,
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 errors.Wrapf(err, "failed to hash data")
}
return nil
@@ -319,8 +317,8 @@ 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",
+ return ri, errors.Errorf(
+ "v1 image format only allows one key, %d keys specified",
len(ic.SigKeys))
}
@@ -349,8 +347,8 @@ func (ic *ImageCreator) CreateV1() (ImageV1, error) {
* 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)
+ return ri, errors.Errorf(
+ "image header must be at least %d bytes", IMAGE_HEADER_SIZE)
}
hdr.HdrSz = uint16(ic.HeaderSize)
@@ -372,8 +370,8 @@ func (ic *ImageCreator) CreateV1() (ImageV1, error) {
// 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)
+ return ri, errors.Errorf(
+ "image header must be at least %d bytes", IMAGE_HEADER_SIZE)
}
hdr.HdrSz = uint16(ic.HeaderSize)
@@ -387,9 +385,6 @@ func (ic *ImageCreator) CreateV1() (ImageV1, error) {
return ri, err
}
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "Computed Hash for image as %s\n", hex.EncodeToString(hashBytes))
-
/*
* Followed by data.
*/
@@ -399,16 +394,14 @@ func (ic *ImageCreator) CreateV1() (ImageV1, error) {
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())
+ return ri, errors.Wrapf(err, "failed to read from image body")
}
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())
+ return ri, errors.Wrapf(err, "failed to write to image body")
}
}
ri.Body = w.Bytes()
@@ -448,8 +441,7 @@ func GenerateV1Image(opts ImageCreateOpts) (ImageV1, error) {
srcBin, err := ioutil.ReadFile(opts.SrcBinFilename)
if err != nil {
- return ImageV1{}, util.FmtNewtError(
- "Can't read app binary: %s", err.Error())
+ return ImageV1{}, errors.Wrapf(err, "can't read app binary")
}
ic.Body = srcBin
@@ -471,8 +463,7 @@ func GenerateV1Image(opts ImageCreateOpts) (ImageV1, error) {
pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename)
if err != nil {
- return ImageV1{}, util.FmtNewtError(
- "Error reading pubkey file: %s", err.Error())
+ return ImageV1{}, errors.Wrapf(err, "error reading pubkey file")
}
cipherSecret, err := GenerateCipherSecret(pubKeBytes, plainSecret)
if err != nil {
diff --git a/image/verify.go b/image/verify.go
new file mode 100644
index 0000000..91ddf28
--- /dev/null
+++ b/image/verify.go
@@ -0,0 +1,87 @@
+package image
+
+import (
+ "bytes"
+ "encoding/hex"
+
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/manifest"
+)
+
+// VerifyManifest compares an image's structure to its manifest. It returns
+// an error if the image doesn't match the manifest.
+func (img *Image) VerifyManifest(man manifest.Manifest) error {
+ ver, err := ParseVersion(man.Version)
+ if err != nil {
+ return errors.Wrapf(err, "manifest contains invalid `version` field")
+ }
+
+ if ver.Major != img.Header.Vers.Major ||
+ ver.Minor != img.Header.Vers.Minor ||
+ ver.Rev != img.Header.Vers.Rev ||
+ ver.BuildNum != img.Header.Vers.BuildNum {
+
+ return errors.Errorf(
+ "manifest version different from image header: man=%s img=%s",
+ ver.String(), img.Header.Vers.String())
+ }
+
+ var imgHash string
+ if hash, err := img.Hash(); err == nil {
+ imgHash = hex.EncodeToString(hash)
+ }
+
+ // A manifest contains two image hashes: `id` and `image_hash`. Check
+ // both.
+
+ checkHash := func(manHash string) error {
+ if imgHash != manHash {
+ return errors.Errorf(
+ "manifest image hash different from image TLV: man=%s img=%s",
+ manHash, imgHash)
+ }
+ return nil
+ }
+
+ if err := checkHash(man.BuildID); err != nil {
+ return err
+ }
+ if err := checkHash(man.ImageHash); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Verify checks an image's structure and internal consistency. It returns
+// an error if the image is incorrect.
+func (img *Image) Verify() error {
+ // Verify the hash.
+
+ haveHash, err := img.Hash()
+ if err != nil {
+ return err
+ }
+
+ wantHash, err := img.CalcHash()
+ if err != nil {
+ return err
+ }
+
+ if !bytes.Equal(haveHash, wantHash) {
+ return errors.Errorf(
+ "image manifest contains incorrect hash: have=%x want=%x",
+ haveHash, wantHash)
+ }
+
+ // Verify that each TLV has a valid "type" field.
+ for _, t := range img.Tlvs {
+ if !ImageTlvTypeIsValid(t.Header.Type) {
+ return errors.Errorf(
+ "image contains TLV with invalid `type` field: %d",
+ t.Header.Type)
+ }
+ }
+
+ return nil
+}
diff --git a/manifest/manifest.go b/manifest/manifest.go
index 95c76c6..3a7298a 100644
--- a/manifest/manifest.go
+++ b/manifest/manifest.go
@@ -23,8 +23,9 @@ import (
"encoding/json"
"io"
"io/ioutil"
+ "strings"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
)
/*
@@ -86,13 +87,12 @@ func ReadManifest(path string) (Manifest, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
- return m, util.ChildNewtError(err)
+ return m, errors.Wrapf(err, "failed to read manifest file")
}
if err := json.Unmarshal(content, &m); err != nil {
- return m, util.FmtNewtError(
- "Failure decoding manifest with path \"%s\": %s",
- path, err.Error())
+ return m, errors.Wrapf(
+ err, "failure decoding manifest with path \"%s\"", path)
}
return m, nil
@@ -101,13 +101,24 @@ func ReadManifest(path string) (Manifest, error) {
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())
+ return 0, errors.Wrapf(err, "Cannot encode manifest")
}
cnt, err := w.Write(buffer)
if err != nil {
- return 0, util.FmtNewtError("Cannot write manifest: %s", err.Error())
+ return 0, errors.Wrapf(err, "Cannot write manifest")
}
return cnt, nil
}
+
+func (m *Manifest) FindTargetVar(key string) string {
+ for _, tv := range m.TgtVars {
+ parts := strings.SplitN(tv, "=", 2)
+ if len(parts) == 2 && parts[0] == key {
+ return parts[1]
+ }
+ }
+
+ return ""
+}
diff --git a/manifest/mfg_manifest.go b/manifest/mfg_manifest.go
index f2c506c..3ebd22a 100644
--- a/manifest/mfg_manifest.go
+++ b/manifest/mfg_manifest.go
@@ -20,11 +20,13 @@
package manifest
import (
+ "encoding/hex"
"encoding/json"
"io/ioutil"
- "mynewt.apache.org/newt/artifact/flash"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/flash"
+ "github.com/apache/mynewt-artifact/sec"
)
type MfgManifestTarget struct {
@@ -48,7 +50,6 @@ type MfgManifestMeta struct {
Hash bool `json:"hash_present"`
FlashMap bool `json:"flash_map_present"`
Mmrs []MfgManifestMetaMmr `json:"mmrs,omitempty"`
- // XXX: refhash
}
type MfgManifestSig struct {
@@ -66,6 +67,7 @@ type MfgManifest struct {
BinPath string `json:"bin_path"`
HexPath string `json:"hex_path"`
Bsp string `json:"bsp"`
+ EraseVal byte `json:"erase_val"`
Signatures []MfgManifestSig `json:"signatures,omitempty"`
FlashAreas []flash.FlashArea `json:"flash_map"`
@@ -73,18 +75,29 @@ type MfgManifest struct {
Meta *MfgManifestMeta `json:"meta,omitempty"`
}
-func ReadMfgManifest(path string) (MfgManifest, error) {
- m := MfgManifest{}
+func ParseMfgManifest(jsonText []byte) (MfgManifest, error) {
+ m := MfgManifest{
+ // Backwards compatibility: assume 0xff if unspecified.
+ EraseVal: 0xff,
+ }
+
+ if err := json.Unmarshal(jsonText, &m); err != nil {
+ return m, errors.Wrapf(err, "failure decoding mfg manifest")
+ }
+ return m, nil
+}
+
+func ReadMfgManifest(path string) (MfgManifest, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
- return m, util.ChildNewtError(err)
+ return MfgManifest{}, errors.Wrapf(err,
+ "failed to read mfg manifest file")
}
- if err := json.Unmarshal(content, &m); err != nil {
- return m, util.FmtNewtError(
- "Failure decoding mfg manifest with path \"%s\": %s",
- path, err.Error())
+ m, err := ParseMfgManifest(content)
+ if err != nil {
+ return m, errors.Wrapf(err, "path=%s", path)
}
return m, nil
@@ -93,9 +106,67 @@ func ReadMfgManifest(path string) (MfgManifest, error) {
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 nil, errors.Wrapf(err, "cannot encode mfg manifest")
}
return buffer, nil
}
+
+func (m *MfgManifest) FindFlashArea(device int, offset int) *flash.FlashArea {
+ for i, _ := range m.FlashAreas {
+ fa := &m.FlashAreas[i]
+ if fa.Device == device && fa.Offset == offset {
+ return fa
+ }
+ }
+
+ return nil
+}
+
+func (m *MfgManifest) FindFlashAreaName(name string) *flash.FlashArea {
+ for i, _ := range m.FlashAreas {
+ fa := &m.FlashAreas[i]
+ if fa.Name == name {
+ return fa
+ }
+ }
+
+ return nil
+}
+
+func (ms *MfgManifestSig) SecSig() (sec.Sig, error) {
+ keyHash, err := hex.DecodeString(ms.Key)
+ if err != nil {
+ return sec.Sig{}, errors.Errorf(
+ "invalid hex-encoded key hash: %s", ms.Key)
+ }
+
+ data, err := hex.DecodeString(ms.Sig)
+ if err != nil {
+ return sec.Sig{}, errors.Errorf(
+ "invalid hex-encoded signature: %s", ms.Sig)
+ }
+
+ return sec.Sig{
+ KeyHash: keyHash,
+ Data: data,
+ }, nil
+}
+
+func (m *MfgManifest) SecSigs() ([]sec.Sig, error) {
+ var sigs []sec.Sig
+ for _, ms := range m.Signatures {
+ s, err := ms.SecSig()
+ if err != nil {
+ return nil, err
+ }
+
+ sigs = append(sigs, s)
+ }
+
+ return sigs, nil
+}
+
+func (mt *MfgManifestTarget) IsBoot() bool {
+ return mt.BinPath != ""
+}
diff --git a/mfg/map_meta.go b/mfg/map_meta.go
index b16c37e..19b1ee8 100644
--- a/mfg/map_meta.go
+++ b/mfg/map_meta.go
@@ -25,7 +25,7 @@ import (
"encoding/hex"
"encoding/json"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
)
func (t *MetaTlv) bodyMap() (map[string]interface{}, error) {
@@ -33,8 +33,7 @@ func (t *MetaTlv) bodyMap() (map[string]interface{}, error) {
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 errors.Wrapf(err, "error parsing TLV data")
}
return nil
}
@@ -62,8 +61,7 @@ func (t *MetaTlv) bodyMap() (map[string]interface{}, error) {
return body.Map(), nil
default:
- return nil, util.FmtNewtError(
- "Unknown meta TLV type: %d", t.Header.Type)
+ return nil, errors.Errorf("unknown meta TLV type: %d", t.Header.Type)
}
}
@@ -88,7 +86,7 @@ func (b *MetaTlvBodyMmrRef) Map() map[string]interface{} {
}
}
-func (t *MetaTlv) Map(offset int) map[string]interface{} {
+func (t *MetaTlv) Map(index int, offset int) map[string]interface{} {
hmap := map[string]interface{}{
"_type_name": MetaTlvTypeName(t.Header.Type),
"type": t.Header.Type,
@@ -105,6 +103,7 @@ func (t *MetaTlv) Map(offset int) map[string]interface{} {
}
return map[string]interface{}{
+ "_index": index,
"_offset": offset,
"header": hmap,
"data": body,
@@ -126,7 +125,7 @@ func (m *Meta) Map(endOffset int) map[string]interface{} {
tlvs := []map[string]interface{}{}
for i, t := range m.Tlvs {
- tlv := t.Map(startOffset + offsets.Tlvs[i])
+ tlv := t.Map(i, startOffset+offsets.Tlvs[i])
tlvs = append(tlvs, tlv)
}
@@ -146,7 +145,7 @@ func (m *Meta) Json(offset int) (string, error) {
bin, err := json.MarshalIndent(mmap, "", " ")
if err != nil {
- return "", util.ChildNewtError(err)
+ return "", errors.Wrapf(err, "failed to marshal MMR")
}
return string(bin), nil
diff --git a/mfg/meta.go b/mfg/meta.go
index 4521a89..b4786bc 100644
--- a/mfg/meta.go
+++ b/mfg/meta.go
@@ -25,7 +25,7 @@ import (
"io"
"io/ioutil"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
)
// The "manufacturing meta region" is located at the end of the boot loader
@@ -139,7 +139,7 @@ func MetaTlvTypeName(typ uint8) string {
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 errors.Wrapf(err, "failed to write MMR element")
}
return nil
}
@@ -160,6 +160,43 @@ func (tlv *MetaTlv) Write(w io.Writer) (int, error) {
return sz, nil
}
+func (tlv *MetaTlv) StructuredBody() (interface{}, error) {
+ r := bytes.NewReader(tlv.Data)
+
+ readBody := func(dst interface{}) error {
+ if err := binary.Read(r, binary.LittleEndian, dst); err != nil {
+ return errors.Wrapf(err, "error parsing TLV data")
+ }
+ return nil
+ }
+
+ switch tlv.Header.Type {
+ case META_TLV_TYPE_HASH:
+ var body MetaTlvBodyHash
+ if err := readBody(&body); err != nil {
+ return nil, err
+ }
+ return &body, nil
+
+ case META_TLV_TYPE_FLASH_AREA:
+ var body MetaTlvBodyFlashArea
+ if err := readBody(&body); err != nil {
+ return nil, err
+ }
+ return &body, nil
+
+ case META_TLV_TYPE_MMR_REF:
+ var body MetaTlvBodyMmrRef
+ if err := readBody(&body); err != nil {
+ return nil, err
+ }
+ return &body, nil
+
+ default:
+ return nil, errors.Errorf("unknown meta TLV type: %d", tlv.Header.Type)
+ }
+}
+
func (meta *Meta) WritePlusOffsets(w io.Writer) (MetaOffsets, error) {
mo := MetaOffsets{}
sz := 0
@@ -270,25 +307,41 @@ func (meta *Meta) Hash() []byte {
return tlv.Data
}
+func (meta *Meta) Clone() Meta {
+ var tlvs []MetaTlv
+ for _, src := range meta.Tlvs {
+ dst := MetaTlv{
+ Header: src.Header,
+ Data: make([]byte, len(src.Data)),
+ }
+ copy(dst.Data, src.Data)
+ tlvs = append(tlvs, dst)
+ }
+
+ return Meta{
+ Tlvs: tlvs,
+ Footer: meta.Footer,
+ }
+}
+
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())
+ return tlv, 0, errors.Wrapf(err, "error reading TLV header")
}
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())
+ return tlv, 0, errors.Wrapf(err,
+ "error reading %d bytes of TLV data",
+ tlv.Header.Size)
}
if sz != len(data) {
- return tlv, 0, util.FmtNewtError(
- "Error reading %d bytes of TLV data: incomplete read",
+ return tlv, 0, errors.Errorf(
+ "error reading %d bytes of TLV data: incomplete read",
tlv.Header.Size)
}
tlv.Data = data
@@ -301,13 +354,13 @@ func parseMetaFooter(bin []byte) (MetaFooter, int, error) {
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())
+ return ftr, 0, errors.Wrapf(err,
+ "error reading meta footer")
}
if ftr.Magic != META_MAGIC {
- return ftr, 0, util.FmtNewtError(
- "Meta footer contains invalid magic; exp:0x%08x, got:0x%08x",
+ return ftr, 0, errors.Errorf(
+ "meta footer contains invalid magic; exp:0x%08x, got:0x%08x",
META_MAGIC, ftr.Magic)
}
@@ -316,8 +369,8 @@ func parseMetaFooter(bin []byte) (MetaFooter, int, error) {
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; "+
+ return Meta{}, 0, errors.Errorf(
+ "binary too small to accommodate meta footer; "+
"bin-size=%d ftr-size=%d", len(bin), META_FOOTER_SZ)
}
@@ -327,8 +380,8 @@ func ParseMeta(bin []byte) (Meta, int, error) {
}
if int(ftr.Size) > len(bin) {
- return Meta{}, 0, util.FmtNewtError(
- "Binary too small to accommodate meta region; "+
+ return Meta{}, 0, errors.Errorf(
+ "binary too small to accommodate meta region; "+
"bin-size=%d meta-size=%d", len(bin), ftr.Size)
}
diff --git a/mfg/mfg.go b/mfg/mfg.go
index 2d5df83..eea6abf 100644
--- a/mfg/mfg.go
+++ b/mfg/mfg.go
@@ -20,9 +20,11 @@
package mfg
import (
+ "bytes"
"crypto/sha256"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/flash"
)
const MFG_BIN_IMG_FILENAME = "mfgimg.bin"
@@ -43,12 +45,22 @@ func Parse(data []byte, metaEndOff int, eraseVal byte) (Mfg, error) {
}
if metaEndOff >= 0 {
+ if metaEndOff > len(data) {
+ return m, errors.Errorf(
+ "MMR offset (%d) beyond end of mfgimage (%d)",
+ metaEndOff, len(data))
+ }
+
meta, _, err := ParseMeta(data[:metaEndOff])
if err != nil {
return m, err
}
m.Meta = &meta
m.MetaOff = metaEndOff - int(meta.Footer.Size)
+
+ for i := 0; i < int(meta.Footer.Size); i++ {
+ m.Bin[m.MetaOff+i] = eraseVal
+ }
}
return m, nil
@@ -85,52 +97,78 @@ func CalcHash(bin []byte) []byte {
return hash[:]
}
-func (m *Mfg) RecalcHash(eraseVal byte) error {
+func (m *Mfg) RecalcHash(eraseVal byte) ([]byte, error) {
+ // The hash TLV needs to be zeroed out in order to calculate the mfg
+ // hash. Duplicate the mfg object so that we don't modify the
+ // original.
+ dup := m.Clone()
+ if dup.Meta != nil {
+ dup.Meta.ClearHash()
+ }
+
+ bin, err := dup.Bytes(eraseVal)
+ if err != nil {
+ return nil, err
+ }
+
+ return CalcHash(bin), nil
+}
+
+func (m *Mfg) RefillHash(eraseVal byte) error {
if m.Meta == nil || m.Meta.Hash() == nil {
return nil
}
+ tlv := m.Meta.FindFirstTlv(META_TLV_TYPE_HASH)
+ if tlv == nil {
+ return nil
+ }
- // First, write with zeroed hash.
- m.Meta.ClearHash()
- bin, err := m.Bytes(eraseVal)
+ // Calculate hash.
+ hash, err := m.RecalcHash(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")
- }
- }
+ // Fill hash TLV.
+ copy(tlv.Data, hash)
return nil
}
-func (m *Mfg) Hash() ([]byte, error) {
+func (m *Mfg) Hash(eraseVal byte) ([]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)
+ b, err := m.RecalcHash(eraseVal)
if err != nil {
return nil, err
}
- hashBytes = CalcHash(bin)
+ hashBytes = b
}
return hashBytes, nil
}
+func (m *Mfg) HashIsValid(eraseVal byte) (bool, error) {
+ // If the mfg doesn't contain a hash TLV, then there is nothing to verify.
+ tlv := m.Meta.FindFirstTlv(META_TLV_TYPE_HASH)
+ if tlv == nil {
+ return true, nil
+ }
+
+ hash, err := m.RecalcHash(eraseVal)
+ if err != nil {
+ return false, err
+ }
+
+ return bytes.Equal(hash, tlv.Data), nil
+}
+
func (m *Mfg) Bytes(eraseVal byte) ([]byte, error) {
binCopy := make([]byte, len(m.Bin))
copy(binCopy, m.Bin)
@@ -149,3 +187,51 @@ func (m *Mfg) Bytes(eraseVal byte) ([]byte, error) {
return binCopy, nil
}
+
+func (m *Mfg) Clone() Mfg {
+ var meta *Meta
+ if m.Meta != nil {
+ metaDup := m.Meta.Clone()
+ meta = &metaDup
+ }
+
+ bin := make([]byte, len(m.Bin))
+ copy(bin, m.Bin)
+
+ return Mfg{
+ Bin: bin,
+ Meta: meta,
+ MetaOff: m.MetaOff,
+ }
+}
+
+func (m *Mfg) ExtractFlashArea(area flash.FlashArea, eraseVal byte) ([]byte, error) {
+ b, err := m.Bytes(eraseVal)
+ if err != nil {
+ return nil, err
+ }
+
+ if area.Offset >= len(b) {
+ return nil, errors.Errorf(
+ "flash area in mmr (\"%s\") is beyond end of mfgimage "+
+ "(offset=%d mfgimg_len=%d)",
+ area.Name, area.Offset, len(b))
+ }
+
+ // If the end of the target contains unwritten bytes, it gets truncated
+ // from the mfgimage.
+ end := area.Offset + area.Size
+ if end > len(b) {
+ end = len(b)
+ }
+
+ return b[area.Offset:end], nil
+}
+
+func (m *Mfg) Tlvs() []MetaTlv {
+ if m.Meta == nil {
+ return nil
+ } else {
+ return m.Meta.Tlvs
+ }
+}
diff --git a/mfg/verify.go b/mfg/verify.go
new file mode 100644
index 0000000..17a5a76
--- /dev/null
+++ b/mfg/verify.go
@@ -0,0 +1,202 @@
+package mfg
+
+import (
+ "bytes"
+ "encoding/hex"
+
+ "github.com/apache/mynewt-artifact/errors"
+ "github.com/apache/mynewt-artifact/flash"
+ "github.com/apache/mynewt-artifact/image"
+ "github.com/apache/mynewt-artifact/manifest"
+)
+
+func (m *Mfg) validateManFlashMap(man manifest.MfgManifest) error {
+ idAreaMap := map[int]flash.FlashArea{}
+ for _, area := range man.FlashAreas {
+ if _, dup := idAreaMap[area.Id]; dup {
+ return errors.Errorf(
+ "mfg manifest contains duplicate flash area: %d", area.Id)
+ }
+
+ idAreaMap[area.Id] = area
+ }
+
+ seen := map[int]struct{}{}
+
+ mmrHasFlash := man.Meta != nil && man.Meta.FlashMap
+
+ for _, t := range m.Tlvs() {
+ if t.Header.Type == META_TLV_TYPE_FLASH_AREA {
+ if !mmrHasFlash {
+ return errors.Errorf(
+ "mmr contains flash map; manifest indicates otherwise")
+ }
+
+ body, err := t.StructuredBody()
+ if err != nil {
+ return err
+ }
+ flashBody := body.(*MetaTlvBodyFlashArea)
+ if _, ok := idAreaMap[int(flashBody.Area)]; !ok {
+ return errors.Errorf(
+ "flash area %d missing from mfg manifest", flashBody.Area)
+ }
+
+ seen[int(flashBody.Area)] = struct{}{}
+ }
+ }
+
+ if mmrHasFlash {
+ for _, area := range man.FlashAreas {
+ if _, ok := seen[area.Id]; !ok {
+ return errors.Errorf("flash area %d missing from mmr", area.Id)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (m *Mfg) validateManMmrs(man manifest.MfgManifest) error {
+ areaMap := map[int]struct{}{}
+ if man.Meta != nil {
+ for _, mmr := range man.Meta.Mmrs {
+ fa := man.FindFlashAreaName(mmr.Area)
+ if fa == nil {
+ return errors.Errorf(
+ "flash area %s missing from mfg manifest", mmr.Area)
+ }
+
+ if _, dup := areaMap[fa.Id]; dup {
+ return errors.Errorf(
+ "mfg manifest contains duplicate mmr ref: %s", mmr.Area)
+ }
+
+ areaMap[fa.Id] = struct{}{}
+ }
+ }
+
+ seen := map[int]struct{}{}
+ for _, t := range m.Tlvs() {
+ if t.Header.Type == META_TLV_TYPE_MMR_REF {
+ body, err := t.StructuredBody()
+ if err != nil {
+ return err
+ }
+
+ mmrBody := body.(*MetaTlvBodyMmrRef)
+ if _, ok := areaMap[int(mmrBody.Area)]; !ok {
+ return errors.Errorf(
+ "mmr ref %d missing from mfg manifest", mmrBody.Area)
+ }
+
+ seen[int(mmrBody.Area)] = struct{}{}
+ }
+ }
+
+ for area, _ := range areaMap {
+ if _, ok := seen[area]; !ok {
+ return errors.Errorf("mmr ref %d missing from mmr", area)
+ }
+ }
+
+ return nil
+}
+
+func (m *Mfg) validateManTargets(man manifest.MfgManifest) error {
+ for _, t := range man.Targets {
+ fa := man.FindFlashArea(man.Device, t.Offset)
+ if fa == nil {
+ return errors.Errorf(
+ "no flash area in mfgimage corresponding to target \"%s\"",
+ t.Name)
+ }
+
+ data, err := m.ExtractFlashArea(*fa, man.EraseVal)
+ if err != nil {
+ return err
+ }
+
+ if !t.IsBoot() {
+ img, err := image.ParseImage(data)
+ if err != nil {
+ return errors.Wrapf(err,
+ "error parsing build \"%s\" embedded in mfgimage", t.Name)
+ }
+
+ if err := img.Verify(); err != nil {
+ return errors.Wrapf(err,
+ "mfgimage contains invalid build \"%s\"", t.Name)
+ }
+ }
+ }
+
+ return nil
+}
+
+// VerifyManifest compares an mfgimage's structure to its manifest. It
+// returns an error if the mfgimage doesn't match the manifest.
+func (m *Mfg) VerifyManifest(man manifest.MfgManifest) error {
+ if man.Format != 2 {
+ return errors.Errorf(
+ "only mfgimage format 2 supported (have=%d)", man.Format)
+ }
+
+ mfgHash, err := m.Hash(man.EraseVal)
+ if err != nil {
+ return err
+ }
+ hashStr := hex.EncodeToString(mfgHash)
+ if hashStr != man.MfgHash {
+ return errors.Errorf(
+ "manifest mfg hash different from mmr: man=%s mfg=%s",
+ man.MfgHash, hashStr)
+ }
+
+ if err := m.validateManFlashMap(man); err != nil {
+ return err
+ }
+
+ if err := m.validateManMmrs(man); err != nil {
+ return err
+ }
+
+ // Verify each embedded build.
+ if err := m.validateManTargets(man); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Verify checks an mfgimage's structure and internal consistency. It
+// returns an error if the mfgimage is incorrect.
+func (m *Mfg) Verify(eraseVal byte) error {
+ for _, t := range m.Tlvs() {
+ // Verify that TLV has a valid `type` field.
+ body, err := t.StructuredBody()
+ if err != nil {
+ return err
+ }
+
+ // Verify contents of hash TLV.
+ switch t.Header.Type {
+ case META_TLV_TYPE_HASH:
+ hashBody := body.(*MetaTlvBodyHash)
+
+ hash, err := m.RecalcHash(eraseVal)
+ if err != nil {
+ return err
+ }
+
+ if !bytes.Equal(hash, hashBody.Hash[:]) {
+ return errors.Errorf(
+ "mmr contains incorrect hash: have=%s want=%s",
+ hex.EncodeToString(hashBody.Hash[:]),
+ hex.EncodeToString(hash))
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/sec/encrypt.go b/sec/encrypt.go
index 35ecdd7..d58806c 100644
--- a/sec/encrypt.go
+++ b/sec/encrypt.go
@@ -25,13 +25,13 @@ import (
"crypto/cipher"
"io"
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
)
func EncryptAES(plain []byte, secret []byte) ([]byte, error) {
block, err := aes.NewCipher(secret)
if err != nil {
- return nil, util.NewNewtError("Failed to create block cipher")
+ return nil, errors.Errorf("Failed to create block cipher")
}
nonce := make([]byte, 16)
stream := cipher.NewCTR(block, nonce)
@@ -43,8 +43,7 @@ func EncryptAES(plain []byte, secret []byte) ([]byte, error) {
for {
cnt, err := r.Read(dataBuf)
if err != nil && err != io.EOF {
- return nil, util.FmtNewtError(
- "Failed to read from plaintext: %s", err.Error())
+ return nil, errors.Wrapf(err, "Failed to read from plaintext")
}
if cnt == 0 {
break
@@ -52,8 +51,7 @@ func EncryptAES(plain []byte, secret []byte) ([]byte, error) {
stream.XORKeyStream(encBuf, dataBuf[0:cnt])
if _, err = w.Write(encBuf[0:cnt]); err != nil {
- return nil, util.FmtNewtError(
- "Failed to write ciphertext: %s", err.Error())
+ return nil, errors.Wrapf(err, "failed to write ciphertext")
}
}
diff --git a/sec/key.go b/sec/key.go
index 9d073bd..473ef62 100644
--- a/sec/key.go
+++ b/sec/key.go
@@ -33,8 +33,7 @@ import (
"io/ioutil"
keywrap "github.com/NickBall/go-aes-key-wrap"
-
- "mynewt.apache.org/newt/util"
+ "github.com/apache/mynewt-artifact/errors"
)
type SignKey struct {
@@ -43,6 +42,11 @@ type SignKey struct {
Ec *ecdsa.PrivateKey
}
+type PubSignKey struct {
+ Rsa *rsa.PublicKey
+ Ec *ecdsa.PublicKey
+}
+
func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
var privKey interface{}
var err error
@@ -63,8 +67,7 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
*/
privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
- return nil, util.FmtNewtError(
- "Private key parsing failed: %s", err)
+ return nil, errors.Wrapf(err, "Private key parsing failed")
}
}
if block != nil && block.Type == "EC PRIVATE KEY" {
@@ -73,8 +76,7 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
*/
privKey, err = x509.ParseECPrivateKey(block.Bytes)
if err != nil {
- return nil, util.FmtNewtError(
- "Private key parsing failed: %s", err)
+ return nil, errors.Wrapf(err, "Private key parsing failed")
}
}
if block != nil && block.Type == "PRIVATE KEY" {
@@ -83,8 +85,7 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
// the key itself.
privKey, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
- return nil, util.FmtNewtError(
- "Private key parsing failed: %s", err)
+ return nil, errors.Wrapf(err, "Private key parsing failed")
}
}
if block != nil && block.Type == "ENCRYPTED PRIVATE KEY" {
@@ -92,12 +93,12 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
// encryption.
privKey, err = parseEncryptedPrivateKey(block.Bytes)
if err != nil {
- return nil, util.FmtNewtError(
- "Unable to decode encrypted private key: %s", err)
+ return nil, errors.Wrapf(
+ err, "Unable to decode encrypted private key")
}
}
if privKey == nil {
- return nil, util.NewNewtError(
+ return nil, errors.Errorf(
"Unknown private key format, EC/RSA private " +
"key in PEM format only.")
}
@@ -119,7 +120,7 @@ func BuildPrivateKey(keyBytes []byte) (SignKey, error) {
case *ecdsa.PrivateKey:
key.Ec = priv
default:
- return key, util.NewNewtError("Unknown private key format")
+ return key, errors.Errorf("Unknown private key format")
}
return key, nil
@@ -128,8 +129,7 @@ func BuildPrivateKey(keyBytes []byte) (SignKey, error) {
func ReadKey(filename string) (SignKey, error) {
keyBytes, err := ioutil.ReadFile(filename)
if err != nil {
- return SignKey{}, util.FmtNewtError(
- "Error reading key file: %s", err)
+ return SignKey{}, errors.Wrapf(err, "Error reading key file")
}
return BuildPrivateKey(keyBytes)
@@ -154,31 +154,21 @@ func (key *SignKey) 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 *SignKey) PubBytes() ([]uint8, error) {
+func (key *SignKey) PubKey() PubSignKey {
key.AssertValid()
- var pubkey []byte
-
if key.Rsa != nil {
- pubkey, _ = asn1.Marshal(key.Rsa.PublicKey)
+ return PubSignKey{Rsa: &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 PubSignKey{Ec: &key.Ec.PublicKey}
}
+}
- return pubkey, nil
+func (key *SignKey) PubBytes() ([]byte, error) {
+ pk := key.PubKey()
+ return pk.PubBytes()
}
func RawKeyHash(pubKeyBytes []byte) []byte {
@@ -211,13 +201,12 @@ func ParsePubKePem(keyBytes []byte) (*rsa.PublicKey, error) {
}
if b.Type != "PUBLIC KEY" && b.Type != "RSA PUBLIC KEY" {
- return nil, util.NewNewtError("Invalid PEM file")
+ return nil, errors.Errorf("Invalid PEM file")
}
pub, err := x509.ParsePKIXPublicKey(b.Bytes)
if err != nil {
- return nil, util.FmtNewtError(
- "Error parsing pubkey file: %s", err.Error())
+ return nil, errors.Wrapf(err, "Error parsing pubkey file")
}
var pubk *rsa.PublicKey
@@ -225,8 +214,7 @@ func ParsePubKePem(keyBytes []byte) (*rsa.PublicKey, error) {
case *rsa.PublicKey:
pubk = pub.(*rsa.PublicKey)
default:
- return nil, util.FmtNewtError(
- "Error parsing pubkey file: %s", err.Error())
+ return nil, errors.Wrapf(err, "Error parsing pubkey file")
}
return pubk, nil
@@ -235,8 +223,7 @@ func ParsePubKePem(keyBytes []byte) (*rsa.PublicKey, error) {
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 nil, errors.Wrapf(err, "Error parsing private key file")
}
return privKey, nil
@@ -247,8 +234,7 @@ func EncryptSecretRsa(pubk *rsa.PublicKey, plainSecret []byte) ([]byte, error) {
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 nil, errors.Wrapf(err, "Error from encryption")
}
return cipherSecret, nil
@@ -261,8 +247,7 @@ func DecryptSecretRsa(privk *rsa.PrivateKey,
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 nil, errors.Wrapf(err, "Error from encryption")
}
return plainSecret, nil
@@ -271,18 +256,18 @@ func DecryptSecretRsa(privk *rsa.PrivateKey,
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())
+ return nil, errors.Wrapf(err,
+ "Error decoding kek: %s")
}
if len(kek) != 16 {
- return nil, util.FmtNewtError(
+ return nil, errors.Errorf(
"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 nil, errors.Wrapf(err,
+ "Error creating keywrap cipher")
}
return cipher, nil
@@ -291,8 +276,39 @@ func ParseKeBase64(keyBytes []byte) (cipher.Block, error) {
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 nil, errors.Wrapf(err, "Error key-wrapping")
}
return cipherSecret, nil
}
+
+func (key *PubSignKey) AssertValid() {
+ if key.Rsa == nil && key.Ec == nil {
+ panic("invalid public key; neither RSA nor ECC")
+ }
+}
+
+func (key *PubSignKey) PubBytes() ([]byte, error) {
+ key.AssertValid()
+
+ var pubBytes []byte
+
+ if key.Rsa != nil {
+ var err error
+ pubBytes, err = asn1.Marshal(*key.Rsa)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ switch key.Ec.Curve.Params().Name {
+ case "P-224":
+ fallthrough
+ case "P-256":
+ pubBytes, _ = x509.MarshalPKIXPublicKey(*key.Ec)
+ default:
+ return nil, errors.Errorf("unsupported ECC curve")
+ }
+ }
+
+ return pubBytes, nil
+}
diff --git a/sec/sig.go b/sec/sig.go
new file mode 100644
index 0000000..8aa0b32
--- /dev/null
+++ b/sec/sig.go
@@ -0,0 +1,55 @@
+package sec
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rsa"
+
+ "github.com/apache/mynewt-artifact/errors"
+)
+
+type Sig struct {
+ KeyHash []byte
+ Data []byte
+}
+
+func checkOneKeyOneSig(k PubSignKey, sig Sig, hash []byte) (bool, error) {
+ pubBytes, err := k.PubBytes()
+ if err != nil {
+ return false, errors.WithStack(err)
+ }
+ keyHash := RawKeyHash(pubBytes)
+
+ if !bytes.Equal(keyHash, sig.KeyHash) {
+ return false, nil
+ }
+
+ if k.Rsa != nil {
+ opts := rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthEqualsHash,
+ }
+ err := rsa.VerifyPSS(k.Rsa, crypto.SHA256, hash, sig.Data, &opts)
+ return err == nil, nil
+ }
+
+ if k.Ec != nil {
+ return false, errors.Errorf(
+ "ecdsa signature verification not supported")
+ }
+
+ return false, nil
+}
+
+func VerifySigs(key PubSignKey, sigs []Sig, hash []byte) (int, error) {
+ for i, s := range sigs {
+ match, err := checkOneKeyOneSig(key, s, hash)
+ if err != nil {
+ return -1, err
+ }
+ if match {
+ return i, nil
+ }
+ }
+
+ return -1, nil
+}