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,7 +233,27 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
has_keydev_dec = true;
|
has_keydev_dec = true;
|
||||||
}
|
}
|
||||||
goto err; //No return
|
}
|
||||||
|
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 {
|
else {
|
||||||
|
@ -80,10 +80,11 @@ int cbor_get_info() {
|
|||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
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_AUT));
|
||||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_KEY_AGREEMENT));
|
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_UNLOCK));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_BACKUP));
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
|
@ -119,6 +119,7 @@ typedef struct {
|
|||||||
#define CTAP_CONFIG_AUT 0x03e43f56b34285e2
|
#define CTAP_CONFIG_AUT 0x03e43f56b34285e2
|
||||||
#define CTAP_CONFIG_KEY_AGREEMENT 0x1831a40f04a25ed9
|
#define CTAP_CONFIG_KEY_AGREEMENT 0x1831a40f04a25ed9
|
||||||
#define CTAP_CONFIG_UNLOCK 0x54365966c9a74770
|
#define CTAP_CONFIG_UNLOCK 0x54365966c9a74770
|
||||||
|
#define CTAP_CONFIG_BACKUP 0x6b1ede62beff0d5e
|
||||||
|
|
||||||
// Command status responses
|
// Command status responses
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import platform
|
import platform
|
||||||
|
from binascii import hexlify
|
||||||
|
from words import words
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from fido2.ctap2.config import Config
|
from fido2.ctap2.config import Config
|
||||||
@ -66,9 +68,11 @@ class VendorConfig(Config):
|
|||||||
CONFIG_AUT = 0x03e43f56b34285e2
|
CONFIG_AUT = 0x03e43f56b34285e2
|
||||||
CONFIG_KEY_AGREEMENT = 0x1831a40f04a25ed9
|
CONFIG_KEY_AGREEMENT = 0x1831a40f04a25ed9
|
||||||
CONFIG_UNLOCK = 0x54365966c9a74770
|
CONFIG_UNLOCK = 0x54365966c9a74770
|
||||||
|
CONFIG_BACKUP = 0x6b1ede62beff0d5e
|
||||||
|
|
||||||
class RESP(IntEnum):
|
class RESP(IntEnum):
|
||||||
KEY_AGREEMENT = 0x01
|
KEY_AGREEMENT = 0x01
|
||||||
|
BACKUP = 0x01
|
||||||
|
|
||||||
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
|
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
|
||||||
super().__init__(ctap, pin_uv_protocol, pin_uv_token)
|
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():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparser = parser.add_subparsers(title="commands", dest="command")
|
subparser = parser.add_subparsers(title="commands", dest="command")
|
||||||
parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.')
|
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_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()
|
args = parser.parse_args()
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@ -159,8 +214,17 @@ def secure(dev, args):
|
|||||||
elif (args.subcommand == 'disable'):
|
elif (args.subcommand == 'disable'):
|
||||||
vcfg.disable_device_aut()
|
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):
|
def main(args):
|
||||||
print('Pico Fido Tool v1.0')
|
print('Pico Fido Tool v1.2')
|
||||||
print('Author: Pol Henarejos')
|
print('Author: Pol Henarejos')
|
||||||
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
|
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
|
||||||
print('')
|
print('')
|
||||||
@ -170,6 +234,8 @@ def main(args):
|
|||||||
|
|
||||||
if (args.command == 'secure'):
|
if (args.command == 'secure'):
|
||||||
secure(dev, args)
|
secure(dev, args)
|
||||||
|
elif (args.command == 'backup'):
|
||||||
|
backup(dev, args)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
args = parse_args()
|
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`')
|
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`')
|
||||||
sys.exit(-1)
|
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):
|
def get_backend(use_secure_enclave=False):
|
||||||
backend = OSXKeychainKeysBackend(
|
backend = OSXKeychainKeysBackend(
|
||||||
@ -32,6 +38,14 @@ def generate_secure_key(use_secure_enclave=False):
|
|||||||
def get_d(key):
|
def get_d(key):
|
||||||
return key.private_numbers().private_value.to_bytes(32, 'big')
|
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():
|
def get_secure_key():
|
||||||
key = None
|
key = None
|
||||||
try:
|
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