create-gpg-shadow-key-from-x509-cert-req.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #!/usr/bin/env python3
  2. import binascii
  3. import cryptography.hazmat.backends
  4. import cryptography.hazmat.primitives.serialization
  5. import cryptography.x509
  6. import ctypes
  7. import math
  8. DEFAULT_KEY_OUTPUT_PATH_PATTERN = '{keygrip_hex}.key'
  9. DEFAULT_SMARTCARD_APP_ID_HEX = 'D2760001240102010001234567890000'
  10. gcrypt = ctypes.cdll.LoadLibrary('libgcrypt.so')
  11. gcrypt.gcry_pk_get_keygrip.restype = ctypes.POINTER(ctypes.c_char)
  12. def convert_to_sexp(data):
  13. if isinstance(data, int):
  14. return convert_to_sexp(data.to_bytes(
  15. math.ceil(data.bit_length() / 8),
  16. 'big',
  17. ))
  18. elif isinstance(data, str):
  19. return convert_to_sexp(data.encode())
  20. elif isinstance(data, bytes):
  21. return str(len(data)).encode() + b':' + data
  22. else:
  23. return b'(' + b''.join(convert_to_sexp(i) for i in data) + b')'
  24. def keygrip_from_key_sexp(key_sexp_data):
  25. sexp = ctypes.c_void_p()
  26. assert not gcrypt.gcry_sexp_new(
  27. ctypes.pointer(sexp),
  28. key_sexp_data,
  29. len(key_sexp_data),
  30. 0,
  31. )
  32. keygrip = gcrypt.gcry_pk_get_keygrip(sexp, None)[:20]
  33. gcrypt.gcry_sexp_release(sexp)
  34. return keygrip
  35. def create_gpg_key(input_path, gpg_key_output_path_pattern, smartcard_app_id_hex):
  36. backend = cryptography.hazmat.backends.default_backend()
  37. with open(input_path, 'rb') as f:
  38. req = cryptography.x509.load_pem_x509_csr(f.read(), backend)
  39. assert req.is_signature_valid
  40. pubnums = req.public_key().public_numbers()
  41. key_data = ['shadowed-private-key', [
  42. 'rsa',
  43. ['n', pubnums.n],
  44. ['e', pubnums.e],
  45. ['shadowed', 't1-v1', [int(smartcard_app_id_hex, 16), 'OPENPGP.1']],
  46. ]]
  47. key_sexp_data = convert_to_sexp(key_data)
  48. keygrip = keygrip_from_key_sexp(key_sexp_data)
  49. keygrip_hex = binascii.hexlify(keygrip).upper().decode()
  50. with open(gpg_key_output_path_pattern.format(keygrip_hex=keygrip_hex), 'wb') as f:
  51. f.write(key_sexp_data)
  52. def _init_argparser():
  53. import argparse
  54. argparser = argparse.ArgumentParser(
  55. description='create a shadowed-private-key in sexp format for gnupg\'s private-keys-v1.d folder'
  56. + ' containing the public key of a PEM-encoded X.509 certificate signing request (CSR)',
  57. )
  58. argparser.add_argument(
  59. 'input_path',
  60. help='path to PEM-encoded X.509 signing request',
  61. )
  62. argparser.add_argument(
  63. '--gpg-key-output-path',
  64. dest='gpg_key_output_path_pattern',
  65. metavar='path-pattern',
  66. default=DEFAULT_KEY_OUTPUT_PATH_PATTERN,
  67. help='path to sexp-encoded shadowed-private-key to be created.'
  68. + ' if specified, {keygrip_hex} will be substituted.'
  69. + ' (default: "%(default)s")',
  70. )
  71. argparser.add_argument(
  72. '--smartcard-app-id',
  73. dest='smartcard_app_id_hex',
  74. metavar='hex-string',
  75. default=DEFAULT_SMARTCARD_APP_ID_HEX,
  76. help='default: %(default)s',
  77. )
  78. return argparser
  79. def main(argv):
  80. argparser = _init_argparser()
  81. args = argparser.parse_args(argv[1:])
  82. create_gpg_key(**vars(args))
  83. return 0
  84. if __name__ == '__main__':
  85. import sys
  86. sys.exit(main(sys.argv))