Added first step to Enterprise Attestation.
Once enabled, it allows to generate a CSR in the device, which is sent to our PKI. If valid, it returns a signed certificate by an intermediate CA that will be used for attestation. Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
#include "mbedtls/ecdh.h"
|
#include "mbedtls/ecdh.h"
|
||||||
#include "mbedtls/chachapoly.h"
|
#include "mbedtls/chachapoly.h"
|
||||||
#include "mbedtls/hkdf.h"
|
#include "mbedtls/hkdf.h"
|
||||||
|
#include "mbedtls/x509_csr.h"
|
||||||
|
|
||||||
extern uint8_t keydev_dec[32];
|
extern uint8_t keydev_dec[32];
|
||||||
extern bool has_keydev_dec;
|
extern bool has_keydev_dec;
|
||||||
@@ -226,6 +227,45 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
has_keydev_dec = true;
|
has_keydev_dec = true;
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
else if (cmd == CTAP_VENDOR_EA) {
|
||||||
|
if (vendorCmd == 0x01) {
|
||||||
|
uint8_t buffer[1024];
|
||||||
|
mbedtls_ecdsa_context ekey;
|
||||||
|
mbedtls_ecdsa_init(&ekey);
|
||||||
|
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||||
|
if (ret != 0) {
|
||||||
|
mbedtls_ecdsa_free(&ekey);
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||||
|
}
|
||||||
|
ret = mbedtls_ecp_mul(&ekey.grp, &ekey.Q, &ekey.d, &ekey.grp.G, random_gen, NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
mbedtls_ecdsa_free(&ekey);
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||||
|
}
|
||||||
|
pico_unique_board_id_t rpiid;
|
||||||
|
pico_get_unique_board_id(&rpiid);
|
||||||
|
mbedtls_x509write_csr ctx;
|
||||||
|
mbedtls_x509write_csr_init(&ctx);
|
||||||
|
snprintf((char *)buffer, sizeof(buffer), "C=ES,O=Pico Keys,OU=Authenticator Attestation,CN=Pico Fido EE Serial %llu", ((uint64_t)rpiid.id[0] << 56) | ((uint64_t)rpiid.id[1] << 48) | ((uint64_t)rpiid.id[2] << 40) | ((uint64_t)rpiid.id[3] << 32) | (rpiid.id[4] << 24) | (rpiid.id[5] << 16) | (rpiid.id[6] << 8) | rpiid.id[7]);
|
||||||
|
mbedtls_x509write_csr_set_subject_name(&ctx, (char *)buffer);
|
||||||
|
mbedtls_pk_context key;
|
||||||
|
mbedtls_pk_init(&key);
|
||||||
|
mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
|
||||||
|
key.pk_ctx = &ekey;
|
||||||
|
mbedtls_x509write_csr_set_key(&ctx, &key);
|
||||||
|
mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256);
|
||||||
|
mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid));
|
||||||
|
ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_gen, NULL);
|
||||||
|
mbedtls_ecdsa_free(&ekey);
|
||||||
|
if (ret <= 0) {
|
||||||
|
mbedtls_x509write_csr_free(&ctx);
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||||
|
}
|
||||||
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||||
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ typedef struct {
|
|||||||
#define CTAP_VENDOR_BACKUP 0x01
|
#define CTAP_VENDOR_BACKUP 0x01
|
||||||
#define CTAP_VENDOR_MSE 0x02
|
#define CTAP_VENDOR_MSE 0x02
|
||||||
#define CTAP_VENDOR_UNLOCK 0x03
|
#define CTAP_VENDOR_UNLOCK 0x03
|
||||||
|
#define CTAP_VENDOR_EA 0x04
|
||||||
|
|
||||||
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
|
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
|
||||||
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
|
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ from words import words
|
|||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import Mapping, Any, Optional, Callable
|
from typing import Mapping, Any, Optional, Callable
|
||||||
import struct
|
import struct
|
||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
from enum import IntEnum, unique
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -48,6 +50,7 @@ try:
|
|||||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography import x509
|
||||||
except:
|
except:
|
||||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
@@ -63,6 +66,21 @@ else:
|
|||||||
print('ERROR: platform not supported')
|
print('ERROR: platform not supported')
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
def get_pki_data(url, data=None, method='GET'):
|
||||||
|
user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; '
|
||||||
|
'rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7'
|
||||||
|
method = 'GET'
|
||||||
|
if (data is not None):
|
||||||
|
method = 'POST'
|
||||||
|
req = urllib.request.Request(f"https://www.picokeys.com/pico/pico-fido/{url}/",
|
||||||
|
method=method,
|
||||||
|
data=data,
|
||||||
|
headers={'User-Agent': user_agent, })
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
resp = response.read().decode('utf-8')
|
||||||
|
j = json.loads(resp)
|
||||||
|
return j
|
||||||
|
|
||||||
class VendorConfig(Config):
|
class VendorConfig(Config):
|
||||||
|
|
||||||
class PARAM(IntEnum):
|
class PARAM(IntEnum):
|
||||||
@@ -179,6 +197,7 @@ class Vendor:
|
|||||||
VENDOR_BACKUP = 0x01
|
VENDOR_BACKUP = 0x01
|
||||||
VENDOR_MSE = 0x02
|
VENDOR_MSE = 0x02
|
||||||
VENDOR_UNLOCK = 0x03
|
VENDOR_UNLOCK = 0x03
|
||||||
|
VENDOR_CSR = 0x04
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class PARAM(IntEnum):
|
class PARAM(IntEnum):
|
||||||
@@ -189,6 +208,7 @@ class Vendor:
|
|||||||
ENABLE = 0x01
|
ENABLE = 0x01
|
||||||
DISABLE = 0x02
|
DISABLE = 0x02
|
||||||
KEY_AGREEMENT = 0x01
|
KEY_AGREEMENT = 0x01
|
||||||
|
CSR = 0x01
|
||||||
|
|
||||||
class RESP(IntEnum):
|
class RESP(IntEnum):
|
||||||
PARAM = 0x01
|
PARAM = 0x01
|
||||||
@@ -342,6 +362,12 @@ class Vendor:
|
|||||||
def disable_device_aut(self):
|
def disable_device_aut(self):
|
||||||
self.vcfg.disable_device_aut()
|
self.vcfg.disable_device_aut()
|
||||||
|
|
||||||
|
def csr(self):
|
||||||
|
return self._call(
|
||||||
|
Vendor.CMD.VENDOR_CSR,
|
||||||
|
Vendor.SUBCMD.CSR,
|
||||||
|
)
|
||||||
|
|
||||||
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")
|
||||||
@@ -352,6 +378,10 @@ def parse_args():
|
|||||||
parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.')
|
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.')
|
parser_backup.add_argument('filename', help='File to save or load the backup.')
|
||||||
|
|
||||||
|
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation')
|
||||||
|
parser_attestation.add_argument('subcommand', choices=['csr', 'upload'])
|
||||||
|
parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -369,9 +399,16 @@ def backup(vdr, args):
|
|||||||
elif (args.subcommand == 'load'):
|
elif (args.subcommand == 'load'):
|
||||||
vdr.backup_load(args.filename)
|
vdr.backup_load(args.filename)
|
||||||
|
|
||||||
|
def attestation(vdr, args):
|
||||||
|
if (args.subcommand == 'csr'):
|
||||||
|
csr = x509.load_der_x509_csr(vdr.csr()[1])
|
||||||
|
data = urllib.parse.urlencode({'csr': csr.public_bytes(Encoding.PEM)}).encode()
|
||||||
|
j = get_pki_data('csr', data=data)
|
||||||
|
cert = x509.load_pem_x509_certificate(j['x509'].encode())
|
||||||
|
print(cert)
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
print('Pico Fido Tool v1.2')
|
print('Pico Fido Tool v1.4')
|
||||||
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('')
|
||||||
@@ -385,6 +422,8 @@ def main(args):
|
|||||||
secure(vdr, args)
|
secure(vdr, args)
|
||||||
elif (args.command == 'backup'):
|
elif (args.command == 'backup'):
|
||||||
backup(vdr, args)
|
backup(vdr, args)
|
||||||
|
elif (args.command == 'attestation'):
|
||||||
|
attestation(vdr, args)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|||||||
Reference in New Issue
Block a user