node.jshttpstls1.2diffie-hellman

node js https minDHSize not closing connection


I am making an https request to a server that supplies a 2048 bit Diffie-Hellman cipher, and trying to get my code to reject the connection and issue a handshake failure back to the server and close the connection. I don't think its used, but I have a 4096 bit DH key in the client, and the connection is made perfectly if the server also uses a 4096 bit DH key.

const dhparam = fs.readFileSync( "dhparam_4096.pem" );
const data = JSON.stringify({ 
    part1 : "Hello", 
    part2 " "Name" });
const httpsOptions = {
    host : address + "%" + Ethernet.GetIfaceName(),
    port : service.port,
    path : service.txt.path,
    method : "PUT",
    rejectUnauthorized : false,
    ciphers : "ADH-AES256-GCM-SHA384:@SECLEVEL=0",
    dhparam,
    minDHSize : 4096,
    headers : { 
       'Content-Type' : 'application/json',
       'Content-Length' : data.length,
    },
    servername : "nameServer",
}
console.log( "    httpsOptions =", httpsOptions );
                        
/*** Send the request ***/
                        
var req = https.request( httpsOptions, res => 
{
    var data = "";
    res.on( 'data', d => data += d );
    res.on( 'end', d =>  
    { 
        if ( d ) data += d;
        console.log( "Received data =", data );
        console.log( "    ", data.toString() );
    });
    console.log( "response =", res.statusCode, res.statusMessage );
});

req.on( "error", err => 
{
    console.log( "error =", err.code );
    if ( err.code === "ERR_TLS_DH_PARAM_SIZE" )
    {
        console.log("    DH Key is not 4096 bits" );
        req.socket.destroy();
    }
                           
});
console.log( "    sending data: ". data );
req.write( data );
req.end();

The minDHSize : 4096 results in the error event being triggered, so I know that the detection mechanism is working.

I have a socket.destroy() in this path, but that does not seem to work.

on the server, I have trace enabled, and get the following:

Received Record
Header:
  Version = TLS 1.0 (0x301)
  Content Type = Handshake (22)
  Length = 153
    ClientHello, Length=149
      client_version=0x303 (TLS 1.2)
      Random:
        gmt_unix_time=0xFE471FB1
        random_bytes (len=28): 3B54266D9E916B151A9A8E940EE4D0BEE0E59C1F2693B6E637CC4D
      session_id (len=0): 
      cipher_suites (len=4)
        {0x00, 0xA7} TLS_DH_anon_WITH_AES_256_GCM_SHA384
        {0x00, 0xFF} TLS_EMPTY_RENEGOTIATION_INFO_SCSV
      compression_methods (len=1)
        No Compression (0x00)
      extensions, length = 104
        extension_type=server_name(0), length=36
        extension_type=session_ticket(35), length=0
        extension_type=encrypt_then_mac(22), length=0
        extension_type=extended_master_secret(23), length=0
        extension_type=signature_algorithms(13), length=48
          ecdsa_secp256r1_sha256 (0x0403)
          ecdsa_secp384r1_sha384 (0x0503)
          ecdsa_secp521r1_sha512 (0x0603)
          ed25519 (0x0807)
          ed448 (0x0808)
          rsa_pss_pss_sha256 (0x0809)
          rsa_pss_pss_sha384 (0x080a)
          rsa_pss_pss_sha512 (0x080b)
          rsa_pss_rsae_sha256 (0x0804)
          rsa_pss_rsae_sha384 (0x0805)
          rsa_pss_rsae_sha512 (0x0806)
          rsa_pkcs1_sha256 (0x0401)
          rsa_pkcs1_sha384 (0x0501)
          rsa_pkcs1_sha512 (0x0601)
          ecdsa_sha224 (0x0303)
          ecdsa_sha1 (0x0203)
          rsa_pkcs1_sha224 (0x0301)
          rsa_pkcs1_sha1 (0x0201)
          dsa_sha224 (0x0302)
          dsa_sha1 (0x0202)
          dsa_sha256 (0x0402)
          dsa_sha384 (0x0502)
          dsa_sha512 (0x0602)


Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 57
    ServerHello, Length=53
      server_version=0x303 (TLS 1.2)
      Random:
        gmt_unix_time=0xDB67568F
        random_bytes (len=28): BF81EBFB16471C15ED8D7D0D5273232CFBD6E70E933CC00747B57899
      session_id (len=0): 
      cipher_suite {0x00, 0xA7} TLS_DH_anon_WITH_AES_256_GCM_SHA384
      compression_method: No Compression (0x00)
      extensions, length = 13
        extension_type=renegotiate(65281), length=1
            <EMPTY>
        extension_type=session_ticket(35), length=0
        extension_type=extended_master_secret(23), length=0

Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 523
    ServerKeyExchange, Length=519
      KeyExchangeAlgorithm=DHE
        dh_p (len=256):     C0C8B68E3EAADEB2ECCA8863B8017D63D3259FEC711A97D84E8100DC427BA45684DF962C00C8116DCE797BD02CA3D02FB2C01F7F212AA1E899CB108A57156841A1CE40F4E2EF65AC1598964851F4DB874BD9E8031B2467B7624CE3CC950B0167C23A91896CAF44199B45A046A9EA1D274D6545AC1AC11542ED9F1C0CA3F8DDF5CB215436C3416FFE20242AA5C3270858D07314C58478F771D201B54D47DFAFD87F127FC3E8C11F40CE5B1A063AF7C6C50347C3049A9EE479EA685499E57A49DE1287DC96AE71E0E7F10B42BB09DC8BAFBE96F710FE20F0FC296C1D951CD258946E1AEF5876494B524809B14F12F393B40AEF0BD4E44F6E67E8A579184CAB393B
        dh_g (len=1): 02
        dh_Ys (len=256): AE3E9B2D557F7A862D323911B17061471865880B8CDA469D94CCEC11A74B6C081A2F0389BF2B961E5A54C7A6DE41D1AFB9388B5BB51EC6F129BA093A83DA68288D5246869B52B54D2CF5CD0F1BDEAA206FC7C680B02B18903969B061817479545BF8EA17901CF86A9E580DF860FD29E25A2EB56D00FE319D2DA17F9E3F33986D4716D86882E76E774A7CB717AD08E60AA182843B139E6EFB63F2EFCD6E351503352F3E5A23D397AAA4BB359A8EF125FF10FE20805B083B15135247E1B08F57B3B43EF13E9B60EEB2EB1743466DD10922ABD588E9031BC1BDF10353484BC10B2E32D24E513E58092373E96360836BE4FFDC3395CB4044BCEE11248E0E5EDA412F

Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 4
    ServerHelloDone, Length=0

Received Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 262
    ClientKeyExchange, Length=258
      KeyExchangeAlgorithm=DHE
        dh_Yc (len=256): 060D2565ABCE52B7480CB72D2DEF6F5B8FC25B15FB070BB97DC0B509143CFC562BF8483D24959B8192B292DE1D17FBEB281734DF062F48313AEBE3F1398EC9C535086D3DBDB7F2F52FB1F656B078908520B2B2285F2382C16E0731AAF00CAF098EBB32AE89CEB50940BA1EBE8A08EF86739CFC6DABD9965E9D2325DA73F53045C686C74006A525A35E27180D51BED9EEB9831EF617C7D78AF19CCBB6EE9F016911E1C2F0D9CEF6F3E31E14109EFDBF1187720AA34324C94CB166DAAA4D2E69D1182D5B86C8F1EA89FB02C5E9420DFC333ABBA4050F8C782F1B16B74F87F0251B06B4D725FC6FE26B3FC5C2CB54FBA94174576F694A02D1FC707BABD2E03FB854

Received Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = ChangeCipherSpec (20)
  Length = 1
Received Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 40
    Finished, Length=12
      verify_data (len=12): DCA9352457FDBF48674FE0B8

Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 202
    NewSessionTicket, Length=198
        ticket_lifetime_hint=7200
        ticket (len=192): 06B807C486272F454FC553528EC5B6932A5989321D4171F7A6DAF4A2F0E9FA9A3620F9F90499FF5FD7C2566D4A5E7574DD6863353CC428E7656168799B3501613A67AF605746F034571AA9264A53A3DCCE4CACCB72FA54FF9B4C735666F4FA0AEC56A6F8819E375BEC01DC9FC282052E4FBFF88D07596247CE2AC92870B4CE759D946FD18E78B48EBF4474F9D6D51BB6924662B9CB3A312E9D7AD76B97C335A07531841F2D7A5A02BCBCE4857EE43626FF07CD4D44937CFF74401FFE2787740C

Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = ChangeCipherSpec (20)
  Length = 1
    change_cipher_spec (1)

Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Handshake (22)
  Length = 40
    Finished, Length=12
      verify_data (len=12): E72603E1A716815F94F9AAEE

Sent Record
Header:
  Version = TLS 1.2 (0x303)
  Content Type = Alert (21)
  Length = 26
    Level=warning(1), description=close notify(0)

There is no message coming from the client to indicate a handshake failure and the TCP connection has not been closed.

What am I missing?


Solution

  • There is no message coming from the client to indicate a handshake failure and the TCP connection has not been closed.

    The handshake, as such, does not fail. OpenSSL has no facility to require a minimum classic-DH size in keyexchange (for DHE and DH_anon which OpenSSL calls ADH), so nodejs enforces this limit after OpenSSL completes the handshake but before allowing any data to pass -- as it also does for the server cert name check, which OpenSSL originally didn't implement and now implements differently from what nodejs chose. See onSecureConnect in _tls_wrap.js .

    The TCP connection is closed, and that is probably why your server is sending close_notify -- or trying to; this send will actually fail because of the TCP closure. The servers I tested (OpenSSL and Java) both show TCP closed from the (nodejs) client, although OpenSSL does so implicitly (as read count 0), and they respond differently: only older Java 8 (before the new 11 stack was backported in 8u261) tried to send close_notify in 'response'. I suspect your server just isn't logging the TCP closure; try an external tool like wireshark or similar.

    To be clear, you set in the client 4096-bit DH parameters, not a key. Only the parameters are set in the server; the keys at both ends are generated per-handshake (in the subgroup defined by the parameters) which is why they are called ephemeral. And (user-chosen) DH parameters set in the client are ignored (but see next).

    FYI: There is a fairly recent protocol option, RFC7919, for TLS1.2* to use standardized (not user-chosen) DHE/anon groups, now called FFDHE to distinguish from ECDHE, which the client can request with the former supported_curves extension now repurposed as supported_groups (in particular it could demand 4096), but OpenSSL and thus nodejs does not implement this option. In 1.3 supported_groups is required, but the FFDHE groups are not required or even particularly encouraged, and AFAICT OpenSSL doesn't (yet?) support them there either -- and anyway 1.3 doesn't allow 'anon' at all. (* Formally 7919 applies to 1.1 and 1.0 also, but I can't imagine any implementation includes 7919 but not 1.2, so you would never need lower protocols.)