Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This patch adds the script used to generate setup directories, needed for the device u2f-emulated configuration in directory mode: python u2f-setup-gen.py $DIR qemu -usb -device u2f-emulated,dir=$DIR Signed-off-by: César Belley <cesar.belley@lse.epita.fr> Message-id: 20200826114209.28821-11-cesar.belley@lse.epita.fr Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
- Loading branch information
1 parent
c81737e
commit dea01f6
Showing
1 changed file
with
170 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
#!/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() |