Files
sourcehub/.venv/Lib/site-packages/joserfc/_rfc7517/pem.py
T
2026-09-14 23:22:10 +09:00

164 lines
4.6 KiB
Python

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