socat
socat - Multipurpose relay is described upstream as:Abstract
- what:
- "netcat++" (extended design, new implementation)
- OS:
- AIX, BSD, HP-UX, Linux, Solaris e.a. (UNIX)
- lic:
- GPL2
- inst:
- tar x...; ./configure; make; make install
- doc:
- README; socat.html, socat.1; xio.help
- ui:
- command line
- exa:
- socat TCP6-LISTEN:8080,reuseaddr,fork PROXY:proxy:www.domain.com:80
- keyw:
- tcp, udp, ipv6, raw ip, unix-socket, pty, pipe, listen, socks4, socks4a, proxy-connect, ssl-client, filedescriptor, readline, stdio, exec, system, file, open, tail -f, termios, setsockopt, chroot, fork, perm, owner, trace, dump, dgram, ext3, resolver, datagram, multicast, broadcast, interface, socket, sctp, generic, ioctl
The main idea of socat is to make a network connection between 2 hosts. It is possible to use a TLS connection. socat can use a private key stored in a file on disk. But it was not possible to use PKCS#11 as the cryptographic engine.
The benefit of using a PKCS#11 engine is that any PKCS#11 library can be used. So, of course, in my case the PKCS#11 library will be to use a smart card. So the private key will be inside a smart card and will stay protected inside the smart card.
My patch
socat already uses OpenSSL to process the private key.OpenSSL already supports PKCS#11 through an "engine" mechanism.
It was rather easy to add support of OpenSSL engines in socat. I just replaced a call to SSL_CTX_use_PrivateKey_file() by a call to ENGINE_load_private_key() with the correct engine initialisation.
If the private key "filename" starts with "pkcs11:" then this is not a filename but a PKCS#11 URI scheme (defined in RFC-7512) already understood by OpenSSL pkcs11 engine.
The socat argument is then something like "key=pkcs11:id=%56%78;pin-value=1234"
diff --git a/sslcls.c b/sslcls.c index f9ce389..ddcefd7 100644 --- a/sslcls.c +++ b/sslcls.c @@ -21,6 +21,8 @@ #include "sysutils.h" #include "sycls.h" +#include <openssl/engine.h> + void sycSSL_load_error_strings(void) { Debug("SSL_load_error_strings()"); SSL_load_error_strings(); @@ -214,10 +216,76 @@ int sycSSL_CTX_use_certificate_chain_file(SSL_CTX *ctx, const char *file) { return result; } +static void display_openssl_errors(int l) +{ + const char *file; + char buf[120]; + int e, line; + + if (ERR_peek_error() == 0) + return; + Error2("At %s:%d:\n", __FILE__, l); + + while ((e = ERR_get_error_line(&file, &line))) { + ERR_error_string(e, buf); + Error3("- SSL %s: %s:%d\n", buf, file, line); + } +} + int sycSSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type) { int result; + Debug3("SSL_CTX_use_PrivateKey_file(%p, \"%s\", %d)", ctx, file, type); - result = SSL_CTX_use_PrivateKey_file(ctx, file, type); + + if (0 == strncmp(file, "pkcs11:", 7)) + { + /* Starts with "pkcs11:" -> use pkcs11 OpenSSL engine */ + /* See RFC-7512: The PKCS #11 URI Scheme */ + const char *privkey = file; + ENGINE *engine; + EVP_PKEY *pkey; + result = -1; /* error by default */ + + ENGINE_add_conf_module(); + + ENGINE_load_builtin_engines(); + + engine = ENGINE_by_id("pkcs11"); + if (engine == NULL) { + Error("Could not get engine\n"); + display_openssl_errors(__LINE__); + goto end; + } + +#if 0 + if (!ENGINE_ctrl_cmd_string(engine, "VERBOSE", NULL, 0)) { + display_openssl_errors(__LINE__); + goto end; + } +#endif + + if (!ENGINE_init(engine)) { + Error("Could not initialize engine\n"); + display_openssl_errors(__LINE__); + goto end; + } + + pkey = ENGINE_load_private_key(engine, privkey, 0, 0); + + if (pkey == NULL) { + printf("Could not load key\n"); + display_openssl_errors(__LINE__); + goto end; + } + else + result = 1; + +end: + ENGINE_finish(engine); + } + else + result = SSL_CTX_use_PrivateKey_file(ctx, file, type); + Debug1("SSL_CTX_use_PrivateKey_file() -> %d", result); return result; }
Usage
You need to have a certificate. For the example I will use OpenSSL to generate a self-signed certificate for the client but you can also use your own certification authority.See socat documentation "Securing Traffic Between two Socat Instances Using SSL" for details.
Client side
Generate an RSA key pair:openssl genrsa -out client.key 2048
Generate a self-signed certificate:
openssl req -new -key client.key -x509 -days 365 -out client.pem
Define some shell variables:
ID="ABCDEF" INDEX=0 PIN=1234
Install the SoftHSM software PKCS#11 lib:
On Debian it is the softhsm2 package.
Of course you can use any PKCS#11 library and use a real smart card. But for now we will use a software only PKCS#11 lib for ease of use.
Create a new SoftHSM token
softhsm2-util --init-token --label "A token" \ --pin $PIN --so-pin 123456 \ --slot $INDEX
Install OpenSC to get pkcs11-tool command:
On Debian it is the opensc package.
Import the certificate:
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --slot-index $INDEX --label "Certificate" \ --write-object client.pem --type cert --id $ID
Import the private key:
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --slot-index $INDEX --label "Private key" \ --write-object client.key --type privkey --id $ID --pin $PIN
List the imported objects:
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --list-objects --pin $PINWe get:
Using slot 0 with a present token (0x3a608bb2) Private Key Object; RSA label: Private key ID: abcdef Usage: decrypt, sign, unwrap Access: sensitive Certificate Object; type = X.509 cert label: Certificate subject: DN: C=FR, ST=Some-State, L=Paris, O=Ludovic Rousseau blog, CN=test.example.org/emailAddress=foo@example.org ID: abcdef
Delete the client private key since the key is now in the PKCS#11 token:
rm client.key
Install the OpenSSL PKCS#11 engine:
On Debian it is the package libengine-pkcs11-openssl that provides the file
Server side
Generate an RSA key pair:openssl genrsa -out server.key 2048
Generate a self-signed certificate:
openssl req -new -key server.key -x509 -days 365 -out server.pem
Copy the client certificate file
Run the socat server:
socat openssl-listen:4433,reuseaddr,cert=server.pem,cafile=client.pem,key=server.key -
Client side
Configure the OpenSSL engine:
Create a file named
openssl_conf = openssl_init [openssl_init] engines = engine_section [engine_section] pkcs11 = pkcs11_section [pkcs11_section] engine_id = pkcs11 dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/libpkcs11.so MODULE_PATH = /usr/lib/softhsm/libsofthsm2.so init = 0
Define and export
export OPENSSL_CONF=engine.conf
Run the client:
socat - 'openssl-connect:www.example.com:4433,cafile=server.pem,cert=client.pem,verify=1,key=pkcs11:id=%AB%CD%EF;pin-value=1234;token=A%20token'
Note the key=pkcs11:id=%AB%CD%EF;pin-value=1234 arguments.
Results
On the client I get:
2020/12/16 22:12:51 socat[6335] E SSL_connect(): error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
On the server I get:
2020/12/16 22:12:52 socat[31137] E SSL_accept(): error:1417C0C7:SSL routines:tls_process_client_certificate:peer did not return a certificate
I have a problem with my self signed certificates. I searched for a few hours but without finding the solution.
Plan B: disable certificate checks
To make the server accept the client connection I have to add the socat argument verify=0 on the server side.
socat openssl-listen:4433,reuseaddr,cert=server.pem,cafile=client.pem,key=server.key,verify=0 -
Now the connection works fine in both direction. It is a bad solution. Don't do that on a production server.
Upstream integration
I sent my patch to the upstream maintainer, Gerhard Rieger, in Feb 2020. Gerhard will review the patch and give it a try.Conclusion
I have no news from the socat maintainer since Feb 2020. So I decided to write my blog article even if my patch has not been reviewed and accepted or rejected.
May my patch help you secure a socat connection with your smart card (or another PKCS#11 token).