Skip to content

Commit

Permalink
Add implementation for loading signing keys from Key Vault skoruba#533
Browse files Browse the repository at this point in the history
  • Loading branch information
berendhaan committed Jul 24, 2020
1 parent 64047b0 commit 0358a47
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 1 deletion.
7 changes: 7 additions & 0 deletions Skoruba.IdentityServer4.Admin.sln
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skoruba.IdentityServer4.Adm
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skoruba.IdentityServer4.Shared", "src\Skoruba.IdentityServer4.Shared\Skoruba.IdentityServer4.Shared.csproj", "{61B285F0-EE06-4AEE-AAF3-71492CBD11C5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Skoruba.IdentityServer4.STS.KeyVault", "src\Skoruba.IdentityServer4.STS.KeyVault\Skoruba.IdentityServer4.STS.KeyVault.csproj", "{21AAAF8E-1A81-4A9E-8A69-E5375ECB81E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -133,6 +135,10 @@ Global
{61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61B285F0-EE06-4AEE-AAF3-71492CBD11C5}.Release|Any CPU.Build.0 = Release|Any CPU
{21AAAF8E-1A81-4A9E-8A69-E5375ECB81E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21AAAF8E-1A81-4A9E-8A69-E5375ECB81E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21AAAF8E-1A81-4A9E-8A69-E5375ECB81E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21AAAF8E-1A81-4A9E-8A69-E5375ECB81E6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -156,6 +162,7 @@ Global
{0A8A0DB7-0509-4DFB-9201-74398511B481} = {2A514C8F-6A53-41CA-AB41-B644E7BC92A7}
{4D123ACB-ACBD-4E40-AE6B-1B0F79D703B0} = {0BC0CC4E-A0F1-45E8-B41A-AE0FA76BF3E5}
{61B285F0-EE06-4AEE-AAF3-71492CBD11C5} = {EE588CE5-51D0-4E98-A2B3-40EC8E655931}
{21AAAF8E-1A81-4A9E-8A69-E5375ECB81E6} = {63D44665-AC4C-45F4-A2C7-A7DB394F44C4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B3166EDE-037B-4C68-BEBA-5DE9C5E3DB82}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Skoruba.IdentityServer4.STS.Identity.Configuration
{
public class TokenSigningConfiguration
{
public static bool UseAzureKeyVault = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<ProjectReference Include="..\Skoruba.IdentityServer4.Admin.EntityFramework.SqlServer\Skoruba.IdentityServer4.Admin.EntityFramework.SqlServer.csproj" />
<ProjectReference Include="..\Skoruba.IdentityServer4.Admin.EntityFramework\Skoruba.IdentityServer4.Admin.EntityFramework.csproj" />
<ProjectReference Include="..\Skoruba.IdentityServer4.Shared\Skoruba.IdentityServer4.Shared.csproj" />
<ProjectReference Include="..\Skoruba.IdentityServer4.STS.KeyVault\Skoruba.IdentityServer4.STS.KeyVault.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
13 changes: 13 additions & 0 deletions src/Skoruba.IdentityServer4.STS.Identity/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Skoruba.IdentityServer4.STS.Identity.Helpers;
using System;
using Microsoft.AspNetCore.DataProtection;
using Skoruba.IdentityServer4.STS.KeyVault;

namespace Skoruba.IdentityServer4.STS.Identity
{
Expand All @@ -39,6 +40,8 @@ public void ConfigureServices(IServiceCollection services)
.SetApplicationName("Skoruba.IdentityServer4")
.PersistKeysToDbContext<IdentityServerDataProtectionDbContext>();

RegisterKeyVaultSingingKeyFeature(services, Configuration);

// Add email senders which is currently setup for SendGrid and SMTP
services.AddEmailSenders(Configuration);

Expand Down Expand Up @@ -125,6 +128,16 @@ public virtual void RegisterHstsOptions(IServiceCollection services)
});
}

public virtual void RegisterKeyVaultSingingKeyFeature(IServiceCollection services, IConfiguration configuration)
{
if (TokenSigningConfiguration.UseAzureKeyVault)
{
return;
}

services.AddKeyVaultSigningKeyFeature(configuration);
}

protected IRootConfiguration CreateRootConfiguration()
{
var rootConfiguration = new RootConfiguration();
Expand Down
6 changes: 5 additions & 1 deletion src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,9 @@
"AdvancedConfiguration": {
"PublicOrigin": ""
},
"BasePath": ""
"BasePath": "",
"AzureKeyVaultConfiguration": {
"KeyVaultUri": "",
"KeyName": ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Skoruba.IdentityServer4.STS.KeyVault.Configuration
{
public class AzureKeyVaultConfiguration
{
public string KeyVaultUri { get; set; }
public string KeyName { get; set; }
public string KeyIdentifier => $"{KeyVaultUri}/keys/{KeyName}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Skoruba.IdentityServer4.STS.KeyVault.Configuration;
using Skoruba.IdentityServer4.STS.KeyVault.Stores;
using Skoruba.IdentityServer4.STS.KeyVault.Tokens;

namespace Skoruba.IdentityServer4.STS.KeyVault
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddKeyVaultSigningKeyFeature(
this IServiceCollection services,
IConfiguration configuration)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var authenticationCallback = new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback);
var keyVaultClient = new KeyVaultClient(authenticationCallback);
var azureKeyVaultConfiguration = new AzureKeyVaultConfiguration();
configuration.GetSection("AzureKeyVaultConfiguration").Bind(azureKeyVaultConfiguration);

services.AddSingleton<IKeyVaultClient>(keyVaultClient)
.AddSingleton(azureKeyVaultConfiguration)
.AddTransient<ITokenCreationService, KeyVaultTokenCreationService>()
.AddTransient<ISigningCredentialStore, AzureKeyVaultKeyStore>()
.AddTransient<IValidationKeysStore, AzureKeyVaultKeyStore>();

return services;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.0.3" />
<PackageReference Include="IdentityServer4" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.3" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.5" />
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.4.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Tokens;
using Skoruba.IdentityServer4.STS.KeyVault.Configuration;

namespace Skoruba.IdentityServer4.STS.KeyVault.Stores
{
public class AzureKeyVaultKeyStore : ISigningCredentialStore, IValidationKeysStore
{
private const string SigningAlgorithm = "PS256";

private readonly AzureKeyVaultConfiguration _configuration;
private readonly IKeyVaultClient _keyVaultClient;

public AzureKeyVaultKeyStore(
AzureKeyVaultConfiguration configuration,
IKeyVaultClient keyVaultClient)
{
_configuration = configuration;
_keyVaultClient = keyVaultClient;
}

public async Task<SigningCredentials> GetSigningCredentialsAsync()
{
var response = await _keyVaultClient.GetKeyAsync(_configuration.KeyIdentifier);
var key = new RsaSecurityKey(response.Key.ToRSA())
{
KeyId = response.KeyIdentifier.Version
};

return new SigningCredentials(key, SigningAlgorithm);
}

public async Task<IEnumerable<SecurityKeyInfo>> GetValidationKeysAsync()
{
var validationKeys = new List<SecurityKeyInfo>();
var keyItemPage = await _keyVaultClient.GetKeyVersionsAsync(_configuration.KeyVaultUri, _configuration.KeyName);

while (true)
{
var validKeys = keyItemPage.Where(key =>
key.Attributes?.Enabled == true &&
key.Attributes?.Expires > DateTime.UtcNow);

foreach (var keyItem in validKeys)
{
var keyBundle = await _keyVaultClient.GetKeyAsync(keyItem.Identifier.Identifier);
var key = new RsaSecurityKey(keyBundle.Key.ToRSA())
{
KeyId = keyBundle.KeyIdentifier.Version
};

validationKeys.Add(new SecurityKeyInfo
{
Key = key,
SigningAlgorithm = SigningAlgorithm
});
}

if (keyItemPage.NextPageLink == null)
{
break;
}

keyItemPage = await _keyVaultClient.GetKeyVersionsNextAsync(keyItemPage.NextPageLink);
}

return validationKeys;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System.Threading.Tasks;
using IdentityServer4.Configuration;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Azure.KeyVault;
using Microsoft.Extensions.Logging;
using Skoruba.IdentityServer4.STS.KeyVault.Configuration;

namespace Skoruba.IdentityServer4.STS.KeyVault.Tokens
{
public class KeyVaultTokenCreationService : DefaultTokenCreationService
{
private readonly AzureKeyVaultConfiguration _configuration;
private readonly IKeyVaultClient _keyVaultClient;

public KeyVaultTokenCreationService(
AzureKeyVaultConfiguration configuration,
ISystemClock clock,
IKeyMaterialService keys,
IdentityServerOptions options,
ILogger<DefaultTokenCreationService> logger,
IKeyVaultClient keyVaultClient)
: base(clock, keys, options, logger)
{
_configuration = configuration;
_keyVaultClient = keyVaultClient;
}

protected override async Task<string> CreateJwtAsync(JwtSecurityToken jwt)
{
var plaintext = $"{jwt.EncodedHeader}.{jwt.EncodedPayload}";

using var hasher = CryptoHelper.GetHashAlgorithmForSigningAlgorithm(jwt.SignatureAlgorithm);
var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(plaintext));

var response = await _keyVaultClient.SignAsync(_configuration.KeyIdentifier, jwt.SignatureAlgorithm, hash);

return $"{plaintext}.{Base64UrlTextEncoder.Encode(response.Result)}";
}
}
}

0 comments on commit 0358a47

Please sign in to comment.