Browse Source

tls_wrap: parse tls session ticket extension

And, if present and non-empty, don't invoke `resumeSession` callback.

fix #5872
v0.11.5-release
Fedor Indutny 12 years ago
parent
commit
dda22a520b
  1. 1
      lib/_tls_wrap.js
  2. 63
      src/tls_wrap.cc
  3. 30
      test/simple/test-tls-session-cache.js

1
lib/_tls_wrap.js

@ -63,6 +63,7 @@ function onclienthello(hello) {
} }
if (hello.sessionId.length <= 0 || if (hello.sessionId.length <= 0 ||
hello.tlsTicket ||
this.server && this.server &&
!this.server.emit('resumeSession', hello.sessionId, callback)) { !this.server.emit('resumeSession', hello.sessionId, callback)) {
callback(null, null); callback(null, null);

63
src/tls_wrap.cc

@ -30,6 +30,7 @@ namespace node {
using crypto::SecureContext; using crypto::SecureContext;
using v8::Array; using v8::Array;
using v8::Boolean;
using v8::Exception; using v8::Exception;
using v8::Function; using v8::Function;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
@ -63,6 +64,7 @@ static Cached<String> name_sym;
static Cached<String> version_sym; static Cached<String> version_sym;
static Cached<String> ext_key_usage_sym; static Cached<String> ext_key_usage_sym;
static Cached<String> sessionid_sym; static Cached<String> sessionid_sym;
static Cached<String> tls_ticket_sym;
static Persistent<Function> tlsWrap; static Persistent<Function> tlsWrap;
@ -682,6 +684,8 @@ void TLSCallbacks::ParseClientHello() {
bool is_clienthello = false; bool is_clienthello = false;
uint8_t session_size = -1; uint8_t session_size = -1;
uint8_t* session_id = NULL; uint8_t* session_id = NULL;
uint16_t tls_ticket_size = -1;
uint8_t* tls_ticket = NULL;
Local<Object> hello_obj; Local<Object> hello_obj;
Handle<Value> argv[1]; Handle<Value> argv[1];
@ -739,6 +743,60 @@ void TLSCallbacks::ParseClientHello() {
session_size = *body; session_size = *body;
session_id = body + 1; session_id = body + 1;
} }
size_t cipher_offset = session_offset + 1 + session_size;
// Session OOB failure
if (cipher_offset + 1 >= avail)
return ParseFinish();
uint16_t cipher_len =
(data[cipher_offset] << 8) + data[cipher_offset + 1];
size_t comp_offset = cipher_offset + 2 + cipher_len;
// Cipher OOB failure
if (comp_offset >= avail)
return ParseFinish();
uint8_t comp_len = data[comp_offset];
size_t extension_offset = comp_offset + 1 + comp_len;
// Compression OOB failure
if (extension_offset > avail)
return ParseFinish();
// Extensions present
if (extension_offset != avail) {
size_t ext_off = extension_offset + 2;
// Parse known extensions
while (ext_off < avail) {
// Extension OOB
if (avail - ext_off < 4)
return ParseFinish();
uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1];
uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3];
// Extension OOB
if (ext_off + ext_len + 4 > avail)
return ParseFinish();
ext_off += 4;
// TLS Session Ticket
if (ext_type == 35) {
tls_ticket_size = ext_len;
tls_ticket = data + ext_off;
}
ext_off += ext_len;
}
// Extensions OOB failure
if (ext_off > avail)
return ParseFinish();
}
} else if (hello_.state == kParseSSLHeader) { } else if (hello_.state == kParseSSLHeader) {
// Skip header, version // Skip header, version
session_offset = hello_.body_offset + 3; session_offset = hello_.body_offset + 3;
@ -773,13 +831,17 @@ void TLSCallbacks::ParseClientHello() {
return ParseFinish(); return ParseFinish();
hello_.state = kParsePaused; hello_.state = kParsePaused;
{
hello_obj = Object::New(); hello_obj = Object::New();
hello_obj->Set(sessionid_sym, hello_obj->Set(sessionid_sym,
Buffer::New(reinterpret_cast<char*>(session_id), Buffer::New(reinterpret_cast<char*>(session_id),
session_size)); session_size));
bool have_tls_ticket = (tls_ticket != NULL && tls_ticket_size != 0);
hello_obj->Set(tls_ticket_sym, Boolean::New(have_tls_ticket));
argv[0] = hello_obj; argv[0] = hello_obj;
MakeCallback(object(), onclienthello_sym, 1, argv); MakeCallback(object(), onclienthello_sym, 1, argv);
}
break; break;
case kParseEnded: case kParseEnded:
default: default:
@ -1364,6 +1426,7 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
version_sym = String::New("version"); version_sym = String::New("version");
ext_key_usage_sym = String::New("ext_key_usage"); ext_key_usage_sym = String::New("ext_key_usage");
sessionid_sym = String::New("sessionId"); sessionid_sym = String::New("sessionId");
tls_ticket_sym = String::New("tlsTicket");
} }
} // namespace node } // namespace node

30
test/simple/test-tls-session-cache.js

@ -28,10 +28,14 @@ require('child_process').exec('openssl version', function(err) {
console.error('Skipping because openssl command is not available.'); console.error('Skipping because openssl command is not available.');
process.exit(0); process.exit(0);
} }
doTest(); doTest({ tickets: false } , function() {
doTest({ tickets: true } , function() {
console.error('all done');
});
});
}); });
function doTest() { function doTest(testOptions, callback) {
var common = require('../common'); var common = require('../common');
var assert = require('assert'); var assert = require('assert');
var tls = require('tls'); var tls = require('tls');
@ -50,6 +54,7 @@ function doTest() {
requestCert: true requestCert: true
}; };
var requestCount = 0; var requestCount = 0;
var resumeCount = 0;
var session; var session;
var badOpenSSL = false; var badOpenSSL = false;
@ -72,6 +77,7 @@ function doTest() {
}; };
}); });
server.on('resumeSession', function(id, callback) { server.on('resumeSession', function(id, callback) {
++resumeCount;
assert.ok(session); assert.ok(session);
assert.equal(session.id.toString('hex'), id.toString('hex')); assert.equal(session.id.toString('hex'), id.toString('hex'));
@ -83,12 +89,12 @@ function doTest() {
server.listen(common.PORT, function() { server.listen(common.PORT, function() {
var client = spawn('openssl', [ var client = spawn('openssl', [
's_client', 's_client',
'-tls1',
'-connect', 'localhost:' + common.PORT, '-connect', 'localhost:' + common.PORT,
'-key', join(common.fixturesDir, 'agent.key'), '-key', join(common.fixturesDir, 'agent.key'),
'-cert', join(common.fixturesDir, 'agent.crt'), '-cert', join(common.fixturesDir, 'agent.crt'),
'-reconnect', '-reconnect'
'-no_ticket' ].concat(testOptions.tickets ? [] : '-no_ticket'), {
], {
stdio: [ 0, 1, 'pipe' ] stdio: [ 0, 1, 'pipe' ]
}); });
var err = ''; var err = '';
@ -97,22 +103,30 @@ function doTest() {
err += chunk; err += chunk;
}); });
client.on('exit', function(code) { client.on('exit', function(code) {
console.error('done');
if (/^unknown option/.test(err)) { if (/^unknown option/.test(err)) {
// using an incompatible version of openssl // using an incompatible version of openssl
assert(code); assert(code);
badOpenSSL = true; badOpenSSL = true;
} else } else
assert.equal(code, 0); assert.equal(code, 0);
server.close(); server.close(function() {
setTimeout(callback, 100);
});
}); });
}); });
process.on('exit', function() { process.on('exit', function() {
if (!badOpenSSL) { if (!badOpenSSL) {
assert.ok(session); if (testOptions.tickets) {
assert.equal(requestCount, 6);
assert.equal(resumeCount, 0);
} else {
// initial request + reconnect requests (5 times) // initial request + reconnect requests (5 times)
assert.ok(session);
assert.equal(requestCount, 6); assert.equal(requestCount, 6);
assert.equal(resumeCount, 5);
}
} }
}); });
} }

Loading…
Cancel
Save