Skip to content

Commit

Permalink
Merge pull request #160 from mark-adams/add-test-vectors
Browse files Browse the repository at this point in the history
Added test vectors from the IETF JOSE Cookbook for HMAC, RSA, and EC.
  • Loading branch information
mark-adams committed May 19, 2015
2 parents 4797f7f + 7c870a8 commit 8f00324
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 3 deletions.
72 changes: 72 additions & 0 deletions tests/keys/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import json
import os

from jwt.utils import base64url_decode

from tests.utils import ensure_bytes, int_from_bytes

BASE_PATH = os.path.dirname(os.path.abspath(__file__))


def decode_value(val):
decoded = base64url_decode(ensure_bytes(val))
return int_from_bytes(decoded, 'big')


def load_hmac_key():
with open(os.path.join(BASE_PATH, 'jwk_hmac.json'), 'r') as infile:
keyobj = json.load(infile)

return base64url_decode(ensure_bytes(keyobj['k']))

try:
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend

has_crypto = True
except ImportError:
has_crypto = False

if has_crypto:
def load_rsa_key():
with open(os.path.join(BASE_PATH, 'jwk_rsa_key.json'), 'r') as infile:
keyobj = json.load(infile)

return rsa.RSAPrivateNumbers(
p=decode_value(keyobj['p']),
q=decode_value(keyobj['q']),
d=decode_value(keyobj['d']),
dmp1=decode_value(keyobj['dp']),
dmq1=decode_value(keyobj['dq']),
iqmp=decode_value(keyobj['qi']),
public_numbers=load_rsa_pub_key().public_numbers()
).private_key(default_backend())

def load_rsa_pub_key():
with open(os.path.join(BASE_PATH, 'jwk_rsa_pub.json'), 'r') as infile:
keyobj = json.load(infile)

return rsa.RSAPublicNumbers(
n=decode_value(keyobj['n']),
e=decode_value(keyobj['e'])
).public_key(default_backend())

def load_ec_key():
with open(os.path.join(BASE_PATH, 'jwk_ec_key.json'), 'r') as infile:
keyobj = json.load(infile)

return ec.EllipticCurvePrivateNumbers(
private_value=decode_value(keyobj['d']),
public_numbers=load_ec_pub_key().public_numbers()
)

def load_ec_pub_key():
with open(os.path.join(BASE_PATH, 'jwk_ec_pub.json'), 'r') as infile:
keyobj = json.load(infile)

return ec.EllipticCurvePublicNumbers(
x=decode_value(keyobj['x']),
y=decode_value(keyobj['y']),
curve=ec.SECP521R1()
).public_key(default_backend())
9 changes: 9 additions & 0 deletions tests/keys/jwk_ec_key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"kty": "EC",
"kid": "[email protected]",
"use": "sig",
"crv": "P-521",
"x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
"y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
"d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"
}
8 changes: 8 additions & 0 deletions tests/keys/jwk_ec_pub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"kty": "EC",
"kid": "[email protected]",
"use": "sig",
"crv": "P-521",
"x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
"y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
}
7 changes: 7 additions & 0 deletions tests/keys/jwk_hmac.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"kty": "oct",
"kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037",
"use": "sig",
"alg": "HS256",
"k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
}
13 changes: 13 additions & 0 deletions tests/keys/jwk_rsa_key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"kty": "RSA",
"kid": "[email protected]",
"use": "sig",
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
"e": "AQAB",
"d": "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ",
"p": "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nRaO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmGpeNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0k",
"q": "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc",
"dp": "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX59ehik",
"dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pErAMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdKT1cYF8",
"qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4"
}
7 changes: 7 additions & 0 deletions tests/keys/jwk_rsa_pub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"kty": "RSA",
"kid": "[email protected]",
"use": "sig",
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
"e": "AQAB"
}
103 changes: 102 additions & 1 deletion tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

from jwt.algorithms import Algorithm, HMACAlgorithm, NoneAlgorithm
from jwt.exceptions import InvalidKeyError
from jwt.utils import base64url_decode

import pytest

from .keys import load_hmac_key
from .utils import ensure_bytes, ensure_unicode, key_path

try:
from jwt.algorithms import RSAAlgorithm, ECAlgorithm, RSAPSSAlgorithm

from .keys import load_rsa_pub_key, load_ec_pub_key
has_crypto = True
except ImportError:
has_crypto = False
Expand Down Expand Up @@ -257,3 +259,102 @@ def test_rsa_pss_verify_should_return_true_if_signature_valid(self):

result = algo.verify(jwt_message, jwt_pub_key, jwt_sig)
assert result


class TestAlgorithmsCookbook:
"""
These test vectors were taken from IETF JOSE Cookbook Draft
(https://www.ietf.org/id/draft-ietf-jose-cookbook-08.txt)
"""

def test_hmac_verify_should_return_true_for_test_vector(self):
signing_input = ensure_bytes(
'eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZ'
'jMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ'
'29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIG'
'lmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmc'
'gd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
)

signature = base64url_decode(ensure_bytes(
's0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0'
))

algo = HMACAlgorithm(HMACAlgorithm.SHA256)
key = algo.prepare_key(load_hmac_key())

result = algo.verify(signing_input, key, signature)
assert result

@pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
def test_rsa_verify_should_return_true_for_test_vector(self):
signing_input = ensure_bytes(
'eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb'
'XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb'
'3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS'
'Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU'
'geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
)

signature = base64url_decode(ensure_bytes(
'MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZop'
'dHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJ'
'K3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4'
'QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic'
'1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogor'
'ee7vjbU5y18kDquDg'
))

algo = RSAAlgorithm(RSAAlgorithm.SHA256)
key = algo.prepare_key(load_rsa_pub_key())

result = algo.verify(signing_input, key, signature)
assert result

@pytest.mark.skipif(True, "I'm not 100% sure if this test is correct")
@pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
def test_rsapss_verify_should_return_true_for_test_vector(self):
signing_input = ensure_bytes(
'eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb'
'XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb'
'3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS'
'Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU'
'geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
)

signature = base64url_decode(ensure_bytes(
'cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2IpN'
'6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvW'
'Xzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ij'
'Q7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6ui'
'P1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmW'
'jwZ6oD4ifKo8DYM-X72Eaw'
))

algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384)
key = algo.prepare_key(load_rsa_pub_key())

result = algo.verify(signing_input, key, signature)
assert result

@pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
def test_ec_verify_should_return_true_for_test_vector(self):
signing_input = ensure_bytes(
'eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb'
'XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb'
'3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS'
'Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU'
'geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
)

signature = base64url_decode(ensure_bytes(
'AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9P'
'lon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890j'
'l8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2'
))

algo = ECAlgorithm(ECAlgorithm.SHA512)
key = algo.prepare_key(load_ec_pub_key())

result = algo.verify(signing_input, key, signature)
assert result
21 changes: 21 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import struct

from calendar import timegm
from datetime import datetime
Expand Down Expand Up @@ -27,3 +28,23 @@ def utc_timestamp():
def key_path(key_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)),
'keys', key_name)

# Borrowed from `cryptography`
if hasattr(int, "from_bytes"):
int_from_bytes = int.from_bytes
else:
def int_from_bytes(data, byteorder, signed=False):
assert byteorder == 'big'
assert not signed

if len(data) % 4 != 0:
data = (b'\x00' * (4 - (len(data) % 4))) + data

result = 0

while len(data) > 0:
digit, = struct.unpack('>I', data[:4])
result = (result << 32) + digit
data = data[4:]

return result
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ envlist = py{26,27,33,34}-crypto, py{27,34}-contrib_crypto, py{27,34}-nocrypto,
commands =
python setup.py pytest
deps =
crypto,flake8: cryptography
crypto: cryptography
pytest
pytest-cov
pytest-runner
contrib_crypto: pycrypto
contrib_crypto: ecdsa

[flake8]
[testenv:flake8]
commands =
flake8
deps =
Expand Down

0 comments on commit 8f00324

Please sign in to comment.