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/26 18:39:42 UTC

[mynewt-artifact] branch master updated (441a611 -> d5de4c2)

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

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


    from 441a611  Merge pull request #1 from ccollins476ad/verify
     new 23225d4  Support verification of encrypted images
     new d5de4c2  image: Add tests for encrypted images

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 image/create.go                                    |  45 +---
 image/image.go                                     | 170 ++++++++++++-
 image/image_test.go                                | 130 +++++++---
 image/keys_test.go                                 |   2 +-
 image/testdata/enc-key-pub.der                     | Bin 0 -> 270 bytes
 image/testdata/enc-key-pub.pem                     |   9 +
 image/testdata/enc-key.der                         | Bin 0 -> 1190 bytes
 image/testdata/enc-key.pem                         |  27 ++
 image/testdata/good-signed-encrypted.img           | Bin 0 -> 9940 bytes
 ...d-signature.json => good-signed-encrypted.json} |   8 +-
 ...good-signed.img => good-signed-unencrypted.img} | Bin
 ...od-signed.json => good-signed-unencrypted.json} |   0
 ...-unsigned.img => good-unsigned-unencrypted.img} | Bin
 ...nsigned.json => good-unsigned-unencrypted.json} |   0
 image/testdata/sign-key-pub.pem                    |   9 +
 image/testdata/wrong-enc-key.img                   | Bin 0 -> 9940 bytes
 .../{bad-signature.json => wrong-enc-key.json}     |   8 +-
 image/v1.go                                        |  18 +-
 image/verify.go                                    | 175 ++++++++++---
 mfg/mfg_test.go                                    |  46 ++--
 mfg/verify.go                                      |  85 +++++--
 sec/encrypt.go                                     | 127 +++++++++-
 sec/key.go                                         | 282 ---------------------
 sec/read.go                                        | 107 ++++++++
 sec/sig.go                                         |  74 ------
 sec/{key.go => sign.go}                            | 211 ++++++---------
 sec/util.go                                        |  28 ++
 27 files changed, 893 insertions(+), 668 deletions(-)
 create mode 100644 image/testdata/enc-key-pub.der
 create mode 100644 image/testdata/enc-key-pub.pem
 create mode 100644 image/testdata/enc-key.der
 create mode 100644 image/testdata/enc-key.pem
 create mode 100644 image/testdata/good-signed-encrypted.img
 copy image/testdata/{bad-signature.json => good-signed-encrypted.json} (99%)
 rename image/testdata/{good-signed.img => good-signed-unencrypted.img} (100%)
 rename image/testdata/{good-signed.json => good-signed-unencrypted.json} (100%)
 rename image/testdata/{good-unsigned.img => good-unsigned-unencrypted.img} (100%)
 rename image/testdata/{good-unsigned.json => good-unsigned-unencrypted.json} (100%)
 create mode 100644 image/testdata/sign-key-pub.pem
 create mode 100644 image/testdata/wrong-enc-key.img
 copy image/testdata/{bad-signature.json => wrong-enc-key.json} (99%)
 create mode 100644 sec/read.go
 delete mode 100644 sec/sig.go
 copy sec/{key.go => sign.go} (54%)
 create mode 100644 sec/util.go


[mynewt-artifact] 01/02: Support verification of encrypted images

Posted by cc...@apache.org.
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 23225d42bed1c5df84896dd2f0315ebf3f906d72
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Tue Jun 25 17:06:00 2019 -0700

    Support verification of encrypted images
    
    An image's hash cannot be verified while the image is encrypted.  To
    verify the hash of such an image, the image must be decrypted first
    (without clearing the encrypted flag in the header).  This complicates
    the API, as the caller now needs to pass in a set of encryption keys.
    
    The fix is to split the image.Verify() function into several pieces:
        * VerifyStructure()
        * VerifyHash()
        * VerifySigs()
        * VerifyManifest()
---
 image/create.go         |  45 +++-----
 image/image.go          | 170 +++++++++++++++++++++++++++--
 image/image_test.go     |  62 ++++++-----
 image/keys_test.go      |   2 +-
 image/v1.go             |  18 ++--
 image/verify.go         | 175 ++++++++++++++++++++++++------
 mfg/mfg_test.go         |  46 +++-----
 mfg/verify.go           |  85 ++++++++++-----
 sec/encrypt.go          | 127 +++++++++++++++++++++-
 sec/key.go              | 282 ------------------------------------------------
 sec/read.go             | 107 ++++++++++++++++++
 sec/sig.go              |  74 -------------
 sec/{key.go => sign.go} | 211 ++++++++++++++----------------------
 sec/util.go             |  28 +++++
 14 files changed, 779 insertions(+), 653 deletions(-)

diff --git a/image/create.go b/image/create.go
index f51d77b..6167453 100644
--- a/image/create.go
+++ b/image/create.go
@@ -38,7 +38,7 @@ import (
 type ImageCreator struct {
 	Body         []byte
 	Version      ImageVersion
-	SigKeys      []sec.SignKey
+	SigKeys      []sec.PrivSignKey
 	PlainSecret  []byte
 	CipherSecret []byte
 	HeaderSize   int
@@ -50,7 +50,7 @@ type ImageCreateOpts struct {
 	SrcBinFilename    string
 	SrcEncKeyFilename string
 	Version           ImageVersion
-	SigKeys           []sec.SignKey
+	SigKeys           []sec.PrivSignKey
 	LoaderHash        []byte
 }
 
@@ -66,7 +66,7 @@ func NewImageCreator() ImageCreator {
 	}
 }
 
-func sigTlvType(key sec.SignKey) uint8 {
+func sigTlvType(key sec.PrivSignKey) uint8 {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -112,7 +112,7 @@ func GenerateEncTlv(cipherSecret []byte) (ImageTlv, error) {
 	}, nil
 }
 
-func GenerateSigRsa(key sec.SignKey, hash []byte) ([]byte, error) {
+func GenerateSigRsa(key sec.PrivSignKey, hash []byte) ([]byte, error) {
 	opts := rsa.PSSOptions{
 		SaltLength: rsa.PSSSaltLengthEqualsHash,
 	}
@@ -125,7 +125,7 @@ func GenerateSigRsa(key sec.SignKey, hash []byte) ([]byte, error) {
 	return signature, nil
 }
 
-func GenerateSigEc(key sec.SignKey, hash []byte) ([]byte, error) {
+func GenerateSigEc(key sec.PrivSignKey, hash []byte) ([]byte, error) {
 	r, s, err := ecdsa.Sign(rand.Reader, key.Ec, hash)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to compute signature")
@@ -152,7 +152,7 @@ func GenerateSigEc(key sec.SignKey, hash []byte) ([]byte, error) {
 	return signature, nil
 }
 
-func GenerateSig(key sec.SignKey, hash []byte) ([]byte, error) {
+func GenerateSig(key sec.PrivSignKey, hash []byte) ([]byte, error) {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -174,7 +174,7 @@ func BuildKeyHashTlv(keyBytes []byte) ImageTlv {
 	}
 }
 
-func BuildSigTlvs(keys []sec.SignKey, hash []byte) ([]ImageTlv, error) {
+func BuildSigTlvs(keys []sec.PrivSignKey, hash []byte) ([]ImageTlv, error) {
 	var tlvs []ImageTlv
 
 	for _, key := range keys {
@@ -215,30 +215,6 @@ func GeneratePlainSecret() ([]byte, error) {
 	return plainSecret, nil
 }
 
-func GenerateCipherSecret(pubKeBytes []byte,
-	plainSecret []byte) ([]byte, error) {
-
-	// Try reading as PEM (asymetric key).
-	rsaPubKe, err := sec.ParsePubKePem(pubKeBytes)
-	if err != nil {
-		return nil, err
-	}
-	if rsaPubKe != nil {
-		return sec.EncryptSecretRsa(rsaPubKe, plainSecret)
-	}
-
-	// Not PEM; assume this is a base64 encoded symetric key
-	aesPubKe, err := sec.ParseKeBase64(pubKeBytes)
-	if err != nil {
-		return nil, err
-	}
-	if aesPubKe != nil {
-		return sec.EncryptSecretAes(aesPubKe, plainSecret)
-	}
-
-	return nil, errors.Errorf("invalid image-crypt key")
-}
-
 func GenerateImage(opts ImageCreateOpts) (Image, error) {
 	ic := NewImageCreator()
 
@@ -269,7 +245,12 @@ func GenerateImage(opts ImageCreateOpts) (Image, error) {
 			return Image{}, errors.Wrapf(err, "error reading pubkey file")
 		}
 
-		cipherSecret, err := GenerateCipherSecret(pubKeBytes, plainSecret)
+		pubKe, err := sec.ParsePubEncKey(pubKeBytes)
+		if err != nil {
+			return Image{}, err
+		}
+
+		cipherSecret, err := pubKe.Encrypt(plainSecret)
 		if err != nil {
 			return Image{}, err
 		}
diff --git a/image/image.go b/image/image.go
index 7289f52..dff4a00 100644
--- a/image/image.go
+++ b/image/image.go
@@ -145,11 +145,23 @@ func ImageTlvTypeIsSig(tlvType uint8) bool {
 		tlvType == IMAGE_TLV_ECDSA256
 }
 
+func ImageTlvTypeIsSecret(tlvType uint8) bool {
+	return tlvType == IMAGE_TLV_ENC_RSA ||
+		tlvType == IMAGE_TLV_ENC_KEK
+}
+
 func (ver ImageVersion) String() string {
 	return fmt.Sprintf("%d.%d.%d.%d",
 		ver.Major, ver.Minor, ver.Rev, ver.BuildNum)
 }
 
+func (tlv *ImageTlv) Clone() ImageTlv {
+	return ImageTlv{
+		Header: tlv.Header,
+		Data:   append([]byte(nil), tlv.Data...),
+	}
+}
+
 func (tlv *ImageTlv) Write(w io.Writer) (int, error) {
 	totalSize := 0
 
@@ -168,13 +180,29 @@ func (tlv *ImageTlv) Write(w io.Writer) (int, error) {
 	return totalSize, nil
 }
 
-// FindTlvIndices searches an image for TLVs of the specified type and
-// returns their indices.
-func (i *Image) FindTlvIndices(tlvType uint8) []int {
+// Clone performs a deep copy of an image.
+func (img *Image) Clone() Image {
+	dup := Image{
+		Header: img.Header,
+		Pad:    append([]byte(nil), img.Pad...),
+		Body:   append([]byte(nil), img.Body...),
+		Tlvs:   make([]ImageTlv, len(img.Tlvs)),
+	}
+
+	for i, tlv := range img.Tlvs {
+		dup.Tlvs[i] = tlv.Clone()
+	}
+
+	return dup
+}
+
+// FindTlvIndicesIf searches an image for TLVs satisfying the given predicate
+// and returns their indices.
+func (img *Image) FindTlvIndicesIf(pred func(tlv ImageTlv) bool) []int {
 	var idxs []int
 
-	for i, tlv := range i.Tlvs {
-		if tlv.Header.Type == tlvType {
+	for i, tlv := range img.Tlvs {
+		if pred(tlv) {
 			idxs = append(idxs, i)
 		}
 	}
@@ -182,13 +210,34 @@ func (i *Image) FindTlvIndices(tlvType uint8) []int {
 	return idxs
 }
 
+// FindTlvIndices searches an image for TLVs of the specified type and
+// returns their indices.
+func (img *Image) FindTlvIndices(tlvType uint8) []int {
+	return img.FindTlvIndicesIf(func(tlv ImageTlv) bool {
+		return tlv.Header.Type == tlvType
+	})
+}
+
+// FindTlvIndices searches an image for TLVs satisfying the given predicate and
+// returns them.
+func (img *Image) FindTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv {
+	var tlvs []*ImageTlv
+
+	idxs := img.FindTlvIndicesIf(pred)
+	for _, idx := range idxs {
+		tlvs = append(tlvs, &img.Tlvs[idx])
+	}
+
+	return tlvs
+}
+
 // FindTlvs retrieves all TLVs in an image's footer with the specified type.
-func (i *Image) FindTlvs(tlvType uint8) []*ImageTlv {
+func (img *Image) FindTlvs(tlvType uint8) []*ImageTlv {
 	var tlvs []*ImageTlv
 
-	idxs := i.FindTlvIndices(tlvType)
+	idxs := img.FindTlvIndices(tlvType)
 	for _, idx := range idxs {
-		tlvs = append(tlvs, &i.Tlvs[idx])
+		tlvs = append(tlvs, &img.Tlvs[idx])
 	}
 
 	return tlvs
@@ -388,3 +437,108 @@ func (img *Image) CollectSigs() ([]sec.Sig, error) {
 
 	return sigs, nil
 }
+
+// CollectSecret finds the "secret" TLV in an image and returns its body.  It
+// returns nil if there is no "secret" TLV.
+func (img *Image) CollectSecret() ([]byte, error) {
+	tlv, err := img.FindUniqueTlv(IMAGE_TLV_ENC_RSA)
+	if err != nil {
+		return nil, err
+	}
+
+	if tlv == nil {
+		return nil, nil
+	}
+
+	return tlv.Data, nil
+}
+
+// ExtractSecret finds the "secret" TLV in an image, removes it, and returns
+// its body.  It returns nil if there is no "secret" TLV.
+func (img *Image) ExtractSecret() ([]byte, error) {
+	tlvs := img.RemoveTlvsWithType(IMAGE_TLV_ENC_RSA)
+
+	if len(tlvs) == 0 {
+		return nil, nil
+	}
+
+	if len(tlvs) > 1 {
+		return nil, errors.Errorf(
+			"image contains >1 ENC_RSA TLVs (%d)", len(tlvs))
+	}
+
+	return tlvs[0].Data, nil
+}
+
+// Encrypt encrypts an image body and adds a "secret" TLV.  It does NOT set the
+// "encrypted" flag in the image header.
+func Encrypt(img Image, pubEncKey sec.PubEncKey) (Image, error) {
+	dup := img.Clone()
+
+	tlvp, err := dup.FindUniqueTlv(IMAGE_TLV_ENC_RSA)
+	if err != nil {
+		return dup, err
+	}
+	if tlvp != nil {
+		return dup, errors.Errorf("image already contains an ENC_RSA TLV")
+	}
+
+	plainSecret, err := GeneratePlainSecret()
+	if err != nil {
+		return dup, err
+	}
+
+	cipherSecret, err := pubEncKey.Encrypt(plainSecret)
+	if err != nil {
+		return dup, err
+	}
+
+	body, err := sec.EncryptAES(dup.Body, plainSecret)
+	if err != nil {
+		return dup, err
+	}
+	dup.Body = body
+
+	tlv, err := GenerateEncTlv(cipherSecret)
+	if err != nil {
+		return dup, err
+	}
+	dup.Tlvs = append(dup.Tlvs, tlv)
+
+	return dup, nil
+}
+
+// Decrypt decrypts an image body and strips the "secret" TLV.  It does NOT
+// clear the "encrypted" flag in the image header.
+func Decrypt(img Image, privEncKey sec.PrivEncKey) (Image, error) {
+	dup := img.Clone()
+
+	tlvs := dup.RemoveTlvsIf(func(tlv ImageTlv) bool {
+		return ImageTlvTypeIsSecret(tlv.Header.Type)
+	})
+	if len(tlvs) != 1 {
+		return dup, errors.Errorf(
+			"failed to decrypt image: wrong count of \"secret\" TLVs; "+
+				"have=%d want=1", len(tlvs))
+	}
+
+	cipherSecret := tlvs[0].Data
+	plainSecret, err := privEncKey.Decrypt(cipherSecret)
+	if err != nil {
+		return img, err
+	}
+
+	body, err := sec.EncryptAES(dup.Body, plainSecret)
+	if err != nil {
+		return img, err
+	}
+
+	dup.Body = body
+
+	return dup, nil
+}
+
+// IsEncrypted indicates whether an image's "encrypted" flag is set.
+func (img *Image) IsEncrypted() bool {
+	return img.Header.Flags&IMAGE_F_ENCRYPTED != 0
+}
diff --git a/image/image_test.go b/image/image_test.go
index 83b4295..7cb5bf6 100644
--- a/image/image_test.go
+++ b/image/image_test.go
@@ -33,7 +33,8 @@ const testdataPath = "testdata"
 type entry struct {
 	basename  string
 	form      bool
-	integrity bool
+	structure bool
+	hash      bool
 	man       bool
 	sign      bool
 }
@@ -63,7 +64,7 @@ func readManifest(basename string) manifest.Manifest {
 func readPubKey() sec.PubSignKey {
 	path := fmt.Sprintf("%s/sign-key.pem", testdataPath)
 
-	key, err := sec.ReadKey(path)
+	key, err := sec.ReadPrivSignKey(path)
 	if err != nil {
 		panic("failed to read key file " + path)
 	}
@@ -97,15 +98,28 @@ func testOne(t *testing.T, e entry) {
 		}
 	}
 
-	err = img.Verify()
-	if !e.integrity {
+	err = img.VerifyStructure()
+	if !e.structure {
 		if err == nil {
-			fatalErr("integrity", "good", "bad", nil)
+			fatalErr("structure", "good", "bad", nil)
 		}
 		return
 	} else {
 		if err != nil {
-			fatalErr("integrity", "bad", "good", err)
+			fatalErr("structure", "bad", "good", err)
+			return
+		}
+	}
+
+	_, err = img.VerifyHash(nil)
+	if !e.hash {
+		if err == nil {
+			fatalErr("hash", "good", "bad", nil)
+		}
+		return
+	} else {
+		if err != nil {
+			fatalErr("hash", "bad", "good", err)
 			return
 		}
 	}
@@ -127,19 +141,7 @@ func testOne(t *testing.T, e entry) {
 
 	key := readPubKey()
 
-	sigs, err := img.CollectSigs()
-	if err != nil {
-		t.Fatalf("failed to collect image signatures: %s", err.Error())
-		return
-	}
-
-	hash, err := img.Hash()
-	if err != nil {
-		t.Fatalf("failed to read image hash: %s", err.Error())
-		return
-	}
-
-	idx, err := sec.VerifySigs(key, sigs, hash)
+	idx, err := img.VerifySigs([]sec.PubSignKey{key})
 	if !e.sign {
 		if err == nil && idx != -1 {
 			fatalErr("signature", "good", "bad", nil)
@@ -157,56 +159,62 @@ func TestImageVerify(t *testing.T) {
 		entry{
 			basename:  "garbage",
 			form:      false,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
 		entry{
 			basename:  "truncated",
 			form:      false,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
 		entry{
 			basename:  "bad-hash",
 			form:      true,
-			integrity: false,
+			structure: true,
+			hash:      false,
 			man:       false,
 			sign:      false,
 		},
 		entry{
 			basename:  "mismatch-hash",
 			form:      true,
-			integrity: true,
+			structure: true,
+			hash:      true,
 			man:       false,
 			sign:      false,
 		},
 		entry{
 			basename:  "mismatch-version",
 			form:      true,
-			integrity: true,
+			structure: true,
+			hash:      true,
 			man:       false,
 			sign:      false,
 		},
 		entry{
 			basename:  "bad-signature",
 			form:      true,
-			integrity: true,
+			structure: true,
+			hash:      true,
 			man:       true,
 			sign:      false,
 		},
 		entry{
 			basename:  "good-unsigned",
 			form:      true,
-			integrity: true,
+			structure: true,
+			hash:      true,
 			man:       true,
 			sign:      false,
 		},
 		entry{
 			basename:  "good-signed",
 			form:      true,
-			integrity: true,
+			structure: true,
+			hash:      true,
 			man:       true,
 			sign:      true,
 		},
diff --git a/image/keys_test.go b/image/keys_test.go
index 0953570..f11bd3f 100644
--- a/image/keys_test.go
+++ b/image/keys_test.go
@@ -69,7 +69,7 @@ func signatureTest(t *testing.T, privateKey []byte) {
 	}
 
 	// Now try with a signature.
-	key, err := sec.BuildPrivateKey(privateKey)
+	key, err := sec.ParsePrivSignKey(privateKey)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/image/v1.go b/image/v1.go
index 0b82086..7f6bb22 100644
--- a/image/v1.go
+++ b/image/v1.go
@@ -157,7 +157,7 @@ func (img *ImageV1) Write(w io.Writer) (int, error) {
 	return offs.TotalSize, nil
 }
 
-func sigHdrTypeV1(key sec.SignKey) (uint32, error) {
+func sigHdrTypeV1(key sec.PrivSignKey) (uint32, error) {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -178,7 +178,7 @@ func sigHdrTypeV1(key sec.SignKey) (uint32, error) {
 	}
 }
 
-func sigTlvTypeV1(key sec.SignKey) uint8 {
+func sigTlvTypeV1(key sec.PrivSignKey) uint8 {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -216,7 +216,7 @@ func generateV1SigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) {
 	return signature, nil
 }
 
-func generateV1SigTlvRsa(key sec.SignKey, hash []byte) (ImageTlv, error) {
+func generateV1SigTlvRsa(key sec.PrivSignKey, hash []byte) (ImageTlv, error) {
 	sig, err := generateV1SigRsa(key.Rsa, hash)
 	if err != nil {
 		return ImageTlv{}, err
@@ -232,7 +232,7 @@ func generateV1SigTlvRsa(key sec.SignKey, hash []byte) (ImageTlv, error) {
 	}, nil
 }
 
-func generateV1SigTlvEc(key sec.SignKey, hash []byte) (ImageTlv, error) {
+func generateV1SigTlvEc(key sec.PrivSignKey, hash []byte) (ImageTlv, error) {
 	sig, err := GenerateSigEc(key, hash)
 	if err != nil {
 		return ImageTlv{}, err
@@ -265,7 +265,7 @@ func generateV1SigTlvEc(key sec.SignKey, hash []byte) (ImageTlv, error) {
 	}, nil
 }
 
-func generateV1SigTlv(key sec.SignKey, hash []byte) (ImageTlv, error) {
+func generateV1SigTlv(key sec.PrivSignKey, hash []byte) (ImageTlv, error) {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -465,7 +465,13 @@ func GenerateV1Image(opts ImageCreateOpts) (ImageV1, error) {
 		if err != nil {
 			return ImageV1{}, errors.Wrapf(err, "error reading pubkey file")
 		}
-		cipherSecret, err := GenerateCipherSecret(pubKeBytes, plainSecret)
+
+		pubKe, err := sec.ParsePubEncKey(pubKeBytes)
+		if err != nil {
+			return ImageV1{}, err
+		}
+
+		cipherSecret, err := pubKe.Encrypt(plainSecret)
 		if err != nil {
 			return ImageV1{}, err
 		}
diff --git a/image/verify.go b/image/verify.go
index 0ac2b25..19bd585 100644
--- a/image/verify.go
+++ b/image/verify.go
@@ -25,8 +25,150 @@ import (
 
 	"github.com/apache/mynewt-artifact/errors"
 	"github.com/apache/mynewt-artifact/manifest"
+	"github.com/apache/mynewt-artifact/sec"
 )
 
+func (img *Image) verifyHashDecrypted() 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 contains incorrect hash: have=%x want=%x",
+			haveHash, wantHash)
+	}
+
+	return nil
+}
+
+func (img *Image) verifyEncState() ([]byte, error) {
+	secret, err := img.CollectSecret()
+	if err != nil {
+		return nil, err
+	}
+
+	if img.Header.Flags&IMAGE_F_ENCRYPTED == 0 {
+		if secret != nil {
+			return nil, errors.Errorf(
+				"encrypted flag set in image header, but no encryption TLV")
+		}
+
+		return nil, nil
+	} else {
+		if secret == nil {
+			return nil, errors.Errorf(
+				"encryption TLV, but encrypted flag unset in image header")
+		}
+
+		return secret, nil
+	}
+}
+
+// VerifyStructure checks an image's structure for internal consistency.  It
+// returns an error if the image is incorrect.
+func (img *Image) VerifyStructure() error {
+	// 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)
+		}
+	}
+
+	if _, err := img.verifyEncState(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// VerifyHash calculates an image's hash and compares it to the image's SHA256
+// TLV.  If the image is encrypted, this function temporarily decrypts it
+// before calculating the hash.  The returned int is the index of the key that
+// was used to decrypt the image, or -1 if none.  An error is returned if the
+// hash is incorrect.
+func (img *Image) VerifyHash(privEncKeys []sec.PrivEncKey) (int, error) {
+	secret, err := img.verifyEncState()
+	if err != nil {
+		return -1, err
+	}
+
+	if secret == nil {
+		// Image not encrypted.
+		if err := img.verifyHashDecrypted(); err != nil {
+			return -1, err
+		}
+
+		return -1, nil
+	}
+
+	// Image is encrypted.
+	if len(privEncKeys) == 0 {
+		return -1, errors.Errorf(
+			"attempt to verify hash of encrypted image: no keys provided")
+	}
+
+	// We don't know which key the image is encrypted with.  For each key,
+	// decrypt and then check the hash.
+	var hashErr error
+	for i, key := range privEncKeys {
+		dec, err := Decrypt(*img, key)
+		if err != nil {
+			return -1, err
+		}
+
+		hashErr = dec.verifyHashDecrypted()
+		if hashErr == nil {
+			return i, nil
+		}
+	}
+
+	return -1, hashErr
+}
+
+// VerifySigs checks an image's attached signatures against the provided set of
+// keys.  It succeeds if the image has no signatures or if any signature can be
+// verified.  The returned int is the index of the key that was used to verify
+// a signature, or -1 if none.  An error is returned if there is at least one
+// signature and they all fail the check.
+func (img *Image) VerifySigs(keys []sec.PubSignKey) (int, error) {
+	sigs, err := img.CollectSigs()
+	if err != nil {
+		return -1, err
+	}
+
+	if len(sigs) == 0 {
+		return -1, nil
+	}
+
+	hash, err := img.Hash()
+	if err != nil {
+		return -1, err
+	}
+
+	for _, k := range keys {
+		idx, err := sec.VerifySigs(k, sigs, hash)
+		if err != nil {
+			return -1, err
+		}
+
+		if idx != -1 {
+			return idx, nil
+		}
+	}
+
+	return -1, errors.Errorf("image signatures do not match provided keys")
+}
+
 // 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 {
@@ -71,36 +213,3 @@ func (img *Image) VerifyManifest(man manifest.Manifest) error {
 
 	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/mfg/mfg_test.go b/mfg/mfg_test.go
index 60f51fd..6d90f68 100644
--- a/mfg/mfg_test.go
+++ b/mfg/mfg_test.go
@@ -33,7 +33,7 @@ const testdataPath = "testdata"
 type entry struct {
 	basename  string
 	form      bool
-	integrity bool
+	structure bool
 	man       bool
 	sign      bool
 }
@@ -63,7 +63,7 @@ func readManifest(basename string) manifest.MfgManifest {
 func readPubKey() sec.PubSignKey {
 	path := fmt.Sprintf("%s/sign-key.pem", testdataPath)
 
-	key, err := sec.ReadKey(path)
+	key, err := sec.ReadPrivSignKey(path)
 	if err != nil {
 		panic("failed to read key file " + path)
 	}
@@ -105,15 +105,15 @@ func testOne(t *testing.T, e entry) {
 		}
 	}
 
-	err = m.Verify(man.EraseVal)
-	if !e.integrity {
+	err = m.VerifyStructure(man.EraseVal)
+	if !e.structure {
 		if err == nil {
-			fatalErr("integrity", "good", "bad", nil)
+			fatalErr("structure", "good", "bad", nil)
 		}
 		return
 	} else {
 		if err != nil {
-			fatalErr("integrity", "bad", "good", err)
+			fatalErr("structure", "bad", "good", err)
 			return
 		}
 	}
@@ -133,19 +133,7 @@ func testOne(t *testing.T, e entry) {
 
 	key := readPubKey()
 
-	sigs, err := man.SecSigs()
-	if err != nil {
-		t.Fatalf("failed to collect mfg signatures: %s", err.Error())
-		return
-	}
-
-	hash, err := m.Hash(man.EraseVal)
-	if err != nil {
-		t.Fatalf("failed to read mfg hash: %s", err.Error())
-		return
-	}
-
-	idx, err := sec.VerifySigs(key, sigs, hash)
+	idx, err := VerifySigs(man, []sec.PubSignKey{key})
 	if !e.sign {
 		if err == nil && idx != -1 {
 			fatalErr("signature", "good", "bad", nil)
@@ -164,7 +152,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "garbage",
 			form:      false,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
@@ -172,7 +160,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "unknown-tlv",
 			form:      true,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
@@ -180,7 +168,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hashx-fm1-ext0-tgts1-sign0",
 			form:      true,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
@@ -188,7 +176,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hashm-fm1-ext0-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -196,7 +184,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hash1-fmm-ext1-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -204,7 +192,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hash1-fm1-extm-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -212,7 +200,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hash1-fm1-ext1-tgtsm-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -220,7 +208,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hash1-fm1-ext0-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       true,
 			sign:      false,
 		},
@@ -228,7 +216,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hash1-fm1-ext1-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       true,
 			sign:      false,
 		},
@@ -236,7 +224,7 @@ func TestMfgVerify(t *testing.T) {
 		entry{
 			basename:  "hash1-fm1-ext1-tgts1-sign1",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       true,
 			sign:      true,
 		},
diff --git a/mfg/verify.go b/mfg/verify.go
index 7fc9f22..cff0a79 100644
--- a/mfg/verify.go
+++ b/mfg/verify.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apache/mynewt-artifact/flash"
 	"github.com/apache/mynewt-artifact/image"
 	"github.com/apache/mynewt-artifact/manifest"
+	"github.com/apache/mynewt-artifact/sec"
 )
 
 func (m *Mfg) validateManFlashMap(man manifest.MfgManifest) error {
@@ -143,7 +144,7 @@ func (m *Mfg) validateManTargets(man manifest.MfgManifest) error {
 					"error parsing build \"%s\" embedded in mfgimage", t.Name)
 			}
 
-			if err := img.Verify(); err != nil {
+			if err := img.VerifyStructure(); err != nil {
 				return errors.Wrapf(err,
 					"mfgimage contains invalid build \"%s\"", t.Name)
 			}
@@ -153,8 +154,40 @@ func (m *Mfg) validateManTargets(man manifest.MfgManifest) error {
 	return nil
 }
 
-// VerifyManifest compares an mfgimage's structure to its manifest.  It
-// returns an error if the mfgimage doesn't match the manifest.
+// VerifyStructure checks an mfgimage's structure and internal consistency.  It
+// returns an error if the mfgimage is incorrect.
+func (m *Mfg) VerifyStructure(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
+}
+
+// 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(
@@ -188,34 +221,32 @@ func (m *Mfg) VerifyManifest(man manifest.MfgManifest) error {
 	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
-		}
+// VerifySigs checks an mfgimage's signatures against the provided set of keys.
+// It succeeds if the mfgimage has no signatures or if any signature can be
+// verified.  An error is returned if there is at least one signature and they
+// all fail the check.
+func VerifySigs(man manifest.MfgManifest, keys []sec.PubSignKey) (int, error) {
+	sigs, err := man.SecSigs()
+	if err != nil {
+		return -1, err
+	}
 
-		// Verify contents of hash TLV.
-		switch t.Header.Type {
-		case META_TLV_TYPE_HASH:
-			hashBody := body.(*MetaTlvBodyHash)
+	hash, err := hex.DecodeString(man.MfgHash)
+	if err != nil {
+		return -1, errors.Wrapf(err,
+			"mfg manifest contains invalid hash: %s", man.MfgHash)
+	}
 
-			hash, err := m.RecalcHash(eraseVal)
-			if err != nil {
-				return err
-			}
+	for keyIdx, k := range keys {
+		sigIdx, err := sec.VerifySigs(k, sigs, hash)
+		if err != nil {
+			return -1, errors.Wrapf(err, "failed to verify mfgimg signatures")
+		}
 
-			if !bytes.Equal(hash, hashBody.Hash[:]) {
-				return errors.Errorf(
-					"mmr contains incorrect hash: have=%s want=%s",
-					hex.EncodeToString(hashBody.Hash[:]),
-					hex.EncodeToString(hash))
-			}
+		if sigIdx != -1 {
+			return keyIdx, nil
 		}
 	}
 
-	return nil
+	return -1, errors.Errorf("mfg signatures do not match provided keys")
 }
diff --git a/sec/encrypt.go b/sec/encrypt.go
index d58806c..75b2a76 100644
--- a/sec/encrypt.go
+++ b/sec/encrypt.go
@@ -23,18 +23,141 @@ import (
 	"bytes"
 	"crypto/aes"
 	"crypto/cipher"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/x509"
+	"encoding/base64"
 	"io"
 
+	keywrap "github.com/NickBall/go-aes-key-wrap"
 	"github.com/apache/mynewt-artifact/errors"
 )
 
+// XXX: Only RSA supported for now.
+type PrivEncKey struct {
+	Rsa *rsa.PrivateKey
+}
+
+type PubEncKey struct {
+	Rsa *rsa.PublicKey
+	Aes cipher.Block
+}
+
+func parsePubKePem(b []byte) (PubEncKey, error) {
+	key := PubEncKey{}
+
+	itf, err := parsePubPemKey(b)
+	if err != nil {
+		return key, err
+	}
+
+	switch pub := itf.(type) {
+	case *rsa.PublicKey:
+		key.Rsa = pub
+	default:
+		return key, errors.Errorf(
+			"unknown public encryption key type: %T", pub)
+	}
+
+	return key, nil
+}
+
+func parsePubKeBase64(keyBytes []byte) (PubEncKey, error) {
+	if len(keyBytes) != 16 {
+		return PubEncKey{}, errors.Errorf(
+			"unexpected key size: %d != 16", len(keyBytes))
+	}
+
+	cipher, err := aes.NewCipher(keyBytes)
+	if err != nil {
+		return PubEncKey{}, errors.Wrapf(err,
+			"error creating keywrap cipher")
+	}
+
+	return PubEncKey{
+		Aes: cipher,
+	}, nil
+}
+
+func ParsePubEncKey(keyBytes []byte) (PubEncKey, error) {
+	b, err := base64.StdEncoding.DecodeString(string(keyBytes))
+	if err == nil {
+		return parsePubKeBase64(b)
+	}
+
+	// Not base64-encoded; assume it is PEM.
+	return parsePubKePem(keyBytes)
+}
+
+func (key *PubEncKey) AssertValid() {
+	if key.Rsa == nil && key.Aes == nil {
+		panic("invalid public encryption key; neither RSA nor AES")
+	}
+}
+
+func encryptRsa(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, errors.Wrapf(err, "Error from encryption")
+	}
+
+	return cipherSecret, nil
+}
+
+func encryptAes(c cipher.Block, plain []byte) ([]byte, error) {
+	ciph, err := keywrap.Wrap(c, plain)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error key-wrapping")
+	}
+
+	return ciph, nil
+}
+
+func (k *PubEncKey) Encrypt(plain []byte) ([]byte, error) {
+	k.AssertValid()
+
+	if k.Rsa != nil {
+		return encryptRsa(k.Rsa, plain)
+	} else {
+		return encryptAes(k.Aes, plain)
+	}
+}
+
+func ParsePrivEncKey(keyBytes []byte) (PrivEncKey, error) {
+	rpk, err := x509.ParsePKCS1PrivateKey(keyBytes)
+	if err != nil {
+		return PrivEncKey{}, errors.Wrapf(err, "error parsing private key file")
+	}
+
+	return PrivEncKey{
+		Rsa: rpk,
+	}, nil
+}
+
+func decryptRsa(privk *rsa.PrivateKey, ciph []byte) ([]byte, error) {
+	rng := rand.Reader
+	plain, err := rsa.DecryptOAEP(sha256.New(), rng, privk, ciph, nil)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error from encryption")
+	}
+
+	return plain, nil
+}
+
+func (k *PrivEncKey) Decrypt(ciph []byte) ([]byte, error) {
+	return decryptRsa(k.Rsa, ciph)
+}
+
 func EncryptAES(plain []byte, secret []byte) ([]byte, error) {
-	block, err := aes.NewCipher(secret)
+	blk, err := aes.NewCipher(secret)
 	if err != nil {
 		return nil, errors.Errorf("Failed to create block cipher")
 	}
 	nonce := make([]byte, 16)
-	stream := cipher.NewCTR(block, nonce)
+	stream := cipher.NewCTR(blk, nonce)
 
 	dataBuf := make([]byte, 16)
 	encBuf := make([]byte, 16)
diff --git a/sec/key.go b/sec/key.go
index dabbd1f..56f9f28 100644
--- a/sec/key.go
+++ b/sec/key.go
@@ -20,292 +20,10 @@
 package sec
 
 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"
-	"github.com/apache/mynewt-artifact/errors"
 )
 
-type SignKey struct {
-	// Only one of these members is non-nil.
-	Rsa *rsa.PrivateKey
-	Ec  *ecdsa.PrivateKey
-}
-
-type PubSignKey struct {
-	Rsa *rsa.PublicKey
-	Ec  *ecdsa.PublicKey
-}
-
-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, errors.Wrapf(err, "Private key parsing failed")
-		}
-	}
-	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, errors.Wrapf(err, "Private key parsing failed")
-		}
-	}
-	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, errors.Wrapf(err, "Private key parsing failed")
-		}
-	}
-	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, errors.Wrapf(
-				err, "Unable to decode encrypted private key")
-		}
-	}
-	if privKey == nil {
-		return nil, errors.Errorf(
-			"Unknown private key format, EC/RSA private " +
-				"key in PEM format only.")
-	}
-
-	return privKey, nil
-}
-
-func BuildPrivateKey(keyBytes []byte) (SignKey, error) {
-	key := SignKey{}
-
-	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, errors.Errorf("Unknown private key format")
-	}
-
-	return key, nil
-}
-
-func ReadKey(filename string) (SignKey, error) {
-	keyBytes, err := ioutil.ReadFile(filename)
-	if err != nil {
-		return SignKey{}, errors.Wrapf(err, "Error reading key file")
-	}
-
-	return BuildPrivateKey(keyBytes)
-}
-
-func ReadKeys(filenames []string) ([]SignKey, error) {
-	keys := make([]SignKey, 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 *SignKey) AssertValid() {
-	if key.Rsa == nil && key.Ec == nil {
-		panic("invalid key; neither RSA nor ECC")
-	}
-}
-
-func (key *SignKey) PubKey() PubSignKey {
-	key.AssertValid()
-
-	if key.Rsa != nil {
-		return PubSignKey{Rsa: &key.Rsa.PublicKey}
-	} else {
-		return PubSignKey{Ec: &key.Ec.PublicKey}
-	}
-}
-
-func (key *SignKey) PubBytes() ([]byte, error) {
-	pk := key.PubKey()
-	return pk.Bytes()
-}
-
 func RawKeyHash(pubKeyBytes []byte) []byte {
 	sum := sha256.Sum256(pubKeyBytes)
 	return sum[:4]
 }
-
-func (key *SignKey) SigLen() uint16 {
-	key.AssertValid()
-
-	if key.Rsa != nil {
-		pubk := key.Rsa.Public().(*rsa.PublicKey)
-		return uint16(pubk.Size())
-	} else {
-		switch key.Ec.Curve.Params().Name {
-		case "P-224":
-			return 68
-		case "P-256":
-			return 72
-		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, errors.Errorf("Invalid PEM file")
-	}
-
-	pub, err := x509.ParsePKIXPublicKey(b.Bytes)
-	if err != nil {
-		return nil, errors.Wrapf(err, "Error parsing pubkey file")
-	}
-
-	var pubk *rsa.PublicKey
-	switch pub.(type) {
-	case *rsa.PublicKey:
-		pubk = pub.(*rsa.PublicKey)
-	default:
-		return nil, errors.Wrapf(err, "Error parsing pubkey file")
-	}
-
-	return pubk, nil
-}
-
-func ParsePrivKeDer(keyBytes []byte) (*rsa.PrivateKey, error) {
-	privKey, err := x509.ParsePKCS1PrivateKey(keyBytes)
-	if err != nil {
-		return nil, errors.Wrapf(err, "Error parsing private key file")
-	}
-
-	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, errors.Wrapf(err, "Error from encryption")
-	}
-
-	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, errors.Wrapf(err, "Error from encryption")
-	}
-
-	return plainSecret, nil
-}
-
-func ParseKeBase64(keyBytes []byte) (cipher.Block, error) {
-	kek, err := base64.StdEncoding.DecodeString(string(keyBytes))
-	if err != nil {
-		return nil, errors.Wrapf(err, "error decoding kek")
-	}
-	if len(kek) != 16 {
-		return nil, errors.Errorf("unexpected key size: %d != 16", len(kek))
-	}
-
-	cipher, err := aes.NewCipher(kek)
-	if err != nil {
-		return nil, errors.Wrapf(err, "error creating keywrap cipher")
-	}
-
-	return cipher, nil
-}
-
-func EncryptSecretAes(c cipher.Block, plainSecret []byte) ([]byte, error) {
-	cipherSecret, err := keywrap.Wrap(c, plainSecret)
-	if err != nil {
-		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) Bytes() ([]byte, error) {
-	key.AssertValid()
-
-	var b []byte
-
-	if key.Rsa != nil {
-		var err error
-		b, 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":
-			b, _ = x509.MarshalPKIXPublicKey(*key.Ec)
-		default:
-			return nil, errors.Errorf("unsupported ECC curve")
-		}
-	}
-
-	return b, nil
-}
diff --git a/sec/read.go b/sec/read.go
new file mode 100644
index 0000000..8fd8a0f
--- /dev/null
+++ b/sec/read.go
@@ -0,0 +1,107 @@
+package sec
+
+import (
+	"io/ioutil"
+
+	"github.com/apache/mynewt-artifact/errors"
+)
+
+// ReadPubSignKey reads a public signing key from a file.
+func ReadPubSignKey(filename string) (PubSignKey, error) {
+	keyBytes, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return PubSignKey{}, errors.Wrapf(err, "error reading key file")
+	}
+
+	return ParsePubSignKey(keyBytes)
+}
+
+// ReadPubSignKeys reads a set of public signing keys from several files.
+func ReadPubSignKeys(filenames []string) ([]PubSignKey, error) {
+	keys := make([]PubSignKey, len(filenames))
+
+	for i, filename := range filenames {
+		key, err := ReadPubSignKey(filename)
+		if err != nil {
+			return nil, err
+		}
+
+		keys[i] = key
+	}
+
+	return keys, nil
+}
+
+// ReadPrivSignKey reads a private signing key from a file.
+func ReadPrivSignKey(filename string) (PrivSignKey, error) {
+	keyBytes, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return PrivSignKey{}, errors.Wrapf(err, "error reading key file")
+	}
+
+	return ParsePrivSignKey(keyBytes)
+}
+
+// ReadPubSignKeys reads a set of private signing keys from several files.
+func ReadPrivSignKeys(filenames []string) ([]PrivSignKey, error) {
+	keys := make([]PrivSignKey, len(filenames))
+
+	for i, filename := range filenames {
+		key, err := ReadPrivSignKey(filename)
+		if err != nil {
+			return nil, err
+		}
+
+		keys[i] = key
+	}
+
+	return keys, nil
+}
+
+// ReadPubEncKey reads a public encryption key from a file.
+func ReadPubEncKey(filename string) (PubEncKey, error) {
+	keyBytes, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return PubEncKey{}, errors.Wrapf(err, "error reading key file")
+	}
+
+	return ParsePubEncKey(keyBytes)
+}
+
+// ReadPubSignKeys reads a set of public encryption keys from several files.
+func ReadPubEncKeys(filenames []string) ([]PubEncKey, error) {
+	keys := make([]PubEncKey, len(filenames))
+	for i, filename := range filenames {
+		key, err := ReadPubEncKey(filename)
+		if err != nil {
+			return nil, err
+		}
+		keys[i] = key
+	}
+
+	return keys, nil
+}
+
+// ReadPubEncKey reads a private encryption key from a file.
+func ReadPrivEncKey(filename string) (PrivEncKey, error) {
+	keyBytes, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return PrivEncKey{}, errors.Wrapf(err, "error reading key file")
+	}
+
+	return ParsePrivEncKey(keyBytes)
+}
+
+// ReadPubSignKeys reads a set of private encryption keys from several files.
+func ReadPrivEncKeys(filenames []string) ([]PrivEncKey, error) {
+	keys := make([]PrivEncKey, len(filenames))
+	for i, filename := range filenames {
+		key, err := ReadPrivEncKey(filename)
+		if err != nil {
+			return nil, err
+		}
+		keys[i] = key
+	}
+
+	return keys, nil
+}
diff --git a/sec/sig.go b/sec/sig.go
deleted file mode 100644
index 26a57ac..0000000
--- a/sec/sig.go
+++ /dev/null
@@ -1,74 +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 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.Bytes()
-	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
-}
diff --git a/sec/key.go b/sec/sign.go
similarity index 54%
copy from sec/key.go
copy to sec/sign.go
index dabbd1f..75ebd74 100644
--- a/sec/key.go
+++ b/sec/sign.go
@@ -20,23 +20,18 @@
 package sec
 
 import (
-	"crypto/aes"
-	"crypto/cipher"
+	"bytes"
+	"crypto"
 	"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"
 	"github.com/apache/mynewt-artifact/errors"
 )
 
-type SignKey struct {
+type PrivSignKey struct {
 	// Only one of these members is non-nil.
 	Rsa *rsa.PrivateKey
 	Ec  *ecdsa.PrivateKey
@@ -47,7 +42,12 @@ type PubSignKey struct {
 	Ec  *ecdsa.PublicKey
 }
 
-func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
+type Sig struct {
+	KeyHash []byte
+	Data    []byte
+}
+
+func parsePrivSignKeyItf(keyBytes []byte) (interface{}, error) {
 	var privKey interface{}
 	var err error
 
@@ -67,7 +67,7 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
 		 */
 		privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Private key parsing failed")
+			return nil, errors.Wrapf(err, "Priv key parsing failed")
 		}
 	}
 	if block != nil && block.Type == "EC PRIVATE KEY" {
@@ -76,7 +76,7 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
 		 */
 		privKey, err = x509.ParseECPrivateKey(block.Bytes)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Private key parsing failed")
+			return nil, errors.Wrapf(err, "Priv key parsing failed")
 		}
 	}
 	if block != nil && block.Type == "PRIVATE KEY" {
@@ -85,7 +85,7 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
 		// the key itself.
 		privKey, err = x509.ParsePKCS8PrivateKey(block.Bytes)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Private key parsing failed")
+			return nil, errors.Wrapf(err, "Priv key parsing failed")
 		}
 	}
 	if block != nil && block.Type == "ENCRYPTED PRIVATE KEY" {
@@ -106,57 +106,53 @@ func ParsePrivateKey(keyBytes []byte) (interface{}, error) {
 	return privKey, nil
 }
 
-func BuildPrivateKey(keyBytes []byte) (SignKey, error) {
-	key := SignKey{}
+func ParsePubSignKey(keyBytes []byte) (PubSignKey, error) {
+	key := PubSignKey{}
 
-	privKey, err := ParsePrivateKey(keyBytes)
+	itf, err := parsePubPemKey(keyBytes)
 	if err != nil {
 		return key, err
 	}
 
-	switch priv := privKey.(type) {
-	case *rsa.PrivateKey:
-		key.Rsa = priv
-	case *ecdsa.PrivateKey:
-		key.Ec = priv
+	switch pub := itf.(type) {
+	case *rsa.PublicKey:
+		key.Rsa = pub
+	case *ecdsa.PublicKey:
+		key.Ec = pub
 	default:
-		return key, errors.Errorf("Unknown private key format")
+		return key, errors.Errorf("unknown public signing key type: %T", pub)
 	}
 
 	return key, nil
 }
 
-func ReadKey(filename string) (SignKey, error) {
-	keyBytes, err := ioutil.ReadFile(filename)
+func ParsePrivSignKey(keyBytes []byte) (PrivSignKey, error) {
+	key := PrivSignKey{}
+
+	itf, err := parsePrivSignKeyItf(keyBytes)
 	if err != nil {
-		return SignKey{}, errors.Wrapf(err, "Error reading key file")
+		return key, err
 	}
 
-	return BuildPrivateKey(keyBytes)
-}
-
-func ReadKeys(filenames []string) ([]SignKey, error) {
-	keys := make([]SignKey, len(filenames))
-
-	for i, filename := range filenames {
-		key, err := ReadKey(filename)
-		if err != nil {
-			return nil, err
-		}
-
-		keys[i] = key
+	switch priv := itf.(type) {
+	case *rsa.PrivateKey:
+		key.Rsa = priv
+	case *ecdsa.PrivateKey:
+		key.Ec = priv
+	default:
+		return key, errors.Errorf("unknown private key type: %T", itf)
 	}
 
-	return keys, nil
+	return key, nil
 }
 
-func (key *SignKey) AssertValid() {
+func (key *PrivSignKey) AssertValid() {
 	if key.Rsa == nil && key.Ec == nil {
 		panic("invalid key; neither RSA nor ECC")
 	}
 }
 
-func (key *SignKey) PubKey() PubSignKey {
+func (key *PrivSignKey) PubKey() PubSignKey {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -166,17 +162,12 @@ func (key *SignKey) PubKey() PubSignKey {
 	}
 }
 
-func (key *SignKey) PubBytes() ([]byte, error) {
+func (key *PrivSignKey) PubBytes() ([]byte, error) {
 	pk := key.PubKey()
 	return pk.Bytes()
 }
 
-func RawKeyHash(pubKeyBytes []byte) []byte {
-	sum := sha256.Sum256(pubKeyBytes)
-	return sum[:4]
-}
-
-func (key *SignKey) SigLen() uint16 {
+func (key *PrivSignKey) SigLen() uint16 {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -194,91 +185,6 @@ func (key *SignKey) SigLen() uint16 {
 	}
 }
 
-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, errors.Errorf("Invalid PEM file")
-	}
-
-	pub, err := x509.ParsePKIXPublicKey(b.Bytes)
-	if err != nil {
-		return nil, errors.Wrapf(err, "Error parsing pubkey file")
-	}
-
-	var pubk *rsa.PublicKey
-	switch pub.(type) {
-	case *rsa.PublicKey:
-		pubk = pub.(*rsa.PublicKey)
-	default:
-		return nil, errors.Wrapf(err, "Error parsing pubkey file")
-	}
-
-	return pubk, nil
-}
-
-func ParsePrivKeDer(keyBytes []byte) (*rsa.PrivateKey, error) {
-	privKey, err := x509.ParsePKCS1PrivateKey(keyBytes)
-	if err != nil {
-		return nil, errors.Wrapf(err, "Error parsing private key file")
-	}
-
-	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, errors.Wrapf(err, "Error from encryption")
-	}
-
-	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, errors.Wrapf(err, "Error from encryption")
-	}
-
-	return plainSecret, nil
-}
-
-func ParseKeBase64(keyBytes []byte) (cipher.Block, error) {
-	kek, err := base64.StdEncoding.DecodeString(string(keyBytes))
-	if err != nil {
-		return nil, errors.Wrapf(err, "error decoding kek")
-	}
-	if len(kek) != 16 {
-		return nil, errors.Errorf("unexpected key size: %d != 16", len(kek))
-	}
-
-	cipher, err := aes.NewCipher(kek)
-	if err != nil {
-		return nil, errors.Wrapf(err, "error creating keywrap cipher")
-	}
-
-	return cipher, nil
-}
-
-func EncryptSecretAes(c cipher.Block, plainSecret []byte) ([]byte, error) {
-	cipherSecret, err := keywrap.Wrap(c, plainSecret)
-	if err != nil {
-		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")
@@ -309,3 +215,44 @@ func (key *PubSignKey) Bytes() ([]byte, error) {
 
 	return b, nil
 }
+
+func checkOneKeyOneSig(k PubSignKey, sig Sig, hash []byte) (bool, error) {
+	pubBytes, err := k.Bytes()
+	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
+}
diff --git a/sec/util.go b/sec/util.go
new file mode 100644
index 0000000..4eb7fbe
--- /dev/null
+++ b/sec/util.go
@@ -0,0 +1,28 @@
+package sec
+
+import (
+	"crypto/x509"
+	"encoding/pem"
+
+	"github.com/apache/mynewt-artifact/errors"
+)
+
+func parsePubPemKey(data []byte) (interface{}, error) {
+	p, _ := pem.Decode(data)
+	if p == nil {
+		return nil, errors.Errorf(
+			"error parsing public key: unknown format")
+	}
+
+	if p.Type != "PUBLIC KEY" && p.Type != "RSA PUBLIC KEY" {
+		return nil, errors.Errorf(
+			"error parsing public key: PEM type=\"%s\"", p.Type)
+	}
+
+	itf, err := x509.ParsePKIXPublicKey(p.Bytes)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error parsing public key")
+	}
+
+	return itf, nil
+}


[mynewt-artifact] 02/02: image: Add tests for encrypted images

Posted by cc...@apache.org.
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 d5de4c207ac0b32f733bedac0c595d5301047280
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Tue Jun 25 17:58:46 2019 -0700

    image: Add tests for encrypted images
---
 image/image_test.go                                |  72 ++++++++++++++++++---
 image/testdata/enc-key-pub.der                     | Bin 0 -> 270 bytes
 image/testdata/enc-key-pub.pem                     |   9 +++
 image/testdata/enc-key.der                         | Bin 0 -> 1190 bytes
 image/testdata/enc-key.pem                         |  27 ++++++++
 image/testdata/good-signed-encrypted.img           | Bin 0 -> 9940 bytes
 ...od-unsigned.json => good-signed-encrypted.json} |   8 +--
 ...good-signed.img => good-signed-unencrypted.img} | Bin
 ...od-signed.json => good-signed-unencrypted.json} |   0
 ...-unsigned.img => good-unsigned-unencrypted.img} | Bin
 ...nsigned.json => good-unsigned-unencrypted.json} |   0
 image/testdata/sign-key-pub.pem                    |   9 +++
 image/testdata/wrong-enc-key.img                   | Bin 0 -> 9940 bytes
 .../{good-unsigned.json => wrong-enc-key.json}     |   8 +--
 14 files changed, 116 insertions(+), 17 deletions(-)

diff --git a/image/image_test.go b/image/image_test.go
index 7cb5bf6..aa3ed13 100644
--- a/image/image_test.go
+++ b/image/image_test.go
@@ -24,6 +24,7 @@ import (
 	"io/ioutil"
 	"testing"
 
+	"github.com/apache/mynewt-artifact/errors"
 	"github.com/apache/mynewt-artifact/manifest"
 	"github.com/apache/mynewt-artifact/sec"
 )
@@ -37,6 +38,7 @@ type entry struct {
 	hash      bool
 	man       bool
 	sign      bool
+	encrypted bool
 }
 
 func readImageData(basename string) []byte {
@@ -44,7 +46,7 @@ func readImageData(basename string) []byte {
 
 	data, err := ioutil.ReadFile(path)
 	if err != nil {
-		panic("failed to read image file " + path)
+		panic(fmt.Sprintf("failed to read image file \"%s\": %s", path, err.Error()))
 	}
 
 	return data
@@ -55,23 +57,34 @@ func readManifest(basename string) manifest.Manifest {
 
 	man, err := manifest.ReadManifest(path)
 	if err != nil {
-		panic("failed to read manifest file " + path)
+		panic(fmt.Sprintf("failed to read manifest file \"%s\": %s", path, err.Error()))
 	}
 
 	return man
 }
 
-func readPubKey() sec.PubSignKey {
+func readPubSignKey() sec.PubSignKey {
 	path := fmt.Sprintf("%s/sign-key.pem", testdataPath)
 
 	key, err := sec.ReadPrivSignKey(path)
 	if err != nil {
-		panic("failed to read key file " + path)
+		panic(fmt.Sprintf("failed to read key file \"%s\": %s", path, err.Error()))
 	}
 
 	return key.PubKey()
 }
 
+func readPrivEncKey() sec.PrivEncKey {
+	path := fmt.Sprintf("%s/enc-key.der", testdataPath)
+
+	key, err := sec.ReadPrivEncKey(path)
+	if err != nil {
+		panic(fmt.Sprintf("failed to read key file \"%s\": %s", path, err.Error()))
+	}
+
+	return key
+}
+
 func testOne(t *testing.T, e entry) {
 	fatalErr := func(field string, have string, want string, err error) {
 		s := fmt.Sprintf("image \"%s\" has unexpected `%s` status: "+
@@ -111,7 +124,9 @@ func testOne(t *testing.T, e entry) {
 		}
 	}
 
-	_, err = img.VerifyHash(nil)
+	kek := readPrivEncKey()
+
+	kekIdx, err := img.VerifyHash([]sec.PrivEncKey{kek})
 	if !e.hash {
 		if err == nil {
 			fatalErr("hash", "good", "bad", nil)
@@ -122,6 +137,19 @@ func testOne(t *testing.T, e entry) {
 			fatalErr("hash", "bad", "good", err)
 			return
 		}
+
+		var wantKekIdx int
+		if e.encrypted {
+			wantKekIdx = 0
+		} else {
+			wantKekIdx = -1
+		}
+
+		if kekIdx != wantKekIdx {
+			fatalErr("hash", "good", "bad", errors.Errorf(
+				"wrong kek idx: have=%d want=%d", kekIdx, wantKekIdx))
+			return
+		}
 	}
 
 	man := readManifest(e.basename)
@@ -139,9 +167,9 @@ func testOne(t *testing.T, e entry) {
 		}
 	}
 
-	key := readPubKey()
+	isk := readPubSignKey()
 
-	idx, err := img.VerifySigs([]sec.PubSignKey{key})
+	idx, err := img.VerifySigs([]sec.PubSignKey{isk})
 	if !e.sign {
 		if err == nil && idx != -1 {
 			fatalErr("signature", "good", "bad", nil)
@@ -162,6 +190,7 @@ func TestImageVerify(t *testing.T) {
 			structure: false,
 			man:       false,
 			sign:      false,
+			encrypted: false,
 		},
 		entry{
 			basename:  "truncated",
@@ -169,6 +198,7 @@ func TestImageVerify(t *testing.T) {
 			structure: false,
 			man:       false,
 			sign:      false,
+			encrypted: false,
 		},
 		entry{
 			basename:  "bad-hash",
@@ -177,6 +207,7 @@ func TestImageVerify(t *testing.T) {
 			hash:      false,
 			man:       false,
 			sign:      false,
+			encrypted: false,
 		},
 		entry{
 			basename:  "mismatch-hash",
@@ -185,6 +216,7 @@ func TestImageVerify(t *testing.T) {
 			hash:      true,
 			man:       false,
 			sign:      false,
+			encrypted: false,
 		},
 		entry{
 			basename:  "mismatch-version",
@@ -193,6 +225,7 @@ func TestImageVerify(t *testing.T) {
 			hash:      true,
 			man:       false,
 			sign:      false,
+			encrypted: false,
 		},
 		entry{
 			basename:  "bad-signature",
@@ -201,22 +234,43 @@ func TestImageVerify(t *testing.T) {
 			hash:      true,
 			man:       true,
 			sign:      false,
+			encrypted: false,
+		},
+		entry{
+			basename:  "wrong-enc-key",
+			form:      true,
+			structure: true,
+			hash:      false,
+			man:       true,
+			sign:      true,
+			encrypted: true,
 		},
 		entry{
-			basename:  "good-unsigned",
+			basename:  "good-unsigned-unencrypted",
 			form:      true,
 			structure: true,
 			hash:      true,
 			man:       true,
 			sign:      false,
+			encrypted: false,
+		},
+		entry{
+			basename:  "good-signed-unencrypted",
+			form:      true,
+			structure: true,
+			hash:      true,
+			man:       true,
+			sign:      true,
+			encrypted: false,
 		},
 		entry{
-			basename:  "good-signed",
+			basename:  "good-signed-encrypted",
 			form:      true,
 			structure: true,
 			hash:      true,
 			man:       true,
 			sign:      true,
+			encrypted: true,
 		},
 	}
 
diff --git a/image/testdata/enc-key-pub.der b/image/testdata/enc-key-pub.der
new file mode 100644
index 0000000..9c9277b
Binary files /dev/null and b/image/testdata/enc-key-pub.der differ
diff --git a/image/testdata/enc-key-pub.pem b/image/testdata/enc-key-pub.pem
new file mode 100644
index 0000000..6eafa46
--- /dev/null
+++ b/image/testdata/enc-key-pub.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAySeOBIFVjILuDa60U6UM
+fdV0UFCXB2QVrIjQiqiLpldYuuyppijQkKb9tUgSXMV3gaXDfshhYx5kJakp8VHs
+IvcBDGALHEt4p0gj+Q+F0/lvql5biL76cx08ZoEzzId7JhhZF1UiRMlnrnZJxcbq
+jbain5HvYlSlcQItwjJW7RoSYGZwWPpPNXyZ+OYt0VVvl8Z86E/as6Crf7Y45HQw
+iFf+njJm7MHnlUsJHwkULt2wD2PvXJGQYPU00SdTscpaxneqi0z2GsAVcPjk66ux
+LSUhsF/dCr+vSanUcwCihdb8/woVb6fUxo4HO7f1Eu5LTLWNX2jRahbybe2Okq4x
+EwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/image/testdata/enc-key.der b/image/testdata/enc-key.der
new file mode 100644
index 0000000..ea988d2
Binary files /dev/null and b/image/testdata/enc-key.der differ
diff --git a/image/testdata/enc-key.pem b/image/testdata/enc-key.pem
new file mode 100644
index 0000000..8345cdb
--- /dev/null
+++ b/image/testdata/enc-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAySeOBIFVjILuDa60U6UMfdV0UFCXB2QVrIjQiqiLpldYuuyp
+pijQkKb9tUgSXMV3gaXDfshhYx5kJakp8VHsIvcBDGALHEt4p0gj+Q+F0/lvql5b
+iL76cx08ZoEzzId7JhhZF1UiRMlnrnZJxcbqjbain5HvYlSlcQItwjJW7RoSYGZw
+WPpPNXyZ+OYt0VVvl8Z86E/as6Crf7Y45HQwiFf+njJm7MHnlUsJHwkULt2wD2Pv
+XJGQYPU00SdTscpaxneqi0z2GsAVcPjk66uxLSUhsF/dCr+vSanUcwCihdb8/woV
+b6fUxo4HO7f1Eu5LTLWNX2jRahbybe2Okq4xEwIDAQABAoIBAENGBUsgbhoGF9Nf
+oFNxGZJj9vh9W2VPZahEQWp+H+ZLxBMP31UAxW/7SVJ9fhaku+kSJSWbomZh3aBy
+yOI6Qb0X2rPm0xBtdTaM++rp9BoGi//werBrHputJWwqvcYjcV42OmWBRWq36QMB
+8H5CnmMyt4Sia+r44DPBRMhzyXqV68kcxw7c0sjYsTPhKCWg5ZmGu77ari13UXmb
+XKb42pC/ijTq2TgabAwbCjlYUkNOTMKdUfysTOH+Pq7KQ/RJ7c0a5dnJymnJHWTM
+GOr0mcwsyk/WsdZDQ6eAaGwGowt8wVt50xaYxAwZK6g1T47z1edgf3jHyYuJRRQH
+ZgUdraECgYEA58mogN3oQZjF+dHcpnat/f9ZxDmKiweiiNMd8vzxJ5oij/zgm3SD
+HHWqYLtcE5kun81nD6Lyfd14kcjH2DkQ4IfsPI4MdeeFEesObQTcUkBgbRv01xCy
+QLo4+wjU/t20fBUwz8GnegkqfiOEKwmflxOyPsf55nAWG6ur96DrbysCgYEA3iq4
+O9MjaeMgPBKL5kF7vx8jAaPhTrxZt9zGu5BQxR8lcWGa4tGB25ALqkbp2/ACdQtr
+Gg34YcsYRngXgIjALkQkkLee3zuTUsivI0e2TASKsoRJvUsTIfRgtWOMGky68JIS
+eUbMrNDnQ6czHU+aqPj0poa3YEwgdfzEVXX7EbkCgYAovWIXnGlZNj/94+wTeiKk
+1T/y5GY8f5AK2oiWD+1XF5lhk4Hq8PSmiOv0apoJe9AdGF43+l0C0G2DujWeBJG5
+1UopbpI0GwhhmN4FPWh4MIaCRvqm3nFmPRUM0oWVcmRpttPIgHIuWfQVDasKYXui
+czzOGhoLbcIFBQyJzsfy1wKBgG5uTaVvDetUOnGhxmhtpFUb5QqrqxK4DOCXnTEe
+Swews6voGFUmTqYUs7ewCA6K/q2vP010JEJ38VkV2JjLYLueo45Lt2y+8Dv2BRhE
+TRj8KPUTTJQK/TejgW6oTLvF6CYsdYJS7un37Pxz37RyHS5gkTs1O3FiZcBAJFdW
+jbYBAoGALzmoQoarI4YOJKUJBYloVdTjM/0V7rrumZZQ+/sSSaI9cJKHJ+tBeMy7
+HFi0VkgK1P5JwvJVl3aMXlQswn8+nljkIpFbOVlTzAukVOPxWnHXlpvxuMC2+fuO
+8W8ZW/pUlrT7hHOiVOVb+VJyiPPS3h9Uy/Gj8U9QhfJkoIkexxI=
+-----END RSA PRIVATE KEY-----
diff --git a/image/testdata/good-signed-encrypted.img b/image/testdata/good-signed-encrypted.img
new file mode 100644
index 0000000..f67c9f1
Binary files /dev/null and b/image/testdata/good-signed-encrypted.img differ
diff --git a/image/testdata/good-unsigned.json b/image/testdata/good-signed-encrypted.json
similarity index 99%
copy from image/testdata/good-unsigned.json
copy to image/testdata/good-signed-encrypted.json
index cb9aa19..bd161c7 100644
--- a/image/testdata/good-unsigned.json
+++ b/image/testdata/good-signed-encrypted.json
@@ -1,10 +1,10 @@
 {
   "name": "targets/blinky-nordic_pca10040",
-  "build_time": "2019-06-17T17:16:49-07:00",
-  "build_version": "1.0.0.0",
-  "id": "8eb006d574ace63cce18a1f2d8f0f2645f1a0e8630a39fb86bbfbb805d4cd3b9",
+  "build_time": "2019-06-25T17:33:19-07:00",
+  "build_version": "1.2.3.4",
+  "id": "1786a1d4e7d9274dfda01e1bfbca24be5f7d848a81ef3ae3dd276f438bafcc6b",
   "image": "/Users/ccollins/proj/myproj/bin/targets/blinky-nordic_pca10040/app/apps/blinky/blinky.img",
-  "image_hash": "8eb006d574ace63cce18a1f2d8f0f2645f1a0e8630a39fb86bbfbb805d4cd3b9",
+  "image_hash": "1786a1d4e7d9274dfda01e1bfbca24be5f7d848a81ef3ae3dd276f438bafcc6b",
   "loader": "",
   "loader_hash": "",
   "pkgs": [
diff --git a/image/testdata/good-signed.img b/image/testdata/good-signed-unencrypted.img
similarity index 100%
rename from image/testdata/good-signed.img
rename to image/testdata/good-signed-unencrypted.img
diff --git a/image/testdata/good-signed.json b/image/testdata/good-signed-unencrypted.json
similarity index 100%
rename from image/testdata/good-signed.json
rename to image/testdata/good-signed-unencrypted.json
diff --git a/image/testdata/good-unsigned.img b/image/testdata/good-unsigned-unencrypted.img
similarity index 100%
rename from image/testdata/good-unsigned.img
rename to image/testdata/good-unsigned-unencrypted.img
diff --git a/image/testdata/good-unsigned.json b/image/testdata/good-unsigned-unencrypted.json
similarity index 100%
copy from image/testdata/good-unsigned.json
copy to image/testdata/good-unsigned-unencrypted.json
diff --git a/image/testdata/sign-key-pub.pem b/image/testdata/sign-key-pub.pem
new file mode 100644
index 0000000..7be0e6f
--- /dev/null
+++ b/image/testdata/sign-key-pub.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApydXFYw0I2tS5/Z7o/e/
+AOTxcQsQA80L02KfAqMT6LsLDGLV1DKfKLLgCOdBjcafKw4updsvW9dPqCB6q4Y5
+8j99D2B7fzw+HIWH5IyL73Nt/ytIcJ0CSL/bfGpaI0dY1V92ooEq8cp2ZZCjrTCW
+mg/oP5SQ9aDEbpTYh1VdHbsxObMQ6A/aaQa1hevjcE7gTFOntKN1QDLaDZXz9CqI
+kiuNQuQhXA0zxDJkWwu7U18fVpxa3ozdU0nLRQp6Edo09MulskwJYF6VCbNEesn/
+7rSbwwI3Wa5RFWcQPRlO2SdqRiYlXSX2fYhp7r/tkBdJSYLns0193zTixECQwjI9
+BQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/image/testdata/wrong-enc-key.img b/image/testdata/wrong-enc-key.img
new file mode 100644
index 0000000..af28f07
Binary files /dev/null and b/image/testdata/wrong-enc-key.img differ
diff --git a/image/testdata/good-unsigned.json b/image/testdata/wrong-enc-key.json
similarity index 99%
rename from image/testdata/good-unsigned.json
rename to image/testdata/wrong-enc-key.json
index cb9aa19..642681e 100644
--- a/image/testdata/good-unsigned.json
+++ b/image/testdata/wrong-enc-key.json
@@ -1,10 +1,10 @@
 {
   "name": "targets/blinky-nordic_pca10040",
-  "build_time": "2019-06-17T17:16:49-07:00",
-  "build_version": "1.0.0.0",
-  "id": "8eb006d574ace63cce18a1f2d8f0f2645f1a0e8630a39fb86bbfbb805d4cd3b9",
+  "build_time": "2019-06-25T17:55:36-07:00",
+  "build_version": "1.2.3.4",
+  "id": "1786a1d4e7d9274dfda01e1bfbca24be5f7d848a81ef3ae3dd276f438bafcc6b",
   "image": "/Users/ccollins/proj/myproj/bin/targets/blinky-nordic_pca10040/app/apps/blinky/blinky.img",
-  "image_hash": "8eb006d574ace63cce18a1f2d8f0f2645f1a0e8630a39fb86bbfbb805d4cd3b9",
+  "image_hash": "1786a1d4e7d9274dfda01e1bfbca24be5f7d848a81ef3ae3dd276f438bafcc6b",
   "loader": "",
   "loader_hash": "",
   "pkgs": [