Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Socket hang up" with Electron 4 (and greater) when contacting Microsoft IIS #18557

Closed
berkon opened this issue Jun 1, 2019 · 10 comments
Closed

Comments

@berkon
Copy link

berkon commented Jun 1, 2019

  • Electron Versions: 4.x.x and later
  • Operating System (Platform and Version): Windows 7
  • Last known working Electron version: 3.1.9

Expected Behavior
When sending this request from the main process I should receive a SOAP response (port 447 is intended and not a typo!):

var request = require ( 'request' );
var url = 'https://sws.unify.com:447/SWSService4CSP/SWSService4CMP.asmx?WSDL';
request ( url, function (err, res, body) {
    if ( err )
        console.log ( err );
    else
        console.log ( body );
}

Actual behavior
When running the request with Electron versions greater or equal 4.0.0 I'm getting "Socket hang up".

To Reproduce
Put the code above in the main process and run it with Electron 4.

Additional Information
When running the request above natively in Node 10.11.0 or in Electron 3.x.x everything works fine. Since Electron 4 has Node 10.11.0 inside as well I wouldn't have expected any difference. But Node 10.11.0 behaves differently in Electron 4 compared to running it natively. E.g. in Electron 4 Node 10.11.0 offers a lot less ciphers for TLS negotiation compared to original Node 10.11.0.

Another thing which I found is that IIS for some reason sends another "Encrypted Handshake Message" (see packet #41 in attached electron4.pcap in Wireshark.zip), after the session was already successfully established. Looks like this is something Node in Electron 4 does not like. As you can see in electron3.pcap there is such package as well (#91). But in this case Node answers with package #92.

Not sure if I'm right, but after searching a lot through the web, my impression is that in Electron 4, Node is no longer compiled with OpenSSL, but instead with Google's BoringSSL library. This could explain the differing behavior.

In fact there are two questions.

  1. Why is IIS sending an additional TLS-Finish ("Encrypted Handshake") Message? I didn't see this on any other server. I'll try to get more information from them, whether this potentially is some miss-configuration.

  2. Why is Electron 4 / Node reacting differently now?

@nornagon
Copy link
Member

nornagon commented Jun 3, 2019

Electron 4 uses BoringSSL instead of OpenSSL in Node.js (it always used BoringSSL when connecting from Chromium; the only change is that now Node.js uses it too). BoringSSL is generally less forgiving than OpenSSL, and generally aims not to support configurations that are known to be insecure (such as SSLv3). It seems like your IIS server is misconfigured.

I'm closing this because I don't believe there's anything to be fixed in Electron related to this, but do let me know what you find and we can reopen if we do uncover an issue we can address.

@nornagon nornagon closed this as completed Jun 3, 2019
@berkon
Copy link
Author

berkon commented Jun 4, 2019

Thanks a lot for the clarification @nornagon! I'll try to get in contact with the admins and check whether they can change the configuration.

@berkon
Copy link
Author

berkon commented Jun 4, 2019

@nornagon, there is just one strange thing: When I'm using Chrome version 74 (currently the latest one), I'm getting the correct response from that URL! Chrome (which obviously has BoringSSL inside) is answering that strange package which I mentioned above (and which causes the "Socket hang up" in Electron), with an own "Encrypted Handshake" message. So could it be that there is a difference (e.g. compile options) how BoringSSL is compiled for NodeJS when building Electron (compared to how it is compiled for Chrome)?

@nornagon
Copy link
Member

nornagon commented Jun 4, 2019

Hm, I doubt it'd be a compilation issue. Do you see the same behaviour when you try accessing the server through web content in Electron 4? Node and Chromium in Electron both use the same BoringSSL module.

The code you posted uses the request module (which depends on node.js's https module internally), not Chrome's networking stack, so I suspect there's a difference there.

@berkon
Copy link
Author

berkon commented Jun 5, 2019

I can confirm that when using this code:

    mainWindow = new BrowserWindow ( { width: 800, height: 600 } );
    mainWindow.loadURL ( 'https://sws.unify.com:447/SWSService4CSP/SWSService4CMP.asmx?WSDL' );

it works correctly. I did this test with Electron 4.

@berkon
Copy link
Author

berkon commented Jun 5, 2019

I also checked the Wireshark trace and indeed that strange "Encrypted Handshake" package from the sever is answered by Chromium with an own "Encrypted Handshake" package. So there must be a difference how BoringSSL is used for Chromium and for NodeJS in Electron 4.

@nornagon
Copy link
Member

nornagon commented Jun 5, 2019

Yep, when using node.js's API, BoringSSL will be invoked slightly differently. If it works for you, you could try using Electron's net module, which uses Chromium's networking stack instead of Node's. Unfortunately it's hard for me to tell what the issue might be without a repro I can run locally, but do let me know what you uncover—it's definitely possible there's a bug in the way that Node is calling BoringSSL!

@berkon
Copy link
Author

berkon commented Jun 6, 2019

Unfortunately the net module does not work correctly with port 447. With port 443 everything works well, but when I'm calling the URL mentioned on top which contacts port 447, I'm not getting a response. Looks like the "response" event is not fired in this case. And in turn this code is not triggered:

request.on ( 'response', (response) => { ... } );

Also:

request.on ( 'error', (error) => { ... } );

is not triggered. Nevertheless I can see a successful negotiation and data exchange sequence on Wireshark. Also I see that in this case (as expected) that packet #41 is answered by the BoringSSL stack.

@nornagon
Copy link
Member

nornagon commented Oct 3, 2019

Ah, that does seem like a bug in the net module that neither response nor error would get triggered! I was able to reproduce that issue in Electron 6, but tried it out just now with the Electron 7 beta and got a net::ERR_ABORTED error (specifically a CERT_COMMON_NAME_INVALID error). Perhaps you were seeing issues where your ssl cert was registered for :443 but you were contacting it on :447?

@gmantri
Copy link

gmantri commented Feb 29, 2020

Thank you for this thread. It was indeed very helpful. I too was hit by this issue pretty badly.

Tried a number of solutions but unfortunately nothing worked. Even using net was of no use to me because for some reason I always got a 403 error (mine was authenticating/authorizing Classic Azure Service Management API endpoint [https://management.azure.com] using Bearer token) and yes, it is broken in Electron v6.x.

I did come up with a workaround so I thought I would share it here. It would require Node to be installed on the target machine.

Basically I wrote a JS file which uses request package to make a request and sends the response back. Then I launched this JS file through child_process.fork in my app.

Here's my request processor JS file looks like:

const request = require('request');

process.on('message', (msg) => {
  const messageType = msg.type;
  const messageBody = msg.body;
  switch (messageType) {
    case 'startRequest': {
      processRequest(messageBody);
      break;
    }
  }
});

const processRequest = (options) => {
  const requestOptions = {
    method: options.method,
    uri: options.uri,
    agentOptions: options.agentOptions
  };
  if (typeof options.rejectUnhauthorized !== 'undefined' && typeof options.rejectUnhauthorized === 'boolean') {
    requestOptions.rejectUnhauthorized = options.rejectUnhauthorized;
  }
  if (typeof options.strictSSL !== 'undefined' && typeof options.strictSSL === 'boolean') {
    requestOptions.strictSSL = options.strictSSL;
  }
  if (typeof options.headers !== 'undefined' && typeof options.headers === 'object' && options.headers !== null) {
    requestOptions.headers = options.headers;
  }
  if (typeof options.body !== 'undefined') {
    requestOptions.body = options.body;
  }
  if (typeof options.form !== 'undefined') {
    requestOptions.form = options.form;
  }
  request(requestOptions, function(error, response, body) {
    process.send({error, response, body});
    setTimeout(() => {
      process.exit(0);
    }, 1000);
  });
};

and this is how I call it from my Electron App:

const p = fork('path to my request processor js file', {detached: true, silent: false, execPath: 'node'});
const requestParams = {
    url: 'my url',
    method: 'request method',
    headers: {
        'key1': 'value1',
        'ket2': 'value2'
    }
    //include any other request parameters like body etc.
}
p.on('error', (error) => {
    console.log('comes in p.error');
    console.log(error);
    //write code to handle the error
});
p.on('message', (result) => {
    console.log('response received from process');
    console.log(result);
    //write code to handle the response result
});
p.send({
    type: 'startRequest',
    body: requestParams
});

As mentioned above, only catch here is that node must be installed on the target machine which is what I specified in the execTarget property. Using Electron's node will result in the same error unfortunately.

I hope somebody will find this workaround useful hence thought of sharing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants