Blame view

RIOT/dist/tools/mcuboot/imgtool/keys.py 4.28 KB
a752c7ab   elopes   add first test an...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
  """
  Cryptographic key management for imgtool.
  """
  
  from Crypto.Hash import SHA256
  from Crypto.PublicKey import RSA
  from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS
  from ecdsa import SigningKey, NIST256p, util
  import hashlib
  from pyasn1.type import namedtype, univ
  from pyasn1.codec.der.encoder import encode
  
  # By default, we use RSA-PSS (PKCS 2.1).  That can be overridden on
  # the command line to support the older (less secure) PKCS1.5
  sign_rsa_pss = True
  
  AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
  
  class RSAPublicKey(univ.Sequence):
      componentType = namedtype.NamedTypes(
              namedtype.NamedType('modulus', univ.Integer()),
              namedtype.NamedType('publicExponent', univ.Integer()))
  
  class RSA2048():
      def __init__(self, key):
          """Construct an RSA2048 key with the given key data"""
          self.key = key
  
      @staticmethod
      def generate():
          return RSA2048(RSA.generate(2048))
  
      def export_private(self, path):
          with open(path, 'wb') as f:
              f.write(self.key.exportKey('PEM'))
  
      def emit_c(self):
          node = RSAPublicKey()
          node['modulus'] = self.key.n
          node['publicExponent'] = self.key.e
          print(AUTOGEN_MESSAGE)
          print("const unsigned char rsa_pub_key[] = {", end='')
          encoded = bytearray(encode(node))
          for count, b in enumerate(encoded):
              if count % 8 == 0:
                  print("\n\t", end='')
              else:
                  print(" ", end='')
              print("0x{:02x},".format(b), end='')
          print("\n};")
          print("const unsigned int rsa_pub_key_len = {};".format(len(encoded)))
  
      def sig_type(self):
          """Return the type of this signature (as a string)"""
          if sign_rsa_pss:
              return "PKCS1_PSS_RSA2048_SHA256"
          else:
              return "PKCS15_RSA2048_SHA256"
  
      def sig_len(self):
          return 256
  
      def sig_tlv(self):
          return "RSA2048"
  
      def sign(self, payload):
          sha = SHA256.new(payload)
          if sign_rsa_pss:
              signer = PKCS1_PSS.new(self.key)
          else:
              signer = PKCS1_v1_5.new(self.key)
          signature = signer.sign(sha)
          assert len(signature) == self.sig_len()
          return signature
  
  class ECDSA256P1():
      def __init__(self, key):
          """Construct an ECDSA P-256 private key"""
          self.key = key
  
      @staticmethod
      def generate():
          return ECDSA256P1(SigningKey.generate(curve=NIST256p))
  
      def export_private(self, path):
          with open(path, 'wb') as f:
              f.write(key.to_pem())
  
      def emit_c(self):
          vk = self.key.get_verifying_key()
          print(AUTOGEN_MESSAGE)
          print("const unsigned char ecdsa_pub_key[] = {", end='')
          encoded = bytes(vk.to_der())
          for count, b in enumerate(encoded):
              if count % 8 == 0:
                  print("\n\t", end='')
              else:
                  print(" ", end='')
              print("0x{:02x},".format(b), end='')
          print("\n};")
          print("const unsigned int ecdsa_pub_key_len = {};".format(len(encoded)))
  
      def sign(self, payload):
          # To make this fixed length, possibly pad with zeros.
          sig = self.key.sign(payload, hashfunc=hashlib.sha256, sigencode=util.sigencode_der)
          sig += b'\000' * (self.sig_len() - len(sig))
          return sig
  
      def sig_len(self):
          # The DER encoding depends on the high bit, and can be
          # anywhere from 70 to 72 bytes.  Because we have to fill in
          # the length field before computing the signature, however,
          # we'll give the largest, and the sig checking code will allow
          # for it to be up to two bytes larger than the actual
          # signature.
          return 72
  
      def sig_type(self):
          """Return the type of this signature (as a string)"""
          return "ECDSA256_SHA256"
  
      def sig_tlv(self):
          return "ECDSA256"
  
  def load(path):
      with open(path, 'rb') as f:
          pem = f.read()
      try:
          key = RSA.importKey(pem)
          if key.n.bit_length() != 2048:
              raise Exception("Unsupported RSA bit length, only 2048 supported")
          return RSA2048(key)
      except ValueError:
          key = SigningKey.from_pem(pem)
          if key.curve.name != 'NIST256p':
              raise Exception("Unsupported ECDSA curve")
          return ECDSA256P1(key)