0

I've made an experimental email and password system using post requests to my Wix site API and I've verified it works through curl and postman. When trying to use it in my VB.NET app however I get the error: "The underlying connection was closed: An unexpected error occurred on a receive." I already previously ran into a "Could not create SSL/TLS secure channel" error but that was resolved when I moved the .NET framework for the project to 4.7.1 and made sure SecurityProtocol was set to SystemDefault as Wix seems to use TLS 1.3. Now however I'm getting this error on receive that I have no idea how to resolve as everything in the request seems to look ok:

ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault
Dim req As HttpWebRequest = DirectCast(WebRequest.Create(New Uri("https://xxxxx.com/mysite/_functions/check?email=" & email & "&pass=" & LCase(passHash))), HttpWebRequest)
req.Method = "POST"
req.ContentType = "none"
req.ContentLength = 0
req.Host = "test"
req.KeepAlive = True

Dim result As HttpWebResponse = req.GetResponse()

The error is thrown when running 'req.GetReponse()'. After looking at countless versions of sample code for .NET POST requests and questions about this error but everyone seems to suggest setting the security prococol type, which I have done and setting it as the system default is the only way I don't get an internal server error status. Another was setting KeepAlive to false, which I have also tried but didn't make any difference.

What could be the problem here? Here is what's included in the postman header if this is of help: enter image description here

Shan
  • 948
  • 1
  • 9
  • 17
Letal1s
  • 47
  • 2
  • 9
  • *...made sure SecurityProtocol was set to SystemDefault*: this is already the default setting if you don't specify a `SecurityProtocol` explicitly. You didn't mention in what System this code is run. In Windows 10 (8.0+), the default is TLS12, in Windows 7 / Windows Server 2008 R2, it's SSL3 / TLS1.0, unless someone or *something* has tampered with it. modifying the Registry. You need to explain in which context this code is run. About the `KeepAlive` setting, read the notes here: [Understand HttpWebRequest in KeepAlive mode](https://stackoverflow.com/a/49609131/7444103) – Jimi Jan 02 '21 at 08:49
  • Also, note that if this code is run in a System prior to Windows 8.1, the Cipher Suites also play a role. Some Servers, in a TLS12 handshake, try to exchange the newer Cipher Suites this protocol allows: Windows 7 / Server 2008 R2 don't have these Cipher Suites and never will, since both have reached end-of-life. Thus, even if TLS12 is agreed upon in the handshake, the Cipher Suites (next step) may not be available, since the Server may not be configured to accept a lower-security Cipher Suite and it will deny the Connection. The same with TLS13, available from .Net 4.8+ – Jimi Jan 02 '21 at 10:28
  • Yeah I figured it was the default setting as the name is self explanatory but I was just ensuring nothing else was changing it for some reason. I am running this on Windows 10 but may have it on other systems such as 7 or 8, although for now just getting it to work on my currently machine would be fine. So would this mean that this request wouldn't even work on older machines? The request is being sent from a login form that just double checks user credentials in a database. After reading your link from the KeepAlive page I don't think I'll need it so thanks for that clarification. – Letal1s Jan 02 '21 at 14:40
  • As mentioned, WebRequest works in older Systems (Windows 7 and earlier) if the Server doesn't require a Cipher Suite that the Client doesn't have. Also, you need to explicitly set `ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12`. some Servers also require the `User-Agent` Header to be set (in this case, use the `IE11` Header, not a recent version of a Browser as FireFox or Chrome - long story). Others that you provide Credentials, or an auth Token, or a valid Client Certificate. On the Client side, If the Server doesn't provide Credentials that can be verified, [...] – Jimi Jan 02 '21 at 14:56
  • [...] you may need to authenticate the Server Certificate(s) yourself (if you trust the Server, that is, you cannot do that for every Server). In this case, setup a `ServerCertificateValidationCallback` that just returns `true`. See the code in the post I've already linked: there's a note related to this. Set a breakpoint in the Callback, when the line `If (PolicyErrors = SslPolicyErrors.None) Then Return True` is hit, check whether you have Policy errors there. – Jimi Jan 02 '21 at 14:56
  • Okay, the only issue is I can't set the security protocol as TLS12 as I then get a "Could not create SSL/TLS secure channel" error. After checking the certificate of the website in my browser it seems to use TLS13 which is why I set the security protocol back to the system default. I know that I don't need to provide credentials or an auth token since I have made the exact same request from curl without any issues, also without authenticating the server certificate myself. It's not as if I haven't sent any requests to the API before so I don't know what I need, as the same request with curl – Letal1s Jan 02 '21 at 15:22
  • and postman worked fine. Would showing the curl command I used previously for the same request be of any use? If not I'll try giving manually authenticating the certificate a go. – Letal1s Jan 02 '21 at 15:23
  • When you use a WebBrowser, the browser will try to negotiate the most secure SSL version it has available, which is now `Tls13`. Unless your Server is configured to **only** use `Tls13` (which would be weird), it should accept `Tls12`. Check the Server requirements. Try to use .Net 4.8 and set `Tls13` explicitly, to test the results. `SystemDefault` is `Tls12` in Windows 10. See the notes and test code here: [Which TLS version was negotiated?](https://stackoverflow.com/a/48675492/7444103) – Jimi Jan 02 '21 at 15:30
  • BTW, remember to set the `User-Agent` Header as described. – Jimi Jan 02 '21 at 15:39
  • Okay so here's where it gets weird. SystemDefault should be Tls12, although when explicitly setting to Tls12 I get the could not create secure channel error. Although when set to SystemDefault or Tls13 I get the original error in the question which is the unexpected error on receive. It seems as though Tls12 is failing so it switches to Tls13? I've also set the user-agent to the latest chrome. – Letal1s Jan 02 '21 at 15:40
  • I doubt it, the handshake goes from the top level version the Server has available to the top level version the Client has available. If it's not enough (or lacks *features*), the Server will refuse it. As mentioned, check the Server configuration. -- Try to install .Net 4.8 and test the code you find in the post linked in my previous comment. The User-Agent Header (which *might* be required - and since it might be, you always set it) is `req.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident / 7.0; rv: 11.0) like Gecko"` – Jimi Jan 02 '21 at 15:48
  • Okay I added a CertificateValidation function and set it to the certificate vallidation callback like you described in the question you linked and the breakpoint doesn't even get chance to run. The "unexpected error occurred on a receive" seems to happen way before the certificate for the server can even be validated. – Letal1s Jan 02 '21 at 16:01
  • Yes, but using what code, Framework version and SecurityProtocol setting (btw, remove that `req.Host = "test"`)? Do you get to here: `Dim result As HttpWebResponse = req.GetResponse()`? Or the exception is thrown before that, i.e., right after you issue your WebRequest? -- There's a whole bunch of information here (plus the code already linked). Since I cannot test the Server, you'll have to work it out. – Jimi Jan 02 '21 at 16:09
  • I'm using the exact same code as before with an added "ServicePointManager.ServiceCertificateValidationCallback = AddressOf CertificateValidation" for which CertificateValidation is a direct copy of what you linked. I'm using .NET 4.8 and SecurityProtocolType is SystemDefault. The exception is thrown right after req.GetResponse() is ran. I know there's a lot of information and I value your help as I am learning a lot. It's just strange how this has worked in everything else I've tested it in. I'll continue experimenting with this and if I can't get anywhere maybe people at Wix could help. – Letal1s Jan 02 '21 at 16:20
  • If you get to `req.GetResponse()` and this is the code that throws, you're way after the handshake, the SSL is not relevant anymore, since it has already been negotiated. It looks like the Server is closing the Connection. I don't see the code that's reading the Connection Stream (`Using stream as Stream = [HttpWebResponse].GetResponseStream()`). You also need to post the whole code, what you have here is a partial initialization, lacking a lot of settings (as you can see in the code I linked before) – Jimi Jan 02 '21 at 16:25
  • I didn't add the other code since none of it got chance to actually run so I figured it would be irrelevant so sorry about that. I checked the response stream afterwards if the status code from the response wasn't "OK". I added the other settings from your code but they didn't really change anything but for some reason setting the protocol back to Tls12 after changing to .NET 4.8 worked and I got an expected response. I'll attach the final code as an answer. Thanks for your help. – Letal1s Jan 02 '21 at 16:51

1 Answers1

0

Ok so eventually I ended up changing my code to use GetResponseAsync() instead of GetResponse() (not that it made any difference to the problem) and set the protocol type back to Tls12 on .NET 4.8. In the end it seemed that having req.Host set was what was causing the issue with the response, Wix doesn't seem to like it. I also added some extra settings to the request but ultimately they make no difference to the response. Here is the final working code:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
Dim req As HttpWebRequest = WebRequest.CreateHttp("https://example.com/check?email=" & email & "&pass=" & LCase(passHash))
req.Method = "POST"
req.ContentType = "none"
req.ContentLength = 0
req.KeepAlive = True
req.CookieContainer = New CookieContainer()
req.UserAgent = "Test"
req.Accept = "ext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
req.Headers.Add(HttpRequestHeader.AcceptLanguage, "en-US;q=0.9,en;q=0.5")
req.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate;q=0.8")
req.Headers.Add(HttpRequestHeader.CacheControl, "no-cache")

Dim statusCode As HttpStatusCode = Nothing
Try
    Using result As HttpWebResponse = CType(Await req.GetResponseAsync(), HttpWebResponse)
        statusCode = result.StatusCode
    End Using

    If statusCode = HttpStatusCode.OK Then
        MsgBox("Valid")
    Else
        Dim response As String = New StreamReader(req.GetResponse().GetResponseStream()).ReadToEnd()

        MsgBox(response)
    End If
Catch webEx As WebException
    MsgBox(New StreamReader(webEx.Response.GetResponseStream()).ReadToEnd())
End Try
Letal1s
  • 47
  • 2
  • 9