first commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
try:
|
||||
from jose.backends.cryptography_backend import get_random_bytes # noqa: F401
|
||||
except ImportError:
|
||||
try:
|
||||
from jose.backends.pycrypto_backend import get_random_bytes # noqa: F401
|
||||
except ImportError:
|
||||
from jose.backends.native import get_random_bytes # noqa: F401
|
||||
|
||||
try:
|
||||
from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401
|
||||
except ImportError:
|
||||
try:
|
||||
from jose.backends.rsa_backend import RSAKey # noqa: F401
|
||||
except ImportError:
|
||||
RSAKey = None
|
||||
|
||||
try:
|
||||
from jose.backends.cryptography_backend import CryptographyECKey as ECKey # noqa: F401
|
||||
except ImportError:
|
||||
from jose.backends.ecdsa_backend import ECDSAECKey as ECKey # noqa: F401
|
||||
|
||||
try:
|
||||
from jose.backends.cryptography_backend import CryptographyAESKey as AESKey # noqa: F401
|
||||
except ImportError:
|
||||
AESKey = None
|
||||
|
||||
try:
|
||||
from jose.backends.cryptography_backend import CryptographyHMACKey as HMACKey # noqa: F401
|
||||
except ImportError:
|
||||
from jose.backends.native import HMACKey # noqa: F401
|
||||
|
||||
from .base import DIRKey # noqa: F401
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,83 @@
|
||||
"""ASN1 encoding helpers for converting between PKCS1 and PKCS8.
|
||||
|
||||
Required by rsa_backend but not cryptography_backend.
|
||||
"""
|
||||
from pyasn1.codec.der import decoder, encoder
|
||||
from pyasn1.type import namedtype, univ
|
||||
|
||||
RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1"
|
||||
|
||||
|
||||
class RsaAlgorithmIdentifier(univ.Sequence):
|
||||
"""ASN1 structure for recording RSA PrivateKeyAlgorithm identifiers."""
|
||||
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()), namedtype.NamedType("parameters", univ.Null())
|
||||
)
|
||||
|
||||
|
||||
class PKCS8PrivateKey(univ.Sequence):
|
||||
"""ASN1 structure for recording PKCS8 private keys."""
|
||||
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("version", univ.Integer()),
|
||||
namedtype.NamedType("privateKeyAlgorithm", RsaAlgorithmIdentifier()),
|
||||
namedtype.NamedType("privateKey", univ.OctetString()),
|
||||
)
|
||||
|
||||
|
||||
class PublicKeyInfo(univ.Sequence):
|
||||
"""ASN1 structure for recording PKCS8 public keys."""
|
||||
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("algorithm", RsaAlgorithmIdentifier()), namedtype.NamedType("publicKey", univ.BitString())
|
||||
)
|
||||
|
||||
|
||||
def rsa_private_key_pkcs8_to_pkcs1(pkcs8_key):
|
||||
"""Convert a PKCS8-encoded RSA private key to PKCS1."""
|
||||
decoded_values = decoder.decode(pkcs8_key, asn1Spec=PKCS8PrivateKey())
|
||||
|
||||
try:
|
||||
decoded_key = decoded_values[0]
|
||||
except IndexError:
|
||||
raise ValueError("Invalid private key encoding")
|
||||
|
||||
return decoded_key["privateKey"]
|
||||
|
||||
|
||||
def rsa_private_key_pkcs1_to_pkcs8(pkcs1_key):
|
||||
"""Convert a PKCS1-encoded RSA private key to PKCS8."""
|
||||
algorithm = RsaAlgorithmIdentifier()
|
||||
algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID
|
||||
|
||||
pkcs8_key = PKCS8PrivateKey()
|
||||
pkcs8_key["version"] = 0
|
||||
pkcs8_key["privateKeyAlgorithm"] = algorithm
|
||||
pkcs8_key["privateKey"] = pkcs1_key
|
||||
|
||||
return encoder.encode(pkcs8_key)
|
||||
|
||||
|
||||
def rsa_public_key_pkcs1_to_pkcs8(pkcs1_key):
|
||||
"""Convert a PKCS1-encoded RSA private key to PKCS8."""
|
||||
algorithm = RsaAlgorithmIdentifier()
|
||||
algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID
|
||||
|
||||
pkcs8_key = PublicKeyInfo()
|
||||
pkcs8_key["algorithm"] = algorithm
|
||||
pkcs8_key["publicKey"] = univ.BitString.fromOctetString(pkcs1_key)
|
||||
|
||||
return encoder.encode(pkcs8_key)
|
||||
|
||||
|
||||
def rsa_public_key_pkcs8_to_pkcs1(pkcs8_key):
|
||||
"""Convert a PKCS8-encoded RSA private key to PKCS1."""
|
||||
decoded_values = decoder.decode(pkcs8_key, asn1Spec=PublicKeyInfo())
|
||||
|
||||
try:
|
||||
decoded_key = decoded_values[0]
|
||||
except IndexError:
|
||||
raise ValueError("Invalid public key encoding.")
|
||||
|
||||
return decoded_key["publicKey"].asOctets()
|
||||
@@ -0,0 +1,89 @@
|
||||
from ..utils import base64url_encode, ensure_binary
|
||||
|
||||
|
||||
class Key:
|
||||
"""
|
||||
A simple interface for implementing JWK keys.
|
||||
"""
|
||||
|
||||
def __init__(self, key, algorithm):
|
||||
pass
|
||||
|
||||
def sign(self, msg):
|
||||
raise NotImplementedError()
|
||||
|
||||
def verify(self, msg, sig):
|
||||
raise NotImplementedError()
|
||||
|
||||
def public_key(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def to_pem(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def to_dict(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def encrypt(self, plain_text, aad=None):
|
||||
"""
|
||||
Encrypt the plain text and generate an auth tag if appropriate
|
||||
|
||||
Args:
|
||||
plain_text (bytes): Data to encrypt
|
||||
aad (bytes, optional): Authenticated Additional Data if key's algorithm supports auth mode
|
||||
|
||||
Returns:
|
||||
(bytes, bytes, bytes): IV, cipher text, and auth tag
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def decrypt(self, cipher_text, iv=None, aad=None, tag=None):
|
||||
"""
|
||||
Decrypt the cipher text and validate the auth tag if present
|
||||
Args:
|
||||
cipher_text (bytes): Cipher text to decrypt
|
||||
iv (bytes): IV if block mode
|
||||
aad (bytes): Additional Authenticated Data to verify if auth mode
|
||||
tag (bytes): Authentication tag if auth mode
|
||||
|
||||
Returns:
|
||||
bytes: Decrypted value
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def wrap_key(self, key_data):
|
||||
"""
|
||||
Wrap the the plain text key data
|
||||
|
||||
Args:
|
||||
key_data (bytes): Key data to wrap
|
||||
|
||||
Returns:
|
||||
bytes: Wrapped key
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def unwrap_key(self, wrapped_key):
|
||||
"""
|
||||
Unwrap the the wrapped key data
|
||||
|
||||
Args:
|
||||
wrapped_key (bytes): Wrapped key data to unwrap
|
||||
|
||||
Returns:
|
||||
bytes: Unwrapped key
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class DIRKey(Key):
|
||||
def __init__(self, key_data, algorithm):
|
||||
self._key = ensure_binary(key_data)
|
||||
self._alg = algorithm
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"alg": self._alg,
|
||||
"kty": "oct",
|
||||
"k": base64url_encode(self._key),
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
import math
|
||||
import warnings
|
||||
|
||||
from cryptography.exceptions import InvalidSignature, InvalidTag
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.bindings.openssl.binding import Binding
|
||||
from cryptography.hazmat.primitives import hashes, hmac, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
|
||||
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, aead, algorithms, modes
|
||||
from cryptography.hazmat.primitives.keywrap import InvalidUnwrap, aes_key_unwrap, aes_key_wrap
|
||||
from cryptography.hazmat.primitives.padding import PKCS7
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
|
||||
from cryptography.utils import int_to_bytes
|
||||
from cryptography.x509 import load_pem_x509_certificate
|
||||
|
||||
from ..constants import ALGORITHMS
|
||||
from ..exceptions import JWEError, JWKError
|
||||
from ..utils import base64_to_long, base64url_decode, base64url_encode, ensure_binary, long_to_base64
|
||||
from .base import Key
|
||||
|
||||
_binding = None
|
||||
|
||||
|
||||
def get_random_bytes(num_bytes):
|
||||
"""
|
||||
Get random bytes
|
||||
|
||||
Currently, Cryptography returns OS random bytes. If you want OpenSSL
|
||||
generated random bytes, you'll have to switch the RAND engine after
|
||||
initializing the OpenSSL backend
|
||||
Args:
|
||||
num_bytes (int): Number of random bytes to generate and return
|
||||
Returns:
|
||||
bytes: Random bytes
|
||||
"""
|
||||
global _binding
|
||||
|
||||
if _binding is None:
|
||||
_binding = Binding()
|
||||
|
||||
buf = _binding.ffi.new("char[]", num_bytes)
|
||||
_binding.lib.RAND_bytes(buf, num_bytes)
|
||||
rand_bytes = _binding.ffi.buffer(buf, num_bytes)[:]
|
||||
return rand_bytes
|
||||
|
||||
|
||||
class CryptographyECKey(Key):
|
||||
SHA256 = hashes.SHA256
|
||||
SHA384 = hashes.SHA384
|
||||
SHA512 = hashes.SHA512
|
||||
|
||||
def __init__(self, key, algorithm, cryptography_backend=default_backend):
|
||||
if algorithm not in ALGORITHMS.EC:
|
||||
raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
|
||||
|
||||
self.hash_alg = {
|
||||
ALGORITHMS.ES256: self.SHA256,
|
||||
ALGORITHMS.ES384: self.SHA384,
|
||||
ALGORITHMS.ES512: self.SHA512,
|
||||
}.get(algorithm)
|
||||
self._algorithm = algorithm
|
||||
|
||||
self.cryptography_backend = cryptography_backend
|
||||
|
||||
if hasattr(key, "public_bytes") or hasattr(key, "private_bytes"):
|
||||
self.prepared_key = key
|
||||
return
|
||||
|
||||
if hasattr(key, "to_pem"):
|
||||
# convert to PEM and let cryptography below load it as PEM
|
||||
key = key.to_pem().decode("utf-8")
|
||||
|
||||
if isinstance(key, dict):
|
||||
self.prepared_key = self._process_jwk(key)
|
||||
return
|
||||
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
|
||||
if isinstance(key, bytes):
|
||||
# Attempt to load key. We don't know if it's
|
||||
# a Public Key or a Private Key, so we try
|
||||
# the Public Key first.
|
||||
try:
|
||||
try:
|
||||
key = load_pem_public_key(key, self.cryptography_backend())
|
||||
except ValueError:
|
||||
key = load_pem_private_key(key, password=None, backend=self.cryptography_backend())
|
||||
except Exception as e:
|
||||
raise JWKError(e)
|
||||
|
||||
self.prepared_key = key
|
||||
return
|
||||
|
||||
raise JWKError("Unable to parse an ECKey from key: %s" % key)
|
||||
|
||||
def _process_jwk(self, jwk_dict):
|
||||
if not jwk_dict.get("kty") == "EC":
|
||||
raise JWKError("Incorrect key type. Expected: 'EC', Received: %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"]]
|
||||
|
||||
public = ec.EllipticCurvePublicNumbers(x, y, curve())
|
||||
|
||||
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 _sig_component_length(self):
|
||||
"""Determine the correct serialization length for an encoded signature component.
|
||||
|
||||
This is the number of bytes required to encode the maximum key value.
|
||||
"""
|
||||
return int(math.ceil(self.prepared_key.key_size / 8.0))
|
||||
|
||||
def _der_to_raw(self, der_signature):
|
||||
"""Convert signature from DER encoding to RAW encoding."""
|
||||
r, s = decode_dss_signature(der_signature)
|
||||
component_length = self._sig_component_length()
|
||||
return int_to_bytes(r, component_length) + int_to_bytes(s, component_length)
|
||||
|
||||
def _raw_to_der(self, raw_signature):
|
||||
"""Convert signature from RAW encoding to DER encoding."""
|
||||
component_length = self._sig_component_length()
|
||||
if len(raw_signature) != int(2 * component_length):
|
||||
raise ValueError("Invalid signature")
|
||||
|
||||
r_bytes = raw_signature[:component_length]
|
||||
s_bytes = raw_signature[component_length:]
|
||||
r = int.from_bytes(r_bytes, "big")
|
||||
s = int.from_bytes(s_bytes, "big")
|
||||
return encode_dss_signature(r, s)
|
||||
|
||||
def sign(self, msg):
|
||||
if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size:
|
||||
raise TypeError(
|
||||
"this curve (%s) is too short "
|
||||
"for your digest (%d)" % (self.prepared_key.curve.name, 8 * self.hash_alg.digest_size)
|
||||
)
|
||||
signature = self.prepared_key.sign(msg, ec.ECDSA(self.hash_alg()))
|
||||
return self._der_to_raw(signature)
|
||||
|
||||
def verify(self, msg, sig):
|
||||
try:
|
||||
signature = self._raw_to_der(sig)
|
||||
self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg()))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def is_public(self):
|
||||
return hasattr(self.prepared_key, "public_bytes")
|
||||
|
||||
def public_key(self):
|
||||
if self.is_public():
|
||||
return self
|
||||
return self.__class__(self.prepared_key.public_key(), self._algorithm)
|
||||
|
||||
def to_pem(self):
|
||||
if self.is_public():
|
||||
pem = self.prepared_key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
return pem
|
||||
pem = self.prepared_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
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).decode("ASCII"),
|
||||
"y": long_to_base64(public_key.public_numbers().y, size=key_size).decode("ASCII"),
|
||||
}
|
||||
|
||||
if not self.is_public():
|
||||
private_value = self.prepared_key.private_numbers().private_value
|
||||
data["d"] = long_to_base64(private_value, size=key_size).decode("ASCII")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CryptographyRSAKey(Key):
|
||||
SHA256 = hashes.SHA256
|
||||
SHA384 = hashes.SHA384
|
||||
SHA512 = hashes.SHA512
|
||||
|
||||
RSA1_5 = padding.PKCS1v15()
|
||||
RSA_OAEP = padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None)
|
||||
RSA_OAEP_256 = padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None)
|
||||
|
||||
def __init__(self, key, algorithm, cryptography_backend=default_backend):
|
||||
if algorithm not in ALGORITHMS.RSA:
|
||||
raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
|
||||
|
||||
self.hash_alg = {
|
||||
ALGORITHMS.RS256: self.SHA256,
|
||||
ALGORITHMS.RS384: self.SHA384,
|
||||
ALGORITHMS.RS512: self.SHA512,
|
||||
}.get(algorithm)
|
||||
self._algorithm = algorithm
|
||||
|
||||
self.padding = {
|
||||
ALGORITHMS.RSA1_5: self.RSA1_5,
|
||||
ALGORITHMS.RSA_OAEP: self.RSA_OAEP,
|
||||
ALGORITHMS.RSA_OAEP_256: self.RSA_OAEP_256,
|
||||
}.get(algorithm)
|
||||
|
||||
self.cryptography_backend = cryptography_backend
|
||||
|
||||
# if it conforms to RSAPublicKey interface
|
||||
if hasattr(key, "public_bytes") and hasattr(key, "public_numbers"):
|
||||
self.prepared_key = key
|
||||
return
|
||||
|
||||
if isinstance(key, dict):
|
||||
self.prepared_key = self._process_jwk(key)
|
||||
return
|
||||
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
|
||||
if isinstance(key, bytes):
|
||||
try:
|
||||
if key.startswith(b"-----BEGIN CERTIFICATE-----"):
|
||||
self._process_cert(key)
|
||||
return
|
||||
|
||||
try:
|
||||
self.prepared_key = load_pem_public_key(key, self.cryptography_backend())
|
||||
except ValueError:
|
||||
self.prepared_key = load_pem_private_key(key, password=None, backend=self.cryptography_backend())
|
||||
except Exception as e:
|
||||
raise JWKError(e)
|
||||
return
|
||||
|
||||
raise JWKError("Unable to parse an RSA_JWK from key: %s" % key)
|
||||
|
||||
def _process_jwk(self, jwk_dict):
|
||||
if not jwk_dict.get("kty") == "RSA":
|
||||
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get("kty"))
|
||||
|
||||
e = base64_to_long(jwk_dict.get("e", 256))
|
||||
n = base64_to_long(jwk_dict.get("n"))
|
||||
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 _process_cert(self, key):
|
||||
key = load_pem_x509_certificate(key, self.cryptography_backend())
|
||||
self.prepared_key = key.public_key()
|
||||
|
||||
def sign(self, msg):
|
||||
try:
|
||||
signature = self.prepared_key.sign(msg, padding.PKCS1v15(), self.hash_alg())
|
||||
except Exception as e:
|
||||
raise JWKError(e)
|
||||
return signature
|
||||
|
||||
def verify(self, msg, sig):
|
||||
if not self.is_public():
|
||||
warnings.warn("Attempting to verify a message with a private key. " "This is not recommended.")
|
||||
|
||||
try:
|
||||
self.public_key().prepared_key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg())
|
||||
return True
|
||||
except InvalidSignature:
|
||||
return False
|
||||
|
||||
def is_public(self):
|
||||
return hasattr(self.prepared_key, "public_bytes")
|
||||
|
||||
def public_key(self):
|
||||
if self.is_public():
|
||||
return self
|
||||
return self.__class__(self.prepared_key.public_key(), self._algorithm)
|
||||
|
||||
def to_pem(self, pem_format="PKCS8"):
|
||||
if self.is_public():
|
||||
if pem_format == "PKCS8":
|
||||
fmt = serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
elif pem_format == "PKCS1":
|
||||
fmt = serialization.PublicFormat.PKCS1
|
||||
else:
|
||||
raise ValueError("Invalid format specified: %r" % pem_format)
|
||||
pem = self.prepared_key.public_bytes(encoding=serialization.Encoding.PEM, format=fmt)
|
||||
return pem
|
||||
|
||||
if pem_format == "PKCS8":
|
||||
fmt = serialization.PrivateFormat.PKCS8
|
||||
elif pem_format == "PKCS1":
|
||||
fmt = serialization.PrivateFormat.TraditionalOpenSSL
|
||||
else:
|
||||
raise ValueError("Invalid format specified: %r" % pem_format)
|
||||
|
||||
return self.prepared_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM, format=fmt, 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).decode("ASCII"),
|
||||
"e": long_to_base64(public_key.public_numbers().e).decode("ASCII"),
|
||||
}
|
||||
|
||||
if not self.is_public():
|
||||
data.update(
|
||||
{
|
||||
"d": long_to_base64(self.prepared_key.private_numbers().d).decode("ASCII"),
|
||||
"p": long_to_base64(self.prepared_key.private_numbers().p).decode("ASCII"),
|
||||
"q": long_to_base64(self.prepared_key.private_numbers().q).decode("ASCII"),
|
||||
"dp": long_to_base64(self.prepared_key.private_numbers().dmp1).decode("ASCII"),
|
||||
"dq": long_to_base64(self.prepared_key.private_numbers().dmq1).decode("ASCII"),
|
||||
"qi": long_to_base64(self.prepared_key.private_numbers().iqmp).decode("ASCII"),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def wrap_key(self, key_data):
|
||||
try:
|
||||
wrapped_key = self.prepared_key.encrypt(key_data, self.padding)
|
||||
except Exception as e:
|
||||
raise JWEError(e)
|
||||
|
||||
return wrapped_key
|
||||
|
||||
def unwrap_key(self, wrapped_key):
|
||||
try:
|
||||
unwrapped_key = self.prepared_key.decrypt(wrapped_key, self.padding)
|
||||
return unwrapped_key
|
||||
except Exception as e:
|
||||
raise JWEError(e)
|
||||
|
||||
|
||||
class CryptographyAESKey(Key):
|
||||
KEY_128 = (ALGORITHMS.A128GCM, ALGORITHMS.A128GCMKW, ALGORITHMS.A128KW, ALGORITHMS.A128CBC)
|
||||
KEY_192 = (ALGORITHMS.A192GCM, ALGORITHMS.A192GCMKW, ALGORITHMS.A192KW, ALGORITHMS.A192CBC)
|
||||
KEY_256 = (
|
||||
ALGORITHMS.A256GCM,
|
||||
ALGORITHMS.A256GCMKW,
|
||||
ALGORITHMS.A256KW,
|
||||
ALGORITHMS.A128CBC_HS256,
|
||||
ALGORITHMS.A256CBC,
|
||||
)
|
||||
KEY_384 = (ALGORITHMS.A192CBC_HS384,)
|
||||
KEY_512 = (ALGORITHMS.A256CBC_HS512,)
|
||||
|
||||
AES_KW_ALGS = (ALGORITHMS.A128KW, ALGORITHMS.A192KW, ALGORITHMS.A256KW)
|
||||
|
||||
MODES = {
|
||||
ALGORITHMS.A128GCM: modes.GCM,
|
||||
ALGORITHMS.A192GCM: modes.GCM,
|
||||
ALGORITHMS.A256GCM: modes.GCM,
|
||||
ALGORITHMS.A128CBC_HS256: modes.CBC,
|
||||
ALGORITHMS.A192CBC_HS384: modes.CBC,
|
||||
ALGORITHMS.A256CBC_HS512: modes.CBC,
|
||||
ALGORITHMS.A128CBC: modes.CBC,
|
||||
ALGORITHMS.A192CBC: modes.CBC,
|
||||
ALGORITHMS.A256CBC: modes.CBC,
|
||||
ALGORITHMS.A128GCMKW: modes.GCM,
|
||||
ALGORITHMS.A192GCMKW: modes.GCM,
|
||||
ALGORITHMS.A256GCMKW: modes.GCM,
|
||||
ALGORITHMS.A128KW: None,
|
||||
ALGORITHMS.A192KW: None,
|
||||
ALGORITHMS.A256KW: None,
|
||||
}
|
||||
|
||||
def __init__(self, key, algorithm):
|
||||
if algorithm not in ALGORITHMS.AES:
|
||||
raise JWKError("%s is not a valid AES algorithm" % algorithm)
|
||||
if algorithm not in ALGORITHMS.SUPPORTED.union(ALGORITHMS.AES_PSEUDO):
|
||||
raise JWKError("%s is not a supported algorithm" % algorithm)
|
||||
|
||||
self._algorithm = algorithm
|
||||
self._mode = self.MODES.get(self._algorithm)
|
||||
|
||||
if algorithm in self.KEY_128 and len(key) != 16:
|
||||
raise JWKError(f"Key must be 128 bit for alg {algorithm}")
|
||||
elif algorithm in self.KEY_192 and len(key) != 24:
|
||||
raise JWKError(f"Key must be 192 bit for alg {algorithm}")
|
||||
elif algorithm in self.KEY_256 and len(key) != 32:
|
||||
raise JWKError(f"Key must be 256 bit for alg {algorithm}")
|
||||
elif algorithm in self.KEY_384 and len(key) != 48:
|
||||
raise JWKError(f"Key must be 384 bit for alg {algorithm}")
|
||||
elif algorithm in self.KEY_512 and len(key) != 64:
|
||||
raise JWKError(f"Key must be 512 bit for alg {algorithm}")
|
||||
|
||||
self._key = key
|
||||
|
||||
def to_dict(self):
|
||||
data = {"alg": self._algorithm, "kty": "oct", "k": base64url_encode(self._key)}
|
||||
return data
|
||||
|
||||
def encrypt(self, plain_text, aad=None):
|
||||
plain_text = ensure_binary(plain_text)
|
||||
try:
|
||||
iv = get_random_bytes(algorithms.AES.block_size // 8)
|
||||
mode = self._mode(iv)
|
||||
if mode.name == "GCM":
|
||||
cipher = aead.AESGCM(self._key)
|
||||
cipher_text_and_tag = cipher.encrypt(iv, plain_text, aad)
|
||||
cipher_text = cipher_text_and_tag[: len(cipher_text_and_tag) - 16]
|
||||
auth_tag = cipher_text_and_tag[-16:]
|
||||
else:
|
||||
cipher = Cipher(algorithms.AES(self._key), mode, backend=default_backend())
|
||||
encryptor = cipher.encryptor()
|
||||
padder = PKCS7(algorithms.AES.block_size).padder()
|
||||
padded_data = padder.update(plain_text)
|
||||
padded_data += padder.finalize()
|
||||
cipher_text = encryptor.update(padded_data) + encryptor.finalize()
|
||||
auth_tag = None
|
||||
return iv, cipher_text, auth_tag
|
||||
except Exception as e:
|
||||
raise JWEError(e)
|
||||
|
||||
def decrypt(self, cipher_text, iv=None, aad=None, tag=None):
|
||||
cipher_text = ensure_binary(cipher_text)
|
||||
try:
|
||||
iv = ensure_binary(iv)
|
||||
mode = self._mode(iv)
|
||||
if mode.name == "GCM":
|
||||
if tag is None:
|
||||
raise ValueError("tag cannot be None")
|
||||
cipher = aead.AESGCM(self._key)
|
||||
cipher_text_and_tag = cipher_text + tag
|
||||
try:
|
||||
plain_text = cipher.decrypt(iv, cipher_text_and_tag, aad)
|
||||
except InvalidTag:
|
||||
raise JWEError("Invalid JWE Auth Tag")
|
||||
else:
|
||||
cipher = Cipher(algorithms.AES(self._key), mode, backend=default_backend())
|
||||
decryptor = cipher.decryptor()
|
||||
padded_plain_text = decryptor.update(cipher_text)
|
||||
padded_plain_text += decryptor.finalize()
|
||||
unpadder = PKCS7(algorithms.AES.block_size).unpadder()
|
||||
plain_text = unpadder.update(padded_plain_text)
|
||||
plain_text += unpadder.finalize()
|
||||
|
||||
return plain_text
|
||||
except Exception as e:
|
||||
raise JWEError(e)
|
||||
|
||||
def wrap_key(self, key_data):
|
||||
key_data = ensure_binary(key_data)
|
||||
cipher_text = aes_key_wrap(self._key, key_data, default_backend())
|
||||
return cipher_text # IV, cipher text, auth tag
|
||||
|
||||
def unwrap_key(self, wrapped_key):
|
||||
wrapped_key = ensure_binary(wrapped_key)
|
||||
try:
|
||||
plain_text = aes_key_unwrap(self._key, wrapped_key, default_backend())
|
||||
except InvalidUnwrap as cause:
|
||||
raise JWEError(cause)
|
||||
return plain_text
|
||||
|
||||
|
||||
class CryptographyHMACKey(Key):
|
||||
"""
|
||||
Performs signing and verification operations using HMAC
|
||||
and the specified hash function.
|
||||
"""
|
||||
|
||||
ALG_MAP = {ALGORITHMS.HS256: hashes.SHA256(), ALGORITHMS.HS384: hashes.SHA384(), ALGORITHMS.HS512: hashes.SHA512()}
|
||||
|
||||
def __init__(self, key, algorithm):
|
||||
if algorithm not in ALGORITHMS.HMAC:
|
||||
raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
|
||||
self._algorithm = algorithm
|
||||
self._hash_alg = self.ALG_MAP.get(algorithm)
|
||||
|
||||
if isinstance(key, dict):
|
||||
self.prepared_key = self._process_jwk(key)
|
||||
return
|
||||
|
||||
if not isinstance(key, str) and not isinstance(key, bytes):
|
||||
raise JWKError("Expecting a string- or bytes-formatted key.")
|
||||
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
|
||||
invalid_strings = [
|
||||
b"-----BEGIN PUBLIC KEY-----",
|
||||
b"-----BEGIN RSA PUBLIC KEY-----",
|
||||
b"-----BEGIN CERTIFICATE-----",
|
||||
b"ssh-rsa",
|
||||
]
|
||||
|
||||
if any(string_value in key for string_value in invalid_strings):
|
||||
raise JWKError(
|
||||
"The specified key is an asymmetric key or x509 certificate and"
|
||||
" should not be used as an HMAC secret."
|
||||
)
|
||||
|
||||
self.prepared_key = key
|
||||
|
||||
def _process_jwk(self, jwk_dict):
|
||||
if not jwk_dict.get("kty") == "oct":
|
||||
raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty"))
|
||||
|
||||
k = jwk_dict.get("k")
|
||||
k = k.encode("utf-8")
|
||||
k = bytes(k)
|
||||
k = base64url_decode(k)
|
||||
|
||||
return k
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"alg": self._algorithm,
|
||||
"kty": "oct",
|
||||
"k": base64url_encode(self.prepared_key).decode("ASCII"),
|
||||
}
|
||||
|
||||
def sign(self, msg):
|
||||
msg = ensure_binary(msg)
|
||||
h = hmac.HMAC(self.prepared_key, self._hash_alg, backend=default_backend())
|
||||
h.update(msg)
|
||||
signature = h.finalize()
|
||||
return signature
|
||||
|
||||
def verify(self, msg, sig):
|
||||
msg = ensure_binary(msg)
|
||||
sig = ensure_binary(sig)
|
||||
h = hmac.HMAC(self.prepared_key, self._hash_alg, backend=default_backend())
|
||||
h.update(msg)
|
||||
try:
|
||||
h.verify(sig)
|
||||
verified = True
|
||||
except InvalidSignature:
|
||||
verified = False
|
||||
return verified
|
||||
@@ -0,0 +1,150 @@
|
||||
import hashlib
|
||||
|
||||
import ecdsa
|
||||
|
||||
from jose.backends.base import Key
|
||||
from jose.constants import ALGORITHMS
|
||||
from jose.exceptions import JWKError
|
||||
from jose.utils import base64_to_long, long_to_base64
|
||||
|
||||
|
||||
class ECDSAECKey(Key):
|
||||
"""
|
||||
Performs signing and verification operations using
|
||||
ECDSA and the specified hash function
|
||||
|
||||
This class requires the ecdsa package to be installed.
|
||||
|
||||
This is based off of the implementation in PyJWT 0.3.2
|
||||
"""
|
||||
|
||||
SHA256 = hashlib.sha256
|
||||
SHA384 = hashlib.sha384
|
||||
SHA512 = hashlib.sha512
|
||||
|
||||
CURVE_MAP = {
|
||||
SHA256: ecdsa.curves.NIST256p,
|
||||
SHA384: ecdsa.curves.NIST384p,
|
||||
SHA512: ecdsa.curves.NIST521p,
|
||||
}
|
||||
CURVE_NAMES = (
|
||||
(ecdsa.curves.NIST256p, "P-256"),
|
||||
(ecdsa.curves.NIST384p, "P-384"),
|
||||
(ecdsa.curves.NIST521p, "P-521"),
|
||||
)
|
||||
|
||||
def __init__(self, key, algorithm):
|
||||
if algorithm not in ALGORITHMS.EC:
|
||||
raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
|
||||
|
||||
self.hash_alg = {
|
||||
ALGORITHMS.ES256: self.SHA256,
|
||||
ALGORITHMS.ES384: self.SHA384,
|
||||
ALGORITHMS.ES512: self.SHA512,
|
||||
}.get(algorithm)
|
||||
self._algorithm = algorithm
|
||||
|
||||
self.curve = self.CURVE_MAP.get(self.hash_alg)
|
||||
|
||||
if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
|
||||
self.prepared_key = key
|
||||
return
|
||||
|
||||
if isinstance(key, dict):
|
||||
self.prepared_key = self._process_jwk(key)
|
||||
return
|
||||
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
|
||||
if isinstance(key, bytes):
|
||||
# Attempt to load key. We don't know if it's
|
||||
# a Signing Key or a Verifying Key, so we try
|
||||
# the Verifying Key first.
|
||||
try:
|
||||
key = ecdsa.VerifyingKey.from_pem(key)
|
||||
except ecdsa.der.UnexpectedDER:
|
||||
key = ecdsa.SigningKey.from_pem(key)
|
||||
except Exception as e:
|
||||
raise JWKError(e)
|
||||
|
||||
self.prepared_key = key
|
||||
return
|
||||
|
||||
raise JWKError("Unable to parse an ECKey from key: %s" % key)
|
||||
|
||||
def _process_jwk(self, jwk_dict):
|
||||
if not jwk_dict.get("kty") == "EC":
|
||||
raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get("kty"))
|
||||
|
||||
if not all(k in jwk_dict for k in ["x", "y", "crv"]):
|
||||
raise JWKError("Mandatory parameters are missing")
|
||||
|
||||
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"))
|
||||
|
||||
if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
|
||||
raise JWKError(f"Point: {x}, {y} is not a valid point")
|
||||
|
||||
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, allow_truncate=False
|
||||
)
|
||||
|
||||
def verify(self, msg, sig):
|
||||
try:
|
||||
return self.prepared_key.verify(
|
||||
sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string, allow_truncate=False
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def is_public(self):
|
||||
return isinstance(self.prepared_key, ecdsa.VerifyingKey)
|
||||
|
||||
def public_key(self):
|
||||
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 = None
|
||||
for key, value in self.CURVE_NAMES:
|
||||
if key == self.prepared_key.curve:
|
||||
crv = value
|
||||
if not crv:
|
||||
raise KeyError(f"Can't match {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).decode("ASCII"),
|
||||
"y": long_to_base64(public_key.pubkey.point.y(), size=key_size).decode("ASCII"),
|
||||
}
|
||||
|
||||
if not self.is_public():
|
||||
data["d"] = long_to_base64(self.prepared_key.privkey.secret_multiplier, size=key_size).decode("ASCII")
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,76 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
|
||||
from jose.backends.base import Key
|
||||
from jose.constants import ALGORITHMS
|
||||
from jose.exceptions import JWKError
|
||||
from jose.utils import base64url_decode, base64url_encode
|
||||
|
||||
|
||||
def get_random_bytes(num_bytes):
|
||||
return bytes(os.urandom(num_bytes))
|
||||
|
||||
|
||||
class HMACKey(Key):
|
||||
"""
|
||||
Performs signing and verification operations using HMAC
|
||||
and the specified hash function.
|
||||
"""
|
||||
|
||||
HASHES = {ALGORITHMS.HS256: hashlib.sha256, ALGORITHMS.HS384: hashlib.sha384, ALGORITHMS.HS512: hashlib.sha512}
|
||||
|
||||
def __init__(self, key, algorithm):
|
||||
if algorithm not in ALGORITHMS.HMAC:
|
||||
raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
|
||||
self._algorithm = algorithm
|
||||
self._hash_alg = self.HASHES.get(algorithm)
|
||||
|
||||
if isinstance(key, dict):
|
||||
self.prepared_key = self._process_jwk(key)
|
||||
return
|
||||
|
||||
if not isinstance(key, str) and not isinstance(key, bytes):
|
||||
raise JWKError("Expecting a string- or bytes-formatted key.")
|
||||
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
|
||||
invalid_strings = [
|
||||
b"-----BEGIN PUBLIC KEY-----",
|
||||
b"-----BEGIN RSA PUBLIC KEY-----",
|
||||
b"-----BEGIN CERTIFICATE-----",
|
||||
b"ssh-rsa",
|
||||
]
|
||||
|
||||
if any(string_value in key for string_value in invalid_strings):
|
||||
raise JWKError(
|
||||
"The specified key is an asymmetric key or x509 certificate and"
|
||||
" should not be used as an HMAC secret."
|
||||
)
|
||||
|
||||
self.prepared_key = key
|
||||
|
||||
def _process_jwk(self, jwk_dict):
|
||||
if not jwk_dict.get("kty") == "oct":
|
||||
raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty"))
|
||||
|
||||
k = jwk_dict.get("k")
|
||||
k = k.encode("utf-8")
|
||||
k = bytes(k)
|
||||
k = base64url_decode(k)
|
||||
|
||||
return k
|
||||
|
||||
def sign(self, msg):
|
||||
return hmac.new(self.prepared_key, msg, self._hash_alg).digest()
|
||||
|
||||
def verify(self, msg, sig):
|
||||
return hmac.compare_digest(sig, self.sign(msg))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"alg": self._algorithm,
|
||||
"kty": "oct",
|
||||
"k": base64url_encode(self.prepared_key).decode("ASCII"),
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import binascii
|
||||
import warnings
|
||||
|
||||
import rsa as pyrsa
|
||||
import rsa.pem as pyrsa_pem
|
||||
from pyasn1.error import PyAsn1Error
|
||||
from rsa import DecryptionError
|
||||
|
||||
from jose.backends._asn1 import (
|
||||
rsa_private_key_pkcs1_to_pkcs8,
|
||||
rsa_private_key_pkcs8_to_pkcs1,
|
||||
rsa_public_key_pkcs1_to_pkcs8,
|
||||
)
|
||||
from jose.backends.base import Key
|
||||
from jose.constants import ALGORITHMS
|
||||
from jose.exceptions import JWEError, JWKError
|
||||
from jose.utils import base64_to_long, long_to_base64
|
||||
|
||||
ALGORITHMS.SUPPORTED.remove(ALGORITHMS.RSA_OAEP) # RSA OAEP not supported
|
||||
|
||||
LEGACY_INVALID_PKCS8_RSA_HEADER = binascii.unhexlify(
|
||||
"30" # sequence
|
||||
"8204BD" # DER-encoded sequence contents length of 1213 bytes -- INCORRECT STATIC LENGTH
|
||||
"020100" # integer: 0 -- Version
|
||||
"30" # sequence
|
||||
"0D" # DER-encoded sequence contents length of 13 bytes -- PrivateKeyAlgorithmIdentifier
|
||||
"06092A864886F70D010101" # OID -- rsaEncryption
|
||||
"0500" # NULL -- parameters
|
||||
)
|
||||
ASN1_SEQUENCE_ID = binascii.unhexlify("30")
|
||||
RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1"
|
||||
|
||||
# Functions gcd and rsa_recover_prime_factors were copied from cryptography 1.9
|
||||
# to enable pure python rsa module to be in compliance with section 6.3.1 of RFC7518
|
||||
# which requires only private exponent (d) for private key.
|
||||
|
||||
|
||||
def _gcd(a, b):
|
||||
"""Calculate the Greatest Common Divisor of a and b.
|
||||
|
||||
Unless b==0, the result will have the same sign as b (so that when
|
||||
b is divided by it, the result comes out positive).
|
||||
"""
|
||||
while b:
|
||||
a, b = b, (a % b)
|
||||
return a
|
||||
|
||||
|
||||
# Controls the number of iterations rsa_recover_prime_factors will perform
|
||||
# to obtain the prime factors. Each iteration increments by 2 so the actual
|
||||
# maximum attempts is half this number.
|
||||
_MAX_RECOVERY_ATTEMPTS = 1000
|
||||
|
||||
|
||||
def _rsa_recover_prime_factors(n, e, d):
|
||||
"""
|
||||
Compute factors p and q from the private exponent d. We assume that n has
|
||||
no more than two factors. This function is adapted from code in PyCrypto.
|
||||
"""
|
||||
# See 8.2.2(i) in Handbook of Applied Cryptography.
|
||||
ktot = d * e - 1
|
||||
# The quantity d*e-1 is a multiple of phi(n), even,
|
||||
# and can be represented as t*2^s.
|
||||
t = ktot
|
||||
while t % 2 == 0:
|
||||
t = t // 2
|
||||
# Cycle through all multiplicative inverses in Zn.
|
||||
# The algorithm is non-deterministic, but there is a 50% chance
|
||||
# any candidate a leads to successful factoring.
|
||||
# See "Digitalized Signatures and Public Key Functions as Intractable
|
||||
# as Factorization", M. Rabin, 1979
|
||||
spotted = False
|
||||
a = 2
|
||||
while not spotted and a < _MAX_RECOVERY_ATTEMPTS:
|
||||
k = t
|
||||
# Cycle through all values a^{t*2^i}=a^k
|
||||
while k < ktot:
|
||||
cand = pow(a, k, n)
|
||||
# Check if a^k is a non-trivial root of unity (mod n)
|
||||
if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
|
||||
# We have found a number such that (cand-1)(cand+1)=0 (mod n).
|
||||
# Either of the terms divides n.
|
||||
p = _gcd(cand + 1, n)
|
||||
spotted = True
|
||||
break
|
||||
k *= 2
|
||||
# This value was not any good... let's try another!
|
||||
a += 2
|
||||
if not spotted:
|
||||
raise ValueError("Unable to compute factors p and q from exponent d.")
|
||||
# Found !
|
||||
q, r = divmod(n, p)
|
||||
assert r == 0
|
||||
p, q = sorted((p, q), reverse=True)
|
||||
return (p, q)
|
||||
|
||||
|
||||
def pem_to_spki(pem, fmt="PKCS8"):
|
||||
key = RSAKey(pem, ALGORITHMS.RS256)
|
||||
return key.to_pem(fmt)
|
||||
|
||||
|
||||
def _legacy_private_key_pkcs8_to_pkcs1(pkcs8_key):
|
||||
"""Legacy RSA private key PKCS8-to-PKCS1 conversion.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is incorrect parsing and only works because the legacy PKCS1-to-PKCS8
|
||||
encoding was also incorrect.
|
||||
"""
|
||||
# Only allow this processing if the prefix matches
|
||||
# AND the following byte indicates an ASN1 sequence,
|
||||
# as we would expect with the legacy encoding.
|
||||
if not pkcs8_key.startswith(LEGACY_INVALID_PKCS8_RSA_HEADER + ASN1_SEQUENCE_ID):
|
||||
raise ValueError("Invalid private key encoding")
|
||||
|
||||
return pkcs8_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER) :]
|
||||
|
||||
|
||||
class RSAKey(Key):
|
||||
SHA256 = "SHA-256"
|
||||
SHA384 = "SHA-384"
|
||||
SHA512 = "SHA-512"
|
||||
|
||||
def __init__(self, key, algorithm):
|
||||
if algorithm not in ALGORITHMS.RSA:
|
||||
raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
|
||||
|
||||
if algorithm in ALGORITHMS.RSA_KW and algorithm != ALGORITHMS.RSA1_5:
|
||||
raise JWKError("alg: %s is not supported by the RSA backend" % algorithm)
|
||||
|
||||
self.hash_alg = {
|
||||
ALGORITHMS.RS256: self.SHA256,
|
||||
ALGORITHMS.RS384: self.SHA384,
|
||||
ALGORITHMS.RS512: self.SHA512,
|
||||
}.get(algorithm)
|
||||
self._algorithm = algorithm
|
||||
|
||||
if isinstance(key, dict):
|
||||
self._prepared_key = self._process_jwk(key)
|
||||
return
|
||||
|
||||
if isinstance(key, (pyrsa.PublicKey, pyrsa.PrivateKey)):
|
||||
self._prepared_key = key
|
||||
return
|
||||
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
|
||||
if isinstance(key, bytes):
|
||||
try:
|
||||
self._prepared_key = pyrsa.PublicKey.load_pkcs1(key)
|
||||
except ValueError:
|
||||
try:
|
||||
self._prepared_key = pyrsa.PublicKey.load_pkcs1_openssl_pem(key)
|
||||
except ValueError:
|
||||
try:
|
||||
self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key)
|
||||
except ValueError:
|
||||
try:
|
||||
der = pyrsa_pem.load_pem(key, b"PRIVATE KEY")
|
||||
try:
|
||||
pkcs1_key = rsa_private_key_pkcs8_to_pkcs1(der)
|
||||
except PyAsn1Error:
|
||||
# If the key was encoded using the old, invalid,
|
||||
# encoding then pyasn1 will throw an error attempting
|
||||
# to parse the key.
|
||||
pkcs1_key = _legacy_private_key_pkcs8_to_pkcs1(der)
|
||||
self._prepared_key = pyrsa.PrivateKey.load_pkcs1(pkcs1_key, format="DER")
|
||||
except ValueError as e:
|
||||
raise JWKError(e)
|
||||
return
|
||||
raise JWKError("Unable to parse an RSA_JWK from key: %s" % key)
|
||||
|
||||
def _process_jwk(self, jwk_dict):
|
||||
if not jwk_dict.get("kty") == "RSA":
|
||||
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get("kty"))
|
||||
|
||||
e = base64_to_long(jwk_dict.get("e"))
|
||||
n = base64_to_long(jwk_dict.get("n"))
|
||||
|
||||
if "d" not in jwk_dict:
|
||||
return pyrsa.PublicKey(e=e, n=n)
|
||||
else:
|
||||
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"])
|
||||
return pyrsa.PrivateKey(e=e, n=n, d=d, p=p, q=q)
|
||||
else:
|
||||
p, q = _rsa_recover_prime_factors(n, e, d)
|
||||
return pyrsa.PrivateKey(n=n, e=e, d=d, p=p, q=q)
|
||||
|
||||
def sign(self, msg):
|
||||
return pyrsa.sign(msg, self._prepared_key, self.hash_alg)
|
||||
|
||||
def verify(self, msg, sig):
|
||||
if not self.is_public():
|
||||
warnings.warn("Attempting to verify a message with a private key. " "This is not recommended.")
|
||||
try:
|
||||
pyrsa.verify(msg, sig, self._prepared_key)
|
||||
return True
|
||||
except pyrsa.pkcs1.VerificationError:
|
||||
return False
|
||||
|
||||
def is_public(self):
|
||||
return isinstance(self._prepared_key, pyrsa.PublicKey)
|
||||
|
||||
def public_key(self):
|
||||
if isinstance(self._prepared_key, pyrsa.PublicKey):
|
||||
return self
|
||||
return self.__class__(pyrsa.PublicKey(n=self._prepared_key.n, e=self._prepared_key.e), self._algorithm)
|
||||
|
||||
def to_pem(self, pem_format="PKCS8"):
|
||||
|
||||
if isinstance(self._prepared_key, pyrsa.PrivateKey):
|
||||
der = self._prepared_key.save_pkcs1(format="DER")
|
||||
if pem_format == "PKCS8":
|
||||
pkcs8_der = rsa_private_key_pkcs1_to_pkcs8(der)
|
||||
pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker="PRIVATE KEY")
|
||||
elif pem_format == "PKCS1":
|
||||
pem = pyrsa_pem.save_pem(der, pem_marker="RSA PRIVATE KEY")
|
||||
else:
|
||||
raise ValueError(f"Invalid pem format specified: {pem_format!r}")
|
||||
else:
|
||||
if pem_format == "PKCS8":
|
||||
pkcs1_der = self._prepared_key.save_pkcs1(format="DER")
|
||||
pkcs8_der = rsa_public_key_pkcs1_to_pkcs8(pkcs1_der)
|
||||
pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker="PUBLIC KEY")
|
||||
elif pem_format == "PKCS1":
|
||||
der = self._prepared_key.save_pkcs1(format="DER")
|
||||
pem = pyrsa_pem.save_pem(der, pem_marker="RSA PUBLIC KEY")
|
||||
else:
|
||||
raise ValueError(f"Invalid pem format specified: {pem_format!r}")
|
||||
return pem
|
||||
|
||||
def to_dict(self):
|
||||
if not self.is_public():
|
||||
public_key = self.public_key()._prepared_key
|
||||
else:
|
||||
public_key = self._prepared_key
|
||||
|
||||
data = {
|
||||
"alg": self._algorithm,
|
||||
"kty": "RSA",
|
||||
"n": long_to_base64(public_key.n).decode("ASCII"),
|
||||
"e": long_to_base64(public_key.e).decode("ASCII"),
|
||||
}
|
||||
|
||||
if not self.is_public():
|
||||
data.update(
|
||||
{
|
||||
"d": long_to_base64(self._prepared_key.d).decode("ASCII"),
|
||||
"p": long_to_base64(self._prepared_key.p).decode("ASCII"),
|
||||
"q": long_to_base64(self._prepared_key.q).decode("ASCII"),
|
||||
"dp": long_to_base64(self._prepared_key.exp1).decode("ASCII"),
|
||||
"dq": long_to_base64(self._prepared_key.exp2).decode("ASCII"),
|
||||
"qi": long_to_base64(self._prepared_key.coef).decode("ASCII"),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def wrap_key(self, key_data):
|
||||
if not self.is_public():
|
||||
warnings.warn("Attempting to encrypt a message with a private key." " This is not recommended.")
|
||||
wrapped_key = pyrsa.encrypt(key_data, self._prepared_key)
|
||||
return wrapped_key
|
||||
|
||||
def unwrap_key(self, wrapped_key):
|
||||
try:
|
||||
unwrapped_key = pyrsa.decrypt(wrapped_key, self._prepared_key)
|
||||
except DecryptionError as e:
|
||||
raise JWEError(e)
|
||||
return unwrapped_key
|
||||
Reference in New Issue
Block a user