class RevocationRequest(Message): """ACME "revocationRequest" message. :ivar certificate: Certificate (:class:`M2Crypto.X509.X509` wrapped in :class:`letsencrypt.acme.util.ComparableX509`). :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). """ typ = "revocationRequest" schema = util.load_schema(typ) certificate = jose.Field("certificate", decoder=jose.decode_cert, encoder=jose.encode_cert) signature = jose.Field("signature", decoder=other.Signature.from_json) @classmethod def create(cls, key, sig_nonce=None, **kwargs): """Create signed "revocationRequest". :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` :param str sig_nonce: Nonce used for signature. Useful for testing. :kwargs: Any other arguments accepted by the class constructor. :returns: Signed "revocationRequest" ACME message. :rtype: :class:`RevocationRequest` """ return cls(signature=other.Signature.from_msg( kwargs["certificate"].as_der(), key, sig_nonce), **kwargs) def verify(self): """Verify signature. .. warning:: Caller must check that the public key encoded in the :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object is the correct key for a given context. :returns: True iff ``signature`` can be verified, False otherwise. :rtype: bool """ # self.signature is not Field | pylint: disable=no-member return self.signature.verify(self.certificate.as_der())
class Error(Message): """ACME "error" message.""" typ = "error" schema = util.load_schema(typ) error = jose.Field("error") message = jose.Field("message", omitempty=True) more_info = jose.Field("moreInfo", omitempty=True) MESSAGE_CODES = { "malformed": "The request message was malformed", "unauthorized": "The client lacks sufficient authorization", "serverInternal": "The server experienced an internal error", "notSupported": "The request type is not supported", "unknown": "The server does not recognize an ID/token in the request", "badCSR": "The CSR is unacceptable (e.g., due to a short key)", }
class ChallengeRequest(Message): """ACME "challengeRequest" message. :ivar str identifier: Domain name. """ acme_type = "challengeRequest" schema = util.load_schema(acme_type) __slots__ = ("identifier", ) def _fields_to_json(self): return { "identifier": self.identifier, } @classmethod def _from_valid_json(cls, jobj): return cls(identifier=jobj["identifier"])
class Defer(Message): """ACME "defer" message.""" acme_type = "defer" schema = util.load_schema(acme_type) __slots__ = ("token", "interval", "message") def _fields_to_json(self): fields = {"token": self.token} if self.interval is not None: fields["interval"] = self.interval if self.message is not None: fields["message"] = self.message return fields @classmethod def _from_valid_json(cls, jobj): return cls(token=jobj["token"], interval=jobj.get("interval"), message=jobj.get("message"))
class Challenge(Message): """ACME "challenge" message.""" acme_type = "challenge" schema = util.load_schema(acme_type) __slots__ = ("session_id", "nonce", "challenges", "combinations") def _fields_to_json(self): fields = { "sessionID": self.session_id, "nonce": jose.b64encode(self.nonce), "challenges": self.challenges, } if self.combinations: fields["combinations"] = self.combinations return fields @classmethod def _from_valid_json(cls, jobj): return cls(session_id=jobj["sessionID"], nonce=jose.b64decode(jobj["nonce"]), challenges=jobj["challenges"], combinations=jobj.get("combinations", []))
class Certificate(Message): """ACME "certificate" message. :ivar certificate: The certificate (:class:`M2Crypto.X509.X509` wrapped in :class:`letsencrypt.acme.util.ComparableX509`). :ivar list chain: Chain of certificates (:class:`M2Crypto.X509.X509` wrapped in :class:`letsencrypt.acme.util.ComparableX509` ). """ acme_type = "certificate" schema = util.load_schema(acme_type) __slots__ = ("certificate", "chain", "refresh") def _fields_to_json(self): fields = {"certificate": self._encode_cert(self.certificate)} if self.chain: fields["chain"] = [self._encode_cert(cert) for cert in self.chain] if self.refresh is not None: fields["refresh"] = self.refresh return fields @classmethod def _decode_cert(cls, b64der): return util.ComparableX509( M2Crypto.X509.load_cert_der_string(jose.b64decode(b64der))) @classmethod def _encode_cert(cls, cert): return jose.b64encode(cert.as_der()) @classmethod def _from_valid_json(cls, jobj): return cls( certificate=cls._decode_cert(jobj["certificate"]), chain=[cls._decode_cert(cert) for cert in jobj.get("chain", [])], refresh=jobj.get("refresh"))
class Authorization(Message): """ACME "authorization" message.""" acme_type = "authorization" schema = util.load_schema(acme_type) __slots__ = ("recovery_token", "identifier", "jwk") def _fields_to_json(self): fields = {} if self.recovery_token is not None: fields["recoveryToken"] = self.recovery_token if self.identifier is not None: fields["identifier"] = self.identifier if self.jwk is not None: fields["jwk"] = self.jwk return fields @classmethod def _from_valid_json(cls, jobj): jwk = jobj.get("jwk") if jwk is not None: jwk = jose.JWK.from_json(jwk, validate=False) return cls(recovery_token=jobj.get("recoveryToken"), identifier=jobj.get("identifier"), jwk=jwk)
class ChallengeRequest(Message): """ACME "challengeRequest" message.""" typ = "challengeRequest" schema = util.load_schema(typ) identifier = jose.Field("identifier")
class StatusRequest(Message): """ACME "statusRequest" message.""" typ = "statusRequest" schema = util.load_schema(typ) token = jose.Field("token")
class Revocation(Message): """ACME "revocation" message.""" typ = "revocation" schema = util.load_schema(typ)
class AuthorizationRequest(Message): """ACME "authorizationRequest" message. :ivar str nonce: Random data from the corresponding :attr:`Challenge.nonce`, **not** base64-encoded. :ivar list responses: List of completed challenges ( :class:`letsencrypt.acme.challenges.ChallengeResponse`). :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). """ typ = "authorizationRequest" schema = util.load_schema(typ) session_id = jose.Field("sessionID") nonce = jose.Field("nonce", encoder=jose.b64encode, decoder=jose.decode_b64jose) responses = jose.Field("responses") signature = jose.Field("signature", decoder=other.Signature.from_json) contact = jose.Field("contact", omitempty=True, default=()) @responses.decoder def responses(value): # pylint: disable=missing-docstring,no-self-argument return tuple( challenges.ChallengeResponse.from_json(chall) for chall in value) @classmethod def create(cls, name, key, sig_nonce=None, **kwargs): """Create signed "authorizationRequest". :param str name: Hostname :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` :param str sig_nonce: Nonce used for signature. Useful for testing. :kwargs: Any other arguments accepted by the class constructor. :returns: Signed "authorizationRequest" ACME message. :rtype: :class:`AuthorizationRequest` """ # pylint: disable=too-many-arguments signature = other.Signature.from_msg(name + kwargs["nonce"], key, sig_nonce) return cls(signature=signature, contact=kwargs.pop("contact", ()), **kwargs) def verify(self, name): """Verify signature. .. warning:: Caller must check that the public key encoded in the :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object is the correct key for a given context. :param str name: Hostname :returns: True iff ``signature`` can be verified, False otherwise. :rtype: bool """ # self.signature is not Field | pylint: disable=no-member return self.signature.verify(name + self.nonce)
class Signature(util.JSONDeSerializable, util.ImmutableMap): """ACME signature. :ivar str alg: Signature algorithm. :ivar str sig: Signature. :ivar str nonce: Nonce. :ivar jwk: JWK. :type jwk: :class:`letsencrypt.acme.jose.JWK` .. todo:: Currently works for RSA keys only. """ __slots__ = ('alg', 'sig', 'nonce', 'jwk') schema = util.load_schema('signature') NONCE_LEN = 16 """Size of nonce in bytes, as specified in the ACME protocol.""" @classmethod def from_msg(cls, msg, key, nonce=None): """Create signature with nonce prepended to the message. .. todo:: Protect against crypto unicode errors... is this sufficient? Do I need to escape? :param str msg: Message to be signed. :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` :param nonce: Nonce to be used. If None, nonce of :const:`NONCE_LEN` size will be randomly generated. :type nonce: str or None """ if nonce is None: nonce = Random.get_random_bytes(cls.NONCE_LEN) msg_with_nonce = nonce + msg hashed = Crypto.Hash.SHA256.new(msg_with_nonce) sig = Crypto.Signature.PKCS1_v1_5.new(key).sign(hashed) logging.debug('%s signed as %s', msg_with_nonce, sig) return cls(alg='RS256', sig=sig, nonce=nonce, jwk=jose.JWK(key=key.publickey())) def verify(self, msg): """Verify the signature. :param str msg: Message that was used in signing. """ hashed = Crypto.Hash.SHA256.new(self.nonce + msg) return Crypto.Signature.PKCS1_v1_5.new(self.jwk.key).verify( hashed, self.sig) def to_json(self): """Prepare JSON serializable object.""" return { 'alg': self.alg, 'sig': jose.b64encode(self.sig), 'nonce': jose.b64encode(self.nonce), 'jwk': self.jwk, } @classmethod def _from_valid_json(cls, jobj): return cls(alg=jobj['alg'], sig=jose.b64decode(jobj['sig']), nonce=jose.b64decode(jobj['nonce']), jwk=jose.JWK.from_json(jobj['jwk'], validate=False))
class RevocationRequest(Message): """ACME "revocationRequest" message. :ivar certificate: Certificate (:class:`M2Crypto.X509.X509` wrapped in :class:`letsencrypt.acme.util.ComparableX509`). :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). """ acme_type = "revocationRequest" schema = util.load_schema(acme_type) __slots__ = ("certificate", "signature") @classmethod def create(cls, key, sig_nonce=None, **kwargs): """Create signed "revocationRequest". :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` :param str sig_nonce: Nonce used for signature. Useful for testing. :kwargs: Any other arguments accepted by the class constructor. :returns: Signed "revocationRequest" ACME message. :rtype: :class:`RevocationRequest` """ return cls(signature=other.Signature.from_msg( kwargs["certificate"].as_der(), key, sig_nonce), **kwargs) def verify(self): """Verify signature. .. warning:: Caller must check that the public key encoded in the :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object is the correct key for a given context. :returns: True iff ``signature`` can be verified, False otherwise. :rtype: bool """ return self.signature.verify(self.certificate.as_der()) @classmethod def _decode_cert(cls, b64der): return util.ComparableX509( M2Crypto.X509.load_cert_der_string(jose.b64decode(b64der))) @classmethod def _encode_cert(cls, cert): return jose.b64encode(cert.as_der()) def _fields_to_json(self): return { "certificate": self._encode_cert(self.certificate), "signature": self.signature, } @classmethod def _from_valid_json(cls, jobj): return cls(certificate=cls._decode_cert(jobj["certificate"]), signature=other.Signature.from_json(jobj["signature"], validate=False))
class AuthorizationRequest(Message): """ACME "authorizationRequest" message. :ivar str session_id: "sessionID" from the server challenge :ivar str nonce: Nonce from the server challenge :ivar list responses: List of completed challenges :ivar signature: Signature (:class:`letsencrypt.acme.other.Signature`). :ivar contact: TODO """ acme_type = "authorizationRequest" schema = util.load_schema(acme_type) __slots__ = ("session_id", "nonce", "responses", "signature", "contact") @classmethod def create(cls, name, key, sig_nonce=None, **kwargs): """Create signed "authorizationRequest". :param str name: Hostname :param key: Key used for signing. :type key: :class:`Crypto.PublicKey.RSA` :param str sig_nonce: Nonce used for signature. Useful for testing. :kwargs: Any other arguments accepted by the class constructor. :returns: Signed "authorizationRequest" ACME message. :rtype: :class:`AuthorizationRequest` """ # pylint: disable=too-many-arguments signature = other.Signature.from_msg(name + kwargs["nonce"], key, sig_nonce) return cls(signature=signature, contact=kwargs.pop("contact", []), **kwargs) def verify(self, name): """Verify signature. .. warning:: Caller must check that the public key encoded in the :attr:`signature`'s :class:`letsencrypt.acme.jose.JWK` object is the correct key for a given context. :param str name: Hostname :returns: True iff ``signature`` can be verified, False otherwise. :rtype: bool """ return self.signature.verify(name + self.nonce) def _fields_to_json(self): fields = { "sessionID": self.session_id, "nonce": jose.b64encode(self.nonce), "responses": self.responses, "signature": self.signature, } if self.contact: fields["contact"] = self.contact return fields @classmethod def _from_valid_json(cls, jobj): return cls(session_id=jobj["sessionID"], nonce=jose.b64decode(jobj["nonce"]), responses=jobj["responses"], signature=other.Signature.from_json(jobj["signature"], validate=False), contact=jobj.get("contact", []))