def parse_endpoint(self, metrics, token, version="v1", ckey_header=None, auth_header=None): """Parse an endpoint into component elements of UAID, CHID and optional key hash if v2 :param token: The obscured subscription data. :param version: This is the API version of the token. :param ckey_header: the Crypto-Key header bearing the public key (from Crypto-Key: p256ecdsa=) :param auth_header: The Authorization header bearing the VAPID info :raises ValueError: In the case of a malformed endpoint. :returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY) """ token = self.fernet.decrypt(repad(token).encode('utf8')) public_key = None if ckey_header: try: crypto_key = CryptoKey(ckey_header) except CryptoKeyException: raise InvalidTokenException("Invalid key data") public_key = crypto_key.get_label('p256ecdsa') if auth_header: vapid_auth = parse_auth_header(auth_header) if not vapid_auth: raise VapidAuthException("Invalid Auth token") metrics.increment("updates.notification.auth.{}".format( vapid_auth['scheme'])) # pull the public key from the VAPID auth header if needed try: if vapid_auth['version'] != 1: public_key = vapid_auth['k'] except KeyError: raise VapidAuthException("Missing Public Key") if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if not auth_header: raise VapidAuthException("Missing Authorization Header") if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise VapidAuthException("Invalid key data") try: decoded_key = base64url_decode(public_key) except TypeError: raise VapidAuthException("Invalid key data") if not constant_time.bytes_eq( sha256(decoded_key).digest(), token[32:]): raise VapidAuthException("Key mismatch") return dict(uaid=token[:16].encode('hex'), chid=token[16:32].encode('hex'), version=version, public_key=public_key)
def validate_auth(self, d): auth = d["headers"].get("authorization") needs_auth = d["token_info"]["api_ver"] == "v2" if not needs_auth and not auth: return try: vapid_auth = parse_auth_header(auth) token = vapid_auth['t'] d["vapid_version"] = "draft{:0>2}".format(vapid_auth['version']) if vapid_auth['version'] == 2: public_key = vapid_auth['k'] else: public_key = d["subscription"].get("public_key") jwt = extract_jwt( token, public_key, is_trusted=self.context['settings'].enable_tls_auth) except (KeyError, ValueError, InvalidSignature, TypeError, VapidAuthException): raise InvalidRequest("Invalid Authorization Header", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) if "exp" not in jwt: raise InvalidRequest("Invalid bearer token: No expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) try: jwt_expires = int(jwt['exp']) except ValueError: raise InvalidRequest("Invalid bearer token: Invalid expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) now = time.time() jwt_has_expired = now > jwt_expires if jwt_has_expired: raise InvalidRequest("Invalid bearer token: Auth expired", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24) if jwt_too_far_in_future: raise InvalidRequest( "Invalid bearer token: Auth > 24 hours in " "the future", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_crypto_key = base64url_encode(public_key) d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)
def validate_auth(self, d): crypto_exceptions = [ KeyError, ValueError, TypeError, VapidAuthException ] if self.context['conf'].use_cryptography: crypto_exceptions.append(InvalidSignature) else: crypto_exceptions.extend([JOSEError, JWTError, AssertionError]) auth = d["headers"].get("authorization") needs_auth = d["token_info"]["api_ver"] == "v2" if not needs_auth and not auth: return try: vapid_auth = parse_auth_header(auth) token = vapid_auth['t'] d["vapid_version"] = "draft{:0>2}".format(vapid_auth['version']) if vapid_auth['version'] == 2: public_key = vapid_auth['k'] else: public_key = d["subscription"].get("public_key") jwt = extract_jwt(token, public_key, is_trusted=self.context['conf'].enable_tls_auth, use_crypto=self.context['conf'].use_cryptography) if not isinstance(jwt, Dict): raise InvalidRequest("Invalid Authorization Header", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) except tuple(crypto_exceptions): raise InvalidRequest("Invalid Authorization Header", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) if "aud" not in jwt: raise InvalidRequest("Invalid bearer token: No Audience specified", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) if jwt['aud'] != self.context["conf"].endpoint_url: raise InvalidRequest( "Invalid bearer token: Invalid Audience Specified", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) if "exp" not in jwt: raise InvalidRequest("Invalid bearer token: No expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) try: jwt_expires = int(jwt['exp']) except (TypeError, ValueError): raise InvalidRequest("Invalid bearer token: Invalid expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) now = time.time() jwt_has_expired = now > jwt_expires if jwt_has_expired: raise InvalidRequest("Invalid bearer token: Auth expired", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24) if jwt_too_far_in_future: raise InvalidRequest( "Invalid bearer token: Auth > 24 hours in " "the future", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_crypto_key = base64url_encode(public_key) d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)