summaryrefslogtreecommitdiff
path: root/src/libstrongswan/plugins/pkcs11/pkcs11_private_key.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstrongswan/plugins/pkcs11/pkcs11_private_key.c')
-rw-r--r--src/libstrongswan/plugins/pkcs11/pkcs11_private_key.c600
1 files changed, 600 insertions, 0 deletions
diff --git a/src/libstrongswan/plugins/pkcs11/pkcs11_private_key.c b/src/libstrongswan/plugins/pkcs11/pkcs11_private_key.c
new file mode 100644
index 000000000..cabca3f54
--- /dev/null
+++ b/src/libstrongswan/plugins/pkcs11/pkcs11_private_key.c
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#include "pkcs11_private_key.h"
+
+#include "pkcs11_library.h"
+#include "pkcs11_manager.h"
+
+#include <debug.h>
+#include <threading/mutex.h>
+
+typedef struct private_pkcs11_private_key_t private_pkcs11_private_key_t;
+
+/**
+ * Private data of an pkcs11_private_key_t object.
+ */
+struct private_pkcs11_private_key_t {
+
+ /**
+ * Public pkcs11_private_key_t interface.
+ */
+ pkcs11_private_key_t public;
+
+ /**
+ * PKCS#11 module
+ */
+ pkcs11_library_t *lib;
+
+ /**
+ * Token session
+ */
+ CK_SESSION_HANDLE session;
+
+ /**
+ * Mutex to lock session
+ */
+ mutex_t *mutex;
+
+ /**
+ * Key object on the token
+ */
+ CK_OBJECT_HANDLE object;
+
+ /**
+ * Key requires reauthentication for each signature/decryption
+ */
+ CK_BBOOL reauth;
+
+ /**
+ * Keyid of the key we use
+ */
+ identification_t *keyid;
+
+ /**
+ * Associated public key
+ */
+ public_key_t *pubkey;
+
+ /**
+ * References to this key
+ */
+ refcount_t ref;
+};
+
+METHOD(private_key_t, get_type, key_type_t,
+ private_pkcs11_private_key_t *this)
+{
+ return this->pubkey->get_type(this->pubkey);
+}
+
+METHOD(private_key_t, get_keysize, int,
+ private_pkcs11_private_key_t *this)
+{
+ return this->pubkey->get_keysize(this->pubkey);
+}
+
+/**
+ * See header.
+ */
+CK_MECHANISM_PTR pkcs11_signature_scheme_to_mech(signature_scheme_t scheme)
+{
+ static struct {
+ signature_scheme_t scheme;
+ CK_MECHANISM mechanism;
+ } mappings[] = {
+ {SIGN_RSA_EMSA_PKCS1_NULL, {CKM_RSA_PKCS, NULL, 0}},
+ {SIGN_RSA_EMSA_PKCS1_SHA1, {CKM_SHA1_RSA_PKCS, NULL, 0}},
+ {SIGN_RSA_EMSA_PKCS1_SHA256, {CKM_SHA256_RSA_PKCS, NULL, 0}},
+ {SIGN_RSA_EMSA_PKCS1_SHA384, {CKM_SHA384_RSA_PKCS, NULL, 0}},
+ {SIGN_RSA_EMSA_PKCS1_SHA512, {CKM_SHA512_RSA_PKCS, NULL, 0}},
+ {SIGN_RSA_EMSA_PKCS1_MD5, {CKM_MD5_RSA_PKCS, NULL, 0}},
+ };
+ int i;
+
+ for (i = 0; i < countof(mappings); i++)
+ {
+ if (mappings[i].scheme == scheme)
+ {
+ return &mappings[i].mechanism;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * See header.
+ */
+CK_MECHANISM_PTR pkcs11_encryption_scheme_to_mech(encryption_scheme_t scheme)
+{
+ static struct {
+ encryption_scheme_t scheme;
+ CK_MECHANISM mechanism;
+ } mappings[] = {
+ {ENCRYPT_RSA_PKCS1, {CKM_RSA_PKCS, NULL, 0}},
+ {ENCRYPT_RSA_OAEP_SHA1, {CKM_RSA_PKCS_OAEP, NULL, 0}},
+ };
+ int i;
+
+ for (i = 0; i < countof(mappings); i++)
+ {
+ if (mappings[i].scheme == scheme)
+ {
+ return &mappings[i].mechanism;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Reauthenticate to do a signature
+ */
+static bool reauth(private_pkcs11_private_key_t *this)
+{
+ enumerator_t *enumerator;
+ shared_key_t *shared;
+ chunk_t pin;
+ CK_RV rv;
+ bool found = FALSE, success = FALSE;
+
+ enumerator = lib->credmgr->create_shared_enumerator(lib->credmgr,
+ SHARED_PIN, this->keyid, NULL);
+ while (enumerator->enumerate(enumerator, &shared, NULL, NULL))
+ {
+ found = TRUE;
+ pin = shared->get_key(shared);
+ rv = this->lib->f->C_Login(this->session, CKU_CONTEXT_SPECIFIC,
+ pin.ptr, pin.len);
+ if (rv == CKR_OK)
+ {
+ success = TRUE;
+ break;
+ }
+ DBG1(DBG_CFG, "reauthentication login failed: %N", ck_rv_names, rv);
+ }
+ enumerator->destroy(enumerator);
+
+ if (!found)
+ {
+ DBG1(DBG_CFG, "private key requires reauthentication, but no PIN found");
+ return FALSE;
+ }
+ return success;
+}
+
+METHOD(private_key_t, sign, bool,
+ private_pkcs11_private_key_t *this, signature_scheme_t scheme,
+ chunk_t data, chunk_t *signature)
+{
+ CK_MECHANISM_PTR mechanism;
+ CK_BYTE_PTR buf;
+ CK_ULONG len;
+ CK_RV rv;
+
+ mechanism = pkcs11_signature_scheme_to_mech(scheme);
+ if (!mechanism)
+ {
+ DBG1(DBG_LIB, "signature scheme %N not supported",
+ signature_scheme_names, scheme);
+ return FALSE;
+ }
+ this->mutex->lock(this->mutex);
+ rv = this->lib->f->C_SignInit(this->session, mechanism, this->object);
+ if (this->reauth && !reauth(this))
+ {
+ return FALSE;
+ }
+ if (rv != CKR_OK)
+ {
+ this->mutex->unlock(this->mutex);
+ DBG1(DBG_LIB, "C_SignInit() failed: %N", ck_rv_names, rv);
+ return FALSE;
+ }
+ len = (get_keysize(this) + 7) / 8;
+ buf = malloc(len);
+ rv = this->lib->f->C_Sign(this->session, data.ptr, data.len, buf, &len);
+ this->mutex->unlock(this->mutex);
+ if (rv != CKR_OK)
+ {
+ DBG1(DBG_LIB, "C_Sign() failed: %N", ck_rv_names, rv);
+ free(buf);
+ return FALSE;
+ }
+ *signature = chunk_create(buf, len);
+ return TRUE;
+}
+
+METHOD(private_key_t, decrypt, bool,
+ private_pkcs11_private_key_t *this, encryption_scheme_t scheme,
+ chunk_t crypt, chunk_t *plain)
+{
+ CK_MECHANISM_PTR mechanism;
+ CK_BYTE_PTR buf;
+ CK_ULONG len;
+ CK_RV rv;
+
+ mechanism = pkcs11_encryption_scheme_to_mech(scheme);
+ if (!mechanism)
+ {
+ DBG1(DBG_LIB, "encryption scheme %N not supported",
+ encryption_scheme_names, scheme);
+ return FALSE;
+ }
+ this->mutex->lock(this->mutex);
+ rv = this->lib->f->C_DecryptInit(this->session, mechanism, this->object);
+ if (this->reauth && !reauth(this))
+ {
+ return FALSE;
+ }
+ if (rv != CKR_OK)
+ {
+ this->mutex->unlock(this->mutex);
+ DBG1(DBG_LIB, "C_DecryptInit() failed: %N", ck_rv_names, rv);
+ return FALSE;
+ }
+ len = (get_keysize(this) + 7) / 8;
+ buf = malloc(len);
+ rv = this->lib->f->C_Decrypt(this->session, crypt.ptr, crypt.len, buf, &len);
+ this->mutex->unlock(this->mutex);
+ if (rv != CKR_OK)
+ {
+ DBG1(DBG_LIB, "C_Decrypt() failed: %N", ck_rv_names, rv);
+ free(buf);
+ return FALSE;
+ }
+ *plain = chunk_create(buf, len);
+ return TRUE;
+}
+
+METHOD(private_key_t, get_public_key, public_key_t*,
+ private_pkcs11_private_key_t *this)
+{
+ return this->pubkey->get_ref(this->pubkey);
+}
+
+METHOD(private_key_t, get_fingerprint, bool,
+ private_pkcs11_private_key_t *this, cred_encoding_type_t type,
+ chunk_t *fingerprint)
+{
+ return this->pubkey->get_fingerprint(this->pubkey, type, fingerprint);
+}
+
+METHOD(private_key_t, get_encoding, bool,
+ private_pkcs11_private_key_t *this, cred_encoding_type_t type,
+ chunk_t *encoding)
+{
+ return FALSE;
+}
+
+METHOD(private_key_t, get_ref, private_key_t*,
+ private_pkcs11_private_key_t *this)
+{
+ ref_get(&this->ref);
+ return &this->public.key;
+}
+
+METHOD(private_key_t, destroy, void,
+ private_pkcs11_private_key_t *this)
+{
+ if (ref_put(&this->ref))
+ {
+ if (this->pubkey)
+ {
+ this->pubkey->destroy(this->pubkey);
+ }
+ this->mutex->destroy(this->mutex);
+ this->keyid->destroy(this->keyid);
+ this->lib->f->C_CloseSession(this->session);
+ free(this);
+ }
+}
+
+/**
+ * Find the PKCS#11 library by its friendly name
+ */
+static pkcs11_library_t* find_lib(char *module)
+{
+ pkcs11_manager_t *manager;
+ enumerator_t *enumerator;
+ pkcs11_library_t *p11, *found = NULL;
+ CK_SLOT_ID slot;
+
+ manager = pkcs11_manager_get();
+ if (!manager)
+ {
+ return NULL;
+ }
+ enumerator = manager->create_token_enumerator(manager);
+ while (enumerator->enumerate(enumerator, &p11, &slot))
+ {
+ if (streq(module, p11->get_name(p11)))
+ {
+ found = p11;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ return found;
+}
+
+/**
+ * Find the PKCS#11 lib having a keyid, and optionally a slot
+ */
+static pkcs11_library_t* find_lib_by_keyid(chunk_t keyid, int *slot)
+{
+ pkcs11_manager_t *manager;
+ enumerator_t *enumerator;
+ pkcs11_library_t *p11, *found = NULL;
+ CK_SLOT_ID current;
+
+ manager = pkcs11_manager_get();
+ if (!manager)
+ {
+ return NULL;
+ }
+ enumerator = manager->create_token_enumerator(manager);
+ while (enumerator->enumerate(enumerator, &p11, &current))
+ {
+ if (*slot == -1 || *slot == current)
+ {
+ /* we look for a public key, it is usually readable without login */
+ CK_OBJECT_CLASS class = CKO_PUBLIC_KEY;
+ CK_ATTRIBUTE tmpl[] = {
+ {CKA_CLASS, &class, sizeof(class)},
+ {CKA_ID, keyid.ptr, keyid.len},
+ };
+ CK_OBJECT_HANDLE object;
+ CK_SESSION_HANDLE session;
+ CK_RV rv;
+ enumerator_t *keys;
+
+ rv = p11->f->C_OpenSession(current, CKF_SERIAL_SESSION, NULL, NULL,
+ &session);
+ if (rv != CKR_OK)
+ {
+ DBG1(DBG_CFG, "opening PKCS#11 session failed: %N",
+ ck_rv_names, rv);
+ continue;
+ }
+ keys = p11->create_object_enumerator(p11, session,
+ tmpl, countof(tmpl), NULL, 0);
+ if (keys->enumerate(keys, &object))
+ {
+ DBG1(DBG_CFG, "found key on PKCS#11 token '%s':%d",
+ p11->get_name(p11), current);
+ found = p11;
+ *slot = current;
+ }
+ keys->destroy(keys);
+ p11->f->C_CloseSession(session);
+ if (found)
+ {
+ break;
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+ return found;
+}
+
+/**
+ * Find the key on the token
+ */
+static bool find_key(private_pkcs11_private_key_t *this, chunk_t keyid)
+{
+ CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;
+ CK_ATTRIBUTE tmpl[] = {
+ {CKA_CLASS, &class, sizeof(class)},
+ {CKA_ID, keyid.ptr, keyid.len},
+ };
+ CK_OBJECT_HANDLE object;
+ CK_KEY_TYPE type;
+ CK_BBOOL reauth;
+ CK_ATTRIBUTE attr[] = {
+ {CKA_KEY_TYPE, &type, sizeof(type)},
+ {CKA_ALWAYS_AUTHENTICATE, &reauth, sizeof(reauth)},
+ {CKA_MODULUS, NULL, 0},
+ {CKA_PUBLIC_EXPONENT, NULL, 0},
+ };
+ enumerator_t *enumerator;
+ chunk_t modulus, pubexp;
+
+ enumerator = this->lib->create_object_enumerator(this->lib,
+ this->session, tmpl, countof(tmpl), attr, countof(attr));
+ if (enumerator->enumerate(enumerator, &object))
+ {
+ switch (type)
+ {
+ case CKK_RSA:
+ if (attr[2].ulValueLen == -1 || attr[3].ulValueLen == -1)
+ {
+ DBG1(DBG_CFG, "reading modulus/exponent from PKCS#1 failed");
+ break;
+ }
+ modulus = chunk_create(attr[2].pValue, attr[2].ulValueLen);
+ pubexp = chunk_create(attr[3].pValue, attr[3].ulValueLen);
+ this->pubkey = lib->creds->create(lib->creds, CRED_PUBLIC_KEY,
+ KEY_RSA, BUILD_RSA_MODULUS, modulus,
+ BUILD_RSA_PUB_EXP, pubexp, BUILD_END);
+ if (!this->pubkey)
+ {
+ DBG1(DBG_CFG, "extracting public key from PKCS#11 RSA "
+ "private key failed");
+ }
+ this->reauth = reauth;
+ this->object = object;
+ break;
+ default:
+ DBG1(DBG_CFG, "PKCS#11 key type %d not supported", type);
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ return this->pubkey != NULL;
+}
+
+/**
+ * Find a PIN and try to log in
+ */
+static bool login(private_pkcs11_private_key_t *this, int slot)
+{
+ enumerator_t *enumerator;
+ shared_key_t *shared;
+ chunk_t pin;
+ CK_RV rv;
+ CK_SESSION_INFO info;
+ bool found = FALSE, success = FALSE;
+
+ rv = this->lib->f->C_GetSessionInfo(this->session, &info);
+ if (rv != CKR_OK)
+ {
+ DBG1(DBG_CFG, "C_GetSessionInfo failed: %N", ck_rv_names, rv);
+ return FALSE;
+ }
+ if (info.state != CKS_RO_PUBLIC_SESSION &&
+ info.state != CKS_RW_PUBLIC_SESSION)
+ { /* already logged in with another session, skip */
+ return TRUE;
+ }
+
+ enumerator = lib->credmgr->create_shared_enumerator(lib->credmgr,
+ SHARED_PIN, this->keyid, NULL);
+ while (enumerator->enumerate(enumerator, &shared, NULL, NULL))
+ {
+ found = TRUE;
+ pin = shared->get_key(shared);
+ rv = this->lib->f->C_Login(this->session, CKU_USER, pin.ptr, pin.len);
+ if (rv == CKR_OK)
+ {
+ success = TRUE;
+ break;
+ }
+ DBG1(DBG_CFG, "login to '%s':%d failed: %N",
+ this->lib->get_name(this->lib), slot, ck_rv_names, rv);
+ }
+ enumerator->destroy(enumerator);
+
+ if (!found)
+ {
+ DBG1(DBG_CFG, "no PIN found for PKCS#11 key %Y", this->keyid);
+ return FALSE;
+ }
+ return success;
+}
+
+/**
+ * See header.
+ */
+pkcs11_private_key_t *pkcs11_private_key_connect(key_type_t type, va_list args)
+{
+ private_pkcs11_private_key_t *this;
+ char *module = NULL;
+ chunk_t keyid = chunk_empty;
+ int slot = -1;
+ CK_RV rv;
+
+ while (TRUE)
+ {
+ switch (va_arg(args, builder_part_t))
+ {
+ case BUILD_PKCS11_KEYID:
+ keyid = va_arg(args, chunk_t);
+ continue;
+ case BUILD_PKCS11_SLOT:
+ slot = va_arg(args, int);
+ continue;
+ case BUILD_PKCS11_MODULE:
+ module = va_arg(args, char*);
+ continue;
+ case BUILD_END:
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ }
+ if (!keyid.len)
+ {
+ return NULL;
+ }
+
+ INIT(this,
+ .public = {
+ .key = {
+ .get_type = _get_type,
+ .sign = _sign,
+ .decrypt = _decrypt,
+ .get_keysize = _get_keysize,
+ .get_public_key = _get_public_key,
+ .equals = private_key_equals,
+ .belongs_to = private_key_belongs_to,
+ .get_fingerprint = _get_fingerprint,
+ .has_fingerprint = private_key_has_fingerprint,
+ .get_encoding = _get_encoding,
+ .get_ref = _get_ref,
+ .destroy = _destroy,
+ },
+ },
+ .ref = 1,
+ );
+
+ if (module && slot != -1)
+ {
+ this->lib = find_lib(module);
+ if (!this->lib)
+ {
+ DBG1(DBG_CFG, "PKCS#11 module '%s' not found", module);
+ free(this);
+ return NULL;
+ }
+ }
+ else
+ {
+ this->lib = find_lib_by_keyid(keyid, &slot);
+ if (!this->lib)
+ {
+ DBG1(DBG_CFG, "no PKCS#11 module found having a keyid %#B", &keyid);
+ free(this);
+ return NULL;
+ }
+ }
+
+ rv = this->lib->f->C_OpenSession(slot, CKF_SERIAL_SESSION,
+ NULL, NULL, &this->session);
+ if (rv != CKR_OK)
+ {
+ DBG1(DBG_CFG, "opening private key session on '%s':%d failed: %N",
+ module, slot, ck_rv_names, rv);
+ free(this);
+ return NULL;
+ }
+
+ this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
+ this->keyid = identification_create_from_encoding(ID_KEY_ID, keyid);
+
+ if (!login(this, slot))
+ {
+ destroy(this);
+ return NULL;
+ }
+
+ if (!find_key(this, keyid))
+ {
+ destroy(this);
+ return NULL;
+ }
+
+ return &this->public;
+}