Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/securesystemslib/signer/_key.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

225 statements  

1"""Key interface and the default implementations""" 

2 

3from __future__ import annotations 

4 

5import logging 

6import warnings 

7from abc import ABCMeta, abstractmethod 

8from typing import Any, cast 

9 

10from securesystemslib._internal.utils import make_hashable 

11from securesystemslib._vendor.ed25519.ed25519 import ( 

12 SignatureMismatch, 

13 checkvalid, 

14) 

15from securesystemslib.exceptions import ( 

16 UnsupportedLibraryError, 

17 UnverifiedSignatureError, 

18 VerificationError, 

19) 

20from securesystemslib.signer._constants import ( 

21 ECDSA_SHA2_NISTP256, 

22 ECDSA_SHA2_NISTP384, 

23 ECDSA_SHA2_NISTP521, 

24 ED25519, 

25 KEY_TYPE_ECDSA, 

26 KEY_TYPE_ED25519, 

27 KEY_TYPE_MLDSA, 

28 KEY_TYPE_RSA, 

29 MLDSA_44_1, 

30 MLDSA_65_1, 

31 MLDSA_87_1, 

32 RSA_PKCS1V15_SHA224, 

33 RSA_PKCS1V15_SHA256, 

34 RSA_PKCS1V15_SHA384, 

35 RSA_PKCS1V15_SHA512, 

36 RSASSA_PSS_SHA224, 

37 RSASSA_PSS_SHA256, 

38 RSASSA_PSS_SHA384, 

39 RSASSA_PSS_SHA512, 

40) 

41from securesystemslib.signer._signature import Signature 

42from securesystemslib.signer._utils import compute_default_keyid, get_mldsa_payload 

43 

44CRYPTO_IMPORT_ERROR = None 

45MLDSA_IMPORT_ERROR = None 

46try: 

47 from cryptography.exceptions import InvalidSignature 

48 from cryptography.hazmat.primitives.asymmetric.ec import ( 

49 ECDSA, 

50 SECP256R1, 

51 SECP384R1, 

52 SECP521R1, 

53 EllipticCurve, 

54 EllipticCurvePublicKey, 

55 ) 

56 from cryptography.hazmat.primitives.asymmetric.ed25519 import ( 

57 Ed25519PublicKey, 

58 ) 

59 from cryptography.hazmat.primitives.asymmetric.padding import ( 

60 MGF1, 

61 PSS, 

62 PKCS1v15, 

63 ) 

64 from cryptography.hazmat.primitives.asymmetric.rsa import ( 

65 AsymmetricPadding, 

66 RSAPublicKey, 

67 ) 

68 from cryptography.hazmat.primitives.asymmetric.types import PublicKeyTypes 

69 from cryptography.hazmat.primitives.hashes import ( 

70 SHA256, 

71 SHA384, 

72 SHA512, 

73 HashAlgorithm, 

74 ) 

75 from cryptography.hazmat.primitives.serialization import ( 

76 Encoding, 

77 PublicFormat, 

78 load_pem_public_key, 

79 ) 

80 

81 from securesystemslib.signer._crypto_utils import get_hash_algorithm 

82 

83except ImportError: 

84 CRYPTO_IMPORT_ERROR = "'pyca/cryptography' library required" 

85 

86try: 

87 from cryptography.hazmat.primitives.asymmetric.mldsa import ( 

88 MLDSA44PublicKey, 

89 MLDSA65PublicKey, 

90 MLDSA87PublicKey, 

91 ) 

92except ImportError: 

93 MLDSA_IMPORT_ERROR = "'cryptography>=48.0.0' required for ML-DSA support" 

94 MLDSA44PublicKey = None # type: ignore[assignment, misc] 

95 MLDSA65PublicKey = None # type: ignore[assignment, misc] 

96 MLDSA87PublicKey = None # type: ignore[assignment, misc] 

97 

98 

99logger = logging.getLogger(__name__) 

100 

101# NOTE Key dispatch table is defined here so it's usable by Key, 

102# but is populated in __init__.py (and can be appended by users). 

103KEY_FOR_TYPE_AND_SCHEME: dict[tuple[str, str], type] = {} 

104"""Key dispatch table for ``Key.from_dict()`` 

105 

106See ``securesystemslib.signer.KEY_FOR_TYPE_AND_SCHEME`` for default key types 

107and schemes, and how to register custom implementations. 

108""" 

109 

110 

111class Key(metaclass=ABCMeta): 

112 """Abstract class representing the public portion of a key. 

113 

114 *All parameters named below are not just constructor arguments but also 

115 instance attributes.* 

116 

117 Args: 

118 keyid: Key identifier that is unique within the metadata it is used in. 

119 Keyid is not verified to be the hash of a specific representation 

120 of the key. 

121 keytype: Key type, e.g. ``KEY_TYPE_RSA``, ``KEY_TYPE_ED25519`` or 

122 ``KEY_TYPE_ECDSA``. 

123 scheme: Signature scheme. For example: 

124 ``RSASSA_PSS_SHA256``, ``ED25519``, and ``ECDSA_SHA2_NISTP256``. 

125 keyval: Opaque key content 

126 unrecognized_fields: Dictionary of all attributes that are not managed 

127 by Securesystemslib 

128 

129 Raises: 

130 TypeError: Invalid type for an argument. 

131 """ 

132 

133 def __init__( 

134 self, 

135 keyid: str, 

136 keytype: str, 

137 scheme: str, 

138 keyval: dict[str, Any], 

139 unrecognized_fields: dict[str, Any] | None = None, 

140 ): 

141 if not all( 

142 isinstance(at, str) for at in [keyid, keytype, scheme] 

143 ) or not isinstance(keyval, dict): 

144 raise TypeError("Unexpected Key attributes types!") 

145 self.keyid = keyid 

146 self.keytype = keytype 

147 self.scheme = scheme 

148 self.keyval = keyval 

149 

150 if unrecognized_fields is None: 

151 unrecognized_fields = {} 

152 

153 self.unrecognized_fields = unrecognized_fields 

154 

155 def __eq__(self, other: Any) -> bool: 

156 if not isinstance(other, Key): 

157 return False 

158 

159 return ( 

160 self.keyid == other.keyid 

161 and self.keytype == other.keytype 

162 and self.scheme == other.scheme 

163 and self.keyval == other.keyval 

164 and self.unrecognized_fields == other.unrecognized_fields 

165 ) 

166 

167 def __hash__(self) -> int: 

168 return hash( 

169 ( 

170 self.keyid, 

171 self.keytype, 

172 self.scheme, 

173 make_hashable(self.keyval), 

174 make_hashable(self.unrecognized_fields), 

175 ) 

176 ) 

177 

178 @classmethod 

179 @abstractmethod 

180 def from_dict(cls, keyid: str, key_dict: dict[str, Any]) -> Key: 

181 """Creates ``Key`` object from a serialization dict 

182 

183 Key implementations must override this factory constructor that is used 

184 as a deserialization helper. 

185 

186 Users should call ``Key.from_dict()``: it dispatches to the actual 

187 subclass implementation based on supported keys in 

188 ``KEY_FOR_TYPE_AND_SCHEME``. 

189 

190 Raises: 

191 KeyError, TypeError: Invalid arguments. 

192 """ 

193 keytype = key_dict.get("keytype") 

194 scheme = key_dict.get("scheme") 

195 if (keytype, scheme) not in KEY_FOR_TYPE_AND_SCHEME: 

196 raise ValueError(f"Unsupported public key {keytype}/{scheme}") 

197 

198 # NOTE: Explicitly not checking the keytype and scheme types to allow 

199 # intoto to use (None,None) to lookup GPGKey, see issue #450 

200 key_impl = KEY_FOR_TYPE_AND_SCHEME[(keytype, scheme)] # type: ignore 

201 return key_impl.from_dict(keyid, key_dict) # type: ignore 

202 

203 @abstractmethod 

204 def to_dict(self) -> dict[str, Any]: 

205 """Returns a serialization dict. 

206 

207 Key implementations must override this serialization helper. 

208 """ 

209 raise NotImplementedError 

210 

211 def _to_dict(self) -> dict[str, Any]: 

212 """Serialization helper to add base Key fields to a dict. 

213 

214 Key implementations may call this in their to_dict, which they must 

215 still provide, in order to avoid unnoticed serialization accidents. 

216 """ 

217 return { 

218 "keytype": self.keytype, 

219 "scheme": self.scheme, 

220 "keyval": self.keyval, 

221 **self.unrecognized_fields, 

222 } 

223 

224 @staticmethod 

225 def _from_dict(key_dict: dict[str, Any]) -> tuple[str, str, dict[str, Any]]: 

226 """Deserialization helper to pop base Key fields off the dict. 

227 

228 Key implementations may call this in their from_dict, in order to parse 

229 out common fields. But they have to create the Key instance themselves. 

230 """ 

231 keytype = key_dict.pop("keytype") 

232 scheme = key_dict.pop("scheme") 

233 keyval = key_dict.pop("keyval") 

234 

235 return keytype, scheme, keyval 

236 

237 @abstractmethod 

238 def verify_signature(self, signature: Signature, data: bytes) -> None: 

239 """Raises if verification of signature over data fails. 

240 

241 Args: 

242 signature: Signature object. 

243 data: Payload bytes. 

244 

245 Raises: 

246 UnverifiedSignatureError: Failed to verify signature. 

247 VerificationError: Signature verification process error. If you 

248 are only interested in the verify result, just handle 

249 UnverifiedSignatureError: it contains VerificationError as well 

250 """ 

251 raise NotImplementedError 

252 

253 

254class SSlibKey(Key): 

255 """Key implementation for RSA, Ed25519, ECDSA and ML-DSA keys""" 

256 

257 def __init__( 

258 self, 

259 keyid: str, 

260 keytype: str, 

261 scheme: str, 

262 keyval: dict[str, Any], 

263 unrecognized_fields: dict[str, Any] | None = None, 

264 ): 

265 if "public" not in keyval or not isinstance(keyval["public"], str): 

266 raise ValueError(f"public key string required for scheme {scheme}") 

267 super().__init__(keyid, keytype, scheme, keyval, unrecognized_fields) 

268 

269 def get_hash_algorithm_name(self) -> str: 

270 """Get hash algorithm name for scheme. Raise 

271 ValueError if the scheme is not a supported pre-hash scheme.""" 

272 if self.scheme in [ 

273 RSASSA_PSS_SHA224, 

274 RSASSA_PSS_SHA256, 

275 RSASSA_PSS_SHA384, 

276 RSASSA_PSS_SHA512, 

277 RSA_PKCS1V15_SHA224, 

278 RSA_PKCS1V15_SHA256, 

279 RSA_PKCS1V15_SHA384, 

280 RSA_PKCS1V15_SHA512, 

281 ECDSA_SHA2_NISTP256, 

282 ECDSA_SHA2_NISTP384, 

283 ]: 

284 return f"sha{self.scheme[-3:]}" 

285 

286 elif self.scheme in [ 

287 ECDSA_SHA2_NISTP521, 

288 MLDSA_44_1, 

289 MLDSA_65_1, 

290 MLDSA_87_1, 

291 ]: 

292 return "sha512" 

293 

294 raise ValueError(f"method not supported for scheme {self.scheme}") 

295 

296 def get_padding_name(self) -> str: 

297 """Get padding name for scheme. Raise 

298 ValueError if the scheme is not a supported padded rsa scheme.""" 

299 if self.scheme in [ 

300 RSASSA_PSS_SHA224, 

301 RSASSA_PSS_SHA256, 

302 RSASSA_PSS_SHA384, 

303 RSASSA_PSS_SHA512, 

304 RSA_PKCS1V15_SHA224, 

305 RSA_PKCS1V15_SHA256, 

306 RSA_PKCS1V15_SHA384, 

307 RSA_PKCS1V15_SHA512, 

308 ]: 

309 return self.scheme.split("-")[1] 

310 

311 raise ValueError(f"method not supported for scheme {self.scheme}") 

312 

313 @classmethod 

314 def from_dict(cls, keyid: str, key_dict: dict[str, Any]) -> SSlibKey: 

315 keytype, scheme, keyval = cls._from_dict(key_dict) 

316 

317 # All fields left in the key_dict are unrecognized. 

318 return cls(keyid, keytype, scheme, keyval, key_dict) 

319 

320 def to_dict(self) -> dict[str, Any]: 

321 return self._to_dict() 

322 

323 def _crypto_key(self) -> PublicKeyTypes: 

324 """Helper to get a `cryptography` public key for this SSlibKey.""" 

325 if self.keytype == KEY_TYPE_ED25519: 

326 return Ed25519PublicKey.from_public_bytes( 

327 bytes.fromhex(self.keyval["public"]) 

328 ) 

329 public_bytes = self.keyval["public"].encode("utf-8") 

330 return load_pem_public_key(public_bytes) 

331 

332 @staticmethod 

333 def _from_crypto(public_key: PublicKeyTypes) -> tuple[str, str, str]: 

334 """Return tuple of keytype, default scheme and serialized public key 

335 value for the passed public key. 

336 

337 Raise ValueError if public key is not supported. 

338 """ 

339 

340 def _raw() -> str: 

341 return public_key.public_bytes( 

342 encoding=Encoding.Raw, format=PublicFormat.Raw 

343 ).hex() 

344 

345 def _pem() -> str: 

346 return public_key.public_bytes( 

347 encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo 

348 ).decode() 

349 

350 if isinstance(public_key, RSAPublicKey): 

351 ret = (KEY_TYPE_RSA, RSASSA_PSS_SHA256, _pem()) 

352 elif isinstance(public_key, EllipticCurvePublicKey): 

353 if isinstance(public_key.curve, SECP256R1): 

354 ret = (KEY_TYPE_ECDSA, ECDSA_SHA2_NISTP256, _pem()) 

355 elif isinstance(public_key.curve, SECP384R1): 

356 ret = (KEY_TYPE_ECDSA, ECDSA_SHA2_NISTP384, _pem()) 

357 elif isinstance(public_key.curve, SECP521R1): 

358 ret = (KEY_TYPE_ECDSA, ECDSA_SHA2_NISTP521, _pem()) 

359 else: 

360 raise ValueError(f"unsupported curve '{public_key.curve.name}'") 

361 elif isinstance(public_key, Ed25519PublicKey): 

362 ret = (KEY_TYPE_ED25519, ED25519, _raw()) 

363 # ML-DSA key types may be None as fallback for cryptography < 48 

364 elif MLDSA44PublicKey is not None and isinstance(public_key, MLDSA44PublicKey): 

365 ret = (KEY_TYPE_MLDSA, MLDSA_44_1, _pem()) 

366 elif MLDSA65PublicKey is not None and isinstance(public_key, MLDSA65PublicKey): 

367 ret = (KEY_TYPE_MLDSA, MLDSA_65_1, _pem()) 

368 elif MLDSA87PublicKey is not None and isinstance(public_key, MLDSA87PublicKey): 

369 ret = (KEY_TYPE_MLDSA, MLDSA_87_1, _pem()) 

370 else: 

371 raise ValueError(f"unsupported key '{type(public_key)}'") 

372 

373 return ret 

374 

375 @classmethod 

376 def from_crypto( 

377 cls, 

378 public_key: PublicKeyTypes, 

379 keyid: str | None = None, 

380 scheme: str | None = None, 

381 ) -> SSlibKey: 

382 """Create SSlibKey from pyca/cryptography public key. 

383 

384 Args: 

385 public_key: pyca/cryptography public key object. 

386 keyid: Key identifier. If not passed, a default keyid is computed. 

387 scheme: SSlibKey signing scheme. If not passed, default value is decided 

388 based on the public key content. 

389 

390 Raises: 

391 UnsupportedLibraryError: pyca/cryptography not installed 

392 ValueError: Key type not supported 

393 

394 Returns: 

395 SSlibKey 

396 

397 """ 

398 if CRYPTO_IMPORT_ERROR: 

399 raise UnsupportedLibraryError(CRYPTO_IMPORT_ERROR) 

400 

401 keytype, default_scheme, public_key_value = cls._from_crypto(public_key) 

402 

403 if not scheme: 

404 scheme = default_scheme 

405 

406 keyval = {"public": public_key_value} 

407 

408 if not keyid: 

409 keyid = compute_default_keyid(keytype, scheme, keyval) 

410 

411 return SSlibKey(keyid, keytype, scheme, keyval) 

412 

413 @staticmethod 

414 def _get_rsa_padding(name: str, hash_algorithm: HashAlgorithm) -> AsymmetricPadding: 

415 """Helper to return rsa signature padding for name.""" 

416 padding: AsymmetricPadding 

417 if name == "pss": 

418 padding = PSS(mgf=MGF1(hash_algorithm), salt_length=PSS.AUTO) 

419 

420 if name == "pkcs1v15": 

421 padding = PKCS1v15() 

422 

423 return padding 

424 

425 def _verify_ed25519_fallback(self, signature: bytes, data: bytes) -> None: 

426 """Helper to verify ed25519 sig if pyca/cryptography is unavailable.""" 

427 try: 

428 public_bytes = bytes.fromhex(self.keyval["public"]) 

429 checkvalid(signature, data, public_bytes) 

430 

431 except SignatureMismatch as e: 

432 raise UnverifiedSignatureError from e 

433 

434 def _verify(self, signature: bytes, data: bytes) -> None: # noqa: PLR0912, PLR0915 

435 """Helper to verify signature using pyca/cryptography (default).""" 

436 

437 def _validate_type(key: object, type_: type) -> None: 

438 if not isinstance(key, type_): 

439 raise ValueError(f"bad key {key} for {self.scheme}") 

440 

441 def _validate_curve( 

442 key: EllipticCurvePublicKey, curve: type[EllipticCurve] 

443 ) -> None: 

444 if not isinstance(key.curve, curve): 

445 raise ValueError(f"bad curve {key.curve} for {self.scheme}") 

446 

447 if self.keytype == KEY_TYPE_MLDSA and MLDSA_IMPORT_ERROR: 

448 raise UnsupportedLibraryError(MLDSA_IMPORT_ERROR) 

449 

450 try: 

451 key: PublicKeyTypes 

452 if self.keytype == KEY_TYPE_RSA and self.scheme in [ 

453 RSASSA_PSS_SHA224, 

454 RSASSA_PSS_SHA256, 

455 RSASSA_PSS_SHA384, 

456 RSASSA_PSS_SHA512, 

457 RSA_PKCS1V15_SHA224, 

458 RSA_PKCS1V15_SHA256, 

459 RSA_PKCS1V15_SHA384, 

460 RSA_PKCS1V15_SHA512, 

461 ]: 

462 key = cast(RSAPublicKey, self._crypto_key()) 

463 _validate_type(key, RSAPublicKey) 

464 hash_name = self.get_hash_algorithm_name() 

465 hash_algorithm = get_hash_algorithm(hash_name) 

466 padding_name = self.get_padding_name() 

467 padding = self._get_rsa_padding(padding_name, hash_algorithm) 

468 key.verify(signature, data, padding, hash_algorithm) 

469 

470 elif ( 

471 self.keytype in [KEY_TYPE_ECDSA, ECDSA_SHA2_NISTP256] 

472 and self.scheme == ECDSA_SHA2_NISTP256 

473 ): 

474 if self.keytype == ECDSA_SHA2_NISTP256: 

475 warnings.warn( 

476 f"keytype '{ECDSA_SHA2_NISTP256}' is deprecated, " 

477 f"use '{KEY_TYPE_ECDSA}' instead", 

478 DeprecationWarning, 

479 stacklevel=2, 

480 ) 

481 key = cast(EllipticCurvePublicKey, self._crypto_key()) 

482 _validate_type(key, EllipticCurvePublicKey) 

483 _validate_curve(key, SECP256R1) 

484 key.verify(signature, data, ECDSA(SHA256())) 

485 

486 elif ( 

487 self.keytype in [KEY_TYPE_ECDSA, ECDSA_SHA2_NISTP384] 

488 and self.scheme == ECDSA_SHA2_NISTP384 

489 ): 

490 if self.keytype == ECDSA_SHA2_NISTP384: 

491 warnings.warn( 

492 f"keytype '{ECDSA_SHA2_NISTP384}' is deprecated, " 

493 f"use '{KEY_TYPE_ECDSA}' instead", 

494 DeprecationWarning, 

495 stacklevel=2, 

496 ) 

497 key = cast(EllipticCurvePublicKey, self._crypto_key()) 

498 _validate_type(key, EllipticCurvePublicKey) 

499 _validate_curve(key, SECP384R1) 

500 key.verify(signature, data, ECDSA(SHA384())) 

501 

502 elif ( 

503 self.keytype in [KEY_TYPE_ECDSA, ECDSA_SHA2_NISTP521] 

504 and self.scheme == ECDSA_SHA2_NISTP521 

505 ): 

506 if self.keytype == ECDSA_SHA2_NISTP521: 

507 warnings.warn( 

508 f"keytype '{ECDSA_SHA2_NISTP521}' is deprecated, " 

509 f"use '{KEY_TYPE_ECDSA}' instead", 

510 DeprecationWarning, 

511 stacklevel=2, 

512 ) 

513 key = cast(EllipticCurvePublicKey, self._crypto_key()) 

514 _validate_type(key, EllipticCurvePublicKey) 

515 _validate_curve(key, SECP521R1) 

516 key.verify(signature, data, ECDSA(SHA512())) 

517 

518 elif self.keytype == KEY_TYPE_ED25519 and self.scheme == ED25519: 

519 key = cast(Ed25519PublicKey, self._crypto_key()) 

520 _validate_type(key, Ed25519PublicKey) 

521 key.verify(signature, data) 

522 

523 elif self.keytype == KEY_TYPE_MLDSA and self.scheme == MLDSA_44_1: 

524 key = cast(MLDSA44PublicKey, self._crypto_key()) 

525 _validate_type(key, MLDSA44PublicKey) 

526 key.verify(signature, get_mldsa_payload(data, 1)) 

527 

528 elif self.keytype == KEY_TYPE_MLDSA and self.scheme == MLDSA_65_1: 

529 key = cast(MLDSA65PublicKey, self._crypto_key()) 

530 _validate_type(key, MLDSA65PublicKey) 

531 key.verify(signature, get_mldsa_payload(data, 1)) 

532 

533 elif self.keytype == KEY_TYPE_MLDSA and self.scheme == MLDSA_87_1: 

534 key = cast(MLDSA87PublicKey, self._crypto_key()) 

535 _validate_type(key, MLDSA87PublicKey) 

536 key.verify(signature, get_mldsa_payload(data, 1)) 

537 

538 else: 

539 raise ValueError(f"Unsupported public key {self.keytype}/{self.scheme}") 

540 

541 except InvalidSignature as e: 

542 raise UnverifiedSignatureError from e 

543 

544 def verify_signature(self, signature: Signature, data: bytes) -> None: 

545 try: 

546 if signature.keyid != self.keyid: 

547 raise ValueError( 

548 f"keyid mismatch: 'key id: {self.keyid}" 

549 f" != signature keyid: {signature.keyid}'" 

550 ) 

551 

552 signature_bytes = bytes.fromhex(signature.signature) 

553 

554 if CRYPTO_IMPORT_ERROR: 

555 if self.scheme != ED25519: 

556 raise UnsupportedLibraryError(CRYPTO_IMPORT_ERROR) 

557 

558 return self._verify_ed25519_fallback(signature_bytes, data) 

559 

560 return self._verify(signature_bytes, data) 

561 

562 except UnverifiedSignatureError as e: 

563 raise UnverifiedSignatureError( 

564 f"Failed to verify signature by {self.keyid}" 

565 ) from e 

566 

567 except Exception as e: 

568 logger.info("Key %s failed to verify sig: %s", self.keyid, e) 

569 raise VerificationError( 

570 f"Unknown failure to verify signature by {self.keyid}" 

571 ) from e