123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- #!/usr/bin/env python3
- """
- create a shadowed-private-key in sexp format
- for gnupg's private-keys-v1.d folder
- containing the public key of a
- X.509 certificate signing request or a public key
- """
- import ctypes
- import math
- import binascii
- import cryptography.hazmat.backends
- import cryptography.hazmat.primitives.serialization
- import cryptography.x509
- DEFAULT_KEY_OUTPUT_PATH_PATTERN = '{keygrip_hex}.key'
- DEFAULT_SMARTCARD_APP_ID_HEX = 'D2760001240102010001234567890000'
- gcrypt = ctypes.cdll.LoadLibrary('libgcrypt.so')
- gcrypt.gcry_pk_get_keygrip.restype = ctypes.POINTER(ctypes.c_char)
- def convert_to_sexp(data):
- if isinstance(data, int):
- return convert_to_sexp(data.to_bytes(
- math.ceil(data.bit_length() / 8),
- 'big',
- ))
- elif isinstance(data, str):
- return convert_to_sexp(data.encode())
- elif isinstance(data, bytes):
- return str(len(data)).encode() + b':' + data
- else:
- return b'(' + b''.join(convert_to_sexp(i) for i in data) + b')'
- def keygrip_from_key_sexp(key_sexp_data):
- sexp = ctypes.c_void_p()
- assert not gcrypt.gcry_sexp_new(
- ctypes.pointer(sexp),
- key_sexp_data,
- len(key_sexp_data),
- 0,
- )
- keygrip = gcrypt.gcry_pk_get_keygrip(sexp, None)[:20]
- gcrypt.gcry_sexp_release(sexp)
- return keygrip
- def load_public_key(input_path):
- backend = cryptography.hazmat.backends.default_backend()
- with open(input_path, 'rb') as input_file:
- input_data = input_file.read()
- try:
- return cryptography.hazmat.primitives.serialization.load_pem_public_key(
- input_data,
- backend,
- )
- except ValueError:
- req = cryptography.x509.load_pem_x509_csr(input_data, backend)
- assert req.is_signature_valid
- return req.public_key()
- def create_gpg_key(input_path, gpg_key_output_path_pattern,
- smartcard_app_id_hex, keygrip_hex_output_path):
- pubnums = load_public_key(input_path).public_numbers()
- key_data = ['shadowed-private-key', [
- 'rsa',
- ['n', pubnums.n],
- ['e', pubnums.e],
- ['shadowed', 't1-v1', [int(smartcard_app_id_hex, 16), 'OPENPGP.1']],
- ]]
- key_sexp_data = convert_to_sexp(key_data)
- keygrip = keygrip_from_key_sexp(key_sexp_data)
- keygrip_hex = binascii.hexlify(keygrip).upper().decode()
- if keygrip_hex_output_path:
- with open(keygrip_hex_output_path, 'w') as keygrip_file:
- keygrip_file.write(keygrip_hex + '\n')
- with open(gpg_key_output_path_pattern.format(keygrip_hex=keygrip_hex), 'wb') as sexp_file:
- sexp_file.write(key_sexp_data)
- def _init_argparser():
- import argparse
- argparser = argparse.ArgumentParser(
- description='create a shadowed-private-key in sexp format'
- ' for gnupg\'s private-keys-v1.d folder'
- ' containing the public key of a'
- ' PEM-encoded X.509 certificate signing request (CSR)',
- )
- argparser.add_argument(
- 'input_path',
- help='path to PEM-encoded X.509 signing request or public key',
- )
- argparser.add_argument(
- '--gpg-key-output-path',
- dest='gpg_key_output_path_pattern',
- metavar='path-pattern',
- default=DEFAULT_KEY_OUTPUT_PATH_PATTERN,
- help='path to sexp-encoded shadowed-private-key to be created.'
- ' if specified, {keygrip_hex} will be substituted.'
- ' (default: "%(default)s")',
- )
- argparser.add_argument(
- '--smartcard-app-id',
- dest='smartcard_app_id_hex',
- metavar='hex-string',
- default=DEFAULT_SMARTCARD_APP_ID_HEX,
- help='default: %(default)s',
- )
- argparser.add_argument(
- '--keygrip-hex-output-path',
- metavar='path',
- default=None,
- help='path where to save keygrip in hex format'
- ' (default: no output)',
- )
- return argparser
- def main(argv):
- argparser = _init_argparser()
- args = argparser.parse_args(argv[1:])
- create_gpg_key(**vars(args))
- return 0
- if __name__ == '__main__':
- import sys
- sys.exit(main(sys.argv))
|