| | | 1 | | using System.Globalization; |
| | | 2 | | using System.Numerics; |
| | | 3 | | using System.Security.Cryptography; |
| | | 4 | | |
| | | 5 | | namespace ArturRios.Jwt; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// The NIST P-256 curve (secp256r1, prime256v1): its identifiers, and the check that a point lies on it. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// The check is explicit rather than left to the platform's import. A public key off the curve is the |
| | | 12 | | /// precondition of invalid-curve attacks, and whether an import rejects one differs between platforms |
| | | 13 | | /// and versions; the curve equation does not. |
| | | 14 | | /// </remarks> |
| | | 15 | | internal static class P256 |
| | | 16 | | { |
| | | 17 | | /// <summary>The curve's object identifier.</summary> |
| | | 18 | | public const string Oid = "1.2.840.10045.3.1.7"; |
| | | 19 | | |
| | | 20 | | /// <summary>The curve's name in a JWK's <c>crv</c> member.</summary> |
| | | 21 | | public const string JwkCurveName = "P-256"; |
| | | 22 | | |
| | | 23 | | /// <summary>The length of each coordinate, and of the private scalar, in bytes.</summary> |
| | | 24 | | public const int CoordinateBytes = 32; |
| | | 25 | | |
| | | 26 | | /// <summary>The field prime p = 2^256 − 2^224 + 2^192 + 2^96 − 1.</summary> |
| | 1 | 27 | | private static readonly BigInteger s_p = BigInteger.Parse( |
| | 1 | 28 | | "0ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", NumberStyles.HexNumber); |
| | | 29 | | |
| | | 30 | | /// <summary>The curve coefficient b of y² = x³ − 3x + b.</summary> |
| | 1 | 31 | | private static readonly BigInteger s_b = BigInteger.Parse( |
| | 1 | 32 | | "05ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", NumberStyles.HexNumber); |
| | | 33 | | |
| | | 34 | | /// <summary>The curve, as the platform names it.</summary> |
| | 93 | 35 | | public static ECCurve Curve => ECCurve.NamedCurves.nistP256; |
| | | 36 | | |
| | | 37 | | /// <summary> |
| | | 38 | | /// Whether <paramref name="curve"/> is P-256. Platforms report it by OID or by one of its friendly |
| | | 39 | | /// names, so any of them counts. |
| | | 40 | | /// </summary> |
| | | 41 | | public static bool IsNamed(ECCurve curve) => |
| | 45 | 42 | | curve.IsNamed && |
| | 45 | 43 | | (curve.Oid.Value == Oid || |
| | 45 | 44 | | curve.Oid.FriendlyName is "nistP256" or "ECDSA_P256" or "prime256v1" or "secp256r1"); |
| | | 45 | | |
| | | 46 | | /// <summary> |
| | | 47 | | /// Whether (<paramref name="x"/>, <paramref name="y"/>), big-endian and 32 bytes each, satisfies |
| | | 48 | | /// y² = x³ − 3x + b over P-256's field, with both coordinates below the field prime. |
| | | 49 | | /// </summary> |
| | | 50 | | public static bool IsOnCurve(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y) |
| | | 51 | | { |
| | 104 | 52 | | if (x.Length != CoordinateBytes || y.Length != CoordinateBytes) |
| | | 53 | | { |
| | 5 | 54 | | return false; |
| | | 55 | | } |
| | | 56 | | |
| | 99 | 57 | | var px = new BigInteger(x, isUnsigned: true, isBigEndian: true); |
| | 99 | 58 | | var py = new BigInteger(y, isUnsigned: true, isBigEndian: true); |
| | | 59 | | |
| | 99 | 60 | | if (px >= s_p || py >= s_p) |
| | | 61 | | { |
| | 1 | 62 | | return false; |
| | | 63 | | } |
| | | 64 | | |
| | 98 | 65 | | var left = BigInteger.ModPow(py, 2, s_p); |
| | 98 | 66 | | var right = (BigInteger.ModPow(px, 3, s_p) - 3 * px + s_b) % s_p; |
| | | 67 | | |
| | 98 | 68 | | if (right.Sign < 0) |
| | | 69 | | { |
| | 72 | 70 | | right += s_p; |
| | | 71 | | } |
| | | 72 | | |
| | 98 | 73 | | return left == right; |
| | | 74 | | } |
| | | 75 | | } |