xref: /qemu/scripts/u2f-setup-gen.py (revision dea01f66)
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