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