u2f-setup-gen.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. import sys
  12. import os
  13. from random import randint
  14. from typing import Tuple
  15. from cryptography.hazmat.backends import default_backend
  16. from cryptography.hazmat.primitives.asymmetric import ec
  17. from cryptography.hazmat.primitives.serialization import Encoding, \
  18. NoEncryption, PrivateFormat, PublicFormat
  19. from OpenSSL import crypto
  20. def write_setup_dir(dirpath: str, privkey_pem: bytes, cert_pem: bytes,
  21. entropy: bytes, counter: int) -> None:
  22. """
  23. Write the setup directory.
  24. Args:
  25. dirpath: The directory path.
  26. key_pem: The private key PEM.
  27. cert_pem: The certificate PEM.
  28. entropy: The 48 bytes of entropy.
  29. counter: The counter value.
  30. """
  31. # Directory
  32. os.mkdir(dirpath)
  33. # Private key
  34. with open(f'{dirpath}/private-key.pem', 'bw') as f:
  35. f.write(privkey_pem)
  36. # Certificate
  37. with open(f'{dirpath}/certificate.pem', 'bw') as f:
  38. f.write(cert_pem)
  39. # Entropy
  40. with open(f'{dirpath}/entropy', 'wb') as f:
  41. f.write(entropy)
  42. # Counter
  43. with open(f'{dirpath}/counter', 'w') as f:
  44. f.write(f'{str(counter)}\n')
  45. def generate_ec_key_pair() -> Tuple[str, str]:
  46. """
  47. Generate an ec key pair.
  48. Returns:
  49. The private and public key PEM.
  50. """
  51. # Key generation
  52. privkey = ec.generate_private_key(ec.SECP256R1, default_backend())
  53. pubkey = privkey.public_key()
  54. # PEM serialization
  55. privkey_pem = privkey.private_bytes(encoding=Encoding.PEM,
  56. format=PrivateFormat.TraditionalOpenSSL,
  57. encryption_algorithm=NoEncryption())
  58. pubkey_pem = pubkey.public_bytes(encoding=Encoding.PEM,
  59. format=PublicFormat.SubjectPublicKeyInfo)
  60. return privkey_pem, pubkey_pem
  61. def generate_certificate(privkey_pem: str, pubkey_pem: str) -> str:
  62. """
  63. Generate a x509 certificate from a key pair.
  64. Args:
  65. privkey_pem: The private key PEM.
  66. pubkey_pem: The public key PEM.
  67. Returns:
  68. The certificate PEM.
  69. """
  70. # Convert key pair
  71. privkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey_pem)
  72. pubkey = crypto.load_publickey(crypto.FILETYPE_PEM, pubkey_pem)
  73. # New x509v3 certificate
  74. cert = crypto.X509()
  75. cert.set_version(0x2)
  76. # Serial number
  77. cert.set_serial_number(randint(1, 2 ** 64))
  78. # Before / After
  79. cert.gmtime_adj_notBefore(0)
  80. cert.gmtime_adj_notAfter(4 * (365 * 24 * 60 * 60))
  81. # Public key
  82. cert.set_pubkey(pubkey)
  83. # Subject name and issueer
  84. cert.get_subject().CN = "U2F emulated"
  85. cert.set_issuer(cert.get_subject())
  86. # Extensions
  87. cert.add_extensions([
  88. crypto.X509Extension(b"subjectKeyIdentifier",
  89. False, b"hash", subject=cert),
  90. ])
  91. cert.add_extensions([
  92. crypto.X509Extension(b"authorityKeyIdentifier",
  93. False, b"keyid:always", issuer=cert),
  94. ])
  95. cert.add_extensions([
  96. crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE")
  97. ])
  98. # Signature
  99. cert.sign(privkey, 'sha256')
  100. return crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
  101. def generate_setup_dir(dirpath: str) -> None:
  102. """
  103. Generates the setup directory.
  104. Args:
  105. dirpath: The directory path.
  106. """
  107. # Key pair
  108. privkey_pem, pubkey_pem = generate_ec_key_pair()
  109. # Certificate
  110. certificate_pem = generate_certificate(privkey_pem, pubkey_pem)
  111. # Entropy
  112. entropy = os.urandom(48)
  113. # Counter
  114. counter = 0
  115. # Write
  116. write_setup_dir(dirpath, privkey_pem, certificate_pem, entropy, counter)
  117. def main() -> None:
  118. """
  119. Main function
  120. """
  121. # Dir path
  122. if len(sys.argv) != 2:
  123. sys.stderr.write(f'Usage: {sys.argv[0]} <setup_dir>\n')
  124. exit(2)
  125. dirpath = sys.argv[1]
  126. # Dir non existence
  127. if os.path.exists(dirpath):
  128. sys.stderr.write(f'Directory: {dirpath} already exists.\n')
  129. exit(1)
  130. generate_setup_dir(dirpath)
  131. if __name__ == '__main__':
  132. main()