Skip to content
Snippets Groups Projects
openvpn_yubikey_key_management.c 22.7 KiB
Newer Older
#include <stdio.h>

/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2021 OpenVPN Inc <sales@openvpn.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 *  This plugin utilizes a YubiKey with an OTP Challenge Response support to
 *  store a 20 Byte long HMAC-SHA1 server key. This server key is then used to
 *  generate client specific server keys, which, if compromised, will only affect
 *  this client. Because YubiKey doesn't support AES Key storage / derivation, we
 *  use a challenge response mechanisms to derive keys which can't be
 *  reverse engineered to the master server key. Using this plugin reduces the
 *  security of the tls-crypt-v2 from 2 256-Bit keys to a single 160-Bit one.
 */

#include <string.h>
#include <stdlib.h>
#include "openvpn-plugin.h"
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <fcntl.h>
#include <sys/syslog.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>

#include "yubikey_handler.c"
#include "ykstatus.h"

static char *MODULE = "OPENVPN_PLUGIN_CLIENT_KEY_WRAPPING";

/*
 * Constants indicating minimum API and struct versions by the functions
 * in this plugin.  Consult openvpn-plugin.h, look for:
 * OPENVPN_PLUGIN_VERSION and OPENVPN_PLUGINv3_STRUCTVER
 */
#define OPENVPN_PLUGIN_VERSION_MIN 3
#define OPENVPN_PLUGIN_STRUCTVER_MIN 5

#define TLS_CRYPT_V2_MAX_WKC_LEN 1024
#define TLS_CRYPT_V2_KEY_LEN 32
#define TLS_CRYPT_V2_TAG_LEN 32
#define TLS_CRYPT_V2_LEN_LEN 2
#define TLS_CRYPT_V2_SERVER_KEY_LEN 64
#define AES_SLOT 0
#define HMAC_SLOT 1

/* Error handling */
#define ERROR_CHECK(cond, text) do { if (cond) { plog(PLOG_ERR, text); return OPENVPN_PLUGIN_FUNC_ERROR; } } while(0)
#define CRYPTO_ECHECK(cond, text) do { if (cond) { plog(PLOG_ERR, text); goto error_exit; } } while(0)
#define RESPONSE_LENGTH 20
#define CHALLENGE_LENGTH TLS_CRYPT_V2_TAG_LEN

#define RESPONSE_INIT_SUCCEEDED 10

/* Exported plug-in v3 API functions */
plugin_vlog_t plugin_vlog_func = NULL;
plugin_base64_decode_t ovpn_base64_decode = NULL;
plugin_base64_encode_t ovpn_base64_encode = NULL;
plugin_secure_memzero_t ovpn_secure_memzero = NULL;

/*
 * Our context, where we keep our state.
 */
struct plugin_context {
    YK_KEY *yk;
    YK_STATUS *st;
    pid_t background_pid;
    int write_pipe;
    int read_pipe;
    unsigned char slot[2];
    void *acc_code;
    int verb;
};

/* local wrapping of the log function, to add more details */
static void
plog(int flags, char *fmt, ...)
{
    char logid[129];

    snprintf(logid, 128, "%s", MODULE);

    va_list arglist;
    va_start(arglist, fmt);
    plugin_vlog_func(flags, logid, fmt, arglist);
    va_end(arglist);
}

/*
 * Given an environmental variable name, search
 * the envp array for its value, returning it
 * if found or NULL otherwise.
 */
static const char *
get_env(const char *name, const char *envp[])
{
    if (envp)
    {
        int i;
        const unsigned int namelen = strlen(name);
        for (i = 0; envp[i]; ++i)
        {
            if (!strncmp(envp[i], name, namelen))
            {
                const char *cp = envp[i] + namelen;
                if (*cp == '=')
                {
                    return cp + 1;
                }
            }
        }
    }
    return NULL;
}

OPENVPN_EXPORT int
openvpn_plugin_min_version_required_v1()
{
    return OPENVPN_PLUGIN_VERSION_MIN;
}

OPENVPN_EXPORT int
openvpn_plugin_select_initialization_point_v1()
{
    return OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE;
}

OPENVPN_EXPORT int
openvpn_plugin_open_v3(const int v3structver,
                       struct openvpn_plugin_args_open_in const *args,
                       struct openvpn_plugin_args_open_return *ret)
{
    if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
    {
        plog(PLOG_ERR, "this plugin is incompatible with the running version of OpenVPN");
        return OPENVPN_PLUGIN_FUNC_ERROR;
    }

    if (args->ssl_api != SSLAPI_OPENSSL)
    {
        plog(PLOG_ERR, "plug-in can only be used against OpenVPN with OpenSSL\n");
        return OPENVPN_PLUGIN_FUNC_ERROR;
    }

    /* Save global pointers to functions exported from openvpn */
    plugin_vlog_func = args->callbacks->plugin_vlog;
    ovpn_base64_decode = args->callbacks->plugin_base64_decode;
    ovpn_base64_encode = args->callbacks->plugin_base64_encode;
    ovpn_secure_memzero = args->callbacks->plugin_secure_memzero;

    struct plugin_context *context = NULL;
    context = (struct plugin_context *) calloc(1, sizeof(struct plugin_context));
    if (!context)
    {
        goto error;
    }

    const char **argv = args->argv;

    if(!argv[1] || !argv[2])
    {
        plog(PLOG_ERR, "Not all args specified!");
        goto error;
    }

    unsigned char slot_preference = (unsigned char) strtol(argv[1], NULL, 10);
    if(slot_preference == 1 || slot_preference == 2)
    {
        context->slot[0] = slot_preference;
        context->slot[1] = slot_preference;
    }
    else if(slot_preference == 3)
    {
        context->slot[AES_SLOT] = 1;
        context->slot[HMAC_SLOT] = 2;
    }
    else
    {
        plog(PLOG_ERR, "Invalid slot specified!");
        goto error;
    }

    if(strcmp(argv[2], "0") != 0)
    {
Emily Ehlert's avatar
Emily Ehlert committed
        context->acc_code = calloc(strlen(argv[2]) + 1, sizeof(char));
        strcpy(context->acc_code, argv[2]);
        plog(PLOG_DEBUG, "Using access code %s", context->acc_code);
        if(!context->acc_code)
        {
            plog(PLOG_ERR, "calloc(acc_code) failed");
            goto error;
        }
    }
    context->background_pid = 0;

    /*
     * Get verbosity level from environment
     */
    {
        const char *verb_string = get_env("verb", args->envp);
        if (verb_string)
        {
            context->verb = (int) strtol(verb_string, NULL, 10);
        }
    }

    /*
     * Which callbacks to intercept.
     */
    ret->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_KEY_WRAPPING) | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP);
    ret->handle = (openvpn_plugin_handle_t *) context;

    return OPENVPN_PLUGIN_FUNC_SUCCESS;

error:
    plog(PLOG_NOTE, "initialization failed");
    return OPENVPN_PLUGIN_FUNC_ERROR;
}


static int
aes_256_ctr(unsigned const char *data, uint16_t data_len, const unsigned char *key, uint16_t key_length,
            const unsigned char *iv, unsigned char *output)
{
    unsigned char normalized_key[TLS_CRYPT_V2_KEY_LEN] = { 0 };
    memcpy(normalized_key, key, key_length);

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if(!ctx)
        return false;

    EVP_CIPHER_CTX_init(ctx);
    if(!EVP_EncryptInit_ex2(ctx, EVP_aes_256_ctr(), normalized_key, iv, NULL))
        return false;
    int out_len;
    if(!EVP_EncryptUpdate(ctx, output, &out_len, data, data_len))
        return false;
    EVP_CIPHER_CTX_free(ctx);
    return true;
}

static int
do_challenge_response(struct plugin_context *context,
                      const unsigned char *challenge,
                      unsigned char *response,
                      unsigned char slot)
{
    if(context->yk)
    {
        plog(PLOG_DEBUG, "Performing local challenge");
        return challenge_response(context->yk, slot, false, CHALLENGE_LENGTH, challenge, response, true);
    }
    else
    {
        plog(PLOG_DEBUG, "Sending challenge to background process");
        ssize_t bytes_processed;
        bytes_processed = write(context->write_pipe, &slot, sizeof(unsigned char));
        if(bytes_processed != sizeof(unsigned char))
        {
            return false;
        }
        bytes_processed = write(context->write_pipe, challenge, CHALLENGE_LENGTH);
        if(bytes_processed != CHALLENGE_LENGTH)
        {
            return false;
        }

        bytes_processed = read(context->read_pipe, response, RESPONSE_LENGTH);
        if(bytes_processed != RESPONSE_LENGTH)
        {
            return false;
        }
    }

    return true;

}

Emily Ehlert's avatar
Emily Ehlert committed
static int
calculate_cipher(struct plugin_context *context,
                 const unsigned char *data, uint16_t data_len,
                 const unsigned char *tag,
                 unsigned char* output)
{
    unsigned char aes_client_key[REQ_RESPONSE_LENGTH] = { 0 };
Emily Ehlert's avatar
Emily Ehlert committed
    int return_code = false;

    // Calculate AES Key with Yubikey
    CRYPTO_ECHECK(!do_challenge_response(context, tag, aes_client_key, context->slot[AES_SLOT]),
Emily Ehlert's avatar
Emily Ehlert committed
                  "Couldn't derive client-server AES key");
    // Process Data
    CRYPTO_ECHECK(!aes_256_ctr(data, data_len,
                               aes_client_key, RESPONSE_LENGTH,
                               tag, output),
                  "aes_256_ctr() failed");
    return_code = true;
error_exit:
    ovpn_secure_memzero(aes_client_key, sizeof(aes_client_key));
    return return_code;
}

static int
calculate_tag(struct plugin_context *context, const unsigned char *data, uint16_t data_len, unsigned char *tag) {
    unsigned char hmac_client_key[REQ_RESPONSE_LENGTH] = { 0 };
    unsigned char data_buffer[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 };
    int exit_code = false;

    CRYPTO_ECHECK(!do_challenge_response(context, data, hmac_client_key, context->slot[HMAC_SLOT]),
Emily Ehlert's avatar
Emily Ehlert committed
                  "Couldn't derive client-server HMAC key");

    data_buffer[0] = (data_len >> 8) & 0xFF;
    data_buffer[1] = data_len & 0xFF;
    memcpy(data_buffer + 2, data, data_len);

Emily Ehlert's avatar
Emily Ehlert committed
    CRYPTO_ECHECK(!HMAC(EVP_sha256(),
                        hmac_client_key, RESPONSE_LENGTH,
                        data_buffer, data_len + TLS_CRYPT_V2_LEN_LEN,
                        tag, NULL),
                  "HMAC() failed");
    exit_code = true;
error_exit:
    ovpn_secure_memzero(hmac_client_key, sizeof(hmac_client_key));
    return exit_code;
}

static uint16_t bytesToShort(const unsigned char *bytes)
{
    return *(bytes) << 8 | *(bytes + 1);
}

static int
handle_return(struct openvpn_plugin_args_func_return *ret,
              const void *data, int data_len)
{
    struct openvpn_plugin_string_list *rl = calloc(1, sizeof(struct openvpn_plugin_string_list));
    if(!rl)
        return false;

    rl->name = strdup("wrapping result");
    int b64_size = ovpn_base64_encode(data, data_len, &rl->value);
    if(b64_size < 0)
        return false;

    struct openvpn_plugin_string_list **ret_list = ret->return_list;
    *ret_list = rl;

    return true;
}

/**
 *  Unwrap a client key by using keys derived from a HMAC-SHA1 key stored inside a YubiKey
 *
 *  @return int    Returns OPENVPN_PLUGIN_FUNC_ERROR on error and OPENVPN_PLUGIN_FUNC_SUCCESS on success
 *
 */
static int
yubikey_unwrap(struct plugin_context *context, const char **argv,
               struct openvpn_plugin_args_func_return *ret)
{
Emily Ehlert's avatar
Emily Ehlert committed
    unsigned char kc[TLS_CRYPT_V2_MAX_WKC_LEN]  = { 0 };
    unsigned char wkc[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 };
    unsigned char tag[TLS_CRYPT_V2_TAG_LEN]     = { 0 };
    int exit_code = OPENVPN_PLUGIN_FUNC_ERROR;

Emily Ehlert's avatar
Emily Ehlert committed
    // Decode WKc from argv
    const char *wkc_base64 = argv[2];
    plog(PLOG_DEBUG, "Received WKc: %s", wkc_base64);

    int wkc_len = ovpn_base64_decode(wkc_base64, wkc, TLS_CRYPT_V2_MAX_WKC_LEN);
    CRYPTO_ECHECK(wkc_len < 0,
Emily Ehlert's avatar
Emily Ehlert committed
                  "ovpn_base64_decode failed");

    // Length checks
    int kc_len = wkc_len - TLS_CRYPT_V2_TAG_LEN - TLS_CRYPT_V2_LEN_LEN;
    CRYPTO_ECHECK(kc_len < 0,
Emily Ehlert's avatar
Emily Ehlert committed
                  "Invalid Length of WKc");
    uint16_t net_len = bytesToShort(wkc + wkc_len - 2);
    CRYPTO_ECHECK(net_len != wkc_len,
Emily Ehlert's avatar
Emily Ehlert committed
                  "Invalid Declaration of Length for WKc");
Emily Ehlert's avatar
Emily Ehlert committed
    // Decrypt cipher block
    CRYPTO_ECHECK(!calculate_cipher(context,
                                    wkc + TLS_CRYPT_V2_TAG_LEN, kc_len,
                                    wkc, kc),
                  "Couldn't decrypt client key");

    // Calculate tag and compare
    CRYPTO_ECHECK(!calculate_tag(context, kc, kc_len, tag),
Emily Ehlert's avatar
Emily Ehlert committed
                  "Couldn't calculate tag");

    CRYPTO_ECHECK(memcmp(tag, wkc, TLS_CRYPT_V2_TAG_LEN) != 0,
Emily Ehlert's avatar
Emily Ehlert committed
                  "Tags don't match");

    // Prepare return for openvpn
    CRYPTO_ECHECK(!handle_return(ret, kc, kc_len),
Emily Ehlert's avatar
Emily Ehlert committed
                  "Returning results failed");

    exit_code = OPENVPN_PLUGIN_FUNC_SUCCESS;
error_exit:
Emily Ehlert's avatar
Emily Ehlert committed
    ovpn_secure_memzero(kc, sizeof(kc));
    return exit_code;
}

/**
 *  Wrap a client key by using keys derived from a HMAC-SHA1 key stored inside a YubiKey
 *
 *  @return int    Returns OPENVPN_PLUGIN_FUNC_ERROR on error and OPENVPN_PLUGIN_FUNC_SUCCESS on success
 *
 */
static int
yubikey_wrap(struct plugin_context *context, const char **argv,
             struct openvpn_plugin_args_func_return *ret)
{
Emily Ehlert's avatar
Emily Ehlert committed
    unsigned char kc[TLS_CRYPT_V2_MAX_WKC_LEN]  = { 0 };
    unsigned char wkc[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 };
Emily Ehlert's avatar
Emily Ehlert committed
    unsigned char tag[TLS_CRYPT_V2_TAG_LEN]     = { 0 };
    int exit_code = OPENVPN_PLUGIN_FUNC_ERROR;

Emily Ehlert's avatar
Emily Ehlert committed
    // Decode Kc from argv
    const char *kc_base64 = argv[2];
Emily Ehlert's avatar
Emily Ehlert committed
    plog(PLOG_DEBUG, "Received Kc: %s", kc_base64);
Emily Ehlert's avatar
Emily Ehlert committed
    int kc_len = ovpn_base64_decode(kc_base64, kc, TLS_CRYPT_V2_MAX_WKC_LEN);
    CRYPTO_ECHECK(kc_len < 0,
                  "ovpn_base64_decode() failed");
    // Calculate tag
    CRYPTO_ECHECK(!calculate_tag(context, kc, kc_len, wkc),
                "Couldn't calculate tag");

Emily Ehlert's avatar
Emily Ehlert committed
    CRYPTO_ECHECK(!calculate_cipher(context,
                                    kc, kc_len,
                                    wkc, wkc + TLS_CRYPT_V2_TAG_LEN),
Emily Ehlert's avatar
Emily Ehlert committed
                  "Couldn't encrypt client key");

    uint16_t wkc_len = kc_len + TLS_CRYPT_V2_TAG_LEN + TLS_CRYPT_V2_LEN_LEN;
    wkc[wkc_len - 2] = (wkc_len >> 8) & 0xFF;
    wkc[wkc_len - 1] = wkc_len & 0xFF;

    // Prepare return for openvpn
    CRYPTO_ECHECK(!handle_return(ret, wkc, wkc_len),
                "Returning results failed");
    exit_code = OPENVPN_PLUGIN_FUNC_SUCCESS;

error_exit:
    return exit_code;
}

/**
 *  Import first 20 Bytes of generated AES Server Key as HMAC-SHA1 Key into Yubikey
 *
 *  @return int    Returns OPENVPN_PLUGIN_FUNC_ERROR on error and OPENVPN_PLUGIN_FUNC_SUCCESS on success
 *
 */
static int
yubikey_import_server_key(struct plugin_context *context, const char **argv) {
    char aes_keybuf[TLS_CRYPT_V2_SERVER_KEY_LEN];
    char hmac_keybuf[TLS_CRYPT_V2_SERVER_KEY_LEN];
    int ret_code = 0;

    ERROR_CHECK(ovpn_base64_decode(argv[2], aes_keybuf, TLS_CRYPT_V2_SERVER_KEY_LEN) <= 0,
                "ovpn_base64_decode() failed");
    ERROR_CHECK(ovpn_base64_decode(argv[3], hmac_keybuf, TLS_CRYPT_V2_SERVER_KEY_LEN) <= 0,
                "ovpn_base64_decode() failed");

Emily Ehlert's avatar
Emily Ehlert committed
    if(context->slot[0] == context->slot[1])
    {
        ret_code |= import_server_key(context->yk, context->st, aes_keybuf, context->acc_code, context->verb, context->slot[AES_SLOT]);}
    else
    {
        ret_code |= import_server_key(context->yk, context->st, aes_keybuf, context->acc_code, context->verb, context->slot[AES_SLOT]);
        ret_code |= import_server_key(context->yk, context->st, hmac_keybuf, context->acc_code, context->verb, context->slot[HMAC_SLOT]);
    }
    ovpn_secure_memzero(aes_keybuf, sizeof(aes_keybuf));
    ovpn_secure_memzero(hmac_keybuf, sizeof(hmac_keybuf));
    return ret_code;

}

static int
initialize_yubikey_context(struct plugin_context *context)
{
    context->yk = 0;
    context->st = ykds_alloc();

    if(!key_init(&context->yk, context->st))
    {
        printf("%s", yk_usb_strerror());
        plog(PLOG_ERR, "Couldn't initialize Yubikey!");
        return false;
    }

    return true;
}

static void
cleanup_yubikey_context(struct plugin_context *context)
{
    if (context->st)
    {
        ykds_free(context->st);
        context->st = NULL;
    }

    if (context->yk)
    {
        yk_close_key(context->yk);
        context->yk = NULL;
    }

    yk_release();
}

static void
cleanup_yubikey_server(const struct plugin_context *context) {
    close(context->write_pipe);
    close(context->read_pipe);
    waitpid(context->background_pid, NULL, 0);
}

/*
 * Daemonize if "daemon" env var is true.
 * Preserve stderr across daemonization if
 * "daemon_log_redirect" env var is true.
 */
static void
daemonize(const char *envp[])
{
    const char *daemon_string = get_env("daemon", envp);
    if (daemon_string && daemon_string[0] == '1')
    {
        const char *log_redirect = get_env("daemon_log_redirect", envp);
        int fd = -1;
        if (log_redirect && log_redirect[0] == '1')
        {
            fd = dup(2);
        }
        if (daemon(0, 0) < 0)
        {
Emily Ehlert's avatar
Emily Ehlert committed
            plog(PLOG_WARN, "daemonize() failed");
        }
        else if (fd >= 3)
        {
            dup2(fd, 2);
            close(fd);
        }
    }
}

static int
try_resurrect(struct plugin_context *context) {
    cleanup_yubikey_context(context);
    return initialize_yubikey_context(context);
}

static void
yubikey_server(struct plugin_context *context, int read_pipe, int write_pipe) {
    if(!initialize_yubikey_context(context))
    {
        plog(PLOG_ERR, "Background Yubikey Initialization failed");
        return;
    }

    unsigned char init_status = RESPONSE_INIT_SUCCEEDED;
    if(write(write_pipe, &init_status, sizeof (init_status)) < 0)
    {
        plog(PLOG_ERR, "Sending Status to Foreground Process failed");
        goto done;
    }

    unsigned char slot;
    unsigned char challenge_buffer[CHALLENGE_LENGTH];
    unsigned char response_buffer[REQ_RESPONSE_LENGTH];
    ssize_t bytes_processed;

    while(1)
    {
        memset(challenge_buffer, 0, CHALLENGE_LENGTH);
        memset(response_buffer, 0, REQ_RESPONSE_LENGTH);
        bytes_processed = read(read_pipe, &slot, sizeof(unsigned char));
        if(bytes_processed != sizeof(unsigned char))
        {
            plog(PLOG_NOTE, "Reading Slot from Pipe not possible");
            goto done;
        }
        bytes_processed = read(read_pipe, challenge_buffer, sizeof(challenge_buffer));
        if(bytes_processed != sizeof(challenge_buffer))
        {
            plog(PLOG_NOTE, "Reading Challenge from Pipe not possible");
            goto done;
        }

        if(!challenge_response(context->yk, slot, false, CHALLENGE_LENGTH, challenge_buffer, response_buffer, true))
        {
            if(!try_resurrect(context) ||
               !challenge_response(context->yk, slot, false, CHALLENGE_LENGTH, challenge_buffer, response_buffer, true))
            {
                plog(PLOG_ERR, "Yubikey Challenge failed");
                goto done;
            }
        }

        bytes_processed = write(write_pipe, response_buffer, RESPONSE_LENGTH);
        if(bytes_processed != RESPONSE_LENGTH)
        {
            plog(PLOG_ERR, "Writing Response to Buffer failed");
            goto done;
        }
    }

done:
    cleanup_yubikey_context(context);
}

static int
initialize_background(struct plugin_context *context, const char *envp[]) {
    int f2b[2];
    int b2f[2];
    pid_t pid;

    ERROR_CHECK(pipe(f2b), "F2B Pipe Failed");
    ERROR_CHECK(pipe(b2f), "F2B Pipe Failed");

    pid = fork();

    if(pid)
    {
        close(f2b[0]);
        close(b2f[1]);

        context->background_pid = pid;
        context->write_pipe = f2b[1];
        context->read_pipe = b2f[0];

        /* don't let future subprocesses inherit child socket */
        ERROR_CHECK(fcntl(context->write_pipe, F_SETFD, FD_CLOEXEC) < 0, "Failed setting FD attribute");
        ERROR_CHECK(fcntl(context->read_pipe, F_SETFD, FD_CLOEXEC) < 0, "Failed setting FD attribute");

        unsigned char status = 0;
        read(context->read_pipe, &status, sizeof(status));
        ERROR_CHECK(status != RESPONSE_INIT_SUCCEEDED, "Failed initializing background process");

        return OPENVPN_PLUGIN_FUNC_SUCCESS;
    } else {
        // Close all file descriptors except pipe
        closelog();
        for (int i = 3; i <= 100; ++i)
        {
            if (i != f2b[0] && i != b2f[1])
            {
                close(i);
            }
        }

        // Set Signals
        signal(SIGTERM, SIG_DFL);
        signal(SIGINT, SIG_IGN);
        signal(SIGHUP, SIG_IGN);
        signal(SIGUSR1, SIG_IGN);
        signal(SIGUSR2, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);

        // Daemonize if requested by user
        daemonize(envp);
        yubikey_server(context, f2b[0], b2f[1]);

        close(f2b[1]);
        close(b2f[0]);
        exit(0);
    }
}

int
openvpn_plugin_func_v3(const int v3structver,
                       struct openvpn_plugin_args_func_in const *args,
                       struct openvpn_plugin_args_func_return *ret)
{
    if (v3structver < OPENVPN_PLUGIN_STRUCTVER_MIN)
    {
        fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n", MODULE);
        return OPENVPN_PLUGIN_FUNC_ERROR;
    }
    const char **argv = args->argv;
    struct plugin_context *context = (struct plugin_context *) args->handle;
    int exit_code;

    if(args->type == OPENVPN_PLUGIN_UP)
    {
        // Initialize background process as privileged, so we can still communicate with YubiKey
        exit_code = initialize_background(context, args->envp);
        goto end;
    }

    if(!context->yk && !context->background_pid)
    {
        plog(PLOG_NOTE, "Initializing context");
        if(!initialize_yubikey_context(context))
        {
            goto end;
        }
    }

    if(args->type != OPENVPN_PLUGIN_CLIENT_KEY_WRAPPING)
    {
        plog(PLOG_NOTE, "OPENVPN_PLUGIN_?");
        exit_code = OPENVPN_PLUGIN_FUNC_ERROR;
        goto end;
    }

    if(strcmp(argv[1], "unwrap") == 0)
    {
        plog(PLOG_NOTE, "Unwrapping Client Key with YubiKey");
        exit_code = yubikey_unwrap(context, argv, ret);
    }
    else if (strcmp(argv[1], "wrap") == 0)
    {
        plog(PLOG_NOTE, "Wrapping Client Key with YubiKey");
        exit_code = yubikey_wrap(context, argv, ret);
    }
    else if (strcmp(argv[1], "import") == 0)
    {
        plog(PLOG_NOTE, "Importing Server Key to YubiKey");
        exit_code = yubikey_import_server_key(context, argv);
    }
    else
    {
        exit_code = OPENVPN_PLUGIN_FUNC_ERROR;
    }

    if(context->yk)
    {
        plog(PLOG_NOTE, "Cleaning up context");
        cleanup_yubikey_context(context);
    }

end:
    return exit_code;
}

void
openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
{
    struct plugin_context *context = (struct plugin_context *) handle;
    if(context->yk)
    {
        cleanup_yubikey_context(context);
    }
    else
    {
        cleanup_yubikey_server(context);
    }
}