def verify(self, assertion, audience=None, now=None):
        """Verify the certificate chain for the receipt
        """
        if now is None:
            now = int(time.time())

        # This catches KeyError and turns it into ValueError.
        # It saves having to test for the existence of individual
        # items in the various payloads.
        try:
            # Grab the assertion, check that it has not expired.
            # No point doing all that crypto if we're going to fail out anyway.
            certificates, assertion = unbundle_certs_and_assertion(assertion)
            assertion = self.parse_jwt(assertion)
            if assertion.payload["exp"] < now:
                raise ExpiredSignatureError(assertion.payload["exp"])

            # Parse out the list of certificates.
            certificates = [self.parse_jwt(c) for c in certificates]

            # Verify the entire chain of certificates.
            cert = self.verify_certificate_chain(certificates, now=now)

            # Check the signature on the assertion.
            if not self.check_token_signature(assertion, cert):
                raise InvalidSignatureError("invalid signature on assertion")
        except KeyError:
            raise ValueError("Malformed JWT")
        # Looks good!
        return True
    def verify_certificate_chain(self, certificates, now=None):
        """Verify a signed chain of certificates.

        This function checks the signatures on the given chain of JWT
        certificates.  It looks up the public key for the issuer of the
        first certificate, then uses each certificate in turn to check the
        signature on its successor.

        If the entire chain is valid then to final certificate is returned.
        """
        if not certificates:
            raise ValueError("chain must have at least one certificate")
        if now is None:
            now = int(time.time())
        root_issuer = certificates[0].payload["iss"]
        root_key = self.certs[root_issuer]
        current_key = root_key
        for cert in certificates:
            if cert.payload["exp"] < now:
                raise ExpiredSignatureError("expired certificate in chain")
            if not cert.check_signature(current_key):
                raise InvalidSignatureError("bad signature in chain by: '%s'" %
                                            current_key['kid'])
            current_key = cert.payload["jwk"][0]
        return cert
Beispiel #3
0
class RemoteVerifier(object):
    implements(IBrowserIdVerifier)

    def __init__(self,
                 audiences=None,
                 trusted_issuers=None,
                 allowed_issuers=None,
                 verifier_url=None):
        # Since we don't parse the assertion locally, we cannot support
        # list- or pattern-based audience strings.
        if audiences is not None:
            assert isinstance(audiences, basestring)
        self.audiences = audiences
        if isinstance(trusted_issuers, basestring):
            trusted_issuers = trusted_issuers.split()
        self.trusted_issuers = trusted_issuers
        if isinstance(allowed_issuers, basestring):
            allowed_issuers = allowed_issuers.split()
        self.allowed_issuers = allowed_issuers
        if verifier_url is None:
            verifier_url = "https://verifier.accounts.firefox.com/v2"
        self.verifier_url = verifier_url
        self.session = requests.Session()
        self.session.verify = True

    def verify(self, assertion, audience=None):
        if audience is None:
            audience = self.audiences

        body = {'assertion': assertion, 'audience': audience}
        if self.trusted_issuers is not None:
            body['trustedIssuers'] = self.trusted_issuers
        headers = {'content-type': 'application/json'}
        try:
            response = self.session.post(self.verifier_url,
                                         data=json.dumps(body),
                                         headers=headers)
        except (socket.error, requests.RequestException), e:
            msg = "Failed to POST %s. Reason: %s" % (self.verifier_url, str(e))
            raise ConnectionError(msg)

        if response.status_code != 200:
            raise ConnectionError('server returned invalid response')
        try:
            data = json.loads(response.text)
        except ValueError:
            raise ConnectionError("server returned invalid response")

        if data.get('status') != "okay":
            reason = data.get('reason', 'unknown error')
            if "audience mismatch" in reason:
                raise AudienceMismatchError(data.get("audience"), audience)
            if "expired" in reason or "issued later than" in reason:
                raise ExpiredSignatureError(reason)
            raise InvalidSignatureError(reason)
        if self.allowed_issuers is not None:
            issuer = data.get('issuer')
            if issuer not in self.allowed_issuers:
                raise InvalidIssuerError("Issuer not allowed: %s" % (issuer, ))
        return data
    def verify(self, assertion, audience=None):
        if audience is None:
            audience = self.audiences

        body = {'assertion': assertion, 'audience': audience}
        if self.trusted_issuers is not None:
            body['trustedIssuers'] = self.trusted_issuers
        headers = {'content-type': 'application/json'}
        try:
            response = self.session.post(self.verifier_url,
                                         data=json.dumps(body),
                                         headers=headers,
                                         timeout=self.timeout)
        except (socket.error, requests.RequestException) as e:
            msg = "Failed to POST %s. Reason: %s" % (self.verifier_url, str(e))
            raise ConnectionError(msg)

        if response.status_code != 200:
            raise ConnectionError('server returned invalid response code')
        try:
            data = json.loads(response.text)
        except ValueError:
            raise ConnectionError("server returned invalid response body")

        if data.get('status') != "okay":
            reason = data.get('reason', 'unknown error')
            if "audience mismatch" in reason:
                raise AudienceMismatchError(data.get("audience"), audience)
            if "expired" in reason or "issued later than" in reason:
                raise ExpiredSignatureError(reason)
            raise InvalidSignatureError(reason)
        if self.allowed_issuers is not None:
            issuer = data.get('issuer')
            if issuer not in self.allowed_issuers:
                raise InvalidIssuerError("Issuer not allowed: %s" % (issuer,))
        return data
Beispiel #5
0
 def _check_cert_validity(cert):
     if cert.payload["exp"] < now:
         raise ExpiredSignatureError("expired certificate in chain")
Beispiel #6
0
    def verify(self, assertion, audience=None, now=None):
        """Verify the given BrowserID assertion.

        This method parses a BrowserID identity assertion, verifies the
        bundled chain of certificates and signatures, and returns the
        extracted email address and audience.

        If the 'audience' argument is given, it first verifies that the
        audience of the assertion matches the one given.  This can help
        avoid doing lots of crypto for assertions that can't be valid.
        If you don't specify an audience, you *MUST* validate the audience
        value returned by this method.

        If the 'now' argument is given, it is used as the current time in
        milliseconds.  This lets you verify expired assertions, e.g. for
        testing purposes.
        """
        if now is None:
            now = int(time.time() * 1000)

        # This catches KeyError and turns it into ValueError.
        # It saves having to test for the existence of individual
        # items in the various assertion payloads.
        try:
            # Check the audience against the given value, or the wildcards.
            self.check_audience(assertion, audience)

            # Grab the assertion, check that it has not expired.
            # No point doing all that crypto if we're going to fail out anyway.
            certificates, assertion = unbundle_certs_and_assertion(assertion)
            if len(certificates) > 1:
                raise UnsupportedCertChainError("too many certs")
            assertion = self.parse_jwt(assertion)
            if assertion.payload["exp"] < now:
                raise ExpiredSignatureError(assertion.payload["exp"])

            # Parse out the list of certificates.
            certificates = [self.parse_jwt(c) for c in certificates]

            # Extract the email, and the hostname of its provider.
            email = certificates[-1].payload["principal"]["email"]
            match = VALID_EMAIL.match(email)
            if match is None:
                raise ValueError("invalid email in assertion")
            provider = match.group(2)

            # Check that the root issuer is trusted.
            # No point doing all that crypto if we're going to fail out anyway.
            root_issuer = certificates[0].payload["iss"]
            if not self.is_trusted_issuer(provider, root_issuer):
                msg = "untrusted root issuer: %s" % (root_issuer, )
                raise InvalidSignatureError(msg)

            # Verify the entire chain of certificates.
            cert = self.verify_certificate_chain(certificates, now=now)

            # Check the signature on the assertion.
            if not self.check_token_signature(assertion, cert):
                raise InvalidSignatureError("invalid signature on assertion")
        except KeyError:
            raise ValueError("Malformed JWT")
        # Looks good!
        res = {
            "status": "okay",
            "audience": assertion.payload["aud"],
            "email": email,
            "issuer": root_issuer,
        }
        idpClaims = extract_extra_claims(certificates[-1])
        if idpClaims:
            res["idpClaims"] = idpClaims
        userClaims = extract_extra_claims(assertion)
        if userClaims:
            res["userClaims"] = userClaims
        return res