OpenVPN management interface

18-05-2018 - 4 minutes, 33 seconds -
crypto

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, pres 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