OpenVPN management socket
One interesting feature of OpenVPN is a management interface through a unix socket:
openvpn --management /dev/openvpn unix --config server_rsa.conf
An easy way to interact with the socket by hand is to use socat:
socat - UNIX-CONNECT:/dev/openvpn
Enable external key management
Instead of relying on OpenVPN to have access to a private key file, it's possible to have the management interface supply a message that could be signed by the private key in another process. The signed message is sent to the management socket and is verified with a public key. The following is a demonstration of how to do this by hand.
openvpn --management /dev/openvpn unix --management-external-key --config server_rsa.conf
After connecting over socat, one should see the following message:
>INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
>RSA_SIGN:MFEwDQYJYIZIAWUDBAIDBQAEQHTjCwsrb+BzOw4cXFTZGf+r0x12SzzdFXeekWCoH6/YbF6/f740Ibvi9Dzf8fY4AXSYWg6qm32/FI42aQtij30=
The message after the colon is base64 encoded, so it has to be decoded into bytes before it's signed with a private key. The following code takes a path to the private key and the base64 encoded message as parameters and prints a base64 encoded signed payload.
#include <stdio.h>
#include <stdint.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include <iostream>
namespace {
struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } };
}
std::string Base64Encode(const std::vector<unsigned char>& binary)
{
std::unique_ptr<BIO,BIOFreeAll> b64(BIO_new(BIO_f_base64()));
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
BIO* sink = BIO_new(BIO_s_mem());
BIO_push(b64.get(), sink);
BIO_write(b64.get(), binary.data(), binary.size());
BIO_flush(b64.get());
const char* encoded;
const long len = BIO_get_mem_data(sink, &encoded);
return std::string(encoded, len);
}
// Assumes no newlines or extra characters in encoded string
std::vector<unsigned char> Base64Decode(const char* encoded)
{
std::unique_ptr<BIO,BIOFreeAll> b64(BIO_new(BIO_f_base64()));
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
BIO* source = BIO_new_mem_buf(encoded, -1); // read-only source
BIO_push(b64.get(), source);
const int maxlen = strlen(encoded) / 4 * 3 + 1;
std::vector<unsigned char> decoded(maxlen);
const int len = BIO_read(b64.get(), decoded.data(), maxlen);
decoded.resize(len);
return decoded;
}
int main(int argc, char ** argv) {
const char* private_key_file = argv[1];
const char* msg = argv[2];
if (private_key_file == NULL || msg == NULL)
return -1;
const std::vector<unsigned char> decoded = Base64Decode(msg);
const unsigned char * message = decoded.data(); // c++11 feature
RSA * privateKey = NULL;
FILE * fp;
if(NULL != (fp= fopen(private_key_file, "r")) )
{
privateKey = PEM_read_RSAPrivateKey(fp,NULL,NULL,NULL);
if(privateKey == NULL)
{
std::cout << "Could NOT read RSA private key file" << std::endl;
}
else
{
std::cout << "RSA modules size " << RSA_size(privateKey) << std::endl;
unsigned char* sigret = (unsigned char*)malloc(RSA_size(privateKey));
int datalen = decoded.size();
std::cout << "Size of message " << datalen << std::endl;
int siglen;
siglen = RSA_private_encrypt(datalen, message, sigret, privateKey, RSA_PKCS1_PADDING);
std::cout << "Size of signature " << siglen << std::endl;
//FILE *fp_sig = fopen("sig.out", "wb");
//fwrite(sigret, sizeof(unsigned char), siglen, fp_sig);
//fclose(fp_sig);
const std::vector<unsigned char> binary(sigret, sigret+siglen);
const std::string encoded = Base64Encode(binary);
std::cout << "Encoded signature: " << std::endl;
std::cout << encoded << std::endl;
free(sigret);
}
}
fclose(fp);
return 0;
}
Compile like this:
g++ -std=c++11 sign_with_private.c -lcrypto -o sign_with_private
Invoke like this:
./sign_with_private ../pki-rsa/private/client1.key MFEwDQYJYIZIAWUDBAIDBQAEQHTjCwsrb+BzOw4cXFTZGf+r0x12SzzdFXeekWCoH6/YbF6/f740Ibvi9Dzf8fY4AXSYWg6qm32/FI42aQtij30=
RSA modules size 256
Size of message 83
Size of signature 256
Encoded signature:
HoOVnqrUXLw0zFxe1ElzcdBxAob/3sD5/8v7zoBXtx0T4PqYKdxtggLzHCoieZlzGQAHIQ30wQHqbezhOLo8JErg6qVNs1dXYMJRaVDQWaXPMKRchhZUFcRkn+ghQrgGKhL1ckLCgbxwKEF2KqxD++BAh3pv4NcS2ml0oGJZ8yxau1YicgEMyeA7h4/pGg1PHvwx3hEd1+UgEJbR6bRRfVXntNEmn1ibIvntY/hOdjIKXHCdhi6mPAZjLAwA2ERHhfDFsCM0nPB26GfEpsJ7jx2090mWD7qaOo+TbU2vxHlIb3nJJeAtKSDhnQlKRIUPeAIwSg1N+qVlZWLJupbcGg==
In the management console, issue rsa-sig command, paste the signature, press enter key, type END and finally press enter again.
External certificate
The management interface can also take a certificate as an input. Start OpenVPN with the appropriate flags:
openvpn --config /data/data/com.machfu.vpn/files/machfu.ovpn --management /dev/openvpn unix --management-external-key --management-external-cert some_cert_description
You should see this message after private key verification:
>INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
>NEED-CERTIFICATE:some_cert_description
In the management console, issue certificate command, copy and paste the certificate, including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----, press enter key, type END and finally press enter again.
References
https://github.com/OpenVPN/openvpn/blob/master/doc/management-notes.txt
https://community.openvpn.net/openvpn/wiki/CVE-2018-7544
https://forums.openvpn.net/viewtopic.php?t=18832
https://community.openvpn.net/openvpn/ticket/764
https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg09399.html
https://www.agwa.name/blog/post/protecting_the_openssl_private_key_in_a_separate_process
https://stackoverflow.com/questions/5288076/base64-encoding-and-decoding-with-openssl