diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index ae4a31720..5f849d17f 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -61,6 +61,7 @@ "stubs/pyasn1", "stubs/pyflakes", "stubs/Pygments", + "stubs/pyjks", "stubs/PyMySQL", "stubs/python-dateutil", "stubs/python-jose", diff --git a/stubs/pyjks/@tests/stubtest_allowlist.txt b/stubs/pyjks/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000..bea8ef120 --- /dev/null +++ b/stubs/pyjks/@tests/stubtest_allowlist.txt @@ -0,0 +1,60 @@ +# Attributes implemented by __getattr__ +jks.jks.PrivateKeyEntry.algorithm_oid +jks.jks.PrivateKeyEntry.pkey +jks.jks.PrivateKeyEntry.pkey_pkcs8 +jks.jks.SecretKeyEntry.algorithm +jks.jks.SecretKeyEntry.key +jks.jks.SecretKeyEntry.key_size +jks.PrivateKeyEntry.algorithm_oid +jks.PrivateKeyEntry.pkey +jks.PrivateKeyEntry.pkey_pkcs8 +jks.SecretKeyEntry.algorithm +jks.SecretKeyEntry.key +jks.SecretKeyEntry.key_size + +# Implicit imports, not re-exported +jks.DSA_OID +jks.DSA_WITH_SHA1_OID +jks.ENTRY_TYPE_CERTIFICATE +jks.ENTRY_TYPE_KEY +jks.ENTRY_TYPE_SEALED +jks.ENTRY_TYPE_SECRET +jks.KEY_TYPE_PRIVATE +jks.KEY_TYPE_PUBLIC +jks.KEY_TYPE_SECRET +jks.MAGIC_NUMBER_JCEKS +jks.MAGIC_NUMBER_JKS +jks.RSA_ENCRYPTION_OID +jks.SIGNATURE_WHITENING +jks.b1 +jks.b2 +jks.b4 +jks.b8 +jks.py23basestring +jks.bks.DSA_OID +jks.bks.DSA_WITH_SHA1_OID +jks.bks.RSA_ENCRYPTION_OID +jks.bks.b1 +jks.bks.b2 +jks.bks.b4 +jks.bks.b8 +jks.bks.py23basestring +jks.jks.DSA_OID +jks.jks.DSA_WITH_SHA1_OID +jks.jks.RSA_ENCRYPTION_OID +jks.jks.b1 +jks.jks.b2 +jks.jks.b4 +jks.jks.b8 +jks.jks.py23basestring +jks.sun_crypto.DSA_OID +jks.sun_crypto.DSA_WITH_SHA1_OID +jks.sun_crypto.RSA_ENCRYPTION_OID +jks.sun_crypto.b1 +jks.sun_crypto.b2 +jks.sun_crypto.b4 +jks.sun_crypto.b8 +jks.sun_crypto.py23basestring + +# Unintended reexports due to `from .utils import *` imports at runtime +jks\..*print_function diff --git a/stubs/pyjks/METADATA.toml b/stubs/pyjks/METADATA.toml new file mode 100644 index 000000000..ccc64c3d6 --- /dev/null +++ b/stubs/pyjks/METADATA.toml @@ -0,0 +1,3 @@ +version = "20.0.*" +upstream_repository = "https://github.com/kurtbrose/pyjks" +requires = ["types-pyasn1"] diff --git a/stubs/pyjks/jks/__init__.pyi b/stubs/pyjks/jks/__init__.pyi new file mode 100644 index 000000000..26eee4cd2 --- /dev/null +++ b/stubs/pyjks/jks/__init__.pyi @@ -0,0 +1,48 @@ +# pyjks exports lots of junk such as jks.jks.SIGNATURE_WHITENING, jks.util.b8 etc. +# We don't mark those as re-exported as those don't seem like intended part of the public API. +from .bks import ( + AbstractBksEntry as AbstractBksEntry, + BksKeyEntry as BksKeyEntry, + BksKeyStore as BksKeyStore, + BksSealedKeyEntry as BksSealedKeyEntry, + BksSecretKeyEntry as BksSecretKeyEntry, + BksTrustedCertEntry as BksTrustedCertEntry, + UberKeyStore as UberKeyStore, +) +from .jks import ( + KeyStore as KeyStore, + PrivateKeyEntry as PrivateKeyEntry, + SecretKeyEntry as SecretKeyEntry, + TrustedCertEntry as TrustedCertEntry, + __version__ as __version__, + __version_info__ as __version_info__, +) +from .util import ( + AbstractKeystore as AbstractKeystore, + AbstractKeystoreEntry as AbstractKeystoreEntry, + BadDataLengthException as BadDataLengthException, + BadHashCheckException as BadHashCheckException, + BadKeystoreFormatException as BadKeystoreFormatException, + BadPaddingException as BadPaddingException, + DecryptionFailureException as DecryptionFailureException, + DuplicateAliasException as DuplicateAliasException, + KeystoreException as KeystoreException, + KeystoreSignatureException as KeystoreSignatureException, + NotYetDecryptedException as NotYetDecryptedException, + UnexpectedAlgorithmException as UnexpectedAlgorithmException, + UnexpectedJavaTypeException as UnexpectedJavaTypeException, + UnexpectedKeyEncodingException as UnexpectedKeyEncodingException, + UnsupportedKeyFormatException as UnsupportedKeyFormatException, + UnsupportedKeystoreEntryTypeException as UnsupportedKeystoreEntryTypeException, + UnsupportedKeystoreTypeException as UnsupportedKeystoreTypeException, + UnsupportedKeystoreVersionException as UnsupportedKeystoreVersionException, + add_pkcs7_padding as add_pkcs7_padding, + as_hex as as_hex, + as_pem as as_pem, + bitstring_to_bytes as bitstring_to_bytes, + pkey_as_pem as pkey_as_pem, + print_pem as print_pem, + strip_pkcs5_padding as strip_pkcs5_padding, + strip_pkcs7_padding as strip_pkcs7_padding, + xor_bytearrays as xor_bytearrays, +) diff --git a/stubs/pyjks/jks/bks.pyi b/stubs/pyjks/jks/bks.pyi new file mode 100644 index 000000000..a22a527f1 --- /dev/null +++ b/stubs/pyjks/jks/bks.pyi @@ -0,0 +1,69 @@ +from _typeshed import Incomplete +from typing_extensions import Final, Self + +from .jks import TrustedCertEntry +from .util import AbstractKeystore, AbstractKeystoreEntry + +ENTRY_TYPE_CERTIFICATE: Final = 1 +ENTRY_TYPE_KEY: Final = 2 +ENTRY_TYPE_SECRET: Final = 3 +ENTRY_TYPE_SEALED: Final = 4 +KEY_TYPE_PRIVATE: Final = 0 +KEY_TYPE_PUBLIC: Final = 1 +KEY_TYPE_SECRET: Final = 2 + +class AbstractBksEntry(AbstractKeystoreEntry): + cert_chain: Incomplete + def __init__(self, **kwargs) -> None: ... + +class BksTrustedCertEntry(TrustedCertEntry): ... + +class BksKeyEntry(AbstractBksEntry): + type: Incomplete + format: Incomplete + algorithm: Incomplete + encoded: Incomplete + pkey_pkcs8: Incomplete + pkey: Incomplete + algorithm_oid: Incomplete + public_key_info: Incomplete + public_key: Incomplete + key: Incomplete + key_size: Incomplete + def __init__(self, type, format, algorithm, encoded, **kwargs) -> None: ... + def is_decrypted(self): ... + def decrypt(self, key_password) -> None: ... + @classmethod + def type2str(cls, t): ... + +class BksSecretKeyEntry(AbstractBksEntry): + key: Incomplete + def __init__(self, **kwargs) -> None: ... + def is_decrypted(self): ... + def decrypt(self, key_password) -> None: ... + +class BksSealedKeyEntry(AbstractBksEntry): + def __init__(self, **kwargs) -> None: ... + def __getattr__(self, name): ... + def is_decrypted(self): ... + def decrypt(self, key_password) -> None: ... + +class BksKeyStore(AbstractKeystore): + version: Incomplete + def __init__(self, store_type, entries, version: int = 2) -> None: ... + @property + def certs(self): ... + @property + def secret_keys(self): ... + @property + def sealed_keys(self): ... + @property + def plain_keys(self): ... + @classmethod + def loads(cls, data, store_password, try_decrypt_keys: bool = True) -> Self: ... + +class UberKeyStore(BksKeyStore): + @classmethod + def loads(cls, data, store_password, try_decrypt_keys: bool = True) -> Self: ... + version: Incomplete + def __init__(self, store_type, entries, version: int = 1) -> None: ... diff --git a/stubs/pyjks/jks/jks.pyi b/stubs/pyjks/jks/jks.pyi new file mode 100644 index 000000000..5afa56c52 --- /dev/null +++ b/stubs/pyjks/jks/jks.pyi @@ -0,0 +1,109 @@ +from _typeshed import Incomplete, SupportsKeysAndGetItem +from collections.abc import Iterable +from typing import Any, NoReturn +from typing_extensions import Final, Literal, Self, TypeAlias + +from .util import AbstractKeystore, AbstractKeystoreEntry + +__version_info__: Final[tuple[int, int, int] | tuple[int, int, int, str]] +__version__: Final[str] +MAGIC_NUMBER_JKS: Final[bytes] +MAGIC_NUMBER_JCEKS: Final[bytes] +SIGNATURE_WHITENING: Final[bytes] + +_JksType: TypeAlias = Literal["jks", "jceks"] +_CertType: TypeAlias = Literal["X.509"] +_KeyFormat: TypeAlias = Literal["pkcs8", "rsa_raw"] + +class TrustedCertEntry(AbstractKeystoreEntry): + store_type: _JksType | None + type: _CertType | None + cert: bytes + def __init__( + self, + *, + type: _CertType = ..., + cert: bytes = ..., + store_type: _JksType = ..., + alias: str = ..., + timestamp: int = ..., + **kwargs: Any, + ) -> None: ... + @classmethod + def new(cls, alias: str, cert: bytes) -> Self: ... # type: ignore[override] + def is_decrypted(self) -> Literal[True]: ... + +class PrivateKeyEntry(AbstractKeystoreEntry): + store_type: _JksType | None + cert_chain: list[tuple[_CertType, bytes]] + # Properties provided by __getattr__ after decryption + @property + def pkey(self) -> bytes: ... + @property + def pkey_pkcs8(self) -> bytes: ... + @property + def algorithm_oid(self) -> tuple[int, ...]: ... + def __init__( + self, + *, + cert_chain: list[tuple[_CertType, bytes]] = ..., + encrypted: bool = ..., + pkey: bytes = ..., + pkey_pkcs8: bytes = ..., + algorithm_oid: tuple[int, ...] = ..., + store_type: _JksType = ..., + alias: str = ..., + timestamp: int = ..., + **kwargs: Any, + ) -> None: ... + @classmethod + def new( # type: ignore[override] + cls, alias: str, certs: Iterable[bytes], key: bytes, key_format: _KeyFormat = "pkcs8" + ) -> Self: ... + +class SecretKeyEntry(AbstractKeystoreEntry): + store_type: _JksType | None + # Properties provided by __getattr__ + @property + def algorithm(self) -> str: ... + @property + def key(self) -> bytes: ... + @property + def key_size(self) -> int: ... + def __init__( + self, + *, + sealed_obj: Incomplete = ..., + algorithm: str = ..., + key: bytes = ..., + key_size: int = ..., + store_type: _JksType = ..., + alias: str = ..., + timestamp: int = ..., + **kwargs: Any, + ) -> None: ... + # Not implemented by pyjks + @classmethod + def new( # type: ignore[override] + cls, alias: str, sealed_obj: bool, algorithm: str, key: bytes, key_size: int + ) -> NoReturn: ... + # Not implemented by pyjks + def encrypt(self, key_password: str) -> NoReturn: ... + +class KeyStore(AbstractKeystore): + entries: dict[str, TrustedCertEntry | PrivateKeyEntry | SecretKeyEntry] # type: ignore[assignment] + store_type: _JksType + @classmethod + def new(cls, store_type: _JksType, store_entries: Iterable[TrustedCertEntry | PrivateKeyEntry | SecretKeyEntry]) -> Self: ... + @classmethod + def loads(cls, data: bytes, store_password: str | None, try_decrypt_keys: bool = True) -> Self: ... + def saves(self, store_password: str) -> bytes: ... + def __init__( + self, store_type: _JksType, entries: SupportsKeysAndGetItem[str, TrustedCertEntry | PrivateKeyEntry | SecretKeyEntry] + ) -> None: ... + @property + def certs(self) -> dict[str, TrustedCertEntry]: ... + @property + def secret_keys(self) -> dict[str, SecretKeyEntry]: ... + @property + def private_keys(self) -> dict[str, PrivateKeyEntry]: ... diff --git a/stubs/pyjks/jks/rfc2898.pyi b/stubs/pyjks/jks/rfc2898.pyi new file mode 100644 index 000000000..a565e41ce --- /dev/null +++ b/stubs/pyjks/jks/rfc2898.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete + +from pyasn1.type import univ + +class PBEParameter(univ.Sequence): + componentType: Incomplete diff --git a/stubs/pyjks/jks/rfc7292.pyi b/stubs/pyjks/jks/rfc7292.pyi new file mode 100644 index 000000000..b9dd49ceb --- /dev/null +++ b/stubs/pyjks/jks/rfc7292.pyi @@ -0,0 +1,28 @@ +from _typeshed import Incomplete +from hashlib import _Hash +from typing_extensions import Final, Literal, TypeAlias + +from pyasn1.type import univ + +PBE_WITH_SHA1_AND_TRIPLE_DES_CBC_OID: Final[tuple[int, ...]] +PURPOSE_KEY_MATERIAL: Final = 1 +PURPOSE_IV_MATERIAL: Final = 2 +PURPOSE_MAC_MATERIAL: Final = 3 + +_Purpose: TypeAlias = Literal[1, 2, 3] + +class Pkcs12PBEParams(univ.Sequence): + componentType: Incomplete + +def derive_key( + hashfn: _Hash, purpose_byte: _Purpose, password_str: str, salt: bytes, iteration_count: int, desired_key_size: int +) -> bytes: ... +def decrypt_PBEWithSHAAnd3KeyTripleDESCBC( + data: bytes | bytearray, password_str: str, salt: bytes, iteration_count: int +) -> bytes: ... +def decrypt_PBEWithSHAAndTwofishCBC( + encrypted_data: bytes | bytearray, password: str, salt: bytes, iteration_count: int +) -> bytes: ... +def encrypt_PBEWithSHAAndTwofishCBC( + plaintext_data: bytes | bytearray, password: str, salt: bytes, iteration_count: int +) -> bytes: ... diff --git a/stubs/pyjks/jks/sun_crypto.pyi b/stubs/pyjks/jks/sun_crypto.pyi new file mode 100644 index 000000000..822892b55 --- /dev/null +++ b/stubs/pyjks/jks/sun_crypto.pyi @@ -0,0 +1,8 @@ +from typing_extensions import Final + +SUN_JKS_ALGO_ID: Final[tuple[int, ...]] +SUN_JCE_ALGO_ID: Final[tuple[int, ...]] + +def jks_pkey_encrypt(key: bytes | bytearray, password_str: str) -> bytes: ... +def jks_pkey_decrypt(data: bytes | bytearray, password_str: str) -> bytes: ... +def jce_pbe_decrypt(data: bytes | bytearray, password: str, salt: bytes, iteration_count: int) -> bytes: ... diff --git a/stubs/pyjks/jks/util.pyi b/stubs/pyjks/jks/util.pyi new file mode 100644 index 000000000..f926b9486 --- /dev/null +++ b/stubs/pyjks/jks/util.pyi @@ -0,0 +1,66 @@ +from _typeshed import FileDescriptorOrPath, SupportsKeysAndGetItem +from collections.abc import Iterable +from struct import Struct +from typing import Any +from typing_extensions import Final, Literal, Self, TypeAlias + +from .bks import BksKeyEntry +from .jks import PrivateKeyEntry + +b8: Final[Struct] +b4: Final[Struct] +b2: Final[Struct] +b1: Final[Struct] +py23basestring: Final[tuple[type[str], type[str]]] +RSA_ENCRYPTION_OID: Final[tuple[int, ...]] +DSA_OID: Final[tuple[int, ...]] +DSA_WITH_SHA1_OID: Final[tuple[int, ...]] + +_KeystoreType: TypeAlias = Literal["jks", "jceks", "bks", "uber"] +_PemType: TypeAlias = Literal["CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY", "RSA PRIVATE KEY"] + +class KeystoreException(Exception): ... +class KeystoreSignatureException(KeystoreException): ... +class DuplicateAliasException(KeystoreException): ... +class NotYetDecryptedException(KeystoreException): ... +class BadKeystoreFormatException(KeystoreException): ... +class BadDataLengthException(KeystoreException): ... +class BadPaddingException(KeystoreException): ... +class BadHashCheckException(KeystoreException): ... +class DecryptionFailureException(KeystoreException): ... +class UnsupportedKeystoreVersionException(KeystoreException): ... +class UnexpectedJavaTypeException(KeystoreException): ... +class UnexpectedAlgorithmException(KeystoreException): ... +class UnexpectedKeyEncodingException(KeystoreException): ... +class UnsupportedKeystoreTypeException(KeystoreException): ... +class UnsupportedKeystoreEntryTypeException(KeystoreException): ... +class UnsupportedKeyFormatException(KeystoreException): ... + +class AbstractKeystore: + store_type: _KeystoreType + entries: dict[str, AbstractKeystoreEntry] + def __init__(self, store_type: _KeystoreType, entries: SupportsKeysAndGetItem[str, AbstractKeystoreEntry]) -> None: ... + @classmethod + def load(cls, filename: FileDescriptorOrPath, store_password: str | None, try_decrypt_keys: bool = True) -> Self: ... + def save(self, filename: FileDescriptorOrPath, store_password: str) -> None: ... + +class AbstractKeystoreEntry: + store_type: _KeystoreType | None + alias: str + timestamp: int + def __init__(self, *, store_type: _KeystoreType = ..., alias: str = ..., timestamp: int = ..., **kwargs: Any) -> None: ... + @classmethod + def new(cls, alias: str) -> Self: ... + def is_decrypted(self) -> bool: ... + def decrypt(self, key_password: str) -> None: ... + def encrypt(self, key_password: str) -> None: ... + +def as_hex(ba: bytes | bytearray) -> str: ... +def as_pem(der_bytes: bytes, type: _PemType) -> str: ... +def bitstring_to_bytes(bitstr: Iterable[int]) -> bytes: ... +def xor_bytearrays(a: bytes | bytearray, b: bytes | bytearray) -> bytearray: ... +def print_pem(der_bytes: bytes, type: _PemType) -> None: ... +def pkey_as_pem(pk: PrivateKeyEntry | BksKeyEntry) -> str: ... +def strip_pkcs5_padding(m: bytes | bytearray) -> bytes: ... +def strip_pkcs7_padding(m: bytes | bytearray, block_size: int) -> bytes: ... +def add_pkcs7_padding(m: bytes | bytearray, block_size: int) -> bytes: ...