Skip to content

Commit f5bda82

Browse files
authored
Improve validation logic (#33)
Added more model validation and fixed `DecodeClaims<T>()` to decode any claims model including auth requests used in callouts.
1 parent 4a9a63c commit f5bda82

File tree

12 files changed

+491
-78
lines changed

12 files changed

+491
-78
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
dotnet-version: |
5151
6.x
5252
8.x
53+
9.x
5354
5455
- name: Restore
5556
working-directory: ./src

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@
66

77
This is a .NET implementation of the JWT library for the NATS ecosystem.
88

9+
> [!CAUTION]
10+
> ### Very Important Disclaimer
11+
>
12+
> This repository provides an API to build NATS JWTs using .NET. However, at
13+
> this time it is _not_ a supported API. Use at your own risk.
14+
>
15+
> One important take away from this project is that the purpose of the library is
16+
> for building JWTs, not to validate them exhaustively. This means that tokens
17+
> generated by this library are expected to be validated by a process that uses
18+
> the [NATS JWT Go library](github.com/nats-io/jwt). As that library is the one
19+
> used by:
20+
>
21+
> - [nats-server](github.com/nats-io/nats-server),
22+
> - [nats-account-server](github.com/nats-io/nats-account-server)
23+
> - [nsc](github.com/nats-io/nsc)
24+
>
25+
> Under that context, ultimate validity of the JWT is delegated to tools or
26+
> servers that use the [NATS JWT Go library](github.com/nats-io/jwt). Use of this
27+
> library implies an agreement with the above disclaimer.
28+
929
## TODO
1030

1131
- [x] Add public API analyzer

src/NATS.Jwt.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F3E7EFAD-B3E
1313
EndProject
1414
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{79B0CD3B-C106-44A2-9A2A-CFDA69A3016A}"
1515
EndProject
16-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".", ".", "{1F24478C-D5CB-4A58-A74E-6371F7F95C01}"
16+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1F24478C-D5CB-4A58-A74E-6371F7F95C01}"
1717
ProjectSection(SolutionItems) = preProject
1818
README.md = ..\README.md
1919
LICENSE = ..\LICENSE

src/NATS.Jwt/EncodingUtils.cs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Text;
65

76
namespace NATS.Jwt
87
{
@@ -33,26 +32,16 @@ public static string ToBase64UrlEncoded(byte[] bytes)
3332
/// </summary>
3433
/// <param name="encodedString">The base64 URL-encoded string to decode.</param>
3534
/// <returns>The decoded string.</returns>
36-
public static string FromBase64UrlEncoded(string encodedString)
35+
public static byte[] FromBase64UrlEncoded(string encodedString)
3736
{
38-
string replace = encodedString.Replace("_", "/").Replace("-", "+");
39-
try
37+
string base64 = encodedString.Replace("_", "/").Replace("-", "+");
38+
switch (base64.Length % 4)
4039
{
41-
return Encoding.ASCII.GetString(Convert.FromBase64String(replace));
42-
}
43-
catch (FormatException)
44-
{
45-
// maybe wasn't padded correctly?
46-
try
47-
{
48-
return Encoding.ASCII.GetString(Convert.FromBase64String(replace + "="));
49-
}
50-
catch (FormatException)
51-
{
52-
// maybe wasn't padded correctly?
53-
return Encoding.ASCII.GetString(Convert.FromBase64String(replace + "=="));
54-
}
40+
case 2: base64 += "=="; break;
41+
case 3: base64 += "="; break;
5542
}
43+
44+
return Convert.FromBase64String(base64);
5645
}
5746
}
5847
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) The NATS Authors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using NATS.Jwt.Models;
7+
using NATS.NKeys;
8+
9+
namespace NATS.Jwt;
10+
11+
/// <summary>
12+
/// Provides extension methods for validating JSON Web Token (JWT) models.
13+
/// </summary>
14+
public static class ModelValidationExtensions
15+
{
16+
private static readonly Dictionary<Type, PrefixByte[]> ExpectedClaimsPrefixes = new()
17+
{
18+
{ typeof(NatsAccountClaims), [PrefixByte.Account, PrefixByte.Operator] },
19+
{ typeof(NatsActivationClaims), [PrefixByte.Account, PrefixByte.Operator] },
20+
{ typeof(NatsAuthorizationRequestClaims), [PrefixByte.Server] },
21+
{ typeof(NatsAuthorizationResponseClaims), [PrefixByte.Account] },
22+
{ typeof(NatsGenericClaims), [] },
23+
{ typeof(NatsOperatorClaims), [PrefixByte.Operator] },
24+
{ typeof(NatsUserClaims), [PrefixByte.Account] },
25+
};
26+
27+
/// <summary>
28+
/// Determines the expected prefix bytes for the given JWT claims data.
29+
/// </summary>
30+
/// <param name="claims">The JWT claims data whose expected prefixes need to be determined.</param>
31+
/// <returns>An array of PrefixByte values indicating the expected prefixes for the specified claims.</returns>
32+
/// <exception cref="NatsJwtException">Thrown if the expected prefixes cannot be determined for the given claims type.</exception>
33+
public static PrefixByte[] ExpectedPrefixes(this JwtClaimsData claims)
34+
{
35+
if (!ExpectedClaimsPrefixes.TryGetValue(claims.GetType(), out var prefixes))
36+
{
37+
throw new NatsJwtException($"Can't find prefixes for {claims.GetType().Name}");
38+
}
39+
40+
return prefixes;
41+
}
42+
43+
/// <summary>
44+
/// Validates the specified JWT header to ensure its type and algorithm are supported.
45+
/// </summary>
46+
/// <param name="header">The JWT header to validate.</param>
47+
/// <exception cref="NatsJwtException">Invalid JWT header.</exception>
48+
public static void Validate(this JwtHeader header)
49+
{
50+
if (!string.Equals("JWT", header.Type, StringComparison.InvariantCultureIgnoreCase))
51+
{
52+
throw new NatsJwtException($"Invalid JWT header: not supported type {header.Type}");
53+
}
54+
55+
if (!string.Equals("ed25519", header.Algorithm, StringComparison.InvariantCultureIgnoreCase)
56+
&& !string.Equals(NatsJwt.AlgorithmNkey, header.Algorithm, StringComparison.InvariantCultureIgnoreCase))
57+
{
58+
throw new NatsJwtException($"Invalid JWT header: unexpected {header.Algorithm} algorithm");
59+
}
60+
}
61+
}

src/NATS.Jwt/NATS.Jwt.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
</ItemGroup>
2727

2828
<ItemGroup>
29+
<PackageReference Include="NATS.NKeys" Version="1.0.0-preview.3" />
2930
<PackageReference Include="System.Text.Json" Version="8.0.5"/>
30-
<PackageReference Include="NATS.NKeys" Version="1.0.0-preview.1"/>
3131
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" ExcludeAssets="Compile" PrivateAssets="All"/>
3232
</ItemGroup>
3333

0 commit comments

Comments
 (0)