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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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 load_public_key(input_path):
  36. backend = cryptography.hazmat.backends.default_backend()
  37. with open(input_path, 'rb') as f:
  38. input_data = f.read()
  39. try:
  40. return cryptography.hazmat.primitives.serialization.load_pem_public_key(
  41. input_data,
  42. backend,
  43. )
  44. except ValueError:
  45. req = cryptography.x509.load_pem_x509_csr(input_data, backend)
  46. assert req.is_signature_valid
  47. return req.public_key()
  48. def create_gpg_key(input_path, gpg_key_output_path_pattern, smartcard_app_id_hex, keygrip_hex_output_path):
  49. pubnums = load_public_key(input_path).public_numbers()
  50. key_data = ['shadowed-private-key', [
  51. 'rsa',
  52. ['n', pubnums.n],
  53. ['e', pubnums.e],
  54. ['shadowed', 't1-v1', [int(smartcard_app_id_hex, 16), 'OPENPGP.1']],
  55. ]]
  56. key_sexp_data = convert_to_sexp(key_data)
  57. keygrip = keygrip_from_key_sexp(key_sexp_data)
  58. keygrip_hex = binascii.hexlify(keygrip).upper().decode()
  59. if keygrip_hex_output_path:
  60. with open(keygrip_hex_output_path, 'w') as f:
  61. f.write(keygrip_hex + '\n')
  62. with open(gpg_key_output_path_pattern.format(keygrip_hex=keygrip_hex), 'wb') as f:
  63. f.write(key_sexp_data)
  64. def _init_argparser():
  65. import argparse
  66. argparser = argparse.ArgumentParser(
  67. description='create a shadowed-private-key in sexp format for gnupg\'s private-keys-v1.d folder'
  68. + ' containing the public key of a PEM-encoded X.509 certificate signing request (CSR)',
  69. )
  70. argparser.add_argument(
  71. 'input_path',
  72. help='path to PEM-encoded X.509 signing request or public key',
  73. )
  74. argparser.add_argument(
  75. '--gpg-key-output-path',
  76. dest='gpg_key_output_path_pattern',
  77. metavar='path-pattern',
  78. default=DEFAULT_KEY_OUTPUT_PATH_PATTERN,
  79. help='path to sexp-encoded shadowed-private-key to be created.'
  80. + ' if specified, {keygrip_hex} will be substituted.'
  81. + ' (default: "%(default)s")',
  82. )
  83. argparser.add_argument(
  84. '--smartcard-app-id',
  85. dest='smartcard_app_id_hex',
  86. metavar='hex-string',
  87. default=DEFAULT_SMARTCARD_APP_ID_HEX,
  88. help='default: %(default)s',
  89. )
  90. argparser.add_argument(
  91. '--keygrip-hex-output-path',
  92. metavar='path',
  93. default=None,
  94. help='path where to save keygrip in hex format'
  95. + ' (default: no output)',
  96. )
  97. return argparser
  98. def main(argv):
  99. argparser = _init_argparser()
  100. args = argparser.parse_args(argv[1:])
  101. create_gpg_key(**vars(args))
  102. return 0
  103. if __name__ == '__main__':
  104. import sys
  105. sys.exit(main(sys.argv))