paramiko-cloud 1.3.1__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/PKG-INFO +5 -5
  2. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/README.md +1 -1
  3. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/aws/keys.py +20 -12
  4. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/azure/keys.py +55 -31
  5. paramiko_cloud-2.0.0/paramiko_cloud/base.py +179 -0
  6. paramiko_cloud-2.0.0/paramiko_cloud/dummy/keys.py +47 -0
  7. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/gcp/keys.py +21 -13
  8. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/grpc_server.py +6 -7
  9. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/pki.py +92 -54
  10. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/protobuf/csr_pb2.py +3 -3
  11. paramiko_cloud-2.0.0/paramiko_cloud/protobuf/csr_pb2.pyi +72 -0
  12. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/protobuf/rpc_pb2.py +3 -3
  13. paramiko_cloud-2.0.0/paramiko_cloud/protobuf/rpc_pb2.pyi +51 -0
  14. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/protobuf/rpc_pb2_grpc.py +4 -4
  15. paramiko_cloud-2.0.0/paramiko_cloud/py.typed +1 -0
  16. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud.egg-info/PKG-INFO +5 -5
  17. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud.egg-info/SOURCES.txt +6 -8
  18. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud.egg-info/requires.txt +2 -2
  19. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/pyproject.toml +21 -14
  20. {paramiko_cloud-1.3.1/paramiko_cloud → paramiko_cloud-2.0.0/tests}/test_grpc_server.py +10 -10
  21. {paramiko_cloud-1.3.1/paramiko_cloud → paramiko_cloud-2.0.0/tests}/test_pki.py +24 -8
  22. paramiko_cloud-1.3.1/paramiko_cloud/aws/test_keys.py +0 -95
  23. paramiko_cloud-1.3.1/paramiko_cloud/azure/test_keys.py +0 -129
  24. paramiko_cloud-1.3.1/paramiko_cloud/base.py +0 -117
  25. paramiko_cloud-1.3.1/paramiko_cloud/dummy/keys.py +0 -36
  26. paramiko_cloud-1.3.1/paramiko_cloud/dummy/test_keys.py +0 -47
  27. paramiko_cloud-1.3.1/paramiko_cloud/gcp/test_keys.py +0 -95
  28. paramiko_cloud-1.3.1/paramiko_cloud/test_helpers.py +0 -87
  29. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/__init__.py +0 -0
  30. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/aws/__init__.py +0 -0
  31. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/azure/__init__.py +0 -0
  32. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/dummy/__init__.py +0 -0
  33. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/gcp/__init__.py +0 -0
  34. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud/protobuf/__init__.py +0 -0
  35. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud.egg-info/dependency_links.txt +0 -0
  36. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/paramiko_cloud.egg-info/top_level.txt +0 -0
  37. {paramiko_cloud-1.3.1 → paramiko_cloud-2.0.0}/setup.cfg +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramiko-cloud
3
- Version: 1.3.1
3
+ Version: 2.0.0
4
4
  Summary: Use cloud-managed keys to sign SSH certificates
5
5
  Author-email: Jason Rigby <hello@jasonrig.by>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/jasonrig/paramiko-cloud/
8
- Requires-Python: >=3.9
8
+ Requires-Python: >=3.10
9
9
  Description-Content-Type: text/markdown
10
- Requires-Dist: paramiko
11
- Requires-Dist: cryptography
10
+ Requires-Dist: paramiko>=5.0.0
11
+ Requires-Dist: cryptography>=45.0.1
12
12
  Requires-Dist: protobuf
13
13
  Requires-Dist: grpcio-tools
14
14
  Provides-Extra: aws
@@ -55,5 +55,5 @@ uv run pytest --cov=./ --cov-report=xml
55
55
  uv run python scripts/build_proto.py
56
56
  uv run ruff check .
57
57
  uv run ruff format . --check
58
- uv run mypy paramiko_cloud
58
+ uv run mypy paramiko_cloud tests
59
59
  ```
@@ -29,5 +29,5 @@ uv run pytest --cov=./ --cov-report=xml
29
29
  uv run python scripts/build_proto.py
30
30
  uv run ruff check .
31
31
  uv run ruff format . --check
32
- uv run mypy paramiko_cloud
32
+ uv run mypy paramiko_cloud tests
33
33
  ```
@@ -1,12 +1,12 @@
1
- from typing import Any, Set, cast
1
+ from typing import Any
2
2
 
3
3
  import boto3
4
4
  from cryptography.hazmat.primitives.asymmetric.ec import (
5
- ECDSA,
6
- EllipticCurve,
7
5
  EllipticCurvePublicKey,
6
+ EllipticCurveSignatureAlgorithm,
8
7
  )
9
8
  from cryptography.hazmat.primitives.serialization import load_der_public_key
9
+ from cryptography.utils import Buffer
10
10
 
11
11
  from paramiko_cloud.base import BaseKeyECDSA, CloudSigningKey
12
12
 
@@ -17,7 +17,11 @@ class _AWSSigningKey(CloudSigningKey):
17
17
  """
18
18
 
19
19
  def __init__(
20
- self, client: Any, key_id: str, signing_algo: str, curve: EllipticCurve
20
+ self,
21
+ client: Any,
22
+ key_id: str,
23
+ signing_algo: str,
24
+ public_key: EllipticCurvePublicKey,
21
25
  ) -> None:
22
26
  """
23
27
  Constructor
@@ -26,15 +30,19 @@ class _AWSSigningKey(CloudSigningKey):
26
30
  client: the AWS KMS client from boto3
27
31
  key_id: the AWS KMS key id
28
32
  signing_algo: the signing algorithm to use
29
- curve: the elliptic curve used for this key
33
+ public_key: the public key corresponding to the KMS key
30
34
  """
31
35
 
32
- super().__init__(curve)
36
+ super().__init__(public_key)
33
37
  self.client = client
34
38
  self.key_id = key_id
35
39
  self.signing_algo = signing_algo
36
40
 
37
- def sign(self, data: bytes, signature_algorithm: ECDSA) -> bytes:
41
+ def sign(
42
+ self,
43
+ data: Buffer,
44
+ signature_algorithm: EllipticCurveSignatureAlgorithm,
45
+ ) -> bytes:
38
46
  """
39
47
  Calculate the signature for the given data
40
48
 
@@ -93,9 +101,9 @@ class ECDSAKey(BaseKeyECDSA):
93
101
  ), "No supported key/algorithm pair found."
94
102
  assert pub_key["KeyUsage"] == "SIGN_VERIFY", "Key does not support signing."
95
103
 
96
- verifying_key = cast(
97
- EllipticCurvePublicKey, load_der_public_key(pub_key["PublicKey"])
98
- )
104
+ verifying_key = load_der_public_key(pub_key["PublicKey"])
105
+ if not isinstance(verifying_key, EllipticCurvePublicKey):
106
+ raise TypeError("AWS KMS public key is not an elliptic curve key")
99
107
 
100
108
  super().__init__(
101
109
  (
@@ -103,13 +111,13 @@ class ECDSAKey(BaseKeyECDSA):
103
111
  client,
104
112
  key_id,
105
113
  self._choose_signing_algo(set(pub_key["SigningAlgorithms"])),
106
- verifying_key.curve,
114
+ verifying_key,
107
115
  ),
108
116
  verifying_key,
109
117
  )
110
118
  )
111
119
 
112
- def _choose_signing_algo(self, supported_algos: Set[str]) -> str:
120
+ def _choose_signing_algo(self, supported_algos: set[str]) -> str:
113
121
  """
114
122
  Selects the appropriate signing algorithm based on the supported and offered signing algorithms
115
123
 
@@ -1,33 +1,36 @@
1
- from typing import Union, cast
1
+ from collections.abc import Callable
2
+ from typing import Protocol, runtime_checkable
2
3
 
3
4
  from azure.identity import (
4
- DefaultAzureCredential,
5
+ AzureCliCredential,
5
6
  AzurePowerShellCredential,
6
- InteractiveBrowserCredential,
7
7
  ChainedTokenCredential,
8
+ DefaultAzureCredential,
8
9
  EnvironmentCredential,
10
+ InteractiveBrowserCredential,
9
11
  ManagedIdentityCredential,
10
12
  SharedTokenCacheCredential,
11
- AzureCliCredential,
12
13
  VisualStudioCodeCredential,
13
14
  )
14
15
  from azure.keyvault.keys import KeyClient
15
16
  from azure.keyvault.keys.crypto import CryptographyClient, SignatureAlgorithm
16
17
  from cryptography.hazmat.primitives.asymmetric.ec import (
17
- ECDSA,
18
- EllipticCurvePublicNumbers,
18
+ SECP192R1,
19
+ SECP224R1,
19
20
  SECP256R1,
20
21
  SECP384R1,
21
22
  SECP521R1,
22
- SECP224R1,
23
- SECP192R1,
24
23
  EllipticCurve,
24
+ EllipticCurvePublicKey,
25
+ EllipticCurvePublicNumbers,
26
+ EllipticCurveSignatureAlgorithm,
25
27
  )
26
28
  from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
29
+ from cryptography.utils import Buffer
27
30
 
28
31
  from paramiko_cloud.base import BaseKeyECDSA, CloudSigningKey
29
32
 
30
- _CURVES = {
33
+ _CURVES: dict[str, Callable[[], EllipticCurve]] = {
31
34
  "P-256": SECP256R1,
32
35
  "P-384": SECP384R1,
33
36
  "P-521": SECP521R1,
@@ -36,17 +39,30 @@ _CURVES = {
36
39
  }
37
40
 
38
41
 
42
+ @runtime_checkable
43
+ class _ECJsonWebKey(Protocol):
44
+ """Elliptic-curve fields dynamically provided by Azure's JsonWebKey."""
45
+
46
+ crv: str
47
+ x: bytes
48
+ y: bytes
49
+
50
+
39
51
  class _AzureSigningKey(CloudSigningKey):
40
52
  """
41
53
  Provides signing operations to Paramiko for the Azure Key Vault-backed key
42
54
 
43
55
  Args:
44
56
  crypto_client: the Key Vault Cryptography Client authenticated to access the selected key
45
- curve: the elliptic curve used for this key
57
+ public_key: the public key corresponding to the Key Vault key
46
58
  """
47
59
 
48
- def __init__(self, crypto_client: CryptographyClient, curve: EllipticCurve):
49
- super().__init__(curve)
60
+ def __init__(
61
+ self,
62
+ crypto_client: CryptographyClient,
63
+ public_key: EllipticCurvePublicKey,
64
+ ):
65
+ super().__init__(public_key)
50
66
  self.crypto_client = crypto_client
51
67
 
52
68
  def _signaure_algorithm(self) -> SignatureAlgorithm:
@@ -66,7 +82,11 @@ class _AzureSigningKey(CloudSigningKey):
66
82
  else:
67
83
  raise NotImplementedError("Unsupported EC signature algorithm")
68
84
 
69
- def sign(self, data: bytes, signature_algorithm: ECDSA) -> bytes:
85
+ def sign(
86
+ self,
87
+ data: Buffer,
88
+ signature_algorithm: EllipticCurveSignatureAlgorithm,
89
+ ) -> bytes:
70
90
  """
71
91
  Calculate the signature for the given data
72
92
 
@@ -103,44 +123,48 @@ class ECDSAKey(BaseKeyECDSA):
103
123
 
104
124
  def __init__(
105
125
  self,
106
- credential: Union[
107
- DefaultAzureCredential,
108
- AzurePowerShellCredential,
109
- InteractiveBrowserCredential,
110
- ChainedTokenCredential,
111
- EnvironmentCredential,
112
- ManagedIdentityCredential,
113
- SharedTokenCacheCredential,
114
- AzureCliCredential,
115
- VisualStudioCodeCredential,
116
- ],
126
+ credential: DefaultAzureCredential
127
+ | AzurePowerShellCredential
128
+ | InteractiveBrowserCredential
129
+ | ChainedTokenCredential
130
+ | EnvironmentCredential
131
+ | ManagedIdentityCredential
132
+ | SharedTokenCacheCredential
133
+ | AzureCliCredential
134
+ | VisualStudioCodeCredential,
117
135
  vault_url: str,
118
136
  key_name: str,
119
137
  ):
120
138
  vault_client = KeyClient(vault_url, credential=credential)
121
139
  pub_key = vault_client.get_key(key_name)
122
140
  assert pub_key.key_type in self._ALLOWED_ALGOS, (
123
- "Unsupported signing algorithm: {}".format(pub_key.key_type)
141
+ f"Unsupported signing algorithm: {pub_key.key_type}"
124
142
  )
125
143
 
126
144
  jwk = pub_key.key
127
145
  assert jwk is not None, "Missing key material from Azure Key Vault."
128
- curve_name = cast(str, getattr(jwk, "crv"))
146
+ if not isinstance(jwk, _ECJsonWebKey):
147
+ raise TypeError("Azure Key Vault returned incomplete EC key material")
148
+ if not isinstance(jwk.crv, str):
149
+ raise TypeError("Azure Key Vault returned an invalid EC curve name")
150
+ if not isinstance(jwk.x, bytes) or not isinstance(jwk.y, bytes):
151
+ raise TypeError("Azure Key Vault returned invalid EC coordinates")
152
+ curve_name = jwk.crv
129
153
 
130
- assert curve_name in _CURVES, "Unsupported curve: {}".format(curve_name)
154
+ assert curve_name in _CURVES, f"Unsupported curve: {curve_name}"
131
155
 
132
- curve = _CURVES[curve_name]() # type: ignore[abstract]
156
+ curve = _CURVES[curve_name]()
133
157
 
134
158
  verifying_key = EllipticCurvePublicNumbers(
135
- int.from_bytes(cast(bytes, getattr(jwk, "x")), "big"),
136
- int.from_bytes(cast(bytes, getattr(jwk, "y")), "big"),
159
+ int.from_bytes(jwk.x, "big"),
160
+ int.from_bytes(jwk.y, "big"),
137
161
  curve,
138
162
  ).public_key()
139
163
 
140
164
  super().__init__(
141
165
  (
142
166
  _AzureSigningKey(
143
- CryptographyClient(pub_key, credential), verifying_key.curve
167
+ CryptographyClient(pub_key, credential), verifying_key
144
168
  ),
145
169
  verifying_key,
146
170
  )
@@ -0,0 +1,179 @@
1
+ import base64
2
+ import hashlib
3
+ import os
4
+ from abc import abstractmethod
5
+ from collections.abc import Callable
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Protocol
8
+
9
+ from cryptography.hazmat.primitives.asymmetric.ec import (
10
+ ECDH,
11
+ ECDSA,
12
+ EllipticCurve,
13
+ EllipticCurvePrivateKey,
14
+ EllipticCurvePrivateNumbers,
15
+ EllipticCurvePublicKey,
16
+ EllipticCurveSignatureAlgorithm,
17
+ )
18
+ from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
19
+ from cryptography.hazmat.primitives.serialization import (
20
+ Encoding,
21
+ KeySerializationEncryption,
22
+ PrivateFormat,
23
+ )
24
+ from cryptography.utils import Buffer
25
+ from paramiko import ECDSAKey, Message
26
+ from paramiko.pkey import PEM, FileFormat
27
+
28
+ from paramiko_cloud.pki import CertificateSigningKeyMixin
29
+
30
+
31
+ class _SupportsWrite(Protocol):
32
+ def write(self, data: str) -> object: ...
33
+
34
+
35
+ class CloudSigningKey(EllipticCurvePrivateKey):
36
+ """
37
+ Base class for all cloud KMS-backed signing keys
38
+ """
39
+
40
+ def __init__(self, public_key: EllipticCurvePublicKey):
41
+ """
42
+ Constructor
43
+
44
+ Args:
45
+ public_key: the public key corresponding to the cloud-managed key
46
+ """
47
+
48
+ self._public_key = public_key
49
+
50
+ @property
51
+ def curve(self) -> EllipticCurve:
52
+ return self._public_key.curve
53
+
54
+ @property
55
+ def key_size(self) -> int:
56
+ return self.curve.key_size
57
+
58
+ def public_key(self) -> EllipticCurvePublicKey:
59
+ return self._public_key
60
+
61
+ def __copy__(self) -> "CloudSigningKey":
62
+ return self
63
+
64
+ def __deepcopy__(self, memo: dict[object, object]) -> "CloudSigningKey":
65
+ return self
66
+
67
+ def exchange(
68
+ self, algorithm: ECDH, peer_public_key: EllipticCurvePublicKey
69
+ ) -> bytes:
70
+ raise RuntimeError("Key exchange is unavailable for cloud-managed keys")
71
+
72
+ def private_numbers(self) -> EllipticCurvePrivateNumbers:
73
+ raise RuntimeError("Private key material is managed externally")
74
+
75
+ def private_bytes(
76
+ self,
77
+ encoding: Encoding,
78
+ format: PrivateFormat,
79
+ encryption_algorithm: KeySerializationEncryption,
80
+ ) -> bytes:
81
+ raise RuntimeError("Private key material is managed externally")
82
+
83
+ @staticmethod
84
+ def digest(
85
+ data: Buffer,
86
+ signature_algorithm: EllipticCurveSignatureAlgorithm,
87
+ ) -> bytes:
88
+ """
89
+ Calculates the hash of the given data according to the given elliptic curve key
90
+
91
+ Args:
92
+ data: the data for which to calculate the hash
93
+ signature_algorithm: the elliptic curve signature algorithm
94
+
95
+ Returns:
96
+ The hash of the data
97
+ """
98
+ if not isinstance(signature_algorithm, ECDSA):
99
+ raise TypeError("Cloud-managed keys require an ECDSA signature algorithm")
100
+ algorithm = signature_algorithm.algorithm
101
+ if isinstance(algorithm, Prehashed):
102
+ algorithm = algorithm._algorithm
103
+ return getattr(hashlib, algorithm.name)(data).digest()
104
+
105
+ @abstractmethod
106
+ def sign(
107
+ self,
108
+ data: Buffer,
109
+ signature_algorithm: EllipticCurveSignatureAlgorithm,
110
+ ) -> bytes:
111
+ """
112
+ Calculate the signature for the given data
113
+
114
+ Args:
115
+ data: data for which to calculate a signature
116
+ signature_algorithm: the curve used for this signature
117
+
118
+ Returns:
119
+ The DER formatted signature
120
+ """
121
+
122
+ raise NotImplementedError()
123
+
124
+
125
+ class BaseKeyECDSA(ECDSAKey, CertificateSigningKeyMixin):
126
+ """
127
+ Base class for all cloud-backed ECDSA keys
128
+ """
129
+
130
+ def __init__(self, vals: tuple[CloudSigningKey, EllipticCurvePublicKey]):
131
+ """
132
+ Constructor
133
+
134
+ Args:
135
+ vals: tuple of signing key and verifying key
136
+ """
137
+ super().__init__(vals=vals)
138
+
139
+ def write_private_key_file(
140
+ self,
141
+ filename: str | bytes | os.PathLike[str] | os.PathLike[bytes],
142
+ password: str | None = None,
143
+ file_format: FileFormat = PEM,
144
+ ) -> None:
145
+ raise RuntimeError("Private key managed externally, cannot export")
146
+
147
+ def write_private_key(
148
+ self,
149
+ file_obj: _SupportsWrite,
150
+ password: str | None = None,
151
+ file_format: FileFormat = PEM,
152
+ ) -> None:
153
+ raise RuntimeError("Private key managed externally, cannot export")
154
+
155
+ @classmethod
156
+ def generate(
157
+ cls,
158
+ curve: EllipticCurve | None = None,
159
+ progress_func: Callable[..., Any] | None = None,
160
+ bits: int | None = None,
161
+ ) -> "BaseKeyECDSA":
162
+ raise RuntimeError(
163
+ "Create new signing keys using the KMS client for your cloud provider"
164
+ )
165
+
166
+ def pubkey_string(self, comment: str | None = None) -> str:
167
+ """
168
+ Render a string suitable for OpenSSH authorized_keys files
169
+
170
+ Args:
171
+ comment: an optional comment, defaulting to the current date and time in ISO format
172
+
173
+ Returns:
174
+ The public key string
175
+ """
176
+ key_bytes = self.asbytes()
177
+ m = Message(self.asbytes())
178
+ key_type = m.get_text()
179
+ return f"{key_type} {base64.standard_b64encode(key_bytes).decode()} {comment or datetime.now(timezone.utc).isoformat()}"
@@ -0,0 +1,47 @@
1
+ from cryptography.hazmat.primitives.asymmetric.ec import (
2
+ EllipticCurvePrivateKey,
3
+ EllipticCurvePublicKey,
4
+ EllipticCurveSignatureAlgorithm,
5
+ )
6
+ from cryptography.hazmat.primitives.serialization import load_pem_private_key
7
+ from cryptography.utils import Buffer
8
+
9
+ from paramiko_cloud.base import BaseKeyECDSA, CloudSigningKey
10
+
11
+
12
+ class _LocalSigningKey(CloudSigningKey):
13
+ """
14
+ A dummy signing key
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ key: EllipticCurvePrivateKey,
20
+ public_key: EllipticCurvePublicKey,
21
+ ):
22
+ super().__init__(public_key)
23
+ self.key = key
24
+
25
+ def sign(
26
+ self,
27
+ data: Buffer,
28
+ signature_algorithm: EllipticCurveSignatureAlgorithm,
29
+ ) -> bytes:
30
+ return self.key.sign(data, signature_algorithm)
31
+
32
+
33
+ class ECDSAKey(BaseKeyECDSA):
34
+ """
35
+ A dummy key that demonstrates the abstraction, but just loads they key from file.
36
+
37
+ Args:
38
+ pem_private_key: A PEM-formatted private key
39
+ password: An optional password to decrypt the private key
40
+ """
41
+
42
+ def __init__(self, pem_private_key: bytes, password: bytes | None = None):
43
+ private_key = load_pem_private_key(pem_private_key, password)
44
+ if not isinstance(private_key, EllipticCurvePrivateKey):
45
+ raise TypeError("PEM private key is not an elliptic curve key")
46
+ public_key = private_key.public_key()
47
+ super().__init__((_LocalSigningKey(private_key, public_key), public_key))
@@ -1,10 +1,12 @@
1
- from typing import cast
2
-
3
- from cryptography.hazmat.primitives.asymmetric.ec import ECDSA, EllipticCurve
1
+ from cryptography.hazmat.primitives.asymmetric.ec import (
2
+ ECDSA,
3
+ EllipticCurvePublicKey,
4
+ EllipticCurveSignatureAlgorithm,
5
+ )
4
6
  from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
5
7
  from cryptography.hazmat.primitives.hashes import HashAlgorithm
6
- from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
7
8
  from cryptography.hazmat.primitives.serialization import load_pem_public_key
9
+ from cryptography.utils import Buffer
8
10
  from google.cloud import kms
9
11
  from google.cloud.kms_v1 import Digest
10
12
 
@@ -17,20 +19,24 @@ class _GCPSigningKey(CloudSigningKey):
17
19
  Args:
18
20
  kms_client: a KMS client that can access the selected key
19
21
  key_name: the name of the key
20
- curve: the elliptic curve used for this key
22
+ public_key: the public key corresponding to the KMS key
21
23
  """
22
24
 
23
25
  def __init__(
24
26
  self,
25
27
  kms_client: kms.KeyManagementServiceClient,
26
28
  key_name: str,
27
- curve: EllipticCurve,
29
+ public_key: EllipticCurvePublicKey,
28
30
  ):
29
- super().__init__(curve)
31
+ super().__init__(public_key)
30
32
  self.client = kms_client
31
33
  self.key_name = key_name
32
34
 
33
- def sign(self, data: bytes, signature_algorithm: ECDSA) -> bytes:
35
+ def sign(
36
+ self,
37
+ data: Buffer,
38
+ signature_algorithm: EllipticCurveSignatureAlgorithm,
39
+ ) -> bytes:
34
40
  """
35
41
  Calculate the signature for the given data
36
42
 
@@ -41,6 +47,8 @@ class _GCPSigningKey(CloudSigningKey):
41
47
  Returns:
42
48
  The DER formatted signature
43
49
  """
50
+ if not isinstance(signature_algorithm, ECDSA):
51
+ raise TypeError("GCP KMS keys require an ECDSA signature algorithm")
44
52
  if isinstance(signature_algorithm.algorithm, Prehashed):
45
53
  algorithm: HashAlgorithm = signature_algorithm.algorithm._algorithm
46
54
  else:
@@ -74,14 +82,14 @@ class ECDSAKey(BaseKeyECDSA):
74
82
  def __init__(self, kms_client: kms.KeyManagementServiceClient, key_name: str):
75
83
  pub_key = kms_client.get_public_key(name=key_name)
76
84
  assert pub_key.algorithm.name in self._ALLOWED_ALGOS, (
77
- "Unsupported signing algorithm: {}".format(pub_key.algorithm.name)
78
- )
79
- verifying_key = cast(
80
- EllipticCurvePublicKey, load_pem_public_key(pub_key.pem.encode())
85
+ f"Unsupported signing algorithm: {pub_key.algorithm.name}"
81
86
  )
87
+ verifying_key = load_pem_public_key(pub_key.pem.encode())
88
+ if not isinstance(verifying_key, EllipticCurvePublicKey):
89
+ raise TypeError("GCP KMS public key is not an elliptic curve key")
82
90
  super().__init__(
83
91
  (
84
- _GCPSigningKey(kms_client, pub_key.name, verifying_key.curve),
92
+ _GCPSigningKey(kms_client, pub_key.name, verifying_key),
85
93
  verifying_key,
86
94
  )
87
95
  )
@@ -1,6 +1,5 @@
1
1
  from concurrent import futures
2
2
  from types import TracebackType
3
- from typing import Optional, Type
4
3
 
5
4
  import grpc
6
5
 
@@ -25,14 +24,14 @@ class GRPCServer:
25
24
  signer_servicer: SignerServicer,
26
25
  bind_addr: str = "[::]",
27
26
  port: int = 50051,
28
- server_credentials: Optional[grpc.ServerCredentials] = None,
27
+ server_credentials: grpc.ServerCredentials | None = None,
29
28
  max_workers: int = 10,
30
- shutdown_grace: Optional[int] = None,
29
+ shutdown_grace: int | None = None,
31
30
  ):
32
31
  self.shutdown_grace = shutdown_grace
33
32
  self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=max_workers))
34
33
  rpc_pb2_grpc.add_SignerServicer_to_server(signer_servicer, self.server)
35
- address = "{}:{}".format(bind_addr, port)
34
+ address = f"{bind_addr}:{port}"
36
35
  if server_credentials:
37
36
  self.server.add_secure_port(address, server_credentials)
38
37
  else:
@@ -44,8 +43,8 @@ class GRPCServer:
44
43
 
45
44
  def __exit__(
46
45
  self,
47
- exc_type: Optional[Type[BaseException]],
48
- exc_val: Optional[BaseException],
49
- exc_tb: Optional[TracebackType],
46
+ exc_type: type[BaseException] | None,
47
+ exc_val: BaseException | None,
48
+ exc_tb: TracebackType | None,
50
49
  ) -> None:
51
50
  self.server.stop(self.shutdown_grace)