| | | 1 | | using System.Buffers.Text; |
| | | 2 | | using System.Security.Cryptography; |
| | | 3 | | using System.Text; |
| | | 4 | | using System.Text.Json; |
| | | 5 | | |
| | | 6 | | namespace ArturRios.Jwt; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// RFC 7638 JWK thumbprints: the SHA-256 of a key's required members, in lexicographic order, as |
| | | 10 | | /// compact JSON, base64url-encoded. It is the id an ES256 key carries, so the id is derived from the |
| | | 11 | | /// key itself and never configured. |
| | | 12 | | /// </summary> |
| | | 13 | | internal static class JwkThumbprint |
| | | 14 | | { |
| | | 15 | | /// <summary> |
| | | 16 | | /// The JSON object RFC 7638 hashes: the given members only, ordered by name (ordinal, which is |
| | | 17 | | /// Unicode code point order for the ASCII names a JWK uses), with no whitespace. |
| | | 18 | | /// </summary> |
| | | 19 | | public static string CanonicalJson(IReadOnlyDictionary<string, string> requiredMembers) |
| | | 20 | | { |
| | 130 | 21 | | using var buffer = new MemoryStream(); |
| | | 22 | | |
| | 130 | 23 | | using (var writer = new Utf8JsonWriter(buffer)) |
| | | 24 | | { |
| | 130 | 25 | | writer.WriteStartObject(); |
| | 648 | 26 | | WriteMembers(writer, requiredMembers.OrderBy(member => member.Key, StringComparer.Ordinal)); |
| | 130 | 27 | | writer.WriteEndObject(); |
| | 130 | 28 | | } |
| | | 29 | | |
| | 130 | 30 | | return Encoding.UTF8.GetString(buffer.ToArray()); |
| | 130 | 31 | | } |
| | | 32 | | |
| | | 33 | | /// <summary> |
| | | 34 | | /// Writes string members into the object the writer has open, in the order given. Shared with the |
| | | 35 | | /// key set writer so a JWK is serialized by one piece of code. |
| | | 36 | | /// </summary> |
| | | 37 | | /// <remarks> |
| | | 38 | | /// Every name is a fixed ASCII JWK member name and every value is base64url or a fixed ASCII word, so |
| | | 39 | | /// the writer's default encoder, which escapes <c>+ < > & '</c> and non-ASCII characters, |
| | | 40 | | /// never changes them, and RFC 7638's unescaped serialization is what is hashed. |
| | | 41 | | /// </remarks> |
| | | 42 | | internal static void WriteMembers(Utf8JsonWriter writer, IEnumerable<KeyValuePair<string, string>> members) |
| | | 43 | | { |
| | 1504 | 44 | | foreach (var (name, value) in members) |
| | | 45 | | { |
| | 609 | 46 | | writer.WriteString(name, value); |
| | | 47 | | } |
| | 143 | 48 | | } |
| | | 49 | | |
| | | 50 | | /// <summary>The thumbprint of a key given by its required members.</summary> |
| | | 51 | | public static string Compute(IReadOnlyDictionary<string, string> requiredMembers) => |
| | 129 | 52 | | Base64Url.EncodeToString(SHA256.HashData(Encoding.UTF8.GetBytes(CanonicalJson(requiredMembers)))); |
| | | 53 | | |
| | | 54 | | /// <summary>The thumbprint of a P-256 public key given by its base64url coordinates.</summary> |
| | | 55 | | public static string ForP256(string x, string y) => |
| | 128 | 56 | | Compute(new Dictionary<string, string> |
| | 128 | 57 | | { |
| | 128 | 58 | | ["crv"] = P256.JwkCurveName, |
| | 128 | 59 | | ["kty"] = "EC", |
| | 128 | 60 | | ["x"] = x, |
| | 128 | 61 | | ["y"] = y |
| | 128 | 62 | | }); |
| | | 63 | | } |