diff --git a/letsencrypt/acme/challenges.py b/letsencrypt/acme/challenges.py index 7a51d7447..cb8badc91 100644 --- a/letsencrypt/acme/challenges.py +++ b/letsencrypt/acme/challenges.py @@ -5,24 +5,20 @@ import hashlib import Crypto.Random +from letsencrypt.acme import fields from letsencrypt.acme import jose +from letsencrypt.acme import messages2 from letsencrypt.acme import other # pylint: disable=too-few-public-methods -class Challenge(jose.TypedJSONObjectWithFields): - # _fields_to_json | pylint: disable=abstract-method - """ACME challenge.""" - TYPES = {} - - -class ContinuityChallenge(Challenge): # pylint: disable=abstract-method +class ContinuityChallenge(messages2.Challenge): # pylint: disable=abstract-method """Client validation challenges.""" -class DVChallenge(Challenge): # pylint: disable=abstract-method +class DVChallenge(messages2.Challenge): # pylint: disable=abstract-method """Domain validation challenges.""" @@ -41,7 +37,7 @@ class ChallengeResponse(jose.TypedJSONObjectWithFields): return super(ChallengeResponse, cls).from_json(jobj) -@Challenge.register +@messages2.Challenge.register class SimpleHTTPS(DVChallenge): """ACME "simpleHttps" challenge.""" typ = "simpleHttps" @@ -69,7 +65,7 @@ class SimpleHTTPSResponse(ChallengeResponse): return self.URI_TEMPLATE.format(domain=domain, path=self.path) -@Challenge.register +@messages2.Challenge.register class DVSNI(DVChallenge): """ACME "dvsni" challenge. @@ -93,6 +89,9 @@ class DVSNI(DVChallenge): nonce = jose.Field("nonce", encoder=binascii.hexlify, decoder=functools.partial(functools.partial( jose.decode_hex16, size=NONCE_SIZE))) + uri = jose.Field('uri') + status = jose.Field('status', decoder=messages2.Status.from_json) + validated = fields.RFC3339Field('validated', omitempty=True) @property def nonce_domain(self): @@ -138,7 +137,7 @@ class DVSNIResponse(ChallengeResponse): """Domain name for certificate subjectAltName.""" return self.z(chall) + self.DOMAIN_SUFFIX -@Challenge.register +@messages2.Challenge.register class RecoveryContact(ContinuityChallenge): """ACME "recoveryContact" challenge.""" typ = "recoveryContact" @@ -147,6 +146,10 @@ class RecoveryContact(ContinuityChallenge): success_url = jose.Field("successURL", omitempty=True) contact = jose.Field("contact", omitempty=True) + uri = jose.Field('uri') + status = jose.Field('status', decoder=messages2.Status.from_json) + validated = fields.RFC3339Field('validated', omitempty=True) + @ChallengeResponse.register class RecoveryContactResponse(ChallengeResponse): @@ -155,11 +158,15 @@ class RecoveryContactResponse(ChallengeResponse): token = jose.Field("token", omitempty=True) -@Challenge.register +@messages2.Challenge.register class RecoveryToken(ContinuityChallenge): """ACME "recoveryToken" challenge.""" typ = "recoveryToken" + uri = jose.Field('uri') + status = jose.Field('status', decoder=messages2.Status.from_json) + validated = fields.RFC3339Field('validated', omitempty=True) + @ChallengeResponse.register class RecoveryTokenResponse(ChallengeResponse): @@ -168,7 +175,7 @@ class RecoveryTokenResponse(ChallengeResponse): token = jose.Field("token", omitempty=True) -@Challenge.register +@messages2.Challenge.register class ProofOfPossession(ContinuityChallenge): """ACME "proofOfPossession" challenge. @@ -180,6 +187,10 @@ class ProofOfPossession(ContinuityChallenge): NONCE_SIZE = 16 + uri = jose.Field('uri') + status = jose.Field('status', decoder=messages2.Status.from_json) + validated = fields.RFC3339Field('validated', omitempty=True) + class Hints(jose.JSONObjectWithFields): """Hints for "proofOfPossession" challenge. @@ -236,12 +247,15 @@ class ProofOfPossessionResponse(ChallengeResponse): return self.signature.verify(self.nonce) -@Challenge.register +@messages2.Challenge.register class DNS(DVChallenge): """ACME "dns" challenge.""" typ = "dns" token = jose.Field("token") + uri = jose.Field('uri') + status = jose.Field('status', decoder=messages2.Status.from_json) + validated = fields.RFC3339Field('validated', omitempty=True) @ChallengeResponse.register class DNSResponse(ChallengeResponse): diff --git a/letsencrypt/acme/jose/json_util.py b/letsencrypt/acme/jose/json_util.py index 01eada89c..a025c9b61 100644 --- a/letsencrypt/acme/jose/json_util.py +++ b/letsencrypt/acme/jose/json_util.py @@ -216,7 +216,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable): value = getattr(self, slot) if field.omit(value): - logging.debug('Ommiting empty field "%s" (%s)', slot, value) + logging.debug('Omitting empty field "%s" (%s)', slot, value) else: try: jobj[field.json_name] = field.encode(value) @@ -246,6 +246,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable): """Deserialize fields from JSON.""" cls._check_required(jobj) fields = {} + for slot, field in cls._fields.iteritems(): if field.json_name not in jobj and field.omitempty: fields[slot] = field.default @@ -372,17 +373,15 @@ class TypedJSONObjectWithFields(JSONObjectWithFields): raise errors.DeserializationError("missing type field") try: - type_cls = cls.TYPES[typ] + return cls.TYPES[typ] except KeyError: raise errors.UnrecognizedTypeError(typ, jobj) - return type_cls - def to_json(self): """Get JSON serializable object. :returns: Serializable JSON object representing ACME typed object. - :meth:`validate` will almost certianly not work, due to reasons + :meth:`validate` will almost certainly not work, due to reasons explained in :class:`letsencrypt.acme.interfaces.IJSONSerializable`. :rtype: dict diff --git a/letsencrypt/acme/messages2.py b/letsencrypt/acme/messages2.py index f4c1e9dce..38b2351b4 100644 --- a/letsencrypt/acme/messages2.py +++ b/letsencrypt/acme/messages2.py @@ -115,6 +115,14 @@ class ResourceBody(jose.JSONObjectWithFields): """ACME Resource Body.""" +class TypedResourceBody(jose.TypedJSONObjectWithFields): + """ACME Resource Body with type.""" + + +class ResourceBody(jose.JSONObjectWithFields): + """ACME Resource Body""" + + class RegistrationResource(Resource): """Registration Resource. @@ -130,7 +138,7 @@ class Registration(ResourceBody): """Registration Resource Body. :ivar letsencrypt.acme.jose.jwk.JWK key: Public key. - :ivar tuple contact: + :ivar tuple contact: Contact information following ACME spec """ @@ -158,41 +166,23 @@ class ChallengeResource(Resource, jose.JSONObjectWithFields): return self.body.uri -class ChallengeBody(ResourceBody): +class Challenge(TypedResourceBody): """Challenge Resource Body. - .. todo:: - Confusingly, this has a similar name to `.challenges.Challenge`, - as well as `.achallenges.AnnotatedChallenge` or - `.achallenges.Indexed`... Once `messages2` and `network2` is - integrated with the rest of the client, this class functionality - will be merged with `.challenges.Challenge`. Meanwhile, - separation allows the ``master`` to be still interoperable with - Node.js server (protocol v00). For the time being use names such - as ``challb`` to distinguish instances of this class from - ``achall`` or ``ichall``. - :ivar letsencrypt.acme.messages2.Status status: :ivar datetime.datetime validated: """ - - __slots__ = ('chall',) + TYPES = {} + # __slots__ = ('chall',) uri = jose.Field('uri') status = jose.Field('status', decoder=Status.from_json) validated = fields.RFC3339Field('validated', omitempty=True) def to_json(self): - jobj = super(ChallengeBody, self).to_json() - jobj.update(self.chall.to_json()) + jobj = super(Challenge, self).to_json() return jobj - @classmethod - def fields_from_json(cls, jobj): - jobj_fields = super(ChallengeBody, cls).fields_from_json(jobj) - jobj_fields['chall'] = challenges.Challenge.from_json(jobj) - return jobj_fields - class AuthorizationResource(Resource): """Authorization Resource. @@ -208,7 +198,7 @@ class Authorization(ResourceBody): """Authorization Resource Body. :ivar letsencrypt.acme.messages2.Identifier identifier: - :ivar list challenges: `list` of `Challenge` + :ivar list challenges: `list` of `ChallengeBody` :ivar tuple combinations: Challenge combinations (`tuple` of `tuple` of `int`, as opposed to `list` of `list` from the spec). :ivar letsencrypt.acme.jose.jwk.JWK key: Public key. @@ -235,7 +225,7 @@ class Authorization(ResourceBody): @challenges.decoder def challenges(value): # pylint: disable=missing-docstring,no-self-argument - return tuple(ChallengeBody.from_json(chall) for chall in value) + return tuple(challenges.Challenge.from_json(chall) for chall in value) @property def resolved_combinations(self): diff --git a/letsencrypt/client/achallenges.py b/letsencrypt/client/achallenges.py index 7bb548dfc..05bd3c67d 100644 --- a/letsencrypt/client/achallenges.py +++ b/letsencrypt/client/achallenges.py @@ -1,7 +1,6 @@ """Client annotated ACME challenges. -Please use names such as ``achall`` and ``ichall`` (respectively ``achalls`` -and ``ichalls`` for collections) to distiguish from variables "of type" +Please use names such as ``achall`` to distiguish from variables "of type" :class:`letsencrypt.acme.challenges.Challenge` (denoted by ``chall``):: from letsencrypt.acme import challenges @@ -9,11 +8,10 @@ and ``ichalls`` for collections) to distiguish from variables "of type" chall = challenges.DNS(token='foo') achall = achallenges.DNS(chall=chall, domain='example.com') - ichall = achallenges.Indexed(achall=achall, index=0) Note, that all annotated challenges act as a proxy objects:: - ichall.token == achall.token == chall.token + achall.token == chall.token """ from letsencrypt.acme import challenges @@ -86,17 +84,3 @@ class ProofOfPossession(AnnotatedChallenge): """Client annotated "proofOfPossession" ACME challenge.""" __slots__ = ('chall', 'domain') acme_type = challenges.ProofOfPossession - - -class Indexed(jose_util.ImmutableMap): - """Indexed and annotated ACME challenge. - - Wraps around :class:`AnnotatedChallenge` and annotates with an - ``index`` in order to maintain the proper position of the response - within a larger challenge list. - - """ - __slots__ = ('achall', 'index') - - def __getattr__(self, name): - return getattr(self.achall, name) diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/client/auth_handler.py index cabb1267a..4af99761f 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/client/auth_handler.py @@ -2,6 +2,7 @@ import itertools import logging import sys +import time import Crypto.PublicKey.RSA @@ -51,7 +52,7 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes self.dv_c = [] self.cont_c = [] - def get_authorizations(self, domains): + def get_authorizations(self, domains, new_authz_uri): """Retrieve all authorizations for challenges. :param set domains: Domains for authorization @@ -64,6 +65,9 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes authorizations """ + for domain in domains: + self.authzr[domain] = self.network.request_domain_challenges( + domain, new_authz_uri) self._choose_challenges(domains) cont_resp, dv_resp = self._get_responses() logging.info("Ready for verification...") @@ -71,13 +75,15 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes # Send all Responses self._respond(cont_resp, dv_resp) + return self._verify_auths() + def _choose_challenges(self, domains): logging.info("Performing the following challenges:") for dom in domains: path = gen_challenge_path( - self.authzr[dom].challenges, + self.authzr[dom].body.challenges, self._get_chall_pref(dom), - self.authzr[dom].combinations) + self.authzr[dom].body.combinations) dom_dv_c, dom_cont_c = self._challenge_factory( dom, path) @@ -106,51 +112,65 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes return cont_resp, dv_resp - def acme_authorization(self, domain): - """Handle ACME "authorization" phase. + def _verify_auths(self): + time.sleep(6) + for domain in self.authzr: + self.authzr[domain], resp = self.network.poll(self.authzr[domain]) + if self.authzr[domain].body.status == messages2.STATUS_INVALID: + raise errors.AuthHandlerError( + "Unable to retrieve authorization for %s" % domain) - :param str domain: domain that is requesting authorization - - :returns: ACME "authorization" message. - :rtype: :class:`letsencrypt.acme.messages.Authorization` - - """ - try: - auth = self.network.send_and_receive_expected( - messages.AuthorizationRequest.create( - session_id=self.msgs[domain].session_id, - nonce=self.msgs[domain].nonce, - responses=self.responses[domain], - name=domain, - key=jose.HashableRSAKey(Crypto.PublicKey.RSA.importKey( - self.authkey[domain].pem))), - messages.Authorization) - logging.info("Received Authorization for %s", domain) - return auth - except errors.LetsEncryptClientError as err: - logging.fatal(str(err)) - logging.fatal( - "Failed Authorization procedure - cleaning up challenges") - sys.exit(1) - finally: - self._cleanup_challenges(domain) + self._cleanup_challenges() + return [self.authzr[domain] for domain in self.authzr] def _respond(self, cont_resp, dv_resp): - """Send/Recieve confirmation of all challenges. + """Send/Receive confirmation of all challenges. .. note:: This method also cleans up the auth_handler state. """ - to_check = self._send_responses(self.dv_c, dv_resp) - to_check.update(self._send_responses(self.cont_c, cont_resp)) + chall_update = dict() + self._send_responses(self.dv_c, dv_resp, chall_update) + self._send_responses(self.cont_c, cont_resp, chall_update) - def _send_responses(self, achalls, resps): + # self._poll_challenges(chall_update) + + def _send_responses(self, achalls, resps, chall_update): """Send responses and make sure errors are handled.""" - to_check = dict() for achall, resp in itertools.izip(achalls, resps): if resp: - to_check[achall.domain] = self.network.answer_challenge( - achall.chall, resp) + challr = self.network.answer_challenge(achall.chall, resp) + chall_update[achall.domain] = chall_update.get( + achall.domain, []).append(challr) + + # def _poll_challenges(self, chall_update): + # to_check = chall_update.keys() + # completed = [] + # while to_check: + # + # def _handle_to_check(self): + # for domain in to_check: + # self.authzr[domain] = self.network.poll(self.authzr[domain]) + # if self.authzr[domain].status == messages2.STATUS_VALID: + # completed.append(domain) + # if self.authzr[domain].status == messages2.STATUS_INVALID: + # logging.error("Failed authorization for %s", domain) + # raise errors.AuthHandlerError( + # "Failed Authorization for %s" % domain) + # for challr in chall_update[domain]: + # status = self._get_status_of_chall(self.authzr[domain], challr) + # if status == messages2.STATUS_VALID: + # chall_update[domain].remove(challr) + # elif status == messages2.STATUS_INVALID: + # raise errors.AuthHandlerError( + # "Failed %s challenge for domain %s" % ( + # challr.body.chall.typ, domain)) + # + # def _get_status_of_chall(self, authzr, challr): + # for challb in authzr.challenges: + # # TODO: Use better identifiers... instead of type + # if isinstance(challb.chall, challr.body.chall): + # return challb.status def _get_chall_pref(self, domain): """Return list of challenge preferences. @@ -192,16 +212,16 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes cont_chall = [] for index in path: - chall = self.authzr[domain].challenges[index] + chall = self.authzr[domain].body.challenges[index] if isinstance(chall, challenges.DVSNI): logging.info(" DVSNI challenge for %s.", domain) achall = achallenges.DVSNI( - chall=chall, domain=domain, key=self.authkey[domain]) + chall=chall, domain=domain, key=self.authkey) elif isinstance(chall, challenges.SimpleHTTPS): logging.info(" SimpleHTTPS challenge for %s.", domain) achall = achallenges.SimpleHTTPS( - chall=chall, domain=domain, key=self.authkey[domain]) + chall=chall, domain=domain, key=self.authkey) elif isinstance(chall, challenges.DNS): logging.info(" DNS challenge for %s.", domain) achall = achallenges.DNS(chall=chall, domain=domain) @@ -211,7 +231,8 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes achall = achallenges.RecoveryToken(chall=chall, domain=domain) elif isinstance(chall, challenges.RecoveryContact): logging.info(" Recovery Contact Challenge for %s.", domain) - achall = achallenges.RecoveryContact(chall=chall, domain=domain) + achall = achallenges.RecoveryContact( + chall=chall, domain=domain) elif isinstance(chall, challenges.ProofOfPossession): logging.info(" Proof-of-Possession Challenge for %s", domain) achall = achallenges.ProofOfPossession( @@ -219,14 +240,13 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes else: raise errors.LetsEncryptClientError( - "Received unsupported challenge of type: %s", chall.typ) - - ichall = achallenges.Indexed(achall=achall, index=index) + "Received unsupported challenge of type: %s", + chall.typ) if isinstance(chall, challenges.ContinuityChallenge): - cont_chall.append(ichall) + cont_chall.append(achall) elif isinstance(chall, challenges.DVChallenge): - dv_chall.append(ichall) + dv_chall.append(achall) return dv_chall, cont_chall diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 70b0796a1..7d84feb10 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -65,8 +65,9 @@ class Client(object): # TODO: Allow for other alg types besides RS256 self.network = network2.Network( - config.server+"/acme/new-registration", + "https://%s/acme/new-reg" % config.server, jwk.JWKRSA.load(authkey.pem)) + self.config = config if dv_auth is not None: @@ -88,9 +89,18 @@ class Client(object): "mailto:" + email if email is not None else None, "tel:" + phone if phone is not None else None ) + contact_tuple = tuple(detail for detail in details if detail is not None) - self.regr = self.network.register( - tuple(detail for detail in details if detail is not None)) + # TODO: Replace with real info once through testing. + if not contact_tuple: + contact_tuple = ("mailto:letsencrypt-client@letsencrypt.org", + "tel:+12025551212") + self.regr = self.network.register(contact=contact_tuple) + + # If terms of service exist... we need to sign it. + # TODO: Replace the `preview EULA` with this... + if self.regr.terms_of_service: + self.network.agree_to_tos(self.regr) def set_regr(self, regr): """Set a preexisting registration resource.""" @@ -122,21 +132,26 @@ class Client(object): # Perform Challenges/Get Authorizations if self.regr.new_authzr_uri: - self.auth_handler.get_authorizations(domains, self.regr) + authzr = self.auth_handler.get_authorizations( + domains, self.regr.new_authzr_uri) else: - self.auth_handler.get_authorizations( - domains, self.config.server + "/acme/new-authorization") + authzr = self.auth_handler.get_authorizations( + domains, + "https://%s/acme/new-authz" % self.config.server) # Create CSR from names if csr is None: csr = init_csr(self.authkey, domains, self.config.cert_dir) # Retrieve certificate - certificate_msg = self.acme_certificate(csr.data) + certr = self.network.request_issuance( + jose.ComparableX509( + M2Crypto.X509.load_request_der_string(csr.data)), + authzr) # Save Certificate cert_file, chain_file = self.save_certificate( - certificate_msg, self.config.cert_path, self.config.chain_path) + certr, self.config.cert_path, self.config.chain_path) revoker.Revoker.store_cert_key( cert_file, self.authkey.file, self.config) @@ -172,12 +187,12 @@ class Client(object): self.authkey.pem))), messages.Certificate) - def save_certificate(self, certificate_msg, cert_path, chain_path): + def save_certificate(self, certr, cert_path, chain_path): # pylint: disable=no-self-use """Saves the certificate received from the ACME server. - :param certificate_msg: ACME "certificate" message from server. - :type certificate_msg: :class:`letsencrypt.acme.messages.Certificate` + :param certr: ACME "certificate" resource. + :type certr: :class:`letsencrypt.acme.messages.Certificate` :param str cert_path: Path to attempt to save the cert file :param str chain_path: Path to attempt to save the chain file @@ -188,17 +203,19 @@ class Client(object): :raises IOError: If unable to find room to write the cert files """ + # try finally close cert_chain_abspath = None cert_fd, cert_file = le_util.unique_file(cert_path, 0o644) - cert_fd.write(certificate_msg.certificate.as_pem()) + cert_fd.write(certr.body.as_pem()) cert_fd.close() logging.info( "Server issued certificate; certificate written to %s", cert_file) - if certificate_msg.chain: + if certr.cert_chain_uri: + # try finally close + chain_cert = self.network.fetch_chain(certr.cert_chain_uri) chain_fd, chain_fn = le_util.unique_file(chain_path, 0o644) - for cert in certificate_msg.chain: - chain_fd.write(cert.to_pem()) + chain_fd.write(chain_cert.to_pem()) chain_fd.close() logging.info("Cert chain written to %s", chain_fn) diff --git a/letsencrypt/client/network2.py b/letsencrypt/client/network2.py index 29fe4a911..aec6f8ddd 100644 --- a/letsencrypt/client/network2.py +++ b/letsencrypt/client/network2.py @@ -50,6 +50,7 @@ class Network(object): """ dumps = obj.json_dumps() logging.debug('Serialized JSON: %s', dumps) + print "json_dumps:", dumps return jose.JWS.sign( payload=dumps, key=self.key, alg=self.alg).json_dumps() @@ -74,7 +75,6 @@ class Network(object): """ response_ct = response.headers.get('Content-Type') - try: # TODO: response.json() is called twice, once here, and # once in _get and _post clients @@ -83,6 +83,9 @@ class Network(object): jobj = None if not response.ok: + print response + print response.headers + print response.content if jobj is not None: if response_ct != cls.JSON_ERROR_CONTENT_TYPE: logging.debug( @@ -167,7 +170,7 @@ class Network(object): 'contact'].default): """Register. - :param contact: Contact list, as accepted by `.RegistrationResource` + :param contact: Contact list, as accepted by `.Registration` :type contact: `tuple` :returns: Registration Resource. @@ -213,6 +216,21 @@ class Network(object): raise errors.UnexpectedUpdate(regr) return updated_regr + def agree_to_tos(self, regr): + """Agree to the terms-of-service. + + Agree to the terms-of-service in a Registration Resource. + + :param regr: Registration Resource. + :type regr: `.RegistrationResource` + + :returns: Updated Registration Resource. + :rtype: `.RegistrationResource` + + """ + self.update_registration( + regr.update(body=regr.body.update(agreement=regr.terms_of_service))) + def _authzr_from_response(self, response, identifier, uri=None, new_cert_uri=None): if new_cert_uri is None: @@ -279,20 +297,23 @@ class Network(object): :raises errors.UnexpectedUpdate: """ + print "sendinging challenge to:", challb.uri response = self._post(challb.uri, self._wrap_in_jws(response)) try: authzr_uri = response.links['up']['url'] except KeyError: - raise errors.NetworkError('"up" Link header missing') - challr = messages2.ChallengeResource( + # TODO: Right now Boulder responds with the authorization resource + # instead of a challenge resource... this can be uncommented + # once the error is fixed. + return challb + # raise errors.NetworkError('"up" Link header missing') + challr2 = messages2.ChallengeResource( authzr_uri=authzr_uri, body=messages2.ChallengeBody.from_json(response.json())) # TODO: check that challr.uri == response.headers['Location']? - if challr.uri != challb.uri: - raise errors.UnexpectedUpdate(challr.uri) - return challr - - def poll_challenge(self, chall): + if challr2.uri != challb.uri: + raise errors.UnexpectedUpdate(challb.uri) + return challr2 @classmethod def retry_after(cls, response, default): @@ -352,6 +373,8 @@ class Network(object): """ assert authzrs, "Authorizations list is empty" + logging.debug("Requesting issuance...") + print "Requesting issuance: ", authzrs[0] # TODO: assert len(authzrs) == number of SANs req = messages2.CertificateRequest( @@ -402,7 +425,7 @@ class Network(object): :rtype: `tuple` """ - # priority queue with datetime (based od Retry-After) as key, + # priority queue with datetime (based on Retry-After) as key, # and original Authorization Resource as value waiting = [(datetime.datetime.now(), authzr) for authzr in authzrs] # mapping between original Authorization Resource and the most diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 3b4b7c10d..ecc96b9e0 100644 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -57,7 +57,7 @@ def create_parser(): config_help = lambda name: interfaces.IConfig[name].__doc__ add("-d", "--domains", metavar="DOMAIN", nargs="+") - add("-s", "--server", default="letsencrypt-demo.org:443", + add("-s", "--server", default="www.letsencrypt-demo.org", help=config_help("server")) add("-k", "--authkey", type=read_file, @@ -202,6 +202,7 @@ def main(): # pylint: disable=too-many-branches, too-many-statements # but this code should be safe on all environments. cert_file = None if auth is not None: + acme.register() cert_file, chain_file = acme.obtain_certificate(doms) if installer is not None and cert_file is not None: acme.deploy_certificate(doms, authkey, cert_file, chain_file)