示例#1
0
    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
示例#2
0
 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
示例#3
0
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')
示例#4
0
 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)