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