mirror of
git://git.gnupg.org/gnupg.git
synced 2024-11-09 21:28:51 +01:00
d0d728a5b7
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
1412 lines
31 KiB
C
1412 lines
31 KiB
C
#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);
|
||
#if 0
|
||
gcry_md_hash_buffer (hash_algo, data+asnlen,
|
||
u_data, u_data_len);
|
||
#else
|
||
/* u_data_len == gcry_md_get_algo_dlen (hash_algo) */
|
||
memcpy (data+asnlen, u_data, u_data_len);
|
||
#endif
|
||
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 */
|
||
gcry_md_hash_buffer (hash_algo, 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;
|
||
}
|