1*dea01f66SCésar Belley#!/usr/bin/env python3 2*dea01f66SCésar Belley# 3*dea01f66SCésar Belley# Libu2f-emu setup directory generator for USB U2F key emulation. 4*dea01f66SCésar Belley# 5*dea01f66SCésar Belley# Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> 6*dea01f66SCésar Belley# Written by César Belley <cesar.belley@lse.epita.fr> 7*dea01f66SCésar Belley# 8*dea01f66SCésar Belley# This work is licensed under the terms of the GNU GPL, version 2 9*dea01f66SCésar Belley# or, at your option, any later version. See the COPYING file in 10*dea01f66SCésar Belley# the top-level directory. 11*dea01f66SCésar Belley 12*dea01f66SCésar Belleyimport sys 13*dea01f66SCésar Belleyimport os 14*dea01f66SCésar Belleyfrom random import randint 15*dea01f66SCésar Belleyfrom typing import Tuple 16*dea01f66SCésar Belley 17*dea01f66SCésar Belleyfrom cryptography.hazmat.backends import default_backend 18*dea01f66SCésar Belleyfrom cryptography.hazmat.primitives.asymmetric import ec 19*dea01f66SCésar Belleyfrom cryptography.hazmat.primitives.serialization import Encoding, \ 20*dea01f66SCésar Belley NoEncryption, PrivateFormat, PublicFormat 21*dea01f66SCésar Belleyfrom OpenSSL import crypto 22*dea01f66SCésar Belley 23*dea01f66SCésar Belley 24*dea01f66SCésar Belleydef write_setup_dir(dirpath: str, privkey_pem: bytes, cert_pem: bytes, 25*dea01f66SCésar Belley entropy: bytes, counter: int) -> None: 26*dea01f66SCésar Belley """ 27*dea01f66SCésar Belley Write the setup directory. 28*dea01f66SCésar Belley 29*dea01f66SCésar Belley Args: 30*dea01f66SCésar Belley dirpath: The directory path. 31*dea01f66SCésar Belley key_pem: The private key PEM. 32*dea01f66SCésar Belley cert_pem: The certificate PEM. 33*dea01f66SCésar Belley entropy: The 48 bytes of entropy. 34*dea01f66SCésar Belley counter: The counter value. 35*dea01f66SCésar Belley """ 36*dea01f66SCésar Belley # Directory 37*dea01f66SCésar Belley os.mkdir(dirpath) 38*dea01f66SCésar Belley 39*dea01f66SCésar Belley # Private key 40*dea01f66SCésar Belley with open(f'{dirpath}/private-key.pem', 'bw') as f: 41*dea01f66SCésar Belley f.write(privkey_pem) 42*dea01f66SCésar Belley 43*dea01f66SCésar Belley # Certificate 44*dea01f66SCésar Belley with open(f'{dirpath}/certificate.pem', 'bw') as f: 45*dea01f66SCésar Belley f.write(cert_pem) 46*dea01f66SCésar Belley 47*dea01f66SCésar Belley # Entropy 48*dea01f66SCésar Belley with open(f'{dirpath}/entropy', 'wb') as f: 49*dea01f66SCésar Belley f.write(entropy) 50*dea01f66SCésar Belley 51*dea01f66SCésar Belley # Counter 52*dea01f66SCésar Belley with open(f'{dirpath}/counter', 'w') as f: 53*dea01f66SCésar Belley f.write(f'{str(counter)}\n') 54*dea01f66SCésar Belley 55*dea01f66SCésar Belley 56*dea01f66SCésar Belleydef generate_ec_key_pair() -> Tuple[str, str]: 57*dea01f66SCésar Belley """ 58*dea01f66SCésar Belley Generate an ec key pair. 59*dea01f66SCésar Belley 60*dea01f66SCésar Belley Returns: 61*dea01f66SCésar Belley The private and public key PEM. 62*dea01f66SCésar Belley """ 63*dea01f66SCésar Belley # Key generation 64*dea01f66SCésar Belley privkey = ec.generate_private_key(ec.SECP256R1, default_backend()) 65*dea01f66SCésar Belley pubkey = privkey.public_key() 66*dea01f66SCésar Belley 67*dea01f66SCésar Belley # PEM serialization 68*dea01f66SCésar Belley privkey_pem = privkey.private_bytes(encoding=Encoding.PEM, 69*dea01f66SCésar Belley format=PrivateFormat.TraditionalOpenSSL, 70*dea01f66SCésar Belley encryption_algorithm=NoEncryption()) 71*dea01f66SCésar Belley pubkey_pem = pubkey.public_bytes(encoding=Encoding.PEM, 72*dea01f66SCésar Belley format=PublicFormat.SubjectPublicKeyInfo) 73*dea01f66SCésar Belley return privkey_pem, pubkey_pem 74*dea01f66SCésar Belley 75*dea01f66SCésar Belley 76*dea01f66SCésar Belleydef generate_certificate(privkey_pem: str, pubkey_pem: str) -> str: 77*dea01f66SCésar Belley """ 78*dea01f66SCésar Belley Generate a x509 certificate from a key pair. 79*dea01f66SCésar Belley 80*dea01f66SCésar Belley Args: 81*dea01f66SCésar Belley privkey_pem: The private key PEM. 82*dea01f66SCésar Belley pubkey_pem: The public key PEM. 83*dea01f66SCésar Belley 84*dea01f66SCésar Belley Returns: 85*dea01f66SCésar Belley The certificate PEM. 86*dea01f66SCésar Belley """ 87*dea01f66SCésar Belley # Convert key pair 88*dea01f66SCésar Belley privkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey_pem) 89*dea01f66SCésar Belley pubkey = crypto.load_publickey(crypto.FILETYPE_PEM, pubkey_pem) 90*dea01f66SCésar Belley 91*dea01f66SCésar Belley # New x509v3 certificate 92*dea01f66SCésar Belley cert = crypto.X509() 93*dea01f66SCésar Belley cert.set_version(0x2) 94*dea01f66SCésar Belley 95*dea01f66SCésar Belley # Serial number 96*dea01f66SCésar Belley cert.set_serial_number(randint(1, 2 ** 64)) 97*dea01f66SCésar Belley 98*dea01f66SCésar Belley # Before / After 99*dea01f66SCésar Belley cert.gmtime_adj_notBefore(0) 100*dea01f66SCésar Belley cert.gmtime_adj_notAfter(4 * (365 * 24 * 60 * 60)) 101*dea01f66SCésar Belley 102*dea01f66SCésar Belley # Public key 103*dea01f66SCésar Belley cert.set_pubkey(pubkey) 104*dea01f66SCésar Belley 105*dea01f66SCésar Belley # Subject name and issueer 106*dea01f66SCésar Belley cert.get_subject().CN = "U2F emulated" 107*dea01f66SCésar Belley cert.set_issuer(cert.get_subject()) 108*dea01f66SCésar Belley 109*dea01f66SCésar Belley # Extensions 110*dea01f66SCésar Belley cert.add_extensions([ 111*dea01f66SCésar Belley crypto.X509Extension(b"subjectKeyIdentifier", 112*dea01f66SCésar Belley False, b"hash", subject=cert), 113*dea01f66SCésar Belley ]) 114*dea01f66SCésar Belley cert.add_extensions([ 115*dea01f66SCésar Belley crypto.X509Extension(b"authorityKeyIdentifier", 116*dea01f66SCésar Belley False, b"keyid:always", issuer=cert), 117*dea01f66SCésar Belley ]) 118*dea01f66SCésar Belley cert.add_extensions([ 119*dea01f66SCésar Belley crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE") 120*dea01f66SCésar Belley ]) 121*dea01f66SCésar Belley 122*dea01f66SCésar Belley # Signature 123*dea01f66SCésar Belley cert.sign(privkey, 'sha256') 124*dea01f66SCésar Belley 125*dea01f66SCésar Belley return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) 126*dea01f66SCésar Belley 127*dea01f66SCésar Belley 128*dea01f66SCésar Belleydef generate_setup_dir(dirpath: str) -> None: 129*dea01f66SCésar Belley """ 130*dea01f66SCésar Belley Generates the setup directory. 131*dea01f66SCésar Belley 132*dea01f66SCésar Belley Args: 133*dea01f66SCésar Belley dirpath: The directory path. 134*dea01f66SCésar Belley """ 135*dea01f66SCésar Belley # Key pair 136*dea01f66SCésar Belley privkey_pem, pubkey_pem = generate_ec_key_pair() 137*dea01f66SCésar Belley 138*dea01f66SCésar Belley # Certificate 139*dea01f66SCésar Belley certificate_pem = generate_certificate(privkey_pem, pubkey_pem) 140*dea01f66SCésar Belley 141*dea01f66SCésar Belley # Entropy 142*dea01f66SCésar Belley entropy = os.urandom(48) 143*dea01f66SCésar Belley 144*dea01f66SCésar Belley # Counter 145*dea01f66SCésar Belley counter = 0 146*dea01f66SCésar Belley 147*dea01f66SCésar Belley # Write 148*dea01f66SCésar Belley write_setup_dir(dirpath, privkey_pem, certificate_pem, entropy, counter) 149*dea01f66SCésar Belley 150*dea01f66SCésar Belley 151*dea01f66SCésar Belleydef main() -> None: 152*dea01f66SCésar Belley """ 153*dea01f66SCésar Belley Main function 154*dea01f66SCésar Belley """ 155*dea01f66SCésar Belley # Dir path 156*dea01f66SCésar Belley if len(sys.argv) != 2: 157*dea01f66SCésar Belley sys.stderr.write(f'Usage: {sys.argv[0]} <setup_dir>\n') 158*dea01f66SCésar Belley exit(2) 159*dea01f66SCésar Belley dirpath = sys.argv[1] 160*dea01f66SCésar Belley 161*dea01f66SCésar Belley # Dir non existence 162*dea01f66SCésar Belley if os.path.exists(dirpath): 163*dea01f66SCésar Belley sys.stderr.write(f'Directory: {dirpath} already exists.\n') 164*dea01f66SCésar Belley exit(1) 165*dea01f66SCésar Belley 166*dea01f66SCésar Belley generate_setup_dir(dirpath) 167*dea01f66SCésar Belley 168*dea01f66SCésar Belley 169*dea01f66SCésar Belleyif __name__ == '__main__': 170*dea01f66SCésar Belley main() 171