+1 vote
by (240 points)

Hello,

I have a Windows service that transfers files via FTPS. The service normally runs under a Windows user that was set up to have appropriate permissions to access various Windows shares.

When the service runs under this Windows user, I receive the following log when trying to upload a file to an FTPS server:

2015-04-13 12:48:14.086 INFO Ftp(1) Info: Connecting to XXXXXXXX:21 using Ftp 4.0.5577.0.
2015-04-13 12:48:14.086 INFO Ftp(1) Info: Using proxy none.
2015-04-13 12:48:14.115 INFO Ftp(1) Response: 220 (vsFTPd 2.2.2)
2015-04-13 12:48:14.122 INFO Ftp(1) Command: AUTH TLS
2015-04-13 12:48:14.122 INFO Ftp(1) Response: 234 Proceed with negotiation.
2015-04-13 12:48:14.142 INFO Ftp(1) TLS: State StateChange:Negotiating
2015-04-13 12:48:14.142 DEBUG Ftp(1) TLS: HandshakeMessage:ClientHello was sent.
2015-04-13 12:48:14.161 DEBUG Ftp(1) TLS: HandshakeMessage:ServerHello was received.
2015-04-13 12:48:14.165 DEBUG Ftp(1) TLS: HandshakeMessage:Certificate was received.
2015-04-13 12:48:14.167 DEBUG Ftp(1) TLS: HandshakeMessage:CertificateRequest was received.
2015-04-13 12:48:14.167 DEBUG Ftp(1) TLS: HandshakeMessage:ServerHelloDone was received.
2015-04-13 12:48:15.211 INFO Ftp(1) TLS: Certificate verification status: UnknownRev, OfflineRev (0)
2015-04-13 12:48:15.216 INFO Ftp(1) TLS: Alert Alert:Alert was sent.
2015-04-13 12:48:15.216 INFO Ftp(1) TLS: State StateChange:Closed
2015-04-13 12:48:15.227 ERROR Ftp(1) Info: Rebex.Net.TlsException: Unable to perform revocation check of the server certificate. ---> Rebex.Net.TlsException: Unable to perform revocation check of the server certificate. ---> Rebex.Net.TlsException: Unable to perform revocation check of the server certificate.
at Rebex.Net.WHB.CE(String A, CertificateChain B)
at Rebex.Net.WHB.EE(Byte[] A, Int32 B, Int32 C, HHB D)
at Rebex.Net.WHB.KC(Byte[] A, Int32 B, Int32 C)
at Rebex.Net.VHB.CD(Byte[] A, Int32 B, Int32 C)
at Rebex.Net.VHB.HD()
--- End of inner exception stack trace ---
at Rebex.Net.VHB.HD()
at Rebex.Net.VHB.PD()
at Rebex.Net.TlsSocket.Negotiate()
at Rebex.Net.CS.UB(TlsParameters A)
at Rebex.Net.Ftp.KN(TlsParameters A, FtpSecureUpgradeType B)
at Rebex.Net.Ftp.BN(String A, Int32 B, TlsParameters C, FtpSecurity D)
--- End of inner exception stack trace ---
at Rebex.Net.Ftp.BN(String A, Int32 B, TlsParameters C, FtpSecurity D)

I noticed a previous question regarding this error at http://forum.rebex.net/4326/unable-to-perform-revocation-check-the-server-certificate . That page mentioned to try to run the service as the LocalSystem user and see if it worked. Indeed, when running as the LocalSystem user, the file transfer works, as shown below:

2015-04-13 12:43:39.096 INFO Ftp(1) Info: Connecting to XXXXXXXX:21 using Ftp 4.0.5577.0.
2015-04-13 12:43:39.096 INFO Ftp(1) Info: Using proxy none.
2015-04-13 12:43:39.127 INFO Ftp(1) Response: 220 (vsFTPd 2.2.2)
2015-04-13 12:43:39.133 INFO Ftp(1) Command: AUTH TLS
2015-04-13 12:43:39.134 INFO Ftp(1) Response: 234 Proceed with negotiation.
2015-04-13 12:43:39.154 INFO Ftp(1) TLS: State StateChange:Negotiating
2015-04-13 12:43:39.154 DEBUG Ftp(1) TLS: HandshakeMessage:ClientHello was sent.
2015-04-13 12:43:39.173 DEBUG Ftp(1) TLS: HandshakeMessage:ServerHello was received.
2015-04-13 12:43:39.176 DEBUG Ftp(1) TLS: HandshakeMessage:Certificate was received.
2015-04-13 12:43:39.178 DEBUG Ftp(1) TLS: HandshakeMessage:CertificateRequest was received.
2015-04-13 12:43:39.178 DEBUG Ftp(1) TLS: HandshakeMessage:ServerHelloDone was received.
2015-04-13 12:43:39.312 DEBUG Ftp(1) TLS: HandshakeMessage:Certificate was sent.
2015-04-13 12:43:39.321 DEBUG Ftp(1) TLS: HandshakeMessage:ClientKeyExchange was sent.
2015-04-13 12:43:39.354 DEBUG Ftp(1) TLS: CipherSpec:ChangeCipherSpec was sent.
2015-04-13 12:43:39.355 DEBUG Ftp(1) TLS: HandshakeMessage:Finished was sent.
2015-04-13 12:43:39.373 DEBUG Ftp(1) TLS: CipherSpec:ChangeCipherSpec was received.
2015-04-13 12:43:39.375 DEBUG Ftp(1) TLS: HandshakeMessage:Finished was received.
2015-04-13 12:43:39.375 INFO Ftp(1) TLS: State StateChange:Secured
2015-04-13 12:43:39.376 INFO Ftp(1) TLS: Connection secured using cipher: TLS 1.1, RSA, 168bit TripleDES in CBC mode, SHA1

However, my service needs to run under a normal Windows user so that it may access Windows shares.

What permissions might I need to change to allow my normal Windows user to verify the certificate?

Note: the certificate that I am verifying in this test is one that was created by our own internal certificate authority. I have installed our certificate authority in the Trusted Root Certification Authorities on this machine. (Just in case that makes a difference.)

Thanks!

Applies to: Rebex FTP/SSL
by (240 points)
I just tested the certificate verification on a normal SSL certificate signed by Thawte, and it also succeeds only when the LocalSystem user is running the Windows service.  It also fails when the service is run as the regular Windows user.  So my custom certificate authority had nothing to do with the failure I mentioned in the first example.  It strictly seems to be a LocalSystem vs. normal Windows user issue.  What steps can I take to allow the normal Windows user to validate SSL certificates?  What permissions would I need to grant?

2 Answers

0 votes
by (58.9k points)
selected by
 
Best answer

Hello, thanks for getting back to us! The fact that the .NET code I gave in my first answer works fine means that there is actually no problem accessing either the LocalMachine or CurrentUser certificate stores as I previously thought.

But something is obviously still wrong with the verification under the CurrentUser profile. Under this environment, please try to download the certificate and verify the certificate using .NET system objects only like this:

using System;
using Rebex.Net;
using Rebex.Security.Certificates;
using System.Security.Cryptography.X509Certificates;
...

    // 1. Use Rebex FTP to download the server certificate into a file
    Ftp client = new Ftp();
    client.Settings.SslAcceptAllCertificates = true;
    client.Connect("server", SslMode.Implicit);
    client.TlsSocket.ServerCertificate[0].Save("cert.der", CertificateFormat.Base64Der);

    // 2. Validate the certificate (from the file) without using any Rebex code

    X509Certificate2 cert = new X509Certificate2("cert.der");

    X509ChainPolicy policy = new X509ChainPolicy();
    policy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
    policy.RevocationMode = X509RevocationMode.Online;

    X509Chain chain = new X509Chain(false);
    try
    {
        chain.ChainPolicy = policy;

        bool valid = chain.Build(cert);
        Console.WriteLine("Is valid: {0}", valid);
        foreach (X509ChainStatus s in chain.ChainStatus)
        {
            X509ChainStatusFlags flags = s.Status;

            Console.WriteLine("Status: {0}", flags);
        }
    }
    finally
    {
        chain.Reset();
    }

What results does the code above show if you run it as a service under the windows user? Thank you for cooperation. Seeing the result will enable us go on with finding a solution.

by (240 points)
Thank you again for responding to my question.

When running the .NET verification code you gave above, I receive the following errors when running as the Windows user:

Is valid: False
Status: PartialChain
Status: RevocationStatusUnknown
Status: OfflineRevocation

But if I then change the service to run as LocalSystem, I get the following success message:

Is valid: True

I hope this helps in the diagnosis of the problem.  Please let me know if I can do anything else.

Thank you!
by (240 points)
Thanks to the code that you gave me to try, I was able to do some better searching, and I found the answer to the problem!

It turns out that the Windows user that the service was running under had a proxy server set up in Internet Explorer.  However, it was pointed to a local proxy server that is no longer installed on this machine, so all requests through Internet Explorer would fail.  I never log in as this special user, as it's mainly used to grant Windows permissions for various shares and so forth.

Once I removed the proxy server setting from Internet Explorer under this user, then the revocation certificate could successfully be downloaded and checked, and everything works fine.

Sorry to have bothered you with this, but thank you for your help.  Without your assistance, I wouldn't have known where to search to find the answer.

Thanks!
by (58.9k points)
Thanks for getting back to us with the conclusion and I am glad that you have a working solution now!
0 votes
by (58.9k points)

It looks as though the certificate store is not accessible in the case when you run the Windows service under the user account. (We have searched and found that sometimes some user profiles fail to be loaded in cases when Windows service is run under the user account which might be the reason for the failing certificate validation).

If you want to see whether the problem is in it, just try the code below - it does not use any Rebex classes, and the code opens and enumerates through the LocalSystem and CurrentUser certificate stores. If this code fails then the problem is definitely not in Rebex components:

var localStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
localStore.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 c in localStore.Certificates)
{
    //_log.Debug("local: certificate for {0} - has private key {1}".ToFormat(c.SubjectName.Name, c.HasPrivateKey));
}
localStore.Close();

var userStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
userStore.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 c in userStore.Certificates)
{
    //_log.Debug("user: certificate for {0} - has private key {1}".ToFormat(c.SubjectName.Name, c.HasPrivateKey));
}
userStore.Close();

As a workaround we suggest you to write your own certificate validation handler which will use the LocalSystem certificate engine like this:

ftp.ValidatingCertificate += (sender, e) =>
{
    ValidationResult result = e.CertificateChain.Validate(e.ServerName, ValidationOptions.None, CertificateChainEngine.LocalMachine);
    if (result.Valid)
        e.Accept();
    else
        e.Reject(result.Status);
};

Please let me know whether you are able to validate the certificate using the CertificateChainEngine.LocalMachine.

by (240 points)
Thank you very much for attempting to answer my question.  I appreciate it.

I ran the non-Rebex .NET code that you gave above, and it works successfully, whether the service is running as the Windows user or if the service is running as LocalSystem.  It always works.  In both cases, it finds 313 certificates in the local store and 313 certificates in the user store.  So it's returning the same certificate list under both users.

I also attempted your certificate validation handler workaround.  When running the service as the Windows user, I now get the following error:

>2015-04-14 17:23:00.081 ERROR Ftp(4) Info: Rebex.Net.TlsException: Server certificate was rejected by the verifier because it is bad. ---> Rebex.Net.TlsException: Server certificate was rejected by the verifier because it is bad. ---> Rebex.Net.TlsException: Server certificate was rejected by the verifier because it is bad. ---> Rebex.Net.TlsException: Server certificate was rejected by the verifier because it is bad.
at Rebex.Net.WHB.CE(String A, CertificateChain B)
at Rebex.Net.WHB.EE(Byte[] A, Int32 B, Int32 C, HHB D)
at Rebex.Net.WHB.KC(Byte[] A, Int32 B, Int32 C)
at Rebex.Net.VHB.CD(Byte[] A, Int32 B, Int32 C)
at Rebex.Net.VHB.HD()
--- End of inner exception stack trace ---
at Rebex.Net.VHB.HD()
at Rebex.Net.VHB.PD()
at Rebex.Net.TlsSocket.Negotiate()
at Rebex.Net.CS.UB(TlsParameters A)
at Rebex.Net.Ftp.BN(String A, Int32 B, TlsParameters C, FtpSecurity D)
--- End of inner exception stack trace ---
at Rebex.Net.Ftp.BN(String A, Int32 B, TlsParameters C, FtpSecurity D)
--- End of inner exception stack trace ---
at Rebex.Net.Ftp.BN(String A, Int32 B, TlsParameters C, FtpSecurity D)

However, when I run the service as LocalSystem, the certificate validates successfully.  So the program still works only when running as LocalSystem.

Any other ideas why this might be failing when running as the Windows user?
...