1

I'm trying to develop a cross-platform app (windows/mac os x) that needs to sign XML files and make web requests on a server using ClientCertificate authentication...

The main constraint is that I need to use a certificate on a smartcard...

I'm using dotnet core 2.1 for the moment.

First I did try to use dotnet core X509Store, but on MacOs I couln't access the PrivateKey object in any case (mandatory for XML signing.), then I discarded this lead.

Then I used pkcs11interop with the vendor-specific dlls to access the smartcard, and it worked well for XML signing (I wrapped pkcs11interop calls inside a RSA object, then I used this object as SigningKey inside SignedXml,) but it did not work for ClientCertificate connection.

Normally I use this method to connect to the server (dotnet framework 4.6.1, windows only) :

HttpWebRequest req = WebRequest.Create("https://sometlsserver.com/") as HttpWebRequest;
req.ClientCertificates.Add(cert);
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
using (StreamReader sr = new StreamReader(res.GetResponseStream(), new UTF8Encoding(false)))
{
    return sr.ReadToEnd();
}

And the certificate is generated:

var certAttr = session.GetAttributeValue(handle, new List<CKA>
{
    CKA.CKA_VALUE,
};
var cert = new X509Certificate2(certAttr[0].GetValueAsByteArray());

When I try to connect with my classic method, I have several exceptions :

System.Net.WebException: The SSL connection could not be established, see inner exception. Authentication failed, see inner exception.
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception: Le message reçu était inattendu ou formaté de façon incorrecte
   --- End of inner exception stack trace ---
   at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Security.SslState.ThrowIfExceptional()
   at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
   at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__47_1(IAsyncResult iar)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync(ValueTask`1 creationTask)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at System.Net.HttpWebRequest.SendRequest()
   at System.Net.HttpWebRequest.GetResponse()
   --- End of inner exception stack trace ---
   at System.Net.HttpWebRequest.GetResponse()
...

I think that the cryptographic operation made by the private key needed during the TLS handshake is missing and it simply fails...

I did try to add my RSA object used for XML signing in the private key property but with dotnet core it ends with a PlatformNotSupportedException...

cert.PrivateKey = myRSAObject; // simple

I'm wondering if that's possible to do that without using a third party library...

Note: Translation of the french error message "Message was unexpected or incorrectly formatted."

Thanks.

(edit: better formatting)

NoZ
  • 11
  • 2
  • The `PrivateKey` property setter was replaced with `CopyWithPrivateKey` extension methods that support RSA, DSA and ECC (eg. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.rsacertificateextensions.copywithprivatekey?view=netframework-4.8). All of these work on .NET Core on all platforms. – Filip Navara May 10 '19 at 06:17
  • @FilipNavara thx for pointing that method out! That might be a gamechanger if `RSA` will be used by SSL implementations. – jariq May 10 '19 at 07:35
  • Thanks, I missed that method, I'll try that asap. – NoZ May 12 '19 at 11:11
  • Sadly, it didn't work. CopyWithPrivateKey is expecting an exponent and a modulus for the private key, and the private exponent can't be extracted from the smartcard... I'm currently investigating bouncycastle, seems promising ? – NoZ May 13 '19 at 08:28

1 Answers1

1

X509Certificate2 is legacy class designed only for Windows that currently has no widely usable alternative or sane multiplatform replacement.

When you construct X509Certificate2 object using byte[] value of CKA_VALUE attribute you get just the certificate without the private key.

The closest you can get to ".NET compatible implementation" is to implement class derived from RSA as I did in Pkcs11Interop.X509Store project. It will work with SignedXML class but as you already found out it won't be usable with most SSL classes that require X509Certificate2 object with correctly associated PrivateKey property.

IMO you have three options:

  1. Switch to appropriate .NET Core version and create X509Certificate2 object associated with your RSA implementation using CopyWithPrivateKey extension method as pointed out by @FilipNavara in his comment. Such X509Certificate2 object might (or rather might not) work with SSL implementation.

  2. Use PFX/PKCS#12 file that contains SSL client certificate with private key protected by user password. Such file can be loaded in X509Certificate2 object correctly associated PrivateKey property on most platforms supported by .NET Core.

  3. Find or write SSL client implementation that will not require X509Certificate2 object with associated PrivateKey property. I'm currently not aware of any library with such design.

jariq
  • 11,681
  • 3
  • 33
  • 52