Esempio n. 1
0
    def verify(self, assertion, audience=None, now=None):
        """Verify the given VEP assertion.

        This method parses a VEP 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)
        data = decode_json_bytes(assertion)
        # This catches KeyError and turns it into ValueError.
        try:
            # Check that the assertion is usable and valid.
            # No point doing all that crypto if we're going to fail out anyway.
            assertion = JWT.parse(data["assertion"])
            if audience is not None:
                if assertion.payload["aud"] != audience:
                    raise AudienceMismatchError(assertion.payload["aud"])
            if assertion.payload["exp"] < now:
                raise ExpiredSignatureError(assertion.payload["exp"])
            # Parse out the list of certificates.
            certificates = data["certificates"]
            certificates = [JWT.parse(c) for c in certificates]
            # Check that the root issuer is trusted.
            # No point doing all that crypto if we're going to fail out anyway.
            email = certificates[-1].payload["principal"]["email"]
            root_issuer = certificates[0].payload["iss"]
            if root_issuer not in self.trusted_secondaries:
                if not email.endswith("@" + 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 assertion.check_signature(cert.payload["public-key"]):
                raise InvalidSignatureError("invalid signature on assertion")
        except KeyError:
            raise ValueError("Malformed JWT")
        # Looks good!
        return {
          "status": "okay",
          "audience": assertion.payload["aud"],
          "email": email,
          "issuer": root_issuer,
        }
Esempio n. 2
0
    def verify(self, assertion, audience=None, now=None):
        """Verify the given VEP assertion.

        This method parses a VEP 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)
        data = decode_json_bytes(assertion)
        # This catches KeyError and turns it into ValueError.
        try:
            # Check that the assertion is usable and valid.
            # No point doing all that crypto if we're going to fail out anyway.
            assertion = JWT.parse(data["assertion"])
            if audience is not None:
                if assertion.payload["aud"] != audience:
                    raise AudienceMismatchError(assertion.payload["aud"])
            if assertion.payload["exp"] < now:
                raise ExpiredSignatureError(assertion.payload["exp"])
            # Parse out the list of certificates.
            certificates = data["certificates"]
            certificates = [JWT.parse(c) for c in certificates]
            # Check that the root issuer is trusted.
            # No point doing all that crypto if we're going to fail out anyway.
            email = certificates[-1].payload["principal"]["email"]
            root_issuer = certificates[0].payload["iss"]
            if root_issuer not in self.trusted_secondaries:
                if not email.endswith("@" + 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 assertion.check_signature(cert.payload["public-key"]):
                raise InvalidSignatureError("invalid signature on assertion")
        except KeyError:
            raise ValueError("Malformed JWT")
        # Looks good!
        return {
            "status": "okay",
            "audience": assertion.payload["aud"],
            "email": email,
            "issuer": root_issuer,
        }
    def make_assertion(cls, email, audience, issuer=None, exp=None,
                       assertion_sig=None, certificate_sig=None):
        """Generate a new dummy assertion for the given email address.

        This method lets you generate VEP assertions using dummy private keys.
        Called with just an email and audience it will generate an assertion
        from browserid.org.

        By specifying the "exp", "assertion_sig" or "certificate_sig" arguments
        it is possible generate invalid assertions for testing purposes.
        """
        if issuer is None:
            issuer = "browserid.org"
        if exp is None:
            exp = int((time.time() + 60) * 1000)
        # Get private key for the email address itself.
        email_pub, email_priv = cls._get_keypair(email)
        # Get private key for the hostname so we can sign it.
        iss_pub, iss_priv = cls._get_keypair(issuer)
        # Generate the assertion, signed with email's public key.
        assertion = {
          "exp": exp,
          "aud": audience,
        }
        assertion = JWT.generate(assertion, email_priv)
        if assertion_sig is not None:
            assertion = ".".join(assertion.split(".")[:-1] +
                                 [encode_bytes(assertion_sig)])
        # Generate the certificate signing the email's public key
        # with the issuer's public key.
        certificate = {
          "iss": issuer,
          "exp": exp,
          "principal": {"email": email},
          "public-key": email_pub,
        }
        certificate = JWT.generate(certificate, iss_priv)
        if certificate_sig is not None:
            certificate = ".".join(certificate.split(".")[:-1] +
                                   [encode_bytes(certificate_sig)])
        # Combine them into a VEP bundled assertion.
        data = {
          "certificates": [certificate],
          "assertion": assertion,
        }
        return encode_bytes(json.dumps(data))
Esempio n. 4
0
 def test_error_handling_in_verify_certificate_chain(self):
     self.assertRaises(ValueError,
                       self.verifier.verify_certificate_chain, [])
     certs = decode_json_bytes(EXPIRED_ASSERTION)["certificates"]
     certs = [JWT.parse(cert) for cert in certs]
     self.assertRaises(ExpiredSignatureError,
                       self.verifier.verify_certificate_chain, certs)
     self.assertTrue(self.verifier.verify_certificate_chain(certs, 0))
Esempio n. 5
0
 def test_malformed_assertions(self):
     # This one doesn't actually contain an assertion
     assertion = encode_json_bytes({})
     self.assertRaises(ValueError, self.verifier.verify, assertion)
     # This one has no certificates
     pub, priv = DummyVerifier._get_keypair("TEST")
     assertion = encode_json_bytes({
         "assertion": JWT.generate({"aud": "TEST"}, priv),
         "certificates": []
     })
     self.assertRaises(ValueError, self.verifier.verify, assertion)
Esempio n. 6
0
 def test_error_jwt_with_mismatched_algorithm(self):
     pub, priv = DummyVerifier._get_keypair("TEST")
     jwt = JWT.generate({}, priv)
     jwt = JWT.parse(jwt)
     pub["algorithm"] = "RS"
     self.assertFalse(jwt.check_signature(pub))