diff --git a/gimmecert/crypto.py b/gimmecert/crypto.py index b78cc505b7e1e76967965902dd2ed5c8eb3752e5..d630119bef6cf940ca7c337081e80b23a48e573c 100644 --- a/gimmecert/crypto.py +++ b/gimmecert/crypto.py @@ -25,6 +25,73 @@ import cryptography.x509 from dateutil.relativedelta import relativedelta +class KeyGenerator: + """ + Provides abstract factory-like interface for generating private + keys. Algorithm and parameters for the private key are provided + during instance initialisation by passing-in a specification. + + Instances are callable objects that generate and return the + private key according to key specification passed-in during the + instance initialisation. + """ + + def __init__(self, specification): + """ + Initialises an instance. + + :param specification: Specification describing the private keys that that instance should be generating. + For RSA keys, use syntax "rsa:BIT_LENGTH". + :type specification: str + + :raises ValueError: If passed-in specification is invalid. + """ + + try: + # This will throw ValueError if we can't get two values + # assigned via split. + key_type, key_parameters = specification.split(":", 2) + + if key_type == "rsa" and key_parameters.isnumeric(): + self._algorithm = "rsa" + self._parameters = int(key_parameters) + else: + raise ValueError() + + except ValueError: + raise ValueError("Invalid key specification: '%s'" % specification) + + def __str__(self): + """ + Returns string (human-readable) representation of stored key + algorithm and parameters. + + :returns: String representation of object. + :rtype: str + """ + + return "%d-bit RSA" % self._parameters + + def __call__(self): + """ + Generates RSA private key. Key size is deterimened by instance's + key specification (passed-in during instance creation). + + :returns: RSA private key. + :rtype: cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey + """ + + rsa_public_exponent = 65537 + + private_key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key( + public_exponent=rsa_public_exponent, + key_size=self._parameters, + backend=cryptography.hazmat.backends.default_backend() + ) + + return private_key + + def generate_private_key(): """ Generates a 2048-bit RSA private key. @@ -136,7 +203,7 @@ def issue_certificate(issuer_dn, subject_dn, signing_key, public_key, not_before return certificate -def generate_ca_hierarchy(base_name, depth): +def generate_ca_hierarchy(base_name, depth, key_generator): """ Generates CA hierarchy with specified depth, using the provided naming as basis for the DNs. @@ -144,6 +211,9 @@ def generate_ca_hierarchy(base_name, depth): :param base_name: Base name for constructing the CA DNs. Resulting DNs are of format 'BASE Level N'. :type base_name: str + :param key_generator: Callable for generating private keys. + :type key_generator: callable[[], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey] + :returns: List of CA private key and certificate pairs, starting with the level 1 (root) CA, and ending with the leaf CA. :rtype: list[(cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey, cryptography.x509.Certificate)] """ @@ -163,7 +233,7 @@ def generate_ca_hierarchy(base_name, depth): for level in range(1, depth+1): # Generate info for the new CA. dn = get_dn("%s Level %d CA" % (base_name, level)) - private_key = generate_private_key() + private_key = key_generator() # First certificate issued needs to be self-signed. issuer_dn = issuer_dn or dn