OpenVPN management interface

18-05-2018 - 3 minutes, 1 second -

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

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.size());
    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(),, maxlen);
    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 =; // 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;
            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);

            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;
    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: 

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

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.