5

The C# code below, which logs in to a website,

  • Works when called on computer A from Excel 2010 VBA via com-interop
  • Works when called on computer B from a C# console application, but
  • Fails when called on computer B from Excel 2010 VBA via com-interop

The main difference between computer A and computer B is that computer A has windows 10 version 1803, whereas computer B has windows 10 version 1809. Both computers have Studio 2017, and in all cases the target .Net Framework is 4.6.2.

using System; using System.Runtime.InteropServices; using System.IO; [Guid("97E1D9DB-8478-4E56-9D6D-26D8EF13B100")] [ComVisible(true)] public interface IToExcel { string Do(); } [Guid("BBF87E31-77E2-46B6-8093-1689A144BFC6")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public class Main : IToExcel { private const string XAPP_ID = "..."; private const string USERNAME = "..."; private const string PASSWORD = "..."; private const string CERT_FILE = @"..."; private const string CERT_PASSWORD = "..."; private const string WEBSITE = "https:// ..."; public string Do() { System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(new Uri(WEBSITE)); request.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate; request.Method = "POST"; request.Accept = "application/json"; request.Timeout = request.ReadWriteTimeout = 20000; request.ContentType = "application/x-www-form-urlencoded"; request.UseDefaultCredentials = true; request.Proxy = null; // setup headers System.Net.WebHeaderCollection whc = new System.Net.WebHeaderCollection { { "X-Application", XAPP_ID }, { System.Net.HttpRequestHeader.AcceptCharset, "utf-8" }, { System.Net.HttpRequestHeader.AcceptEncoding, "gzip,deflate" } }; request.Headers.Add(whc); // setup certificate System.Security.Cryptography.X509Certificates.X509Certificate2 m_x509certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(CERT_FILE, CERT_PASSWORD); request.ClientCertificates.Add(m_x509certificate); // do call using (Stream stream = request.GetRequestStream()) { using (StreamWriter writer = new StreamWriter(stream, System.Text.Encoding.Default)) { writer.Write("username=" + USERNAME + "&password=" + PASSWORD); } } string responseData = string.Empty; using (System.Net.WebResponse response = request.GetResponse()) { using (Stream responseStream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(responseStream, System.Text.Encoding.UTF8)) { responseData = reader.ReadToEnd(); } } } return responseData; } } 

In all cases a little JSON object is returned, where the JSON object has a field called "loginStatus". When it works, "loginStatus"="SUCCESS", but when it fails "loginStatus"="CERT_AUTH_REQUIRED".

I tried looking at all the settings in System.Net.ServicePointManager but in all cases the settings were the same:

  • ReusePort: False
  • ServerCertificateValidationCallback:
  • DnsRefreshTimeout: 120000
  • EnableDnsRoundRobin: False
  • Expect100Continue: True
  • UseNagleAlgorithm: True
  • MaxServicePointIdleTime: 100000
  • DefaultConnectionLimit: 2
  • MaxServicePoints: 0
  • SecurityProtocol: Tls, Tls11, Tls12
  • CheckCertificateRevocationList: False
  • EncryptionPolicy: RequireEncryption

Beyond that, I don't know what else to check. Upgrading from .Net 4.6.2 to 4.7.1 had no effect, the results were the same.

I had wondered whether this is a bug in Windows 1809, but since it works when called directly in a .Net console application I assume it's some subtle configuration issue. Can anyone help me get this working from Excel 2010 on computer B ?

Update 8-Feb-2019

As suggested in the comments, I used Fiddler to review the structure of the https calls that are being made to the website. The two that work look identical, and the one that fails looks slightly different:

Calls that work OK

  • TLS extension ec_point_formats = uncompressed [0x0]
  • TLS extension encrypt_then_mac (RFC7366) not specified
  • TLS extension renegotiation_info = 0
  • Cipher TLS_EMPTY_RENEGOTIATION_INFO_SCSV not specified

Failed call

  • TLS extension ec_point_formats = uncompressed [0x0], ansiX962_compressed_prime [0x1], ansiX962_compressed_char2 [0x2]
  • TLS extension encrypt_then_mac (RFC7366) = empty
  • TLS extension renegotiation_info not specified
  • Cipher TLS_EMPTY_RENEGOTIATION_INFO_SCSV specified

But now I have that information, I'm not sure if it helps. Perhaps the calls that work are both generated by the same low level code (in spite of the fact that they're on different versions of Windows 10), and the call that fails is generated by different low level code.

Update 10-Feb-2019

When calling from Excel via com-interop, I made the code execute the EXE in a new AppDomain instead of calling the login code directly. And when I did that, the EXE failed to work and produced the same output as if I'd called the login code directly.

Below is some of the Visual Studio Output window which shows the order in which DLLs are loaded when the EXE file runs, just before the login code executes. The biggest difference between both the scenarios which succeed and the scenario which fails is that the scenario which fails never loads C:\Windows\System32\ncryptprov.dll. Does anyone know what causes that DLL to be loaded?

(Win32): Loaded 'C:\Windows\System32\msisip.dll'

(Win32): Loaded 'C:\Windows\System32\coml2.dll' --- LOADED EARLIER FROM EXCEL

(Win32): Loaded 'C:\Windows\System32\wshext.dll'

(Win32): Loaded 'C:\Windows\System32\AppxSip.dll'

(Win32): Loaded 'C:\Windows\System32\tdh.dll'

(Win32): Loaded 'C:\Windows\System32\xmllite.dll'

(Win32): Loaded 'C:\Windows\System32\OpcServices.dll'

(Win32): Loaded 'C:\Windows\System32\mintdh.dll'

(Win32): Loaded 'C:\Windows\System32\urlmon.dll' --- LOADED EARLIER FROM EXCEL

(Win32): Loaded 'C:\Windows\System32\mintdh.dll'

(Win32): Unloaded 'C:\Windows\System32\mintdh.dll'

(Win32): Loaded 'C:\Windows\System32\iertutil.dll' --- LOADED EARLIER FROM EXCEL

(Win32): Loaded 'C:\Windows\System32\WindowsPowerShell\v1.0\pwrshsip.dll'

(Win32): Loaded 'C:\Windows\System32\EsdSip.dll'

(Win32): Loaded 'C:\Windows\System32\userenv.dll' --- LOADED EARLIER FROM EXCEL

(Win32): Loaded 'C:\Windows\System32\dpapi.dll'

(Win32): Loaded 'C:\Windows\System32\dnsapi.dll'

(Win32): Loaded 'C:\Windows\System32\rasadhlp.dll'

(Win32): Loaded 'C:\Windows\System32\FWPUCLNT.DLL'

(Win32): Loaded 'C:\Windows\System32\secur32.dll'

(Win32): Loaded 'C:\Windows\System32\sspicli.dll' --- LOADED EARLIER FROM EXCEL

(Win32): Loaded 'C:\Windows\System32\schannel.dll'

(Win32): Loaded 'C:\Windows\System32\mskeyprotect.dll'

(Win32): Loaded 'C:\Windows\System32\ncrypt.dll'

(Win32): Loaded 'C:\Windows\System32\ntasn1.dll'

(Win32): Loaded 'C:\Windows\System32\ncryptprov.dll' --- NOT EVER LOADED FROM EXCEL

(Win32): Loaded 'C:\Windows\System32\ncryptsslp.dll'

At this point the C# code executes

Update 12-Feb-2019

Many many thanks to Simon Mourier for telling me how to setup the System.Net diagnostics. Running the diagnostics on computer B, the "System.Net information" rows that get output for the two cases start off the same, but eventually there is a difference. This is the output from the Console EXE file on Computer B (i.e. the case that works):

System.Net Information: 0 : [35268] Current OS installation type is 'Client'. System.Net Information: 0 : [35268] RAS supported: True System.Net Information: 0 : [35268] Associating HttpWebRequest#21454193 with ServicePoint#34640832 System.Net Information: 0 : [35268] Associating Connection#43332040 with HttpWebRequest#21454193 System.Net Information: 0 : [35268] Connection#43332040 - Created connection from XXX.XXX.XXX.XXX:53002 to YYY.YYY.YYY.YYY:443. System.Net Information: 0 : [35268] TlsStream#54444047::.ctor(host=<TargetWebSite>, #certs=1, checkCertificateRevocationList=False, sslProtocols=Tls12) System.Net Information: 0 : [35268] Associating HttpWebRequest#21454193 with ConnectStream#20234383 System.Net Information: 0 : [35268] HttpWebRequest#21454193 - Request: POST /api/certlogin HTTP/1.1 System.Net Information: 0 : [35268] ConnectStream#20234383 - Sending headers System.Net Information: 0 : [35268] SecureChannel#47891719::.ctor(hostname=<TargetWebSite>, #clientCertificates=1, encryptionPolicy=RequireEncryption) System.Net Information: 0 : [35268] Enumerating security packages: System.Net Information: 0 : [35268] Negotiate System.Net Information: 0 : [35268] NegoExtender System.Net Information: 0 : [35268] Kerberos System.Net Information: 0 : [35268] NTLM System.Net Information: 0 : [35268] TSSSP System.Net Information: 0 : [35268] pku2u System.Net Information: 0 : [35268] CloudAP System.Net Information: 0 : [35268] WDigest System.Net Information: 0 : [35268] Schannel System.Net Information: 0 : [35268] Microsoft Unified Security Protocol Provider System.Net Information: 0 : [35268] Default TLS SSP System.Net Information: 0 : [35268] CREDSSP System.Net Information: 0 : [35268] SecureChannel#47891719 - Attempting to restart the session using the user-provided certificate: [Version] System.Net Information: 0 : [35268] SecureChannel#47891719 - Left with 1 client certificates to choose from. System.Net Information: 0 : [35268] SecureChannel#47891719 - Trying to find a matching certificate in the certificate store. System.Net Information: 0 : [35268] SecureChannel#47891719 - Locating the private key for the certificate: [Version] System.Net Information: 0 : [35268] SecureChannel#47891719 - Certificate is of type X509Certificate2 and contains the private key. System.Net Information: 0 : [35268] SecureChannel#47891719::.AcquireClientCredentials, new SecureCredential() (flags=(ValidateManual, NoDefaultCred, SendAuxRecord, UseStrongCrypto), m_ProtocolFlags=(Tls12Client), m_EncryptionPolicy=RequireEncryption) System.Net Information: 0 : [35268] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent = Outbound, scc = System.Net.SecureCredential) System.Net Information: 0 : [35268] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = (null), targetName = <TargetWebSite>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation) System.Net Information: 0 : [35268] InitializeSecurityContext(In-Buffer length=0, Out-Buffer length=184, returned code=ContinueNeeded). System.Net Information: 0 : [35268] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 227c85a89b0:2449d0deff0, targetName = <TargetWebSite>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation) System.Net Information: 0 : [35268] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded). 

However, when running from Excel 2010 via com-interop, instead of those last 4 InitializeSecurityContext lines, there are 6 InitializeSecurityContext lines as follows:

System.Net Information: 0 : [39988] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = (null), targetName = <TargetWebSite>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation) System.Net Information: 0 : [39988] InitializeSecurityContext(In-Buffer length=0, Out-Buffer length=184, returned code=ContinueNeeded). System.Net Information: 0 : [39988] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 8a8e2f0:2449d0def90, targetName = <TargetWebSite>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation) System.Net Information: 0 : [39988] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded). System.Net Information: 0 : [39988] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 8a8e2f0:2449d0def90, targetName = <TargetWebSite>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation) System.Net Information: 0 : [39988] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded). 

The first two InitializeSecurityContext lines are the identical, so presumably crucial difference is on the third InitializeSecurityContext line, where the console EXE has

context = 227c85a89b0:2449d0deff0

but the failed run via com-interop executino has

context = 8a8e2f0:2449d0def90

After that, things don't look the same, as one would expect. Does anyone know what that difference means, and how to make the difference go away so that the com-interop execution behaves in the same way as the com-interop execution?

Update 13-Feb-2019

I have posted more of the diagnostic output on an MSDN forum.

14
  • 1
    Have you compared the requests with Fiddler or online sandbox like webhook.site? Commented Feb 6, 2019 at 10:17
  • Thanks @omegastripes , no, I am not an expert in this so I was unaware of both. I will try :-) Commented Feb 6, 2019 at 10:27
  • I'd focus on the Console vs Excel scenario on the same machine. I guess code using Excel and code using Console doesn't use the same .NET Framework layer underneath, so inner settings are probably different. There's been a great disturbance in the SSL force over the past months. learn.microsoft.com/en-us/dotnet/framework/network-programming/… can you try to define ServicePointManager.SecurityProtol = 0 ? Commented Feb 9, 2019 at 9:58
  • 1
    Is there's only 1 certificate? stackoverflow.com/questions/39528973/… otherwise you can try to add some traces stackoverflow.com/questions/3808016/… .ncryptprov is Microsoft Key Storage Provider (KSP, an essential library for CNG, modern Windows crypto) thing is it looks like both codes take different crypto routes for some reason... Commented Feb 11, 2019 at 8:20
  • 1
    Your MSDN traces show you don't end up with the same certificate issuer. I bet your server doesn't like the excel one. Or stop your anti virus and see if it wouldn't fix your issue Commented Feb 13, 2019 at 8:33

1 Answer 1

2
+200

Focusing on the C# console vs Excel 2010 issue (same machine, both running as 64-bit processes), traces have been added as explained here: https://stackoverflow.com/a/25683524/403671

The initial traces from Excel and Console are exactly the same (see the question for more detail). But the last traces (posted to MSDN) exhibit this:

Console:

System.Net Information: 0 : [35268] Remote certificate: [Version] V3 [ Lots of lines describing a certificate with [Subject]=<TargetWebSite>, [Issuer]=HydrantID SSL ICA G2, etc] System.Net Information: 0 : [35268] SecureChannel#47891719 - Remote certificate was verified as valid by the user. 

Excel:

System.Net Information: 0 : [39988] Remote certificate: [Version] V3 [ Lots of lines describing a certificate with [Subject]=<TargetWebSite>, [Issuer]=Kaspersky, etc] System.Net Information: 0 : [39988] SecureChannel#2383799 - Remote certificate was verified as valid by the user. 

It shows the certificate used in Excel is now issued by Kaspersky, which turns out to be the anti-virus product running on the PC. In fact, it's a Kaspersky product security feature to intercept communications, and this causes numerous issues, like KIS Interfering with Git, for example.

Once this anti-virus is removed everything works as expected.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.