Skip to content

Commit

Permalink
Merge pull request #40 from EventStore/timothycoleman/validate-license
Browse files Browse the repository at this point in the history
Add validation to License
  • Loading branch information
timothycoleman authored Apr 26, 2024
2 parents 5fec51d + b1d76f6 commit 5e4d327
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 2 deletions.
59 changes: 59 additions & 0 deletions src/EventStore.Plugins.Tests/Licensing/LicenseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using EventStore.Plugins.Licensing;
using Xunit;

namespace EventStore.Plugins.Tests.Licensing;

public class LicenseTests {
public static (string PublicKey, string PrivateKey) CreateKeyPair() {
using var rsa = RSA.Create(512);
var publicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey());
var privateKey = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
return (publicKey, privateKey);
}

[Fact]
public async Task can_create_and_validate_license() {
var (publicKey, privateKey) = CreateKeyPair();

var license = await License.CreateAsync(publicKey, privateKey, new Dictionary<string, object>() {
{ "foo", "bar"},
});

// check repeatedly because of https://github.com/dotnet/runtime/issues/43087
Assert.True(await license.IsValidAsync(publicKey));
Assert.True(await license.IsValidAsync(publicKey));
Assert.True(await license.IsValidAsync(publicKey));

Assert.Equal("bar", license.Token.Claims.First(c => c.Type == "foo").Value);
}

[Fact]
public async Task detects_incorrect_public_key() {
var (publicKey, privateKey) = CreateKeyPair();
var (publicKey2, _) = CreateKeyPair();

var license = await License.CreateAsync(publicKey, privateKey, new Dictionary<string, object>() {
{ "foo", "bar"},
});

Assert.False(await license.IsValidAsync(publicKey2));
}

[Fact]
public async Task cannot_create_with_inconsistent_keys() {
var (publicKey, _) = CreateKeyPair();
var (_, privateKey) = CreateKeyPair();

var ex = await Assert.ThrowsAsync<Exception>(() =>
License.CreateAsync(publicKey, privateKey, new Dictionary<string, object>() {
{ "foo", "bar"},
}));

Assert.Equal("Token could not be validated", ex.Message);
}
}
61 changes: 59 additions & 2 deletions src/EventStore.Plugins/Licensing/License.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
using Microsoft.IdentityModel.JsonWebTokens;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Generic;

namespace EventStore.Plugins.Licensing;

public record License(JsonWebToken Token);
public record License(JsonWebToken Token) {
public async Task<bool> IsValidAsync(string publicKey) {
var result = await ValidateTokenAsync(publicKey, Token.EncodedToken);
return result.IsValid;
}

public static async Task<License> CreateAsync(
string publicKey,
string privateKey,
IDictionary<string, object> claims) {

using var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateKey), out _);
var tokenHandler = new JsonWebTokenHandler();
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor {
Audience = "esdb",
Issuer = "esdb",
Expires = DateTime.UtcNow + TimeSpan.FromHours(1),
Claims = claims,
SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256),
});

var result = await ValidateTokenAsync(publicKey, token);

if (!result.IsValid)
throw new Exception("Token could not be validated");

if (result.SecurityToken is not JsonWebToken jwt)
throw new Exception("Token is not a JWT");

return new License(jwt);
}

private static async Task<TokenValidationResult> ValidateTokenAsync(string publicKey, string token) {
// not very satisfactory https://github.com/dotnet/runtime/issues/43087
CryptoProviderFactory.Default.CacheSignatureProviders = false;

using var rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicKey), out _);
var result = await new JsonWebTokenHandler().ValidateTokenAsync(
token,
new TokenValidationParameters {
ValidIssuer = "esdb",
ValidAudience = "esdb",
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
});
return result;
}
}

0 comments on commit 5e4d327

Please sign in to comment.