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(), }
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(), }
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
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)
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()})
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
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 = [
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)
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)
def acme_client_for_private_key(acme_directory_url, private_key): return Client( acme_directory_url, key=jose.JWKRSA(key=private_key) )
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
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
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"])
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')
def get_jwk_key(self): return jose.JWKRSA(key=self.get_key())
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
def _deserialize_key(self, k): return jose.JWKRSA(key=serialization.load_pem_private_key( str(k), password=None, backend=default_backend()))
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()))
"""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)
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))