Ejemplo n.º 1
0
 def test_csr_without_sans(self):
     pk = RSAPrivateKey()
     pk.generate()
     csr = CertificateSigningRequest(
         private_key=pk,
         common_name="acmechief.test",
         sans=(),
     )
     self.assertFalse(csr.wildcard)
Ejemplo n.º 2
0
    def test_key_generation(self):
        rsa_pk = RSAPrivateKey()
        rsa_pk.generate()
        ec_pk = ECPrivateKey()
        ec_pk.generate()

        self.assertIsInstance(rsa_pk.key, rsa.RSAPrivateKey)
        self.assertEqual(rsa_pk.key.key_size, DEFAULT_RSA_KEY_SIZE)
        self.assertIsInstance(ec_pk.key, ec.EllipticCurvePrivateKey)
        self.assertIsInstance(ec_pk.key.curve, DEFAULT_EC_CURVE)
Ejemplo n.º 3
0
    def test_key_generation_non_default_values(self):
        rsa_pk = RSAPrivateKey()
        rsa_pk.generate(size=4096)
        ec_pk = ECPrivateKey()
        ec_pk.generate(curve=ec.SECP521R1)

        self.assertIsInstance(rsa_pk.key, rsa.RSAPrivateKey)
        self.assertEqual(rsa_pk.key.key_size, 4096)

        self.assertIsInstance(ec_pk.key, ec.EllipticCurvePrivateKey)
        self.assertIsInstance(ec_pk.key.curve, ec.SECP521R1)
Ejemplo n.º 4
0
def get_self_signed_certificate(from_date, until_date):
    pk = RSAPrivateKey()
    pk.generate()
    return SelfSignedCertificate(
        private_key=pk,
        common_name="acmechief.test",
        sans=(
            '*.acmechief.test',
            ipaddress.IPv4Address('127.0.0.1'),
            ipaddress.IPv6Address('::1'),
        ),
        from_date=from_date,
        until_date=until_date,
    )
Ejemplo n.º 5
0
 def __init__(self,
              *,
              key=None,
              regr=None,
              base_path=BASEPATH,
              directory_url=DIRECTORY_URL):
     self.base_path = base_path
     self.directory_url = directory_url
     if key is not None:
         self.key = key
     else:
         self.key = RSAPrivateKey()
         self.key.generate()
     self.regr = regr
     self.account_id = hashlib.md5(self.key.public_pem).hexdigest()
Ejemplo n.º 6
0
    def test_full_workflow_dns_challenge(self):
        """Expects pebble to be invoked with PEBBLE_VA_ALWAYS_VALID=1"""
        with mock.patch('acme_chief.acme_requests.TLS_VERIFY', False):
            account = ACMEAccount.create('*****@*****.**',
                                         directory_url=DIRECTORY_URL)
            session = ACMERequests(account)
        self.assertIsNotNone(account.regr)

        key = RSAPrivateKey()
        key.generate()
        csr = CertificateSigningRequest(
            private_key=key,
            common_name="tests.wmflabs.org",
            sans=[
                "tests.wmflabs.org",
                "*.tests.wmflabs.org",
            ],
        )

        challenges = session.push_csr(csr)
        self.assertIn(ACMEChallengeType.DNS01, challenges)
        self.assertNotIn(ACMEChallengeType.HTTP01, challenges)

        session.push_solved_challenges(csr.csr_id)
        # pebble adds a random delay on validations, so we need to pull until we
        # get the certificate
        while True:
            self.assertTrue(session.orders)
            try:
                session.finalize_order(csr.csr_id)
                certificate = session.get_certificate(csr.csr_id)
                break
            except ACMEChallengeNotValidatedError:
                pass
        self.assertFalse(session.orders)
        self.assertFalse(certificate.needs_renew())
        self.assertFalse(certificate.self_signed)
        self.assertEqual(len(certificate.chain), 2)
        sans = certificate.certificate.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
        self.assertEqual(sans.value.get_values_for_type(crypto_x509.DNSName),
                         ['tests.wmflabs.org', '*.tests.wmflabs.org'])

        # if everything goes as expected this shouldn't raise an exception
        session.revoke_certificate(certificate)
Ejemplo n.º 7
0
    def _generate_pebble_config(config_dir, http_port):
        cert_path = os.path.join(config_dir, 'pebble.key')
        key_path = os.path.join(config_dir, 'pebble.pem')
        config_path = os.path.join(config_dir, 'pebble.json')
        key = RSAPrivateKey()
        key.generate()
        key.save(key_path)

        cert = SelfSignedCertificate(
            private_key=key,
            common_name='localhost',
            sans=[
                '127.0.0.1',
                'pebble',
                'localhost',
            ],
            from_date=datetime.utcnow(),
            until_date=datetime.utcnow() + timedelta(days=1),
        )
        cert.save(cert_path)

        config = {
            'pebble': {
                'listenAddress': PEBBLE_LISTEN_ADDRESS,
                'certificate': cert_path,
                'privateKey': key_path,
                'httpPort': http_port,  # only used when PEBBLE_VA_ALWAYS_VALID=0
                'tlsPort': 5001,        # only used when PEBBLE_VA_ALWAYS_VALID=0
            }
        }
        with open(config_path, 'w') as config_file:
            json.dump(config, config_file)
        return config_path
Ejemplo n.º 8
0
    def test_key_saving(self):
        pk = RSAPrivateKey()
        pk.generate()
        with tempfile.TemporaryDirectory() as tmpdir:
            pk.save(os.path.join(tmpdir, 'rsa.pem'))
            pk_stat = os.stat(os.path.join(tmpdir, 'rsa.pem'))

        # check private key file permissions
        self.assertEqual(stat.S_IMODE(pk_stat.st_mode), OPENER_MODE)
Ejemplo n.º 9
0
    def test_CSR_generation(self):
        pk = RSAPrivateKey()
        pk.generate()
        csr_sans = (
            '*.acmechief.test',
            ipaddress.IPv4Address('127.0.0.1'),
            ipaddress.IPv6Address('::1'),
        )
        csr = CertificateSigningRequest(
            private_key=pk,
            common_name="acmechief.test",
            sans=csr_sans,
        )

        self.assertIsInstance(csr.request,
                              crypto_x509.CertificateSigningRequest)
        self.assertIsInstance(csr.request.signature_hash_algorithm,
                              hashes.SHA256)
        self.assertTrue(csr.request.is_signature_valid)
        cn = csr.request.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
        self.assertEqual(len(cn), 1)
        self.assertEqual(cn[0].value, "acmechief.test")
        sans = csr.request.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
        self.assertEqual(sans.value.get_values_for_type(crypto_x509.DNSName),
                         ['*.acmechief.test'])
        self.assertEqual(
            sans.value.get_values_for_type(crypto_x509.IPAddress),
            [ipaddress.IPv4Address('127.0.0.1'),
             ipaddress.IPv6Address('::1')])
        self.assertEqual(
            csr.csr_id,
            CertificateSigningRequest.generate_csr_id(pk.public_pem,
                                                      'acmechief.test',
                                                      csr_sans))
        self.assertTrue(csr.wildcard)
Ejemplo n.º 10
0
class ACMEAccount:
    """"ACMEv2 account management
    heavily based on https://github.com/certbot/certbot/blob/master/certbot/account.py
    """
    def __init__(self,
                 *,
                 key=None,
                 regr=None,
                 base_path=BASEPATH,
                 directory_url=DIRECTORY_URL):
        self.base_path = base_path
        self.directory_url = directory_url
        if key is not None:
            self.key = key
        else:
            self.key = RSAPrivateKey()
            self.key.generate()
        self.regr = regr
        self.account_id = hashlib.md5(self.key.public_pem).hexdigest()

    @staticmethod
    def _get_acme_client(jkey, regr=None, directory_url=DIRECTORY_URL):
        net = client.ClientNetwork(key=jkey,
                                   account=regr,
                                   verify_ssl=TLS_VERIFY)
        try:
            directory = messages.Directory.from_json(
                net.get(directory_url).json())
        except (errors.Error, ValueError) as dir_error:
            raise ACMEError('Unable to fetch directory URLs') from dir_error

        return ACMEClient(directory, net)

    @staticmethod
    def _get_paths(account_id, base_path=BASEPATH, create_directory=False):
        directory_name = os.path.join(base_path, account_id)
        if create_directory:
            os.makedirs(directory_name, mode=0o700, exist_ok=True)

        return {
            account_file: os.path.join(directory_name, account_file.value)
            for account_file in ACMEAccountFiles
        }

    @property
    def client(self):
        """Return an acme.client.ClientV2 for the current ACMEAccount"""
        return self._get_acme_client(self.jkey,
                                     self.regr,
                                     directory_url=self.directory_url)

    @property
    def jkey(self):
        """Return a JOSE JWKRSA instance of the account key"""
        return jose.JWKRSA(key=self.key.key)

    @classmethod
    def create(cls, email, base_path=BASEPATH, directory_url=DIRECTORY_URL):
        """Creates a new ACME Account using the specified email as point of contact"""
        ret = ACMEAccount(base_path=base_path, directory_url=directory_url)
        new_reg = messages.NewRegistration.from_data(
            email=email, terms_of_service_agreed=True)
        acme = cls._get_acme_client(ret.jkey, directory_url=directory_url)
        try:
            regr = acme.new_account(new_reg)
        except errors.Error as account_error:
            raise ACMEError('Unable to create ACME account') from account_error
        except requests.exceptions.RequestException as request_error:
            raise ACMETransportError(
                'Unable to create ACME account') from request_error

        ret.regr = messages.RegistrationResource(body=regr.body, uri=regr.uri)

        return ret

    @classmethod
    def load(cls, account_id, base_path=BASEPATH, directory_url=DIRECTORY_URL):
        """Load the account with the specified account_id from disk"""
        logger.debug("Loading ACME account %s from directory: %s", account_id,
                     directory_url)
        paths = ACMEAccount._get_paths(account_id, base_path=base_path)

        key = PrivateKeyLoader.load(paths[ACMEAccountFiles.KEY])
        with open(paths[ACMEAccountFiles.REGR], 'r') as regr_file:
            regr = messages.RegistrationResource.json_loads(regr_file.read())

        return ACMEAccount(key=key,
                           regr=regr,
                           base_path=base_path,
                           directory_url=directory_url)

    def save(self):
        """Stores the account on disk to be used in the future"""
        paths = ACMEAccount._get_paths(self.account_id,
                                       base_path=self.base_path,
                                       create_directory=True)
        self.key.save(paths[ACMEAccountFiles.KEY])
        with open(paths[ACMEAccountFiles.REGR], 'w',
                  opener=secure_opener) as regr_file:
            regr_file.write(self.regr.json_dumps())