first commit
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
from typing import Any, Literal, cast
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from cryptography.x509 import load_pem_x509_certificate
|
||||
from cryptography.hazmat.primitives.serialization import (
|
||||
load_pem_private_key,
|
||||
load_pem_public_key,
|
||||
load_ssh_public_key,
|
||||
load_ssh_private_key,
|
||||
load_der_private_key,
|
||||
load_der_public_key,
|
||||
Encoding,
|
||||
PrivateFormat,
|
||||
PublicFormat,
|
||||
KeySerializationEncryption,
|
||||
BestAvailableEncryption,
|
||||
NoEncryption,
|
||||
)
|
||||
from .models import NativeKeyBinding, GenericKey
|
||||
from .types import DictKey
|
||||
from ..errors import InvalidKeyTypeError
|
||||
from ..util import to_bytes
|
||||
|
||||
|
||||
def import_from_ssh_key(raw: bytes) -> Any:
|
||||
return load_ssh_public_key(raw)
|
||||
|
||||
|
||||
def import_from_pem_key(raw: bytes, password: bytes | None = None) -> Any:
|
||||
key: Any
|
||||
|
||||
if b"OPENSSH PRIVATE" in raw:
|
||||
key = load_ssh_private_key(raw, password=password)
|
||||
|
||||
elif b"PUBLIC" in raw:
|
||||
key = load_pem_public_key(raw)
|
||||
|
||||
elif b"PRIVATE" in raw:
|
||||
key = load_pem_private_key(raw, password=password)
|
||||
|
||||
elif b"CERTIFICATE" in raw:
|
||||
cert = load_pem_x509_certificate(raw)
|
||||
return cert.public_key()
|
||||
|
||||
else:
|
||||
try:
|
||||
key = load_der_private_key(raw, password=password)
|
||||
except ValueError:
|
||||
key = load_der_public_key(raw)
|
||||
return key
|
||||
|
||||
|
||||
def dump_pem_key(
|
||||
key: Any,
|
||||
encoding: Literal["PEM", "DER"] | None = None,
|
||||
private: bool | None = False,
|
||||
password: Any | None = None,
|
||||
) -> bytes:
|
||||
"""Export key into PEM/DER format bytes.
|
||||
|
||||
:param key: native cryptography key
|
||||
:param encoding: "PEM" or "DER"
|
||||
:param private: export private key or public key
|
||||
:param password: encrypt private key with password
|
||||
:return: bytes
|
||||
"""
|
||||
|
||||
if encoding is None or encoding == "PEM":
|
||||
encoding_enum = Encoding.PEM
|
||||
elif encoding == "DER":
|
||||
encoding_enum = Encoding.DER
|
||||
else: # pragma: no cover
|
||||
raise ValueError(f"Invalid encoding: {encoding}")
|
||||
|
||||
if private:
|
||||
encryption_algorithm: KeySerializationEncryption
|
||||
if password is None:
|
||||
encryption_algorithm = NoEncryption()
|
||||
else:
|
||||
encryption_algorithm = BestAvailableEncryption(to_bytes(password))
|
||||
value = key.private_bytes(
|
||||
encoding=encoding_enum,
|
||||
format=PrivateFormat.PKCS8,
|
||||
encryption_algorithm=encryption_algorithm,
|
||||
)
|
||||
else:
|
||||
value = key.public_bytes(
|
||||
encoding=encoding_enum,
|
||||
format=PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
return cast(bytes, value)
|
||||
|
||||
|
||||
class CryptographyBinding(NativeKeyBinding, metaclass=ABCMeta):
|
||||
key_type: str
|
||||
ssh_type: bytes
|
||||
_cryptography_key_types: Any
|
||||
|
||||
@classmethod
|
||||
def check_ssh_type(cls, value: bytes) -> bool:
|
||||
return value.startswith(cls.ssh_type)
|
||||
|
||||
@classmethod
|
||||
def check_cryptography_key(cls, native_key: Any) -> bool:
|
||||
return isinstance(native_key, cls._cryptography_key_types)
|
||||
|
||||
@classmethod
|
||||
def convert_raw_key_to_dict(cls, raw_key: Any, private: bool) -> DictKey:
|
||||
if private:
|
||||
value = cls.export_private_key(raw_key)
|
||||
else:
|
||||
value = cls.export_public_key(raw_key)
|
||||
return cast(DictKey, value)
|
||||
|
||||
@classmethod
|
||||
def import_from_dict(cls, value: DictKey) -> Any:
|
||||
if "d" in value:
|
||||
return cls.import_private_key(value)
|
||||
return cls.import_public_key(value)
|
||||
|
||||
@classmethod
|
||||
def import_from_bytes(cls, value: bytes, password: Any | None = None) -> Any:
|
||||
if cls.check_ssh_type(value):
|
||||
return import_from_ssh_key(value)
|
||||
|
||||
if password is not None:
|
||||
password = to_bytes(password)
|
||||
|
||||
key = import_from_pem_key(value, password)
|
||||
if not cls.check_cryptography_key(key):
|
||||
raise InvalidKeyTypeError(f"Not a key of: '{cls.key_type}'")
|
||||
return key
|
||||
|
||||
@staticmethod
|
||||
def as_bytes(
|
||||
key: GenericKey,
|
||||
encoding: Literal["PEM", "DER"] | None = None,
|
||||
private: bool = False,
|
||||
password: Any | None = None,
|
||||
) -> bytes:
|
||||
if private:
|
||||
return dump_pem_key(key.private_key, encoding, private, password)
|
||||
else:
|
||||
return dump_pem_key(key.public_key, encoding, private, password)
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def import_private_key(cls, obj: Any) -> Any:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def import_public_key(cls, obj: Any) -> Any:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def export_private_key(cls, key: Any) -> Any:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def export_public_key(cls, key: Any) -> Any:
|
||||
pass
|
||||
Reference in New Issue
Block a user