Skip to content

Commit

Permalink
Merge pull request #227 from Aguafrommars/user/ole
Browse files Browse the repository at this point in the history
Keys rotation refactoring
  • Loading branch information
aguacongas authored Sep 30, 2020
2 parents b287be3 + 2ce2bbe commit 20b6ab4
Show file tree
Hide file tree
Showing 69 changed files with 3,219 additions and 164 deletions.
2 changes: 2 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ deploy:
draft: true
prerelease: true
release: $(Version)
artifact: /.*\.zip/
on:
branch:
- /preview\/*/
Expand All @@ -59,6 +60,7 @@ deploy:
auth_token: $(GH_TOKEN)
draft: true
release: $(Version)
artifact: /.*\.zip/
on:
branch:
- /release\/*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public static IDataProtectionBuilder ConfigureDataProtection(this IDataProtectio
break;
case StorageKind.Redis:
var redis = ConnectionMultiplexer.Connect(dataProtectionsOptions.StorageConnectionString);
if (string.IsNullOrEmpty(dataProtectionsOptions.RedisKey))
{
builder.PersistKeysToStackExchangeRedis(redis);
break;
}
builder.PersistKeysToStackExchangeRedis(redis, dataProtectionsOptions.RedisKey);
break;
case StorageKind.Registry:
Expand All @@ -52,17 +57,7 @@ public static IDataProtectionBuilder ConfigureDataProtection(this IDataProtectio
builder.ProtectKeysWithDpapi(protectOptions.WindowsDPAPILocalMachine);
break;
case KeyProtectionKind.WindowsDpApiNg:
if (!string.IsNullOrEmpty(protectOptions.WindowsDpApiNgCerticate))
{
builder.ProtectKeysWithDpapiNG($"CERTIFICATE=HashId:{protectOptions.WindowsDpApiNgCerticate}", flags: DpapiNGProtectionDescriptorFlags.None);
break;
}
if (!string.IsNullOrEmpty(protectOptions.WindowsDpApiNgSid))
{
builder.ProtectKeysWithDpapiNG($"SID={protectOptions.WindowsDpApiNgSid}", flags: DpapiNGProtectionDescriptorFlags.None);
break;
}
builder.ProtectKeysWithDpapiNG();
ConfigureWindowsDpApiNg(builder, protectOptions);
break;
case KeyProtectionKind.X509:
if (!string.IsNullOrEmpty(protectOptions.X509CertificatePath))
Expand All @@ -78,5 +73,20 @@ public static IDataProtectionBuilder ConfigureDataProtection(this IDataProtectio

return builder;
}

private static void ConfigureWindowsDpApiNg(IDataProtectionBuilder builder, KeyProtectionOptions protectOptions)
{
if (!string.IsNullOrEmpty(protectOptions.WindowsDpApiNgCerticate))
{
builder.ProtectKeysWithDpapiNG($"CERTIFICATE=HashId:{protectOptions.WindowsDpApiNgCerticate}", flags: DpapiNGProtectionDescriptorFlags.None);
return;
}
if (!string.IsNullOrEmpty(protectOptions.WindowsDpApiNgSid))
{
builder.ProtectKeysWithDpapiNG($"SID={protectOptions.WindowsDpApiNgSid}", flags: DpapiNGProtectionDescriptorFlags.None);
return;
}
builder.ProtectKeysWithDpapiNG();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Aguacongas.IdentityServer.Admin.Configuration;
using Aguacongas.IdentityServer.EntityFramework.Store;
using Aguacongas.IdentityServer.KeysRotation;
using Aguacongas.IdentityServer.KeysRotation.Extentions;
using Aguacongas.TheIdServer.Models;
using Microsoft.Extensions.Configuration;
using StackExchange.Redis;
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Extensions.DependencyInjection
{
public static class IdentityServerBuilderExtensions
{
public static IIdentityServerBuilder ConfigureKeysRotation(this IIdentityServerBuilder identityServerBuilder, IConfiguration configuration)
{
var builder = identityServerBuilder.AddKeysRotation(options => configuration.GetSection(nameof(KeyRotationOptions))?.Bind(options))
.AddRsaEncryptorConfiguration(options => configuration.GetSection(nameof(RsaEncryptorConfiguration))?.Bind(options));
var dataProtectionsOptions = configuration.Get<DataProtectionOptions>();
switch (dataProtectionsOptions.StorageKind)
{
case StorageKind.AzureStorage:
builder.PersistKeysToAzureBlobStorage(new Uri(dataProtectionsOptions.StorageConnectionString));
break;
case StorageKind.EntityFramework:
builder.PersistKeysToDbContext<OperationalDbContext>();
break;
case StorageKind.FileSytem:
builder.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionsOptions.StorageConnectionString));
break;
case StorageKind.Redis:
var redis = ConnectionMultiplexer.Connect(dataProtectionsOptions.StorageConnectionString);
if (string.IsNullOrEmpty(dataProtectionsOptions.RedisKey))
{
builder.PersistKeysToStackExchangeRedis(redis);
break;
}
builder.PersistKeysToStackExchangeRedis(redis, dataProtectionsOptions.RedisKey);
break;
}
var protectOptions = dataProtectionsOptions.KeyProtectionOptions;
if (protectOptions != null)
{
switch (protectOptions.KeyProtectionKind)
{
case KeyProtectionKind.AzureKeyVault:
builder.ProtectKeysWithAzureKeyVault(protectOptions.AzureKeyVaultKeyId, protectOptions.AzureKeyVaultClientId, protectOptions.AzureKeyVaultClientSecret);
break;
case KeyProtectionKind.X509:
if (!string.IsNullOrEmpty(protectOptions.X509CertificatePath))
{
var certificate = SigningKeysLoader.LoadFromFile(protectOptions.X509CertificatePath, protectOptions.X509CertificatePassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet);
builder.ProtectKeysWithCertificate(certificate);
break;
}
builder.ProtectKeysWithCertificate(protectOptions.X509CertificateThumbprint);
break;
}
}
return identityServerBuilder;
}
}
}
18 changes: 16 additions & 2 deletions src/Aguacongas.TheIdServer/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Aguacongas.AspNetCore.Authentication;
using Aguacongas.IdentityServer;
using Aguacongas.IdentityServer.Abstractions;
using Aguacongas.IdentityServer.Admin.Configuration;
using Aguacongas.IdentityServer.Admin.Options;
using Aguacongas.IdentityServer.Admin.Services;
using Aguacongas.IdentityServer.EntityFramework.Store;
Expand Down Expand Up @@ -75,9 +76,10 @@ public void ConfigureServices(IServiceCollection services)
.AddOidcStateDataFormatterCache()
.AddIdentityServer(Configuration.GetSection(nameof(IdentityServerOptions)))
.AddAspNetIdentity<ApplicationUser>()
.AddSigningCredentials()
.AddDynamicClientRegistration();

ConfigureSigningCredentials(identityBuilder);

identityBuilder.AddJwtRequestUriHttpClient();

if (isProxy)
Expand Down Expand Up @@ -147,7 +149,19 @@ public void ConfigureServices(IServiceCollection services)
.AddEntityFrameworkStore<ConfigurationDbContext>();
}
services.AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("Identity", "/Account"));
}
}

private void ConfigureSigningCredentials(IIdentityServerBuilder identityBuilder)
{
if (Configuration.GetSection("IdentityServer:Key")?.Get<KeyDefinition>()?.Type == KeyKinds.KeysRotation)
{
identityBuilder.ConfigureKeysRotation(Configuration.GetSection("IdentityServer:Key"));
}
else
{
identityBuilder.AddSigningCredentials();
}
}

[SuppressMessage("Usage", "ASP0001:Authorization middleware is incorrectly configured.", Justification = "<Pending>")]
public void Configure(IApplicationBuilder app)
Expand Down
9 changes: 7 additions & 2 deletions src/Aguacongas.TheIdServer/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"IdentityServer": {
"Key": {
"Type": "Development"
}
"Type": "KeysRotation",
"StorageKind": "EntityFramework",
"KeyRotationOptions": {
"NewKeyLifetime": "14.00:00:00",
"KeyPropagationWindow": "30.00:00:00"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,66 @@

namespace Aguacongas.IdentityServer.Admin.Configuration
{
internal class KeyDefinition
/// <summary>
/// Signing key definition
/// </summary>
public class KeyDefinition
{
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>
/// The type.
/// </value>
public KeyKinds? Type { get; set; }
/// <summary>
/// Gets or sets the persisted.
/// </summary>
/// <value>
/// The persisted.
/// </value>
public bool? Persisted { get; set; }
/// <summary>
/// Gets or sets the file path.
/// </summary>
/// <value>
/// The file path.
/// </value>
public string FilePath { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
/// <value>
/// The password.
/// </value>
public string Password { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the store location.
/// </summary>
/// <value>
/// The store location.
/// </value>
public StoreLocation? StoreLocation { get; set; }
/// <summary>
/// Gets or sets the name of the store.
/// </summary>
/// <value>
/// The name of the store.
/// </value>
public string StoreName { get; set; }
/// <summary>
/// Gets or sets the storage flags.
/// </summary>
/// <value>
/// The storage flags.
/// </value>
public string StorageFlags { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@
// Copyright (c) 2020 @Olivier Lefebvre
namespace Aguacongas.IdentityServer.Admin.Configuration
{
internal enum KeyKinds
/// <summary>
/// Signing key definition kinds
/// </summary>
public enum KeyKinds
{
/// <summary>
/// From X509 file
/// </summary>
File,
/// <summary>
/// Temp key for development
/// </summary>
Development,
Store
/// <summary>
/// From X509 store
/// </summary>
Store,
/// <summary>
/// From keys rotation
/// </summary>
KeysRotation
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2020 @Olivier Lefebvre
using Aguacongas.IdentityServer.KeysRotation;
using Aguacongas.IdentityServer.KeysRotation.EntityFrameworkCore;
using Aguacongas.IdentityServer.Store.Entity;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -30,7 +30,7 @@ public OperationalDbContext(DbContextOptions<OperationalDbContext> options):base

public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }

public DbSet<DataProtectionKey> KeyRotationKeys { get; set; }
public DbSet<KeyRotationKey> KeyRotationKeys { get; set; }

public override int SaveChanges()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="3.1.8" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="3.1.8" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="3.1.8" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.8" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="5.2.8" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.7.1" />
<PackageReference Include="StackExchange.Redis" Version="2.1.58" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;

namespace Aguacongas.IdentityServer.KeysRotation.AzureKeyVault
{
internal class AzureKeyVaultXmlDecryptor : IXmlDecryptor
{
private readonly IKeyVaultWrappingClient _client;

public AzureKeyVaultXmlDecryptor(IServiceProvider serviceProvider)
{
_client = serviceProvider.GetService<IKeyVaultWrappingClient>();
}

public XElement Decrypt(XElement encryptedElement)
{
return DecryptAsync(encryptedElement).GetAwaiter().GetResult();
}

private async Task<XElement> DecryptAsync(XElement encryptedElement)
{
var kid = (string)encryptedElement.Element("kid");
var symmetricKey = Convert.FromBase64String((string)encryptedElement.Element("key"));
var symmetricIV = Convert.FromBase64String((string)encryptedElement.Element("iv"));

var encryptedValue = Convert.FromBase64String((string)encryptedElement.Element("value"));

var result = await _client.UnwrapKeyAsync(kid, AzureKeyVaultXmlEncryptor.DefaultKeyEncryption, symmetricKey).ConfigureAwait(false);

byte[] decryptedValue;
using (var symmetricAlgorithm = AzureKeyVaultXmlEncryptor.DefaultSymmetricAlgorithmFactory())
{
using var decryptor = symmetricAlgorithm.CreateDecryptor(result.Result, symmetricIV);
decryptedValue = decryptor.TransformFinalBlock(encryptedValue, 0, encryptedValue.Length);
}

using var memoryStream = new MemoryStream(decryptedValue);
return XElement.Load(memoryStream);
}
}
}
Loading

0 comments on commit 20b6ab4

Please sign in to comment.