Skip to content
3 changes: 3 additions & 0 deletions jose/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ def public_key(self):

def to_pem(self):
raise NotImplementedError()

def to_dict(self):
raise NotImplementedError()
141 changes: 123 additions & 18 deletions jose/backends/cryptography_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der

from jose.backends.base import Key
from jose.utils import base64_to_long
from jose.utils import base64_to_long, long_to_base64
from jose.constants import ALGORITHMS
from jose.exceptions import JWKError

Expand Down Expand Up @@ -68,19 +68,26 @@ def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'EC':
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))

if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
raise JWKError('Mandatory parameters are missing')

x = base64_to_long(jwk_dict.get('x'))
y = base64_to_long(jwk_dict.get('y'))

curve = {
'P-256': ec.SECP256R1,
'P-384': ec.SECP384R1,
'P-521': ec.SECP521R1,
}[jwk_dict['crv']]

ec_pn = ec.EllipticCurvePublicNumbers(x, y, curve())
verifying_key = ec_pn.public_key(self.cryptography_backend())
public = ec.EllipticCurvePublicNumbers(x, y, curve())

return verifying_key
if 'd' in jwk_dict:
d = base64_to_long(jwk_dict.get('d'))
private = ec.EllipticCurvePrivateNumbers(d, public)

return private.private_key(self.cryptography_backend())
else:
return public.public_key(self.cryptography_backend())

def sign(self, msg):
if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size:
Expand All @@ -101,13 +108,16 @@ def verify(self, msg, sig):
except:
return False

def is_public(self):
return hasattr(self.prepared_key, 'public_bytes')

def public_key(self):
if hasattr(self.prepared_key, 'public_bytes'):
if self.is_public():
return self
return self.__class__(self.prepared_key.public_key(), self._algorithm)

def to_pem(self):
if hasattr(self.prepared_key, 'public_bytes'):
if self.is_public():
pem = self.prepared_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
Expand All @@ -120,6 +130,39 @@ def to_pem(self):
)
return pem

def to_dict(self):
if not self.is_public():
public_key = self.prepared_key.public_key()
else:
public_key = self.prepared_key

crv = {
'secp256r1': 'P-256',
'secp384r1': 'P-384',
'secp521r1': 'P-521',
}[self.prepared_key.curve.name]

# Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of
# RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve
# points must be encoded as octed-strings of this length.
key_size = (self.prepared_key.curve.key_size + 7) // 8

data = {
'alg': self._algorithm,
'kty': 'EC',
'crv': crv,
'x': long_to_base64(public_key.public_numbers().x, size=key_size),
'y': long_to_base64(public_key.public_numbers().y, size=key_size),
}

if not self.is_public():
data['d'] = long_to_base64(
self.prepared_key.private_numbers().private_value,
size=key_size
)

return data


class CryptographyRSAKey(Key):
SHA256 = hashes.SHA256
Expand Down Expand Up @@ -170,17 +213,51 @@ def _process_jwk(self, jwk_dict):

e = base64_to_long(jwk_dict.get('e', 256))
n = base64_to_long(jwk_dict.get('n'))

verifying_key = rsa.RSAPublicNumbers(e, n).public_key(self.cryptography_backend())
return verifying_key
public = rsa.RSAPublicNumbers(e, n)

if 'd' not in jwk_dict:
return public.public_key(self.cryptography_backend())
else:
# This is a private key.
d = base64_to_long(jwk_dict.get('d'))

extra_params = ['p', 'q', 'dp', 'dq', 'qi']

if any(k in jwk_dict for k in extra_params):
# Precomputed private key parameters are available.
if not all(k in jwk_dict for k in extra_params):
# These values must be present when 'p' is according to
# Section 6.3.2 of RFC7518, so if they are not we raise
# an error.
raise JWKError('Precomputed private key parameters are incomplete.')

p = base64_to_long(jwk_dict['p'])
q = base64_to_long(jwk_dict['q'])
dp = base64_to_long(jwk_dict['dp'])
dq = base64_to_long(jwk_dict['dq'])
qi = base64_to_long(jwk_dict['qi'])
else:
# The precomputed private key parameters are not available,
# so we use cryptography's API to fill them in.
p, q = rsa.rsa_recover_prime_factors(n, e, d)
dp = rsa.rsa_crt_dmp1(d, p)
dq = rsa.rsa_crt_dmq1(d, q)
qi = rsa.rsa_crt_iqmp(p, q)

private = rsa.RSAPrivateNumbers(p, q, d, dp, dq, qi, public)

return private.private_key(self.cryptography_backend())

def sign(self, msg):
signer = self.prepared_key.signer(
padding.PKCS1v15(),
self.hash_alg()
)
signer.update(msg)
signature = signer.finalize()
try:
signer = self.prepared_key.signer(
padding.PKCS1v15(),
self.hash_alg()
)
signer.update(msg)
signature = signer.finalize()
except Exception as e:
raise JWKError(e)
return signature

def verify(self, msg, sig):
Expand All @@ -196,13 +273,16 @@ def verify(self, msg, sig):
except InvalidSignature:
return False

def is_public(self):
return hasattr(self.prepared_key, 'public_bytes')

def public_key(self):
if hasattr(self.prepared_key, 'public_bytes'):
if self.is_public():
return self
return self.__class__(self.prepared_key.public_key(), self._algorithm)

def to_pem(self):
if hasattr(self.prepared_key, 'public_bytes'):
if self.is_public():
return self.prepared_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
Expand All @@ -213,3 +293,28 @@ def to_pem(self):
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)

def to_dict(self):
if not self.is_public():
public_key = self.prepared_key.public_key()
else:
public_key = self.prepared_key

data = {
'alg': self._algorithm,
'kty': 'RSA',
'n': long_to_base64(public_key.public_numbers().n),
'e': long_to_base64(public_key.public_numbers().e),
}

if not self.is_public():
data.update({
'd': long_to_base64(self.prepared_key.private_numbers().d),
'p': long_to_base64(self.prepared_key.private_numbers().p),
'q': long_to_base64(self.prepared_key.private_numbers().q),
'dp': long_to_base64(self.prepared_key.private_numbers().dmp1),
'dq': long_to_base64(self.prepared_key.private_numbers().dmq1),
'qi': long_to_base64(self.prepared_key.private_numbers().iqmp),
})

return data
61 changes: 52 additions & 9 deletions jose/backends/ecdsa_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from jose.constants import ALGORITHMS
from jose.exceptions import JWKError
from jose.utils import base64_to_long
from jose.utils import base64_to_long, long_to_base64


class ECDSAECKey(Key):
Expand Down Expand Up @@ -72,16 +72,23 @@ def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'EC':
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))

x = base64_to_long(jwk_dict.get('x'))
y = base64_to_long(jwk_dict.get('y'))
if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
raise JWKError('Mandatory parameters are missing')

if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
raise JWKError("Point: %s, %s is not a valid point" % (x, y))
if 'd' in jwk_dict:
# We are dealing with a private key; the secret exponent is enough
# to create an ecdsa key.
d = base64_to_long(jwk_dict.get('d'))
return ecdsa.keys.SigningKey.from_secret_exponent(d, self.curve)
else:
x = base64_to_long(jwk_dict.get('x'))
y = base64_to_long(jwk_dict.get('y'))

point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order)
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, self.curve)
if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
raise JWKError("Point: %s, %s is not a valid point" % (x, y))

return verifying_key
point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order)
return ecdsa.keys.VerifyingKey.from_public_point(point, self.curve)

def sign(self, msg):
return self.prepared_key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string)
Expand All @@ -92,10 +99,46 @@ def verify(self, msg, sig):
except:
return False

def is_public(self):
return isinstance(self.prepared_key, ecdsa.VerifyingKey)

def public_key(self):
if isinstance(self.prepared_key, ecdsa.VerifyingKey):
if self.is_public():
return self
return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm)

def to_pem(self):
return self.prepared_key.to_pem()

def to_dict(self):
if not self.is_public():
public_key = self.prepared_key.get_verifying_key()
else:
public_key = self.prepared_key

crv = {
ecdsa.curves.NIST256p: 'P-256',
ecdsa.curves.NIST384p: 'P-384',
ecdsa.curves.NIST521p: 'P-521',
}[self.prepared_key.curve]

# Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of
# RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve
# points must be encoded as octed-strings of this length.
key_size = self.prepared_key.curve.baselen

data = {
'alg': self._algorithm,
'kty': 'EC',
'crv': crv,
'x': long_to_base64(public_key.pubkey.point.x(), size=key_size),
'y': long_to_base64(public_key.pubkey.point.y(), size=key_size),
}

if not self.is_public():
data['d'] = long_to_base64(
self.prepared_key.privkey.secret_multiplier,
size=key_size
)

return data
66 changes: 63 additions & 3 deletions jose/backends/pycrypto_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from Crypto.Util.asn1 import DerSequence

from jose.backends.base import Key
from jose.utils import base64_to_long
from jose.utils import base64_to_long, long_to_base64
from jose.constants import ALGORITHMS
from jose.exceptions import JWKError
from jose.utils import base64url_decode
Expand Down Expand Up @@ -79,8 +79,35 @@ def _process_jwk(self, jwk_dict):

e = base64_to_long(jwk_dict.get('e', 256))
n = base64_to_long(jwk_dict.get('n'))
params = (n, e)

if 'd' in jwk_dict:
params += (base64_to_long(jwk_dict.get('d')),)

extra_params = ['p', 'q', 'dp', 'dq', 'qi']

if any(k in jwk_dict for k in extra_params):
# Precomputed private key parameters are available.
if not all(k in jwk_dict for k in extra_params):
# These values must be present when 'p' is according to
# Section 6.3.2 of RFC7518, so if they are not we raise
# an error.
raise JWKError('Precomputed private key parameters are incomplete.')

p = base64_to_long(jwk_dict.get('p'))
q = base64_to_long(jwk_dict.get('q'))
qi = base64_to_long(jwk_dict.get('qi'))

# PyCrypto does not take the dp and dq as arguments, so we do
# not pass them. Furthermore, the parameter qi specified in
# the JWK is the inverse of q modulo p, whereas PyCrypto
# takes the inverse of p modulo q. We therefore switch the
# parameters to make the third parameter the inverse of the
# second parameter modulo the first parameter.
params += (q, p, qi)

self.prepared_key = RSA.construct(params)

self.prepared_key = RSA.construct((n, e))
return self.prepared_key

def _process_cert(self, key):
Expand All @@ -105,8 +132,11 @@ def verify(self, msg, sig):
except Exception as e:
return False

def is_public(self):
return not self.prepared_key.has_private()

def public_key(self):
if not self.prepared_key.has_private():
if self.is_public():
return self
return self.__class__(self.prepared_key.publickey(), self._algorithm)

Expand All @@ -121,3 +151,33 @@ def to_pem(self):
if not pem.endswith(b'\n'):
pem = pem + b'\n'
return pem

def to_dict(self):
data = {
'alg': self._algorithm,
'kty': 'RSA',
'n': long_to_base64(self.prepared_key.n),
'e': long_to_base64(self.prepared_key.e),
}

if not self.is_public():
# Section 6.3.2 of RFC7518 prescribes that when we include the
# optional parameters p and q, we must also include the values of
# dp and dq, which are not readily available from PyCrypto - so we
# calculate them. Moreover, PyCrypto stores the inverse of p
# modulo q rather than the inverse of q modulo p, so we switch
# p and q. As far as I can tell, this is OK - RFC7518 only
# asserts that p is the 'first factor', but does not specify
# what 'first' means in this case.
dp = self.prepared_key.d % (self.prepared_key.p - 1)
dq = self.prepared_key.d % (self.prepared_key.q - 1)
data.update({
'd': long_to_base64(self.prepared_key.d),
'p': long_to_base64(self.prepared_key.q),
'q': long_to_base64(self.prepared_key.p),
'dp': long_to_base64(dq),
'dq': long_to_base64(dp),
'qi': long_to_base64(self.prepared_key.u),
})

return data
Loading