#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); if(rv != EXIT_SUCCESS) { plog(PLOG_WARN, "Sending APDU failed. Trying to reconnect to card."); 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; 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"); 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"); 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); if(rv != 0) { plog(PLOG_ERR, "Couldn't disconnect from smartcard"); goto end; } releaseContext(context->card_ctx); end: free(context->card_ctx); free(context); }