GeezerVison is showing github.com — tls,http2: send fatal alert on ALPN mismatch · nodejs/node@77def91
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/http2.md +14Lines changed: 14 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -2275,6 +2275,11 @@ a given number of milliseconds set using `http2secureServer.setTimeout()`. | 2275 | 2275 |
| 2276 | 2276 | <!-- YAML | 2277 | 2277 | added: v8.4.0 | | 2278 | +changes: | | 2279 | + - version: REPLACEME | | 2280 | + pr-url: https://github.com/nodejs/node/pull/44031 | | 2281 | + description: This event will only be emitted if the client did not transmit | | 2282 | + an ALPN extension during the TLS handshake. | 2278 | 2283 | --> | 2279 | 2284 |
| 2280 | 2285 | * `socket` {stream.Duplex} | @@ -2284,6 +2289,15 @@ negotiate an allowed protocol (i.e. HTTP/2 or HTTP/1.1). The event handler | 2284 | 2289 | receives the socket for handling. If no listener is registered for this event, | 2285 | 2290 | the connection is terminated. A timeout may be specified using the | 2286 | 2291 | `'unknownProtocolTimeout'` option passed to [`http2.createSecureServer()`][]. | | 2292 | + | | 2293 | +In earlier versions of Node.js, this event would be emitted if `allowHTTP1` is | | 2294 | +`false` and, during the TLS handshake, the client either does not send an ALPN | | 2295 | +extension or sends an ALPN extension that does not include HTTP/2 (`h2`). Newer | | 2296 | +versions of Node.js only emit this event if `allowHTTP1` is `false` and the | | 2297 | +client does not send an ALPN extension. If the client sends an ALPN extension | | 2298 | +that does not include HTTP/2 (or HTTP/1.1 if `allowHTTP1` is `true`), the TLS | | 2299 | +handshake will fail and no secure connection will be established. | | 2300 | + | 2287 | 2301 | See the [Compatibility API][]. | 2288 | 2302 |
| 2289 | 2303 | #### `server.close([callback])` | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/tls.md +7-2Lines changed: 7 additions & 2 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -683,8 +683,8 @@ is set to describe how authorization failed. Depending on the settings | 683 | 683 | of the TLS server, unauthorized connections may still be accepted. | 684 | 684 |
| 685 | 685 | The `tlsSocket.alpnProtocol` property is a string that contains the selected | 686 | | -ALPN protocol. When ALPN has no selected protocol, `tlsSocket.alpnProtocol` | 687 | | -equals `false`. | | 686 | +ALPN protocol. When ALPN has no selected protocol because the client or the | | 687 | +server did not send an ALPN extension, `tlsSocket.alpnProtocol` equals `false`. | 688 | 688 |
| 689 | 689 | The `tlsSocket.servername` property is a string containing the server name | 690 | 690 | requested via SNI. | @@ -2012,6 +2012,11 @@ where `secureSocket` has the same API as `pair.cleartext`. | 2012 | 2012 | <!-- YAML | 2013 | 2013 | added: v0.3.2 | 2014 | 2014 | changes: | | 2015 | + - version: REPLACEME | | 2016 | + pr-url: https://github.com/nodejs/node/pull/44031 | | 2017 | + description: If `ALPNProtocols` is set, incoming connections that send an | | 2018 | + ALPN extension with no supported protocols are terminated with | | 2019 | + a fatal `no_application_protocol` alert. | 2015 | 2020 | - version: v12.3.0 | 2016 | 2021 | pr-url: https://github.com/nodejs/node/pull/27665 | 2017 | 2022 | description: The `options` parameter now supports `net.createServer()` | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/http2/core.js +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -3055,7 +3055,7 @@ function connectionListener(socket) { | 3055 | 3055 | // going on in a format that they *might* understand. | 3056 | 3056 | socket.end('HTTP/1.0 403 Forbidden\r\n' + | 3057 | 3057 | 'Content-Type: text/plain\r\n\r\n' + | 3058 | | - 'Unknown ALPN Protocol, expected `h2` to be available.\n' + | | 3058 | + 'Missing ALPN Protocol, expected `h2` to be available.\n' + | 3059 | 3059 | 'If this is a HTTP request: The server was not ' + | 3060 | 3060 | 'configured with the `allowHTTP1` option or a ' + | 3061 | 3061 | 'listener for the `unknownProtocol` event.\n'); | |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_tls.cc +7-7Lines changed: 7 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -246,13 +246,13 @@ int SelectALPNCallback( | 246 | 246 | in, | 247 | 247 | inlen); | 248 | 248 |
| 249 | | - // According to 3.2. Protocol Selection of RFC7301, fatal | 250 | | - // no_application_protocol alert shall be sent but OpenSSL 1.0.2 does not | 251 | | - // support it yet. See | 252 | | - // https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest | 253 | | - return status == OPENSSL_NPN_NEGOTIATED | 254 | | - ? SSL_TLSEXT_ERR_OK | 255 | | - : SSL_TLSEXT_ERR_NOACK; | | 249 | + // Previous versions of Node.js returned SSL_TLSEXT_ERR_NOACK if no protocol | | 250 | + // match was found. This would neither cause a fatal alert nor would it result | | 251 | + // in a useful ALPN response as part of the Server Hello message. | | 252 | + // We now return SSL_TLSEXT_ERR_ALERT_FATAL in that case as per Section 3.2 | | 253 | + // of RFC 7301, which causes a fatal no_application_protocol alert. | | 254 | + return status == OPENSSL_NPN_NEGOTIATED ? SSL_TLSEXT_ERR_OK | | 255 | + : SSL_TLSEXT_ERR_ALERT_FATAL; | 256 | 256 | } | 257 | 257 |
| 258 | 258 | int TLSExtStatusCallback(SSL* s, void* arg) { | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-http2-https-fallback.js +13-3Lines changed: 13 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -126,7 +126,7 @@ function onSession(session, next) { | 126 | 126 | const { port } = server.address(); | 127 | 127 | const origin = `https://localhost:${port}`; | 128 | 128 |
| 129 | | - const cleanup = countdown(3, () => server.close()); | | 129 | + const cleanup = countdown(4, () => server.close()); | 130 | 130 |
| 131 | 131 | // HTTP/2 client | 132 | 132 | connect( | @@ -149,12 +149,22 @@ function onSession(session, next) { | 149 | 149 |
| 150 | 150 | function testWrongALPN() { | 151 | 151 | // Incompatible ALPN TLS client | 152 | | - let text = ''; | 153 | 152 | tls(Object.assign({ port, ALPNProtocols: ['fake'] }, clientOptions)) | | 153 | + .on('error', common.mustCall((err) => { | | 154 | + strictEqual(err.code, 'ECONNRESET'); | | 155 | + cleanup(); | | 156 | + testNoALPN(); | | 157 | + })); | | 158 | + } | | 159 | + | | 160 | + function testNoALPN() { | | 161 | + // TLS client does not send an ALPN extension | | 162 | + let text = ''; | | 163 | + tls(Object.assign({ port }, clientOptions)) | 154 | 164 | .setEncoding('utf8') | 155 | 165 | .on('data', (chunk) => text += chunk) | 156 | 166 | .on('end', common.mustCall(() => { | 157 | | - ok(/Unknown ALPN Protocol, expected `h2` to be available/.test(text)); | | 167 | + ok(/Missing ALPN Protocol, expected `h2` to be available/.test(text)); | 158 | 168 | cleanup(); | 159 | 169 | })); | 160 | 170 | } | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-http2-server-unknown-protocol.js +17-3Lines changed: 17 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -8,6 +8,7 @@ const fixtures = require('../common/fixtures'); | 8 | 8 | if (!common.hasCrypto) | 9 | 9 | common.skip('missing crypto'); | 10 | 10 |
| | 11 | +const assert = require('assert'); | 11 | 12 | const h2 = require('http2'); | 12 | 13 | const tls = require('tls'); | 13 | 14 |
| @@ -18,16 +19,29 @@ const server = h2.createSecureServer({ | 18 | 19 | allowHalfOpen: true | 19 | 20 | }); | 20 | 21 |
| 21 | | -server.on('connection', (socket) => { | | 22 | +server.on('secureConnection', common.mustCall((socket) => { | 22 | 23 | socket.on('close', common.mustCall(() => { | 23 | 24 | server.close(); | 24 | 25 | })); | 25 | | -}); | | 26 | +})); | 26 | 27 |
| 27 | 28 | server.listen(0, function() { | | 29 | + // If the client does not send an ALPN connection, and the server has not been | | 30 | + // configured with allowHTTP1, then the server should destroy the socket | | 31 | + // after unknownProtocolTimeout. | 28 | 32 | tls.connect({ | 29 | 33 | port: server.address().port, | 30 | 34 | rejectUnauthorized: false, | 31 | | - ALPNProtocols: ['bogus'] | 32 | 35 | }); | | 36 | + | | 37 | + // If the client sends an ALPN extension that does not contain 'h2', the | | 38 | + // server should send a fatal alert to the client before a secure connection | | 39 | + // is established at all. | | 40 | + tls.connect({ | | 41 | + port: server.address().port, | | 42 | + rejectUnauthorized: false, | | 43 | + ALPNProtocols: ['bogus'] | | 44 | + }).on('error', common.mustCall((err) => { | | 45 | + assert.strictEqual(err.code, 'ECONNRESET'); | | 46 | + })); | 33 | 47 | }); |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-tls-alpn-server-client.js +48-3Lines changed: 48 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -5,6 +5,7 @@ if (!common.hasCrypto) | 5 | 5 | common.skip('missing crypto'); | 6 | 6 |
| 7 | 7 | const assert = require('assert'); | | 8 | +const { spawn } = require('child_process'); | 8 | 9 | const tls = require('tls'); | 9 | 10 | const fixtures = require('../common/fixtures'); | 10 | 11 |
| @@ -68,7 +69,7 @@ function Test1() { | 68 | 69 | }, { | 69 | 70 | ALPNProtocols: ['c', 'b', 'e'], | 70 | 71 | }, { | 71 | | - ALPNProtocols: ['first-priority-unsupported', 'x', 'y'], | | 72 | + ALPNProtocols: ['x', 'y', 'c'], | 72 | 73 | }]; | 73 | 74 |
| 74 | 75 | runTest(clientsOptions, serverOptions, function(results) { | @@ -82,8 +83,8 @@ function Test1() { | 82 | 83 | client: { ALPN: 'b' } }); | 83 | 84 | // Nothing is selected by ALPN | 84 | 85 | checkResults(results[2], | 85 | | - { server: { ALPN: false }, | 86 | | - client: { ALPN: false } }); | | 86 | + { server: { ALPN: 'c' }, | | 87 | + client: { ALPN: 'c' } }); | 87 | 88 | // execute next test | 88 | 89 | Test2(); | 89 | 90 | }); | @@ -161,6 +162,50 @@ function Test4() { | 161 | 162 | { server: { ALPN: false }, | 162 | 163 | client: { ALPN: false } }); | 163 | 164 | }); | | 165 | + | | 166 | + TestFatalAlert(); | | 167 | +} | | 168 | + | | 169 | +function TestFatalAlert() { | | 170 | + const server = tls.createServer({ | | 171 | + ALPNProtocols: ['foo'], | | 172 | + key: loadPEM('agent2-key'), | | 173 | + cert: loadPEM('agent2-cert') | | 174 | + }, common.mustNotCall()); | | 175 | + | | 176 | + server.listen(0, serverIP, common.mustCall(() => { | | 177 | + const { port } = server.address(); | | 178 | + | | 179 | + // The Node.js client will just report ECONNRESET because the connection | | 180 | + // is severed before the TLS handshake completes. | | 181 | + tls.connect({ | | 182 | + host: serverIP, | | 183 | + port, | | 184 | + rejectUnauthorized: false, | | 185 | + ALPNProtocols: ['bar'] | | 186 | + }, common.mustNotCall()).on('error', common.mustCall((err) => { | | 187 | + assert.strictEqual(err.code, 'ECONNRESET'); | | 188 | + | | 189 | + // OpenSSL's s_client should output the TLS alert number, which is 120 | | 190 | + // for the 'no_application_protocol' alert. | | 191 | + const { opensslCli } = common; | | 192 | + if (opensslCli) { | | 193 | + const addr = `${serverIP}:${port}`; | | 194 | + let stderr = ''; | | 195 | + spawn(opensslCli, ['s_client', '--alpn', 'bar', addr], { | | 196 | + stdio: ['ignore', 'ignore', 'pipe'] | | 197 | + }).stderr | | 198 | + .setEncoding('utf8') | | 199 | + .on('data', (chunk) => stderr += chunk) | | 200 | + .on('close', common.mustCall(() => { | | 201 | + assert.match(stderr, /SSL alert number 120/); | | 202 | + server.close(); | | 203 | + })); | | 204 | + } else { | | 205 | + server.close(); | | 206 | + } | | 207 | + })); | | 208 | + })); | 164 | 209 | } | 165 | 210 |
| 166 | 211 | Test1(); |
|