mirror of
https://github.com/polhenarejos/pico-fido.git
synced 2024-09-20 03:10:10 +00:00
Adding support for backup.
Now it is possible to backup and restore the internal keys to recover a pico fido. The process is splitted in two parts: a list of 24 words and a file, which stores the security key. Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
parent
a42131876f
commit
43cd8869f9
@ -233,8 +233,28 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
}
|
||||
has_keydev_dec = true;
|
||||
}
|
||||
}
|
||||
goto err; //No return
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_BACKUP) {
|
||||
if (vendorAutCt.present == false) { // Save
|
||||
if (has_keydev_dec == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc)));
|
||||
}
|
||||
else { // Load
|
||||
uint8_t zeros[32];
|
||||
memset(zeros, 0, sizeof(zeros));
|
||||
flash_write_data_to_file(ef_keydev_enc, vendorAutCt.data, vendorAutCt.len);
|
||||
flash_write_data_to_file(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
|
||||
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
|
@ -80,10 +80,11 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_KEY_AGREEMENT));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_UNLOCK));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_BACKUP));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
|
@ -119,6 +119,7 @@ typedef struct {
|
||||
#define CTAP_CONFIG_AUT 0x03e43f56b34285e2
|
||||
#define CTAP_CONFIG_KEY_AGREEMENT 0x1831a40f04a25ed9
|
||||
#define CTAP_CONFIG_UNLOCK 0x54365966c9a74770
|
||||
#define CTAP_CONFIG_BACKUP 0x6b1ede62beff0d5e
|
||||
|
||||
// Command status responses
|
||||
|
||||
|
@ -22,6 +22,8 @@
|
||||
import sys
|
||||
import argparse
|
||||
import platform
|
||||
from binascii import hexlify
|
||||
from words import words
|
||||
|
||||
try:
|
||||
from fido2.ctap2.config import Config
|
||||
@ -66,9 +68,11 @@ class VendorConfig(Config):
|
||||
CONFIG_AUT = 0x03e43f56b34285e2
|
||||
CONFIG_KEY_AGREEMENT = 0x1831a40f04a25ed9
|
||||
CONFIG_UNLOCK = 0x54365966c9a74770
|
||||
CONFIG_BACKUP = 0x6b1ede62beff0d5e
|
||||
|
||||
class RESP(IntEnum):
|
||||
KEY_AGREEMENT = 0x01
|
||||
BACKUP = 0x01
|
||||
|
||||
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
|
||||
super().__init__(ctap, pin_uv_protocol, pin_uv_token)
|
||||
@ -140,12 +144,63 @@ class VendorConfig(Config):
|
||||
},
|
||||
)
|
||||
|
||||
def backup_save(self, filename):
|
||||
ret = self._call(
|
||||
Config.CMD.VENDOR_PROTOTYPE,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_BACKUP,
|
||||
},
|
||||
)
|
||||
data = ret[VendorConfig.RESP.BACKUP]
|
||||
d = int.from_bytes(skey.get_secure_key(), 'big')
|
||||
with open(filename, 'wb') as fp:
|
||||
fp.write(b'\x01')
|
||||
fp.write(data)
|
||||
pk = ec.derive_private_key(d, ec.SECP256R1())
|
||||
signature = pk.sign(data, ec.ECDSA(hashes.SHA256()))
|
||||
fp.write(signature)
|
||||
print('Remember the following words in this order:')
|
||||
for c in range(24):
|
||||
coef = (d//(2048**c))%2048
|
||||
print(f'{(c+1):02d} - {words[coef]}')
|
||||
|
||||
def backup_load(self, filename):
|
||||
d = 0
|
||||
if (d == 0):
|
||||
for c in range(24):
|
||||
word = input(f'Introduce word {(c+1):02d}: ')
|
||||
while (word not in words):
|
||||
word = input(f'Word not found. Please, tntroduce the correct word {(c+1):02d}: ')
|
||||
coef = words.index(word)
|
||||
d = d+(2048**c)*coef
|
||||
|
||||
pk = ec.derive_private_key(d, ec.SECP256R1())
|
||||
pb = pk.public_key()
|
||||
with open(filename, 'rb') as fp:
|
||||
format = fp.read(1)[0]
|
||||
if (format == 0x1):
|
||||
data = fp.read(60)
|
||||
signature = fp.read()
|
||||
pb.verify(signature, data, ec.ECDSA(hashes.SHA256()))
|
||||
skey.set_secure_key(pk)
|
||||
self._call(
|
||||
Config.CMD.VENDOR_PROTOTYPE,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_BACKUP,
|
||||
VendorConfig.PARAM.VENDOR_AUT_CT: data,
|
||||
},
|
||||
)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparser = parser.add_subparsers(title="commands", dest="command")
|
||||
parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.')
|
||||
parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.')
|
||||
|
||||
parser_backup = subparser.add_parser('backup', help='Manages the backup of Pico Fido.')
|
||||
parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.')
|
||||
parser_backup.add_argument('filename', help='File to save or load the backup.')
|
||||
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
@ -159,8 +214,17 @@ def secure(dev, args):
|
||||
elif (args.subcommand == 'disable'):
|
||||
vcfg.disable_device_aut()
|
||||
|
||||
def backup(dev, args):
|
||||
vcfg = VendorConfig(Ctap2(dev))
|
||||
|
||||
if (args.subcommand == 'save'):
|
||||
vcfg.backup_save(args.filename)
|
||||
elif (args.subcommand == 'load'):
|
||||
vcfg.backup_load(args.filename)
|
||||
|
||||
|
||||
def main(args):
|
||||
print('Pico Fido Tool v1.0')
|
||||
print('Pico Fido Tool v1.2')
|
||||
print('Author: Pol Henarejos')
|
||||
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
|
||||
print('')
|
||||
@ -170,6 +234,8 @@ def main(args):
|
||||
|
||||
if (args.command == 'secure'):
|
||||
secure(dev, args)
|
||||
elif (args.command == 'backup'):
|
||||
backup(dev, args)
|
||||
|
||||
def run():
|
||||
args = parse_args()
|
||||
|
@ -11,6 +11,12 @@ except:
|
||||
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`')
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
|
||||
except:
|
||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def get_backend(use_secure_enclave=False):
|
||||
backend = OSXKeychainKeysBackend(
|
||||
@ -32,6 +38,14 @@ def generate_secure_key(use_secure_enclave=False):
|
||||
def get_d(key):
|
||||
return key.private_numbers().private_value.to_bytes(32, 'big')
|
||||
|
||||
def set_secure_key(pk):
|
||||
backend = get_backend(False)
|
||||
try:
|
||||
backend.delete_password(DOMAIN, USERNAME)
|
||||
except:
|
||||
pass
|
||||
backend.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
|
||||
|
||||
def get_secure_key():
|
||||
key = None
|
||||
try:
|
||||
|
1
tools/words.py
Normal file
1
tools/words.py
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user