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()
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 @@
 type ImageCreator struct {
 	Body         []byte
 	Version      ImageVersion
-	SigKeys      []sec.SignKey
+	SigKeys      []sec.PrivSignKey
 	PlainSecret  []byte
 	CipherSecret []byte
 	HeaderSize   int
@@ -50,7 +50,7 @@
 	SrcBinFilename    string
 	SrcEncKeyFilename string
 	Version           ImageVersion
-	SigKeys           []sec.SignKey
+	SigKeys           []sec.PrivSignKey
 	LoaderHash        []byte
 }
 
@@ -66,7 +66,7 @@
 	}
 }
 
-func sigTlvType(key sec.SignKey) uint8 {
+func sigTlvType(key sec.PrivSignKey) uint8 {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -112,7 +112,7 @@
 	}, 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 @@
 	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 @@
 	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 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 @@
 	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 @@
 			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 @@
 		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 @@
 	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 @@
 	return idxs
 }
 
-// FindTlvs retrieves all TLVs in an image's footer with the specified type.
-func (i *Image) FindTlvs(tlvType uint8) []*ImageTlv {
+// 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 := i.FindTlvIndices(tlvType)
+	idxs := img.FindTlvIndicesIf(pred)
 	for _, idx := range idxs {
-		tlvs = append(tlvs, &i.Tlvs[idx])
+		tlvs = append(tlvs, &img.Tlvs[idx])
+	}
+
+	return tlvs
+}
+
+// FindTlvs retrieves all TLVs in an image's footer with the specified type.
+func (img *Image) FindTlvs(tlvType uint8) []*ImageTlv {
+	var tlvs []*ImageTlv
+
+	idxs := img.FindTlvIndices(tlvType)
+	for _, idx := range idxs {
+		tlvs = append(tlvs, &img.Tlvs[idx])
 	}
 
 	return tlvs
@@ -388,3 +437,108 @@
 
 	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 @@
 type entry struct {
 	basename  string
 	form      bool
-	integrity bool
+	structure bool
+	hash      bool
 	man       bool
 	sign      bool
 }
@@ -63,7 +64,7 @@
 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 @@
 		}
 	}
 
-	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 @@
 
 	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 @@
 		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 @@
 	}
 
 	// 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 @@
 	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 sigTlvTypeV1(key sec.SignKey) uint8 {
+func sigTlvTypeV1(key sec.PrivSignKey) uint8 {
 	key.AssertValid()
 
 	if key.Rsa != nil {
@@ -216,7 +216,7 @@
 	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 @@
 	}, 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 @@
 	}, 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 @@
 		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 @@
 
 	"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 @@
 
 	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 @@
 type entry struct {
 	basename  string
 	form      bool
-	integrity bool
+	structure bool
 	man       bool
 	sign      bool
 }
@@ -63,7 +63,7 @@
 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 @@
 		}
 	}
 
-	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 @@
 
 	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 @@
 		entry{
 			basename:  "garbage",
 			form:      false,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
@@ -172,7 +160,7 @@
 		entry{
 			basename:  "unknown-tlv",
 			form:      true,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
@@ -180,7 +168,7 @@
 		entry{
 			basename:  "hashx-fm1-ext0-tgts1-sign0",
 			form:      true,
-			integrity: false,
+			structure: false,
 			man:       false,
 			sign:      false,
 		},
@@ -188,7 +176,7 @@
 		entry{
 			basename:  "hashm-fm1-ext0-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -196,7 +184,7 @@
 		entry{
 			basename:  "hash1-fmm-ext1-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -204,7 +192,7 @@
 		entry{
 			basename:  "hash1-fm1-extm-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -212,7 +200,7 @@
 		entry{
 			basename:  "hash1-fm1-ext1-tgtsm-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       false,
 			sign:      false,
 		},
@@ -220,7 +208,7 @@
 		entry{
 			basename:  "hash1-fm1-ext0-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       true,
 			sign:      false,
 		},
@@ -228,7 +216,7 @@
 		entry{
 			basename:  "hash1-fm1-ext1-tgts1-sign0",
 			form:      true,
-			integrity: true,
+			structure: true,
 			man:       true,
 			sign:      false,
 		},
@@ -236,7 +224,7 @@
 		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 @@
 	"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 @@
 					"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 @@
 	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 @@
 	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()
+// 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
+	}
+
+	hash, err := hex.DecodeString(man.MfgHash)
+	if err != nil {
+		return -1, errors.Wrapf(err,
+			"mfg manifest contains invalid hash: %s", man.MfgHash)
+	}
+
+	for keyIdx, k := range keys {
+		sigIdx, err := sec.VerifySigs(k, sigs, hash)
 		if err != nil {
-			return err
+			return -1, errors.Wrapf(err, "failed to verify mfgimg signatures")
 		}
 
-		// 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))
-			}
+		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 @@
 	"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/sign.go b/sec/sign.go
new file mode 100644
index 0000000..75ebd74
--- /dev/null
+++ b/sec/sign.go
@@ -0,0 +1,258 @@
+/**
+ * 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/ecdsa"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/asn1"
+	"encoding/pem"
+
+	"github.com/apache/mynewt-artifact/errors"
+)
+
+type PrivSignKey struct {
+	// Only one of these members is non-nil.
+	Rsa *rsa.PrivateKey
+	Ec  *ecdsa.PrivateKey
+}
+
+type PubSignKey struct {
+	Rsa *rsa.PublicKey
+	Ec  *ecdsa.PublicKey
+}
+
+type Sig struct {
+	KeyHash []byte
+	Data    []byte
+}
+
+func parsePrivSignKeyItf(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, "Priv 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, "Priv 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, "Priv 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 ParsePubSignKey(keyBytes []byte) (PubSignKey, error) {
+	key := PubSignKey{}
+
+	itf, err := parsePubPemKey(keyBytes)
+	if err != nil {
+		return key, err
+	}
+
+	switch pub := itf.(type) {
+	case *rsa.PublicKey:
+		key.Rsa = pub
+	case *ecdsa.PublicKey:
+		key.Ec = pub
+	default:
+		return key, errors.Errorf("unknown public signing key type: %T", pub)
+	}
+
+	return key, nil
+}
+
+func ParsePrivSignKey(keyBytes []byte) (PrivSignKey, error) {
+	key := PrivSignKey{}
+
+	itf, err := parsePrivSignKeyItf(keyBytes)
+	if err != nil {
+		return key, err
+	}
+
+	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 key, nil
+}
+
+func (key *PrivSignKey) AssertValid() {
+	if key.Rsa == nil && key.Ec == nil {
+		panic("invalid key; neither RSA nor ECC")
+	}
+}
+
+func (key *PrivSignKey) PubKey() PubSignKey {
+	key.AssertValid()
+
+	if key.Rsa != nil {
+		return PubSignKey{Rsa: &key.Rsa.PublicKey}
+	} else {
+		return PubSignKey{Ec: &key.Ec.PublicKey}
+	}
+}
+
+func (key *PrivSignKey) PubBytes() ([]byte, error) {
+	pk := key.PubKey()
+	return pk.Bytes()
+}
+
+func (key *PrivSignKey) 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 (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
+}
+
+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
+}