Skip to content
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
8 changes: 6 additions & 2 deletions src/josepy/jwa.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ def sign(self, key, msg):
"""Sign the ``msg`` using ``key``."""
sig = self._sign(key, msg)
dr, ds = decode_dss_signature(sig)
return (dr.to_bytes(length=math.ceil(dr.bit_length() / 8), byteorder='big') +
ds.to_bytes(length=math.ceil(ds.bit_length() / 8), byteorder='big'))
length = math.ceil(key.key_size / 8)
return (dr.to_bytes(length=length, byteorder='big') +
ds.to_bytes(length=length, byteorder='big'))

def _sign(self, key, msg):
# If cryptography library supports new style api (v1.4 and later)
Expand All @@ -198,6 +199,9 @@ def _sign(self, key, msg):
def verify(self, key, msg, sig):
"""Verify the ``msg` and ``sig`` using ``key``."""
rlen = math.ceil(key.key_size / 8)
if len(sig) != 2 * rlen:
# Format error - rfc7518 - 3.4 … MUST NOT be shortened to omit any leading zero octets
return False
asn1sig = encode_dss_signature(
int.from_bytes(sig[0:rlen], byteorder='big'),
int.from_bytes(sig[rlen:], byteorder='big')
Expand Down
24 changes: 20 additions & 4 deletions src/josepy/jwa_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def test_sign_new_api(self):

def test_sign_old_api(self):
from josepy.jwa import ES256
key = mock.MagicMock(spec=[u'signer'])
key = mock.MagicMock(spec=[u'signer'], key_size=256)
signer = mock.MagicMock()
key.signer.return_value = signer
with mock.patch("josepy.jwa.decode_dss_signature") as decode_patch:
Expand All @@ -189,8 +189,8 @@ def test_sign_old_api(self):

def test_verify_new_api(self):
from josepy.jwa import ES256
key = mock.MagicMock()
ES256.verify(key, "message", "signature".encode())
key = mock.MagicMock(key_size=256)
ES256.verify(key, "message", b'\x00' * int(key.key_size / 8) * 2)
self.assertIs(key.verify.called, True)

def test_verify_old_api(self):
Expand All @@ -199,11 +199,27 @@ def test_verify_old_api(self):
verifier = mock.MagicMock()
key.verifier.return_value = verifier
key.key_size = 65 * 8
ES256.verify(key, "message", "signature".encode())
ES256.verify(key, "message", b'\x00' * int(key.key_size / 8) * 2)
self.assertIs(key.verifier.called, True)
self.assertIs(verifier.update.called, True)
self.assertIs(verifier.verify.called, True)

def test_signature_size(self):
from josepy.jwa import ES512
from josepy.jwk import JWK
key = JWK.from_json(
{
'd': 'Af9KP6DqLRbtit6NS_LRIaCP_-NdC5l5R2ugbILdfpv6dS9R4wUPNxiGw-vVWumA56Yo1oBnEm8ZdR4W-u1lPHw5',
'x': 'PiLhJPInTuJkmQeSkoQ64gKmfogeSfPACWt_7XDVrl2o6xF7fQQQJI3i8XFp4Ca10FIIoHAKruHWrhs-AysxS8U',
'y': 'cCVfGtpuNzH_IHEY5ueb8OQRAwkrUTr04djfHdXEXlVegpz3cIbgYuho--mFlC9me3kR8TFCg-S3A4whWEEdoVE',
'crv': 'P-521',
'kty': 'EC'
})
with mock.patch("josepy.jwa.decode_dss_signature") as decode_patch:
decode_patch.return_value = (0, 0)
sig = ES512.sign(key.key, b"test")
self.assertEqual(len(sig), 2 * 66)


if __name__ == '__main__':
unittest.main() # pragma: no cover
33 changes: 12 additions & 21 deletions src/josepy/jwk.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""JSON Web Key."""
import abc
import binascii
import json
import logging
import math

import cryptography.exceptions
from cryptography.hazmat.backends import default_backend
Expand Down Expand Up @@ -164,25 +164,21 @@ def __init__(self, *args, **kwargs):
@classmethod
def _encode_param(cls, data):
"""Encode Base64urlUInt.

:type data: long
:rtype: unicode

"""

def _leading_zeros(arg):
if len(arg) % 2:
return '0' + arg
return arg

return json_util.encode_b64jose(binascii.unhexlify(
_leading_zeros(hex(data)[2:].rstrip('L'))))
length = max(data.bit_length(), 8) # decoding 0
length = math.ceil(length / 8)
return json_util.encode_b64jose(data.to_bytes(byteorder="big", length=length))

@classmethod
def _decode_param(cls, data):
"""Decode Base64urlUInt."""
try:
return int(binascii.hexlify(json_util.decode_b64jose(data)), 16)
binary = json_util.decode_b64jose(data)
if not binary:
raise errors.DeserializationError()
return int.from_bytes(binary, byteorder="big")
except ValueError: # invalid literal for long() with base 16
raise errors.DeserializationError()

Expand Down Expand Up @@ -280,14 +276,9 @@ def _encode_param(cls, data):
:type data: long
:rtype: unicode
"""

def _leading_zeros(arg):
if len(arg) % 2:
return '0' + arg
return arg

return json_util.encode_b64jose(binascii.unhexlify(
_leading_zeros(hex(data)[2:].rstrip('L'))))
length = max(data.bit_length(), 8) # decoding 0
length = math.ceil(length / 8)
return json_util.encode_b64jose(data.to_bytes(byteorder="big", length=length))

@classmethod
def _decode_param(cls, data, name, valid_lengths):
Expand All @@ -300,7 +291,7 @@ def _decode_param(cls, data, name, valid_lengths):
'after base64-decoding; got {length} bytes instead'.format(
name=name, valid_lengths=valid_lengths, length=len(binary))
)
return int(binascii.hexlify(binary), 16)
return int.from_bytes(binary, byteorder="big")
except ValueError: # invalid literal for long() with base 16
raise errors.DeserializationError()

Expand Down