Sitelet https://github.com/nodejs/node/issues/869
Skip to content

https SNICallback hangs if optional callback isn't provided on ARM6 (and possibly others) #869

Description

@coolaj86

Originally I found this bug #867 on ARM6 and while testing further on OS X I found a workaround.

I took my band-aided test case back to my Rapsberry Pi to test and found that the https request still just hangs forever.

I commented out the SNICallback entirely and found that the certificates I'm loading are working by themselves, but the SNICallback is broken.

'use strict';

var https           = require('https');
var fs              = require('fs');
var path            = require('path');
var crypto          = require('crypto');
var connect         = require('connect');

module.exports.create = function (_securePort, _insecurePort) {
    // connect / express app
  var app             = connect();

    // SSL Server
  var secureContexts  = {};
  var dummyCerts;
  var secureOpts;
  var secureServer;
  var securePort      = _securePort || 443;

    // force SSL upgrade server
  var insecureServer;
  var insecurePort    = _insecurePort || 80;

  function loadDummyCerts() {
    var certsPath = path.join(__dirname, 'certs');
    var certs = {
      key:          fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.key.pem'))
    , cert:         fs.readFileSync(path.join(certsPath, 'server', 'dummy-server.crt.pem'))
    , ca:           fs.readdirSync(path.join(certsPath, 'ca')).filter(function (node) {
                       return /crt\.pem$/.test(node);
                     }).map(function (node) {
                       console.log('[Add CA]', node);
                      return fs.readFileSync(path.join(certsPath, 'ca', node));
                    })
    };
    secureContexts.dummy = crypto.createCredentials(certs).context;
    dummyCerts = certs;
  }
  loadDummyCerts();

  app.use(function (req, res, next) {
    console.log('[log] request for ' + req.headers.host + req.url);
    next();
  });

  function runServer() {
    //provide a SNICallback when you create the options for the https server
    secureOpts = {
                    // fallback / default dummy certs
      key:          dummyCerts.key
    , cert:         dummyCerts.cert
    , ca:           dummyCerts.ca
      //SNICallback is passed the domain name, see NodeJS docs on TLS
  /*
    , SNICallback:  function (domainname) {
                      console.log('[log] SNI:', domainname);
                      console.log('[log] SNI:', secureContexts[domainname]);
                      var secureContext = secureContexts[domainname] || secureContexts.dummy;
                      console.log('[log]', secureContext);
                      return secureContext;
                    }
  */
    };

    secureServer = https.createServer(secureOpts);
    secureServer.on('request', function (req, res) {
      console.log('[log] request');
      app(req, res);
    });
    secureServer.listen(securePort, function () {
      console.log("Listening on https://localhost:" + secureServer.address().port);
    });
  }

  runServer();
}
module.exports.create(65443, 65080);

To experience the hang, simply uncomment the SNICallback block.

Activity

  1. coolaj86 commented on Feb 17, 2015

    @coolaj86
    Author

    (testing from my macbook to my rpi)

    It hangs with curl https://10.0.0.20:65443 by itself.

    It doesn't hang with curl -k https://10.0.0.20:65443

    It does hang in Chrome with https://10.0.0.20:65443

  2. coolaj86 commented on Feb 17, 2015

    @coolaj86
    Author

    Testing on localhost

    Testing from the Raspberry Pi (localhost -> localhost):

    Works or Gives Error

    curl https://127.0.0.1:65443 -k
    curl https://10.0.0.20:65443 -k
    works
    
    curl https://127.0.0.1:65443 --cacert certs/ca/dummy-root-ca.crt
    curl https://10.0.0.20:65443 --cacert certs/ca/dummy-root-ca.crt
    curl: (51) SSL: certificate subject name 'local.helloworld3000.com' does not match target host name '10.0.0.20'
    
    curl https://127.0.0.1:65443
    curl https://10.0.0.20:65443
    curl: (60) SSL certificate problem: self signed certificate in certificate chain
    More details here: http://curl.haxx.se/docs/sslcerts.html
    
    curl performs SSL certificate verification by default, using a "bundle"
     of Certificate Authority (CA) public keys (CA certs). If the default
     bundle file isn't adequate, you can specify an alternate file
     using the --cacert option.
    If this HTTPS server uses a certificate signed by a CA represented in
     the bundle, the certificate verification probably failed due to a
     problem with the certificate (it might be expired, or the name might
     not match the domain name in the URL).
    If you'd like to turn off curl's verification of the certificate, use
     the -k (or --insecure) option.
    

    Hangs Forever

    curl https://localhost:65443
    
    curl https://localhost:65443 --cacert certs/ca/dummy-root-ca.crt.pem
    
    curl https://localhost:65443 -k
    
    curl https://local.helloworld3000.com:65443
    
    curl https://local.helloworld3000.com:65443 --cacert certs/ca/dummy-root-ca.crt.pem
    
    curl https://local.helloworld3000.com:65443 -k
    

    Testing NOT on localhost

    curl -vvv https://10.0.0.20:65443
    * Rebuilt URL to: https://10.0.0.20:65443/
    * Hostname was NOT found in DNS cache
    *   Trying 10.0.0.20...
    * Connected to 10.0.0.20 (10.0.0.20) port 65443 (#0)
    * WARNING: using IP address, SNI is being disabled by the OS.
    (hangs forever)
    
    curl -vvv -k https://10.0.0.20:65443
    * Rebuilt URL to: https://10.0.0.20:65443/
    * Hostname was NOT found in DNS cache
    *   Trying 10.0.0.20...
    * Connected to 10.0.0.20 (10.0.0.20) port 65443 (#0)
    * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
    * Server certificate: local.helloworld3000.com
    * Server certificate: example.com
    > GET / HTTP/1.1
    > User-Agent: curl/7.37.1
    > Host: 10.0.0.20:65443
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Date: Tue, 17 Feb 2015 20:00:03 GMT
    < Connection: keep-alive
    < Transfer-Encoding: chunked
    <
    * Connection #0 to host 10.0.0.20 left intact
    works
    
    curl -vvv https://10.0.0.20:65443 --cacert certs/ca/dummy-root-ca.crt.pem
    * Rebuilt URL to: https://10.0.0.20:65443/
    * Hostname was NOT found in DNS cache
    *   Trying 10.0.0.20...
    * Connected to 10.0.0.20 (10.0.0.20) port 65443 (#0)
    * WARNING: using IP address, SNI is being disabled by the OS.
    (hangs forever)
    
  3. changed the title [-]https SNICallback always hangs on ARM6 (Raspberry Pi)[/-] [+]https SNICallback always hangs on ARM6 (and possibly others)[/+] on Feb 17, 2015
  4. coolaj86 commented on Feb 17, 2015

    @coolaj86
    Author

    The Problem and the Workaround

    The API for SNICallback has changed since v0.10.36. Now there's an optional callback, but on ARM, for some reason, returning the context without using the callback doesn't work, while on OS X it does work.

    P.S. I'm definitely in favor of the callback remaining optional since it's difficult for me, the node user, to feature detect which version of node / io.js my code is running on.

    Update: Nevermind, I can check 'function' === typeof cb... DUH

    Update 2: it turns out I need the async version anyway because I'll be using let's encrypt and I have to do some lazy loading for performance reasons.

  5. changed the title [-]https SNICallback always hangs on ARM6 (and possibly others)[/-] [+]https SNICallback hangs if optional callback isn't provided on ARM6 (and possibly others)[/+] on Feb 17, 2015
  6. indutny commented on Feb 19, 2015

    @indutny
    Member
  7. coolaj86 commented on Feb 19, 2015

    @coolaj86
    Author

    This is a DIFFERENT bug. (and #867 is yet another different bug)

    The problem here is that on OS X the callback is optional (if you return synchronously, it works) but on ARM returning synchronously is not supported and you MUST use the callback.

  8. indutny commented on Feb 19, 2015

    @indutny
    Member

    You must use the callback on every platform. I don't think that there is any way to opt-out from it, unless the SNICallback is not called for that connection.

  9. coolaj86 commented on Feb 26, 2015

    @coolaj86
    Author

    Oh, I didn't see this comment here before.

    I'll close the issue, but I'm interested to retest since I originally claimed to have tested it on 2 devices with 1.2 and 1 with 0.12...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions