def get_cert_days(self, cert_filename=None, cert_content=None, now=None): ''' Return the days the certificate in cert_filename remains valid and -1 if the file was not found. If cert_filename contains more than one certificate, only the first one will be considered. If now is not specified, datetime.datetime.now() is used. ''' if cert_filename is not None: cert_content = None if os.path.exists(cert_filename): cert_content = read_file(cert_filename) else: cert_content = to_bytes(cert_content) if cert_content is None: return -1 try: cert = cryptography.x509.load_pem_x509_certificate( cert_content, _cryptography_backend) except Exception as e: if cert_filename is None: raise BackendException( 'Cannot parse certificate: {0}'.format(e)) raise BackendException('Cannot parse certificate {0}: {1}'.format( cert_filename, e)) if now is None: now = datetime.datetime.now() return (cert.not_valid_after - now).days
def get_csr_identifiers(self, csr_filename=None, csr_content=None): ''' Return a set of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. ''' identifiers = set([]) if csr_content is None: csr_content = read_file(csr_filename) else: csr_content = to_bytes(csr_content) csr = cryptography.x509.load_pem_x509_csr(csr_content, _cryptography_backend) for sub in csr.subject: if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME: identifiers.add(('dns', sub.value)) for extension in csr.extensions: if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME: for name in extension.value: if isinstance(name, cryptography.x509.DNSName): identifiers.add(('dns', name.value)) elif isinstance(name, cryptography.x509.IPAddress): identifiers.add(('ip', name.value.compressed)) else: raise BackendException( 'Found unsupported SAN identifier {0}'.format( name)) return identifiers
def test_read_file(tmpdir): fn = tmpdir / 'test.txt' fn.write(TEST_TEXT) assert read_file(str(fn), 't') == TEST_TEXT assert read_file(str(fn), 'b') == TEST_TEXT.encode('utf-8')
def parse_key(self, key_file=None, key_content=None, passphrase=None): ''' Parses an RSA or Elliptic Curve key file in PEM format and returns key_data. Raises KeyParsingError in case of errors. ''' # If key_content isn't given, read key_file if key_content is None: key_content = read_file(key_file) else: key_content = to_bytes(key_content) # Parse key try: key = cryptography.hazmat.primitives.serialization.load_pem_private_key( key_content, password=to_bytes(passphrase) if passphrase is not None else None, backend=_cryptography_backend) except Exception as e: raise KeyParsingError('error while loading key: {0}'.format(e)) if isinstance( key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): pk = key.public_key().public_numbers() return { 'key_obj': key, 'type': 'rsa', 'alg': 'RS256', 'jwk': { "kty": "RSA", "e": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.e), pk.e)), "n": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.n), pk.n)), }, 'hash': 'sha256', } elif isinstance( key, cryptography.hazmat.primitives.asymmetric.ec. EllipticCurvePrivateKey): pk = key.public_key().public_numbers() if pk.curve.name == 'secp256r1': bits = 256 alg = 'ES256' hashalg = 'sha256' point_size = 32 curve = 'P-256' elif pk.curve.name == 'secp384r1': bits = 384 alg = 'ES384' hashalg = 'sha384' point_size = 48 curve = 'P-384' elif pk.curve.name == 'secp521r1': # Not yet supported on Let's Encrypt side, see # https://github.com/letsencrypt/boulder/issues/2217 bits = 521 alg = 'ES512' hashalg = 'sha512' point_size = 66 curve = 'P-521' else: raise KeyParsingError('unknown elliptic curve: {0}'.format( pk.curve.name)) num_bytes = (bits + 7) // 8 return { 'key_obj': key, 'type': 'ec', 'alg': alg, 'jwk': { "kty": "EC", "crv": curve, "x": nopad_b64(_convert_int_to_bytes(num_bytes, pk.x)), "y": nopad_b64(_convert_int_to_bytes(num_bytes, pk.y)), }, 'hash': hashalg, 'point_size': point_size, } else: raise KeyParsingError('unknown key type "{0}"'.format(type(key)))
def main(): module = AnsibleModule( argument_spec=dict( challenge=dict(type='str', required=True, choices=['tls-alpn-01']), challenge_data=dict(type='dict', required=True), private_key_src=dict(type='path'), private_key_content=dict(type='str', no_log=True), private_key_passphrase=dict(type='str', no_log=True), ), required_one_of=(['private_key_src', 'private_key_content'], ), mutually_exclusive=(['private_key_src', 'private_key_content'], ), ) if not HAS_CRYPTOGRAPHY: module.fail_json(msg=missing_required_lib('cryptography >= 1.3'), exception=CRYPTOGRAPHY_IMP_ERR) try: # Get parameters challenge = module.params['challenge'] challenge_data = module.params['challenge_data'] # Get hold of private key private_key_content = module.params.get('private_key_content') private_key_passphrase = module.params.get('private_key_passphrase') if private_key_content is None: private_key_content = read_file(module.params['private_key_src']) else: private_key_content = to_bytes(private_key_content) try: private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key( private_key_content, password=to_bytes(private_key_passphrase) if private_key_passphrase is not None else None, backend=_cryptography_backend) except Exception as e: raise ModuleFailException( 'Error while loading private key: {0}'.format(e)) # Some common attributes domain = to_text(challenge_data['resource']) identifier_type, identifier = to_text( challenge_data.get('resource_original', 'dns:' + challenge_data['resource'])).split( ':', 1) subject = issuer = cryptography.x509.Name([]) not_valid_before = datetime.datetime.utcnow() not_valid_after = datetime.datetime.utcnow() + datetime.timedelta( days=10) if identifier_type == 'dns': san = cryptography.x509.DNSName(identifier) elif identifier_type == 'ip': san = cryptography.x509.IPAddress(ipaddress.ip_address(identifier)) else: raise ModuleFailException( 'Unsupported identifier type "{0}"'.format(identifier_type)) # Generate regular self-signed certificate regular_certificate = cryptography.x509.CertificateBuilder( ).subject_name(subject).issuer_name(issuer).public_key( private_key.public_key()).serial_number( cryptography.x509.random_serial_number()).not_valid_before( not_valid_before).not_valid_after( not_valid_after).add_extension( cryptography.x509.SubjectAlternativeName([san]), critical=False, ).sign(private_key, cryptography.hazmat.primitives.hashes.SHA256(), _cryptography_backend) # Process challenge if challenge == 'tls-alpn-01': value = base64.b64decode(challenge_data['resource_value']) challenge_certificate = cryptography.x509.CertificateBuilder( ).subject_name(subject).issuer_name(issuer).public_key( private_key.public_key()).serial_number( cryptography.x509.random_serial_number()).not_valid_before( not_valid_before).not_valid_after( not_valid_after).add_extension( cryptography.x509.SubjectAlternativeName([san ]), critical=False, ).add_extension( cryptography.x509.UnrecognizedExtension( cryptography.x509.ObjectIdentifier( "1.3.6.1.5.5.7.1.31"), encode_octet_string(value), ), critical=True, ).sign( private_key, cryptography.hazmat.primitives.hashes.SHA256(), _cryptography_backend) module.exit_json( changed=True, domain=domain, identifier_type=identifier_type, identifier=identifier, challenge_certificate=challenge_certificate.public_bytes( cryptography.hazmat.primitives.serialization.Encoding.PEM), regular_certificate=regular_certificate.public_bytes( cryptography.hazmat.primitives.serialization.Encoding.PEM)) except ModuleFailException as e: e.do_fail(module)