Add imgtool support for encrypted image creation

Adds a new flag to imgtool, -E/--encrypt which accepts a public rsa-2048
key file that will be used to encrypt the image.

The encryption method uses AES-128-CTR to encrypt the image data (ignores
the header and TLVs), using a random key that is itself encrypted using
RSA-2048-OAEP and added to the generated image as a new TLV.

Signed-off-by: Fabio Utzig <utzig@apache.org>
diff --git a/scripts/imgtool.py b/scripts/imgtool.py
index 76ed130..61122b4 100755
--- a/scripts/imgtool.py
+++ b/scripts/imgtool.py
@@ -114,6 +114,8 @@
 
 @click.argument('outfile')
 @click.argument('infile')
+@click.option('-E', '--encrypt', metavar='filename',
+              help='Encrypt image using the provided public key')
 @click.option('-e', '--endian', type=click.Choice(['little', 'big']),
               default='little', help="Select little or big endian")
 @click.option('--overwrite-only', default=False, is_flag=True,
@@ -133,15 +135,23 @@
 @click.option('-k', '--key', metavar='filename')
 @click.command(help='Create a signed or unsigned image')
 def sign(key, align, version, header_size, pad_header, slot_size, pad,
-         max_sectors, overwrite_only, endian, infile, outfile):
+         max_sectors, overwrite_only, endian, encrypt, infile, outfile):
     img = image.Image.load(infile, version=decode_version(version),
                            header_size=header_size, pad_header=pad_header,
                            pad=pad, align=int(align), slot_size=slot_size,
                            max_sectors=max_sectors,
                            overwrite_only=overwrite_only,
-                           endian=endian)
+                           endian=endian,
+                           encrypt=encrypt)
     key = load_key(key) if key else None
-    img.sign(key)
+    enckey = load_key(encrypt) if encrypt else None
+    if enckey:
+        if not isinstance(enckey, (keys.RSA2048, keys.RSA2048Public)):
+            raise Exception("Encryption only available with RSA")
+        if key and not isinstance(key, (keys.RSA2048, keys.RSA2048Public)):
+            raise Exception("Encryption with sign only available with RSA")
+    if key or enckey:
+        img.create(key, enckey)
 
     if pad:
         img.pad_to(slot_size)
diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py
index 7a6111d..8087447 100644
--- a/scripts/imgtool/image.py
+++ b/scripts/imgtool/image.py
@@ -22,6 +22,10 @@
 import hashlib
 import struct
 import os.path
+from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
 
 IMAGE_MAGIC = 0x96f3b83d
 IMAGE_HEADER_SIZE = 32
@@ -32,14 +36,19 @@
 # Image header flags.
 IMAGE_F = {
         'PIC':                   0x0000001,
-        'NON_BOOTABLE':          0x0000010, }
+        'NON_BOOTABLE':          0x0000010,
+        'ENCRYPTED':             0x0000004,
+}
 
 TLV_VALUES = {
         'KEYHASH': 0x01,
         'SHA256': 0x10,
         'RSA2048': 0x20,
         'ECDSA224': 0x21,
-        'ECDSA256': 0x22, }
+        'ECDSA256': 0x22,
+        'ENCRSA2048': 0x30,
+        'ENCKW128': 0x31,
+}
 
 TLV_INFO_SIZE = 4
 TLV_INFO_MAGIC = 0x6907
@@ -138,8 +147,8 @@
                         len(self.payload), tsize, self.slot_size)
                 raise Exception(msg)
 
-    def sign(self, key):
-        self.add_header(key)
+    def create(self, key, enckey):
+        self.add_header(key, enckey)
 
         tlv = TLV(self.endian)
 
@@ -161,15 +170,34 @@
             sig = key.sign(bytes(self.payload))
             tlv.add(key.sig_tlv(), sig)
 
+        if enckey is not None:
+            plainkey = os.urandom(16)
+            cipherkey = enckey._get_public().encrypt(
+                plainkey, padding.OAEP(
+                    mgf=padding.MGF1(algorithm=hashes.SHA256()),
+                    algorithm=hashes.SHA256(),
+                    label=None))
+            tlv.add('ENCRSA2048', cipherkey)
+
+            nonce = bytes([0] * 16)
+            cipher = Cipher(algorithms.AES(plainkey), modes.CTR(nonce),
+                            backend=default_backend())
+            encryptor = cipher.encryptor()
+            img = bytes(self.payload[self.header_size:])
+            self.payload[self.header_size:] = encryptor.update(img) + \
+                                              encryptor.finalize()
+
         self.payload += tlv.get()
 
-    def add_header(self, key):
+    def add_header(self, key, enckey):
         """Install the image header.
 
         The key is needed to know the type of signature, and
         approximate the size of the signature."""
 
         flags = 0
+        if enckey is not None:
+            flags |= IMAGE_F['ENCRYPTED']
 
         e = STRUCT_ENDIAN_DICT[self.endian]
         fmt = (e +