| #!/usr/bin/env python3 | |
| # | |
| # Libu2f-emu setup directory generator for USB U2F key emulation. | |
| # | |
| # Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> | |
| # Written by César Belley <cesar.belley@lse.epita.fr> | |
| # | |
| # This work is licensed under the terms of the GNU GPL, version 2 | |
| # or, at your option, any later version. See the COPYING file in | |
| # the top-level directory. | |
| import sys | |
| import os | |
| from random import randint | |
| from typing import Tuple | |
| from cryptography.hazmat.backends import default_backend | |
| from cryptography.hazmat.primitives.asymmetric import ec | |
| from cryptography.hazmat.primitives.serialization import Encoding, \ | |
| NoEncryption, PrivateFormat, PublicFormat | |
| from OpenSSL import crypto | |
| def write_setup_dir(dirpath: str, privkey_pem: bytes, cert_pem: bytes, | |
| entropy: bytes, counter: int) -> None: | |
| """ | |
| Write the setup directory. | |
| Args: | |
| dirpath: The directory path. | |
| key_pem: The private key PEM. | |
| cert_pem: The certificate PEM. | |
| entropy: The 48 bytes of entropy. | |
| counter: The counter value. | |
| """ | |
| # Directory | |
| os.mkdir(dirpath) | |
| # Private key | |
| with open(f'{dirpath}/private-key.pem', 'bw') as f: | |
| f.write(privkey_pem) | |
| # Certificate | |
| with open(f'{dirpath}/certificate.pem', 'bw') as f: | |
| f.write(cert_pem) | |
| # Entropy | |
| with open(f'{dirpath}/entropy', 'wb') as f: | |
| f.write(entropy) | |
| # Counter | |
| with open(f'{dirpath}/counter', 'w') as f: | |
| f.write(f'{str(counter)}\n') | |
| def generate_ec_key_pair() -> Tuple[str, str]: | |
| """ | |
| Generate an ec key pair. | |
| Returns: | |
| The private and public key PEM. | |
| """ | |
| # Key generation | |
| privkey = ec.generate_private_key(ec.SECP256R1, default_backend()) | |
| pubkey = privkey.public_key() | |
| # PEM serialization | |
| privkey_pem = privkey.private_bytes(encoding=Encoding.PEM, | |
| format=PrivateFormat.TraditionalOpenSSL, | |
| encryption_algorithm=NoEncryption()) | |
| pubkey_pem = pubkey.public_bytes(encoding=Encoding.PEM, | |
| format=PublicFormat.SubjectPublicKeyInfo) | |
| return privkey_pem, pubkey_pem | |
| def generate_certificate(privkey_pem: str, pubkey_pem: str) -> str: | |
| """ | |
| Generate a x509 certificate from a key pair. | |
| Args: | |
| privkey_pem: The private key PEM. | |
| pubkey_pem: The public key PEM. | |
| Returns: | |
| The certificate PEM. | |
| """ | |
| # Convert key pair | |
| privkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey_pem) | |
| pubkey = crypto.load_publickey(crypto.FILETYPE_PEM, pubkey_pem) | |
| # New x509v3 certificate | |
| cert = crypto.X509() | |
| cert.set_version(0x2) | |
| # Serial number | |
| cert.set_serial_number(randint(1, 2 ** 64)) | |
| # Before / After | |
| cert.gmtime_adj_notBefore(0) | |
| cert.gmtime_adj_notAfter(4 * (365 * 24 * 60 * 60)) | |
| # Public key | |
| cert.set_pubkey(pubkey) | |
| # Subject name and issueer | |
| cert.get_subject().CN = "U2F emulated" | |
| cert.set_issuer(cert.get_subject()) | |
| # Extensions | |
| cert.add_extensions([ | |
| crypto.X509Extension(b"subjectKeyIdentifier", | |
| False, b"hash", subject=cert), | |
| ]) | |
| cert.add_extensions([ | |
| crypto.X509Extension(b"authorityKeyIdentifier", | |
| False, b"keyid:always", issuer=cert), | |
| ]) | |
| cert.add_extensions([ | |
| crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE") | |
| ]) | |
| # Signature | |
| cert.sign(privkey, 'sha256') | |
| return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) | |
| def generate_setup_dir(dirpath: str) -> None: | |
| """ | |
| Generates the setup directory. | |
| Args: | |
| dirpath: The directory path. | |
| """ | |
| # Key pair | |
| privkey_pem, pubkey_pem = generate_ec_key_pair() | |
| # Certificate | |
| certificate_pem = generate_certificate(privkey_pem, pubkey_pem) | |
| # Entropy | |
| entropy = os.urandom(48) | |
| # Counter | |
| counter = 0 | |
| # Write | |
| write_setup_dir(dirpath, privkey_pem, certificate_pem, entropy, counter) | |
| def main() -> None: | |
| """ | |
| Main function | |
| """ | |
| # Dir path | |
| if len(sys.argv) != 2: | |
| sys.stderr.write(f'Usage: {sys.argv[0]} <setup_dir>\n') | |
| exit(2) | |
| dirpath = sys.argv[1] | |
| # Dir non existence | |
| if os.path.exists(dirpath): | |
| sys.stderr.write(f'Directory: {dirpath} already exists.\n') | |
| exit(1) | |
| generate_setup_dir(dirpath) | |
| if __name__ == '__main__': | |
| main() |