Search code examples
c#monoazure-active-directorysharepoint-online

AzureAD - mono only - System.Security.Cryptography.CryptographicException: Keyset does not exist


I am trying to use AzureAD (mono only) to authenticate as an AD app-user to make requests to SharePoint.

The Azure AD app user basically requires you provide [clientID, certificate path, certificate password].

The following code works on Windows:

  string siteUrl = "https://xxxxxxx.sharepoint.com";
  string clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx";
  string domain = "xxxxxxx.onmicrosoft.com";
  string certificatePath = "/path/to/xxxxxxx.pfx";
  string certificatePassword = "xxxxxxx";

  using (var cc = new AuthenticationManager().GetAzureADAppOnlyAuthenticatedContext(siteUrl, clientId, domain, certificatePath, certificatePassword)) {
    cc.Load(cc.Web, p => p.Title);
    cc.ExecuteQuery();
    Console.WriteLine(cc.Web.Title);
  };

But on Mono you get this error:

System.Security.Cryptography.CryptographicException: Keyset does not exist

Seems to be related to:

But these are supposedly fixed, yet I'm still having these problems.

Full error stack:

System.Security.Cryptography.CryptographicException: Keyset does not exist
  at System.Security.Cryptography.RSACryptoServiceProvider.Common (System.Security.Cryptography.CspParameters p) [0x00039] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Security.Cryptography.RSACryptoServiceProvider..ctor (System.Int32 dwKeySize, System.Security.Cryptography.CspParameters parameters) [0x0001d] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Security.Cryptography.RSACryptoServiceProvider..ctor (System.Security.Cryptography.CspParameters parameters) [0x00000] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.CryptographyHelper.GetCryptoProviderForSha256 (System.Security.Cryptography.RSACryptoServiceProvider rsaProvider) [0x0007e] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.CryptographyHelper.SignWithCertificate (System.String message, System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate) [0x0001b] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate.Sign (System.String message) [0x00007] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.JsonWebToken.Sign (Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate credential) [0x0002b] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.RequestParameters.AddClientKey (Microsoft.IdentityModel.Clients.ActiveDirectory.ClientKey clientKey) [0x000b7] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.RequestParameters..ctor (System.String resource, Microsoft.IdentityModel.Clients.ActiveDirectory.ClientKey clientKey) [0x0001a] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase+<SendTokenRequestAsync>d__9.MoveNext () [0x00024] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase+<RunAsync>d__0.MoveNext () [0x004f3] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
--- End of stack trace from previous location where exception was thrown ---
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.RunAsyncTask[T] (System.Threading.Tasks.Task`1[TResult] task) [0x00031] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.AcquireToken (System.String resource, Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate clientCertificate) [0x00014] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at pnp_test_2.Program.Main (System.String[] args) [0x000a8] in <8c5b1bd4cf9047a3868c8cacd6143dd1>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.Security.Cryptography.CryptographicException: Keyset does not exist
  at System.Security.Cryptography.RSACryptoServiceProvider.Common (System.Security.Cryptography.CspParameters p) [0x00039] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Security.Cryptography.RSACryptoServiceProvider..ctor (System.Int32 dwKeySize, System.Security.Cryptography.CspParameters parameters) [0x0001d] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Security.Cryptography.RSACryptoServiceProvider..ctor (System.Security.Cryptography.CspParameters parameters) [0x00000] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.CryptographyHelper.GetCryptoProviderForSha256 (System.Security.Cryptography.RSACryptoServiceProvider rsaProvider) [0x0007e] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.CryptographyHelper.SignWithCertificate (System.String message, System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate) [0x0001b] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate.Sign (System.String message) [0x00007] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.JsonWebToken.Sign (Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate credential) [0x0002b] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.RequestParameters.AddClientKey (Microsoft.IdentityModel.Clients.ActiveDirectory.ClientKey clientKey) [0x000b7] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.RequestParameters..ctor (System.String resource, Microsoft.IdentityModel.Clients.ActiveDirectory.ClientKey clientKey) [0x0001a] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase+<SendTokenRequestAsync>d__9.MoveNext () [0x00024] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <bb7b695b8c6246b3ac1646577aea7650>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase+<RunAsync>d__0.MoveNext () [0x004f3] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
--- End of stack trace from previous location where exception was thrown ---
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.RunAsyncTask[T] (System.Threading.Tasks.Task`1[TResult] task) [0x00031] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.AcquireToken (System.String resource, Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate clientCertificate) [0x00014] in <211fb7a0ce9049e5a2768849f2fd6a88>:0 
  at pnp_test_2.Program.Main (System.String[] args) [0x000a8] in <8c5b1bd4cf9047a3868c8cacd6143dd1>:0 

How can I authenticate with Azure AD app-only accounts + pfx key on mono?


Solution

  • See my comment here: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/509

    I have gotten the work-around to work for SharePoint Online now:

    You can remove the CorePNP library if you want. But do not use the OfficeDevPnP.Core.AuthenticationManager for Linux because it won't work! That library does work great for Windows however.

    Add nuget dependencies:

    Microsoft.IdentityModel.Clients.ActiveDirectory
    Microsoft.IdentityModel.Tokens
    

    c# code to get a client context with the auth correctly:

    using System;
    using System.Text;
    using System.Globalization;
    using System.Security.Cryptography.X509Certificates;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Microsoft.IdentityModel.Tokens;
    using System.Security.Cryptography;
    using Microsoft.SharePoint.Client;
    
    namespace PnpTest {
    
      class ClientAssertionCertificate : IClientAssertionCertificate {
    
        X509Certificate2 certificate;
        public string ClientId { get; private set; }
    
        public string Thumbprint {
          get {
            return Base64UrlEncoder.Encode(certificate.GetCertHash());
          }
        }
    
        public ClientAssertionCertificate(string clientId, X509Certificate2 certificate) {
          ClientId = clientId;
          this.certificate = certificate;
        }
    
        public byte[] Sign(string message) {
          using (var key = certificate.GetRSAPrivateKey()) {
            return key.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
          }
        }
      }
    
      class Program {
        static void Main(string[] args) {
          string siteUrl = "https://xxxxxxxxxxxxxxx.sharepoint.com";
          string clientId = "xxxxxxxxxxxxxxx";
          string tenant = "xxxxxxxxxxxxxxx.onmicrosoft.com";
          string certificatePath = "resources/xxxxxxxx.pfx";
          string certificatePassword = "xxxxxxxx";
    
          var certfile = System.IO.File.OpenRead(certificatePath);
          var certificateBytes = new byte[certfile.Length];
          certfile.Read(certificateBytes, 0, (int)certfile.Length);
          var certificate = new X509Certificate2(
              certificateBytes,
              certificatePassword,
              X509KeyStorageFlags.Exportable |
              X509KeyStorageFlags.MachineKeySet |
              X509KeyStorageFlags.PersistKeySet);
    
          var clientAssertionCertificate = new ClientAssertionCertificate(clientId, certificate);
    
          string authority = string.Format(CultureInfo.InvariantCulture, "{0}/{1}/", "https://login.windows.net", tenant);
    
          var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
    
          var host = new Uri(siteUrl);
    
          using (var clientContext = new ClientContext(siteUrl)) {
            clientContext.ExecutingWebRequest += (sender, webRequestArgs) => {
              var arFuture = authContext.AcquireTokenAsync(host.Scheme + "://" + host.Host + "/", clientAssertionCertificate);
              var ar = arFuture.Result;
    
              webRequestArgs.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + ar.AccessToken;
            };
            clientContext.Load(clientContext.Web, p => p.Title);
            clientContext.ExecuteQuery();
            Console.WriteLine(clientContext.Web.Title);
          }
        }
      }
    }