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 test_parse_and_get_label(self): assert CryptoKey.parse_and_get_label( self.valid_key, "p256ecdsa") == ("BF92zdI_AKcH5Q31_Rr-04bPqOHU_Qg6lAawHbvfQrY" "xV_vIsAsHSyaiuyfofvxT8ZVIXccykd4V2Z7iJVfreT8") assert CryptoKey.parse_and_get_label(self.valid_key, "missing") is None assert CryptoKey.parse_and_get_label("invalid key", "missing") is None
def test_parse_lenient(self): ckey = CryptoKey(self.valid_key.replace('"', '')) str = ckey.to_string() ckey2 = CryptoKey(str) assert ckey.get_keyid("p256dh") == ckey2.get_keyid("p256dh") assert ckey.get_label("p256ecdsa") is not None assert ckey.get_label("p256ecdsa") == ckey2.get_label("p256ecdsa")
def test_string(self): ckey = CryptoKey(self.valid_key) str = ckey.to_string() ckey2 = CryptoKey(str) assert ckey.get_keyid("p256dh") == ckey2.get_keyid("p256dh") assert ckey.get_label("p256ecdsa") is not None assert ckey.get_label("p256ecdsa") == ckey2.get_label("p256ecdsa")
def test_string(self): ckey = CryptoKey(self.valid_key) str = ckey.to_string() ckey2 = CryptoKey(str) ok_(ckey.get_keyid("p256dh"), ckey2.get_keyid("p256dh")) ok_(ckey.get_label("p256ecdsa") is not None) ok_(ckey.get_label("p256ecdsa"), ckey2.get_label("p256ecdsa"))
def test_parse(self): ckey = CryptoKey(self.valid_key) eq_(ckey.get_keyid("p256dh"), {"keyid": "p256dh", "dh": "BDw9T0eImd4ax818VcYqDK_DOhcuDswKero" "YyNkdhYmygoLSDlSiWpuoWYUSSFxi25cyyNTR5k9Ny93DzZc0UI4"}) eq_(ckey.get_label("p256ecdsa"), "BF92zdI_AKcH5Q31_Rr-04bPqOHU_Qg6lAawHbvfQrY" "xV_vIsAsHSyaiuyfofvxT8ZVIXccykd4V2Z7iJVfreT8") ok_(ckey.get_keyid("missing") is None) ok_(ckey.get_label("missing") is None)
def validate_crypto_key(self, value): if CryptoKey.parse_and_get_label(value, "dh"): raise InvalidRequest( "Do not include 'dh' in aes128gcm " "Crypto-Key header", status_code=400, errno=110)
def validate_crypto_key(self, value): """Must contain a dh value""" dh = CryptoKey.parse_and_get_label(value, "dh") if not dh or not VALID_BASE64_URL.match("dh"): raise InvalidRequest("Invalid dh value in Encryption-Key header", status_code=400, errno=110)
def validate_encryption(self, value): if CryptoKey.parse_and_get_label(value, "salt"): raise InvalidRequest( "Do not include 'salt' in aes128gcm " "Encryption header", status_code=400, errno=110)
def validate_encryption(self, value): """Must contain a salt value""" salt = CryptoKey.parse_and_get_label(value, "salt") if not salt or not VALID_BASE64_URL.match(salt): raise InvalidRequest("Invalid salt value in Encryption header", status_code=400, errno=110)
def parse_endpoint(self, token, version="v0", ckey_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=) :raises ValueError: In the case of a malformed endpoint. :returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY) """ token = self.fernet.decrypt(token.encode('utf8')) public_key = None if ckey_header: try: crypto_key = CryptoKey(ckey_header) except CryptoKeyException: raise InvalidTokenException("Invalid key data") label = crypto_key.get_label('p256ecdsa') try: public_key = base64url_decode(label) except: # Ignore missing and malformed app server keys. pass if version == 'v0': if not VALID_V0_TOKEN.match(token): raise InvalidTokenException("Corrupted push token") items = token.split(':') return dict(uaid=items[0], chid=items[1], public_key=public_key) if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise InvalidTokenException("Invalid key data") if not constant_time.bytes_eq( sha256(public_key).digest(), token[32:]): raise InvalidTokenException("Key mismatch") return dict(uaid=token[:16].encode('hex'), chid=token[16:32].encode('hex'), public_key=public_key)
def parse_endpoint(self, token, version="v0", ckey_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=) :raises ValueError: In the case of a malformed endpoint. :returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY) """ token = self.fernet.decrypt(token.encode('utf8')) public_key = None if ckey_header: try: crypto_key = CryptoKey(ckey_header) except CryptoKeyException: raise InvalidTokenException("Invalid key data") label = crypto_key.get_label('p256ecdsa') try: public_key = base64url_decode(label) except: # Ignore missing and malformed app server keys. pass if version == 'v0': if not VALID_V0_TOKEN.match(token): raise InvalidTokenException("Corrupted push token") items = token.split(':') return dict(uaid=items[0], chid=items[1], public_key=public_key) if version == 'v1' and len(token) != 32: raise InvalidTokenException("Corrupted push token") if version == 'v2': if len(token) != 64: raise InvalidTokenException("Corrupted push token") if not public_key: raise InvalidTokenException("Invalid key data") if not constant_time.bytes_eq(sha256(public_key).digest(), token[32:]): raise InvalidTokenException("Key mismatch") return dict(uaid=token[:16].encode('hex'), chid=token[16:32].encode('hex'), public_key=public_key)
def test_parse_lenient(self): ckey = CryptoKey(self.valid_key.replace('"', '')) str = ckey.to_string() ckey2 = CryptoKey(str) ok_(ckey.get_keyid("p256dh"), ckey2.get_keyid("p256dh")) ok_(ckey.get_label("p256ecdsa") is not None) ok_(ckey.get_label("p256ecdsa"), ckey2.get_label("p256ecdsa"))
def validate_crypto_key(self, value): """Must not contain a dh value""" dh = CryptoKey.parse_and_get_label(value, "dh") if dh: raise InvalidRequest( "dh value in Crypto-Key header not valid for 01 or earlier " "webpush-encryption", status_code=400, errno=110, )
def test_parse(self): ckey = CryptoKey(self.valid_key) assert ckey.get_keyid("p256dh") == { "keyid": "p256dh", "dh": "BDw9T0eImd4ax818VcYqDK_DOhcuDswKero" "YyNkdhYmygoLSDlSiWpuoWYUSSFxi25cyyNTR5k9Ny93DzZc0UI4" } assert ckey.get_label("p256ecdsa") == ( "BF92zdI_AKcH5Q31_Rr-04bPqOHU_Qg6lAawHbvfQrY" "xV_vIsAsHSyaiuyfofvxT8ZVIXccykd4V2Z7iJVfreT8") assert ckey.get_keyid("missing") is None assert ckey.get_label("missing") is None
def test_parse_different_order(self): ckey = CryptoKey(self.valid_key) ckey2 = CryptoKey(','.join(self.valid_key.split(',')[::-1])) assert ckey.get_keyid("p256dh") == ckey2.get_keyid("p256dh") assert ckey.get_label("p256ecdsa") is not None assert ckey.get_label("p256ecdsa") == ckey2.get_label("p256ecdsa")
def test_parse_invalid(self): with pytest.raises(CryptoKeyException) as ex: CryptoKey("invalid key") assert ex.value.message == "Invalid Crypto Key value"
def test_parse_different_order(self): ckey = CryptoKey(self.valid_key) ckey2 = CryptoKey(','.join(self.valid_key.split(',')[::-1])) ok_(ckey.get_keyid("p256dh"), ckey2.get_keyid("p256dh")) ok_(ckey.get_label("p256ecdsa") is not None) ok_(ckey.get_label("p256ecdsa"), ckey2.get_label("p256ecdsa"))