Skip to content

Commit 557291d

Browse files
committed
pk/tls: Add PrivateKey interface from PEP 543
1 parent 5c3b817 commit 557291d

File tree

5 files changed

+97
-38
lines changed

5 files changed

+97
-38
lines changed

src/mbedtls/_tls.pyi

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ from mbedtls._tlsi import (
1616
TLSConfiguration,
1717
TLSVersion,
1818
)
19-
from mbedtls.pk import ECC, RSA
2019

2120
if sys.version_info < (3, 8):
2221
from typing_extensions import Literal
@@ -84,8 +83,6 @@ class Purpose(enum.IntEnum):
8483
SERVER_AUTH: int
8584
CLIENT_AUTH: int
8685

87-
_Key = Union[RSA, ECC]
88-
PrivateKey = _Key
8986
CipherSuite = str
9087
ServerNameCallback = object
9188

src/mbedtls/_tlsi.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@
99
import os
1010
import sys
1111
from dataclasses import dataclass, field
12-
from typing import Mapping, Optional, Tuple, TypeVar, Union
12+
from typing import Callable, Mapping, Optional, Tuple, TypeVar, Union
1313

1414
if sys.version_info < (3, 8):
1515
from typing_extensions import Literal, Protocol
1616
else:
1717
from typing import Literal, Protocol
1818

19+
if sys.version_info < (3, 9):
20+
_PathLike = os.PathLike
21+
else:
22+
_PathLike = os.PathLike[str]
23+
1924
__all__ = ["NextProtocol", "TLSVersion", "DTLSVersion"]
2025

26+
_Path = Union[_PathLike, str]
27+
2128

2229
@enum.unique
2330
class NextProtocol(enum.Enum):
@@ -59,7 +66,7 @@ def system(cls) -> TrustStore:
5966
"""
6067

6168
@classmethod
62-
def from_pem_file(cls, path: Union[str, os.PathLike[str]]) -> TrustStore:
69+
def from_pem_file(cls, path: _Path) -> TrustStore:
6370
"""Initializes a trust store from a single file full of PEMs."""
6471

6572

@@ -78,7 +85,7 @@ def from_buffer(cls, buffer: bytes) -> Certificate:
7885
"""
7986

8087
@classmethod
81-
def from_file(cls, path: Union[str, os.PathLike[str]]) -> Certificate:
88+
def from_file(cls, path: _Path) -> Certificate:
8289
"""Creates a Certificate object from a file on disk.
8390
8491
This method may be a convenience method that wraps ``open`` and
@@ -89,7 +96,55 @@ def from_file(cls, path: Union[str, os.PathLike[str]]) -> Certificate:
8996
"""
9097

9198

92-
PrivateKey = object
99+
class PrivateKey(Protocol):
100+
@classmethod
101+
def from_buffer(
102+
cls,
103+
buffer: bytes,
104+
password: Optional[
105+
Union[Callable[[], Union[bytes, bytearray]], bytes, bytearray]
106+
] = None,
107+
) -> PrivateKey:
108+
"""Creates a PrivateKey object from a byte buffer.
109+
110+
This byte buffer may be either PEM-encoded or DER-encoded. If the
111+
buffer is PEM encoded it *must* begin with the standard PEM
112+
preamble (a series of dashes followed by the ASCII bytes "BEGIN",
113+
the key type, and another series of dashes). In the absence of
114+
that preamble, the implementation may assume that the certificate
115+
is DER-encoded instead.
116+
117+
The key may additionally be encrypted. If it is, the ``password``
118+
argument can be used to decrypt the key. The ``password`` argument
119+
may be a function to call to get the password for decrypting the
120+
private key. It will only be called if the private key is encrypted
121+
and a password is necessary. It will be called with no arguments,
122+
and it should return either bytes or bytearray containing the
123+
password. Alternatively a bytes, or bytearray value may be supplied
124+
directly as the password argument. It will be ignored if the
125+
private key is not encrypted and no password is needed.
126+
"""
127+
128+
@classmethod
129+
def from_file(
130+
cls,
131+
path: _Path,
132+
password: Optional[
133+
Union[Callable[[], Union[bytes, bytearray]], bytes, bytearray]
134+
] = None,
135+
) -> PrivateKey:
136+
"""Creates a PrivateKey object from a file on disk.
137+
138+
This method may be a convenience method that wraps ``open`` and
139+
``from_buffer``, but some TLS implementations may be able to
140+
provide more-secure or faster methods of loading certificates that
141+
do not involve Python code.
142+
143+
The ``password`` parameter behaves exactly as the equivalent
144+
parameter on ``from_buffer``.
145+
"""
146+
147+
93148
CipherSuite = object
94149
DEFAULT_CIPHER_LIST = ()
95150

src/mbedtls/pk.pyi

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from __future__ import annotations
55

66
import enum
7+
import os
78
import sys
89
from typing import (
10+
Callable,
911
NamedTuple,
1012
NoReturn,
1113
Optional,
@@ -21,6 +23,12 @@ if sys.version_info < (3, 8):
2123
else:
2224
from typing import Final, Literal
2325

26+
if sys.version_info < (3, 9):
27+
_PathLike = os.PathLike
28+
else:
29+
_PathLike = os.PathLike[str]
30+
31+
_Path = Union[_PathLike, str]
2432
CIPHER_NAME: Final[Sequence[bytes]] = ...
2533
_DER = bytes
2634
_PEM = str
@@ -72,7 +80,21 @@ class CipherBase:
7280
def __hash__(self) -> int: ...
7381
def __eq__(self, other: object) -> bool: ...
7482
@classmethod
75-
def from_buffer(cls: Type[_TCipherBase], key: bytes) -> _TCipherBase: ...
83+
def from_buffer(
84+
cls: Type[_TCipherBase],
85+
buffer: bytes,
86+
password: Optional[
87+
Union[Callable[[], Union[bytes, bytearray]], bytes, bytearray]
88+
] = None,
89+
) -> _TCipherBase: ...
90+
@classmethod
91+
def from_file(
92+
cls: Type[_TCipherBase],
93+
path: _Path,
94+
password: Optional[
95+
Union[Callable[[], Union[bytes, bytearray]], bytes, bytearray]
96+
] = None,
97+
) -> _TCipherBase: ...
7698
@classmethod
7799
def from_DER(cls: Type[_TCipherBase], key: bytes) -> _TCipherBase: ...
78100
@classmethod

src/mbedtls/pk.pyx

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ cimport mbedtls.pk as _pk
2626
import enum
2727
from collections import namedtuple
2828
from functools import partial
29+
from pathlib import Path
2930

3031
import mbedtls._random as _rnd
3132
import mbedtls.exceptions as _exc
@@ -220,7 +221,11 @@ cdef class CipherBase:
220221
return other.export_public_key() == self.export_public_key()
221222

222223
@classmethod
223-
def from_buffer(cls, const unsigned char[:] key not None):
224+
def from_buffer(
225+
cls,
226+
const unsigned char[:] key not None,
227+
password=None,
228+
):
224229
"""Import a key (public or private half).
225230
226231
The public half is generated upon importing a private key.
@@ -231,7 +236,13 @@ cdef class CipherBase:
231236
password-protected private keys.
232237
233238
"""
234-
raise NotImplementedError
239+
if callable(password):
240+
return cls(key=key, password=password())
241+
return cls(key=key, password=password)
242+
243+
@classmethod
244+
def from_file(cls, path, password=None):
245+
return cls.from_buffer(Path(path).read_bytes(), password)
235246

236247
@classmethod
237248
def from_DER(cls, const unsigned char[:] key not None):
@@ -531,20 +542,6 @@ cdef class RSA(CipherBase):
531542
const unsigned char[:] password=None):
532543
super().__init__(b"RSA", key, password)
533544

534-
@classmethod
535-
def from_buffer(cls, const unsigned char[:] key):
536-
"""Import a key (public or private half).
537-
538-
The public half is generated upon importing a private key.
539-
540-
Arguments:
541-
key (bytes): The key in PEM or DER format.
542-
password (bytes, optional): The password for
543-
password-protected private keys.
544-
545-
"""
546-
return cls(key)
547-
548545
def _has_private(self):
549546
"""Return `True` if the key contains a valid private half."""
550547
return _pk.mbedtls_rsa_check_privkey(
@@ -667,20 +664,6 @@ cdef class ECC(CipherBase):
667664
def curve(self):
668665
return self._curve
669666

670-
@classmethod
671-
def from_buffer(cls, const unsigned char[:] key not None):
672-
"""Import a key (public or private half).
673-
674-
The public half is generated upon importing a private key.
675-
676-
Arguments:
677-
key (bytes): The key in PEM or DER format.
678-
password (bytes, optional): The password for
679-
password-protected private keys.
680-
681-
"""
682-
return cls(None, key)
683-
684667
def _has_private(self):
685668
"""Return `True` if the key contains a valid private half."""
686669
cdef const mbedtls_ecp_keypair* ecp = _pk.mbedtls_pk_ec(self._ctx)

src/mbedtls/tls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from ._tlsi import DTLSConfiguration as DTLSConfiguration
2828
from ._tlsi import DTLSVersion as DTLSVersion
2929
from ._tlsi import NextProtocol as NextProtocol
30+
from ._tlsi import PrivateKey as PrivateKey
3031
from ._tlsi import TLSConfiguration as TLSConfiguration
3132
from ._tlsi import TLSVersion as TLSVersion
3233

@@ -47,6 +48,7 @@
4748
"DTLSVersion",
4849
"HelloVerifyRequest",
4950
"NextProtocol",
51+
"PrivateKey",
5052
"Purpose",
5153
"RaggedEOF",
5254
"ServerContext",

0 commit comments

Comments
 (0)