Skip to content
Snippets Groups Projects
openvpn_smartcard_key_wrapping.c 11.2 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.
 */

#include <string.h>
#include <stdlib.h>
#include "openvpn-plugin.h"

#include "smart_card_handler.c"

#define UNUSED(x) (void)(x)

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
 *
 * Strictly speaking, this sample code only requires plugin_log, a feature
 * of structver version 1.  However, '1' lines up with ancient versions
 * of openvpn that are past end-of-support.  As such, we are requiring
 * structver '5' here to indicate a desire for modern openvpn, rather
 * than a need for any particular feature found in structver beyond '1'.
 */
#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 EXTENDED_APDU_SIZE_WITHOUT_DATA 9
#define APDU_LEN_WITHOUT_DATA 5
#define AES_KEY 0x01
#define HMAC_KEY 0x02
#define DO_WRAP 0x01
#define DO_UNWRAP 0x02

#define CLA 0xA0
#define CLA_OFF 0x00
#define INS_OFF 0x01
#define P1_OFF  0x02
#define P2_OFF  0x03
#define LC_OFF  0x04

/* Error handling */
#define ERROR_CHECK(cond, text) do { if (cond) { plog(PLOG_ERR, text); return EXIT_FAILURE; } } while(0)
#define RESPONSE_CHECK(response, text) ERROR_CHECK(response.buffer[response.length - 2] != 0x90 || response.buffer[response.length - 1] != 0x00, text)

/* 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;

/*
 * Our context, where we keep our state.
 */

struct plugin_context {
    struct CardConnectionContext *card_ctx;
};

/* 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);
}


/* Require a minimum OpenVPN Plugin API */
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;
}

/* use v3 functions, so we can use openvpn's logging and base64 etc. */
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)
    {
        fprintf(stderr, "%s: this plugin is incompatible with the running version of OpenVPN\n", MODULE);
        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;

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

    /*
     * Connect to smartcard
     */
    context->card_ctx = malloc(sizeof(SCARDCONTEXT));
    LONG rv = connectToCard(context->card_ctx);
    if (rv != EXIT_SUCCESS)
    {
        goto error;
    }

    rv = selectApp(context->card_ctx);
    if (rv != EXIT_SUCCESS)
    {
        goto error;
    }

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

    return OPENVPN_PLUGIN_FUNC_SUCCESS;

error:
    plog(PLOG_NOTE, "initialization failed");
    if (context)
    {
        free(context);
    }
    return OPENVPN_PLUGIN_FUNC_ERROR;
}

static long
trySendAPDU(struct CardConnectionContext *card_ctx, struct Request *sendBuffer, struct Request *receiveBuffer)
{
    LONG rv = sendAPDU(card_ctx, sendBuffer, receiveBuffer);
Emily Ehlert's avatar
Emily Ehlert committed
    if(rv != EXIT_SUCCESS) {
        plog(PLOG_WARN, "Sending APDU failed. Trying to reconnect to card.");
Emily Ehlert's avatar
Emily Ehlert committed
        unselectApp(card_ctx);
        disconnectFromCard(card_ctx);
        rv = connectToCard(card_ctx);
        ERROR_CHECK(rv != EXIT_SUCCESS, "Can't connect to card");
        rv = selectApp(card_ctx);
        ERROR_CHECK(rv != EXIT_SUCCESS, "Can't select applet");
        rv = sendAPDU(card_ctx, sendBuffer, receiveBuffer);
    }
    return rv;
}

static int
create_wrap_apdu(const char *b64_data, BYTE unwrap_apdu[], int ins_type) {
    const BYTE INS      = (ins_type == DO_UNWRAP) ? 0x02 : 0x03;
    const uint DATA_OFF = 0x07;

    unwrap_apdu[CLA_OFF]    = CLA;
    unwrap_apdu[INS_OFF]    = INS;
    unwrap_apdu[P1_OFF]     = 0x00;
    unwrap_apdu[P2_OFF]     = 0x00;

    const uint16_t len = ovpn_base64_decode(b64_data, unwrap_apdu + DATA_OFF, (int) strlen(b64_data));
    if(len < 0) return -1;
    const uint16_t return_len = (ins_type == DO_UNWRAP) ? (len - TLS_CRYPT_V2_TAG_LEN - TLS_CRYPT_V2_LEN_LEN) : (len + TLS_CRYPT_V2_TAG_LEN + TLS_CRYPT_V2_LEN_LEN);
    const uint LE_OFF   = DATA_OFF + len;

    unwrap_apdu[LC_OFF]     = 0x00;
    unwrap_apdu[LC_OFF + 1] = len >> 8;
    unwrap_apdu[LC_OFF + 2] = len & 0xFF;
    unwrap_apdu[LE_OFF]     = return_len >> 8;
    unwrap_apdu[LE_OFF + 1] = return_len & 0xFF;

    return len;
}

static int
smartcard_process_key(struct plugin_context *context, const char *argv[], struct openvpn_plugin_args_func_return *ret,
                      const char op_type)
{
    LONG rv;

    BYTE apdu[TLS_CRYPT_V2_MAX_WKC_LEN + EXTENDED_APDU_SIZE_WITHOUT_DATA];
    uint16_t data_len = create_wrap_apdu(argv[2], apdu, op_type);
    ERROR_CHECK(data_len < 0, "Creating APDU failed");
    uint16_t apdu_len = data_len + EXTENDED_APDU_SIZE_WITHOUT_DATA;
    struct Request wrapRequest = {apdu, apdu_len};
    const uint16_t return_len = (op_type == DO_UNWRAP) ? (data_len - TLS_CRYPT_V2_TAG_LEN - TLS_CRYPT_V2_LEN_LEN) : (data_len + TLS_CRYPT_V2_TAG_LEN + TLS_CRYPT_V2_LEN_LEN);
    BYTE resultBuffer[return_len + 2];
    struct Request result = {resultBuffer, sizeof (resultBuffer)};

    rv = trySendAPDU(context->card_ctx, &wrapRequest, &result);
    ERROR_CHECK(rv != EXIT_SUCCESS, "Transmit failed");

    RESPONSE_CHECK(result, "Wrapping operation on smartcard failed");

    // Prepare return for openvpn
    struct openvpn_plugin_string_list *rl = calloc(1, sizeof(struct openvpn_plugin_string_list));
    ERROR_CHECK(!rl, "malloc(return_list) failed");

    rl->name = strdup("wrapping result");
    int b64_size = ovpn_base64_encode(result.buffer, (int) result.length - 2, &rl->value);
    ERROR_CHECK(b64_size < 0, "ovpn_base64_encode failed");

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

    return EXIT_SUCCESS;
}

static int
create_import_apdu(const char *b64_data, BYTE *apdu, uint8_t key_type) {
    // smartcard constants
    const BYTE INS      = 0x01;
    const BYTE P1       = key_type;

    // defining offsets
    const uint DATA_OFF = 0x05;

    apdu[CLA_OFF]    = CLA;
    apdu[INS_OFF]    = INS;
    apdu[P1_OFF]     = P1;
    apdu[P2_OFF]     = 0x00;
Emily Ehlert's avatar
Emily Ehlert committed
    apdu[LC_OFF]     = TLS_CRYPT_V2_KEY_LEN;
    // OpenVPN send 64 Byte key
    BYTE keyBuffer[2 * TLS_CRYPT_V2_KEY_LEN];
    int len = ovpn_base64_decode(b64_data, keyBuffer, sizeof(keyBuffer));
    memcpy(apdu + DATA_OFF, keyBuffer, TLS_CRYPT_V2_KEY_LEN);

    return len;
}

static int
smartcard_import_key(struct plugin_context *context, const char **argv) {
    LONG rv;
    BYTE import_apdu[APDU_LEN_WITHOUT_DATA + TLS_CRYPT_V2_KEY_LEN];
    uint16_t key_len;
    struct Request importKeyRequest  = {import_apdu, sizeof(import_apdu)};
    BYTE responseBuffer[2];
    struct Request responseRequest = {responseBuffer, sizeof(responseBuffer)};

    // AES Key
    key_len = create_import_apdu(argv[2], import_apdu, AES_KEY);
    ERROR_CHECK(key_len < 0, "Creating AES Import Key APDU failed");
    rv = sendAPDU(context->card_ctx, &importKeyRequest, &responseRequest);
    ERROR_CHECK(rv != EXIT_SUCCESS, "Transmit failed");
Emily Ehlert's avatar
Emily Ehlert committed
    RESPONSE_CHECK(responseRequest, "AES KEY import failed");

    // HMAC Key
    key_len = create_import_apdu(argv[3], import_apdu, HMAC_KEY);
    ERROR_CHECK(key_len < 0, "Creating AES Import Key APDU failed");
    rv = sendAPDU(context->card_ctx, &importKeyRequest, &responseRequest);
    ERROR_CHECK(rv != EXIT_SUCCESS, "Transmit failed");
Emily Ehlert's avatar
Emily Ehlert committed
    RESPONSE_CHECK(responseRequest, "HMAC key import failed");

    return EXIT_SUCCESS;
}

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;
    switch (args->type)
    {
        case OPENVPN_PLUGIN_CLIENT_KEY_WRAPPING:
            if(strcmp(argv[1], "unwrap") == 0) {
                return smartcard_process_key(context, argv, ret, DO_UNWRAP);
            } else if(strcmp(argv[1], "wrap") == 0) {
                return smartcard_process_key(context, argv, ret, DO_WRAP);
            } else if(strcmp(argv[1], "import") == 0) {
                return smartcard_import_key(context, argv);
            } else {
                return OPENVPN_PLUGIN_FUNC_ERROR;
            }
        default:
            plog(PLOG_NOTE, "OPENVPN_PLUGIN_?");
            return OPENVPN_PLUGIN_FUNC_ERROR;
    }
}

OPENVPN_EXPORT void
openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
{
    struct plugin_context *context = (struct plugin_context *) handle;
    LONG rv = unselectApp(context->card_ctx);
    if(rv != 0) {
        plog(PLOG_WARN, "Couldn't unselect app on smartcard");
    }
    rv = disconnectFromCard(context->card_ctx);
        plog(PLOG_ERR, "Couldn't disconnect from smartcard");
        goto end;
    }
    releaseContext(context->card_ctx);
end:
    free(context->card_ctx);
    free(context);
}