/* * GnuTLS PKCS#11 support * Copyright (C) 2010-2012 Free Software Foundation, Inc. * * Authors: Nikos Mavrogiannopoulos, Stef Walter * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #include #include #include #include #include #include #include #include #include #include struct gnutls_pkcs11_privkey_st { gnutls_pk_algorithm_t pk_algorithm; unsigned int flags; struct p11_kit_uri *info; struct pkcs11_session_info sinfo; ck_object_handle_t obj; /* the key in the session */ struct pin_info_st pin; }; /** * gnutls_pkcs11_privkey_init: * @key: The structure to be initialized * * This function will initialize an private key structure. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_pkcs11_privkey_init (gnutls_pkcs11_privkey_t * key) { *key = gnutls_calloc (1, sizeof (struct gnutls_pkcs11_privkey_st)); if (*key == NULL) { gnutls_assert (); return GNUTLS_E_MEMORY_ERROR; } (*key)->info = p11_kit_uri_new (); if ((*key)->info == NULL) { free (*key); gnutls_assert (); return GNUTLS_E_MEMORY_ERROR; } return 0; } /** * gnutls_pkcs11_privkey_deinit: * @key: The structure to be initialized * * This function will deinitialize a private key structure. **/ void gnutls_pkcs11_privkey_deinit (gnutls_pkcs11_privkey_t key) { p11_kit_uri_free (key->info); if (key->sinfo.init != 0) pkcs11_close_session (&key->sinfo); gnutls_free (key); } /** * gnutls_pkcs11_privkey_get_pk_algorithm: * @key: should contain a #gnutls_pkcs11_privkey_t structure * @bits: if bits is non null it will hold the size of the parameters' in bits * * This function will return the public key algorithm of a private * key. * * Returns: a member of the #gnutls_pk_algorithm_t enumeration on * success, or a negative error code on error. **/ int gnutls_pkcs11_privkey_get_pk_algorithm (gnutls_pkcs11_privkey_t key, unsigned int *bits) { if (bits) *bits = 0; /* FIXME */ return key->pk_algorithm; } /** * gnutls_pkcs11_privkey_get_info: * @pkey: should contain a #gnutls_pkcs11_privkey_t structure * @itype: Denotes the type of information requested * @output: where output will be stored * @output_size: contains the maximum size of the output and will be overwritten with actual * * This function will return information about the PKCS 11 private key such * as the label, id as well as token information where the key is stored. When * output is text it returns null terminated string although #output_size contains * the size of the actual data only. * * Returns: %GNUTLS_E_SUCCESS (0) on success or a negative error code on error. **/ int gnutls_pkcs11_privkey_get_info (gnutls_pkcs11_privkey_t pkey, gnutls_pkcs11_obj_info_t itype, void *output, size_t * output_size) { return pkcs11_get_info (pkey->info, itype, output, output_size); } static int find_object (struct pkcs11_session_info* sinfo, struct pin_info_st * pin_info, ck_object_handle_t * _obj, struct p11_kit_uri *info, unsigned int flags) { int ret; ck_object_handle_t obj; struct ck_attribute *attrs; unsigned long attr_count; unsigned long count; ck_rv_t rv; ret = pkcs11_open_session (sinfo, pin_info, info, flags & SESSION_LOGIN); if (ret < 0) { gnutls_assert (); return ret; } attrs = p11_kit_uri_get_attributes (info, &attr_count); rv = pkcs11_find_objects_init (sinfo->module, sinfo->pks, attrs, attr_count); if (rv != CKR_OK) { gnutls_assert (); _gnutls_debug_log ("pk11: FindObjectsInit failed.\n"); ret = pkcs11_rv_to_err (rv); goto fail; } if (pkcs11_find_objects (sinfo->module, sinfo->pks, &obj, 1, &count) == CKR_OK && count == 1) { *_obj = obj; pkcs11_find_objects_final (sinfo); return 0; } ret = GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; pkcs11_find_objects_final (sinfo); fail: pkcs11_close_session (sinfo); return ret; } #define FIND_OBJECT(sinfo, pin_info, obj, key) \ do { \ int retries = 0; \ int rret; \ ret = find_object (sinfo, pin_info, &obj, key->info, \ SESSION_LOGIN); \ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { \ if (_gnutls_token_func) \ { \ rret = pkcs11_call_token_func (key->info, retries++); \ if (rret == 0) continue; \ } \ return gnutls_assert_val(ret); \ } else if (ret < 0) { \ return gnutls_assert_val(ret); \ } \ } while (0); /*- * _gnutls_pkcs11_privkey_sign_hash: * @key: Holds the key * @hash: holds the data to be signed (should be output of a hash) * @signature: will contain the signature allocated with gnutls_malloc() * * This function will sign the given data using a signature algorithm * supported by the private key. It is assumed that the given data * are the output of a hash function. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. -*/ int _gnutls_pkcs11_privkey_sign_hash (gnutls_pkcs11_privkey_t key, const gnutls_datum_t * hash, gnutls_datum_t * signature) { ck_rv_t rv; int ret; struct ck_mechanism mech; gnutls_datum_t tmp = {NULL, 0}; unsigned long siglen; struct pkcs11_session_info _sinfo; struct pkcs11_session_info *sinfo; ck_object_handle_t obj; if (key->sinfo.init != 0) { sinfo = &key->sinfo; obj = key->obj; } else { sinfo = &_sinfo; memset(sinfo, 0, sizeof(*sinfo)); FIND_OBJECT (sinfo, &key->pin, obj, key); } mech.mechanism = pk_to_mech(key->pk_algorithm); mech.parameter = NULL; mech.parameter_len = 0; /* Initialize signing operation; using the private key discovered * earlier. */ rv = pkcs11_sign_init (sinfo->module, sinfo->pks, &mech, obj); if (rv != CKR_OK) { gnutls_assert (); ret = pkcs11_rv_to_err (rv); goto cleanup; } /* Work out how long the signature must be: */ rv = pkcs11_sign (sinfo->module, sinfo->pks, hash->data, hash->size, NULL, &siglen); if (rv != CKR_OK) { gnutls_assert (); ret = pkcs11_rv_to_err (rv); goto cleanup; } tmp.data = gnutls_malloc (siglen); tmp.size = siglen; rv = pkcs11_sign (sinfo->module, sinfo->pks, hash->data, hash->size, tmp.data, &siglen); if (rv != CKR_OK) { gnutls_assert (); ret = pkcs11_rv_to_err (rv); goto cleanup; } if (key->pk_algorithm == GNUTLS_PK_EC || key->pk_algorithm == GNUTLS_PK_DSA) { unsigned int hlen = siglen / 2; gnutls_datum_t r, s; if (siglen % 2 != 0) { gnutls_assert(); ret = GNUTLS_E_PK_SIGN_FAILED; goto cleanup; } r.data = tmp.data; r.size = hlen; s.data = &tmp.data[hlen]; s.size = hlen; ret = _gnutls_encode_ber_rs_raw (signature, &r, &s); if (ret < 0) { gnutls_assert(); goto cleanup; } gnutls_free(tmp.data); tmp.data = NULL; } else { signature->size = siglen; signature->data = tmp.data; } ret = 0; cleanup: if (sinfo != &key->sinfo) pkcs11_close_session (sinfo); if (ret < 0) gnutls_free(tmp.data); return ret; } /** * gnutls_pkcs11_privkey_status: * @key: Holds the key * * Checks the status of the private key token. * * Returns: this function will return non-zero if the token * holding the private key is still available (inserted), and zero otherwise. * * Since: 3.1.9 * **/ int gnutls_pkcs11_privkey_status (gnutls_pkcs11_privkey_t key) { ck_rv_t rv; int ret; struct pkcs11_session_info _sinfo; struct pkcs11_session_info *sinfo; ck_object_handle_t obj; struct ck_session_info session_info; if (key->sinfo.init != 0) { sinfo = &key->sinfo; obj = key->obj; } else { sinfo = &_sinfo; memset(sinfo, 0, sizeof(*sinfo)); FIND_OBJECT (sinfo, &key->pin, obj, key); } rv = (sinfo->module)->C_GetSessionInfo (sinfo->pks, &session_info); if (rv != CKR_OK) { ret = 0; goto cleanup; } ret = 1; cleanup: if (sinfo != &key->sinfo) pkcs11_close_session (sinfo); return ret; } /** * gnutls_pkcs11_privkey_import_url: * @pkey: The structure to store the parsed key * @url: a PKCS 11 url identifying the key * @flags: sequence of GNUTLS_PKCS_PRIVKEY_* * * This function will "import" a PKCS 11 URL identifying a private * key to the #gnutls_pkcs11_privkey_t structure. In reality since * in most cases keys cannot be exported, the private key structure * is being associated with the available operations on the token. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_pkcs11_privkey_import_url (gnutls_pkcs11_privkey_t pkey, const char *url, unsigned int flags) { int ret; struct ck_attribute *attr; ck_object_handle_t obj; struct ck_attribute a[4]; ck_key_type_t key_type; struct pkcs11_session_info sinfo; memset(&sinfo, 0, sizeof(sinfo)); ret = pkcs11_url_to_info (url, &pkey->info); if (ret < 0) { gnutls_assert (); return ret; } pkey->flags = flags; attr = p11_kit_uri_get_attribute (pkey->info, CKA_CLASS); if (!attr || attr->value_len != sizeof (ck_object_class_t) || *(ck_object_class_t*)attr->value != CKO_PRIVATE_KEY) { gnutls_assert (); return GNUTLS_E_INVALID_REQUEST; } attr = p11_kit_uri_get_attribute (pkey->info, CKA_ID); if (!attr || !attr->value_len) { attr = p11_kit_uri_get_attribute (pkey->info, CKA_LABEL); if (!attr || !attr->value_len) { gnutls_assert (); return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; } } FIND_OBJECT (&sinfo, &pkey->pin, obj, pkey); a[0].type = CKA_KEY_TYPE; a[0].value = &key_type; a[0].value_len = sizeof (key_type); if (pkcs11_get_attribute_value (sinfo.module, sinfo.pks, obj, a, 1) == CKR_OK) { pkey->pk_algorithm = mech_to_pk(key_type); if (pkey->pk_algorithm == GNUTLS_PK_UNKNOWN) { _gnutls_debug_log("Cannot determine PKCS #11 key algorithm\n"); ret = GNUTLS_E_UNKNOWN_ALGORITHM; goto cleanup; } } ret = 0; if (pkey->sinfo.init) pkcs11_close_session (&pkey->sinfo); if (sinfo.tinfo.max_session_count != 1) { /* We do not keep the session open in tokens that can * only support a single session. */ memcpy(&pkey->sinfo, &sinfo, sizeof(pkey->sinfo)); pkey->obj = obj; return ret; } cleanup: pkcs11_close_session (&sinfo); return ret; } /*- * _gnutls_pkcs11_privkey_decrypt_data: * @key: Holds the key * @flags: should be 0 for now * @ciphertext: holds the data to be signed * @plaintext: will contain the plaintext, allocated with gnutls_malloc() * * This function will decrypt the given data using the public key algorithm * supported by the private key. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. -*/ int _gnutls_pkcs11_privkey_decrypt_data (gnutls_pkcs11_privkey_t key, unsigned int flags, const gnutls_datum_t * ciphertext, gnutls_datum_t * plaintext) { ck_rv_t rv; int ret; struct ck_mechanism mech; unsigned long siglen; ck_object_handle_t obj; struct pkcs11_session_info _sinfo; struct pkcs11_session_info *sinfo; if (key->sinfo.init != 0) { sinfo = &key->sinfo; obj = key->obj; } else { sinfo = &_sinfo; memset(sinfo, 0, sizeof(*sinfo)); FIND_OBJECT (sinfo, &key->pin, obj, key); } if (key->pk_algorithm != GNUTLS_PK_RSA) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); mech.mechanism = CKM_RSA_PKCS; mech.parameter = NULL; mech.parameter_len = 0; /* Initialize signing operation; using the private key discovered * earlier. */ rv = pkcs11_decrypt_init (sinfo->module, sinfo->pks, &mech, obj); if (rv != CKR_OK) { gnutls_assert (); ret = pkcs11_rv_to_err (rv); goto cleanup; } /* Work out how long the plaintext must be: */ rv = pkcs11_decrypt (sinfo->module, sinfo->pks, ciphertext->data, ciphertext->size, NULL, &siglen); if (rv != CKR_OK) { gnutls_assert (); ret = pkcs11_rv_to_err (rv); goto cleanup; } plaintext->data = gnutls_malloc (siglen); plaintext->size = siglen; rv = pkcs11_decrypt (sinfo->module, sinfo->pks, ciphertext->data, ciphertext->size, plaintext->data, &siglen); if (rv != CKR_OK) { gnutls_free (plaintext->data); gnutls_assert (); ret = pkcs11_rv_to_err (rv); goto cleanup; } plaintext->size = siglen; ret = 0; cleanup: if (key->sinfo.init == 0) pkcs11_close_session (sinfo); return ret; } /** * gnutls_pkcs11_privkey_export_url: * @key: Holds the PKCS 11 key * @detailed: non zero if a detailed URL is required * @url: will contain an allocated url * * This function will export a URL identifying the given key. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. **/ int gnutls_pkcs11_privkey_export_url (gnutls_pkcs11_privkey_t key, gnutls_pkcs11_url_type_t detailed, char **url) { int ret; ret = pkcs11_info_to_url (key->info, detailed, url); if (ret < 0) { gnutls_assert (); return ret; } return 0; } /** * gnutls_pkcs11_privkey_generate: * @url: a token URL * @pk: the public key algorithm * @bits: the security bits * @label: a label * @flags: should be zero * * This function will generate a private key in the specified * by the @url token. The private key will be generate within * the token and will not be exportable. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.0 **/ int gnutls_pkcs11_privkey_generate (const char* url, gnutls_pk_algorithm_t pk, unsigned int bits, const char* label, unsigned int flags) { return gnutls_pkcs11_privkey_generate2( url, pk, bits, label, 0, NULL, flags); } /** * gnutls_pkcs11_privkey_generate2: * @url: a token URL * @pk: the public key algorithm * @bits: the security bits * @label: a label * @fmt: the format of output params. PEM or DER. * @pubkey: will hold the public key (may be %NULL) * @flags: should be zero * * This function will generate a private key in the specified * by the @url token. The private key will be generate within * the token and will not be exportable. This function will * store the DER-encoded public key in the SubjectPublicKeyInfo format * in @pubkey. The @pubkey should be deinitialized using gnutls_free(). * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. * * Since: 3.1.5 **/ int gnutls_pkcs11_privkey_generate2 (const char* url, gnutls_pk_algorithm_t pk, unsigned int bits, const char* label, gnutls_x509_crt_fmt_t fmt, gnutls_datum_t * pubkey, unsigned int flags) { int ret; const ck_bool_t tval = 1; const ck_bool_t fval = 0; struct pkcs11_session_info sinfo; struct p11_kit_uri *info = NULL; ck_rv_t rv; struct ck_attribute a[10], p[10]; ck_object_handle_t pub, priv; unsigned long _bits = bits; int a_val, p_val; struct ck_mechanism mech; gnutls_pubkey_t pkey = NULL; gnutls_pkcs11_obj_t obj = NULL; memset(&sinfo, 0, sizeof(sinfo)); ret = pkcs11_url_to_info (url, &info); if (ret < 0) { gnutls_assert (); return ret; } ret = pkcs11_open_session (&sinfo, NULL, info, SESSION_WRITE | pkcs11_obj_flags_to_int (flags)); p11_kit_uri_free (info); if (ret < 0) { gnutls_assert (); goto cleanup; } /* a holds the public key template * and p the private key */ a_val = p_val = 0; mech.parameter = NULL; mech.parameter_len = 0; mech.mechanism = pk_to_genmech(pk); switch(pk) { case GNUTLS_PK_RSA: p[p_val].type = CKA_DECRYPT; p[p_val].value = (void*)&tval; p[p_val].value_len = sizeof (tval); p_val++; p[p_val].type = CKA_SIGN; p[p_val].value = (void*)&tval; p[p_val].value_len = sizeof (tval); p_val++; a[a_val].type = CKA_ENCRYPT; a[a_val].value = (void*)&tval; a[a_val].value_len = sizeof (tval); a_val++; a[a_val].type = CKA_VERIFY; a[a_val].value = (void*)&tval; a[a_val].value_len = sizeof (tval); a_val++; a[a_val].type = CKA_MODULUS_BITS; a[a_val].value = &_bits; a[a_val].value_len = sizeof (_bits); a_val++; break; case GNUTLS_PK_DSA: p[p_val].type = CKA_SIGN; p[p_val].value = (void*)&tval; p[p_val].value_len = sizeof (tval); p_val++; a[a_val].type = CKA_VERIFY; a[a_val].value = (void*)&tval; a[a_val].value_len = sizeof (tval); a_val++; a[a_val].type = CKA_MODULUS_BITS; a[a_val].value = &_bits; a[a_val].value_len = sizeof (_bits); a_val++; break; case GNUTLS_PK_EC: p[p_val].type = CKA_SIGN; p[p_val].value = (void*)&tval; p[p_val].value_len = sizeof (tval); p_val++; a[a_val].type = CKA_VERIFY; a[a_val].value = (void*)&tval; a[a_val].value_len = sizeof (tval); a_val++; a[a_val].type = CKA_MODULUS_BITS; a[a_val].value = &_bits; a[a_val].value_len = sizeof (_bits); a_val++; break; default: ret = gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); goto cleanup; } /* a private key is set always as private unless * requested otherwise */ if (flags & GNUTLS_PKCS11_OBJ_FLAG_MARK_NOT_PRIVATE) { p[p_val].type = CKA_PRIVATE; p[p_val].value = (void*)&fval; p[p_val].value_len = sizeof(fval); p_val++; } else { p[p_val].type = CKA_PRIVATE; p[p_val].value = (void*)&tval; p[p_val].value_len = sizeof (tval); p_val++; } p[p_val].type = CKA_TOKEN; p[p_val].value = (void *)&tval; p[p_val].value_len = sizeof (tval); p_val++; if (label) { p[p_val].type = CKA_LABEL; p[p_val].value = (void*)label; p[p_val].value_len = strlen (label); p_val++; a[a_val].type = CKA_LABEL; a[a_val].value = (void*)label; a[a_val].value_len = strlen (label); a_val++; } if (flags & GNUTLS_PKCS11_OBJ_FLAG_MARK_SENSITIVE) { p[p_val].type = CKA_SENSITIVE; p[p_val].value = (void*)&tval; p[p_val].value_len = sizeof (tval); p_val++; } else { p[p_val].type = CKA_SENSITIVE; p[p_val].value = (void*)&fval; p[p_val].value_len = sizeof (fval); p_val++; } rv = pkcs11_generate_key_pair( sinfo.module, sinfo.pks, &mech, a, a_val, p, p_val, &pub, &priv); if (rv != CKR_OK) { gnutls_assert (); _gnutls_debug_log ("pkcs11: %s\n", pkcs11_strerror (rv)); ret = pkcs11_rv_to_err (rv); goto cleanup; } /* extract the public key */ if (pubkey) { ret = gnutls_pubkey_init(&pkey); if (ret < 0) { gnutls_assert (); goto cleanup; } ret = gnutls_pkcs11_obj_init(&obj); if (ret < 0) { gnutls_assert (); goto cleanup; } obj->pk_algorithm = pk; obj->type = GNUTLS_PKCS11_OBJ_PUBKEY; ret = pkcs11_read_pubkey(sinfo.module, sinfo.pks, pub, mech.mechanism, obj->pubkey); if (ret < 0) { gnutls_assert (); goto cleanup; } ret = gnutls_pubkey_import_pkcs11 (pkey, obj, 0); if (ret < 0) { gnutls_assert (); goto cleanup; } ret = gnutls_pubkey_export2 (pkey, fmt, pubkey); if (ret < 0) { gnutls_assert (); goto cleanup; } } cleanup: if (obj != NULL) gnutls_pkcs11_obj_deinit(obj); if (pkey != NULL) gnutls_pubkey_deinit(pkey); if (sinfo.pks != 0) pkcs11_close_session (&sinfo); return ret; } /** * gnutls_pkcs11_privkey_set_pin_function: * @key: The private key * @fn: the callback * @userdata: data associated with the callback * * This function will set a callback function to be used when * required to access the object. This function overrides the global * set using gnutls_pkcs11_set_pin_function(). * * Since: 3.1.0 * **/ void gnutls_pkcs11_privkey_set_pin_function (gnutls_pkcs11_privkey_t key, gnutls_pin_callback_t fn, void *userdata) { key->pin.cb = fn; key->pin.data = userdata; }