|
| 1 | +import jwt |
| 2 | +import py_vapid |
| 3 | +from time import time |
| 4 | +from cryptography import ec |
| 5 | +from machine import RTC |
| 6 | + |
| 7 | + |
| 8 | +""" |
| 9 | +Run tests by executing: |
| 10 | +
|
| 11 | +``` |
| 12 | +mpremote fs cp py_vapid/__init__.py :lib/py_vapid.py + run test_vapid.py |
| 13 | +``` |
| 14 | +
|
| 15 | +The [ucryptography](https://github.com/dmazzella/ucryptography) library must |
| 16 | +be present in the firmware for this library and tests to work. |
| 17 | +""" |
| 18 | + |
| 19 | +rtc = RTC() |
| 20 | + |
| 21 | +GOLDEN_0 = ( |
| 22 | + 0xEB6DFB26C7A3C23D33C60F7C7BA61B6893451F2643E0737B20759E457825EE75, |
| 23 | + (2010, 1, 1, 0, 0, 0, 0, 0), |
| 24 | + { |
| 25 | + "aud": "https://updates.push.services.mozilla.com", |
| 26 | + "sub": "mailto:[email protected]", |
| 27 | + "exp": 9876543, |
| 28 | + }, |
| 29 | + "vapid t=eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFUzI1NiJ9.eyJhdWQiOiAiaHR0cHM6Ly91cGRhdGVzLnB1c2guc2VydmljZXMubW96aWxsYS5jb20iLCAic3ViIjogIm1haWx0bzphZG1pbkBleGFtcGxlLmNvbSIsICJleHAiOiA5ODc2NTQzfQ.DLB6PF2RApzk0n0oH-Kv_Onuwg9C7VXakM-GlEMCwj50rQ7G0hF_vLIYzCPeXT8Hu8Uup900YBapZ9y45vc8QA,k=BKoKs6nJ3466nCEQ5TvFkBIGBKSGplPTUBzJlLXM13I8S0SF-o_NSB-Q4At3BeLSrZVptEd5xBuGRXCKMe_YRg8", |
| 30 | +) |
| 31 | + |
| 32 | +GOLDEN_1 = ( |
| 33 | + 0x4370082632776C74FDC5517AC12881413A60B25D10E863296AD67E4260A3BF56, |
| 34 | + (2015, 1, 1, 0, 0, 0, 0, 0), |
| 35 | + { |
| 36 | + "aud": "https://updates.push.services.mozilla.com", |
| 37 | + "sub": "mailto:[email protected]", |
| 38 | + }, |
| 39 | + "vapid t=eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFUzI1NiJ9.eyJleHAiOiAxNDIwMTU2ODAwLCAic3ViIjogIm1haWx0bzphZG1pbkBleGFtcGxlLmNvbSIsICJhdWQiOiAiaHR0cHM6Ly91cGRhdGVzLnB1c2guc2VydmljZXMubW96aWxsYS5jb20ifQ.NlVtqjGWy-hvNtoScrwAv-4cpNYrgUJ4EVgtxTnIn-haPtBSpak7aQN518tVYelQB1TZqc0bxAjWfK9QvZUbOA,k=BGEwf7m9F3vCvOuPeN4pEZ91t-dpSmg_y8ZXMfOyl-f22zw10ho_4EeBqZj2-NtW_Kb98b6tGjOKO_-TJiWvyfo", |
| 40 | +) |
| 41 | + |
| 42 | +# Set of opaquely known-good scenarios to check against |
| 43 | +golden_test_cases = [GOLDEN_0, GOLDEN_1] |
| 44 | + |
| 45 | + |
| 46 | +# Test basic validation of claim |
| 47 | +private_key_0 = ec.derive_private_key( |
| 48 | + 0x5C76C15BBC541E7BF6987557124A6E6EB745723B1CF20E2ED2A3ED5B7C16DD46, ec.SECP256R1() |
| 49 | +) |
| 50 | +vapid = py_vapid.Vapid(private_key=private_key_0) |
| 51 | +rtc.datetime((2018, 1, 1, 0, 0, 0, 0, 0)) |
| 52 | +headers = vapid.sign( |
| 53 | + { |
| 54 | + "aud": "https://fcm.googleapis.com", |
| 55 | + "sub": "mailto:[email protected]", |
| 56 | + "exp": 1493315200, |
| 57 | + } |
| 58 | +) |
| 59 | + |
| 60 | +actual_token = headers["Authorization"].split(" ")[1].split(",")[0].split("=")[1] |
| 61 | +actual_decoded_claim = jwt.decode(actual_token, private_key_0.public_key(), "ES256") |
| 62 | +assert ( |
| 63 | + actual_decoded_claim["aud"] == "https://fcm.googleapis.com" |
| 64 | +), f"Claim audience '{actual_decoded_claim['aud']}' does not match input" |
| 65 | +assert ( |
| 66 | + actual_decoded_claim[ "sub"] == "mailto:[email protected]" |
| 67 | +), f"Claim subscriber '{actual_decoded_claim['sub']}' does not match input" |
| 68 | +assert ( |
| 69 | + actual_decoded_claim["exp"] == 1493315200 |
| 70 | +), f"Claim exp '{actual_decoded_claim['exp']}' does not match input" |
| 71 | +print(f"Test claim validation: Passed") |
| 72 | + |
| 73 | + |
| 74 | +# Test auto expiration date population |
| 75 | +private_key_1 = ec.derive_private_key( |
| 76 | + 0x5C76C15BBC541E7BF6987557124A6E6EB745723B1CF20E2ED2A3ED5B7C16DD46, ec.SECP256R1() |
| 77 | +) |
| 78 | +vapid = py_vapid.Vapid(private_key=private_key_1) |
| 79 | +rtc.datetime((2017, 1, 1, 0, 0, 0, 0, 0)) |
| 80 | +headers = vapid.sign( |
| 81 | + { |
| 82 | + "aud": "https://updates.push.services.mozilla.com", |
| 83 | + "sub": "mailto:[email protected]", |
| 84 | + } |
| 85 | +) |
| 86 | + |
| 87 | +actual_token = headers["Authorization"].split(" ")[1].split(",")[0].split("=")[1] |
| 88 | +actual_decoded_claim = jwt.decode(actual_token, private_key_1.public_key(), "ES256") |
| 89 | +assert ( |
| 90 | + actual_decoded_claim["exp"] == 1483315200 |
| 91 | +), f"Claim exp '{actual_decoded_claim['exp']}' does not match expected 2017-01-02 value" |
| 92 | +print(f"Test auto expiry: Passed") |
| 93 | + |
| 94 | + |
| 95 | +# Because they provide the least information about what could have gone wrong, |
| 96 | +# Run golden test cases after all more specific tests pass first. |
| 97 | +for case_no, case in enumerate(golden_test_cases): |
| 98 | + private_key_number, curr_time, claim, expected_id = case |
| 99 | + try: |
| 100 | + private_key = ec.derive_private_key(private_key_number, ec.SECP256R1()) |
| 101 | + vapid = py_vapid.Vapid(private_key=private_key) |
| 102 | + rtc.datetime(curr_time) |
| 103 | + headers = vapid.sign(claim) |
| 104 | + |
| 105 | + assert ( |
| 106 | + headers["Authorization"] == expected_id |
| 107 | + ), f"Authorization header '{headers['Authorization']}' does not match golden test case {case_no}" |
| 108 | + print(f"Golden test case {case_no}: Passed") |
| 109 | + except Exception as e: |
| 110 | + print(f"Golden test case {case_no}: Failed") |
| 111 | + raise e |
0 commit comments