pkcs11: Lookup the public key of a private key by CKA_ID.
[strongswan.git] / src / libstrongswan / plugins / pkcs11 / pkcs11_private_key.c
index b50ae07..fda6ae3 100644 (file)
@@ -1,4 +1,7 @@
 /*
+ * Copyright (C) 2011 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
  * Copyright (C) 2010 Martin Willi
  * Copyright (C) 2010 revosec AG
  *
@@ -19,7 +22,6 @@
 #include "pkcs11_manager.h"
 
 #include <debug.h>
-#include <threading/mutex.h>
 
 typedef struct private_pkcs11_private_key_t private_pkcs11_private_key_t;
 
@@ -39,14 +41,14 @@ struct private_pkcs11_private_key_t {
        pkcs11_library_t *lib;
 
        /**
-        * Token session
+        * Slot the token is in
         */
-       CK_SESSION_HANDLE session;
+       CK_SLOT_ID slot;
 
        /**
-        * Mutex to lock session
+        * Token session
         */
-       mutex_t *mutex;
+       CK_SESSION_HANDLE session;
 
        /**
         * Key object on the token
@@ -54,6 +56,16 @@ struct private_pkcs11_private_key_t {
        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;
@@ -62,24 +74,36 @@ struct private_pkcs11_private_key_t {
         * References to this key
         */
        refcount_t ref;
+
+       /**
+        * Type of this private key
+        */
+       key_type_t type;
 };
 
+/**
+ * Implemented in pkcs11_public_key.c
+ */
+public_key_t *pkcs11_public_key_connect(pkcs11_library_t *p11,
+                                                                       int slot, key_type_t type, chunk_t keyid);
+
+
 METHOD(private_key_t, get_type, key_type_t,
        private_pkcs11_private_key_t *this)
 {
-       return this->pubkey->get_type(this->pubkey);
+       return this->type;
 }
 
-METHOD(private_key_t, get_keysize, size_t,
+METHOD(private_key_t, get_keysize, int,
        private_pkcs11_private_key_t *this)
 {
        return this->pubkey->get_keysize(this->pubkey);
 }
 
 /**
- * Get the Cryptoki mechanism for a signature scheme
+ * See header.
  */
-static CK_MECHANISM_PTR scheme_to_mechanism(signature_scheme_t scheme)
+CK_MECHANISM_PTR pkcs11_signature_scheme_to_mech(signature_scheme_t scheme)
 {
        static struct {
                signature_scheme_t scheme;
@@ -104,33 +128,107 @@ static CK_MECHANISM_PTR scheme_to_mechanism(signature_scheme_t scheme)
        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,
+                                  CK_SESSION_HANDLE session)
+{
+       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(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_SESSION_HANDLE session;
        CK_BYTE_PTR buf;
        CK_ULONG len;
        CK_RV rv;
 
-       mechanism = scheme_to_mechanism(scheme);
+       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);
+       rv = this->lib->f->C_OpenSession(this->slot, CKF_SERIAL_SESSION, NULL, NULL,
+                                                                        &session);
        if (rv != CKR_OK)
        {
-               this->mutex->unlock(this->mutex);
+               DBG1(DBG_CFG, "opening PKCS#11 session failed: %N", ck_rv_names, rv);
+               return FALSE;
+       }
+       rv = this->lib->f->C_SignInit(session, mechanism, this->object);
+       if (this->reauth && !reauth(this, session))
+       {
+               this->lib->f->C_CloseSession(session);
+               return FALSE;
+       }
+       if (rv != CKR_OK)
+       {
+               this->lib->f->C_CloseSession(session);
                DBG1(DBG_LIB, "C_SignInit() failed: %N", ck_rv_names, rv);
                return FALSE;
        }
-       buf = malloc(get_keysize(this));
-       rv = this->lib->f->C_Sign(this->session, data.ptr, data.len, buf, &len);
-       this->mutex->unlock(this->mutex);
+       len = (get_keysize(this) + 7) / 8;
+       buf = malloc(len);
+       rv = this->lib->f->C_Sign(session, data.ptr, data.len, buf, &len);
+       this->lib->f->C_CloseSession(session);
        if (rv != CKR_OK)
        {
                DBG1(DBG_LIB, "C_Sign() failed: %N", ck_rv_names, rv);
@@ -142,9 +240,53 @@ METHOD(private_key_t, sign, bool,
 }
 
 METHOD(private_key_t, decrypt, bool,
-       private_pkcs11_private_key_t *this, chunk_t crypto, chunk_t *plain)
+       private_pkcs11_private_key_t *this, encryption_scheme_t scheme,
+       chunk_t crypt, chunk_t *plain)
 {
-       return FALSE;
+       CK_MECHANISM_PTR mechanism;
+       CK_SESSION_HANDLE session;
+       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;
+       }
+       rv = this->lib->f->C_OpenSession(this->slot, CKF_SERIAL_SESSION, NULL, NULL,
+                                                                        &session);
+       if (rv != CKR_OK)
+       {
+               DBG1(DBG_CFG, "opening PKCS#11 session failed: %N", ck_rv_names, rv);
+               return FALSE;
+       }
+       rv = this->lib->f->C_DecryptInit(session, mechanism, this->object);
+       if (this->reauth && !reauth(this, session))
+       {
+               this->lib->f->C_CloseSession(session);
+               return FALSE;
+       }
+       if (rv != CKR_OK)
+       {
+               this->lib->f->C_CloseSession(session);
+               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(session, crypt.ptr, crypt.len, buf, &len);
+       this->lib->f->C_CloseSession(session);
+       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*,
@@ -183,7 +325,7 @@ METHOD(private_key_t, destroy, void,
                {
                        this->pubkey->destroy(this->pubkey);
                }
-               this->mutex->destroy(this->mutex);
+               this->keyid->destroy(this->keyid);
                this->lib->f->C_CloseSession(this->session);
                free(this);
        }
@@ -199,7 +341,7 @@ static pkcs11_library_t* find_lib(char *module)
        pkcs11_library_t *p11, *found = NULL;
        CK_SLOT_ID slot;
 
-       manager = pkcs11_manager_get();
+       manager = lib->get(lib, "pkcs11-manager");
        if (!manager)
        {
                return NULL;
@@ -227,7 +369,7 @@ static pkcs11_library_t* find_lib_by_keyid(chunk_t keyid, int *slot)
        pkcs11_library_t *p11, *found = NULL;
        CK_SLOT_ID current;
 
-       manager = pkcs11_manager_get();
+       manager = lib->get(lib, "pkcs11-manager");
        if (!manager)
        {
                return NULL;
@@ -289,37 +431,34 @@ static bool find_key(private_pkcs11_private_key_t *this, chunk_t keyid)
        };
        CK_OBJECT_HANDLE object;
        CK_KEY_TYPE type;
+       CK_BBOOL reauth = FALSE;
        CK_ATTRIBUTE attr[] = {
                {CKA_KEY_TYPE, &type, sizeof(type)},
-               {CKA_MODULUS, NULL, 0},
-               {CKA_PUBLIC_EXPONENT, NULL, 0},
+               {CKA_ALWAYS_AUTHENTICATE, &reauth, sizeof(reauth)},
        };
        enumerator_t *enumerator;
-       chunk_t modulus, pubexp;
+       int count = countof(attr);
+       bool found = FALSE;
 
+       /* do not use CKA_ALWAYS_AUTHENTICATE if not supported */
+       if (!(this->lib->get_features(this->lib) & PKCS11_ALWAYS_AUTH_KEYS))
+       {
+               count--;
+       }
        enumerator = this->lib->create_object_enumerator(this->lib,
-                                               this->session, tmpl, countof(tmpl), attr, countof(attr));
+                                                       this->session, tmpl, countof(tmpl), attr, count);
        if (enumerator->enumerate(enumerator, &object))
        {
+               this->type = KEY_RSA;
                switch (type)
                {
+                       case CKK_ECDSA:
+                               this->type = KEY_ECDSA;
+                               /* fall-through */
                        case CKK_RSA:
-                               if (attr[0].ulValueLen == -1 || attr[1].ulValueLen == -1)
-                               {
-                                       DBG1(DBG_CFG, "reading modulus/exponent from PKCS#1 failed");
-                                       break;
-                               }
-                               modulus = chunk_create(attr[1].pValue, attr[1].ulValueLen);
-                               pubexp = chunk_create(attr[2].pValue, attr[2].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;
+                               found = TRUE;
                                break;
                        default:
                                DBG1(DBG_CFG, "PKCS#11 key type %d not supported", type);
@@ -327,37 +466,56 @@ static bool find_key(private_pkcs11_private_key_t *this, chunk_t keyid)
                }
        }
        enumerator->destroy(enumerator);
-       return this->pubkey != NULL;
+       return found;
 }
 
 /**
  * Find a PIN and try to log in
  */
-static bool login(private_pkcs11_private_key_t *this, chunk_t keyid, int slot)
+static bool login(private_pkcs11_private_key_t *this, int slot)
 {
-       identification_t *id;
+       enumerator_t *enumerator;
        shared_key_t *shared;
        chunk_t pin;
        CK_RV rv;
+       CK_SESSION_INFO info;
+       bool found = FALSE, success = FALSE;
 
-       id = identification_create_from_encoding(ID_KEY_ID, keyid);
-       shared = lib->credmgr->get_shared(lib->credmgr, SHARED_PIN, id, NULL);
-       id->destroy(id);
-       if (!shared)
+       rv = this->lib->f->C_GetSessionInfo(this->session, &info);
+       if (rv != CKR_OK)
        {
-               DBG1(DBG_CFG, "no PIN found for PKCS#11 key %#B", keyid);
+               DBG1(DBG_CFG, "C_GetSessionInfo failed: %N", ck_rv_names, rv);
                return FALSE;
        }
-       pin = shared->get_key(shared);
-       rv = this->lib->f->C_Login(this->session, CKU_USER, pin.ptr, pin.len);
-       shared->destroy(shared);
-       if (rv != CKR_OK)
+       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 TRUE;
+       return success;
 }
 
 /**
@@ -397,19 +555,21 @@ pkcs11_private_key_t *pkcs11_private_key_connect(key_type_t type, va_list args)
        }
 
        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,
+               .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,
        );
@@ -445,9 +605,10 @@ pkcs11_private_key_t *pkcs11_private_key_connect(key_type_t type, va_list args)
                return NULL;
        }
 
-       this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
+       this->slot = slot;
+       this->keyid = identification_create_from_encoding(ID_KEY_ID, keyid);
 
-       if (!login(this, keyid, slot))
+       if (!login(this, slot))
        {
                destroy(this);
                return NULL;
@@ -459,5 +620,13 @@ pkcs11_private_key_t *pkcs11_private_key_connect(key_type_t type, va_list args)
                return NULL;
        }
 
+       this->pubkey = pkcs11_public_key_connect(this->lib, slot, this->type,
+                                                                                        keyid);
+       if (!this->pubkey)
+       {
+               destroy(this);
+               return NULL;
+       }
+
        return &this->public;
 }