|
26 | 26 | _RSA_PUBLIC_EXPONENT = 65537 |
27 | 27 |
|
28 | 28 |
|
29 | | -def _int_to_base64url(num: int) -> str: |
30 | | - """Encode integer to base64url without padding.""" |
31 | | - size = (num.bit_length() + _BITS_PER_BYTE - 1) // _BITS_PER_BYTE |
| 29 | +def _int_to_base64url(num: int, *, fixed_length: int | None = None) -> str: |
| 30 | + """Encode integer to base64url without padding. |
| 31 | +
|
| 32 | + Args: |
| 33 | + num: Non-negative integer to encode. |
| 34 | + fixed_length: If set, pad the big-endian representation to exactly |
| 35 | + this many bytes. Required for EC coordinates where RFC 7518 §6.2.1 |
| 36 | + mandates a fixed octet length (e.g. 32 for P-256). |
| 37 | + """ |
| 38 | + if fixed_length is not None: |
| 39 | + size = fixed_length |
| 40 | + else: |
| 41 | + size = (num.bit_length() + _BITS_PER_BYTE - 1) // _BITS_PER_BYTE |
32 | 42 | data = num.to_bytes(size, "big") |
33 | 43 | return base64.urlsafe_b64encode(data).decode().rstrip("=") |
34 | 44 |
|
@@ -107,11 +117,13 @@ def _key_to_jwk(key: EllipticCurvePrivateKey | RSAPrivateKey) -> dict[str, Any]: |
107 | 117 | if isinstance(key, EllipticCurvePrivateKey): |
108 | 118 | pub = key.public_key() |
109 | 119 | nums = pub.public_numbers() |
| 120 | + # P-256 coordinates must be exactly 32 bytes per RFC 7518 §6.2.1.2 |
| 121 | + ec_coord_length = 32 |
110 | 122 | return { |
111 | 123 | "kty": "EC", |
112 | 124 | "crv": "P-256", |
113 | | - "x": _int_to_base64url(nums.x), |
114 | | - "y": _int_to_base64url(nums.y), |
| 125 | + "x": _int_to_base64url(nums.x, fixed_length=ec_coord_length), |
| 126 | + "y": _int_to_base64url(nums.y, fixed_length=ec_coord_length), |
115 | 127 | } |
116 | 128 | # key is RSAPrivateKey (union type) |
117 | 129 | pub = key.public_key() |
|
0 commit comments