From 61ad43d4672de605ef1e7f7a493695cf1efdffb4 Mon Sep 17 00:00:00 2001
From: janskoruba <jan@skoruba.com>
Date: Sat, 26 Sep 2020 15:09:39 +0200
Subject: [PATCH 1/5] Add loading certificates from Azure Key Vault

---
 .../IdentityServerBuilderExtensions.cs        |  19 ++++
 .../appsettings.json                          |  19 +++-
 .../Common/AzureKeyVaultConfiguration.cs      |  19 ++++
 .../Common}/CertificateConfiguration.cs       |   6 +-
 .../Helpers/AzureKeyVaultHelpers.cs           |  27 +++++
 .../Services/AzureKeyVaultService.cs          | 105 ++++++++++++++++++
 .../Skoruba.IdentityServer4.Shared.csproj     |   2 +
 7 files changed, 192 insertions(+), 5 deletions(-)
 create mode 100644 src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs
 rename src/{Skoruba.IdentityServer4.STS.Identity/Configuration => Skoruba.IdentityServer4.Shared/Configuration/Common}/CertificateConfiguration.cs (81%)
 create mode 100644 src/Skoruba.IdentityServer4.Shared/Helpers/AzureKeyVaultHelpers.cs
 create mode 100644 src/Skoruba.IdentityServer4.Shared/Services/AzureKeyVaultService.cs

diff --git a/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs b/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs
index 2b9e68668..e3559b92f 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs
+++ b/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs
@@ -5,6 +5,8 @@
 using System.Security.Cryptography.X509Certificates;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
+using Skoruba.IdentityServer4.Shared.Configuration.Common;
+using Skoruba.IdentityServer4.Shared.Helpers;
 using Skoruba.IdentityServer4.STS.Identity.Configuration;
 
 namespace Skoruba.IdentityServer4.STS.Identity.Helpers
@@ -27,6 +29,7 @@ public static class IdentityServerBuilderExtensions
         public static IIdentityServerBuilder AddCustomSigningCredential(this IIdentityServerBuilder builder, IConfiguration configuration)
         {
             var certificateConfiguration = configuration.GetSection(nameof(CertificateConfiguration)).Get<CertificateConfiguration>();
+            var azureKeyVaultConfiguration = configuration.GetSection(nameof(AzureKeyVaultConfiguration)).Get<AzureKeyVaultConfiguration>();
 
             if (certificateConfiguration.UseSigningCertificateThumbprint)
             {
@@ -66,6 +69,12 @@ public static IIdentityServerBuilder AddCustomSigningCredential(this IIdentitySe
 
                 builder.AddSigningCredential(certificate);
             }
+            else if (certificateConfiguration.UseSigningCertificateForAzureKeyVault)
+            {
+                var x509Certificate2Certs = AzureKeyVaultHelpers.GetCertificates(azureKeyVaultConfiguration).GetAwaiter().GetResult();
+
+                builder.AddSigningCredential(x509Certificate2Certs.ActiveCertificate);
+            }
             else if (certificateConfiguration.UseSigningCertificatePfxFile)
             {
                 if (string.IsNullOrWhiteSpace(certificateConfiguration.SigningCertificatePfxFilePath))
@@ -112,6 +121,7 @@ public static IIdentityServerBuilder AddCustomSigningCredential(this IIdentitySe
         public static IIdentityServerBuilder AddCustomValidationKey(this IIdentityServerBuilder builder, IConfiguration configuration)
         {
             var certificateConfiguration = configuration.GetSection(nameof(CertificateConfiguration)).Get<CertificateConfiguration>();
+            var azureKeyVaultConfiguration = configuration.GetSection(nameof(AzureKeyVaultConfiguration)).Get<AzureKeyVaultConfiguration>();
 
             if (certificateConfiguration.UseValidationCertificateThumbprint)
             {
@@ -134,6 +144,15 @@ public static IIdentityServerBuilder AddCustomValidationKey(this IIdentityServer
                 builder.AddValidationKey(certificate);
 
             }
+            else if (certificateConfiguration.UseValidationCertificateForAzureKeyVault)
+            {
+                var x509Certificate2Certs = AzureKeyVaultHelpers.GetCertificates(azureKeyVaultConfiguration).GetAwaiter().GetResult();
+
+                if (x509Certificate2Certs.SecondaryCertificate != null)
+                {
+                    builder.AddValidationKey(x509Certificate2Certs.SecondaryCertificate);
+                }
+            }
             else if (certificateConfiguration.UseValidationCertificatePfxFile)
             {
                 if (string.IsNullOrWhiteSpace(certificateConfiguration.ValidationCertificatePfxFilePath))
diff --git a/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json b/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
index c156f7bcb..b6b7eaddb 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
+++ b/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
@@ -27,7 +27,18 @@
         "ValidationCertificatePfxFilePassword": "",
 
         "UseValidationCertificateThumbprint": false,
-        "ValidationCertificateThumbprint": ""
+        "ValidationCertificateThumbprint": "",
+
+        "UseSigningCertificateForAzureKeyVault": false,
+        "UseValidationCertificateForAzureKeyVault": false
+    },
+    "AzureKeyVaultConfiguration": {
+        "AzureKeyVaultEndpoint": "",
+        "ClientId": "",
+        "ClientSecret": "",
+        "UseClientCredentials": true,
+        "IdentityServerCertificateName": "",
+        "DataProtectionKeyIdentifier": ""
     },
     "RegisterConfiguration": {
         "Enabled": true
@@ -67,13 +78,13 @@
     "BasePath": "",
     "IdentityOptions": {
         "Password": {
-          "RequiredLength": 8
+            "RequiredLength": 8
         },
         "User": {
-          "RequireUniqueEmail": true
+            "RequireUniqueEmail": true
         },
         "SignIn": {
-          "RequireConfirmedAccount": false
+            "RequireConfirmedAccount": false
         }
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs
new file mode 100644
index 000000000..1517faa05
--- /dev/null
+++ b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs
@@ -0,0 +1,19 @@
+using System.Diagnostics;
+
+namespace Skoruba.IdentityServer4.Shared.Configuration.Common
+{
+    public class AzureKeyVaultConfiguration
+    {
+        public string AzureKeyVaultEndpoint { get; set; }
+
+        public string ClientId { get; set; }
+
+        public string ClientSecret { get; set; }
+
+        public bool UseClientCredentials { get; set; }
+
+        public string IdentityServerCertificateName { get; set; }
+
+        public string DataProtectionKeyIdentifier { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.STS.Identity/Configuration/CertificateConfiguration.cs b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/CertificateConfiguration.cs
similarity index 81%
rename from src/Skoruba.IdentityServer4.STS.Identity/Configuration/CertificateConfiguration.cs
rename to src/Skoruba.IdentityServer4.Shared/Configuration/Common/CertificateConfiguration.cs
index 99e8f00c1..cf9783023 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/Configuration/CertificateConfiguration.cs
+++ b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/CertificateConfiguration.cs
@@ -1,4 +1,4 @@
-namespace Skoruba.IdentityServer4.STS.Identity.Configuration
+namespace Skoruba.IdentityServer4.Shared.Configuration.Common
 {
     public class CertificateConfiguration
     {
@@ -26,5 +26,9 @@ public class CertificateConfiguration
         public string ValidationCertificatePfxFilePath { get; set; }
 
         public string ValidationCertificatePfxFilePassword { get; set; }
+
+        public bool UseSigningCertificateForAzureKeyVault { get; set; }
+
+        public bool UseValidationCertificateForAzureKeyVault { get; set; }
     }
 }
diff --git a/src/Skoruba.IdentityServer4.Shared/Helpers/AzureKeyVaultHelpers.cs b/src/Skoruba.IdentityServer4.Shared/Helpers/AzureKeyVaultHelpers.cs
new file mode 100644
index 000000000..0b54103c3
--- /dev/null
+++ b/src/Skoruba.IdentityServer4.Shared/Helpers/AzureKeyVaultHelpers.cs
@@ -0,0 +1,27 @@
+// Original file comes from: https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate
+// Modified by Jan Škoruba
+
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Skoruba.IdentityServer4.Shared.Configuration.Common;
+using Skoruba.IdentityServer4.Shared.Services;
+
+namespace Skoruba.IdentityServer4.Shared.Helpers
+{
+    public class AzureKeyVaultHelpers
+    {
+        public static async Task<(X509Certificate2 ActiveCertificate, X509Certificate2 SecondaryCertificate)> GetCertificates(AzureKeyVaultConfiguration certificateConfiguration)
+        {
+            (X509Certificate2 ActiveCertificate, X509Certificate2 SecondaryCertificate) certs = (null, null);
+
+            if (!string.IsNullOrEmpty(certificateConfiguration.AzureKeyVaultEndpoint))
+            {
+                var keyVaultCertificateService = new AzureKeyVaultService(certificateConfiguration);
+
+                certs = await keyVaultCertificateService.GetCertificatesFromKeyVault().ConfigureAwait(false);
+            }
+
+            return certs;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Shared/Services/AzureKeyVaultService.cs b/src/Skoruba.IdentityServer4.Shared/Services/AzureKeyVaultService.cs
new file mode 100644
index 000000000..8115e36ea
--- /dev/null
+++ b/src/Skoruba.IdentityServer4.Shared/Services/AzureKeyVaultService.cs
@@ -0,0 +1,105 @@
+// Original file comes from: https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate
+// Modified by Jan Škoruba
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Azure.KeyVault.Models;
+using Microsoft.Azure.Services.AppAuthentication;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using Skoruba.IdentityServer4.Shared.Configuration.Common;
+
+namespace Skoruba.IdentityServer4.Shared.Services
+{
+    public class AzureKeyVaultService
+    {
+        private readonly AzureKeyVaultConfiguration _azureKeyVaultConfiguration;
+
+        public AzureKeyVaultService(AzureKeyVaultConfiguration azureKeyVaultConfiguration)
+        {
+            if (azureKeyVaultConfiguration == null)
+            {
+                throw new ArgumentException("missing azureKeyVaultConfiguration");
+            }
+
+            if (string.IsNullOrEmpty(azureKeyVaultConfiguration.AzureKeyVaultEndpoint))
+            {
+                throw new ArgumentException("missing keyVaultEndpoint");
+            }
+
+            _azureKeyVaultConfiguration = azureKeyVaultConfiguration;
+        }
+
+        public async Task<(X509Certificate2 ActiveCertificate, X509Certificate2 SecondaryCertificate)> GetCertificatesFromKeyVault()
+        {
+            (X509Certificate2 ActiveCertificate, X509Certificate2 SecondaryCertificate) certs = (null, null);
+
+            var keyVaultClient = BuildKeyVaultClient();
+
+            var certificateItems = await GetAllEnabledCertificateVersionsAsync(keyVaultClient);
+            var item = certificateItems.FirstOrDefault();
+            if (item != null)
+            {
+                certs.ActiveCertificate = await GetCertificateAsync(item.Identifier.Identifier, keyVaultClient);
+            }
+
+            if (certificateItems.Count > 1)
+            {
+                certs.SecondaryCertificate = await GetCertificateAsync(certificateItems[1].Identifier.Identifier, keyVaultClient);
+            }
+
+            return certs;
+        }
+
+        /// <summary>
+        /// Build KeyVaultClient according to authentication method
+        /// </summary>
+        /// <returns></returns>
+        public IKeyVaultClient BuildKeyVaultClient()
+        {
+            IKeyVaultClient keyVaultClient;
+
+            if (_azureKeyVaultConfiguration.UseClientCredentials)
+            {
+                keyVaultClient = new KeyVaultClient(async (authority, resource, scope) =>
+                {
+                    var adCredential = new ClientCredential(_azureKeyVaultConfiguration.ClientId, _azureKeyVaultConfiguration.ClientSecret);
+                    var authenticationContext = new AuthenticationContext(authority, null);
+                    return (await authenticationContext.AcquireTokenAsync(resource, adCredential)).AccessToken;
+                });
+            }
+            else
+            {
+                var azureServiceTokenProvider = new AzureServiceTokenProvider();
+                keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
+            }
+
+            return keyVaultClient;
+        }
+
+        private async Task<List<CertificateItem>> GetAllEnabledCertificateVersionsAsync(IKeyVaultClient keyVaultClient)
+        {
+            // Get all the certificate versions (this will also get the current active version)
+            var certificateVersions = await keyVaultClient.GetCertificateVersionsAsync(_azureKeyVaultConfiguration.AzureKeyVaultEndpoint, _azureKeyVaultConfiguration.IdentityServerCertificateName);
+
+            // Find all enabled versions of the certificate and sort them by creation date in descending order 
+            return certificateVersions
+              .Where(certVersion => certVersion.Attributes.Enabled.HasValue && certVersion.Attributes.Enabled.Value)
+              .OrderByDescending(certVersion => certVersion.Attributes.Created)
+              .ToList();
+        }
+
+        private async Task<X509Certificate2> GetCertificateAsync(string identifier, IKeyVaultClient keyVaultClient)
+        {
+            var certificateVersionBundle = await keyVaultClient.GetCertificateAsync(identifier);
+            var certificatePrivateKeySecretBundle = await keyVaultClient.GetSecretAsync(certificateVersionBundle.SecretIdentifier.Identifier);
+            var privateKeyBytes = Convert.FromBase64String(certificatePrivateKeySecretBundle.Value);
+            var certificateWithPrivateKey = new X509Certificate2(privateKeyBytes, (string)null, X509KeyStorageFlags.MachineKeySet);
+
+            return certificateWithPrivateKey;
+        }
+    }
+}
diff --git a/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj b/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj
index 7cff92f2d..1be7c473a 100644
--- a/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj
+++ b/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj
@@ -13,6 +13,8 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.6" />
+    <PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.5" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.8" />
     <PackageReference Include="Sendgrid" Version="9.18.0" />
   </ItemGroup>
 

From 80e09981ceacd8f925cb9b1a4d52dbbc232651c2 Mon Sep 17 00:00:00 2001
From: janskoruba <jan@skoruba.com>
Date: Sat, 26 Sep 2020 15:39:47 +0200
Subject: [PATCH 2/5] Add support for ProtectKeysWithAzureKeyVault

---
 .../Startup.cs                                |  4 +--
 src/Skoruba.IdentityServer4.Admin/Startup.cs  |  4 +--
 .../Startup.cs                                |  4 +--
 .../Common/DataProtectionConfiguration.cs     |  7 ++++
 .../Helpers/StartupHelpers.cs                 | 34 ++++++++++++++++++-
 .../Skoruba.IdentityServer4.Shared.csproj     |  4 ++-
 6 files changed, 46 insertions(+), 11 deletions(-)
 create mode 100644 src/Skoruba.IdentityServer4.Shared/Configuration/Common/DataProtectionConfiguration.cs

diff --git a/src/Skoruba.IdentityServer4.Admin.Api/Startup.cs b/src/Skoruba.IdentityServer4.Admin.Api/Startup.cs
index 3fd2d0fb8..63b95040b 100644
--- a/src/Skoruba.IdentityServer4.Admin.Api/Startup.cs
+++ b/src/Skoruba.IdentityServer4.Admin.Api/Startup.cs
@@ -45,9 +45,7 @@ public void ConfigureServices(IServiceCollection services)
             // Add DbContexts
             RegisterDbContexts(services);
 
-            services.AddDataProtection()
-                .SetApplicationName("Skoruba.IdentityServer4")
-                .PersistKeysToDbContext<IdentityServerDataProtectionDbContext>();
+            services.AddDataProtection<IdentityServerDataProtectionDbContext>(Configuration);
 
             // Add email senders which is currently setup for SendGrid and SMTP
             services.AddEmailSenders(Configuration);
diff --git a/src/Skoruba.IdentityServer4.Admin/Startup.cs b/src/Skoruba.IdentityServer4.Admin/Startup.cs
index 1b0522b9e..e4c907f47 100644
--- a/src/Skoruba.IdentityServer4.Admin/Startup.cs
+++ b/src/Skoruba.IdentityServer4.Admin/Startup.cs
@@ -45,9 +45,7 @@ public void ConfigureServices(IServiceCollection services)
             RegisterDbContexts(services);
 
             // Save data protection keys to db, using a common application name shared between Admin and STS
-            services.AddDataProtection()
-                .SetApplicationName("Skoruba.IdentityServer4")
-                .PersistKeysToDbContext<IdentityServerDataProtectionDbContext>();
+            services.AddDataProtection<IdentityServerDataProtectionDbContext>(Configuration);
 
             // Add email senders which is currently setup for SendGrid and SMTP
             services.AddEmailSenders(Configuration);
diff --git a/src/Skoruba.IdentityServer4.STS.Identity/Startup.cs b/src/Skoruba.IdentityServer4.STS.Identity/Startup.cs
index 61c887227..88516dee0 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/Startup.cs
+++ b/src/Skoruba.IdentityServer4.STS.Identity/Startup.cs
@@ -36,9 +36,7 @@ public void ConfigureServices(IServiceCollection services)
             RegisterDbContexts(services);
 
             // Save data protection keys to db, using a common application name shared between Admin and STS
-            services.AddDataProtection()
-                .SetApplicationName("Skoruba.IdentityServer4")
-                .PersistKeysToDbContext<IdentityServerDataProtectionDbContext>();
+            services.AddDataProtection<IdentityServerDataProtectionDbContext>(Configuration);
 
             // Add email senders which is currently setup for SendGrid and SMTP
             services.AddEmailSenders(Configuration);
diff --git a/src/Skoruba.IdentityServer4.Shared/Configuration/Common/DataProtectionConfiguration.cs b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/DataProtectionConfiguration.cs
new file mode 100644
index 000000000..1ac4444fb
--- /dev/null
+++ b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/DataProtectionConfiguration.cs
@@ -0,0 +1,7 @@
+namespace Skoruba.IdentityServer4.Shared.Configuration.Common
+{
+    public class DataProtectionConfiguration
+    {
+        public bool ProtectKeysWithAzureKeyVault { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs b/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs
index 45c2d9ada..637272ece 100644
--- a/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs
+++ b/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs
@@ -1,7 +1,13 @@
-using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Azure.Services.AppAuthentication;
+using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using SendGrid;
+using Skoruba.IdentityServer4.Shared.Configuration.Common;
 using Skoruba.IdentityServer4.Shared.Configuration.Email;
 using Skoruba.IdentityServer4.Shared.Email;
 
@@ -35,5 +41,31 @@ public static void AddEmailSenders(this IServiceCollection services, IConfigurat
                 services.AddSingleton<IEmailSender, LogEmailSender>();
             }
         }
+
+        public static void AddDataProtection<TDbContext>(this IServiceCollection services, IConfiguration configuration)
+            where TDbContext : DbContext, IDataProtectionKeyContext
+        {
+            var dataProtectionConfiguration = configuration.GetSection(nameof(DataProtectionConfiguration)).Get<DataProtectionConfiguration>();
+            var azureKeyVaultConfiguration = configuration.GetSection(nameof(AzureKeyVaultConfiguration)).Get<AzureKeyVaultConfiguration>();
+
+            var dataProtectionBuilder = services.AddDataProtection()
+                .SetApplicationName("Skoruba.IdentityServer4")
+                .PersistKeysToDbContext<TDbContext>();
+
+            if (dataProtectionConfiguration.ProtectKeysWithAzureKeyVault)
+            {
+                if (azureKeyVaultConfiguration.UseClientCredentials)
+                {
+                    dataProtectionBuilder.ProtectKeysWithAzureKeyVault(azureKeyVaultConfiguration.DataProtectionKeyIdentifier, azureKeyVaultConfiguration.ClientId, azureKeyVaultConfiguration.ClientSecret);
+                }
+                else
+                {
+                    var azureServiceTokenProvider = new AzureServiceTokenProvider();
+                    var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
+
+                    dataProtectionBuilder.ProtectKeysWithAzureKeyVault(keyVaultClient, azureKeyVaultConfiguration.DataProtectionKeyIdentifier);
+                }
+            }
+        }
     }
 }
diff --git a/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj b/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj
index 1be7c473a..3a00d6acd 100644
--- a/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj
+++ b/src/Skoruba.IdentityServer4.Shared/Skoruba.IdentityServer4.Shared.csproj
@@ -12,9 +12,11 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureKeyVault" Version="3.1.6" />
+    <PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="3.1.6" />
     <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.6" />
     <PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.5" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.8" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.6" />
     <PackageReference Include="Sendgrid" Version="9.18.0" />
   </ItemGroup>
 

From 1d3e9246a4f3fe8e4a28f8f1679250e800e7c577 Mon Sep 17 00:00:00 2001
From: janskoruba <jan@skoruba.com>
Date: Sat, 26 Sep 2020 15:41:35 +0200
Subject: [PATCH 3/5] Add DataProtectionConfiguration

---
 .../appsettings.json                                |  9 ++++++---
 src/Skoruba.IdentityServer4.Admin/appsettings.json  | 13 ++++++++-----
 .../appsettings.json                                |  3 +++
 3 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json b/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json
index d7d2aff40..1005977bf 100644
--- a/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json
+++ b/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json
@@ -40,13 +40,16 @@
     },
     "IdentityOptions": {
         "Password": {
-          "RequiredLength": 8
+            "RequiredLength": 8
         },
         "User": {
-          "RequireUniqueEmail": true
+            "RequireUniqueEmail": true
         },
         "SignIn": {
-          "RequireConfirmedAccount": false
+            "RequireConfirmedAccount": false
         }
+    },
+    "DataProtectionConfiguration": {
+        "ProtectKeysWithAzureKeyVault": false
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Admin/appsettings.json b/src/Skoruba.IdentityServer4.Admin/appsettings.json
index ddd8eadf8..c4117c36e 100644
--- a/src/Skoruba.IdentityServer4.Admin/appsettings.json
+++ b/src/Skoruba.IdentityServer4.Admin/appsettings.json
@@ -61,13 +61,16 @@
     "BasePath": "",
     "IdentityOptions": {
         "Password": {
-          "RequiredLength": 8
+            "RequiredLength": 8
         },
         "User": {
-          "RequireUniqueEmail": true
+            "RequireUniqueEmail": true
         },
-      "SignIn": {
-        "RequireConfirmedAccount": false
-      }
+        "SignIn": {
+            "RequireConfirmedAccount": false
+        }
+    },
+    "DataProtectionConfiguration": {
+        "ProtectKeysWithAzureKeyVault": false
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json b/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
index b6b7eaddb..942017d6d 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
+++ b/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
@@ -86,5 +86,8 @@
         "SignIn": {
             "RequireConfirmedAccount": false
         }
+    },
+    "DataProtectionConfiguration": {
+        "ProtectKeysWithAzureKeyVault": false
     }
 }
\ No newline at end of file

From d15542b419373db32a3c17b8110c8ca1b0cf4366 Mon Sep 17 00:00:00 2001
From: janskoruba <jan@skoruba.com>
Date: Sat, 26 Sep 2020 16:04:55 +0200
Subject: [PATCH 4/5] Add Azure KeyVault for reading secrets

---
 .../Program.cs                                |  8 ++++++
 .../appsettings.json                          |  8 ++++++
 src/Skoruba.IdentityServer4.Admin/Program.cs  | 14 +++++++++-
 .../appsettings.json                          |  9 +++++++
 .../Program.cs                                |  8 ++++++
 .../appsettings.json                          | 17 ++++++------
 .../Common/AzureKeyVaultConfiguration.cs      |  2 ++
 .../Helpers/StartupHelpers.cs                 | 27 +++++++++++++++++++
 8 files changed, 84 insertions(+), 9 deletions(-)

diff --git a/src/Skoruba.IdentityServer4.Admin.Api/Program.cs b/src/Skoruba.IdentityServer4.Admin.Api/Program.cs
index 47d0deb20..bd431d3c1 100644
--- a/src/Skoruba.IdentityServer4.Admin.Api/Program.cs
+++ b/src/Skoruba.IdentityServer4.Admin.Api/Program.cs
@@ -50,6 +50,10 @@ private static IConfiguration GetConfiguration(string[] args)
                 configurationBuilder.AddUserSecrets<Startup>();
             }
 
+            var configuration = configurationBuilder.Build();
+
+            configuration.AddAzureKeyVaultConfiguration(configurationBuilder);
+
             configurationBuilder.AddCommandLine(args);
             configurationBuilder.AddEnvironmentVariables();
 
@@ -60,6 +64,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
             Host.CreateDefaultBuilder(args)
                  .ConfigureAppConfiguration((hostContext, configApp) =>
                  {
+                     var configurationRoot = configApp.Build();
+
                      configApp.AddJsonFile("serilog.json", optional: true, reloadOnChange: true);
 
                      var env = hostContext.HostingEnvironment;
@@ -71,6 +77,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
                          configApp.AddUserSecrets<Startup>();
                      }
 
+                     configurationRoot.AddAzureKeyVaultConfiguration(configApp);
+
                      configApp.AddEnvironmentVariables();
                      configApp.AddCommandLine(args);
                  })
diff --git a/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json b/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json
index 1005977bf..06a004bb1 100644
--- a/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json
+++ b/src/Skoruba.IdentityServer4.Admin.Api/appsettings.json
@@ -51,5 +51,13 @@
     },
     "DataProtectionConfiguration": {
         "ProtectKeysWithAzureKeyVault": false
+    },
+    "AzureKeyVaultConfiguration": {
+        "AzureKeyVaultEndpoint": "",
+        "ClientId": "",
+        "ClientSecret": "",
+        "UseClientCredentials": true,
+        "DataProtectionKeyIdentifier": "",
+        "ReadConfigurationFromKeyVault": false
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Admin/Program.cs b/src/Skoruba.IdentityServer4.Admin/Program.cs
index 7f2199272..e298c76ff 100644
--- a/src/Skoruba.IdentityServer4.Admin/Program.cs
+++ b/src/Skoruba.IdentityServer4.Admin/Program.cs
@@ -3,13 +3,17 @@
 using System.Linq;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Hosting;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Azure.Services.AppAuthentication;
 using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.AzureKeyVault;
 using Microsoft.Extensions.Hosting;
 using Serilog;
 using Skoruba.IdentityServer4.Admin.Configuration;
 using Skoruba.IdentityServer4.Admin.EntityFramework.Shared.DbContexts;
 using Skoruba.IdentityServer4.Admin.EntityFramework.Shared.Entities.Identity;
 using Skoruba.IdentityServer4.Admin.Helpers;
+using Skoruba.IdentityServer4.Shared.Configuration.Common;
 using Skoruba.IdentityServer4.Shared.Helpers;
 
 namespace Skoruba.IdentityServer4.Admin
@@ -49,7 +53,7 @@ public static async Task Main(string[] args)
         private static async Task ApplyDbMigrationsWithDataSeedAsync(string[] args, IConfiguration configuration, IHost host)
         {
             var applyDbMigrationWithDataSeedFromProgramArguments = args.Any(x => x == SeedArgs);
-            if (applyDbMigrationWithDataSeedFromProgramArguments) args = args.Except(new[] {SeedArgs}).ToArray();
+            if (applyDbMigrationWithDataSeedFromProgramArguments) args = args.Except(new[] { SeedArgs }).ToArray();
 
             var seedConfiguration = configuration.GetSection(nameof(SeedConfiguration)).Get<SeedConfiguration>();
             var databaseMigrationsConfiguration = configuration.GetSection(nameof(DatabaseMigrationsConfiguration))
@@ -79,6 +83,10 @@ private static IConfiguration GetConfiguration(string[] args)
                 configurationBuilder.AddUserSecrets<Startup>();
             }
 
+            var configuration = configurationBuilder.Build();
+
+            configuration.AddAzureKeyVaultConfiguration(configurationBuilder);
+
             configurationBuilder.AddCommandLine(args);
             configurationBuilder.AddEnvironmentVariables();
 
@@ -89,6 +97,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
             Host.CreateDefaultBuilder(args)
                  .ConfigureAppConfiguration((hostContext, configApp) =>
                  {
+                     var configurationRoot = configApp.Build();
+
                      configApp.AddJsonFile("serilog.json", optional: true, reloadOnChange: true);
                      configApp.AddJsonFile("identitydata.json", optional: true, reloadOnChange: true);
                      configApp.AddJsonFile("identityserverdata.json", optional: true, reloadOnChange: true);
@@ -104,6 +114,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
                          configApp.AddUserSecrets<Startup>();
                      }
 
+                     configurationRoot.AddAzureKeyVaultConfiguration(configApp);
+
                      configApp.AddEnvironmentVariables();
                      configApp.AddCommandLine(args);
                  })
diff --git a/src/Skoruba.IdentityServer4.Admin/appsettings.json b/src/Skoruba.IdentityServer4.Admin/appsettings.json
index c4117c36e..cc0c121b4 100644
--- a/src/Skoruba.IdentityServer4.Admin/appsettings.json
+++ b/src/Skoruba.IdentityServer4.Admin/appsettings.json
@@ -72,5 +72,14 @@
     },
     "DataProtectionConfiguration": {
         "ProtectKeysWithAzureKeyVault": false
+    },
+
+    "AzureKeyVaultConfiguration": {
+        "AzureKeyVaultEndpoint": "",
+        "ClientId": "",
+        "ClientSecret": "",
+        "UseClientCredentials": true,
+        "DataProtectionKeyIdentifier": "",
+        "ReadConfigurationFromKeyVault": false 
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.STS.Identity/Program.cs b/src/Skoruba.IdentityServer4.STS.Identity/Program.cs
index 227ddd10d..2f09930ca 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/Program.cs
+++ b/src/Skoruba.IdentityServer4.STS.Identity/Program.cs
@@ -50,6 +50,10 @@ private static IConfiguration GetConfiguration(string[] args)
                 configurationBuilder.AddUserSecrets<Startup>();
             }
 
+            var configuration = configurationBuilder.Build();
+
+            configuration.AddAzureKeyVaultConfiguration(configurationBuilder);
+
             configurationBuilder.AddCommandLine(args);
             configurationBuilder.AddEnvironmentVariables();
 
@@ -60,6 +64,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
             Host.CreateDefaultBuilder(args)
                  .ConfigureAppConfiguration((hostContext, configApp) =>
                  {
+                     var configurationRoot = configApp.Build();
+
                      configApp.AddJsonFile("serilog.json", optional: true, reloadOnChange: true);
 
                      var env = hostContext.HostingEnvironment;
@@ -71,6 +77,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
                          configApp.AddUserSecrets<Startup>();
                      }
 
+                     configurationRoot.AddAzureKeyVaultConfiguration(configApp);
+
                      configApp.AddEnvironmentVariables();
                      configApp.AddCommandLine(args);
                  })
diff --git a/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json b/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
index 942017d6d..0a686cbf8 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
+++ b/src/Skoruba.IdentityServer4.STS.Identity/appsettings.json
@@ -32,14 +32,6 @@
         "UseSigningCertificateForAzureKeyVault": false,
         "UseValidationCertificateForAzureKeyVault": false
     },
-    "AzureKeyVaultConfiguration": {
-        "AzureKeyVaultEndpoint": "",
-        "ClientId": "",
-        "ClientSecret": "",
-        "UseClientCredentials": true,
-        "IdentityServerCertificateName": "",
-        "DataProtectionKeyIdentifier": ""
-    },
     "RegisterConfiguration": {
         "Enabled": true
     },
@@ -89,5 +81,14 @@
     },
     "DataProtectionConfiguration": {
         "ProtectKeysWithAzureKeyVault": false
+    },
+    "AzureKeyVaultConfiguration": {
+        "AzureKeyVaultEndpoint": "",
+        "ClientId": "",
+        "ClientSecret": "",
+        "UseClientCredentials": true,
+        "IdentityServerCertificateName": "",
+        "DataProtectionKeyIdentifier": "",
+        "ReadConfigurationFromKeyVault": false
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs
index 1517faa05..a68dde187 100644
--- a/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs
+++ b/src/Skoruba.IdentityServer4.Shared/Configuration/Common/AzureKeyVaultConfiguration.cs
@@ -15,5 +15,7 @@ public class AzureKeyVaultConfiguration
         public string IdentityServerCertificateName { get; set; }
 
         public string DataProtectionKeyIdentifier { get; set; }
+
+        public bool ReadConfigurationFromKeyVault { get; set; }
     }
 }
\ No newline at end of file
diff --git a/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs b/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs
index 637272ece..df1a492e5 100644
--- a/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs
+++ b/src/Skoruba.IdentityServer4.Shared/Helpers/StartupHelpers.cs
@@ -5,6 +5,7 @@
 using Microsoft.Azure.Services.AppAuthentication;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.AzureKeyVault;
 using Microsoft.Extensions.DependencyInjection;
 using SendGrid;
 using Skoruba.IdentityServer4.Shared.Configuration.Common;
@@ -67,5 +68,31 @@ public static void AddDataProtection<TDbContext>(this IServiceCollection service
                 }
             }
         }
+
+        public static void AddAzureKeyVaultConfiguration(this IConfiguration configuration, IConfigurationBuilder configurationBuilder)
+        {
+            if (configuration.GetSection(nameof(AzureKeyVaultConfiguration)).Exists())
+            {
+                var azureKeyVaultConfiguration = configuration.GetSection(nameof(AzureKeyVaultConfiguration)).Get<AzureKeyVaultConfiguration>();
+
+                if (azureKeyVaultConfiguration.ReadConfigurationFromKeyVault)
+                {
+                    if (azureKeyVaultConfiguration.UseClientCredentials)
+                    {
+                        configurationBuilder.AddAzureKeyVault(azureKeyVaultConfiguration.AzureKeyVaultEndpoint,
+                            azureKeyVaultConfiguration.ClientId, azureKeyVaultConfiguration.ClientSecret);
+                    }
+                    else
+                    {
+                        var keyVaultClient = new KeyVaultClient(
+                            new KeyVaultClient.AuthenticationCallback(new AzureServiceTokenProvider()
+                                .KeyVaultTokenCallback));
+
+                        configurationBuilder.AddAzureKeyVault(azureKeyVaultConfiguration.AzureKeyVaultEndpoint,
+                            keyVaultClient, new DefaultKeyVaultSecretManager());
+                    }
+                }
+            }
+        }
     }
 }

From 2bd4c5bce36496f7c47aa01932d467ef8fd50eca Mon Sep 17 00:00:00 2001
From: janskoruba <jan@skoruba.com>
Date: Wed, 30 Sep 2020 22:35:55 +0200
Subject: [PATCH 5/5] Fix #589

---
 .../Helpers/IdentityServerBuilderExtensions.cs                  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs b/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs
index e3559b92f..69b78e771 100644
--- a/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs
+++ b/src/Skoruba.IdentityServer4.STS.Identity/Helpers/IdentityServerBuilderExtensions.cs
@@ -174,7 +174,7 @@ public static IIdentityServerBuilder AddCustomValidationKey(this IIdentityServer
                 }
                 else
                 {
-                    throw new Exception($"Validation key file: {certificateConfiguration.SigningCertificatePfxFilePath} not found");
+                    throw new Exception($"Validation key file: {certificateConfiguration.ValidationCertificatePfxFilePath} not found");
                 }
             }