- add support for OpenSSL engine and loading private keys from HSM - for when kamailio is a TLS edge proxy and needs to use HSM - currently we initialize the engine in worker processes as PKCS#11 libraries are not guaranteed to be fork() safe
- new config params - engine: name the OpenSSL engine - engine_config: an OpenSSL config format file used to bootstrap engines - engine_algorithms: list of algorithms to delegate to the engine
- tested with Gemalto SafeNet Luna (AWS CloudHSM) with RSA and EC private keys TLSv1.2 and PFS cipher suites
<!-- Kamailio Pull Request Template -->
<!-- IMPORTANT: - for detailed contributing guidelines, read: https://github.com/kamailio/kamailio/blob/master/.github/CONTRIBUTING.md - pull requests must be done to master branch, unless they are backports of fixes from master branch to a stable branch - backports to stable branches must be done with 'git cherry-pick -x ...' - code is contributed under BSD for core and main components (tm, sl, auth, tls) - code is contributed GPLv2 or a compatible license for the other components - GPL code is contributed with OpenSSL licensing exception -->
#### Pre-Submission Checklist <!-- Go over all points below, and after creating the PR, tick all the checkboxes that apply --> <!-- All points should be verified, otherwise, read the CONTRIBUTING guidelines from above--> <!-- If you're unsure about any of these, don't hesitate to ask on sr-dev mailing list --> - [ ] Commit message has the format required by CONTRIBUTING guide - [ ] Commits are split per component (core, individual modules, libs, utils, ...) - [ ] Each component has a single commit (if not, squash them into one commit) - [ ] No commits to README files for modules (changes must be done to docbook files in `doc/` subfolder, the README file is autogenerated)
#### Type Of Change - [ ] Small bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds new functionality) - [ ] Breaking change (fix or feature that would change existing functionality)
#### Checklist: <!-- Go over all points below, and after creating the PR, tick the checkboxes that apply --> - [ ] PR should be backported to stable branches - [ ] Tested changes locally - [ ] Related to issue #XXXX (replace XXXX with an open issue number)
#### Description <!-- Describe your changes in detail --> - add support for OpenSSL engine and loading private keys from HSM - for when kamailio is a TLS edge proxy and needs to use HSM - currently we initialize the engine in worker processes as PKCS#11 libraries are not guaranteed to be fork() safe
- new config params - engine: name the OpenSSL engine - engine_config: an OpenSSL config format file used to bootstrap engines - engine_algorithms: list of algorithms to delegate to the engine
- tested with Gemalto SafeNet Luna (AWS CloudHSM) with RSA and EC private keys TLSv1.2 and PFS cipher suites You can view, comment on, or merge this pull request online at:
https://github.com/kamailio/kamailio/pull/1484
-- Commit Summary --
* tls: add support for OpenSSL engine and private keys in HSM
-- File Changes --
M src/modules/tls/tls_domain.c (178) M src/modules/tls/tls_mod.c (91) M src/modules/tls/tls_mod.h (9)
-- Patch Links --
https://github.com/kamailio/kamailio/pull/1484.patch https://github.com/kamailio/kamailio/pull/1484.diff
Documentation updates will followed after feedback on this PR.
Hello, thank you for the contribution! A few observations/questions from my side:
* you added the new functionality as a pre-processor define, what was the reason for this? Will this change break some other functionality, or is there some other compatibility issue/new dependencies introduced? Otherwise I would suggest to make it configurable during run time. * you have added a fair number of white space changes, was this caused from a automated code formatting or something similar? * you added a configuration snipped to the code, this should be also moved to the module README or another file (but its OK to do it later as normal docs are also missing, as you mentioned)
Lets work on this first and then have a closer look to the code.
Thanks for the comments, I summarize actionable items at the bottom as the conversation develops. I can push further commits, and do the final squash when it can be accepted.
1. Preprocessor defines `OPENSSL_NO_ENGINE` - followed nginx and HAProxy where they use this to omit compile-time code that uses OpenSSL `ENGINE_xxxx` functions. Frankly I doubt any modern OpenSSL actually defines this. Same purpose as `OPENSSL_NO_ECDH` in existing `tls.c`.
At runtime it might be difficult as the symbol won't be in the users `libcrypto.so`. If we include these symbols, then the users `libcrypto.so` is required to have engine support (even if they don't use it)
Currently the runtime use is controlled by the proposed modparam `engine`, but ENGINE symbols are still UND in `tls.so`.
1. whitespace - added to TODO list below: it was a code editor setting, my bad 1. documentation - added to TODO list
Sample nginx code (because of `ENGINE_*` symbols). HAProxy has similar constructs: ``` #ifndef OPENSSL_NO_ENGINE u_char *p, *last; ENGINE *engine; EVP_PKEY *pkey; p = key->data + sizeof("engine:") - 1; last = (u_char *) ngx_strchr(p, ':'); if (last == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid syntax in "%V"", key); return NGX_ERROR; } *last = '\0'; engine = ENGINE_by_id((char *) p); ```
TODO list: * revert code editor gratuitous whitespace changes * documentation updates for new configuration directives
You will notice that the PR moves HSM private keys loading to child (after fork()). Some further explanation is in order:
Engines like AWS CloudHSM(SafeNet "gem" and "LunaCA3" engines) are wrappers around their PKCS 11 implementations. Some of these libraries do not behave predictably after fork(). For example, if the token is initialized in master, then some HSM keys loaded, the handles can become invalid in a fork()'ed child or you will get weird runtime errors.
GNUTLS describes this problem: https://www.gnutls.org/manual/gnutls.html#PKCS11-Initialization
Using opensc or SoftHSM2 is usually not a problem as they handle fork() properly.
* I understand the reasoning behind the pre-processor defines now, if this is an existing patter used in other projects its probably a good idea to use it in our code as well. * As for the private key loading procedure related to the fork(), I know this issue from other modules, good that you take care of that. I will have a closer look to the code, and comment in-line there.
@aalba6675 pushed 1 commit.
2b90923 revert editor whitespace changes
Are the new config parameters needed only in the global scope of the module level, or it can be something needed per tls config profile (client/server) inside tls.cfg?
* The current implementation assumes a single global engine, and per profile private key: via the syntax `private_key: /engine:HSMPRIVATEKEY`. This is an expedient workaround as the parser treats strings not starting with `/` as relative PEM files. The magic prefix `/engine:` is meant for the profile to select its HSM key. * it should be possible to have per profile engine as well: that is indeed a bit more complicated so I would like to try to get the easier case critiqued first, and possibly merged
henningw commented on this pull request.
@@ -1113,7 +1119,73 @@ static int passwd_cb(char *buf, int size, int rwflag, void *filename)
#endif }
+#ifndef OPENSSL_NO_ENGINE +/**
Great that you added doxygen comments
@@ -496,3 +527,59 @@ int mod_register(char *path, int *dlflags, void *p1, void *p2)
return 0; } + + + +#ifndef OPENSSL_NO_ENGINE +/* + * initialize OpenSSL engine in child process + * PKCS#11 libraries are not guaranteed to be fork() safe + * + */ +static int engine_init() +{ + /*
As discussed, this should be either in the module README or as dedicated cfg example in the module dir.
*
+ * # OpenSSL config file: /usr/local/etc/kamailio/tls_gemalto.conf + * # https://www.openssl.org/docs/manmaster/man5/config.html + * # /usr/lib64/openssl/engines/libgem.so is the Gemalto OpenSSL engine for CloudHSM/SafeNet Luna + * + * kamailio = kamailio_init + * [kamailio_init] + * engines = engine_section + * [engine_section] + * gem = gemalto_section + * [ gemalto_section ] + * # engines support ctrl strings; this is Gemalto specific + * # ENGINE_INIT = <slot>:<appid>:<appid>:password=<slot-password> + * ENGINE_INIT = 0:23:24:password=1233-ABCD-EFGH-XY32 + */ + LM_INFO("With OpenSSL engine support\n");
Is this an information for the user? Otherwise change it to debug level.
@@ -30,6 +30,15 @@
#include "../../core/locking.h" #include "tls_domain.h"
+#ifndef OPENSSL_NO_ENGINE +typedef struct tls_engine { + str engine; + str engine_config; + str engine_algorithms; +} tls_engine_t; +#include <openssl/conf.h>
I think you don't need this includes in the header file, consider move this to the tls_mod.c file.
@@ -30,6 +30,15 @@
#include "../../core/locking.h" #include "tls_domain.h"
+#ifndef OPENSSL_NO_ENGINE +typedef struct tls_engine { + str engine; + str engine_config; + str engine_algorithms; +} tls_engine_t; +#include <openssl/conf.h> +#include <openssl/engine.h>
same
err = CONF_modules_load_file(engine_settings.engine_config.s, "kamailio", 0);
+ if (!err) goto error; + } + engine = ENGINE_by_id(engine_settings.engine.s); + if (!engine) goto error; + err = ENGINE_init(engine); + if (!err) goto error; + if (strncmp(engine_settings.engine_algorithms.s, "NONE", 4)) { + err = ENGINE_set_default_string(engine, engine_settings.engine_algorithms.s); + if (!err) goto error; + } + LM_INFO("OpenSSL engine %*s initialized\n", engine_settings.engine.len, engine_settings.engine.s); + } + return 0; +error: + return -1;
It would be great to add some error logging here, to give some hints to the user that the engine, the engine config or the algorithm setting failed.
- @return 0 on success, -1 on error
+ * + * Do this in mod_child() as PKCS#11 libraries are not guaranteed + * to be fork() safe + * + * private_key setting which starts with /engine: is assumed to be + * an HSM key and not a file-based key + */ +static int load_engine_private_key(tls_domain_t* d) +{ + int idx, ret_pwd, i; + EVP_PKEY *pkey; + int procs_no; + + if (!d->pkey_file.s || !d->pkey_file.len) { + DBG("%s: No private key specified\n", tls_domain_str(d));
Is there a valid condition that we should continue here without an private key? If not, we should log an error and return -1, or I am wrong?
@henningw I need your advice on one point: the OpenSSL memory allocation functions are set to ser_* (wrappers around shm*). This means when I allocate private keys in the worker process, they are actually overwriting the private keys from the other children. (The SSL_CTX arrays d->ctx[i] are the same for all workers).
One engine I tested, amazingly could use the private keys overwritten by another mod_chiild().
Further testing with other engines, shows this usually won't work. For such tls domains the engine might need per-worker SSL_CTX array in the domain structure.
@aalba6675 pushed 1 commit.
6966c9f proof-of-concept: implement process-local storage for HSM keys
@aalba6675 pushed 1 commit.
5d5aae2 tls/tls_server.c: add HSM key support in outbound connections
The feature set is generally complete now with the last commit. Just leaving the documentation of the directives TODO
* support for OpenSSL engine and HSM keys for TLS server and client domains * HSM private keys are stored in worker-local memory - probably this is the most intrusive change; the rest is just related to initialization.
@aalba6675 pushed 1 commit.
064689c tls: add documentation for engine params
It happens that I am traveling right now and don't have much spare time, but I will review the code in the next few days and then report if I find something, or squash+merge the commits in this PR if all ok and no other objection meanwhile.
Sorry for the late reply, was yesterday pretty busy as well.
Generally speaking, these are the different approaches that modules use for data access in kamailio child processes:
* all children needs to access to a one shared data structure Create one global structure in mod_init in shm_memory, and then access it from different children. Protect the (possible) concurrent access with locks, if you support run-time modification of this data. You find examples in the carrierroute module, mod_init() -> init_route_data() and its reload functions and other modules.
* All children needs access to a individual local data set Just allocate it in pkg_memory and access it from the children individually. You can find an example in the auth_diameter module, auth_diameter.c mod_child_init() function.
* All children needs access to all local sets Then you need to implement a solution like you choose with a shared data structure, e.g. in a hash table that you write in child_init to. I did not fully understand why you need this here, maybe you can elaborate a bit on the requirements of the HSM child_init.
I have another remark about the hash table you' added. The hash table uses system malloc() to allocate memory. Please change this to pkg_memory, if you need per-process individual memory, or shm_memory for shared memory. If you have more detailed questions, feel free to contact me per e-mail as well (hw at kamailio dot org).
@miconda - as you currently traveling, I can do the further processing of this patch, no need to hurry from your side.
@henningw - thanks for your review and work here! I wrote it more from the perspective that I want to do also a deep review, because tls has some complexity in handling all those per server attributes and I would prefer not to break (if possible!!!). Somehow it was triggered by the reference in the comments about using private memory in the context that most of data for tls is in shared memory. I didn't want to start questioning that, preferring a look at the code before. As it could take time in my side, I made the last remark that people should not wait for me, if they need to do something else.
@aalba6675 pushed 1 commit.
c802024 tls: use pkg_* functions
Thanks for the comments - I have replaced malloc/free in the mapping utilities with `pkg_malloc()/pkg_free()`. Re: "I did not fully understand why you need this here, maybe you can elaborate a bit on the requirements of the HSM child_init."
Background: For soft keys, we initialize the SSL_CTX `d->ctx[i]` in PROC_INIT, then fork(). All SSL_CTX objects are now completely initialized and can be used as-is by SSL* objects.
For the HSM case, the private key handle from `ENGINE_load_private_key()` is a proxy for the private key in the HSM. This proxy object is not guaranteed to be valid in a different process whether by fork() or shared memory.
During `mod_child()` we also cannot load the private key into `SSL_CTX *d->ctx[i]`, as the next child will simply overwrite the value since these are in shared memory. In my first implementation I forgot about shared memory so the keys are what the final child loaded. These keys would only work for the last child and the handle was invalid when other children tried to use them.
In my current implementation, I leave the SSL_CTXs as keyless. Instead loading the private key into a local set, keyed by the SSL_CTX pointer. Therefore SSL_CTX for HSM domains are keyless and incomplete for SSL_accept(). At runtime, just before
SSL_accept(ssl)
I retrieve the SSL_CTX* from ssl, and check if it is indeed keyless, i.e, it exists in the local key set. Then we retrieve the HSM key in just-in-time mode and load into the `SSL* ssl` object.
Summary: Soft key: d->ctx[i] fully initialized with private key from PEM file
HSM key: d->ctx[i] keyless, the privatekey is stored in a local set: as hash table { domain0->ctx[0]: EVP_PKEY* domain0->ctx[1]: EVP_PKEY* .... domain1->ctx[0]: EVP_PKEY* domain1->ctx[2]: EVP_PKEY* ... }.
Notes:
1. Most engines that deal with HSM private keys are wrappers around a vendor or OpenSC PKCS 11 library. 1. AWS CloudHSM engine (`libgem.so`) a wrapper around SafeNet Luna HSM `libCryptoki2_64.so` actually produces private keys that work even in a different process. If the last child/master loads its private key into `d->ctx[i]` all the other children can use this `SSL_CTX`. This type of behaviour is due to their special care in implementaion and not mandated. 1. OpenSC/libp11: this engine can wrap any PKCS11 library into an engine. When wrapping `libCryptoki2_64.so` its private keys didn't work in another process. That is why I chose to use a local set. 1. Interestingly the NGINX developers refused to accept HSM private key handling to the child. They state that is the role of the PKCS 11 library to make sure its objects are valid across a fork(). Sadly, this principle does not accord with reality. 1. Although HAProxy has engine support, they do not use engine private keys yet, so have not addressed this issue. 1. GNUTLS recommends that all PKCS 11 objects be used only in one process; i.e don't try to leak handles across fork() or shared memory.
Thank you for the detailed explanation, I understand the problem now. Then indeed you need a solution like you implemented. With regards to testing, is there a way to test it also without a HSM module, or exists something like a software "HSM" for testing?
@miconda Understood - this is a quite important module, of course its better to review it from different sides.
https://www.opendnssec.org/softhsm/ is a software based HSM
Packaging is here: stretch: https://packages.debian.org/stretch/softhsm2
@miconda - do you had time to do a review as well?
Just to confirm I haven't missed something -- the private keys stored in worker-local memory refer to keeping them in the map structure you introduced with the new files tls_map.{c,h}. They are not referenced from old structures of the tls module, right?
I see that the define conditions are on `#ifndef OPENSSL_NO_ENGINE`, understanding that `OPENSSL_NO_ENGINE` is defined if libssl is compiled without this engine feature. But is this feature depending on some version, or is in libssl for very long time and makes no sense to check for a version that doesn't have support for it at all?
The files tls_map.{c,h} seems to be imported from external source, being under MIT license. tls module seems to be under BSD, anyone knows if there is any conflict between the two or something needs to be mentioned in the README of the tls module?
Some cosmetic things I would like to have for a safety future:
* define guards inside tls_map.h should rely on the name of the file, like in the other cases. right now is `MAP_H`, exposing a risk of a conflict in the future someone adds a map.h somewhere in kamailio code that will be included in the same file with tls_map.h * the global variable `engine` has a rather common name, should be renamed like `ksr_tls_engine`, to make it more specific for kamailio context -- this should avoid unexpected behaviour if one opens the shared objects with RTLD_GLOBAL when there will be an overlap with such common name
1. Yes - HSM private keys are stored in worker local memory and are not referenced in old structures during SIP connections. We make one reference during mod_child: we install it into the shmem SSL_CTX structure once (proc_no == 0) just to check the the private key corresponds to the cert; subsequently this reference is not used at connection time.
Later at connection time, even when we use SSL_CTX for proc_no == 0, we load the worker-local HSM private key JIT into the SSL *object and don't use the (probably invalid) private key reference in SSL_CTX.
2. All main distros debian/RHEL/ubuntu build OpenSSL with engine support. We can skip this check and just assume that kamailio is being built with a reasonable OpenSSL prerequisite if you prefer.
3. License - comments from the community?
4. A few commits for better naming and guards: use better module/filename-specificsymbol names; also make a few more symbols static to avoid accidental leakage with common names.
* About the question: "The files tls_map.{c,h} seems to be imported from external source, being under MIT license. tls module seems to be under BSD, anyone knows if there is any conflict between the two or something needs to be mentioned in the README of the tls module?"
According to www.dwheeler.com/essays/floss-license-slide.html - the MIT licence is compatible with the (new) BSD licence, and the result can be still licenced under BSD licence. So no additional information should be necessary.
OK, thanks for all those details!
I guess this can be merged if nobody else has comments.
Thanks will squash and merge.
Merged #1484.