diff --git a/python/README.rst b/python/README.rst index d3d3645..e54c498 100644 --- a/python/README.rst +++ b/python/README.rst @@ -1,4 +1,3 @@ -`PyPI version py_vapid `__ Easy VAPID generation ===================== @@ -95,3 +94,6 @@ that some User Agents may require you `to decode this string into a Uint8Array `__. See ``bin/vapid -h`` for all options and commands. + +.. |PyPI version py_vapid| image:: https://badge.fury.io/py/py-vapid.svg + :target: https://pypi.org/project/py-vapid/ diff --git a/python/claims.json b/python/claims.json index e4ddac4..a5bc959 100644 --- a/python/claims.json +++ b/python/claims.json @@ -1,6 +1,5 @@ { "sub": "mailto:admin@example.com", - "aud": "https://push.services.mozilla.com", - "exp": "1463001340" + "aud": "https://push.services.mozilla.com" } diff --git a/python/py_vapid/__init__.py b/python/py_vapid/__init__.py index 8adfffb..1103582 100644 --- a/python/py_vapid/__init__.py +++ b/python/py_vapid/__init__.py @@ -110,6 +110,7 @@ def from_file(cls, private_key_file=None): """ if not os.path.isfile(private_key_file): + logging.info("Private key not found, generating key...") vapid = cls() vapid.generate_keys() vapid.save_key(private_key_file) diff --git a/python/py_vapid/tests/test_vapid.py b/python/py_vapid/tests/test_vapid.py index bba60ef..d6fd743 100644 --- a/python/py_vapid/tests/test_vapid.py +++ b/python/py_vapid/tests/test_vapid.py @@ -5,7 +5,6 @@ import json import unittest from cryptography.hazmat.primitives import serialization -from nose.tools import eq_, ok_ from mock import patch, Mock from py_vapid import Vapid01, Vapid02, VapidException @@ -49,7 +48,7 @@ ).strip('=').encode('utf8') -def setUp(self): +def setup_module(self): with open('/tmp/private', 'w') as ff: ff.write(TEST_KEY_PRIVATE_PEM) with open('/tmp/public', 'w') as ff: @@ -58,16 +57,16 @@ def setUp(self): ff.write(TEST_KEY_PRIVATE_DER) -def tearDown(self): +def teardown_module(self): os.unlink('/tmp/private') os.unlink('/tmp/public') class VapidTestCase(unittest.TestCase): def check_keys(self, v): - eq_(v.private_key.private_numbers().private_value, key.get('d')) - eq_(v.public_key.public_numbers().x, key.get('x')) - eq_(v.public_key.public_numbers().y, key.get('y')) + assert v.private_key.private_numbers().private_value == key.get('d') + assert v.public_key.public_numbers().x == key.get('x') + assert v.public_key.public_numbers().y == key.get('y') def test_init(self): v1 = Vapid01.from_file("/tmp/private") @@ -80,7 +79,7 @@ def test_init(self): self.check_keys(v4) no_exist = '/tmp/not_exist' Vapid01.from_file(no_exist) - ok_(os.path.isfile(no_exist)) + assert os.path.isfile(no_exist) os.unlink(no_exist) def repad(self, data): @@ -95,8 +94,8 @@ def test_init_bad_read(self, mm): def test_gen_key(self): v = Vapid01() v.generate_keys() - ok_(v.public_key) - ok_(v.private_key) + assert v.public_key + assert v.private_key def test_private_key(self): v = Vapid01() @@ -105,8 +104,8 @@ def test_private_key(self): def test_public_key(self): v = Vapid01() - eq_(v._private_key, None) - eq_(v._public_key, None) + assert v._private_key is None + assert v._public_key is None def test_save_key(self): v = Vapid01() @@ -135,7 +134,7 @@ def test_sign_01(self): claims = {"aud": "https://example.com", "sub": "mailto:admin@example.com"} result = v.sign(claims, "id=previous") - eq_(result['Crypto-Key'], + assert result['Crypto-Key'] == ( 'id=previous;p256ecdsa=' + TEST_KEY_PUBLIC_RAW.decode('utf8')) pkey = binascii.b2a_base64( v.public_key.public_bytes( @@ -145,16 +144,16 @@ def test_sign_01(self): ).decode('utf8').replace('+', '-').replace('/', '_').strip() items = decode(result['Authorization'].split(' ')[1], pkey) for k in claims: - eq_(items[k], claims[k]) + assert items[k] == claims[k] result = v.sign(claims) - eq_(result['Crypto-Key'], - 'p256ecdsa=' + TEST_KEY_PUBLIC_RAW.decode('utf8')) + assert result['Crypto-Key'] == ('p256ecdsa=' + + TEST_KEY_PUBLIC_RAW.decode('utf8')) # Verify using the same function as Integration # this should ensure that the r,s sign values are correctly formed - ok_(Vapid01.verify( + assert Vapid01.verify( key=result['Crypto-Key'].split('=')[1], auth=result['Authorization'] - )) + ) def test_sign_02(self): v = Vapid02.from_file("/tmp/private") @@ -164,20 +163,20 @@ def test_sign_02(self): claim_check = copy.deepcopy(claims) result = v.sign(claims, "id=previous") auth = result['Authorization'] - eq_(auth[:6], 'vapid ') - ok_(' t=' in auth) - ok_(',k=' in auth) + assert auth[:6] == 'vapid ' + assert ' t=' in auth + assert ',k=' in auth parts = auth[6:].split(',') - eq_(len(parts), 2) + assert len(parts) == 2 t_val = json.loads(base64.urlsafe_b64decode( self.repad(parts[0][2:].split('.')[1]) ).decode('utf8')) k_val = binascii.a2b_base64(self.repad(parts[1][2:])) - eq_(binascii.hexlify(k_val)[:2], b'04') - eq_(len(k_val), 65) - eq_(claims, claim_check) + assert binascii.hexlify(k_val)[:2] == b'04' + assert len(k_val) == 65 + assert claims == claim_check for k in claims: - eq_(t_val[k], claims[k]) + assert t_val[k] == claims[k] def test_sign_02_localhost(self): v = Vapid02.from_file("/tmp/private") @@ -186,9 +185,9 @@ def test_sign_02_localhost(self): "foo": "extra value"} result = v.sign(claims, "id=previous") auth = result['Authorization'] - eq_(auth[:6], 'vapid ') - ok_(' t=' in auth) - ok_(',k=' in auth) + assert auth[:6] == 'vapid ' + assert ' t=' in auth + assert ',k=' in auth def test_integration(self): # These values were taken from a test page. DO NOT ALTER! @@ -199,8 +198,8 @@ def test_integration(self): "4cCI6MTQ5NDY3MTQ3MCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlb" "W9AZ2F1bnRmYWNlLmNvLnVrIn0.LqPi86T-HJ71TXHAYFptZEHD7Wlfjcc" "4u5jYZ17WpqOlqDcW-5Wtx3x1OgYX19alhJ9oLumlS2VzEvNioZolQA") - ok_(Vapid01.verify(key=key, auth="webpush {}".format(auth))) - ok_(Vapid02.verify(auth="vapid t={},k={}".format(auth, key))) + assert Vapid01.verify(key=key, auth="webpush {}".format(auth)) + assert Vapid02.verify(auth="vapid t={},k={}".format(auth, key)) def test_bad_integration(self): # These values were taken from a test page. DO NOT ALTER! @@ -211,7 +210,7 @@ def test_bad_integration(self): "4cCI6MTQ5NDY3MTQ3MCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlb" "W9AZ2F1bnRmYWNlLmNvLnVrIn0.LqPi86T-HJ71TXHAYFptZEHD7Wlfjcc" "4u5jYZ17WpqOlqDcW-5Wtx3x1OgYX19alhJ9oLumlS2VzEvNioZ_BAD") - eq_(Vapid01.verify(key=key, auth=auth), False) + assert Vapid01.verify(key=key, auth=auth) == False def test_bad_sign(self): v = Vapid01.from_file("/tmp/private") diff --git a/python/setup.py b/python/setup.py index 0dceeef..c59bc07 100644 --- a/python/setup.py +++ b/python/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages -__version__ = "1.7.1" +__version__ = "1.8.0" def read_from(file): @@ -24,31 +24,27 @@ def read_from(file): here = os.path.abspath(os.path.dirname(__file__)) with io.open(os.path.join(here, 'README.rst'), encoding='utf8') as f: README = f.read() -with io.open(os.path.join(here, 'CHANGELOG.md'), encoding='utf8') as f: - CHANGES = f.read() +#with io.open(os.path.join(here, 'CHANGELOG.md'), encoding='utf8') as f: +# CHANGES = f.read() setup(name="py-vapid", version=__version__, description='Simple VAPID header generation library', - long_description=README + '\n\n' + CHANGES, + long_description=README, + long_description_content_type="text/x-rst", classifiers=["Topic :: Internet :: WWW/HTTP", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", ], keywords='vapid push webpush', author="JR Conlin", author_email="src+vapid@jrconlin.com", url='https://github.com/mozilla-services/vapid', license="MPL2", - test_suite="nose.collector", include_package_data=True, zip_safe=False, packages=find_packages(), - package_data={'': ['README.md', 'CHANGELOG.md', + package_data={'': ['README.rst', 'CHANGELOG.rst', 'requirements.txt', 'test-requirements.txt']}, install_requires=read_from('requirements.txt'), tests_require=read_from('test-requirements.txt'), diff --git a/python/test-requirements.txt b/python/test-requirements.txt index 6cd2359..bfce84f 100644 --- a/python/test-requirements.txt +++ b/python/test-requirements.txt @@ -1,4 +1,5 @@ -nose +-r requirements.txt +pytest coverage mock>=1.0.1 flake8 diff --git a/python/upload.sh b/python/upload.sh index a1dc144..088999b 100644 --- a/python/upload.sh +++ b/python/upload.sh @@ -1,6 +1,7 @@ #!/bin/sh # Package the current branch up to pypi # remember to update the README.rst file -pandoc --from=markdown --to=rst --output README.rst README.md -bin/python setup.py sdist -bin/twine upload dist/* +#pandoc --from=markdown --to=rst --output README.rst README.md +#pandoc --from=markdown --to=rst --output CHANGELOG.rst CHANGELOG.md +venv/bin/python setup.py sdist +venv/bin/twine upload dist/* --verbose diff --git a/rust/vapid/Cargo.toml b/rust/vapid/Cargo.toml index 07d5d50..b002def 100644 --- a/rust/vapid/Cargo.toml +++ b/rust/vapid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vapid" -version = "0.2.0" +version = "0.3.0" authors = ["jrconlin "] edition = "2018" description = "An implementation of the RFC 8292 Voluntary Application Server Identification (VAPID) Auth header generator" @@ -10,6 +10,6 @@ license = "MPL 2.0" [dependencies] openssl = "0.10" serde_json = "1.0" -base64 = "0.10" -time = "0.1" +base64 = "0.13" +time = "0.2" failure = "0.1" diff --git a/rust/vapid/src/error.rs b/rust/vapid/src/error.rs index 8508234..6fd6505 100644 --- a/rust/vapid/src/error.rs +++ b/rust/vapid/src/error.rs @@ -23,7 +23,7 @@ pub enum VapidErrorKind { } impl Fail for VapidError { - fn cause(&self) -> Option<&Fail> { + fn cause(&self) -> Option<&dyn Fail> { self.inner.cause() } diff --git a/rust/vapid/src/lib.rs b/rust/vapid/src/lib.rs index 3d9b181..b3e3c0f 100644 --- a/rust/vapid/src/lib.rs +++ b/rust/vapid/src/lib.rs @@ -30,11 +30,7 @@ //! //! ``` -extern crate base64; -extern crate failure; -extern crate openssl; -extern crate serde_json; -extern crate time; +use std::time::SystemTime; use std::collections::HashMap; use std::fs; @@ -170,6 +166,12 @@ fn parse_auth_token(auth_token: &str) -> Result { // Preferred schema static SCHEMA: &str = "vapid"; +fn to_secs(t: SystemTime) -> u64 { + t.duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() +} + /// Convert the HashMap containing the claims into an Authorization header. /// `key` must be generated or initialized before this is used. See `Key::from_pem()` or /// `Key::generate()`. @@ -194,21 +196,21 @@ pub fn sign( return Err(error::VapidErrorKind::VapidError("'sub' not found".to_owned()).into()); } } - let today = time::now_utc(); + let today = SystemTime::now(); let tomorrow = today + time::Duration::hours(24); claims .entry(String::from("exp")) - .or_insert_with(|| serde_json::Value::from(tomorrow.to_timespec().sec)); + .or_insert_with(|| serde_json::Value::from(to_secs(tomorrow))); match claims.get("exp") { Some(exp) => { let exp_val = exp.as_i64().unwrap(); - if exp_val < today.to_timespec().sec { + if (exp_val as u64) < to_secs(today) { return Err(error::VapidErrorKind::VapidError( r#""exp" already expired"#.to_owned(), ) .into()); } - if exp_val > tomorrow.to_timespec().sec { + if (exp_val as u64) > to_secs(tomorrow) { return Err(error::VapidErrorKind::VapidError( r#""exp" set too far ahead"#.to_owned(), ) @@ -273,7 +275,7 @@ pub fn sign( let auth_t = format!( "{}.{}", - content.clone(), + content, base64::encode_config( unsafe { &String::from_utf8_unchecked(sigval) }, base64::URL_SAFE_NO_PAD, @@ -289,7 +291,7 @@ pub fn sign( pub fn verify(auth_token: String) -> Result, String> { //Verify that the auth token string matches for the verification token string let auth_token = - parse_auth_token(&auth_token.clone()).expect("Authorization header is invalid."); + parse_auth_token(&auth_token).expect("Authorization header is invalid."); let pub_ec_key = Key::from_public_raw(auth_token.k).expect("'k' token is not a valid public key"); let pub_key = &match PKey::from_ec_key(pub_ec_key) { @@ -487,5 +489,4 @@ mod tests { } //TODO: Add key input/output tests here. - }