Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Commit

Permalink
feat: make cryptography lib optional
Browse files Browse the repository at this point in the history
Closes #979
  • Loading branch information
jrconlin committed Jul 22, 2017
1 parent 0d14662 commit c96fea9
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 10 deletions.
2 changes: 2 additions & 0 deletions autopush/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def from_argparse(cls, ns):
cors=not ns.no_cors,
bear_hash_key=ns.auth_key,
proxy_protocol_port=ns.proxy_protocol_port,
use_cryptography=ns.use_cryptography,
)


Expand Down Expand Up @@ -281,4 +282,5 @@ def from_argparse(cls, ns):
auto_ping_timeout=ns.auto_ping_timeout,
max_connections=ns.max_connections,
close_handshake_timeout=ns.close_handshake_timeout,
use_cryptography=ns.use_cryptography,
)
4 changes: 4 additions & 0 deletions autopush/main_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ def add_shared_args(parser):
help="Enable the debug _memusage API on Port",
type=int, default=None,
env_var='MEMUSAGE_PORT')
parser.add_argument('--use_cryptography',
help="Use the cryptography library vs. JOSE",
action="store_true",
default=False, env_var="USE_CRYPTOGRAPHY")
# No ENV because this is for humans
_add_external_router_args(parser)
_obsolete_args(parser)
Expand Down
3 changes: 3 additions & 0 deletions autopush/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class AutopushSettings(object):
# generate legacy data.
_notification_legacy = attrib(default=False) # type: bool

# Use the cryptography library
use_cryptography = attrib(default=False) # type: bool

def __attrs_post_init__(self):
"""Initialize the Settings object"""
# Setup hosts/ports/urls
Expand Down
2 changes: 2 additions & 0 deletions autopush/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ class IntegrationBase(unittest.TestCase):
router_tablename=ROUTER_TABLE,
storage_tablename=STORAGE_TABLE,
message_tablename=MESSAGE_TABLE,
use_cryptography=True,
)

_conn_defaults = dict(
Expand All @@ -359,6 +360,7 @@ class IntegrationBase(unittest.TestCase):
router_tablename=ROUTER_TABLE,
storage_tablename=STORAGE_TABLE,
message_tablename=MESSAGE_TABLE,
use_cryptography=True,
)

def setUp(self):
Expand Down
13 changes: 12 additions & 1 deletion autopush/tests/test_web_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -856,8 +856,9 @@ def test_valid_vapid_crypto_header(self):
eq_(errors, {})
ok_("jwt" in result)

def test_valid_vapid_crypto_header_webpush(self):
def test_valid_vapid_crypto_header_webpush(self, use_crypto=False):
schema = self._make_fut()
schema.context["settings"].use_cryptography = use_crypto

header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://pusher_origin.example.com",
Expand Down Expand Up @@ -887,6 +888,9 @@ def test_valid_vapid_crypto_header_webpush(self):
eq_(errors, {})
ok_("jwt" in result)

def test_valid_vapid_crypto_header_webpush_crypto(self):
self.test_valid_vapid_crypto_header_webpush(use_crypto=True)

def test_valid_vapid_02_crypto_header_webpush(self):
schema = self._make_fut()

Expand Down Expand Up @@ -985,6 +989,8 @@ def test_bad_vapid_02_crypto_header(self):

def test_invalid_vapid_draft2_crypto_header(self):
schema = self._make_fut()
schema.context["settings"].use_cryptography = True

header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://pusher_origin.example.com",
"exp": int(time.time()) + 86400,
Expand Down Expand Up @@ -1018,6 +1024,8 @@ def test_invalid_vapid_draft2_crypto_header(self):
@patch("autopush.web.webpush.extract_jwt")
def test_invalid_vapid_crypto_header(self, mock_jwt):
schema = self._make_fut()
schema.context["settings"].use_cryptography = True

mock_jwt.side_effect = ValueError("Unknown public key "
"format specified")

Expand Down Expand Up @@ -1154,6 +1162,7 @@ def test_invalid_encryption_header(self, mock_jwt):
@patch("autopush.web.webpush.extract_jwt")
def test_invalid_encryption_jwt(self, mock_jwt):
schema = self._make_fut()
schema.context['settings'].use_cryptography = True
# use a deeply superclassed error to make sure that it gets picked up.
mock_jwt.side_effect = InvalidSignature("invalid signature")

Expand Down Expand Up @@ -1225,6 +1234,7 @@ def test_invalid_crypto_key_header_content(self, mock_jwt):

def test_expired_vapid_header(self):
schema = self._make_fut()
schema.context["settings"].use_cryptography = True

header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://pusher_origin.example.com",
Expand Down Expand Up @@ -1291,6 +1301,7 @@ def test_missing_vapid_header(self):

def test_bogus_vapid_header(self):
schema = self._make_fut()
schema.context["settings"].use_cryptography = True

header = {"typ": "JWT", "alg": "ES256"}
payload = {
Expand Down
1 change: 1 addition & 0 deletions autopush/tests/test_web_webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def setUp(self):
self.ap_settings = settings = AutopushSettings(
hostname="localhost",
statsd_host=None,
use_cryptography=True,
)
self.fernet_mock = settings.fernet = Mock(spec=Fernet)

Expand Down
28 changes: 22 additions & 6 deletions autopush/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
Tuple,
)
from ua_parser import user_agent_parser
import ecdsa
from jose import jwt

from autopush.exceptions import (InvalidTokenException, VapidAuthException)
from autopush.jwt import repad, VerifyJWT as jwt
from autopush.jwt import repad, VerifyJWT
from autopush.types import ItemLike # noqa
from autopush.web.base import AUTH_SCHEMES

Expand Down Expand Up @@ -188,17 +190,31 @@ def decipher_public_key(key_data):
raise ValueError("Unknown public key format specified")


def extract_jwt(token, crypto_key, is_trusted=False):
def extract_jwt(token, crypto_key, is_trusted=False, use_crypto=False):
# type: (str, str, bool) -> Dict[str, str]
"""Extract the claims from the validated JWT. """
# first split and convert the jwt.
if not token or not crypto_key:
return {}
if is_trusted:
return jwt.extract_assertion(token)
return jwt.validate_and_extract_assertion(
token,
decipher_public_key(crypto_key.encode('utf8')))
return VerifyJWT.extract_assertion(token)
if use_crypto:
return VerifyJWT.validate_and_extract_assertion(
token,
decipher_public_key(crypto_key.encode('utf8')))
else:
key = ecdsa.VerifyingKey.from_string(
base64.urlsafe_b64decode(
repad(crypto_key.encode('utf8')))[-64:],
curve=ecdsa.NIST256p
)
return jwt.decode(token,
dict(keys=[key]),
options=dict(
verify_aud=False,
verify_sub=False,
verify_exp=False,
))


def parse_user_agent(agent_string):
Expand Down
15 changes: 12 additions & 3 deletions autopush/web/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Dict,
Optional
)
from jose import JOSEError, JWTError

from autopush.crypto_key import CryptoKey
from autopush.db import DatabaseManager # noqa
Expand Down Expand Up @@ -356,6 +357,14 @@ def token_prep(self, d):
return d

def validate_auth(self, d):
crypto_exceptions = [KeyError, ValueError, TypeError,
VapidAuthException]

if self.context['settings'].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:
Expand All @@ -372,10 +381,10 @@ def validate_auth(self, d):
jwt = extract_jwt(
token,
public_key,
is_trusted=self.context['settings'].enable_tls_auth
is_trusted=self.context['settings'].enable_tls_auth,
use_crypto=self.context['settings'].use_cryptography
)
except (KeyError, ValueError, InvalidSignature, TypeError,
VapidAuthException):
except tuple(crypto_exceptions):
raise InvalidRequest("Invalid Authorization Header",
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ objgraph
pyasn1
pyfcm
pyopenssl
python-jose
raven
requests
service-identity
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ cyclone==1.1
datadog==0.16.0
decorator==4.0.11 # via datadog
docutils==0.13.1 # via botocore
ecdsa==0.13 # via python-jose
enum34==1.1.6 # via h2
futures==3.1.1 # via s3transfer
gcm-client==0.1.4
Expand All @@ -41,6 +42,7 @@ pycparser==2.17 # via cffi
pyfcm==1.3.1
pyopenssl==17.1.0
python-dateutil==2.6.1 # via botocore
python-jose==1.3.2
raven==6.1.0
requests-toolbelt==0.8.0 # via pyfcm
requests==2.18.1
Expand Down

0 comments on commit c96fea9

Please sign in to comment.