#!/usr/bin/env python3 import binascii import cryptography.hazmat.backends import cryptography.hazmat.primitives.serialization import cryptography.x509 import ctypes import math 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 create_gpg_key(input_path, gpg_key_output_path_pattern, smartcard_app_id_hex): backend = cryptography.hazmat.backends.default_backend() with open(input_path, 'rb') as f: req = cryptography.x509.load_pem_x509_csr(f.read(), backend) assert req.is_signature_valid pubnums = req.public_key().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() with open(gpg_key_output_path_pattern.format(keygrip_hex=keygrip_hex), 'wb') as f: f.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', ) 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', ) 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))