|
1 | 1 | """Key interface and the default implementations"""
|
2 | 2 | import logging
|
3 | 3 | from abc import ABCMeta, abstractmethod
|
4 |
| -from typing import Any, Dict, Optional, Tuple, Type |
| 4 | +from typing import Any, Dict, Optional, Tuple, Type, cast |
5 | 5 |
|
6 |
| -import securesystemslib.keys as sslib_keys |
7 | 6 | from securesystemslib import exceptions
|
| 7 | +from securesystemslib._vendor.ed25519.ed25519 import ( |
| 8 | + SignatureMismatch, |
| 9 | + checkvalid, |
| 10 | +) |
8 | 11 | from securesystemslib.signer._signature import Signature
|
9 | 12 |
|
| 13 | +CRYPTO_IMPORT_ERROR = None |
| 14 | +try: |
| 15 | + from cryptography.exceptions import InvalidSignature |
| 16 | + from cryptography.hazmat.primitives.asymmetric.ec import ( |
| 17 | + ECDSA, |
| 18 | + EllipticCurvePublicKey, |
| 19 | + ) |
| 20 | + from cryptography.hazmat.primitives.asymmetric.ed25519 import ( |
| 21 | + Ed25519PublicKey, |
| 22 | + ) |
| 23 | + from cryptography.hazmat.primitives.asymmetric.padding import ( |
| 24 | + MGF1, |
| 25 | + PSS, |
| 26 | + PKCS1v15, |
| 27 | + ) |
| 28 | + from cryptography.hazmat.primitives.asymmetric.rsa import ( |
| 29 | + AsymmetricPadding, |
| 30 | + RSAPublicKey, |
| 31 | + ) |
| 32 | + from cryptography.hazmat.primitives.asymmetric.types import PublicKeyTypes |
| 33 | + from cryptography.hazmat.primitives.hashes import ( |
| 34 | + SHA224, |
| 35 | + SHA256, |
| 36 | + SHA384, |
| 37 | + SHA512, |
| 38 | + HashAlgorithm, |
| 39 | + ) |
| 40 | + from cryptography.hazmat.primitives.serialization import load_pem_public_key |
| 41 | +except ImportError: |
| 42 | + CRYPTO_IMPORT_ERROR = "'pyca/cryptography' library required" |
| 43 | + |
| 44 | + |
10 | 45 | logger = logging.getLogger(__name__)
|
11 | 46 |
|
12 | 47 | # NOTE Key dispatch table is defined here so it's usable by Key,
|
@@ -180,22 +215,80 @@ def from_dict(cls, keyid: str, key_dict: Dict[str, Any]) -> "SSlibKey":
|
180 | 215 | def to_dict(self) -> Dict[str, Any]:
|
181 | 216 | return self._to_dict()
|
182 | 217 |
|
| 218 | + def _from_pem(self) -> "PublicKeyTypes": |
| 219 | + """Helper to load public key instance from PEM-formatted keyval.""" |
| 220 | + public_bytes = self.keyval["public"].encode("utf-8") |
| 221 | + return load_pem_public_key(public_bytes) |
| 222 | + |
| 223 | + @staticmethod |
| 224 | + def _hash_algo(name) -> Type["HashAlgorithm"]: |
| 225 | + """Helper to return hash algorithm class for name.""" |
| 226 | + algos = { |
| 227 | + "sha224": SHA224, |
| 228 | + "sha256": SHA256, |
| 229 | + "sha384": SHA384, |
| 230 | + "sha512": SHA512, |
| 231 | + } |
| 232 | + return algos[name] |
| 233 | + |
183 | 234 | def verify_signature(self, signature: Signature, data: bytes) -> None:
|
184 | 235 | try:
|
185 |
| - if not sslib_keys.verify_signature( |
186 |
| - self.to_securesystemslib_key(), |
187 |
| - signature.to_dict(), |
188 |
| - data, |
| 236 | + sig = bytes.fromhex(signature.signature) |
| 237 | + |
| 238 | + if CRYPTO_IMPORT_ERROR: |
| 239 | + if self.scheme == "ed25519": |
| 240 | + # Verify using vendored ed25519 implementation |
| 241 | + pub = bytes.fromhex(self.keyval["public"]) |
| 242 | + checkvalid(sig, data, pub) |
| 243 | + return |
| 244 | + |
| 245 | + raise exceptions.UnsupportedLibraryError(CRYPTO_IMPORT_ERROR) |
| 246 | + |
| 247 | + key: PublicKeyTypes |
| 248 | + if self.scheme in [ |
| 249 | + "rsassa-pss-sha224", |
| 250 | + "rsassa-pss-sha256", |
| 251 | + "rsassa-pss-sha384", |
| 252 | + "rsassa-pss-sha512", |
| 253 | + "rsa-pkcs1v15-sha224", |
| 254 | + "rsa-pkcs1v15-sha256", |
| 255 | + "rsa-pkcs1v15-sha384", |
| 256 | + "rsa-pkcs1v15-sha512", |
| 257 | + ]: |
| 258 | + key = cast(RSAPublicKey, self._from_pem()) |
| 259 | + padding_name, algo_name = self.scheme.split("-")[1:] |
| 260 | + algo = self._hash_algo(algo_name)() |
| 261 | + padding: AsymmetricPadding |
| 262 | + if padding_name == "pss": |
| 263 | + padding = PSS(mgf=MGF1(algo), salt_length=PSS.AUTO) |
| 264 | + else: |
| 265 | + padding = PKCS1v15() |
| 266 | + key.verify(sig, data, padding, algo) |
| 267 | + |
| 268 | + elif self.scheme in ["ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]: |
| 269 | + key = cast(EllipticCurvePublicKey, self._from_pem()) |
| 270 | + algo_name = f"sha{self.scheme[-3:]}" |
| 271 | + algo = self._hash_algo(algo_name)() |
| 272 | + key.verify(sig, data, ECDSA(algo)) |
| 273 | + |
| 274 | + elif self.scheme in ["ed25519"]: |
| 275 | + public_bytes = bytes.fromhex(self.keyval["public"]) |
| 276 | + key = Ed25519PublicKey.from_public_bytes(public_bytes) |
| 277 | + key.verify(sig, data) |
| 278 | + |
| 279 | + else: |
| 280 | + raise ValueError(f"unknown scheme '{self.scheme}'") |
| 281 | + |
| 282 | + # Workaround for 'except (SignatureMismatch, InvalidSignature)' to |
| 283 | + # conditionally evaluate the optional 'InvalidSignature': |
| 284 | + except Exception as e: |
| 285 | + if isinstance(e, SignatureMismatch) or ( |
| 286 | + not CRYPTO_IMPORT_ERROR and isinstance(e, InvalidSignature) |
189 | 287 | ):
|
190 | 288 | raise exceptions.UnverifiedSignatureError(
|
191 | 289 | f"Failed to verify signature by {self.keyid}"
|
192 |
| - ) |
193 |
| - except ( |
194 |
| - exceptions.CryptoError, |
195 |
| - exceptions.FormatError, |
196 |
| - exceptions.UnsupportedAlgorithmError, |
197 |
| - exceptions.UnsupportedLibraryError, |
198 |
| - ) as e: |
| 290 | + ) from e |
| 291 | + |
199 | 292 | logger.info("Key %s failed to verify sig: %s", self.keyid, str(e))
|
200 | 293 | raise exceptions.VerificationError(
|
201 | 294 | f"Unknown failure to verify signature by {self.keyid}"
|
|
0 commit comments