imgtool: Improve ECDSA key generation

This patch improves the existing ECDSA key generation feature
in the imgtool by:
 - Fix a bug in the 'minimal' representation of PKCS#8 keys where
   the resulting ASN.1 DER encoding is not compliant
 - Add the option to export ECDSA private keys in SEC1 format by
   providing a command line option -f or --format that can be
   'openssl' (for SEC1 format) or 'pkcs8'. This format ends up in
   key encodings which are generally smaller than PKCS#8.

Signed-off-by: Antonio de Angelis <Antonio.deAngelis@arm.com>
diff --git a/scripts/imgtool/keys/ecdsa.py b/scripts/imgtool/keys/ecdsa.py
index 9198bb4..0433beb 100644
--- a/scripts/imgtool/keys/ecdsa.py
+++ b/scripts/imgtool/keys/ecdsa.py
@@ -3,6 +3,7 @@
 """
 
 # SPDX-License-Identifier: Apache-2.0
+import os.path
 
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization
@@ -102,37 +103,60 @@
     def _get_public(self):
         return self.key.public_key()
 
-    def _build_minimal_ecdsa_privkey(self, der):
+    def _build_minimal_ecdsa_privkey(self, der, format):
         '''
         Builds a new DER that only includes the EC private key, removing the
         public key that is added as an "optional" BITSTRING.
         '''
-        offset_PUB = 68
+
+        if format == serialization.PrivateFormat.OpenSSH:
+            print(os.path.basename(__file__) +
+                  ': Warning: --minimal is supported only for PKCS8 '
+                  'or TraditionalOpenSSL formats')
+            return bytearray(der)
+
         EXCEPTION_TEXT = "Error parsing ecdsa key. Please submit an issue!"
-        if der[offset_PUB] != 0xa1:
-            raise ECDSAUsageError(EXCEPTION_TEXT)
-        len_PUB = der[offset_PUB + 1]
-        b = bytearray(der[:-offset_PUB])
-        offset_SEQ = 29
-        if b[offset_SEQ] != 0x30:
-            raise ECDSAUsageError(EXCEPTION_TEXT)
-        b[offset_SEQ + 1] -= len_PUB
-        offset_OCT_STR = 27
-        if b[offset_OCT_STR] != 0x04:
-            raise ECDSAUsageError(EXCEPTION_TEXT)
-        b[offset_OCT_STR + 1] -= len_PUB
-        if b[0] != 0x30 or b[1] != 0x81:
-            raise ECDSAUsageError(EXCEPTION_TEXT)
-        b[2] -= len_PUB
+        if format == serialization.PrivateFormat.PKCS8:
+            offset_PUB = 68  # where the context specific TLV starts (tag 0xA1)
+            if der[offset_PUB] != 0xa1:
+                raise ECDSAUsageError(EXCEPTION_TEXT)
+            len_PUB = der[offset_PUB + 1] + 2  # + 2 for 0xA1 0x44 bytes
+            b = bytearray(der[:offset_PUB])  # remove the TLV with the PUB key
+            offset_SEQ = 29
+            if b[offset_SEQ] != 0x30:
+                raise ECDSAUsageError(EXCEPTION_TEXT)
+            b[offset_SEQ + 1] -= len_PUB
+            offset_OCT_STR = 27
+            if b[offset_OCT_STR] != 0x04:
+                raise ECDSAUsageError(EXCEPTION_TEXT)
+            b[offset_OCT_STR + 1] -= len_PUB
+            if b[0] != 0x30 or b[1] != 0x81:
+                raise ECDSAUsageError(EXCEPTION_TEXT)
+            # as b[1] has bit7 set, the length is on b[2]
+            b[2] -= len_PUB
+            if b[2] < 0x80:
+                del(b[1])
+
+        elif format == serialization.PrivateFormat.TraditionalOpenSSL:
+            offset_PUB = 51
+            if der[offset_PUB] != 0xA1:
+                raise ECDSAUsageError(EXCEPTION_TEXT)
+            len_PUB = der[offset_PUB + 1] + 2
+            b = bytearray(der[0:offset_PUB])
+            b[1] -= len_PUB
+
         return b
 
-    def get_private_bytes(self, minimal):
+    def get_private_bytes(self, minimal, format):
+        formats = {'pkcs8': serialization.PrivateFormat.PKCS8,
+                   'openssl': serialization.PrivateFormat.TraditionalOpenSSL
+                   }
         priv = self.key.private_bytes(
                 encoding=serialization.Encoding.DER,
-                format=serialization.PrivateFormat.PKCS8,
+                format=formats[format],
                 encryption_algorithm=serialization.NoEncryption())
         if minimal:
-            priv = self._build_minimal_ecdsa_privkey(priv)
+            priv = self._build_minimal_ecdsa_privkey(priv, formats[format])
         return priv
 
     def export_private(self, path, passwd=None):
diff --git a/scripts/imgtool/keys/general.py b/scripts/imgtool/keys/general.py
index 77caf5d..033a70f 100644
--- a/scripts/imgtool/keys/general.py
+++ b/scripts/imgtool/keys/general.py
@@ -6,6 +6,7 @@
 
 AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
 
+
 class KeyClass(object):
     def _emit(self, header, trailer, encoded_bytes, indent, file=sys.stdout, len_format=None):
         print(AUTOGEN_MESSAGE, file=file)
@@ -40,11 +41,11 @@
     def emit_public_pem(self, file=sys.stdout):
         print(str(self.get_public_pem(), 'utf-8'), file=file, end='')
 
-    def emit_private(self, minimal, file=sys.stdout):
+    def emit_private(self, minimal, format, file=sys.stdout):
         self._emit(
                 header="const unsigned char enc_priv_key[] = {",
                 trailer="};",
-                encoded_bytes=self.get_private_bytes(minimal),
+                encoded_bytes=self.get_private_bytes(minimal, format),
                 indent="    ",
                 len_format="const unsigned int enc_priv_key_len = {};",
                 file=file)
diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py
old mode 100755
new mode 100644
index f7d8eea..e43eaf1
--- a/scripts/imgtool/main.py
+++ b/scripts/imgtool/main.py
@@ -69,6 +69,7 @@
     'ed25519':    gen_ed25519,
     'x25519':     gen_x25519,
 }
+valid_formats = ['openssl', 'pkcs8']
 
 def load_signature(sigfile):
     with open(sigfile, 'rb') as f:
@@ -150,13 +151,17 @@
                    'might require changes to the build config. Check the docs!'
               )
 @click.option('-k', '--key', metavar='filename', required=True)
+@click.option('-f', '--format',
+              type=click.Choice(valid_formats),
+              help='Valid formats: {}'.format(', '.join(valid_formats)),
+              default='pkcs8')
 @click.command(help='Dump private key from keypair')
-def getpriv(key, minimal):
+def getpriv(key, minimal, format):
     key = load_key(key)
     if key is None:
         print("Invalid passphrase")
     try:
-        key.emit_private(minimal)
+        key.emit_private(minimal, format)
     except (RSAUsageError, ECDSAUsageError, Ed25519UsageError,
             X25519UsageError) as e:
         raise click.UsageError(e)