Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jwt/algorithms.py: 30%

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

528 statements  

1from __future__ import annotations 

2 

3import hashlib 

4import hmac 

5import json 

6import os 

7import sys 

8from abc import ABC, abstractmethod 

9from typing import ( 

10 TYPE_CHECKING, 

11 Any, 

12 ClassVar, 

13 Literal, 

14 NoReturn, 

15 Union, 

16 cast, 

17 get_args, 

18 overload, 

19) 

20 

21from .exceptions import InvalidKeyError 

22from .types import HashlibHash, JWKDict 

23from .utils import ( 

24 base64url_decode, 

25 base64url_encode, 

26 der_to_raw_signature, 

27 force_bytes, 

28 from_base64url_uint, 

29 is_pem_format, 

30 is_ssh_key, 

31 raw_to_der_signature, 

32 to_base64url_uint, 

33) 

34 

35try: 

36 from cryptography import x509 

37 from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm 

38 from cryptography.hazmat.backends import default_backend 

39 from cryptography.hazmat.primitives import hashes 

40 from cryptography.hazmat.primitives.asymmetric import padding 

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

42 ECDSA, 

43 SECP256K1, 

44 SECP256R1, 

45 SECP384R1, 

46 SECP521R1, 

47 EllipticCurve, 

48 EllipticCurvePrivateKey, 

49 EllipticCurvePrivateNumbers, 

50 EllipticCurvePublicKey, 

51 EllipticCurvePublicNumbers, 

52 ) 

53 from cryptography.hazmat.primitives.asymmetric.ed448 import ( 

54 Ed448PrivateKey, 

55 Ed448PublicKey, 

56 ) 

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

58 Ed25519PrivateKey, 

59 Ed25519PublicKey, 

60 ) 

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

62 RSAPrivateKey, 

63 RSAPrivateNumbers, 

64 RSAPublicKey, 

65 RSAPublicNumbers, 

66 rsa_crt_dmp1, 

67 rsa_crt_dmq1, 

68 rsa_crt_iqmp, 

69 rsa_recover_prime_factors, 

70 ) 

71 from cryptography.hazmat.primitives.serialization import ( 

72 Encoding, 

73 NoEncryption, 

74 PrivateFormat, 

75 PublicFormat, 

76 load_der_public_key, 

77 load_pem_private_key, 

78 load_pem_public_key, 

79 load_ssh_public_key, 

80 ) 

81 

82 if sys.version_info >= (3, 10): 

83 from typing import TypeAlias 

84 else: 

85 # Python 3.9 and lower 

86 from typing_extensions import TypeAlias 

87 

88 # Type aliases for convenience in algorithms method signatures 

89 AllowedRSAKeys: TypeAlias = Union[RSAPrivateKey, RSAPublicKey] 

90 AllowedECKeys: TypeAlias = Union[EllipticCurvePrivateKey, EllipticCurvePublicKey] 

91 AllowedOKPKeys: TypeAlias = Union[ 

92 Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey 

93 ] 

94 AllowedKeys: TypeAlias = Union[AllowedRSAKeys, AllowedECKeys, AllowedOKPKeys] 

95 #: Type alias for allowed ``cryptography`` private keys (requires ``cryptography`` to be installed) 

96 AllowedPrivateKeys: TypeAlias = Union[ 

97 RSAPrivateKey, EllipticCurvePrivateKey, Ed25519PrivateKey, Ed448PrivateKey 

98 ] 

99 #: Type alias for allowed ``cryptography`` public keys (requires ``cryptography`` to be installed) 

100 AllowedPublicKeys: TypeAlias = Union[ 

101 RSAPublicKey, EllipticCurvePublicKey, Ed25519PublicKey, Ed448PublicKey 

102 ] 

103 

104 if TYPE_CHECKING or bool(os.getenv("SPHINX_BUILD", "")): 

105 from cryptography.hazmat.primitives.asymmetric.types import ( 

106 PrivateKeyTypes, 

107 PublicKeyTypes, 

108 ) 

109 

110 has_crypto = True 

111except ModuleNotFoundError: 

112 if sys.version_info >= (3, 11): 

113 from typing import Never 

114 else: 

115 from typing_extensions import Never 

116 

117 AllowedRSAKeys = Never # type: ignore[misc] 

118 AllowedECKeys = Never # type: ignore[misc] 

119 AllowedOKPKeys = Never # type: ignore[misc] 

120 AllowedKeys = Never # type: ignore[misc] 

121 AllowedPrivateKeys = Never # type: ignore[misc] 

122 AllowedPublicKeys = Never # type: ignore[misc] 

123 has_crypto = False 

124 

125 

126requires_cryptography = { 

127 "RS256", 

128 "RS384", 

129 "RS512", 

130 "ES256", 

131 "ES256K", 

132 "ES384", 

133 "ES521", 

134 "ES512", 

135 "PS256", 

136 "PS384", 

137 "PS512", 

138 "EdDSA", 

139} 

140 

141 

142def get_default_algorithms() -> dict[str, Algorithm]: 

143 """ 

144 Returns the algorithms that are implemented by the library. 

145 """ 

146 default_algorithms: dict[str, Algorithm] = { 

147 "none": NoneAlgorithm(), 

148 "HS256": HMACAlgorithm(HMACAlgorithm.SHA256), 

149 "HS384": HMACAlgorithm(HMACAlgorithm.SHA384), 

150 "HS512": HMACAlgorithm(HMACAlgorithm.SHA512), 

151 } 

152 

153 if has_crypto: 

154 default_algorithms.update( 

155 { 

156 "RS256": RSAAlgorithm(RSAAlgorithm.SHA256), 

157 "RS384": RSAAlgorithm(RSAAlgorithm.SHA384), 

158 "RS512": RSAAlgorithm(RSAAlgorithm.SHA512), 

159 "ES256": ECAlgorithm(ECAlgorithm.SHA256, SECP256R1), 

160 "ES256K": ECAlgorithm(ECAlgorithm.SHA256, SECP256K1), 

161 "ES384": ECAlgorithm(ECAlgorithm.SHA384, SECP384R1), 

162 "ES521": ECAlgorithm(ECAlgorithm.SHA512, SECP521R1), 

163 "ES512": ECAlgorithm( 

164 ECAlgorithm.SHA512, SECP521R1 

165 ), # Backward compat for #219 fix 

166 "PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), 

167 "PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), 

168 "PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512), 

169 "EdDSA": OKPAlgorithm(), 

170 } 

171 ) 

172 

173 return default_algorithms 

174 

175 

176class Algorithm(ABC): 

177 """ 

178 The interface for an algorithm used to sign and verify tokens. 

179 """ 

180 

181 # pyjwt-964: Validate to ensure the key passed in was decoded to the correct cryptography key family 

182 _crypto_key_types: tuple[type[AllowedKeys], ...] | None = None 

183 

184 def compute_hash_digest(self, bytestr: bytes) -> bytes: 

185 """ 

186 Compute a hash digest using the specified algorithm's hash algorithm. 

187 

188 If there is no hash algorithm, raises a NotImplementedError. 

189 """ 

190 # lookup self.hash_alg if defined in a way that mypy can understand 

191 hash_alg = getattr(self, "hash_alg", None) 

192 if hash_alg is None: 

193 raise NotImplementedError 

194 

195 if ( 

196 has_crypto 

197 and isinstance(hash_alg, type) 

198 and issubclass(hash_alg, hashes.HashAlgorithm) 

199 ): 

200 digest = hashes.Hash(hash_alg(), backend=default_backend()) 

201 digest.update(bytestr) 

202 return bytes(digest.finalize()) 

203 else: 

204 return bytes(hash_alg(bytestr).digest()) 

205 

206 def check_crypto_key_type(self, key: PublicKeyTypes | PrivateKeyTypes) -> None: 

207 """Check that the key belongs to the right cryptographic family. 

208 

209 Note that this method only works when ``cryptography`` is installed. 

210 

211 :param key: Potentially a cryptography key 

212 :type key: :py:data:`PublicKeyTypes <cryptography.hazmat.primitives.asymmetric.types.PublicKeyTypes>` | :py:data:`PrivateKeyTypes <cryptography.hazmat.primitives.asymmetric.types.PrivateKeyTypes>` 

213 :raises ValueError: if ``cryptography`` is not installed, or this method is called by a non-cryptography algorithm 

214 :raises InvalidKeyError: if the key doesn't match the expected key classes 

215 """ 

216 if not has_crypto or self._crypto_key_types is None: 

217 raise ValueError( 

218 "This method requires the cryptography library, and should only be used by cryptography-based algorithms." 

219 ) 

220 

221 if not isinstance(key, self._crypto_key_types): 

222 valid_classes = (cls.__name__ for cls in self._crypto_key_types) 

223 actual_class = key.__class__.__name__ 

224 self_class = self.__class__.__name__ 

225 raise InvalidKeyError( 

226 f"Expected one of {valid_classes}, got: {actual_class}. Invalid Key type for {self_class}" 

227 ) 

228 

229 @abstractmethod 

230 def prepare_key(self, key: Any) -> Any: 

231 """ 

232 Performs necessary validation and conversions on the key and returns 

233 the key value in the proper format for sign() and verify(). 

234 """ 

235 

236 @abstractmethod 

237 def sign(self, msg: bytes, key: Any) -> bytes: 

238 """ 

239 Returns a digital signature for the specified message 

240 using the specified key value. 

241 """ 

242 

243 @abstractmethod 

244 def verify(self, msg: bytes, key: Any, sig: bytes) -> bool: 

245 """ 

246 Verifies that the specified digital signature is valid 

247 for the specified message and key values. 

248 """ 

249 

250 @overload 

251 @staticmethod 

252 @abstractmethod 

253 def to_jwk(key_obj: Any, as_dict: Literal[True]) -> JWKDict: ... # pragma: no cover 

254 

255 @overload 

256 @staticmethod 

257 @abstractmethod 

258 def to_jwk( 

259 key_obj: Any, as_dict: Literal[False] = False 

260 ) -> str: ... # pragma: no cover 

261 

262 @staticmethod 

263 @abstractmethod 

264 def to_jwk(key_obj: Any, as_dict: bool = False) -> JWKDict | str: 

265 """ 

266 Serializes a given key into a JWK 

267 """ 

268 

269 @staticmethod 

270 @abstractmethod 

271 def from_jwk(jwk: str | JWKDict) -> Any: 

272 """ 

273 Deserializes a given key from JWK back into a key object 

274 """ 

275 

276 def check_key_length(self, key: Any) -> str | None: 

277 """ 

278 Return a warning message if the key is below the minimum 

279 recommended length for this algorithm, or None if adequate. 

280 """ 

281 return None 

282 

283 

284class NoneAlgorithm(Algorithm): 

285 """ 

286 Placeholder for use when no signing or verification 

287 operations are required. 

288 """ 

289 

290 def prepare_key(self, key: str | None) -> None: 

291 if key == "": 

292 key = None 

293 

294 if key is not None: 

295 raise InvalidKeyError('When alg = "none", key value must be None.') 

296 

297 return key 

298 

299 def sign(self, msg: bytes, key: None) -> bytes: 

300 return b"" 

301 

302 def verify(self, msg: bytes, key: None, sig: bytes) -> bool: 

303 return False 

304 

305 @staticmethod 

306 def to_jwk(key_obj: Any, as_dict: bool = False) -> NoReturn: 

307 raise NotImplementedError() 

308 

309 @staticmethod 

310 def from_jwk(jwk: str | JWKDict) -> NoReturn: 

311 raise NotImplementedError() 

312 

313 

314class HMACAlgorithm(Algorithm): 

315 """ 

316 Performs signing and verification operations using HMAC 

317 and the specified hash function. 

318 """ 

319 

320 SHA256: ClassVar[HashlibHash] = hashlib.sha256 

321 SHA384: ClassVar[HashlibHash] = hashlib.sha384 

322 SHA512: ClassVar[HashlibHash] = hashlib.sha512 

323 

324 def __init__(self, hash_alg: HashlibHash) -> None: 

325 self.hash_alg = hash_alg 

326 

327 @staticmethod 

328 def _is_der_key(key_bytes: bytes) -> bool: 

329 if not has_crypto: 

330 return False 

331 

332 try: 

333 load_der_public_key(key_bytes) 

334 except (TypeError, ValueError, UnsupportedAlgorithm): 

335 pass 

336 else: 

337 return True 

338 

339 try: 

340 x509.load_der_x509_certificate(key_bytes) 

341 except (TypeError, ValueError, UnsupportedAlgorithm): 

342 return False 

343 else: 

344 return True 

345 

346 def prepare_key(self, key: str | bytes) -> bytes: 

347 key_bytes = force_bytes(key) 

348 

349 if len(key_bytes) == 0: 

350 raise InvalidKeyError("HMAC key must not be empty.") 

351 

352 if ( 

353 is_pem_format(key_bytes) 

354 or is_ssh_key(key_bytes) 

355 or self._is_der_key(key_bytes) 

356 ): 

357 raise InvalidKeyError( 

358 "The specified key is an asymmetric key or x509 certificate and" 

359 " should not be used as an HMAC secret." 

360 ) 

361 

362 # Defense against algorithm-confusion attacks: an attacker with 

363 # control over the token header can force this code path by setting 

364 # alg=HS*, and HMACAlgorithm is the only algorithm that accepts 

365 # arbitrary bytes as a valid secret. Other algorithms reject 

366 # non-key-shaped input naturally. Even a symmetric (kty=oct) JWK 

367 # should be loaded via PyJWK / from_jwk rather than fed as raw JSON 

368 # bytes (whose contents are not the secret material). 

369 try: 

370 jwk_obj = json.loads(key_bytes, parse_int=lambda _: 0) 

371 except RecursionError: 

372 try: 

373 decoded_key = key_bytes.decode( 

374 json.detect_encoding(key_bytes), errors="surrogatepass" 

375 ) 

376 except UnicodeError: 

377 decoded_key = "" 

378 stripped_key = decoded_key.lstrip("\ufeff \t\r\n") 

379 has_jwk_member = False 

380 index = 0 

381 while index < len(decoded_key): 

382 if decoded_key[index] != '"': 

383 index += 1 

384 continue 

385 end = index + 1 

386 while end < len(decoded_key): 

387 if decoded_key[end] == "\\": 

388 end += 2 

389 elif decoded_key[end] == '"': 

390 break 

391 else: 

392 end += 1 

393 if end >= len(decoded_key): 

394 break 

395 next_index = end + 1 

396 while ( 

397 next_index < len(decoded_key) 

398 and decoded_key[next_index] in " \t\r\n" 

399 ): 

400 next_index += 1 

401 if next_index < len(decoded_key) and decoded_key[next_index] == ":": 

402 try: 

403 has_jwk_member = ( 

404 json.loads(decoded_key[index : end + 1]) == "kty" 

405 ) 

406 except ValueError: 

407 pass 

408 if has_jwk_member: 

409 break 

410 index = end + 1 

411 if stripped_key.startswith("{") or ( 

412 stripped_key.startswith("[") and has_jwk_member 

413 ): 

414 raise InvalidKeyError( 

415 "The specified key looks like a JWK and should not be " 

416 "used directly as an HMAC secret. Load it via " 

417 "PyJWK / HMACAlgorithm.from_jwk first." 

418 ) from None 

419 jwk_obj = None 

420 except ValueError: 

421 jwk_obj = None 

422 contains_jwk_member = False 

423 objects_to_check = [jwk_obj] 

424 while objects_to_check: 

425 obj = objects_to_check.pop() 

426 if isinstance(obj, dict): 

427 if "kty" in obj: 

428 contains_jwk_member = True 

429 break 

430 objects_to_check.extend(obj.values()) 

431 elif isinstance(obj, list): 

432 objects_to_check.extend(obj) 

433 if contains_jwk_member: 

434 raise InvalidKeyError( 

435 "The specified key looks like a JWK and should not be " 

436 "used directly as an HMAC secret. Load it via " 

437 "PyJWK / HMACAlgorithm.from_jwk first." 

438 ) 

439 

440 return key_bytes 

441 

442 @overload 

443 @staticmethod 

444 def to_jwk(key_obj: str | bytes, as_dict: Literal[True]) -> JWKDict: ... 

445 

446 @overload 

447 @staticmethod 

448 def to_jwk(key_obj: str | bytes, as_dict: Literal[False] = False) -> str: ... 

449 

450 @staticmethod 

451 def to_jwk(key_obj: str | bytes, as_dict: bool = False) -> JWKDict | str: 

452 jwk = { 

453 "k": base64url_encode(force_bytes(key_obj)).decode(), 

454 "kty": "oct", 

455 } 

456 

457 if as_dict: 

458 return jwk 

459 else: 

460 return json.dumps(jwk) 

461 

462 @staticmethod 

463 def from_jwk(jwk: str | JWKDict) -> bytes: 

464 try: 

465 if isinstance(jwk, str): 

466 obj: JWKDict = json.loads(jwk) 

467 elif isinstance(jwk, dict): 

468 obj = jwk 

469 else: 

470 raise ValueError 

471 except ValueError: 

472 raise InvalidKeyError("Key is not valid JSON") from None 

473 

474 if obj.get("kty") != "oct": 

475 raise InvalidKeyError("Not an HMAC key") 

476 

477 key_bytes = base64url_decode(obj["k"]) 

478 if len(key_bytes) == 0: 

479 raise InvalidKeyError("HMAC key must not be empty.") 

480 return key_bytes 

481 

482 def check_key_length(self, key: bytes) -> str | None: 

483 min_length = self.hash_alg().digest_size 

484 if len(key) < min_length: 

485 return ( 

486 f"The HMAC key is {len(key)} bytes long, which is below " 

487 f"the minimum recommended length of {min_length} bytes for " 

488 f"{self.hash_alg().name.upper()}. " 

489 f"See RFC 7518 Section 3.2." 

490 ) 

491 return None 

492 

493 def sign(self, msg: bytes, key: bytes) -> bytes: 

494 return hmac.new(key, msg, self.hash_alg).digest() 

495 

496 def verify(self, msg: bytes, key: bytes, sig: bytes) -> bool: 

497 return hmac.compare_digest(sig, self.sign(msg, key)) 

498 

499 

500if has_crypto: 

501 

502 class RSAAlgorithm(Algorithm): 

503 """ 

504 Performs signing and verification operations using 

505 RSASSA-PKCS-v1_5 and the specified hash function. 

506 """ 

507 

508 SHA256: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA256 

509 SHA384: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA384 

510 SHA512: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA512 

511 

512 _crypto_key_types = cast( 

513 tuple[type[AllowedKeys], ...], 

514 get_args(Union[RSAPrivateKey, RSAPublicKey]), 

515 ) 

516 _MIN_KEY_SIZE: ClassVar[int] = 2048 

517 

518 def __init__(self, hash_alg: type[hashes.HashAlgorithm]) -> None: 

519 self.hash_alg = hash_alg 

520 

521 def check_key_length(self, key: AllowedRSAKeys) -> str | None: 

522 if key.key_size < self._MIN_KEY_SIZE: 

523 return ( 

524 f"The RSA key is {key.key_size} bits long, which is below " 

525 f"the minimum recommended size of {self._MIN_KEY_SIZE} bits. " 

526 f"See NIST SP 800-131A." 

527 ) 

528 return None 

529 

530 def prepare_key(self, key: AllowedRSAKeys | str | bytes) -> AllowedRSAKeys: 

531 if isinstance(key, self._crypto_key_types): 

532 # Cast is required for type narrowing on Python 3.9's mypy 

533 # but redundant on newer mypy versions; suppress both 

534 # diagnostics so the line works across all supported envs. 

535 return cast(AllowedRSAKeys, key) # type: ignore[redundant-cast,unused-ignore] 

536 

537 if not isinstance(key, (bytes, str)): 

538 raise TypeError("Expecting a PEM-formatted key.") 

539 

540 key_bytes = force_bytes(key) 

541 

542 try: 

543 if key_bytes.startswith(b"ssh-rsa"): 

544 public_key: PublicKeyTypes = load_ssh_public_key(key_bytes) 

545 self.check_crypto_key_type(public_key) 

546 return cast(RSAPublicKey, public_key) 

547 else: 

548 private_key: PrivateKeyTypes = load_pem_private_key( 

549 key_bytes, password=None 

550 ) 

551 self.check_crypto_key_type(private_key) 

552 return cast(RSAPrivateKey, private_key) 

553 except ValueError: 

554 try: 

555 public_key = load_pem_public_key(key_bytes) 

556 self.check_crypto_key_type(public_key) 

557 return cast(RSAPublicKey, public_key) 

558 except (ValueError, UnsupportedAlgorithm): 

559 raise InvalidKeyError( 

560 "Could not parse the provided public key." 

561 ) from None 

562 

563 @overload 

564 @staticmethod 

565 def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[True]) -> JWKDict: ... 

566 

567 @overload 

568 @staticmethod 

569 def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[False] = False) -> str: ... 

570 

571 @staticmethod 

572 def to_jwk(key_obj: AllowedRSAKeys, as_dict: bool = False) -> JWKDict | str: 

573 obj: dict[str, Any] | None = None 

574 

575 if hasattr(key_obj, "private_numbers"): 

576 # Private key 

577 numbers = key_obj.private_numbers() 

578 

579 obj = { 

580 "kty": "RSA", 

581 "key_ops": ["sign"], 

582 "n": to_base64url_uint(numbers.public_numbers.n).decode(), 

583 "e": to_base64url_uint(numbers.public_numbers.e).decode(), 

584 "d": to_base64url_uint(numbers.d).decode(), 

585 "p": to_base64url_uint(numbers.p).decode(), 

586 "q": to_base64url_uint(numbers.q).decode(), 

587 "dp": to_base64url_uint(numbers.dmp1).decode(), 

588 "dq": to_base64url_uint(numbers.dmq1).decode(), 

589 "qi": to_base64url_uint(numbers.iqmp).decode(), 

590 } 

591 

592 elif hasattr(key_obj, "verify"): 

593 # Public key 

594 numbers = key_obj.public_numbers() 

595 

596 obj = { 

597 "kty": "RSA", 

598 "key_ops": ["verify"], 

599 "n": to_base64url_uint(numbers.n).decode(), 

600 "e": to_base64url_uint(numbers.e).decode(), 

601 } 

602 else: 

603 raise InvalidKeyError("Not a public or private key") 

604 

605 if as_dict: 

606 return obj 

607 else: 

608 return json.dumps(obj) 

609 

610 @staticmethod 

611 def from_jwk(jwk: str | JWKDict) -> AllowedRSAKeys: 

612 try: 

613 if isinstance(jwk, str): 

614 obj = json.loads(jwk) 

615 elif isinstance(jwk, dict): 

616 obj = jwk 

617 else: 

618 raise ValueError 

619 except ValueError: 

620 raise InvalidKeyError("Key is not valid JSON") from None 

621 

622 if obj.get("kty") != "RSA": 

623 raise InvalidKeyError("Not an RSA key") from None 

624 

625 if "d" in obj and "e" in obj and "n" in obj: 

626 # Private key 

627 if "oth" in obj: 

628 raise InvalidKeyError( 

629 "Unsupported RSA private key: > 2 primes not supported" 

630 ) 

631 

632 other_props = ["p", "q", "dp", "dq", "qi"] 

633 props_found = [prop in obj for prop in other_props] 

634 any_props_found = any(props_found) 

635 

636 if any_props_found and not all(props_found): 

637 raise InvalidKeyError( 

638 "RSA key must include all parameters if any are present besides d" 

639 ) from None 

640 

641 public_numbers = RSAPublicNumbers( 

642 from_base64url_uint(obj["e"]), 

643 from_base64url_uint(obj["n"]), 

644 ) 

645 

646 if any_props_found: 

647 numbers = RSAPrivateNumbers( 

648 d=from_base64url_uint(obj["d"]), 

649 p=from_base64url_uint(obj["p"]), 

650 q=from_base64url_uint(obj["q"]), 

651 dmp1=from_base64url_uint(obj["dp"]), 

652 dmq1=from_base64url_uint(obj["dq"]), 

653 iqmp=from_base64url_uint(obj["qi"]), 

654 public_numbers=public_numbers, 

655 ) 

656 else: 

657 d = from_base64url_uint(obj["d"]) 

658 p, q = rsa_recover_prime_factors( 

659 public_numbers.n, d, public_numbers.e 

660 ) 

661 

662 numbers = RSAPrivateNumbers( 

663 d=d, 

664 p=p, 

665 q=q, 

666 dmp1=rsa_crt_dmp1(d, p), 

667 dmq1=rsa_crt_dmq1(d, q), 

668 iqmp=rsa_crt_iqmp(p, q), 

669 public_numbers=public_numbers, 

670 ) 

671 

672 return numbers.private_key() 

673 elif "n" in obj and "e" in obj: 

674 # Public key 

675 return RSAPublicNumbers( 

676 from_base64url_uint(obj["e"]), 

677 from_base64url_uint(obj["n"]), 

678 ).public_key() 

679 else: 

680 raise InvalidKeyError("Not a public or private key") 

681 

682 def sign(self, msg: bytes, key: RSAPrivateKey) -> bytes: 

683 signature: bytes = key.sign(msg, padding.PKCS1v15(), self.hash_alg()) 

684 return signature 

685 

686 def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool: 

687 try: 

688 key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg()) 

689 return True 

690 except InvalidSignature: 

691 return False 

692 

693 class ECAlgorithm(Algorithm): 

694 """ 

695 Performs signing and verification operations using 

696 ECDSA and the specified hash function 

697 """ 

698 

699 SHA256: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA256 

700 SHA384: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA384 

701 SHA512: ClassVar[type[hashes.HashAlgorithm]] = hashes.SHA512 

702 

703 _crypto_key_types = cast( 

704 tuple[type[AllowedKeys], ...], 

705 get_args(Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]), 

706 ) 

707 

708 def __init__( 

709 self, 

710 hash_alg: type[hashes.HashAlgorithm], 

711 expected_curve: type[EllipticCurve] | None = None, 

712 ) -> None: 

713 self.hash_alg = hash_alg 

714 self.expected_curve = expected_curve 

715 

716 def _validate_curve(self, key: AllowedECKeys) -> None: 

717 """Validate that the key's curve matches the expected curve.""" 

718 if self.expected_curve is None: 

719 return 

720 

721 if not isinstance(key.curve, self.expected_curve): 

722 raise InvalidKeyError( 

723 f"The key's curve '{key.curve.name}' does not match the expected " 

724 f"curve '{self.expected_curve.name}' for this algorithm" 

725 ) 

726 

727 def prepare_key(self, key: AllowedECKeys | str | bytes) -> AllowedECKeys: 

728 if isinstance(key, self._crypto_key_types): 

729 # See note in RSAAlgorithm.prepare_key. 

730 ec_key = cast(AllowedECKeys, key) # type: ignore[redundant-cast,unused-ignore] 

731 self._validate_curve(ec_key) 

732 return ec_key 

733 

734 if not isinstance(key, (bytes, str)): 

735 raise TypeError("Expecting a PEM-formatted key.") 

736 

737 key_bytes = force_bytes(key) 

738 

739 # Attempt to load key. We don't know if it's 

740 # a Signing Key or a Verifying Key, so we try 

741 # the Verifying Key first. 

742 try: 

743 if key_bytes.startswith(b"ecdsa-sha2-"): 

744 public_key: PublicKeyTypes = load_ssh_public_key(key_bytes) 

745 else: 

746 public_key = load_pem_public_key(key_bytes) 

747 

748 # Explicit check the key to prevent confusing errors from cryptography 

749 self.check_crypto_key_type(public_key) 

750 ec_public_key = cast(EllipticCurvePublicKey, public_key) 

751 self._validate_curve(ec_public_key) 

752 return ec_public_key 

753 except ValueError: 

754 private_key = load_pem_private_key(key_bytes, password=None) 

755 self.check_crypto_key_type(private_key) 

756 ec_private_key = cast(EllipticCurvePrivateKey, private_key) 

757 self._validate_curve(ec_private_key) 

758 return ec_private_key 

759 

760 def sign(self, msg: bytes, key: EllipticCurvePrivateKey) -> bytes: 

761 der_sig = key.sign(msg, ECDSA(self.hash_alg())) 

762 

763 return der_to_raw_signature(der_sig, key.curve) 

764 

765 def verify(self, msg: bytes, key: AllowedECKeys, sig: bytes) -> bool: 

766 try: 

767 der_sig = raw_to_der_signature(sig, key.curve) 

768 except ValueError: 

769 return False 

770 

771 try: 

772 public_key = ( 

773 key.public_key() 

774 if isinstance(key, EllipticCurvePrivateKey) 

775 else key 

776 ) 

777 public_key.verify(der_sig, msg, ECDSA(self.hash_alg())) 

778 return True 

779 except InvalidSignature: 

780 return False 

781 

782 @overload 

783 @staticmethod 

784 def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[True]) -> JWKDict: ... 

785 

786 @overload 

787 @staticmethod 

788 def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[False] = False) -> str: ... 

789 

790 @staticmethod 

791 def to_jwk(key_obj: AllowedECKeys, as_dict: bool = False) -> JWKDict | str: 

792 if isinstance(key_obj, EllipticCurvePrivateKey): 

793 public_numbers = key_obj.public_key().public_numbers() 

794 elif isinstance(key_obj, EllipticCurvePublicKey): 

795 public_numbers = key_obj.public_numbers() 

796 else: 

797 raise InvalidKeyError("Not a public or private key") 

798 

799 if isinstance(key_obj.curve, SECP256R1): 

800 crv = "P-256" 

801 elif isinstance(key_obj.curve, SECP384R1): 

802 crv = "P-384" 

803 elif isinstance(key_obj.curve, SECP521R1): 

804 crv = "P-521" 

805 elif isinstance(key_obj.curve, SECP256K1): 

806 crv = "secp256k1" 

807 else: 

808 raise InvalidKeyError(f"Invalid curve: {key_obj.curve}") 

809 

810 obj: dict[str, Any] = { 

811 "kty": "EC", 

812 "crv": crv, 

813 "x": to_base64url_uint( 

814 public_numbers.x, 

815 bit_length=key_obj.curve.key_size, 

816 ).decode(), 

817 "y": to_base64url_uint( 

818 public_numbers.y, 

819 bit_length=key_obj.curve.key_size, 

820 ).decode(), 

821 } 

822 

823 if isinstance(key_obj, EllipticCurvePrivateKey): 

824 obj["d"] = to_base64url_uint( 

825 key_obj.private_numbers().private_value, 

826 bit_length=key_obj.curve.key_size, 

827 ).decode() 

828 

829 if as_dict: 

830 return obj 

831 else: 

832 return json.dumps(obj) 

833 

834 @staticmethod 

835 def from_jwk(jwk: str | JWKDict) -> AllowedECKeys: 

836 try: 

837 if isinstance(jwk, str): 

838 obj = json.loads(jwk) 

839 elif isinstance(jwk, dict): 

840 obj = jwk 

841 else: 

842 raise ValueError 

843 except ValueError: 

844 raise InvalidKeyError("Key is not valid JSON") from None 

845 

846 if obj.get("kty") != "EC": 

847 raise InvalidKeyError("Not an Elliptic curve key") from None 

848 

849 if "x" not in obj or "y" not in obj: 

850 raise InvalidKeyError("Not an Elliptic curve key") from None 

851 

852 x = base64url_decode(obj.get("x")) 

853 y = base64url_decode(obj.get("y")) 

854 

855 curve = obj.get("crv") 

856 curve_obj: EllipticCurve 

857 

858 if curve == "P-256": 

859 if len(x) == len(y) == 32: 

860 curve_obj = SECP256R1() 

861 else: 

862 raise InvalidKeyError( 

863 "Coords should be 32 bytes for curve P-256" 

864 ) from None 

865 elif curve == "P-384": 

866 if len(x) == len(y) == 48: 

867 curve_obj = SECP384R1() 

868 else: 

869 raise InvalidKeyError( 

870 "Coords should be 48 bytes for curve P-384" 

871 ) from None 

872 elif curve == "P-521": 

873 if len(x) == len(y) == 66: 

874 curve_obj = SECP521R1() 

875 else: 

876 raise InvalidKeyError( 

877 "Coords should be 66 bytes for curve P-521" 

878 ) from None 

879 elif curve == "secp256k1": 

880 if len(x) == len(y) == 32: 

881 curve_obj = SECP256K1() 

882 else: 

883 raise InvalidKeyError( 

884 "Coords should be 32 bytes for curve secp256k1" 

885 ) 

886 else: 

887 raise InvalidKeyError(f"Invalid curve: {curve}") 

888 

889 public_numbers = EllipticCurvePublicNumbers( 

890 x=int.from_bytes(x, byteorder="big"), 

891 y=int.from_bytes(y, byteorder="big"), 

892 curve=curve_obj, 

893 ) 

894 

895 if "d" not in obj: 

896 return public_numbers.public_key() 

897 

898 d = base64url_decode(obj.get("d")) 

899 if len(d) != len(x): 

900 raise InvalidKeyError( 

901 "D should be {} bytes for curve {}", len(x), curve 

902 ) 

903 

904 return EllipticCurvePrivateNumbers( 

905 int.from_bytes(d, byteorder="big"), public_numbers 

906 ).private_key() 

907 

908 class RSAPSSAlgorithm(RSAAlgorithm): 

909 """ 

910 Performs a signature using RSASSA-PSS with MGF1 

911 """ 

912 

913 def sign(self, msg: bytes, key: RSAPrivateKey) -> bytes: 

914 signature: bytes = key.sign( 

915 msg, 

916 padding.PSS( 

917 mgf=padding.MGF1(self.hash_alg()), 

918 salt_length=self.hash_alg().digest_size, 

919 ), 

920 self.hash_alg(), 

921 ) 

922 return signature 

923 

924 def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool: 

925 try: 

926 key.verify( 

927 sig, 

928 msg, 

929 padding.PSS( 

930 mgf=padding.MGF1(self.hash_alg()), 

931 salt_length=self.hash_alg().digest_size, 

932 ), 

933 self.hash_alg(), 

934 ) 

935 return True 

936 except InvalidSignature: 

937 return False 

938 

939 class OKPAlgorithm(Algorithm): 

940 """ 

941 Performs signing and verification operations using EdDSA 

942 

943 This class requires ``cryptography>=2.6`` to be installed. 

944 """ 

945 

946 _crypto_key_types = cast( 

947 tuple[type[AllowedKeys], ...], 

948 get_args( 

949 Union[ 

950 Ed25519PrivateKey, 

951 Ed25519PublicKey, 

952 Ed448PrivateKey, 

953 Ed448PublicKey, 

954 ] 

955 ), 

956 ) 

957 

958 def __init__(self, **kwargs: Any) -> None: 

959 pass 

960 

961 def prepare_key(self, key: AllowedOKPKeys | str | bytes) -> AllowedOKPKeys: 

962 if not isinstance(key, (str, bytes)): 

963 self.check_crypto_key_type(key) 

964 return key 

965 

966 key_str = key.decode("utf-8") if isinstance(key, bytes) else key 

967 key_bytes = key.encode("utf-8") if isinstance(key, str) else key 

968 

969 loaded_key: PublicKeyTypes | PrivateKeyTypes 

970 if "-----BEGIN PUBLIC" in key_str: 

971 loaded_key = load_pem_public_key(key_bytes) 

972 elif "-----BEGIN PRIVATE" in key_str: 

973 loaded_key = load_pem_private_key(key_bytes, password=None) 

974 elif key_str[0:4] == "ssh-": 

975 loaded_key = load_ssh_public_key(key_bytes) 

976 else: 

977 raise InvalidKeyError("Not a public or private key") 

978 

979 # Explicit check the key to prevent confusing errors from cryptography 

980 self.check_crypto_key_type(loaded_key) 

981 return cast("AllowedOKPKeys", loaded_key) 

982 

983 def sign( 

984 self, msg: str | bytes, key: Ed25519PrivateKey | Ed448PrivateKey 

985 ) -> bytes: 

986 """ 

987 Sign a message ``msg`` using the EdDSA private key ``key`` 

988 :param str|bytes msg: Message to sign 

989 :param Ed25519PrivateKey}Ed448PrivateKey key: A :class:`.Ed25519PrivateKey` 

990 or :class:`.Ed448PrivateKey` isinstance 

991 :return bytes signature: The signature, as bytes 

992 """ 

993 msg_bytes = msg.encode("utf-8") if isinstance(msg, str) else msg 

994 signature: bytes = key.sign(msg_bytes) 

995 return signature 

996 

997 def verify( 

998 self, msg: str | bytes, key: AllowedOKPKeys, sig: str | bytes 

999 ) -> bool: 

1000 """ 

1001 Verify a given ``msg`` against a signature ``sig`` using the EdDSA key ``key`` 

1002 

1003 :param str|bytes sig: EdDSA signature to check ``msg`` against 

1004 :param str|bytes msg: Message to sign 

1005 :param Ed25519PrivateKey|Ed25519PublicKey|Ed448PrivateKey|Ed448PublicKey key: 

1006 A private or public EdDSA key instance 

1007 :return bool verified: True if signature is valid, False if not. 

1008 """ 

1009 try: 

1010 msg_bytes = msg.encode("utf-8") if isinstance(msg, str) else msg 

1011 sig_bytes = sig.encode("utf-8") if isinstance(sig, str) else sig 

1012 

1013 public_key = ( 

1014 key.public_key() 

1015 if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)) 

1016 else key 

1017 ) 

1018 public_key.verify(sig_bytes, msg_bytes) 

1019 return True # If no exception was raised, the signature is valid. 

1020 except InvalidSignature: 

1021 return False 

1022 

1023 @overload 

1024 @staticmethod 

1025 def to_jwk(key: AllowedOKPKeys, as_dict: Literal[True]) -> JWKDict: ... 

1026 

1027 @overload 

1028 @staticmethod 

1029 def to_jwk(key: AllowedOKPKeys, as_dict: Literal[False] = False) -> str: ... 

1030 

1031 @staticmethod 

1032 def to_jwk(key: AllowedOKPKeys, as_dict: bool = False) -> JWKDict | str: 

1033 if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)): 

1034 x = key.public_bytes( 

1035 encoding=Encoding.Raw, 

1036 format=PublicFormat.Raw, 

1037 ) 

1038 crv = "Ed25519" if isinstance(key, Ed25519PublicKey) else "Ed448" 

1039 

1040 obj = { 

1041 "x": base64url_encode(force_bytes(x)).decode(), 

1042 "kty": "OKP", 

1043 "crv": crv, 

1044 } 

1045 

1046 if as_dict: 

1047 return obj 

1048 else: 

1049 return json.dumps(obj) 

1050 

1051 if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)): 

1052 d = key.private_bytes( 

1053 encoding=Encoding.Raw, 

1054 format=PrivateFormat.Raw, 

1055 encryption_algorithm=NoEncryption(), 

1056 ) 

1057 

1058 x = key.public_key().public_bytes( 

1059 encoding=Encoding.Raw, 

1060 format=PublicFormat.Raw, 

1061 ) 

1062 

1063 crv = "Ed25519" if isinstance(key, Ed25519PrivateKey) else "Ed448" 

1064 obj = { 

1065 "x": base64url_encode(force_bytes(x)).decode(), 

1066 "d": base64url_encode(force_bytes(d)).decode(), 

1067 "kty": "OKP", 

1068 "crv": crv, 

1069 } 

1070 

1071 if as_dict: 

1072 return obj 

1073 else: 

1074 return json.dumps(obj) 

1075 

1076 raise InvalidKeyError("Not a public or private key") 

1077 

1078 @staticmethod 

1079 def from_jwk(jwk: str | JWKDict) -> AllowedOKPKeys: 

1080 try: 

1081 if isinstance(jwk, str): 

1082 obj = json.loads(jwk) 

1083 elif isinstance(jwk, dict): 

1084 obj = jwk 

1085 else: 

1086 raise ValueError 

1087 except ValueError: 

1088 raise InvalidKeyError("Key is not valid JSON") from None 

1089 

1090 if obj.get("kty") != "OKP": 

1091 raise InvalidKeyError("Not an Octet Key Pair") 

1092 

1093 curve = obj.get("crv") 

1094 if curve != "Ed25519" and curve != "Ed448": 

1095 raise InvalidKeyError(f"Invalid curve: {curve}") 

1096 

1097 if "x" not in obj: 

1098 raise InvalidKeyError('OKP should have "x" parameter') 

1099 x = base64url_decode(obj.get("x")) 

1100 

1101 try: 

1102 if "d" not in obj: 

1103 if curve == "Ed25519": 

1104 return Ed25519PublicKey.from_public_bytes(x) 

1105 return Ed448PublicKey.from_public_bytes(x) 

1106 d = base64url_decode(obj.get("d")) 

1107 if curve == "Ed25519": 

1108 return Ed25519PrivateKey.from_private_bytes(d) 

1109 return Ed448PrivateKey.from_private_bytes(d) 

1110 except ValueError as err: 

1111 raise InvalidKeyError("Invalid key parameter") from err