4
votes

I'm referencing two (nuget) packages from my app and setting up KeyVault DI configuration. Both the packages reference Microsoft.IdentityModel.Clients.ActiveDirectory nuget package. One references major version 3, the other major version 4. Major version 4 removes the dll Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.

When I call AddAzureKeyVault() with a certificate to add the KV configuration I get an AssemblyNotFoundException for the dll Microsoft.IdentityModel.Clients.ActiveDirectory.Platform at runtime. The file is correctly not there and I've redirected the binding of the associated dll to the major version so why is it being requested at runtime? Is there a way of binding a call to Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll back to Microsoft.IdentityModel.Clients.ActiveDirectory.dll? I've provided binding redirects and tried multiple permutations.

To replicate, from a console app you can reference packages:

Microsoft.Extensions.Configuration.AzureKeyVault 2.2.0 Microsoft.IdentityModel.Client.ActiveDirectory 4.5.1

using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Configuration;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var cb = new ConfigurationBuilder();

            using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
            {
                store.Open(OpenFlags.ReadOnly);

                var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "DummyValue", false);

                cb.AddAzureKeyVault("https://this-is-a-dummy.vault.azure.net", "dummy-client-id", certs.OfType<X509Certificate2>().Single());

                store.Close();
            }


            var config = cb.Build();
        } 
    }
}

Could not load file or assembly 'Microsoft.IdentityModel.Clients.ActiveDirectory.Platform, Version=3.14.2.11, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

Pre-bind FusionLog

LOG: DisplayName = Microsoft.IdentityModel.Clients.ActiveDirectory.Platform, Version=3.14.2.11, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (Fully-specified)

LOG: Appbase = file:///C:/Code/sandbox/why-doesnt-this-work/ConsoleApp1/ConsoleApp1/bin/Debug/

LOG: Initial PrivatePath = NULL Calling assembly : Microsoft.Extensions.Configuration.AzureKeyVault, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60.

1
What version of .NET Framework / .NET Core do you use?Sergiy
Same result for .NET Core 2.2 or .NET Framework 4.7.2. Digging a little further I can see other people having similar problems with this package: github.com/aspnet/Extensions/issues/1728MrPanucci

1 Answers

0
votes

I had the exact same problem in .net core 3.1 and it looks like my problem was solved by getting the certificate manually and using AddAzureClient(string vault, KeyVaultClient client, IKeyVaultSecretManager manager) instead.

I'm using Azure Stack Hub and have not tested this with regular Azure, but don't see why this would not work in regular Azure.

This is how I implemented it:

using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Azure.KeyVault;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace MyOrganization.Host
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((context, config) =>
                {
                    var root = config.Build();
                    var client = GetKeyVaultClient(root["AzureKeyVault:CertificateThumbprint"], root["AzureKeyVault:ClientId"]);
                    config.AddAzureKeyVault(root["AzureKeyVault:KeyVaultURL"], client, new DefaultKeyVaultSecretManager());
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

        private static KeyVaultClient GetKeyVaultClient(string certificateThumbprint, string clientId)
        {
            string token = null;
            var client = new KeyVaultClient(async (authority, resource, _) =>
            {
                if (token != null)
                    return token;
                var adFsAuthority = GetAdFsAuthority(authority);
                var certificate = GetCertificate(certificateThumbprint);
                token = await GetAccessTokenAsync(resource, clientId, adFsAuthority, certificate);
                return token;
            });
            return client;
        }

        private static string GetAdFsAuthority(string authority)
        {
            // Azure Stack Key Vault gives authentication challenge at an authority which has an invalid uri.
            // For cause, see https://<your azure stack hub adfs address>/adfs/.well-known/openid-configuration.
            return $"{new Uri(authority).GetLeftPart(UriPartial.Authority)}/adfs";
        }

        public static X509Certificate2 GetCertificate(string thumbprint)
        {
            X509Store store = new X509Store(StoreLocation.CurrentUser);
            try
            {
                store.Open(OpenFlags.ReadOnly);
                var certs = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
                if (certs.Count == 0)
                    throw new Exception("Could not find certificate!");
                return certs[0];
            }
            finally
            {
                store.Close();
            }
        }

        private static async Task<string> GetAccessTokenAsync(
            string resource, string clientId, string authority, X509Certificate2 certificate)
        {
            // Must set to false with authorities with URL starting with adfs
            const bool validateAuthority = false;
            var context = new AuthenticationContext(authority, validateAuthority);
            var assertionCertificate = new ClientAssertionCertificate(clientId, certificate);
            var result = await context.AcquireTokenAsync(resource, assertionCertificate);
            return result.AccessToken;
        }
    }
}