aboutsummaryrefslogtreecommitdiffstats
path: root/tkd/pkcs11.c
diff options
context:
space:
mode:
Diffstat (limited to 'tkd/pkcs11.c')
-rw-r--r--tkd/pkcs11.c1407
1 files changed, 1407 insertions, 0 deletions
diff --git a/tkd/pkcs11.c b/tkd/pkcs11.c
new file mode 100644
index 000000000..33c19ecdd
--- /dev/null
+++ b/tkd/pkcs11.c
@@ -0,0 +1,1407 @@
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "tkdaemon.h"
+
+#include <gcrypt.h>
+#include "../common/util.h"
+#include "pkcs11.h"
+
+/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN. That
+ * length needs to small compared to the maximum Assuan line length. */
+#define MAXLEN_PIN 100
+
+/* Maximum allowed total data size for VALUE. */
+#define MAXLEN_VALUE 4096
+
+#define ck_function_list _CK_FUNCTION_LIST
+#define ck_token_info _CK_TOKEN_INFO
+#define ck_attribute _CK_ATTRIBUTE
+
+#define ck_mechanism _CK_MECHANISM
+#define parameter pParameter
+#define parameter_len ulParameterLen
+
+#define ck_slot_id_t CK_SLOT_ID
+#define ck_session_handle_t CK_SESSION_HANDLE
+#define ck_notification_t CK_NOTIFICATION
+#define ck_flags_t CK_FLAGS
+#define ck_object_handle_t CK_OBJECT_HANDLE
+#define ck_mechanism_type_t CK_MECHANISM_TYPE
+
+/*
+ * d_list -> dev
+ * session -> key_list -> key
+ *
+ */
+
+/*
+ * Major use cases:
+ * a few keys (two or three at maximum)
+ * with a single device, which only has one slot.
+ *
+ * So, static fixed allocation is better.
+ */
+#define MAX_KEYS 10
+#define MAX_SLOTS 10
+
+enum key_type {
+ KEY_RSA,
+ KEY_EC,
+ KEY_EDDSA,
+};
+
+#define KEY_FLAG_VALID (1 << 0)
+#define KEY_FLAG_NO_PUBKEY (1 << 1)
+#define KEY_FLAG_USAGE_SIGN (1 << 2)
+#define KEY_FLAG_USAGE_DECRYPT (1 << 3)
+
+struct key {
+ struct token *token; /* Back pointer. */
+ unsigned long flags;
+ int key_type;
+ char keygrip[2*KEYGRIP_LEN+1];
+ gcry_sexp_t pubkey;
+ /* PKCS#11 interface */
+ unsigned char label[256];
+ unsigned long label_len;
+ unsigned char id[256];
+ unsigned long id_len;
+ ck_object_handle_t p11_keyid;
+ ck_mechanism_type_t mechanism;
+};
+
+struct token {
+ struct cryptoki *ck; /* Back pointer. */
+ int valid;
+ ck_slot_id_t slot_id;
+ int login_required;
+ ck_session_handle_t session;
+ int num_keys;
+ struct key key_list[MAX_KEYS];
+};
+
+struct cryptoki {
+ void *handle; /* DL handle to PKCS#11 Module. */
+ struct ck_function_list *f;
+ int num_slots;
+ struct token token_list[MAX_SLOTS];
+};
+
+/* Possibly, we will extend this to support multiple PKCS#11 modules.
+ * For now, it's only one.
+ */
+static struct cryptoki ck_instance[1];
+
+
+static long
+get_function_list (struct cryptoki *ck)
+{
+ unsigned long r = 0;
+ unsigned long (*p_func) (struct ck_function_list **);
+
+ p_func = (CK_C_GetFunctionList)dlsym (ck->handle, "C_GetFunctionList");
+ if (p_func == NULL)
+ {
+ return -1;
+ }
+ r = p_func (&ck->f);
+
+ if (r || ck->f == NULL)
+ {
+ return -1;
+ }
+
+ r = ck->f->C_Initialize (NULL);
+ if (r)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static long
+get_slot_list (struct cryptoki *ck,
+ unsigned long *num_slot_p,
+ ck_slot_id_t *slot_list)
+{
+ unsigned long err = 0;
+
+ /* Scute requires first call with NULL, to rescan. */
+ err = ck->f->C_GetSlotList (TRUE, NULL, num_slot_p);
+ if (err)
+ return err;
+
+ err = ck->f->C_GetSlotList (TRUE, slot_list, num_slot_p);
+ if (err)
+ {
+ return err;
+ }
+
+ return 0;
+}
+
+static long
+get_token_info (struct token *token, struct ck_token_info *tk_info)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+ ck_slot_id_t slot_id = token->slot_id;
+
+ err = ck->f->C_GetTokenInfo (slot_id, tk_info);
+ if (err)
+ {
+ return err;
+ }
+
+ return 0;
+}
+
+/* XXX Implement some useful things to be notified... */
+struct p11dev {
+ int d;
+};
+
+static struct p11dev p11_priv;
+
+static unsigned long
+notify_cb (ck_session_handle_t session,
+ ck_notification_t event, void *application)
+{
+ struct p11dev *priv = application;
+
+ (void)priv;
+ (void)session;
+ (void)event;
+ (void)application;
+ return 0;
+}
+
+static long
+open_session (struct token *token)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+ ck_slot_id_t slot_id = token->slot_id;
+ ck_session_handle_t session_handle;
+ ck_flags_t session_flags;
+
+ session_flags = CKU_USER;
+ // session_flags = session_flags | CKF_RW_SESSION;
+ session_flags = session_flags | CKF_SERIAL_SESSION;
+
+ err = ck->f->C_OpenSession (slot_id, session_flags,
+ (void *)&p11_priv, notify_cb, &session_handle);
+ if (err)
+ {
+ log_debug ("open_session: %ld\n", err);
+ return -1;
+ }
+
+ token->session = session_handle;
+ token->valid = 1;
+ token->num_keys = 0;
+
+ return 0;
+}
+
+static long
+close_session (struct token *token)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+
+ if (!token->valid)
+ return -1;
+
+ err = ck->f->C_CloseSession (token->session);
+ if (err)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static long
+login (struct token *token,
+ const unsigned char *pin, int pin_len)
+{
+ unsigned long err = 0;
+ unsigned long user_type = CKU_USER;
+ struct cryptoki *ck = token->ck;
+
+ err = ck->f->C_Login (token->session, user_type,
+ (unsigned char *)pin, pin_len);
+ if (err)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static long
+logout (struct token *token)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+
+ err = ck->f->C_Logout (token->session);
+ if (err)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void
+compute_keygrip_rsa (char *keygrip, gcry_sexp_t *r_pubkey,
+ const char *modulus, unsigned long modulus_len,
+ const char *exponent, unsigned long exponent_len)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_pkey = NULL;
+ const char *format = "(public-key(rsa(n%b)(e%b)))";
+ unsigned char grip[20];
+
+ *r_pubkey = NULL;
+ err = gcry_sexp_build (&s_pkey, NULL, format,
+ (int)modulus_len, modulus,
+ (int)exponent_len, exponent);
+ if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
+ err = gpg_error (GPG_ERR_INTERNAL);
+ else
+ {
+ bin2hex (grip, 20, keygrip);
+ log_debug ("keygrip: %s\n", keygrip);
+ *r_pubkey = s_pkey;
+ }
+}
+
+static void
+compute_keygrip_ec (char *keygrip, gcry_sexp_t *r_pubkey,
+ const char *curve, const char *ecpoint,
+ unsigned long ecpoint_len)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_pkey = NULL;
+ const char *format = "(public-key(ecc(curve %s)(q%b)))";
+ unsigned char grip[20];
+
+ *r_pubkey = NULL;
+ err = gcry_sexp_build (&s_pkey, NULL, format, curve, (int)ecpoint_len,
+ ecpoint);
+ if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
+ err = gpg_error (GPG_ERR_INTERNAL);
+ else
+ {
+ bin2hex (grip, 20, keygrip);
+ log_debug ("keygrip: %s\n", keygrip);
+ *r_pubkey = s_pkey;
+ }
+}
+
+
+static long
+examine_public_key (struct token *token, struct key *k, unsigned long keytype,
+ int update_keyid, ck_object_handle_t obj)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+ unsigned char modulus[1024];
+ unsigned char exponent[8];
+ unsigned char ecparams[256];
+ unsigned char ecpoint[256];
+ struct ck_attribute templ[3];
+ unsigned long mechanisms[3];
+ unsigned char supported;
+
+ if (keytype == CKK_RSA)
+ {
+ if (update_keyid)
+ k->p11_keyid = obj;
+ k->key_type = KEY_RSA;
+
+ templ[0].type = CKA_MODULUS;
+ templ[0].pValue = (void *)modulus;
+ templ[0].ulValueLen = sizeof (modulus);
+
+ templ[1].type = CKA_PUBLIC_EXPONENT;
+ templ[1].pValue = (void *)exponent;
+ templ[1].ulValueLen = sizeof (exponent);
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 2);
+ if (err)
+ {
+ k->flags |= KEY_FLAG_NO_PUBKEY;
+ return 1;
+ }
+
+ k->flags |= KEY_FLAG_VALID;
+ k->flags &= ~KEY_FLAG_NO_PUBKEY;
+ if ((modulus[0] & 0x80))
+ {
+ memmove (modulus+1, modulus, templ[0].ulValueLen);
+ templ[0].ulValueLen++;
+ modulus[0] = 0;
+ }
+
+ /* Found a RSA key. */
+ log_debug ("RSA: %ld %ld\n",
+ templ[0].ulValueLen,
+ templ[1].ulValueLen);
+
+ compute_keygrip_rsa (k->keygrip, &k->pubkey,
+ modulus, templ[0].ulValueLen,
+ exponent, templ[1].ulValueLen);
+
+ k->mechanism = CKM_RSA_PKCS;
+ }
+ else if (keytype == CKK_EC)
+ {
+ char *curve_oid = NULL;
+ const char *curve;
+
+ if (update_keyid)
+ k->p11_keyid = obj;
+ k->key_type = KEY_EC;
+
+ templ[0].type = CKA_EC_PARAMS;
+ templ[0].pValue = ecparams;
+ templ[0].ulValueLen = sizeof (ecparams);
+
+ templ[1].type = CKA_EC_POINT;
+ templ[1].pValue = (void *)ecpoint;
+ templ[1].ulValueLen = sizeof (ecpoint);
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 2);
+ if (err)
+ {
+ k->flags |= KEY_FLAG_NO_PUBKEY;
+ return 1;
+ }
+
+ k->flags |= KEY_FLAG_VALID;
+ k->flags &= ~KEY_FLAG_NO_PUBKEY;
+ /* Found an ECC key. */
+ log_debug ("ECC: %ld %ld\n",
+ templ[0].ulValueLen,
+ templ[1].ulValueLen);
+
+ curve_oid = openpgp_oidbuf_to_str (ecparams+1, templ[0].ulValueLen-1);
+ curve = openpgp_oid_to_curve (curve_oid, 1);
+ xfree (curve_oid);
+
+ compute_keygrip_ec (k->keygrip, &k->pubkey,
+ curve, ecpoint, templ[1].ulValueLen);
+
+ templ[0].type = CKA_ALLOWED_MECHANISMS;
+ templ[0].pValue = (void *)mechanisms;
+ templ[0].ulValueLen = sizeof (mechanisms);
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 1);
+ if (!err)
+ {
+ if (templ[0].ulValueLen)
+ {
+ /* Scute works well. */
+ log_debug ("mechanism: %lx %ld\n", mechanisms[0], templ[0].ulValueLen);
+ k->mechanism = mechanisms[0];
+ }
+ else
+ {
+ log_debug ("SoftHSMv2???");
+ k->mechanism = CKM_ECDSA;
+ }
+ }
+ else
+ {
+ /* Yubkey YKCS doesn't offer CKA_ALLOWED_MECHANISMS,
+ unfortunately. */
+ log_debug ("Yubikey???");
+ k->mechanism = CKM_ECDSA_SHA256;
+ }
+ }
+
+ templ[0].type = CKA_SIGN;
+ templ[0].pValue = (void *)&supported;
+ templ[0].ulValueLen = sizeof (supported);
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 1);
+ if (!err)
+ {
+ /* XXX: Scute has the attribute, but not set. */
+ k->flags |= KEY_FLAG_USAGE_SIGN;
+ }
+
+ templ[0].type = CKA_DECRYPT;
+ templ[0].pValue = (void *)&supported;
+ templ[0].ulValueLen = sizeof (supported);
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 1);
+ if (!err && supported)
+ {
+ k->flags |= KEY_FLAG_USAGE_DECRYPT;
+ }
+
+ return 0;
+}
+
+static long
+detect_private_keys (struct token *token)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+
+ struct ck_attribute templ[8];
+
+ unsigned long class;
+ unsigned long keytype;
+
+ unsigned long cnt = 0;
+ ck_object_handle_t obj;
+
+ class = CKO_PRIVATE_KEY;
+ templ[0].type = CKA_CLASS;
+ templ[0].pValue = (void *)&class;
+ templ[0].ulValueLen = sizeof (class);
+
+ token->num_keys = 0;
+
+ err = ck->f->C_FindObjectsInit (token->session, templ, 1);
+ if (!err)
+ {
+ while (TRUE)
+ {
+ unsigned long any;
+ struct key *k = &token->key_list[cnt]; /* Allocate a key. */
+
+ k->token = token;
+ k->flags = 0;
+
+ /* Portable way to get objects... is get it one by one. */
+ err = ck->f->C_FindObjects (token->session, &obj, 1, &any);
+ if (err || any == 0)
+ break;
+
+ templ[0].type = CKA_KEY_TYPE;
+ templ[0].pValue = &keytype;
+ templ[0].ulValueLen = sizeof (keytype);
+
+ templ[1].type = CKA_LABEL;
+ templ[1].pValue = (void *)k->label;
+ templ[1].ulValueLen = sizeof (k->label) - 1;
+
+ templ[2].type = CKA_ID;
+ templ[2].pValue = (void *)k->id;
+ templ[2].ulValueLen = sizeof (k->id) - 1;
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 3);
+ if (err)
+ {
+ continue;
+ }
+
+ cnt++;
+
+ k->label_len = templ[1].ulValueLen;
+ k->label[k->label_len] = 0;
+ k->id_len = templ[2].ulValueLen;
+ k->id[k->id_len] = 0;
+
+ log_debug ("slot: %lx handle: %ld label: %s key_type: %ld id: %s\n",
+ token->slot_id, obj, k->label, keytype, k->id);
+
+ if (examine_public_key (token, k, keytype, 1, obj))
+ continue;
+ }
+
+ token->num_keys = cnt;
+ err = ck->f->C_FindObjectsFinal (token->session);
+ if (err)
+ {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static long
+check_public_keys (struct token *token)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+
+ struct ck_attribute templ[8];
+
+ unsigned char label[256];
+ unsigned long class;
+ unsigned long keytype;
+ unsigned char id[256];
+
+ ck_object_handle_t obj;
+ int i;
+
+ class = CKO_PUBLIC_KEY;
+ templ[0].type = CKA_CLASS;
+ templ[0].pValue = (void *)&class;
+ templ[0].ulValueLen = sizeof (class);
+
+ err = ck->f->C_FindObjectsInit (token->session, templ, 1);
+ if (!err)
+ {
+ while (TRUE)
+ {
+ unsigned long any;
+ struct key *k = NULL;
+
+ /* Portable way to get objects... is get it one by one. */
+ err = ck->f->C_FindObjects (token->session, &obj, 1, &any);
+ if (err || any == 0)
+ break;
+
+ templ[0].type = CKA_LABEL;
+ templ[0].pValue = (void *)label;
+ templ[0].ulValueLen = sizeof (label);
+
+ templ[1].type = CKA_KEY_TYPE;
+ templ[1].pValue = &keytype;
+ templ[1].ulValueLen = sizeof (keytype);
+
+ templ[2].type = CKA_ID;
+ templ[2].pValue = (void *)id;
+ templ[2].ulValueLen = sizeof (id);
+
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 3);
+ if (err)
+ {
+ continue;
+ }
+
+ label[templ[0].ulValueLen] = 0;
+ id[templ[2].ulValueLen] = 0;
+
+ /* Locate matching private key. */
+ for (i = 0; i < token->num_keys; i++)
+ {
+ k = &token->key_list[i];
+
+ if ((k->flags & KEY_FLAG_NO_PUBKEY)
+ && k->label_len == templ[0].ulValueLen
+ && memcmp (label, k->label, k->label_len) == 0
+ && ((keytype == CKK_RSA && k->key_type == KEY_RSA)
+ || (keytype == CKK_EC && k->key_type == KEY_EC))
+ && k->id_len == templ[2].ulValueLen
+ && memcmp (id, k->id, k->id_len) == 0)
+ break;
+ }
+
+ if (i == token->num_keys)
+ continue;
+
+ log_debug ("pub: slot: %lx handle: %ld label: %s key_type: %ld id: %s\n",
+ token->slot_id, obj, label, keytype, id);
+
+ if (examine_public_key (token, k, keytype, 0, obj))
+ continue;
+ }
+
+ err = ck->f->C_FindObjectsFinal (token->session);
+ if (err)
+ {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+#if 0
+static long
+get_certificate (struct token *token)
+{
+ unsigned long err = 0;
+ struct cryptoki *ck = token->ck;
+
+ struct ck_attribute templ[1];
+
+ unsigned long class;
+ unsigned char certificate[4096];
+ unsigned long cert_len;
+ int certificate_available;
+
+ ck_object_handle_t obj;
+ int i;
+
+ class = CKO_CERTIFICATE;
+ templ[0].type = CKA_CLASS;
+ templ[0].pValue = (void *)&class;
+ templ[0].ulValueLen = sizeof (class);
+
+ err = ck->f->C_FindObjectsInit (token->session, templ, 1);
+ if (!err)
+ {
+ while (TRUE)
+ {
+ unsigned long any;
+
+ /* Portable way to get objects... is get it one by one. */
+ err = ck->f->C_FindObjects (token->session, &obj, 1, &any);
+ if (err || any == 0)
+ break;
+
+ templ[0].type = CKA_VALUE;
+ templ[0].pValue = (void *)certificate;
+ templ[0].ulValueLen = sizeof (certificate);
+ err = ck->f->C_GetAttributeValue (token->session, obj, templ, 1);
+ if (err)
+ certificate_available = 0;
+ else
+ {
+ certificate_available = 1;
+ cert_len = templ[0].ulValueLen;
+
+ puts ("Certificate available:");
+ for (i = 0; i < cert_len; i++)
+ {
+ printf ("%02x", certificate[i]);
+ if ((i % 16) == 15)
+ puts ("");
+ }
+ puts ("");
+ }
+ }
+
+ err = ck->f->C_FindObjectsFinal (token->session);
+ if (err)
+ {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static long
+learn_keys (struct token *token)
+{
+ int i;
+
+ /* Detect private keys on the token.
+ * It's good if it also offers raw public key material.
+ */
+ detect_private_keys (token);
+
+ /*
+ * In some implementations (EC key on SoftHSMv2, for example),
+ * attributes for raw public key material is not available in
+ * a CKO_PRIVATE_KEY object.
+ *
+ * We try to examine CKO_PUBLIC_KEY objects, too see if it provides
+ * raw public key material in a CKO_PUBLIC_KEY object.
+ */
+ check_public_keys (token);
+
+ for (i = 0; i < token->num_keys; i++)
+ {
+ struct key *k = &token->key_list[i];
+
+ if ((k->flags & KEY_FLAG_NO_PUBKEY))
+ k->flags &= ~KEY_FLAG_NO_PUBKEY;
+ }
+
+#if 0
+ /* Another way to get raw public key material is get it from the
+ certificate, if available. */
+ get_certificate (token);
+#endif
+
+ return 0;
+}
+
+
+static long
+find_key (struct cryptoki *ck, const char *keygrip, struct key **r_key)
+{
+ int i;
+ int j;
+
+ log_debug ("find_key: %s\n", keygrip);
+
+ *r_key = NULL;
+ for (i = 0; i < ck->num_slots; i++)
+ {
+ struct token *token = &ck->token_list[i];
+
+ if (!token->valid)
+ continue;
+
+ for (j = 0; j < token->num_keys; j++)
+ {
+ struct key *k = &token->key_list[j];
+
+ if ((k->flags & KEY_FLAG_VALID) == 0)
+ continue;
+
+ if (memcmp (k->keygrip, keygrip, 40) == 0)
+ {
+ *r_key = k;
+ log_debug ("found a key at %d:%d\n", i, j);
+ return 0;
+ }
+ }
+ }
+
+ return -1;
+}
+
+struct iter_key {
+ struct cryptoki *ck;
+ int i;
+ int j;
+ unsigned long mask;
+ int st;
+};
+
+static void
+iter_find_key_setup (struct iter_key *iter, struct cryptoki *ck, int cap)
+{
+ iter->st = 0;
+ iter->ck = ck;
+ iter->i = 0;
+ iter->j = 0;
+ iter->mask = 0;
+ if (cap == GCRY_PK_USAGE_SIGN)
+ iter->mask |= KEY_FLAG_USAGE_SIGN;
+ else if (cap == GCRY_PK_USAGE_ENCR)
+ iter->mask = KEY_FLAG_USAGE_DECRYPT;
+ else
+ iter->mask = KEY_FLAG_USAGE_SIGN | KEY_FLAG_USAGE_DECRYPT;
+}
+
+static int
+iter_find_key (struct iter_key *iter, struct key **r_key)
+{
+ struct cryptoki *ck = iter->ck;
+ struct token *token;
+ struct key *k;
+
+ *r_key = NULL;
+
+ if (iter->i < ck->num_slots)
+ token = &ck->token_list[iter->i];
+ else
+ token = NULL;
+
+ switch (iter->st)
+ while (1)
+ {
+ case 0:
+ if (iter->i < ck->num_slots)
+ {
+ token = &ck->token_list[iter->i++];
+ if (!token->valid)
+ continue;
+ }
+ else
+ {
+ iter->st = 2;
+ /*FALLTHROUGH*/
+ default:
+ return 0;
+ }
+
+ iter->j = 0;
+ while (1)
+ {
+ /*FALLTHROUGH*/
+ case 1:
+ if (token && iter->j < token->num_keys)
+ {
+ k = &token->key_list[iter->j++];
+ if ((k->flags & KEY_FLAG_VALID) && (k->flags & iter->mask))
+ {
+ /* Found */
+ *r_key = k;
+ iter->st = 1;
+ return 1;
+ }
+ }
+ else
+ break;
+ }
+ }
+}
+
+static gpg_error_t
+setup_pksign (struct key *key, int hash_algo,
+ unsigned char **r_signature, unsigned long *r_signature_len)
+{
+ gpg_error_t err = 0;
+ unsigned long r = 0;
+ struct token *token = key->token;
+ struct cryptoki *ck = token->ck;
+ ck_mechanism_type_t mechanism;
+ struct ck_mechanism mechanism_struct;
+ unsigned int nbits;
+ unsigned long siglen;
+ unsigned char *sig;
+
+ nbits = gcry_pk_get_nbits (key->pubkey);
+
+ mechanism = key->mechanism;
+ if (key->key_type == KEY_RSA)
+ {
+ /* It's CKM_RSA_PKCS, it requires that hash algo OID included in
+ the data to be signed. */
+ if (!hash_algo)
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+
+ siglen = (nbits+7)/8;
+ }
+ else if (key->key_type == KEY_EC)
+ {
+ siglen = ((nbits+7)/8) * 2;
+ }
+ else if (key->key_type == KEY_EDDSA)
+ {
+ mechanism = CKM_EDDSA;
+ siglen = ((nbits+7)/8)*2;
+ }
+ else
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+
+ mechanism_struct.mechanism = mechanism;
+ mechanism_struct.parameter = NULL;
+ mechanism_struct.parameter_len = 0;
+
+ r = ck->f->C_SignInit (token->session, &mechanism_struct,
+ key->p11_keyid);
+ if (r)
+ {
+ log_error ("C_SignInit error: %ld", r);
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ sig = xtrymalloc (siglen);
+ if (!sig)
+ return gpg_error_from_syserror ();
+
+ *r_signature = sig;
+ *r_signature_len = siglen;
+
+ return err;
+}
+
+static gpg_error_t
+do_pksign (struct key *key, int hash_algo,
+ const unsigned char *u_data, unsigned long u_data_len,
+ unsigned char *signature,
+ unsigned long *r_signature_len)
+{
+ gpg_error_t err = 0;
+ unsigned long r = 0;
+ struct token *token = key->token;
+ struct cryptoki *ck = token->ck;
+ ck_mechanism_type_t mechanism;
+ unsigned char data[1024];
+ unsigned long data_len;
+
+ mechanism = key->mechanism;
+ if (key->key_type == KEY_RSA)
+ {
+ size_t asnlen = sizeof (data);
+
+ gcry_md_get_asnoid (hash_algo, data, &asnlen);
+ /* u_data_len == gcry_md_get_algo_dlen (hash_algo) */
+ memcpy (data+asnlen, u_data, u_data_len);
+ data_len = asnlen+gcry_md_get_algo_dlen (hash_algo);
+ }
+ else if (key->key_type == KEY_EC)
+ {
+ if (mechanism == CKM_ECDSA)
+ {
+ /* SoftHSMv2 */
+ memcpy (data, u_data, u_data_len);
+ data_len = u_data_len;
+ }
+ else
+ {
+ if (!hash_algo)
+ {
+ /* Not specified by user, determine from MECHANISM */
+ if (mechanism == CKM_ECDSA_SHA256)
+ hash_algo = GCRY_MD_SHA256;
+ else if (mechanism == CKM_ECDSA_SHA384)
+ hash_algo = GCRY_MD_SHA384;
+ else if (mechanism == CKM_ECDSA_SHA384)
+ hash_algo = GCRY_MD_SHA512;
+ else
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ /* Scute, YKCS11 */
+ /* u_data_len == gcry_md_get_algo_dlen (hash_algo) */
+ memcpy (data, u_data, u_data_len);
+ data_len = gcry_md_get_algo_dlen (hash_algo);
+ }
+ }
+ else if (key->key_type == KEY_EDDSA)
+ {
+ mechanism = CKM_EDDSA;
+ memcpy (data, u_data, u_data_len);
+ data_len = u_data_len;
+ }
+ else
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+
+ r = ck->f->C_Sign (token->session,
+ data, data_len,
+ signature, r_signature_len);
+ if (r)
+ {
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ return err;
+}
+
+static gpg_error_t
+token_open (assuan_context_t ctx, struct cryptoki *ck, struct token *token,
+ ck_slot_id_t slot_id)
+{
+ gpg_error_t err = 0;
+ struct ck_token_info tk_info;
+ long r;
+
+ token->ck = ck;
+ token->valid = 0;
+ token->slot_id = slot_id;
+
+ if (get_token_info (token, &tk_info))
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+
+ if ((tk_info.flags & CKF_TOKEN_INITIALIZED) == 0
+ || (tk_info.flags & CKF_TOKEN_PRESENT) == 0
+ || (tk_info.flags & CKF_USER_PIN_LOCKED) != 0)
+ return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+
+ token->login_required = (tk_info.flags & CKF_LOGIN_REQUIRED);
+
+ r = open_session (token);
+ if (r)
+ {
+ log_error ("Error at open_session: %ld\n", r);
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ if (token->login_required)
+ {
+ char *command;
+ int rc;
+ unsigned char *value;
+ size_t valuelen;
+
+ log_debug ("asking for PIN '%ld'\n", token->slot_id);
+
+ rc = gpgrt_asprintf (&command, "NEEDPIN %ld", token->slot_id);
+ if (rc < 0)
+ return gpg_error (gpg_err_code_from_errno (errno));
+
+ assuan_begin_confidential (ctx);
+ err = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
+ assuan_end_confidential (ctx);
+ xfree (command);
+ if (err)
+ {
+ close_session (token);
+ token->session = 0;
+ return err;
+ }
+
+ login (token, value, valuelen);
+ xfree (value);
+ }
+
+ r = learn_keys (token);
+ return 0;
+}
+
+static gpg_error_t
+token_close (struct token *token)
+{
+ int j;
+ long r;
+ int num_keys = token->num_keys;
+
+ if (!token->valid)
+ return 0;
+
+ if (token->login_required)
+ logout (token);
+
+ r = close_session (token);
+ if (r)
+ log_error ("Error at close_session: %ld\n", r);
+
+ token->ck = NULL;
+ token->slot_id = 0;
+ token->login_required = 0;
+ token->session = 0;
+ token->num_keys = 0;
+
+ for (j = 0; j < num_keys; j++)
+ {
+ struct key *k = &token->key_list[j];
+
+ if ((k->flags & KEY_FLAG_VALID))
+ {
+ gcry_sexp_release (k->pubkey);
+ k->pubkey = NULL;
+ }
+
+ k->token = NULL;
+ k->flags = 0;
+ k->key_type = 0;
+ k->label_len = 0;
+ k->id_len = 0;
+ k->p11_keyid = 0;
+ k->mechanism = 0;
+ }
+
+ token->valid = 0;
+ return 0;
+}
+
+
+static gpg_error_t
+token_check (struct token *token)
+{
+ struct ck_token_info tk_info;
+
+ if (get_token_info (token, &tk_info))
+ {
+ /* Possibly, invalidate the token and close session.
+ * Now, ingore the error. */
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ if ((tk_info.flags & CKF_TOKEN_INITIALIZED) == 0
+ || (tk_info.flags & CKF_TOKEN_PRESENT) == 0
+ || (tk_info.flags & CKF_USER_PIN_LOCKED) != 0)
+ {
+ token_close (token);
+ return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+ }
+
+ return 0;
+}
+
+
+gpg_error_t
+tkd_init (ctrl_t ctrl, assuan_context_t ctx, int rescan)
+{
+ gpg_error_t err = 0;
+
+ long r;
+ struct cryptoki *ck = ck_instance;
+ unsigned long num_slots = MAX_SLOTS;
+ ck_slot_id_t slot_list[MAX_SLOTS];
+ int i;
+
+ const char *module_name;
+
+ module_name = opt.pkcs11_driver;
+ if (!module_name)
+ return gpg_error (GPG_ERR_NO_NAME);
+
+ if (ck->handle == NULL)
+ {
+ void *handle;
+ int num_tokens = 0;
+
+ handle = dlopen (module_name, RTLD_NOW);
+ if (handle == NULL)
+ {
+ return -1;
+ }
+
+ ck->handle = handle;
+
+ r = get_function_list (ck);
+ if (r)
+ {
+ dlclose (ck->handle);
+ ck->handle = NULL;
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ r = get_slot_list (ck, &num_slots, slot_list);
+ if (r)
+ {
+ dlclose (ck->handle);
+ ck->handle = NULL;
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ for (i = 0; i < num_slots; i++)
+ {
+ struct token *token = &ck->token_list[num_tokens]; /* Allocate one token in CK */
+
+ err = token_open (ctx, ck, token, slot_list[i]);
+ if (!err)
+ num_tokens++;
+ }
+
+ ck->num_slots = num_tokens;
+ return 0;
+ }
+ else if (rescan == 0)
+ return 0;
+
+ /* Rescan the slots to see the changes. */
+
+ r = get_slot_list (ck, &num_slots, slot_list);
+ if (r)
+ {
+ tkd_fini (ctrl, ctx);
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ for (i = 0; i < num_slots; i++)
+ {
+ int j;
+ ck_slot_id_t slot_id = slot_list[i];
+ struct token *token = NULL;
+
+ for (j = 0; j < ck->num_slots; j++)
+ if (slot_id == ck->token_list[j].slot_id)
+ {
+ token = &ck->token_list[j];
+ break;
+ }
+
+ if (token)
+ {
+ err = token_check (token);
+ }
+ else
+ /* new token */
+ {
+ /* Allocate one token in CK */
+ token = &ck->token_list[ck->num_slots];
+ err = token_open (ctx, ck, token, slot_id);
+ if (!err)
+ ck->num_slots++;
+ }
+ }
+
+ return err;
+}
+
+gpg_error_t
+tkd_fini (ctrl_t ctrl, assuan_context_t ctx)
+{
+ long r;
+ struct cryptoki *ck = ck_instance;
+ int i;
+
+ (void)ctrl;
+ (void)ctx;
+
+ for (i = 0; i < ck->num_slots; i++)
+ {
+ struct token *token = &ck->token_list[i];
+
+ token_close (token);
+ }
+
+ ck->num_slots = 0;
+
+ r = ck->f->C_Finalize (NULL);
+ if (r)
+ {
+ return -1;
+ }
+
+ ck->f = NULL;
+
+ dlclose (ck->handle);
+ ck->handle = NULL;
+
+ return 0;
+}
+
+
+gpg_error_t
+tkd_sign (ctrl_t ctrl, assuan_context_t ctx,
+ const char *keygrip, int hash_algo,
+ unsigned char **r_outdata, size_t *r_outdatalen)
+{
+ gpg_error_t err;
+ struct key *k;
+ struct cryptoki *ck = ck_instance;
+ unsigned long r;
+
+ (void)ctrl;
+ /* mismatch: size_t for GnuPG, unsigned long for PKCS#11 */
+ /* mismatch: application prepare buffer for PKCS#11 */
+
+ if (!ck->handle)
+ {
+ err = tkd_init (ctrl, ctx, 0);
+ if (err)
+ return err;
+ }
+
+ *r_outdata = NULL;
+ r = find_key (ck, keygrip, &k);
+ if (r)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+ else
+ {
+ const char *cmd;
+ unsigned char *value;
+ size_t valuelen;
+ unsigned char *sig = NULL;
+
+ err = setup_pksign (k, hash_algo, &sig, r_outdatalen);
+ if (err)
+ return err;
+
+ cmd = "EXTRA";
+ err = assuan_inquire (ctx, cmd, &value, &valuelen, MAXLEN_VALUE);
+ if (err)
+ {
+ xfree (sig);
+ return err;
+ }
+
+ err = do_pksign (k, hash_algo, value, valuelen, sig, r_outdatalen);
+ wipememory (value, valuelen);
+ xfree (value);
+ if (err)
+ {
+ xfree (sig);
+ return err;
+ }
+
+ *r_outdata = sig;
+ }
+
+ return err;
+}
+
+static const char *
+get_usage_string (struct key *k)
+{
+ const char *usage = NULL;
+
+ if ((k->flags & KEY_FLAG_USAGE_SIGN))
+ {
+ if ((k->flags & KEY_FLAG_USAGE_DECRYPT))
+ usage = "se";
+ else
+ usage = "s";
+ }
+ else
+ {
+ if ((k->flags & KEY_FLAG_USAGE_DECRYPT))
+ usage = "e";
+ else
+ usage = "-";
+ }
+
+ return usage;
+}
+
+gpg_error_t
+tkd_readkey (ctrl_t ctrl, assuan_context_t ctx, const char *keygrip)
+{
+ gpg_error_t err = 0;
+ struct key *k;
+ struct cryptoki *ck = ck_instance;
+ unsigned long r;
+ unsigned char *pk;
+ size_t pklen;
+
+ (void)ctrl;
+ (void)ctx;
+
+ if (!ck->handle)
+ {
+ err = tkd_init (ctrl, ctx, 0);
+ if (err)
+ return err;
+ }
+
+ r = find_key (ck, keygrip, &k);
+ if (r)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ pklen = gcry_sexp_sprint (k->pubkey, GCRYSEXP_FMT_CANON, NULL, 0);
+ pk = xtrymalloc (pklen);
+ if (!pk)
+ {
+ return gpg_error_from_syserror ();
+ }
+ gcry_sexp_sprint (k->pubkey, GCRYSEXP_FMT_CANON, pk, pklen);
+ err = assuan_send_data (ctx, pk, pklen);
+ xfree (pk);
+ return err;
+}
+
+gpg_error_t
+tkd_keyinfo (ctrl_t ctrl, assuan_context_t ctx, const char *keygrip,
+ int opt_data, int cap)
+{
+ gpg_error_t err = 0;
+ struct cryptoki *ck = ck_instance;
+ struct key *k;
+ const char *usage;
+
+ if (!ck->handle)
+ {
+ err = tkd_init (ctrl, ctx, 0);
+ if (err)
+ return err;
+ }
+
+ if (keygrip)
+ {
+ unsigned long r;
+
+ r = find_key (ck, keygrip, &k);
+ if (r)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ usage = get_usage_string (k);
+ send_keyinfo (ctrl, opt_data, keygrip,
+ k->label_len ? (const char *)k->label : "-",
+ k->id_len ? (const char *)k->id : "-",
+ usage);
+ }
+ else
+ {
+ struct iter_key iter;
+
+ iter_find_key_setup (&iter, ck, cap);
+ while (iter_find_key (&iter, &k))
+ {
+ usage = get_usage_string (k);
+ send_keyinfo (ctrl, opt_data, k->keygrip,
+ k->label_len ? (const char *)k->label : "-",
+ k->id_len ? (const char *)k->id : "-",
+ usage);
+ }
+ }
+
+ return err;
+}