1
0
mirror of https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git synced 2025-08-14 00:25:46 +02:00

Initial commit

This commit is contained in:
MoonTestUse1
2024-12-23 19:27:44 +06:00
commit e81df4c87e
4952 changed files with 1705479 additions and 0 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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),
}

View File

@@ -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

View File

@@ -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

View File

@@ -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"),
}

View File

@@ -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