Index: aclocal/shobj.m4 ================================================================== --- aclocal/shobj.m4 +++ aclocal/shobj.m4 @@ -164,11 +164,10 @@ ;; esac ;; mingw32|mingw32msvc*) SHOBJEXT="dll" - AREXT='lib' CFLAGS="$CFLAGS -mms-bitfields" CPPFLAGS="$CPPFLAGS -mms-bitfields" SHOBJCPPFLAGS="-DPIC" SHOBJLDFLAGS='-shared -Wl,--dll -Wl,--enable-auto-image-base -Wl,--output-def,$[@].def,--out-implib,$[@].a' ;; Index: tclpkcs11.c ================================================================== --- tclpkcs11.c +++ tclpkcs11.c @@ -355,11 +355,27 @@ for (bufidx = idx = 0; (idx < datalen) && (bufidx < sizeof(buf)); idx++) { buf[bufidx++] = alphabet[(data[idx] >> 4) & 0xf]; buf[bufidx++] = alphabet[data[idx] & 0xf]; } - retval = Tcl_NewByteArrayObj((unsigned char *) buf, bufidx); + retval = Tcl_NewStringObj(buf, bufidx); + + return(retval); +} + +MODULE_SCOPE Tcl_Obj *tclpkcs11_bytearray_to_string_from_obj(Tcl_Obj *data) { + unsigned char *buf; + int buflen; + Tcl_Obj *retval; + + if (data == NULL) { + return(Tcl_NewObj()); + } + + buf = Tcl_GetByteArrayFromObj(data, &buflen); + + retval = tclpkcs11_bytearray_to_string(buf, buflen); return(retval); } MODULE_SCOPE unsigned long tclpkcs11_string_to_bytearray(Tcl_Obj *data, unsigned char *outbuf, unsigned long outbuflen) { @@ -678,15 +694,19 @@ initargs.LibraryFlags = NULL; initargs.pReserved = NULL; chk_rv = pkcs11_function_list->C_Initialize(&initargs); if (chk_rv == CKR_CANT_LOCK) { + /* + * If the PKCS#11 module rejects using our locking + * functions, retry with OS locking functions. + */ initargs.CreateMutex = NULL; initargs.DestroyMutex = NULL; initargs.LockMutex = NULL; initargs.UnlockMutex = NULL; - initargs.flags = 0; + initargs.flags = CKF_OS_LOCKING_OK; initargs.LibraryFlags = NULL; initargs.pReserved = NULL; chk_rv = pkcs11_function_list->C_Initialize(&initargs); } @@ -996,11 +1016,11 @@ {CKA_LABEL, NULL, 0}, {CKA_ID, NULL, 0}, {CKA_VALUE, NULL, 0} }, *curr_attr; CK_ULONG curr_attr_idx; - CK_OBJECT_CLASS *objectclass; + CK_OBJECT_CLASS objectclass, *objectclass_p; CK_RV chk_rv; if (!cd) { Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid clientdata", -1)); @@ -1008,10 +1028,259 @@ } if (objc != 3) { Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"pki::pkcs11::listcerts handle slot\"", -1)); + return(TCL_ERROR); + } + + tcl_handle = objv[1]; + tcl_slotid = objv[2]; + + interpdata = (struct tclpkcs11_interpdata *) cd; + + tcl_handle_entry = Tcl_FindHashEntry(&interpdata->handles, (const char *) tcl_handle); + if (!tcl_handle_entry) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid handle", -1)); + + return(TCL_ERROR); + } + + handle = (struct tclpkcs11_handle *) Tcl_GetHashValue(tcl_handle_entry); + if (!handle) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid handle", -1)); + + return(TCL_ERROR); + } + + tcl_rv = Tcl_GetLongFromObj(interp, tcl_slotid, &slotid_long); + if (tcl_rv != TCL_OK) { + return(tcl_rv); + } + + slotid = slotid_long; + + chk_rv = tclpkcs11_start_session(handle, slotid); + if (chk_rv != CKR_OK) { + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); + + return(TCL_ERROR); + } + + chk_rv = handle->pkcs11->C_FindObjectsInit(handle->session, NULL, 0); + if (chk_rv != CKR_OK) { + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); + + return(TCL_ERROR); + } + + ret_list = Tcl_NewObj(); + while (1) { + chk_rv = handle->pkcs11->C_FindObjects(handle->session, &hObject, 1, &ulObjectCount); + if (chk_rv != CKR_OK) { + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); + + handle->pkcs11->C_FindObjectsFinal(handle->session); + + return(TCL_ERROR); + } + + if (ulObjectCount == 0) { + break; + } + + if (ulObjectCount != 1) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("FindObjects() returned a weird number of objects.", -1)); + + handle->pkcs11->C_FindObjectsFinal(handle->session); + + return(TCL_ERROR); + } + + for (curr_attr_idx = 0; curr_attr_idx < (sizeof(template) / sizeof(template[0])); curr_attr_idx++) { + curr_attr = &template[curr_attr_idx]; + if (curr_attr->pValue) { + ckfree(curr_attr->pValue); + } + + curr_attr->pValue = NULL; + curr_attr->ulValueLen = 0; + } + + /* Determine size of values to allocate */ + chk_rv = handle->pkcs11->C_GetAttributeValue(handle->session, hObject, template, sizeof(template) / sizeof(template[0])); + if (chk_rv == CKR_ATTRIBUTE_TYPE_INVALID || chk_rv == CKR_ATTRIBUTE_SENSITIVE || chk_rv == CKR_BUFFER_TOO_SMALL) { + chk_rv = CKR_OK; + } + if (chk_rv != CKR_OK) { + /* Skip this object if we are not able to process it */ + continue; + } + + /* Allocate values */ + for (curr_attr_idx = 0; curr_attr_idx < (sizeof(template) / sizeof(template[0])); curr_attr_idx++) { + curr_attr = &template[curr_attr_idx]; + + if (((CK_LONG) curr_attr->ulValueLen) != ((CK_LONG) -1)) { + curr_attr->pValue = (void *) ckalloc(curr_attr->ulValueLen); + } + } + + /* Populate template values */ + chk_rv = handle->pkcs11->C_GetAttributeValue(handle->session, hObject, template, sizeof(template) / sizeof(template[0])); + if (chk_rv != CKR_OK && chk_rv != CKR_ATTRIBUTE_SENSITIVE && chk_rv != CKR_ATTRIBUTE_TYPE_INVALID && chk_rv != CKR_BUFFER_TOO_SMALL) { + /* Return an error if we are unable to process this entry due to unexpected errors */ + for (curr_attr_idx = 0; curr_attr_idx < (sizeof(template) / sizeof(template[0])); curr_attr_idx++) { + curr_attr = &template[curr_attr_idx]; + if (curr_attr->pValue) { + ckfree(curr_attr->pValue); + } + } + + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); + + handle->pkcs11->C_FindObjectsFinal(handle->session); + + return(TCL_ERROR); + } + + /* Extract certificate data */ + obj_label = NULL; + obj_id = NULL; + obj_cert = NULL; + objectclass_p = NULL; + for (curr_attr_idx = 0; curr_attr_idx < (sizeof(template) / sizeof(template[0])); curr_attr_idx++) { + curr_attr = &template[curr_attr_idx]; + + if (!curr_attr->pValue) { + continue; + } + + if (curr_attr->type != CKA_CLASS && !objectclass_p) { + ckfree(curr_attr->pValue); + curr_attr->pValue = NULL; + + continue; + } + + switch (curr_attr->type) { + case CKA_CLASS: + objectclass_p = (CK_OBJECT_CLASS *) curr_attr->pValue; + objectclass = *objectclass_p; + break; + case CKA_LABEL: + obj_label = Tcl_NewStringObj(curr_attr->pValue, curr_attr->ulValueLen); + break; + case CKA_ID: + /* Convert the ID into a readable string */ + obj_id = tclpkcs11_bytearray_to_string(curr_attr->pValue, curr_attr->ulValueLen); + + break; + case CKA_VALUE: + obj_cert = Tcl_NewByteArrayObj(curr_attr->pValue, curr_attr->ulValueLen); + + break; + } + + ckfree(curr_attr->pValue); + curr_attr->pValue = NULL; + } + + /* Add this certificate data to return list, if all found */ + if (obj_label == NULL || obj_id == NULL || obj_cert == NULL) { + continue; + } + + if (objectclass != CKO_CERTIFICATE) { + continue; + } + + /* Create the current item list */ + curr_item_list = Tcl_NewObj(); + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("pkcs11_handle", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, tcl_handle); + + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("pkcs11_slotid", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, tcl_slotid); + + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("pkcs11_id", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, obj_id); + + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("pkcs11_label", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, obj_label); + + /* Call "::pki::x509::parse_cert" to parse the cert */ + parse_cert_cmd = Tcl_NewObj(); + Tcl_ListObjAppendElement(interp, parse_cert_cmd, Tcl_NewStringObj("::pki::x509::parse_cert", -1)); + Tcl_ListObjAppendElement(interp, parse_cert_cmd, obj_cert); + + tcl_rv = Tcl_EvalObjEx(interp, parse_cert_cmd, 0); + if (tcl_rv != TCL_OK) { + continue; + } + + /* Add results of [parse_cert] to our return value */ + Tcl_ListObjAppendList(interp, curr_item_list, Tcl_GetObjResult(interp)); + + /* + * Override the "type" so that [array set] returns our new + * type, but we can still parse through the list and figure + * out the real subordinate type + */ + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("type", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("pkcs11", -1)); + + /* Add the current item to the return value list */ + Tcl_ListObjAppendElement(interp, ret_list, curr_item_list); + } + + /* Terminate search */ + handle->pkcs11->C_FindObjectsFinal(handle->session); + + /* Return */ + Tcl_SetObjResult(interp, ret_list); + + return(TCL_OK); +} + +/* XXX:TODO: Merge this with list_certs */ +MODULE_SCOPE int tclpkcs11_list_keys(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { + struct tclpkcs11_interpdata *interpdata; + struct tclpkcs11_handle *handle; + Tcl_HashEntry *tcl_handle_entry; + Tcl_Obj *tcl_handle, *tcl_slotid; + long slotid_long; + Tcl_Obj *obj_label, *obj_id, *obj_exponent, *obj_modulus; + Tcl_Obj *ret_list, *curr_item_list; + int tcl_rv; + + CK_SLOT_ID slotid; + CK_OBJECT_HANDLE hObject; + CK_ULONG ulObjectCount; + CK_ATTRIBUTE template[] = { + {CKA_CLASS, NULL, 0}, + {CKA_LABEL, NULL, 0}, + {CKA_ID, NULL, 0}, + {CKA_KEY_TYPE, NULL, 0}, + {CKA_PUBLIC_EXPONENT, NULL, 0}, + {CKA_MODULUS, NULL, 0} + }; + CK_ATTRIBUTE *curr_attr; + CK_ULONG curr_attr_idx; + CK_OBJECT_CLASS objectclass, *objectclass_p; + CK_KEY_TYPE keytype; + CK_RV chk_rv; + + if (!cd) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid clientdata", -1)); + + return(TCL_ERROR); + } + + if (objc != 3) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"pki::pkcs11::listkeys handle slot\"", -1)); + return(TCL_ERROR); } tcl_handle = objv[1]; tcl_slotid = objv[2]; @@ -1122,55 +1391,71 @@ handle->pkcs11->C_FindObjectsFinal(handle->session); return(TCL_ERROR); } - /* Extract certificate data */ + /* Extract key data */ obj_label = NULL; obj_id = NULL; - obj_cert = NULL; - objectclass = NULL; + objectclass_p = NULL; + obj_exponent = NULL; + obj_modulus = NULL; + keytype = -1; for (curr_attr_idx = 0; curr_attr_idx < (sizeof(template) / sizeof(template[0])); curr_attr_idx++) { curr_attr = &template[curr_attr_idx]; if (!curr_attr->pValue) { continue; } + + if (curr_attr->type != CKA_CLASS && !objectclass) { + ckfree(curr_attr->pValue); + curr_attr->pValue = NULL; + + continue; + } switch (curr_attr->type) { case CKA_CLASS: - objectclass = (CK_OBJECT_CLASS *) curr_attr->pValue; - - if (*objectclass != CKO_CERTIFICATE) { - continue; - } - + objectclass_p = (CK_OBJECT_CLASS *) curr_attr->pValue; + objectclass = *objectclass_p; break; case CKA_LABEL: obj_label = Tcl_NewStringObj(curr_attr->pValue, curr_attr->ulValueLen); break; case CKA_ID: /* Convert the ID into a readable string */ obj_id = tclpkcs11_bytearray_to_string(curr_attr->pValue, curr_attr->ulValueLen); break; - case CKA_VALUE: - if (!objectclass) { - break; - } + case CKA_KEY_TYPE: + /* Convert the ID into a readable string */ + keytype = *((CK_KEY_TYPE *) curr_attr->pValue); - obj_cert = Tcl_NewByteArrayObj(curr_attr->pValue, curr_attr->ulValueLen); - + break; + case CKA_MODULUS: + obj_modulus = Tcl_NewByteArrayObj(curr_attr->pValue, curr_attr->ulValueLen); + break; + case CKA_PUBLIC_EXPONENT: + obj_exponent = Tcl_NewByteArrayObj(curr_attr->pValue, curr_attr->ulValueLen); break; } ckfree(curr_attr->pValue); curr_attr->pValue = NULL; } /* Add this certificate data to return list, if all found */ - if (obj_label == NULL || obj_id == NULL || obj_cert == NULL) { + if (obj_label == NULL || obj_id == NULL || obj_exponent == NULL || obj_modulus == NULL || keytype == -1) { + continue; + } + + if (objectclass != CKO_PUBLIC_KEY) { + continue; + } + + if (keytype != CKK_RSA) { continue; } /* Create the current item list */ curr_item_list = Tcl_NewObj(); @@ -1184,22 +1469,22 @@ Tcl_ListObjAppendElement(interp, curr_item_list, obj_id); Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("pkcs11_label", -1)); Tcl_ListObjAppendElement(interp, curr_item_list, obj_label); - /* Call "::pki::x509::parse_cert" to parse the cert */ - parse_cert_cmd = Tcl_NewObj(); - Tcl_ListObjAppendElement(interp, parse_cert_cmd, Tcl_NewStringObj("::pki::x509::parse_cert", -1)); - Tcl_ListObjAppendElement(interp, parse_cert_cmd, obj_cert); - - tcl_rv = Tcl_EvalObjEx(interp, parse_cert_cmd, 0); - if (tcl_rv != TCL_OK) { - continue; - } - - /* Add results of [parse_cert] to our return value */ - Tcl_ListObjAppendList(interp, curr_item_list, Tcl_GetObjResult(interp)); + /* Add the RSA key to the list */ + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("n", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_ObjPrintf("0x%s", Tcl_GetString(tclpkcs11_bytearray_to_string_from_obj(obj_modulus)))); + + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("e", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_ObjPrintf("0x%s", Tcl_GetString(tclpkcs11_bytearray_to_string_from_obj(obj_exponent)))); + + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("l", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_ObjPrintf("%i", Tcl_GetCharLength(obj_modulus) * 8)); + + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("type", -1)); + Tcl_ListObjAppendElement(interp, curr_item_list, Tcl_NewStringObj("rsa", -1)); /* * Override the "type" so that [array set] returns our new * type, but we can still parse through the list and figure * out the real subordinate type @@ -1558,29 +1843,35 @@ template[1].pValue = &objectclass_pk; template[1].ulValueLen = sizeof(objectclass_pk); chk_rv = handle->pkcs11->C_FindObjectsInit(handle->session, template, sizeof(template) / sizeof(template[0])); if (chk_rv != CKR_OK) { + tclpkcs11_close_session(handle); + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); return(TCL_ERROR); } foundObjs = 0; chk_rv = handle->pkcs11->C_FindObjects(handle->session, &hObject, 1, &foundObjs); if (chk_rv != CKR_OK) { - Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); - handle->pkcs11->C_FindObjectsFinal(handle->session); + tclpkcs11_close_session(handle); + + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); + return(TCL_ERROR); } /* Terminate Search */ handle->pkcs11->C_FindObjectsFinal(handle->session); if (foundObjs < 1) { + tclpkcs11_close_session(handle); + Tcl_SetObjResult(interp, Tcl_NewStringObj("PKCS11_ERROR MAYBE_LOGIN", -1)); return(TCL_ERROR); } @@ -1592,10 +1883,12 @@ if (chk_rv != CKR_OK) { sign = 1; chk_rv = handle->pkcs11->C_SignInit(handle->session, &mechanism, hObject); } if (chk_rv != CKR_OK) { + tclpkcs11_close_session(handle); + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); return(TCL_ERROR); } @@ -1639,25 +1932,31 @@ handle->pkcs11->C_SignFinal(handle->session, dummybuf, &dummybuf_len); } } if (chk_rv != CKR_OK) { + tclpkcs11_close_session(handle); + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); return(TCL_ERROR); } } else { chk_rv = handle->pkcs11->C_DecryptInit(handle->session, &mechanism, hObject); if (chk_rv != CKR_OK) { + tclpkcs11_close_session(handle); + Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); return(TCL_ERROR); } resultbuf_len = sizeof(resultbuf); chk_rv = handle->pkcs11->C_Decrypt(handle->session, input, input_len, resultbuf, &resultbuf_len); if (chk_rv != CKR_OK) { + tclpkcs11_close_session(handle); + if (chk_rv == CKR_BUFFER_TOO_SMALL) { /* Terminate decryption operation */ handle->pkcs11->C_DecryptFinal(handle->session, NULL, 0); } @@ -1664,10 +1963,12 @@ Tcl_SetObjResult(interp, tclpkcs11_pkcs11_error(chk_rv)); return(TCL_ERROR); } } + + tclpkcs11_close_session(handle); tcl_result = Tcl_NewByteArrayObj(resultbuf, resultbuf_len); Tcl_SetObjResult(interp, tcl_result); @@ -1848,10 +2149,15 @@ tclCreatComm_ret = Tcl_CreateObjCommand(interp, "pki::pkcs11::listcerts", tclpkcs11_list_certs, interpdata, NULL); if (!tclCreatComm_ret) { return(TCL_ERROR); } + + tclCreatComm_ret = Tcl_CreateObjCommand(interp, "pki::pkcs11::listkeys", tclpkcs11_list_keys, interpdata, NULL); + if (!tclCreatComm_ret) { + return(TCL_ERROR); + } tclCreatComm_ret = Tcl_CreateObjCommand(interp, "pki::pkcs11::login", tclpkcs11_login, interpdata, NULL); if (!tclCreatComm_ret) { return(TCL_ERROR); } Index: test.tcl ================================================================== --- test.tcl +++ test.tcl @@ -2,10 +2,18 @@ lappend auto_path [file join [pwd] work lib] lappend auto_path [file join [pwd] lib] set pkcs11_module "/usr/local/lib/libcackey.so" +set pkcs11_module /home/rkeene/devel/cackey/libcackey.so +set pkcs11_module /home/rkeene/devel/saml-idp/archive/gcp-pkcs11.so + +if {0} { + set env(PKCS11SPY) $pkcs11_module + set env(PKCS11SPY_OUTPUT) /dev/stderr + set pkcs11_module /usr/lib/x86_64-linux-gnu/pkcs11/pkcs11-spy.so +} load ./tclpkcs11.so Tclpkcs11 set handle [pki::pkcs11::loadmodule $pkcs11_module] puts "Handle: $handle" @@ -17,20 +25,37 @@ set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { - set token_slotlabel $slotlabel - set token_slotid $slotid + if {![info exists token_slotid]} { + set token_slotlabel $slotlabel + set token_slotid $slotid + } } } if {![info exists token_slotid]} { puts stderr "Found no slots with tokens, aborting." exit 1 } + +set pubKeys [pki::pkcs11::listkeys $handle $token_slotid] +puts "Found [llength $pubKeys] keys" + +set orig "TestMsg" +foreach keyinfo_list $pubKeys { + unset -nocomplain keyinfo + array set keyinfo $keyinfo_list + puts "Key: $keyinfo(pkcs11_label)" + + set signature [pki::sign $orig $keyinfo_list sha256] + set verify [pki::verify $signature $orig $keyinfo_list] + + puts "Signature valid: $verify" +} set certs [pki::pkcs11::listcerts $handle $token_slotid] puts "Found [llength $certs] certificates" set orig "TestMsg" @@ -75,8 +100,10 @@ if {!$verify} { puts "Signature verification error!" break } + + puts "OK" } pki::pkcs11::unloadmodule $handle Index: usage.txt ================================================================== --- usage.txt +++ usage.txt @@ -1,8 +1,9 @@ ::pki::pkcs11::loadmodule -> handle ::pki::pkcs11::unloadmodule -> true/false ::pki::pkcs11::listslots -> list: slotId label flags +::pki::pkcs11::listkeys -> list: keylist ::pki::pkcs11::listcerts -> list: keylist ::pki::pkcs11::encrypt -> data ::pki::pkcs11::decrypt -> data ::pki::pkcs11::login -> true/false ::pki::pkcs11::logout -> true/false