Add support for image encryption using ECIES-P256
This adds support for encrypting images using ECIES-P256. The MCUBoot
implementation was described here:
https://github.com/JuulLabs-OSS/mcuboot/blob/master/docs/encrypted_images.md#ecies-p256-encryption
Signed-off-by: Fabio Utzig <utzig@apache.org>
diff --git a/image/create.go b/image/create.go
index 0ee3bb9..6fc0f27 100644
--- a/image/create.go
+++ b/image/create.go
@@ -129,6 +129,8 @@
if len(cipherSecret) == 256 {
encType = IMAGE_TLV_ENC_RSA
+ } else if len(cipherSecret) == 113 {
+ encType = IMAGE_TLV_ENC_EC256
} else if len(cipherSecret) == 24 {
encType = IMAGE_TLV_ENC_KEK
} else {
diff --git a/image/image.go b/image/image.go
index 7cd1e1a..f0a033e 100644
--- a/image/image.go
+++ b/image/image.go
@@ -64,6 +64,7 @@
IMAGE_TLV_ED25519 = 0x24
IMAGE_TLV_ENC_RSA = 0x30
IMAGE_TLV_ENC_KEK = 0x31
+ IMAGE_TLV_ENC_EC256 = 0x32
IMAGE_TLV_AES_NONCE = 0x50
IMAGE_TLV_SECRET_ID = 0x60
)
@@ -78,6 +79,7 @@
IMAGE_TLV_ED25519: "ED25519",
IMAGE_TLV_ENC_RSA: "ENC_RSA",
IMAGE_TLV_ENC_KEK: "ENC_KEK",
+ IMAGE_TLV_ENC_EC256: "ENC_EC256",
IMAGE_TLV_AES_NONCE: "AES_NONCE",
IMAGE_TLV_SECRET_ID: "SEC_KEY_ID",
}
@@ -171,7 +173,8 @@
func ImageTlvTypeIsSecret(tlvType uint8) bool {
return tlvType == IMAGE_TLV_ENC_RSA ||
- tlvType == IMAGE_TLV_ENC_KEK
+ tlvType == IMAGE_TLV_ENC_KEK ||
+ tlvType == IMAGE_TLV_ENC_EC256
}
func (ver ImageVersion) String() string {
diff --git a/sec/encrypt.go b/sec/encrypt.go
index b9a579e..85d4571 100644
--- a/sec/encrypt.go
+++ b/sec/encrypt.go
@@ -23,11 +23,15 @@
"bytes"
"crypto/aes"
"crypto/cipher"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
+ "golang.org/x/crypto/hkdf"
"io"
keywrap "github.com/NickBall/go-aes-key-wrap"
@@ -49,6 +53,7 @@
type PubEncKey struct {
Rsa *rsa.PublicKey
+ Ec *ecdsa.PublicKey
Aes cipher.Block
}
@@ -88,6 +93,8 @@
switch pub := itf.(type) {
case *rsa.PublicKey:
key.Rsa = pub
+ case *ecdsa.PublicKey:
+ key.Ec = pub
default:
return key, errors.Errorf(
"unknown public encryption key type: %T", pub)
@@ -130,8 +137,8 @@
}
func (key *PubEncKey) AssertValid() {
- if key.Rsa == nil && key.Aes == nil {
- panic("invalid public encryption key; neither RSA nor AES")
+ if key.Rsa == nil && key.Aes == nil && key.Ec == nil {
+ panic("invalid public encryption key; neither RSA nor AES nor EC-P256")
}
}
@@ -163,6 +170,40 @@
return cipherSecret, nil
}
+func encryptEc256(peerPubK *ecdsa.PublicKey, plainSecret []byte) ([]byte, error) {
+ pk, x, y, err := elliptic.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Could not generate ephemeral EC keypair")
+ }
+
+ pubk := elliptic.Marshal(elliptic.P256(), x, y)
+
+ shared, _ := elliptic.P256().ScalarMult(peerPubK.X, peerPubK.Y, pk)
+
+ kdf := hkdf.New(sha256.New, shared.Bytes(), nil, []byte("MCUBoot_ECIES_v1"))
+ derived := make([]byte, 48)
+ _, err = kdf.Read(derived)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error during key derivation")
+ }
+
+ cipherSecret, err := EncryptAES(plainSecret, derived[:16], nil)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error encrypting key")
+ }
+
+ h := hmac.New(sha256.New, derived[16:])
+ h.Write(cipherSecret)
+ mac := h.Sum(nil)
+
+ var tlv []byte
+ tlv = append(tlv, pubk...)
+ tlv = append(tlv, mac...)
+ tlv = append(tlv, cipherSecret...)
+
+ return tlv, nil
+}
+
func encryptAes(c cipher.Block, plain []byte) ([]byte, error) {
ciph, err := keywrap.Wrap(c, plain)
if err != nil {
@@ -177,6 +218,8 @@
if k.Rsa != nil {
return encryptRsa(k.Rsa, plain)
+ } else if k.Ec != nil {
+ return encryptEc256(k.Ec, plain)
} else {
return encryptAes(k.Aes, plain)
}