-
Notifications
You must be signed in to change notification settings - Fork 34
/
key_attestation.py
385 lines (325 loc) · 14.2 KB
/
key_attestation.py
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
"""
Asymmetric Key Attestation Example
"""
# (c) 2015-2019 Microchip Technology Inc. and its subsidiaries.
#
# Subject to your compliance with these terms, you may use Microchip software
# and any derivatives exclusively with Microchip products. It is your
# responsibility to comply with third party license terms applicable to your
# use of third party software (including open source software) that may
# accompany Microchip software.
#
# THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
# EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
# WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
# PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT,
# SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE
# OF ANY KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF
# MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE
# FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL
# LIABILITY ON ALL CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED
# THE AMOUNT OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR
# THIS SOFTWARE.
from textwrap import TextWrapper
import os
from hashlib import sha256
import struct
import copy
from cryptoauthlib import *
from cryptoauthlib.device import *
from cryptoauthlib.library import ctypes_to_bytes
from common import *
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.exceptions import InvalidSignature
from cryptography.utils import int_from_bytes, int_to_bytes
import time
ATCA_SUCCESS = 0x00
def init_device(iface='hid', **kwargs):
"""Initialize CryptoAuthLib for the current device"""
# Loading cryptoauthlib(python specific)
load_cryptoauthlib()
# Get the target default config
cfg = eval('cfg_ateccx08a_{}_default()'.format(atca_names_map.get(iface)))
# Set interface parameters
if kwargs is not None:
for k, v in kwargs.items():
icfg = getattr(cfg.cfg, 'atca{}'.format(iface))
setattr(icfg, k, int(v, 16))
# Basic Raspberry Pi I2C check
if 'i2c' == iface and check_if_rpi():
cfg.cfg.atcai2c.bus = 1
# Initialize the stack
assert atcab_init(cfg) == ATCA_SUCCESS
# Check device type
info = bytearray(4)
assert atcab_info(info) == ATCA_SUCCESS
dev_name = get_device_name(info)
dev_type = get_device_type_id(dev_name)
# Reinitialize if the device type doesn't match the default
if dev_type != cfg.devtype:
cfg.dev_type = dev_type
assert atcab_release() == ATCA_SUCCESS
time.sleep(1)
assert atcab_init(cfg) == ATCA_SUCCESS
return dev_name
def read_config(dev_name):
"""Read the device configuration into the appropriate config structure"""
config_data = bytearray(128)
assert ATCA_SUCCESS == atcab_read_config_zone(config_data)
if dev_name == 'ATSHA204A':
config = Atsha204aConfig.from_buffer(config_data[:88])
elif dev_name == 'ATECC508A':
config = Atecc508aConfig.from_buffer(config_data)
elif dev_name == 'ATECC608A':
config = Atecc608aConfig.from_buffer(config_data)
else:
raise ValueError('Unsupported device {}'.format(dev_name))
return config
def find_attestation_key_slot(config):
"""Search for a suitable private attestation key."""
for slot in range(16):
if not config.KeyConfig[slot].Private:
continue # Slot is not a private key
if config.SlotConfig[slot].ReadKey & 0x03 == 0x02:
# External sign is disabled (bit 0) and internal sign is enabled (bit 1)
return slot
raise RuntimeError('No suitable attestation key found.')
def find_external_key_slot(config):
"""Search for a suitable private key that can perform external signs."""
for slot in range(16):
if not config.KeyConfig[slot].Private:
continue # Slot is not a private key
if config.SlotConfig[slot].ReadKey & 0x01 == 0x01:
# External sign is enabled (bit 0)
return slot
raise RuntimeError('No suitable external key found.')
def key_attestation(dev_name, attestation_key_slot, key_slot):
"""Demo the key attestation flow"""
print('Setup: Establish Trust with Attestation Key')
wrapper = TextWrapper(width=70, initial_indent=' ', subsequent_indent=' ')
print(wrapper.fill(
'The verifier (entity requesting key attestation) needs to trust the'
' attestation key in the device for this process to work. This could'
' be as simple as reading and storing the attestation public keys in'
' a trusted environment (e.g. during manufacturing). The verifier also'
' needs to know the expected configuration and state of the key/slot'
' being attested.'
))
print('\n Reading attestation public key from slot {}:'.format(attestation_key_slot))
attestation_public_key = bytearray(64)
assert ATCA_SUCCESS == atcab_get_pubkey(key_id=attestation_key_slot, public_key=attestation_public_key)
print(convert_ec_pub_to_pem(attestation_public_key))
print('\n Read configuration:')
config = read_config(dev_name)
print(pretty_print_hex(ctypes_to_bytes(config), indent=' '))
if config.LockConfig == 0x55:
raise RuntimeError('Config zone must be locked for this example to run')
if config.LockValue == 0x55:
raise RuntimeError('Data zone muct be locked for this example to run')
print('\nVERIFIER: Send key attestation request')
print(' Generate verifier challenge/nonce:')
verifier_nonce = os.urandom(20)
print(pretty_print_hex(verifier_nonce, indent=' '))
print('\nDEVICE: Generate key attestation signature')
print(wrapper.fill(
'Generate attestation nonce in TempKey from verifier and internal'
' device nonces.'
))
device_nonce = bytearray(32)
assert ATCA_SUCCESS == atcab_nonce_rand(num_in=verifier_nonce, rand_out=device_nonce)
print('\n Device nonce:')
print(pretty_print_hex(device_nonce, indent=' '))
print('\n' + wrapper.fill(
'Create a PubKey digest using the GenKey command with the Digest mode'
' (0x08). This special mode combines TempKey (attestation nonce) with'
' the public key being attested using SHA256 and stores the resulting'
' digest back into TempKey.'
))
# Note that other_data is ignored and not required for this mode
public_key = bytearray(64)
assert ATCA_SUCCESS == atcab_genkey_base(mode=0x08, key_id=key_slot, other_data=b'\x00'*3, public_key=public_key)
print('\n Public key being attested from slot {}:'.format(key_slot))
print(convert_ec_pub_to_pem(public_key))
print('\n' + wrapper.fill(
'Use attestation key to sign (Sign command in internal mode) a message'
' including the PubKey digest in TempKey with additional slot/key'
' configuration and state information.'
))
signature = bytearray(64)
assert ATCA_SUCCESS == atcab_sign_internal(
key_id=attestation_key_slot,
is_invalidate=False,
is_full_sn=False,
signature=signature)
print('\n Key attestation signature:')
print(pretty_print_hex(signature, indent=' '))
print('\nVERIFIER: Validate key attestation signature')
print(wrapper.fill(
'The verifier has now has the data from the device required to'
' perform key attestation. This includes the public key being'
' attested, device nonce, key/slot configuration and state, and'
' attestation signature. The verifier will need to build the'
' attestation message from this information and verify it against the'
' signature and the trusted attestation public key.'
))
print('\n Calculate attestation nonce from verifier and device nonces:')
nonce = calc_nonce(mode=0x00, zero=0x0000, num_in=verifier_nonce, rand_out=device_nonce)
print(pretty_print_hex(nonce, indent=' '))
print('\n Calculate PubKey digest:')
pubkey_digest = calc_genkey_pubkey_digest(
mode=0x08,
key_id=key_slot,
public_key=public_key,
temp_key=nonce,
sn=ctypes_to_bytes(config.SN03) + ctypes_to_bytes(config.SN48)
)
print(pretty_print_hex(pubkey_digest, indent=' '))
print('\n Calculate the internal sign message digest:')
msg_digest = calc_sign_internal_digest(
mode=0x00,
key_id=attestation_key_slot,
temp_key=pubkey_digest,
temp_key_key_id=key_slot,
temp_key_source_flag=0, # Device nonce in internally generated random number
temp_key_gendig_data=False,
temp_key_genkey_data=True,
temp_key_no_mac=False,
config=config
)
print(pretty_print_hex(msg_digest, indent=' '))
print('\n Verifying signature:')
attestation_public_key = ec.EllipticCurvePublicNumbers(
curve=ec.SECP256R1(),
x=int_from_bytes(attestation_public_key[0:32], byteorder='big'),
y=int_from_bytes(attestation_public_key[32:64], byteorder='big'),
).public_key(default_backend())
r = int_from_bytes(signature[0:32], byteorder='big', signed=False)
s = int_from_bytes(signature[32:64], byteorder='big', signed=False)
attestation_public_key.verify(
signature=utils.encode_dss_signature(r, s),
data=msg_digest,
signature_algorithm=ec.ECDSA(utils.Prehashed(hashes.SHA256()))
)
print(' SUCCESS! Key has been attested.')
def calc_nonce(mode, zero, num_in, rand_out=None, temp_key=None):
"""Replicate the internal TempKey calculations of the Nonce command"""
if mode == 0x03:
# Passthrough mode
if len(num_in) != 32:
raise ValueError('num_in must be 32 bytes')
return copy.copy(num_in)
elif mode == 0x00 or mode == 0x01:
# Random mode
if len(rand_out) != 32:
raise ValueError('rand_out must be 32 bytes')
if len(num_in) != 20:
raise ValueError('num_in must be 20 bytes')
msg = b''
msg += rand_out
msg += num_in
msg += b'\x16' # Nonce Opcode
msg += struct.pack("B", mode)
msg += struct.pack("<H", zero)[:1]
return sha256(msg).digest()
else:
raise BadArgumentError('Unsupported or invalid mode 0x{:02X}'.format(mode))
def calc_genkey_pubkey_digest(mode, key_id, public_key, temp_key, sn, other_data=None):
"""
Replicate the internal TempKey calculations of the GenKey command in
digest mode.
"""
if len(public_key) != 64:
raise ValueError('public_key must be 64 bytes')
if len(temp_key) != 32:
raise ValueError('temp_key must be 32 bytes')
msg = b''
msg += temp_key
msg += b'\x40' # GenKey Opcode
if mode & 0x10:
# Slot is storing a public key directly, use OtherData for Mode and KeyID
if len(other_data) != 3:
raise ValueError('other_data must be 3 bytes')
msg += other_data
else:
# Slot is storing a private key and the public key is generated
msg += struct.pack("B", mode)
msg += struct.pack("<H", key_id)
msg += sn[8:9]
msg += sn[0:2]
msg += b'\x00'*25
msg += public_key
return sha256(msg).digest()
def calc_sign_internal_digest(mode, key_id, temp_key, temp_key_key_id, temp_key_source_flag, temp_key_gendig_data,
temp_key_genkey_data, temp_key_no_mac, config, for_invalidate=False):
"""
Replicate the internal message digest calculations of the Sign command in
sign internal mode.
"""
# TODO: Doesn't work for ATECC108A
if mode & 0x80:
raise ValueError('Invalid mode, not internal sign')
if len(temp_key) != 32:
raise ValueError('temp_key must be 32 bytes')
msg = b''
msg += temp_key
msg += b'\x41' # Sign Opcode
msg += struct.pack('B', mode)
msg += struct.pack('<H', key_id)
msg += ctypes_to_bytes(config.SlotConfig[temp_key_key_id])
msg += ctypes_to_bytes(config.KeyConfig[temp_key_key_id])
temp_key_flags = 0
if temp_key_key_id < 0 or temp_key_key_id > 15:
raise ValueError('temp_key_key_id must be 0 to 15')
if temp_key_gendig_data and temp_key_genkey_data:
raise ValueError(
'temp_key_gendig_data and temp_key_genkey_data are mutually exclusive and both can not be true'
)
temp_key_flags += temp_key_key_id
temp_key_flags += (1 << 4) if temp_key_source_flag else 0
temp_key_flags += (1 << 5) if temp_key_gendig_data else 0
temp_key_flags += (1 << 6) if temp_key_genkey_data else 0
temp_key_flags += (1 << 7) if temp_key_no_mac else 0
msg += struct.pack('B', temp_key_flags)
msg += b'\x00'*2
is_full_sn = bool(mode & 0x40)
sn = ctypes_to_bytes(config.SN03) + ctypes_to_bytes(config.SN48)
msg += sn[8:9]
msg += sn[4:8] if is_full_sn else b'\x00'*4
msg += sn[0:2]
msg += sn[2:4] if is_full_sn else b'\x00'*2
msg += b'\x01' if config.SlotLocked & (1 << temp_key_key_id) else b'\x00'
msg += b'\x01' if for_invalidate else b'\x00'
msg += b'\x00'
return sha256(msg).digest()
if __name__ == '__main__':
parser = setup_example_runner(__file__)
parser.add_argument(
'-a',
'--attestation-key',
help='Slot of the attestation key. If omitted, a suitable key will be searched for.',
default=None,
type=int
)
parser.add_argument(
'-k',
'--key',
help='Slot of the key being attested. If omitted, a suitable key will be searched for.',
default=None,
type=int
)
args = parser.parse_args()
dev_name = init_device(args.iface, **parse_interface_params(args.params))
if 'ECC' not in dev_name:
raise RuntimeError('Unsupported device {}'.format(dev_name))
config = None
if args.attestation_key is None:
if config is None:
config = read_config(dev_name)
args.attestation_key = find_attestation_key_slot(config)
if args.key is None:
if config is None:
config = read_config(dev_name)
args.key = find_external_key_slot(config)
key_attestation(dev_name=dev_name, attestation_key_slot=args.attestation_key, key_slot=args.key)