Skip to content

crypto: add ticketKeys option in createSecureContext - #20916

Closed
ryzokuken wants to merge 4 commits into
nodejs:masterfrom
ryzokuken:crypto-20908
Closed

crypto: add ticketKeys option in createSecureContext#20916
ryzokuken wants to merge 4 commits into
nodejs:masterfrom
ryzokuken:crypto-20908

Conversation

@ryzokuken

@ryzokukenryzokuken commented May 23, 2018

Copy link
Copy Markdown
Contributor

There's a method to initialize a TLS Server using tls.createSever by
specifying a ticketKeys option, but none in the underlying constructor,
tls.createSecureContext.

This PR adds the ticketKeys option to tls.createSecureContext.

Fixes: #20908

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

/cc @nodejs/crypto @bnoordhuis@silverwind@DiegoTUI

I guess this qualifies as a semver-minor?

There's a method to initialize a TLS Server using tls.createSever by
specifying a ticketKeys option, but none in the underlying constructor,
tls.createSecureContext.
This PR adds the ticketKeys option to tls.createSecureContext.
Fixes: nodejs#20908
@nodejs-github-botnodejs-github-bot added the tls Issues and PRs related to the tls subsystem. label May 23, 2018
@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

Running tests locally.

@silverwind

Copy link
Copy Markdown
Contributor

Change seems good to me. Couly you update the docs and add a test that verifies that both createServer and createSecureContext honor the option (compare the given buffer with the output of .getTicketKeys())?

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@silverwind exactly what I had in mind once the tests passed. I guess I'd also have to add a "Changes" entry to the docs.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

Tests pass. Let me add the docs for this.

@DiegoTUI

Copy link
Copy Markdown
Contributor

Other than the docs, I think it also makes sense to add the methods getTicketKeys() and setTicketKeys(keys) to SecureContext. tls.Server has them and it ends up getting the keys from the context. Both methods are pretty convenient for sharing keys among contexts.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@DiegoTUI that does sound like a logical thing to do, thanks. I will add those today.

If I had any doubts regarding the change, this makes it certain that it'll indeed be a semver-minor.

@bnoordhuisbnoordhuis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs documentation and tests. It's not useful to expose this without a documented way to:

  1. get the ticket keys
  2. configure the session timeout

See tls.Server#getTicketKeys() and the sessionTimeout option to tls.createServer().

Comment threadlib/_tls_common.js Outdated
options.clientCertEngine);
}

// Set ticketKeys right inside createSecureContext

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous comment.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove it, in that case.

Comment threadlib/_tls_wrap.js Outdated
crl: this.crl,
sessionIdContext: this.sessionIdContext
sessionIdContext: this.sessionIdContext,
ticketKeys: this.ticketKeys

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a trailing comma?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I certainly will! I didn't add one because there wasn't one to begin with 😅

@silverwind

silverwind commented May 24, 2018

Copy link
Copy Markdown
Contributor

Noticed that some documentation links to the ticketKeys option of createServe in tls.md, those references have to be updated.

@silverwindsilverwind added the semver-minor PRs that contain new features and should be released in the next minor version. label May 24, 2018
@ryzokuken

ryzokuken commented May 24, 2018

Copy link
Copy Markdown
ContributorAuthor
Helpful checklist:
  • Remove superfluous comment
  • Add a trailing comma
  • getTicketKeys
    • function
    • docs
    • tests
  • setTicketKeys
    • function
    • docs
    • tests
  • sessionTimeout
    • function
    • docs
    • tests
  • overall docs
  • overall tests
  • changes block

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@bnoordhuis@silverwind No docs or tests still, but added the required functions and option, so PTAL

Comment threadlib/_tls_common.js Outdated
}

SecureContext.prototype.getTicketKeys = function getTicketKeys(keys) {
return this.context.getTicketKeys(keys);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize you copied this from elsewhere but this method doesn't take any arguments, it returns a new buffer.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd look into the original context's getTicketKeys() method and try working with that one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, it can take arguments? Idk, the code was:

voidSecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
SecureContext* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
Local<Object> buff = Buffer::New(wrap->env(), 48).ToLocalChecked();
memcpy(Buffer::Data(buff), wrap->ticket_key_name_, 16);
memcpy(Buffer::Data(buff) + 16, wrap->ticket_key_hmac_, 16);
memcpy(Buffer::Data(buff) + 32, wrap->ticket_key_aes_, 16);
args.GetReturnValue().Set(buff);
#endif// !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I think most if not all people call it without any args plus no arguments mentioned in the docs, so I'll make the change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean args? Any C++ API function takes that as its argument.

If you're referring to args.Holder(), that's the this object. Positional arguments are accessed using array notation, i.e., args[0].

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis TIL, I guess 😅 Not super good at the C++ part of things.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, getTicketKeys indeed doesn't use any arguments while setTicketKeys takes one, perfect.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@bnoordhuis added a few tests, they're running locally. PTAL and let me know if I missed anything.

@ryzokuken

ryzokuken commented May 26, 2018

Copy link
Copy Markdown
ContributorAuthor

I needed deepStrictEqual instead! Made the change, will push alongside others, please ignore that part.

@ryzokuken

ryzokuken commented May 26, 2018

Copy link
Copy Markdown
ContributorAuthor

@bnoordhuis I have added the sessionTimeout option, but cannot decide how to test it. I could either add a getter for it and assert the value from the getter with the one I enter at the constructor or do something closer to the real world but a tad hacky like what we do at test-tls-session-timeout.js.

test-tls-session-timeout.js
'use strict';constcommon=require('../common');if(!common.opensslCli)common.skip('node compiled without OpenSSL CLI.');if(!common.hasCrypto)common.skip('missing crypto');consttmpdir=require('../common/tmpdir');doTest();// This test consists of three TLS requests --// * The first one should result in a new connection because we don't have// a valid session ticket.// * The second one should result in connection resumption because we used// the session ticket we saved from the first connection.// * The third one should result in a new connection because the ticket// that we used has expired by now.functiondoTest(){constassert=require('assert');consttls=require('tls');constfs=require('fs');constjoin=require('path').join;constfixtures=require('../common/fixtures');constspawn=require('child_process').spawn;constSESSION_TIMEOUT=1;constkey=fixtures.path('agent.key');constcert=fixtures.path('agent.crt');constoptions={key: key,cert: cert,ca: [cert],sessionTimeout: SESSION_TIMEOUT};// We need to store a sample session ticket in the fixtures directory because// `s_client` behaves incorrectly if we do not pass in both the `-sess_in`// and the `-sess_out` flags, and the `-sess_in` argument must point to a// file containing a proper serialization of a session ticket.// To avoid a source control diff, we copy the ticket to a temporary file.constsessionFileName=(function(){constticketFileName='tls-session-ticket.txt';consttmpPath=join(tmpdir.path,ticketFileName);fs.writeFileSync(tmpPath,fixtures.readSync(ticketFileName));returntmpPath;}());// Expects a callback -- cb(connectionType : enum ['New'|'Reused'])functionClient(cb){constflags=['s_client','-connect',`localhost:${common.PORT}`,'-sess_in',sessionFileName,'-sess_out',sessionFileName];constclient=spawn(common.opensslCli,flags,{stdio: ['ignore','pipe','ignore']});letclientOutput='';client.stdout.on('data',function(data){clientOutput+=data.toString();});client.on('exit',function(code){letconnectionType;constgrepConnectionType=(line)=>{constmatches=line.match(/(New|Reused),/);if(matches){connectionType=matches[1];returntrue;}};constlines=clientOutput.split('\n');if(!lines.some(grepConnectionType)){thrownewError('unexpected output from openssl client');}cb(connectionType);});}constserver=tls.createServer(options,function(cleartext){cleartext.on('error',function(er){if(er.code!=='ECONNRESET')thrower;});cleartext.end();});server.listen(common.PORT,function(){Client(function(connectionType){assert.strictEqual(connectionType,'New');Client(function(connectionType){assert.strictEqual(connectionType,'Reused');setTimeout(function(){Client(function(connectionType){assert.strictEqual(connectionType,'New');server.close();});},(SESSION_TIMEOUT+1)*1000);});});});}

@bnoordhuis

Copy link
Copy Markdown
Member

You could test sessionTimeout by setting it to 1s, creating a ticket, then checking that it's gone after one second.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@bnoordhuis I assigned the sessionTimeout property in a way similar to how we did it in tls.Server, but it probably doesn't work. Pushing the failing test (or passing, in this case, but it shouldn't) for inspection.

@ryzokuken

ryzokuken commented May 26, 2018

Copy link
Copy Markdown
ContributorAuthor

Built Node in debug mode and checked it out myself, SSL_CTX_set_timeout is infact being called. I wonder what else is wrong?

Breakpoint and Backtrace
~/Code/nodejs/node crypto-20908*lldb -- ./out/Debug/node test/parallel/test-tls-securecontext-ticketkeys.js(lldb) target create "./out/Debug/node"Current executable set to './out/Debug/node' (x86_64).(lldb) settings set -- target.run-args "test/parallel/test-tls-securecontext-ticketkeys.js"(lldb) breakpoint set --name SSL_CTX_set_timeoutBreakpoint 1: where = node`SSL_CTX_set_timeout + 12 at ssl_sess.c:916, address = 0x0000000101a910bc(lldb) rProcess 56423 launched: './out/Debug/node' (x86_64)Process 56423 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000101a910bc node`SSL_CTX_set_timeout(s=0x00000001053064b0, t=1) at ssl_sess.c:916 913 long SSL_CTX_set_timeout(SSL_CTX *s, long t) 914 { 915 long l;-> 916 if (s == NULL) 917 return (0); 918 l = s->session_timeout; 919 s->session_timeout = t;Target 0: (node) stopped.(lldb) bt* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000101a910bc node`SSL_CTX_set_timeout(s=0x00000001053064b0, t=1) at ssl_sess.c:916 frame #1: 0x00000001002b5d44 node`node::crypto::SecureContext::SetSessionTimeout(args=0x00007ffeefbfcf70) at node_crypto.cc:1025 frame #2: 0x0000000100607cb3 node`v8::internal::FunctionCallbackArguments::Call(this=0x00007ffeefbfd0d0, handler=0x00003969690bdd41) at api-arguments.cc:29 frame #3: 0x00000001007283ee node`v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(isolate=0x0000000105804c00, function=Handle<v8::internal::HeapObject> @ 0x00007ffeefbfd1e0, new_target=Handle<v8::internal::HeapObject> @ 0x00007ffeefbfd1d8, fun_data=Handle<v8::internal::FunctionTemplateInfo> @ 0x00007ffeefbfd1d0, receiver=Handle<v8::internal::Object> @ 0x00007ffeefbfd1c8, args=BuiltinArguments @ 0x00007ffeefbfd200) at builtins-api.cc:108 frame #4: 0x0000000100726d0a node`v8::internal::Builtin_Impl_HandleApiCall(args=BuiltinArguments @ 0x00007ffeefbfd330, isolate=0x0000000105804c00) at builtins-api.cc:138 frame #5: 0x000000010072683d node`v8::internal::Builtin_HandleApiCall(args_length=6, args_object=0x00007ffeefbfd410, isolate=0x0000000105804c00) at builtins-api.cc:126 frame #6: 0x00001f0f67a042c4 frame #7: 0x00001f0f67a1cf78 frame #8: 0x00001f0f67a108e3 frame #9: 0x00001f0f67a1cf78 frame #10: 0x00001f0f67a1cf78 frame #11: 0x00001f0f67a1cf78 frame #12: 0x00001f0f67a1cf78 frame #13: 0x00001f0f67a1cf78 frame #14: 0x00001f0f67a1cf78 frame #15: 0x00001f0f67a1cf78 frame #16: 0x00001f0f67a1cf78 frame #17: 0x00001f0f67a1cf78 frame #18: 0x00001f0f67a16e15 frame #19: 0x00001f0f67a0b581 frame #20: 0x0000000100fd0efd node`v8::internal::GeneratedCode<v8::internal::Object*, v8::internal::Object*, v8::internal::Object*, v8::internal::Object*, int, v8::internal::Object***>::Call(this=0x00007ffeefbfdd50, args=0x00003969a12822e1, args=0x000039699cc0e4d9, args=0x00003969a1282201, args=2, args=0x00007ffeefbfe3e0) at simulator.h:110 frame #21: 0x0000000100fcf182 node`v8::internal::(anonymous namespace)::Invoke(isolate=0x0000000105804c00, is_construct=false, target=Handle<v8::internal::Object> @ 0x00007ffeefbfde70, receiver=Handle<v8::internal::Object> @ 0x00007ffeefbfde68, argc=2, args=0x00007ffeefbfe3e0, new_target=Handle<v8::internal::Object> @ 0x00007ffeefbfde60, message_handling=kReport, execution_target=kCallable) at execution.cc:153 frame #22: 0x0000000100fcea03 node`v8::internal::(anonymous namespace)::CallInternal(isolate=0x0000000105804c00, callable=Handle<v8::internal::Object> @ 0x00007ffeefbfdf40, receiver=Handle<v8::internal::Object> @ 0x00007ffeefbfdf38, argc=2, argv=0x00007ffeefbfe3e0, message_handling=kReport, target=kCallable) at execution.cc:189 frame #23: 0x0000000100fce89d node`v8::internal::Execution::Call(isolate=0x0000000105804c00, callable=Handle<v8::internal::Object> @ 0x00007ffeefbfdfa0, receiver=Handle<v8::internal::Object> @ 0x00007ffeefbfdf98, argc=2, argv=0x00007ffeefbfe3e0) at execution.cc:200 frame #24: 0x00000001005b0f48 node`v8::Function::Call(this=0x000000010703ea68, context=(val_ = 0x00000001058216e0), recv=(val_ = 0x0000000105804c78), argc=2, argv=0x00007ffeefbfe3e0) at api.cc:5077 frame #25: 0x000000010008a872 node`node::ExecuteBootstrapper(env=0x00007ffeefbfe4f0, bootstrapper=(val_ = 0x000000010703ea68), argc=2, argv=0x00007ffeefbfe3e0, out=0x00007ffeefbfe258) at node.cc:3067 frame #26: 0x0000000100089a0c node`node::LoadEnvironment(env=0x00007ffeefbfe4f0) at node.cc:3167 frame #27: 0x000000010009460e node`node::Start(isolate=0x0000000105804c00, isolate_data=0x0000000107040400, argc=2, argv=0x0000000105200050, exec_argc=0, exec_argv=0x00000001052001f0) at node.cc:4256 frame #28: 0x000000010008f19b node`node::Start(event_loop=0x0000000102da7a40, argc=2, argv=0x0000000105200050, exec_argc=0, exec_argv=0x00000001052001f0) at node.cc:4363 frame #29: 0x000000010008e7c5 node`node::Start(argc=2, argv=0x0000000105200050) at node.cc:4414 frame #30: 0x0000000101cb912e node`main(argc=2, argv=0x00007ffeefbff170) at node_main.cc:124 frame #31: 0x0000000100001034 node`start + 52(lldb) cProcess 56423 resuming<Buffer 38 67 52 0b 46 dc 16 f1 df df cf 62 81 f8 a0 3b ed 45 27 75 d4 f9 9e 00 2d 5b fc 1d 8a 3e 60 20 eb ae ea 7f 31 45 0d f8 24 f6 4a 48 6d e1 ca 04> <Buffer 38 67 52 0b 46 dc 16 f1 df df cf 62 81 f8 a0 3b ed 45 27 75 d4 f9 9e 00 2d 5b fc 1d 8a 3e 60 20 eb ae ea 7f 31 45 0d f8 24 f6 4a 48 6d e1 ca 04>Process 56423 exited with status = 0 (0x00000000)(lldb) exit

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@nodejs/crypto @bnoordhuis PTAL.

@ryzokuken

ryzokuken commented May 31, 2018

Copy link
Copy Markdown
ContributorAuthor

@nodejs/crypto anyone willing to give this a shot with me during the summit?

P.S. giving this a shot == helping me debug this, I'm clueless.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

ping @nodejs/crypto please help?

@tniessen

tniessen commented Jun 10, 2018

Copy link
Copy Markdown
Member

Sorry, didn't have time to look at this yet, and I am not that confident with the TLS APIs of OpenSSL. Are you sure that the test case is correct?

Edit: Also, I believe the subsystem should be tls, not crypto.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

@tniessen that's what I'm afraid of. I'm so bad with the specifics of this that I don't know if the test case is correct. Let me try looking deeper too.

@jasnell

Copy link
Copy Markdown
Member

Ping @ryzokuken

@jasnelljasnell added the stalled Issues and PRs that are stalled. label Sep 10, 2018
@Trott

Copy link
Copy Markdown
Member

@ryzokuken Still working on this?

@BridgeAR

Copy link
Copy Markdown
Member

Closing due to long inactivity. @ryzokuken please reopen / open a new PR in case you want to continue working on this.

@ryzokuken

Copy link
Copy Markdown
ContributorAuthor

I'm sorry I put this in the backseat, @jasnell@Trott@BridgeAR. Hopefully will publish a fix later.

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

Labels

semver-minorPRs that contain new features and should be released in the next minor version.stalledIssues and PRs that are stalled.tlsIssues and PRs related to the tls subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to share ticketKeys in secureContext

9 participants

@ryzokuken@silverwind@DiegoTUI@bnoordhuis@tniessen@jasnell@Trott@BridgeAR@nodejs-github-bot