Example #1
0
    def setUp(self):
        self.key = jose.JWKRSA(key=KEY)

        from acme.challenges import DNS
        self.chall = DNS(token=jose.b64decode(
            b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"))
        self.validation = jose.JWS.sign(
            payload=self.chall.json_dumps(sort_keys=True).encode(),
            key=self.key, alg=jose.RS256)

        from acme.challenges import DNSResponse
        self.msg = DNSResponse(validation=self.validation)
        self.jmsg_to = {
            'resource': 'challenge',
            'type': 'dns',
            'validation': self.validation,
        }
        self.jmsg_from = {
            'resource': 'challenge',
            'type': 'dns',
            'validation': self.validation.to_json(),
        }
Example #2
0
    def setUp(self):
        from acme.challenges import ProofOfPossession
        hints = ProofOfPossession.Hints(
            jwk=jose.JWKRSA(key=KEY.public_key()), cert_fingerprints=(),
            certs=(), serial_numbers=(), subject_key_identifiers=(),
            issuers=(), authorized_for=())
        self.msg = ProofOfPossession(
            alg=jose.RS256, hints=hints,
            nonce=b'xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ')

        self.jmsg_to = {
            'type': 'proofOfPossession',
            'alg': jose.RS256,
            'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
            'hints': hints,
        }
        self.jmsg_from = {
            'type': 'proofOfPossession',
            'alg': jose.RS256.to_json(),
            'nonce': 'eET5udtV7aoX8Xl8gYiZIA',
            'hints': hints.to_json(),
        }
Example #3
0
    def setUp(self):
        self.sig_nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9'

        signature = other.Signature(
            alg=jose.RS256,
            jwk=jose.JWKRSA(key=KEY.publickey()),
            sig='eJ\xfe\x12"U\x87\x8b\xbf/ ,\xdeP\xb2\xdc1\xb00\xe5\x1dB'
            '\xfch<\xc6\x9eH@!\x1c\x16\xb2\x0b_\xc4\xddP\x89\xc8\xce?'
            '\x16g\x069I\xb9\xb3\x91\xb9\x0e$3\x9f\x87\x8e\x82\xca\xc5'
            's\xd9\xd0\xe7',
            nonce=self.sig_nonce)

        from acme.messages import RevocationRequest
        self.msg = RevocationRequest(certificate=CERT, signature=signature)

        self.jmsg_to = {
            'type': 'revocationRequest',
            'certificate': jose.b64encode(CERT.as_der()),
            'signature': signature,
        }
        self.jmsg_from = self.jmsg_to.copy()
        self.jmsg_from['signature'] = self.jmsg_from['signature'].to_json()
def load_or_generate_private_key(keyfile, log):
    from acme import jose
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives import serialization

    if os.path.exists(keyfile):
        # Load from file.
        log("Reading account key from %s." % keyfile)
        with open(keyfile, 'rb') as f:
            pem = f.read()
            key = serialization.load_pem_private_key(pem,
                                                     password=None,
                                                     backend=default_backend())

    elif not os.path.exists(os.path.dirname(keyfile)):
        # Directory for storage does not exist.
        raise ValueError("The path %s does not exist." %
                         os.path.dirname(keyfile))

    else:
        # Generate new key and write to file.
        log("Generating a new account key.")
        key = rsa.generate_private_key(public_exponent=65537,
                                       key_size=ACCOUNT_KEY_SIZE,
                                       backend=default_backend())

        pem = key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        )
        with open(keyfile, 'wb') as f:
            f.write(pem)

    key = jose.JWKRSA(key=jose.ComparableRSAKey(key))

    return key
Example #5
0
def maybe_key(pem_path):
    """
    Set up a client key if one does not exist already.

    https://gist.github.com/glyph/27867a478bb71d8b6046fbfb176e1a33#file-local-certs-py-L32-L50

    :type pem_path: twisted.python.filepath.FilePath
    :param pem_path:
        The path to the certificate directory to use.
    """
    acme_key_file = pem_path.child(u'client.key')
    if acme_key_file.exists():
        key = serialization.load_pem_private_key(acme_key_file.getContent(),
                                                 password=None,
                                                 backend=default_backend())
    else:
        key = generate_private_key(u'rsa')
        acme_key_file.setContent(
            key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption()))
    return jose.JWKRSA(key=key)
Example #6
0
    def setUp(self):
        jwk = jose.JWKRSA(key=KEY.publickey())
        issuers = (
            'C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA',
            'O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure',
        )
        cert_fingerprints = (
            '93416768eb85e33adc4277f4c9acd63e7418fcfe',
            '16d95b7b63f1972b980b14c20291f3c0d1855d95',
            '48b46570d9fc6358108af43ad1649484def0debf',
        )
        subject_key_identifiers = ('d0083162dcc4c8a23ecb8aecbd86120e56fd24e5')
        authorized_for = ('www.example.com', 'example.net')
        serial_numbers = (34234239832, 23993939911, 17)

        from acme.challenges import ProofOfPossession
        self.msg = ProofOfPossession.Hints(
            jwk=jwk,
            issuers=issuers,
            cert_fingerprints=cert_fingerprints,
            certs=(CERT, ),
            subject_key_identifiers=subject_key_identifiers,
            authorized_for=authorized_for,
            serial_numbers=serial_numbers)

        self.jmsg_to = {
            'jwk': jwk,
            'certFingerprints': cert_fingerprints,
            'certs': (jose.b64encode(CERT.as_der()), ),
            'subjectKeyIdentifiers': subject_key_identifiers,
            'serialNumbers': serial_numbers,
            'issuers': issuers,
            'authorizedFor': authorized_for,
        }
        self.jmsg_from = self.jmsg_to.copy()
        self.jmsg_from.update({'jwk': jwk.to_json()})
Example #7
0
def register(config, account_storage, tos_cb=None):
    """Register new account with an ACME CA.

    This function takes care of generating fresh private key,
    registering the account, optionally accepting CA Terms of Service
    and finally saving the account. It should be called prior to
    initialization of `Client`, unless account has already been created.

    :param .IConfig config: Client configuration.

    :param .AccountStorage account_storage: Account storage where newly
        registered account will be saved to. Save happens only after TOS
        acceptance step, so any account private keys or
        `.RegistrationResource` will not be persisted if `tos_cb`
        returns ``False``.

    :param tos_cb: If ACME CA requires the user to accept a Terms of
        Service before registering account, client action is
        necessary. For example, a CLI tool would prompt the user
        acceptance. `tos_cb` must be a callable that should accept
        `.RegistrationResource` and return a `bool`: ``True`` iff the
        Terms of Service present in the contained
        `.Registration.terms_of_service` is accepted by the client, and
        ``False`` otherwise. ``tos_cb`` will be called only if the
        client acction is necessary, i.e. when ``terms_of_service is not
        None``. This argument is optional, if not supplied it will
        default to automatic acceptance!

    :raises letsencrypt.errors.Error: In case of any client problems, in
        particular registration failure, or unaccepted Terms of Service.
    :raises acme.errors.Error: In case of any protocol problems.

    :returns: Newly registered and saved account, as well as protocol
        API handle (should be used in `Client` initialization).
    :rtype: `tuple` of `.Account` and `acme.client.Client`

    """
    # Log non-standard actions, potentially wrong API calls
    if account_storage.find_all():
        logger.info("There are already existing accounts for %s",
                    config.server)
    if config.email is None:
        logger.warn("Registering without email!")

    # Each new registration shall use a fresh new key
    key = jose.JWKRSA(key=jose.ComparableRSAKey(
        rsa.generate_private_key(public_exponent=65537,
                                 key_size=config.rsa_key_size,
                                 backend=default_backend())))
    acme = _acme_from_config_key(config, key)
    # TODO: add phone?
    regr = acme.register(
        messages.NewRegistration.from_data(email=config.email))

    if regr.terms_of_service is not None:
        if tos_cb is not None and not tos_cb(regr):
            raise errors.Error("Registration cannot proceed without accepting "
                               "Terms of Service.")
        regr = acme.agree_to_tos(regr)

    acc = account.Account(regr, key)
    account.report_new_account(acc, config)
    account_storage.save(acc)
    return acc, acme
Example #8
0
    token="evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA")
DVSNI = challenges.DVSNI(
    r="O*\xb4-\xad\xec\x95>\xed\xa9\r0\x94\xe8\x97\x9c&6\xbf'\xb3"
    "\xed\x9a9nX\x0f'\\m\xe7\x12",
    nonce="a82d5ff8ef740d12881f6d3c2277ab2e")
DNS = challenges.DNS(token="17817c66b60ce2e4012dfad92657527a")
RECOVERY_CONTACT = challenges.RecoveryContact(
    activation_url="https://example.ca/sendrecovery/a5bd99383fb0",
    success_url="https://example.ca/confirmrecovery/bb1b9928932",
    contact="c********[email protected]")
RECOVERY_TOKEN = challenges.RecoveryToken()
POP = challenges.ProofOfPossession(
    alg="RS256",
    nonce="xD\xf9\xb9\xdbU\xed\xaa\x17\xf1y|\x81\x88\x99 ",
    hints=challenges.ProofOfPossession.Hints(
        jwk=jose.JWKRSA(key=KEY.publickey()),
        cert_fingerprints=("93416768eb85e33adc4277f4c9acd63e7418fcfe",
                           "16d95b7b63f1972b980b14c20291f3c0d1855d95",
                           "48b46570d9fc6358108af43ad1649484def0debf"),
        certs=(),  # TODO
        subject_key_identifiers=("d0083162dcc4c8a23ecb8aecbd86120e56fd24e5"),
        serial_numbers=(34234239832, 23993939911, 17),
        issuers=(
            "C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA",
            "O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure",
        ),
        authorized_for=("www.example.com", "example.net"),
    ))

CHALLENGES = [SIMPLE_HTTP, DVSNI, DNS, RECOVERY_CONTACT, RECOVERY_TOKEN, POP]
DV_CHALLENGES = [
Example #9
0
    async def connect(cls,
                      directory: str,
                      key: crypto.PrivateKey,
                      *,
                      loop: _T_AIO_LOOP = None) -> 'AsyncmeClient':
        """
        Connect to an ACME server.

        :param directory: The URI of the ACME server's directory.
        :param key: Private key used for interacting with the ACME server.
        :param loop: Event loop.

        :return: A new instance of ``AsyncmeClient`` which has been
         registered against the given ACME server, if the key has not already
         been registered.
        """

        # Determine the JWS 'alg' Type for the Given Key
        # RSA
        if key.algorithm is crypto.KeyAlgorithmType.RSA:
            if key.size / 8 == 256:
                alg = jose.jwa.RS256
            elif key.size / 8 == 384:
                alg = jose.jwa.RS384
            elif key.size / 8 == 512:
                alg = jose.jwa.RS512
            else:
                raise ValueError("RSA key size of {} "
                                 "is unsupported".format(key.size))

        # ECDSA
        elif key.algorithm is crypto.KeyAlgorithmType.ECDSA:
            if key.size == 256:
                alg = jose.jwa.ES256
            elif key.size == 384:
                alg = jose.jwa.ES384
            elif key.size == 521:
                alg = jose.jwa.ES512
            else:
                raise ValueError("ECDSA key size of {} "
                                 "is unsupported".format(key.size))

        # Other
        else:
            raise ValueError("Unsupported private key type, must "
                             "be RSA or ECDSA")

        # Convert Key
        password = os.urandom(255)
        acme_key = jose.JWKRSA(key=serialization.load_der_private_key(
            key.to_bytes(encoding=crypto.EncodingType.DER, password=password),
            password, default_backend()))

        # Get Async. Loop
        loop = loop or asyncio.get_event_loop()

        # Instantiate Underlying Client
        # (and make the initial connection to the ACME server)
        try:
            client = await ExecutorClient.connect(
                directory=directory,
                key=acme_key,
                alg=alg,
                net=ExtendedNetwork(
                    key=acme_key,
                    alg=alg,
                    user_agent='asyncme-python-{}'.format(ASYNCME_VERSION)),
                loop=loop)
        except TypeError:
            raise ValueError("Invalid ACME directory given")

        # Client Registration
        try:
            regr = await client.register()
        except errors.Error as e:

            # The upstream client currently has no way to "recover" a lost
            # registration resource, thus we must resort to the tactics below.

            if "key is already in use" in str(e):

                LOG.debug("Key already in use: attempting to recover "
                          "registration resource.")

                # (1) Send a Post in Attempt to Register the Client
                #     and Get a 409 Back in Response with Location
                #     Header set.

                post = functools.partial(client.net.post,
                                         client.directory['new-reg'],
                                         messages.NewRegistration(),
                                         checked=False)
                response = await loop.run_in_executor(None, post)
                assert response.status_code == 409

                # (2) Manually create a new Registration Resource and
                #     send an empty registration update.
                #     (per draft-ietf-acme-acme-03#section-6.2)

                regr = messages.RegistrationResource(
                    uri=response.headers.get("location"))

                # Empty Update
                regr = await client._send_recv_regr(
                    regr, body=messages.UpdateRegistration())

            else:
                raise RuntimeError(e)

        return cls(client, regr)
Example #10
0
from acme import client
from acme import messages
from acme import jose


logging.basicConfig(level=logging.DEBUG)


NEW_REG_URL = 'https://www.letsencrypt-demo.org/acme/new-reg'
BITS = 2048  # minimum for Boulder
DOMAIN = 'example1.com'  # example.com is ignored by Boulder

# generate_private_key requires cryptography>=0.5
key = jose.JWKRSA(key=rsa.generate_private_key(
    public_exponent=65537,
    key_size=BITS,
    backend=default_backend()))
acme = client.Client(NEW_REG_URL, key)

regr = acme.register()
logging.info('Auto-accepting TOS: %s', regr.terms_of_service)
acme.update_registration(regr.update(
    body=regr.body.update(agreement=regr.terms_of_service)))
logging.debug(regr)

authzr = acme.request_challenges(
    identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=DOMAIN),
    new_authzr_uri=regr.new_authzr_uri)
logging.debug(authzr)

authzr, authzr_response = acme.poll(authzr)
Example #11
0
def acme_client_for_private_key(acme_directory_url, private_key):
    return Client(
        acme_directory_url, key=jose.JWKRSA(key=private_key)
    )
Example #12
0
def register_account_key(directory_url, accnt_key):
    accnt_key = jose.JWKRSA(key=accnt_key)
    acme = client.Client(directory_url, accnt_key)

    regr = acme.register()
    return regr.uri, regr.terms_of_service
Example #13
0
def run_acme_reg_to_finish(domain, regr_uri, accnt_key, site_key, csr, tmp_chall_dict, directory_url):
    accnt_key = jose.JWKRSA(key=accnt_key)
    acme = client.Client(directory_url, accnt_key)
    msg = messages.RegistrationResource(uri=regr_uri)
    regr = acme.query_registration(msg)

    log.info('Auto-accepting TOS: %s from: %s', regr.terms_of_service, directory_url)
    acme.agree_to_tos(regr)

    authzr = acme.request_challenges(
        identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=domain))
    log.debug('Created auth client %s', authzr)

    def get_http_challenge(x, y):
        return y if isinstance(y.chall, challenges.HTTP01) else x

    challb = reduce(get_http_challenge, authzr.body.challenges, None)
    chall_tok = challb.chall.validation(accnt_key)

    v = chall_tok.split('.')[0]
    log.info('Exposing challenge on %s', v)
    tmp_chall_dict.set(v, ChallTok(chall_tok))

    test_path = 'http://localhost:8082{}'.format(challb.path)
    local_req = Request(test_path, headers={'Host': domain})
    log.debug('Testing local url path: %s', test_path)

    try:
        resp = urlopen(local_req)
        t = resp.read().decode('utf-8').strip()
        if t != chall_tok:
            raise ValueError
    except (IOError, ValueError):
        log.info('Resolving challenge locally failed. ACME request will fail. %s', test_path)
        raise

    cr = acme.answer_challenge(challb, challb.chall.response(accnt_key))
    log.debug('Acme CA responded to challenge request with: %s', cr)

    try:
        # Wrap this step and log the failure particularly here because this is
        # the expected point of failure for applications that are not reachable
        # from the public internet.
        cert_res, _ = acme.poll_and_request_issuance(jose.util.ComparableX509(csr), (authzr,))

        # NOTE pylint disabled due to spurious reporting. See docs:
        # https://letsencrypt.readthedocs.io/projects/acme/en/latest/api/jose/util.html#acme.jose.util.ComparableX509
        # pylint: disable=no-member
        cert_str = cert_res.body._dump(FILETYPE_PEM)
    except messages.Error as error:
        log.err("Failed in request issuance step %s", error)
        raise

    chain_certs = acme.fetch_chain(cert_res)

    # The chain certs returned by the LE CA will always have at least one
    # intermediate cert. Other certificate authorities that run ACME may
    # behave differently, but we aren't using them.
    chain_str = dump_certificate(FILETYPE_PEM, chain_certs[0])

    # pylint: disable=no-member
    expr_date = convert_asn1_date(cert_res.body.wrapped.get_notAfter())
    log.info('Retrieved cert using ACME that expires on %s', expr_date)

    return cert_str, chain_str
Example #14
0
import copy
import os
import re
import shutil
import tarfile

from acme import jose
from acme import test_util
from certbot import constants

from certbot_compatibility_test import errors

_KEY_BASE = "rsa1024_key.pem"
KEY_PATH = test_util.vector_path(_KEY_BASE)
KEY = test_util.load_pyopenssl_private_key(_KEY_BASE)
JWK = jose.JWKRSA(key=test_util.load_rsa_private_key(_KEY_BASE))
IP_REGEX = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")


def create_le_config(parent_dir):
    """Sets up LE dirs in parent_dir and returns the config dict"""
    config = copy.deepcopy(constants.CLI_DEFAULTS)

    le_dir = os.path.join(parent_dir, "certbot")
    config["config_dir"] = os.path.join(le_dir, "config")
    config["work_dir"] = os.path.join(le_dir, "work")
    config["logs_dir"] = os.path.join(le_dir, "logs_dir")
    os.makedirs(config["config_dir"])
    os.mkdir(config["work_dir"])
    os.mkdir(config["logs_dir"])
Example #15
0
def acme_jwk():
    return jose.JWKRSA(key=rsa.generate_private_key(
        public_exponent=65537, key_size=MIN_BITS, backend=default_backend()))
 def setUp(self):
     self.privkey = jose.JWKRSA(key=RSA512_KEY)
     self.pubkey = self.privkey.public()
     self.nonce = jose.b64encode('Nonce')
Example #17
0
 def get_jwk_key(self):
     return jose.JWKRSA(key=self.get_key())
Example #18
0
import mock
import OpenSSL

from acme import challenges
from acme import jose

from letsencrypt import achallenges

from letsencrypt.tests import acme_util

KEY_PATH = pkg_resources.resource_filename(
    "letsencrypt.tests", os.path.join("testdata", "rsa512_key.pem"))
KEY_DATA = pkg_resources.resource_string(
    "letsencrypt.tests", os.path.join("testdata", "rsa512_key.pem"))
KEY = jose.JWKRSA(key=jose.ComparableRSAKey(
    serialization.load_pem_private_key(
        KEY_DATA, password=None, backend=default_backend())))
PRIVATE_KEY = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM,
                                             KEY_DATA)
CONFIG = mock.Mock(dvsni_port=5001)

# Classes based on to allow interrupting infinite loop under test
# after one iteration, based on.
# http://igorsobreira.com/2013/03/17/testing-infinite-loops.html


class _SocketAcceptOnlyNTimes(object):
    # pylint: disable=too-few-public-methods
    """
    Callable that will raise `CallableExhausted`
    exception after `limit` calls, modified to also return
Example #19
0
 def _deserialize_key(self, k):
     return jose.JWKRSA(key=serialization.load_pem_private_key(
         str(k), password=None, backend=default_backend()))
Example #20
0
 def test_gen_response(self):
     key = jose.JWKRSA(key=KEY)
     from acme.challenges import DVSNI
     self.assertEqual(self.msg, DVSNI.json_loads(
         self.msg.gen_response(key).validation.payload.decode()))
Example #21
0
"""Tests for acme.challenges."""
import unittest

import mock
import OpenSSL
import requests

from six.moves.urllib import parse as urllib_parse  # pylint: disable=import-error

from acme import errors
from acme import jose
from acme import test_util

CERT = test_util.load_comparable_cert('cert.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))


class ChallengeTest(unittest.TestCase):
    def test_from_json_unrecognized(self):
        from acme.challenges import Challenge
        from acme.challenges import UnrecognizedChallenge
        chall = UnrecognizedChallenge({"type": "foo"})
        # pylint: disable=no-member
        self.assertEqual(chall, Challenge.from_json(chall.jobj))


class UnrecognizedChallengeTest(unittest.TestCase):
    def setUp(self):
        from acme.challenges import UnrecognizedChallenge
        self.jobj = {"type": "foo"}
        self.chall = UnrecognizedChallenge(self.jobj)
Example #22
0
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

from acme import client as acme_client
from acme import errors as acme_errors
from acme import jose
from acme import messages

DIRECTORY = os.getenv('DIRECTORY', 'http://localhost:4000/directory')

if len(sys.argv) != 2:
    print("Usage: python deactivate.py private_key.pem")
    sys.exit(1)

data = open(sys.argv[1], "r").read()
key = jose.JWKRSA(
    key=serialization.load_pem_private_key(data, None, default_backend()))

net = acme_client.ClientNetwork(key,
                                verify_ssl=False,
                                user_agent="acme account deactivator")

client = acme_client.Client(DIRECTORY, key=key, net=net)
try:
    # We expect this to fail and give us a Conflict response with a Location
    # header pointing at the account's URL.
    client.register()
except acme_errors.ConflictError as e:
    location = e.location
if location is None:
    raise "Key was not previously registered (but now is)."
client.deactivate_registration(messages.RegistrationResource(uri=location))