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 get_email_and_assertion(audience): global _email_assertion if not _email_assertion: import pickle try: import datetime from browserid.utils import unbundle_certs_and_assertion, decode_json_bytes # Get one from the cache, and check it's expiration email_assertion = pickle.load(open(ASSERTION_CACHE_FILE, 'rb')) _, assertion = unbundle_certs_and_assertion(email_assertion[1]) exp = decode_json_bytes(assertion.split('.')[1])['exp'] expiration_date = datetime.datetime.utcfromtimestamp(exp/1000) if expiration_date > datetime.datetime.utcnow(): # If it hasn't expired, return _email_assertion = email_assertion return email_assertion except FileNotFoundError: pass # If no good one is available, get a new one import requests from urllib.parse import quote_plus r = requests.get('http://personatestuser.org/email_with_assertion/%s'%quote_plus(audience)) _email_assertion = r.json()['email'], r.json()['assertion'] pickle.dump(_email_assertion, open(ASSERTION_CACHE_FILE, 'wb')) return _email_assertion def _login(self): res = self.testapp.get('/', status=403)
def check_audience(self, assertion, expected_audience=None): """Check that the assertion matches the expected audience. This method verifies that the audience for the given assertion is as expected - either matching the audience parameter if given, or or matching one of the audience patterns from the constructor if not. If the audience matches then it is returned as a string; if not then an AudienceMismatchError is raised. """ try: _, token = unbundle_certs_and_assertion(assertion) audience = decode_json_bytes(token.split(".")[1])["aud"] except (KeyError, IndexError): raise ValueError("Malformed JWT") if expected_audience is None: audience_re = self._audience_re else: audience_re = self._compile_audience_patterns(expected_audience) if audience_re is None: raise AudienceMismatchError if not audience_re.match(audience): raise AudienceMismatchError return audience
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
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