Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added test vectors from the IETF JOSE Cookbook for HMAC, RSA, and EC. #160

Merged
merged 2 commits into from
May 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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