PKCS#11 support in socat

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 $PIN
We 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 /usr/lib/x86_64-linux-gnu/engines-1.1/libpkcs11.so (on a x86_64 architecture).

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 client.pem to the server, and the server certificate file server.pem to the client.

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 engine.conf and containing:

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 OPENSSL_CONF environment variable:

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).