3 # Libu2f-emu setup directory generator for USB U2F key emulation.
5 # Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr>
6 # Written by César Belley <cesar.belley@lse.epita.fr>
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.
14 from random
import randint
15 from typing
import Tuple
17 from cryptography
.hazmat
.backends
import default_backend
18 from cryptography
.hazmat
.primitives
.asymmetric
import ec
19 from cryptography
.hazmat
.primitives
.serialization
import Encoding
, \
20 NoEncryption
, PrivateFormat
, PublicFormat
21 from OpenSSL
import crypto
24 def write_setup_dir(dirpath
: str, privkey_pem
: bytes
, cert_pem
: bytes
,
25 entropy
: bytes
, counter
: int) -> None:
27 Write the setup directory.
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.
40 with
open(f
'{dirpath}/private-key.pem', 'bw') as f
:
44 with
open(f
'{dirpath}/certificate.pem', 'bw') as f
:
48 with
open(f
'{dirpath}/entropy', 'wb') as f
:
52 with
open(f
'{dirpath}/counter', 'w') as f
:
53 f
.write(f
'{str(counter)}\n')
56 def generate_ec_key_pair() -> Tuple
[str, str]:
58 Generate an ec key pair.
61 The private and public key PEM.
64 privkey
= ec
.generate_private_key(ec
.SECP256R1
, default_backend())
65 pubkey
= privkey
.public_key()
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
76 def generate_certificate(privkey_pem
: str, pubkey_pem
: str) -> str:
78 Generate a x509 certificate from a key pair.
81 privkey_pem: The private key PEM.
82 pubkey_pem: The public key PEM.
88 privkey
= crypto
.load_privatekey(crypto
.FILETYPE_PEM
, privkey_pem
)
89 pubkey
= crypto
.load_publickey(crypto
.FILETYPE_PEM
, pubkey_pem
)
91 # New x509v3 certificate
96 cert
.set_serial_number(randint(1, 2 ** 64))
99 cert
.gmtime_adj_notBefore(0)
100 cert
.gmtime_adj_notAfter(4 * (365 * 24 * 60 * 60))
103 cert
.set_pubkey(pubkey
)
105 # Subject name and issueer
106 cert
.get_subject().CN
= "U2F emulated"
107 cert
.set_issuer(cert
.get_subject())
110 cert
.add_extensions([
111 crypto
.X509Extension(b
"subjectKeyIdentifier",
112 False, b
"hash", subject
=cert
),
114 cert
.add_extensions([
115 crypto
.X509Extension(b
"authorityKeyIdentifier",
116 False, b
"keyid:always", issuer
=cert
),
118 cert
.add_extensions([
119 crypto
.X509Extension(b
"basicConstraints", True, b
"CA:TRUE")
123 cert
.sign(privkey
, 'sha256')
125 return crypto
.dump_certificate(crypto
.FILETYPE_PEM
, cert
)
128 def generate_setup_dir(dirpath
: str) -> None:
130 Generates the setup directory.
133 dirpath: The directory path.
136 privkey_pem
, pubkey_pem
= generate_ec_key_pair()
139 certificate_pem
= generate_certificate(privkey_pem
, pubkey_pem
)
142 entropy
= os
.urandom(48)
148 write_setup_dir(dirpath
, privkey_pem
, certificate_pem
, entropy
, counter
)
156 if len(sys
.argv
) != 2:
157 sys
.stderr
.write(f
'Usage: {sys.argv[0]} <setup_dir>\n')
159 dirpath
= sys
.argv
[1]
162 if os
.path
.exists(dirpath
):
163 sys
.stderr
.write(f
'Directory: {dirpath} already exists.\n')
166 generate_setup_dir(dirpath
)
169 if __name__
== '__main__':