Esempio n. 1
0
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())
Esempio n. 2
0
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)",
    }
Esempio n. 3
0
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"])
Esempio n. 4
0
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"))
Esempio n. 5
0
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", []))
Esempio n. 6
0
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"))
Esempio n. 7
0
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)
Esempio n. 8
0
class ChallengeRequest(Message):
    """ACME "challengeRequest" message."""
    typ = "challengeRequest"
    schema = util.load_schema(typ)
    identifier = jose.Field("identifier")
Esempio n. 9
0
class StatusRequest(Message):
    """ACME "statusRequest" message."""
    typ = "statusRequest"
    schema = util.load_schema(typ)
    token = jose.Field("token")
Esempio n. 10
0
class Revocation(Message):
    """ACME "revocation" message."""
    typ = "revocation"
    schema = util.load_schema(typ)
Esempio n. 11
0
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)
Esempio n. 12
0
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))
Esempio n. 13
0
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))
Esempio n. 14
0
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", []))