-
Notifications
You must be signed in to change notification settings - Fork 7.6k
OpenSSL ECH integration #840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
Excellent work. ECH would be very useful in nginx. My biggest concern with proposed approach is that this integration makes it virtually impossible to use nginx ECH with other TLS libraries such as BoringSSL or AWS-LC. They have no notion of ECHCONFIG PEM format. I would strongly recommend to have a simple configuration similar to |
|
Thanks for taking a look!
That's not correct. Lighttpd supports both OpenSSL and boring with this format. That's s simple enough shim to handle the file format at the application layer. See here for the code. I'd be happy to do similarly for nginx should boring support be considered a requirement for this PR. (Which'd not be unreasonable.)
I really don't think we want an ECH Lastly, the apache2 maintainers seemed fine with this aspect in the comments they've made on the almost equivalent PR to this one. |
Thanks for the correction! Indeed, not hardcoding |
|
push above includes the obvious things from @yaroslavros comments today |
|
As an FYI, the corresponding apache httpd functionality has been committed there: apache/httpd@0c9cd09 |
Indeed we do need BoringSSL support. Also, reading a PEM file in nginx is only a half of the problem. The other half is how to create those files with BoringSSL? |
|
Regarding the code, @sftcd could you please follow the nginx code style, see https://github.com/nginx/nginx/blob/master/CONTRIBUTING.md for details. |
src/event/ngx_event_openssl.c
Outdated
| case SSL_ECH_STATUS_NOT_TRIED: | ||
| snprintf(buf,PATH_MAX, "not attempted"); | ||
| break; | ||
| case SSL_ECH_STATUS_FAILED: | ||
| snprintf(buf, PATH_MAX, "tried but failed"); | ||
| break; | ||
| case SSL_ECH_STATUS_BAD_NAME: | ||
| snprintf(buf, PATH_MAX, "worked but bad name"); | ||
| break; | ||
| case SSL_ECH_STATUS_SUCCESS: | ||
| snprintf(buf, PATH_MAX, "success"); | ||
| break; | ||
| case SSL_ECH_STATUS_GREASE: | ||
| snprintf(buf, PATH_MAX, "GREASEd ECH"); | ||
| break; | ||
| case SSL_ECH_STATUS_BACKEND: | ||
| snprintf(buf, PATH_MAX, "Backend/inner ECH"); | ||
| break; | ||
| default: | ||
| snprintf(buf, PATH_MAX, "error getting ECH status"); | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure PATH_MAX is related to these values. Moreover, I suggest short upper-case status strings (NOT_TRIED, FAILED etc), see ngx_ssl_get_client_verify() for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack, will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The values do not fully match, eg SSL_ECH_STATUS_BAD_NAME -> WORKED_BAD_NAME, SSL_ECH_STATUS_GREASE -> GREASED.
Also, could you please explain what the SSL_ECH_STATUS_BACKEND status means. I looked into the code and it looks like the flag only makes sense in split mode.
|
@arut: happy to make changes along the above lines (maybe tomorrow before I get time to start that), but I've a question first: over the last week or so, the freenginx project has added ECH support taking a slightly different approach to what I did with this PR, the main difference is that they chose to configure file names for the ECH PEM files rather than a directory name (from which those ECH PEM files are read) as is done here. I don't know how or if nginx and freenginx try to maintain config file or other compatibility, but probably good if you tell me that you prefer following the file name or directory name approach before we go much further. Some details can be seen here |
Totally reasonable. If it's ok I'd suggest trying to agree how to use the OpenSSL ECH APIs first, then add a shim to make the same thing work with boring (or AWS-LC). That'd be simpler for me at least:-) Anyway, I'd be happy to follow such a plan.
That's easy enough. I knocked up a small bash script that shows how. |
Had tried to, and now have tried again (seeing that I'd missed some). Might take a few goes to be quite right, but happy to get there. Next push will include that. |
|
push above tries to address @arut comments above, modulo when to do what about boring etc. (and of course my mistakes in trying to do the right thing:-) |
arut
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also don't forget to do all this for the Stream module as well.
src/event/ngx_event_openssl.h
Outdated
| /* check defines from <openssl/ssl.h> for ECH support */ | ||
| #if !defined(SSL_OP_ECH_GREASE) | ||
| #define OPENSSL_NO_ECH | ||
| #endif | ||
| #ifndef OPENSSL_NO_ECH | ||
| #include <openssl/ech.h> | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this block? As I see, openssl/ech.h is included automatically by openssl/ssl.h.
Regarding checking OPENSSL_NO_ECH in nginx code for conditional compilation. Since ECH may not be available at all, relying solely on this macro is not a good idea. Modifying this macro is not a good idea either. We already have one feature that's compiled conditionally and may not be available at all, which is OCSP stapling. Here's how this problem is solved for OCSP. In the case of ECH, the solution would look like this:
#if (!defined OPENSSL_NO_ECH && defined SSL_OP_ECH_GREASE)
...
#endif
Now this brings up another problem. In your change there are multiple conditional blocks with #ifndef OPENSSL_NO_ECH and using the condition above would significantly complicate the code, especially if we add BoringSSL ECH at some point. The solution is to minimize the number of those #if's. And again, stapling is a good example. What we need to do is to try to move all conditional compilation to ngx_event_openssl.c. I will comment the code below for more details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, aiming for that now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned before, I don't think it's a good idea to define/redefine OpenSSL macros like OPENSSL_NO_ECH in nginx code. I suggest using the condition #if (!defined OPENSSL_NO_ECH && defined SSL_OP_ECH_GREASE) instead and regrouping the code in a way that reduces the number of such ifs. I think 2 ifs should be enough, one for the functions and one for the variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, tried to do that better now - should we aim for setting an NGX_ECH from auto/lib/openssl/conf or something though? Not sure if that'd be better. See what you think when I push the next rev.
| { ngx_string("ssl_ech_inner_sni"), NULL, ngx_http_ssl_variable, | ||
| (uintptr_t) ngx_ssl_get_ech_inner_sni, NGX_HTTP_VAR_CHANGEABLE, 0 }, | ||
| { ngx_string("ssl_ech_outer_sni"), NULL, ngx_http_ssl_variable, | ||
| (uintptr_t) ngx_ssl_get_ech_outer_sni, NGX_HTTP_VAR_CHANGEABLE, 0 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I"m not sure we need both of them. The outer SNI does make sense. But I'm not sure we need the inner one. The outer one can be called ssl_ech_server_name, similar to ssl_server_name we already have, which is the SNI value (and is the inner SNI for ECH).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair enough that we don't really need ssl_ech_inner_sni but I'd argue to keep the name for the outer - ssl_ech_server_name would easily confuse as to whether it means inner or outer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In any case, we don't use the term sni in variables and directives. It's always $ssl_server_name, $ssl_preread_server_name, proxy_ssl_server_name. Let's try ssl_ech_outer_server_name at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack ssl_ech_outer_server_name it is. I didn't change the function name but can if you prefer. (For now the function is still ngx_ssl_get_ech_outer_sni().
|
|
||
| #ifndef OPENSSL_NO_ECH | ||
| { ngx_string("ssl_echkeydir"), | ||
| NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this directive should be allowed at the server level as well. Please add NGX_HTTP_SRV_CONF:
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll argue (a bit) against that - it seems simpler and better if ECH is inherently set up/available for all virtual servers rather than for some, and there's no real cost to doing so I think. I don't see why you'd want ECH to be available only for some virtual servers and not others?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to force it for all servers. And anyway, you are already using the server configuration (ngx_stream_ssl_srv_conf_t) which means it's already per-server. And there's no cost in enabling this directive at the server level as well. I'm not advocating for removing the main scope btw, let's just add the server scope in addition to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure - did that, hopefully correctly:-)
|
Those all look sensible, thanks. Will get at 'em shortly but travelling today. |
Also please rebase the code to the recent master. We had some SNI-related changes there recently. |
|
✅ All required contributors have signed the F5 CLA for this PR. Thank you! |
|
I have hereby read the F5 CLA and agree to its terms |
|
recheck |
|
The push above fixes the things requested this week except:
I'll take a look at 1 above and we can discuss 2/3/4. First though, I'm gonna do a bit of playing with boringssl to see if the work the lighttpd maintainer did translates over as easily as I think it should. |
|
The push above is a version that seems to work (with limited localhost tests) with BoringSSL. Likely would need more work, but passes those localhost tests ok (with real or GREASEd ECH and an HRR case). |
|
The push above tries to add ECH to the stream module. Not that sure I've done that correctly, but it works in local testing. |
|
@arut just checking where we're at (I have to do some project-internal reporting for month end:-) - do you need me to do anything to progress things? |
I'm getting back to this now. I've been busy with the release. @sftcd, any news about split mode? Feels like split mode is a bigger feature than shared mode. |
Interesting! A lot of people have said to me that they don't find split-mode so interesting due to how they terminate TLS, but OTOH, we did a small trial with the Wikimedia foundation and they'd have loved to use split-mode if they had someone to be the client-facing server for them. (Finding such a someone turns out not that easy.) Anyway, yes, I have a split-mode implementation for nginx, (and also for haproxy) using the stream module, but that needs an API that hasn't yet been agreed with the OpenSSL maintainers and that isn't in the OpenSSL feature branch. You'd have to build against a personal (or DEfO-project-specific) fork of OpenSSL rather than the OpenSSL project's ECH feature branch. The split-mode API required is an oddball thing from the POV of the OpenSSL library - you need to use an You can see the DEfO-project fork's split-mode API here and the core of how we use that in nginx here. I'd not be surprised if the latter needed changes btw:-) IIUC split-mode is also not yet supported by other TLS libraries. So I think the way forward would be to handle ECH shared-mode first, then later to add split-mode in a separate PR once we've agreed a split-mode API with OpenSSL maintainers. (Once I've gotten that API agreed with OpenSSL maintainers I do plan to raise corresponding PRs with TLS server projects.) |
|
@sftcd apparently there's a conflict with the latest changes in nginx, probably because of |
| { ngx_string("ssl_ech_status"), NULL, ngx_http_ssl_variable, | ||
| (uintptr_t) ngx_ssl_get_ech_status, NGX_HTTP_VAR_CHANGEABLE, 0 }, | ||
| { ngx_string("ssl_ech_inner_sni"), NULL, ngx_http_ssl_variable, | ||
| (uintptr_t) ngx_ssl_get_ech_inner_sni, NGX_HTTP_VAR_CHANGEABLE, 0 }, | ||
|
|
||
| { ngx_string("ssl_ech_outer_sni"), NULL, ngx_http_ssl_variable, | ||
| (uintptr_t) ngx_ssl_get_ech_outer_sni, NGX_HTTP_VAR_CHANGEABLE, 0 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest moving these variables down somewhere between ssl_alpn_protocol and ssl_client_cert. Here the ech variables split ssl_curve and ssl_curves. Same for Stream.
The same applies to the order of function declarations in src/event/ngx_event_openssl.h and implementations in src/event/ngx_event_openssl.c.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, think I did that.
src/event/ngx_event_openssl.h
Outdated
| ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, | ||
| ngx_uint_t flags); | ||
| #ifndef OPENSSL_NO_ECH | ||
| ngx_int_t ngx_ssl_echkeydir(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *dir); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the best place for this declaration, next to ngx_ssl_create_connection() which is a runtime function as opposed to ngx_ssl_echkeydir() which is a config-time function . Please move it to other similar declarations, for example after ngx_ssl_dhparam(). Same for the implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also tried to do that
Thanks for details. Please keep us updated regarding the progress in OpenSSL. |
Just rebased, no other changes yet. |
|
Ok pushed a version that tries to address all yesterday's comments unless I missed some;-( I'd say there's likely still more to be done too. Given there's a lot of changes (moving chunks of code about) if you'd like me to squash the commits, just say. (I'll be travelling tomorrow to the IETF meeting so might be slower responding for the next week.) |
I looked at the split ech nginx implementation and I have one question. Obviously it's too early, but maybe discussing this now will help to come up with the right API. The question is, how is config retry supposed to work? I only see one function |
What I've done for split-mode is setup a virtual host for the |
Forgot to say: thanks for taking a look, and if it turns out that the current API looks good to you, or we fix it to make that true, then that may well help when discussing split-mode with the OpenSSL maintainers... so thanks again:-) |
But to do this, you need to shove the outer CH back into the socket, don't you? Or you do it on a remote server after proxying? |
Ah, no - if the split-mode decrypt fails, the original (outer) CH is just processed as normal, so the connection will go to the server for the Not sure if it'll help or be more confusing but this config is our test case - after ECH decryption is attempted, if it worked, we'll route based on the inner SNI to foo.example.com, if ECH decryption failed, we route to example.com's server. (Note that that config doesn't take into account changes resulting from this PR as we've not updated that build for those changes yet.) |
OK, so the outer CH is proxied in this case and then the backend will do the retry config. My point is, it would be more efficient to handle it at the proxy though. Otherwise the backend needs the public certificate/key to do the retry config, which looks completely unnecessary. |
In no particular order:
We should end up with a very simple patch at this point. Regarding the squash, yes please do it. Have a good trip! |
|
@Maryna-f5 - if that milestone has a date that it'd be useful for me to understand be good to know that so I don't miss it. (I'm travelling home today so will be back at this once I get there.) |
|
Hi, @sftcd, Thank you for your contribution! |
|
Gonna start doing that squash-commits and splitting out the boring stuff shortly. Will post on here when things are ready for another review, as I might temporarily break something in the PR branch along the way:-) |
|
OK, I think the push above is the more minimal PR asked-for. It squashed commits, allows multiple |
|
The more complete PR (with boriing support and globbing) is #973. A couple of notes on that:
|
|
oops, I notice I forgot to do |
arut
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, the change looks mostly ok. I suggest some cleanup and simplification.
I left some comments, mostly style-related. Generally, I suggest looking at the code in src/event/ngx_event_openssl.c and copying the style.
src/event/ngx_event_openssl.c
Outdated
| /* check defines from <openssl/ssl.h> for OpenSSL ECH support */ | ||
| #if !defined(OPENSSL_NO_ECH) && defined(SSL_OP_ECH_GREASE) | ||
| #define NGX_ECH | ||
| #endif | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at this again and I think we can eliminate NGX_ECH and use #ifdef SSL_OP_ECH_GREASE instead, just like we do in other places. A good example is #ifdef SSL_READ_EARLY_DATA_SUCCESS.
For BoringSSL we'll choose another macro. A good example is ngx_ssl_early_data() where we use different macros for OpenSSL and BoringSSL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Had to re-check how that connected with OPENSSL_NO_ECH but as of now SSL_OP_ECH_GREASE is only defined if OPENSSL_NO_ECH is not. That could change though to avoid possible conflicts since SSL_OP_ECH_GREASE is an index for a bit-mask, which is why I did it as above. But, in any case, you'd get a compiler or linker error I guess, so I'll do the change requested.
src/event/ngx_event_openssl.c
Outdated
| static ngx_int_t | ||
| ngx_ssl_load_one_echkey(ngx_ssl_t *ssl, ngx_str_t *filename, void *echstore, | ||
| int for_retry) | ||
| { | ||
| BIO *in; | ||
| ngx_int_t rv; | ||
| OSSL_ECHSTORE *es; | ||
|
|
||
| rv = NGX_ERROR; | ||
| in = BIO_new_file((char *)filename->data, "r"); | ||
| if (in == NULL) | ||
| return NGX_ERROR; | ||
| es = (OSSL_ECHSTORE*) echstore; | ||
| if (1 == OSSL_ECHSTORE_read_pem(es, in, for_retry)) | ||
| rv = NGX_OK; | ||
| BIO_free_all(in); | ||
| return rv; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest inlining this function into ngx_ssl_echfiles().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
src/event/ngx_event_openssl.c
Outdated
| if (somekeyworked == 0) { | ||
| ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | ||
| "ngx_ssl_echfiles loaded no keys but ECH configured"); | ||
| return NGX_ERROR; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like dead code. I suggest removing it, as well as the somekeyworked variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
src/event/ngx_event_openssl.c
Outdated
| } | ||
|
|
||
| /* subsequent times around the loop not returned for retries */ | ||
| for_retry = OSSL_ECH_NO_RETRY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can derive this value from i. I suggest removing the for_retry variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, though for me that's less clear for a reader but I added a comment.
| for_retry = OSSL_ECH_NO_RETRY; | ||
| } | ||
|
|
||
| if (OSSL_ECHSTORE_num_keys(es, &numkeys) != 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why request the number of keys? If the array exists, the number of files should be >= 1, so as the number of keys. I suggest removing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is allowed to have PEM files with just ECHConfigList values and no private key (the current spec is for zero or one private keys per file plus one ECHConfigList), so we need to check we have some private key before ECH can work. I cleaned up a bit and added a comment to explain.
| #endif | ||
|
|
||
| #endif | ||
| return NGX_OK; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There needs to be a default s->len = 0 in this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done now.
| ngx_str_set(s, "FAILED"); | ||
| break; | ||
| case SSL_ECH_STATUS_BAD_NAME: | ||
| ngx_str_set(s, "WORKED_BAD_NAME"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just BAD_NAME?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's really for completeness and would only happen on a client. If at some point, you extend nginx as a proxy that terminates TLS inbound and sets up TLS outbound with ECH used for the latter then it could be a status that's returned. It could be taken out for now if desired but might get forgotten later. As to the string, I figured just BAD_NAME might be more easily confused with the ECH fallback via retry-configs, but can change to that if you prefer.
| case SSL_ECH_STATUS_BACKEND: | ||
| ngx_str_set(s, "INNER"); | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain what this code means? And what's the difference between this and SUCCESS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In ECH split-more the backend server will get a ClientHello that does contain an ECH extension but with the 'inner' value set. Even though we're not supporting split-more as a client-facing server (i.e. front-end) yet, we might be a backend behind some who is an ECH front-end, so this would report on that situation. And it's not ECH 'SUCCESS' since we didn't ECH decrypt, but just got presented with a ClientHelllo containing an ECH extension set to 'inner'.
src/event/ngx_event_openssl.c
Outdated
| if (in == NULL) | ||
| return NGX_ERROR; | ||
| es = (OSSL_ECHSTORE*) echstore; | ||
| if (1 == OSSL_ECHSTORE_read_pem(es, in, for_retry)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: we prefer to use if (fun() == 1) {..} instead of if (1 == fun()) {..}.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
src/stream/ngx_stream_ssl_module.c
Outdated
| offsetof(ngx_stream_ssl_srv_conf_t, dhparam), | ||
| NULL }, | ||
|
|
||
| { ngx_string("ssl_echfile"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ssl_ech_file? Same for the field and function names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think I got all those now (bit of testing then I'll do a push)
|
Push above tries to handle all today's @arut comments. |
Proposed changes
This PR adds Encrypted Client Hello (ECH) functionality to NGINX, when using OpenSSL for TLS.
This addresses #266
Notes:
Lastly, for open-ness, our work on this has been funded by the Open Technology Fund (OTF) in the DEfO project.