5

I have this code:

string certificateFilePath = @"C:\Users\Administrator\Documents\Certificate.pfx";

string certificateFilePassword = "Some Password Here";

X509Certificate clientCertificate = new X509Certificate(certificateFilePath, certificateFilePassword);

TcpClient client = new TcpClient(host, port);

SslStream stream = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true);

X509CertificateCollection clientCertificates = new X509CertificateCollection {clientCertificate};

stream.AuthenticateAsClient(host, clientCertificates, SslProtocols.Tls, false);

When I run the code in a Console Application, everything works fine, stream.IsAuthenticated and stream.IsMutuallyAuthenticated return true and stream.LocalCertificate contains the correct certificate object.

However when running the exact same code in a Windows Service (as LOCAL SYSTEM user), although stream.IsAuthenticated returns true, stream.IsMutuallyAuthenticated returns false and stream.LocalCertificate returns null.

This happens while in both scenarios, after the first line is ran clientCertificate loads the correct certification data and contains the correct information for Certificate's Subject and Issuer.

I have also tried forcing the SslStream to pick the Certificate using this code:

string certificateFilePath = @"C:\Users\Administrator\Documents\Certificate.pfx";

string certificateFilePassword = "Some Password Here";

X509Certificate clientCertificate = new X509Certificate(certificateFilePath, certificateFilePassword);

TcpClient client = new TcpClient(host, port);

SslStream stream = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true, (sender, host, certificates, certificate, issuers) => clientCertificate);

X509CertificateCollection clientCertificates = new X509CertificateCollection {clientCertificate};

stream.AuthenticateAsClient(host, clientCertificates, SslProtocols.Tls, false);

However the code still doesn't work and stream.IsMutuallyAuthenticated returns false and stream.LocalCertificate returns null.

I have been exploring this for a few days now and I can't figure it out. Any help is highly appreciated.

Edit: After trying the certificate with WinHttpCertCfg tool it turns out that unlike similar question(s), LOCAL SYSTEM account already has access to the private key for the target certificate as you can see in the picture below: Output from WinHttpCertCfg tool for the target certificate Therefore the problem still remains unsolved.

Rojan Gh.
  • 1,062
  • 1
  • 9
  • 32
  • What happens if you attempt to run the service as `NETWORK SERVICE` or `LOCAL SERVICE` instead of `LOCAL SYSTEM`? – Camilo Terevinto Jul 10 '17 at 15:27
  • @CamiloTerevinto I'm gonna try it and come back to you in a few minutes. – Rojan Gh. Jul 10 '17 at 15:28
  • Exact same result with `NETWORK SERVICE`, `LOCAL SERVICE` and `LOCAL SYSTEM` @CamiloTerevinto – Rojan Gh. Jul 10 '17 at 15:42
  • Can you run it as administrator and let us know? – JuanR Jul 10 '17 at 15:48
  • It runs perfectly as Administrator @Juan – Rojan Gh. Jul 10 '17 at 15:49
  • I thought so. What are you passing for certificationFilePath? Can you obscure the data and post a sample of what you are using as a parameter? – JuanR Jul 10 '17 at 15:55
  • I am passing the absolute path to the file. I'm going to update the code with sample data @Juan – Rojan Gh. Jul 10 '17 at 16:04
  • I have updated the code with some sample settings similar to what I'm using @Juan – Rojan Gh. Jul 10 '17 at 16:08
  • I see. I believe you need to grant the account access to the private key. I will mark this as a possible duplicate. Check this out: https://stackoverflow.com/questions/4151401/avoiding-administrator-access-for-sslstream-authenticateasclient/4194126 – JuanR Jul 10 '17 at 17:26
  • Possible duplicate of [Avoiding administrator access for SslStream.AuthenticateAsClient?](https://stackoverflow.com/questions/4151401/avoiding-administrator-access-for-sslstream-authenticateasclient) – JuanR Jul 10 '17 at 17:27
  • @Juan I have updated the question with output from WinHttpCertCfg tool showing that LOCAL SYSTEM account has already had access to the private keys and therefore this is not a similar question to the one you have suggested. – Rojan Gh. Jul 12 '17 at 12:17
  • Got it. How about file permissions? The problem is clearly security-related as it runs fine as an Administrator. – JuanR Jul 12 '17 at 13:46
  • LOCAL SYSTEM has had full permissions for Certificate file @Juan – Rojan Gh. Jul 13 '17 at 08:30

2 Answers2

3

I finally made the code work while playing around with X509 classes.

Here is the code which works for me:

string host = "The Host";

int port = 777;

string certificateFilePath = @"C:\Users\Administrator\Documents\Certificate.pfx";

string certificateFilePassword = "Some Password Here";

X509Certificate clientCertificate = new X509Certificate(certificateFilePath, certificateFilePassword);

X509Certificate2 clientCertificate2 = new X509Certificate2(clientCertificate); //<== Create a X509Certificate2 object from the X509Certificate which was loaded from the file. The clientCertificate2 loads the proper data

TcpClient client = new TcpClient(host, port);

SslStream stream = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true);

X509CertificateCollection clientCertificates = new X509CertificateCollection { clientCertificate2 }; //<== Using the clientCertificate2 which has loaded the proper data instead of the clientCertificate object

stream.AuthenticateAsClient(host, clientCertificates, SslProtocols.Tls, false);

This way my code finds the proper X509Store, certificate and Private Key from the system.

I have figured this out by experience. I couldn't find a clear explanations on why it should be like this on MSDN though.

Rojan Gh.
  • 1,062
  • 1
  • 9
  • 32
0

One debugging technique we use for such service issues is to use an utility like PsExec. This will allow you to run an interactive process as the necessary service account.

-s Run the process in the System account.

The help content would say "remote process", but it can be used for local process as well.

For e.g., The below would get you a command prompt as system account, after running the below in a command prompt with Admin rights

PsExec.exe -s cmd

In the command window, you can check by using the WhoAmI command

C:\Windows\system32>whoami
nt authority\system

This would enable you to do some interactive tests to try different combinations.

The SYSTEM account has the maximum possible privilege in the local machine.

You would also have seen that creating a custom account to run a service is the recommended option. But, this has the over-head of maintaining another password. In newer versions of Windows there is a managed service accounts. This may be a better option.

the managed service account and the virtual account—are designed to provide applications with the isolation of their own accounts, while eliminating the need for an administrator to manually administer the SPN and credentials for these accounts.

Subbu
  • 2,130
  • 1
  • 19
  • 28
  • 1
    If you read the comments on my question, I have already tried to run the process in `NETWORK SERVICE`, `LOCAL SERVICE` and `LOCAL SYSTEM` to see if there is any permission issue and etc, and I have been using `PsExec.exe` either with `-s` flag or other commands to do so, but it didn't help. – Rojan Gh. Jul 14 '17 at 08:21