From b00eb56b7ffe5019bb33ad399d351b90f4715132 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 3 Dec 2008 00:20:13 +0000 Subject: Add basic key object support. No support yet for actual crypto ops. Not tested. Wanted to check it in so it doesn't get lost as it represents a lot of work. --- ckcapi-cert.c | 34 +- ckcapi-cert.h | 4 +- ckcapi-key.c | 1033 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ckcapi-key.h | 33 ++ ckcapi-trust.c | 4 +- ckcapi-util.c | 17 + ckcapi-util.h | 3 + ckcapi.c | 79 ++++- ckcapi.h | 14 +- ckcapi.vcproj | 14 +- 10 files changed, 1185 insertions(+), 50 deletions(-) create mode 100644 ckcapi-key.c create mode 100644 ckcapi-key.h diff --git a/ckcapi-cert.c b/ckcapi-cert.c index 6e51f3e..6d2dd53 100644 --- a/ckcapi-cert.c +++ b/ckcapi-cert.c @@ -106,7 +106,7 @@ cert_bool_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) return CKR_ATTRIBUTE_TYPE_INVALID; }; - return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_BBOOL)); + return ckcapi_return_data(attr, &val, sizeof(CK_BBOOL)); } static CK_RV @@ -148,7 +148,7 @@ cert_ulong_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) return CKR_ATTRIBUTE_TYPE_INVALID; }; - return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_ULONG)); + return ckcapi_return_data(attr, &val, sizeof(CK_ULONG)); } static CK_RV @@ -286,12 +286,9 @@ static const CkCapiObjectVtable cert_object_vtable = { CK_RV ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) { - CK_VOID_PTR data = attr->pValue; - CK_ULONG_PTR len = &attr->ulValueLen; DWORD err; ASSERT(cert); - ASSERT(len); ASSERT(attr); switch(attr->type) @@ -325,7 +322,7 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) return ckcapi_winerr_to_ckr(GetLastError()); } - return ckcapi_return_string(data, len, utf16); + return ckcapi_return_string(attr, utf16); } break; @@ -338,7 +335,7 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) */ case CKA_ID: if(!CertGetCertificateContextProperty(cert, CERT_KEY_IDENTIFIER_PROP_ID, - data, (DWORD*)len)) + attr->pValue, (DWORD*)&attr->ulValueLen)) { err = GetLastError(); if(err == CRYPT_E_NOT_FOUND) @@ -355,8 +352,8 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) * directly. */ case CKA_SUBJECT: - return ckcapi_return_data(data, len, cert->pCertInfo->Subject.pbData, - cert->pCertInfo->Subject.cbData); + return ckcapi_return_data(attr, cert->pCertInfo->Subject.pbData, + cert->pCertInfo->Subject.cbData); /* * DER-encoding of the certificate issuer name. @@ -365,8 +362,8 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) * directly. */ case CKA_ISSUER: - return ckcapi_return_data(data, len, cert->pCertInfo->Issuer.pbData, - cert->pCertInfo->Issuer.cbData); + return ckcapi_return_data(attr, cert->pCertInfo->Issuer.pbData, + cert->pCertInfo->Issuer.cbData); /* * DER-encoding of the certificate serial number. @@ -380,7 +377,8 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) */ case CKA_SERIAL_NUMBER: if(!CryptEncodeObject(X509_ASN_ENCODING, X509_MULTI_BYTE_INTEGER, - &cert->pCertInfo->SerialNumber, data, len)) + &cert->pCertInfo->SerialNumber, + attr->pValue, (DWORD*)&attr->ulValueLen)) { err = GetLastError(); if(err == ERROR_FILE_NOT_FOUND) @@ -395,7 +393,7 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) * We use CAPI's CERT_CONTEXT pbCertEncoded field directly. */ case CKA_VALUE: - return ckcapi_return_data(data, len, cert->pbCertEncoded, + return ckcapi_return_data(attr, cert->pbCertEncoded, cert->cbCertEncoded); /* @@ -406,29 +404,27 @@ ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) * in full. */ case CKA_URL: - break; + return CKR_ATTRIBUTE_TYPE_INVALID; /* * Checksum * - TODO: Work out what to do here */ case CKA_CHECK_VALUE: - break; + return CKR_ATTRIBUTE_TYPE_INVALID; /* * TODO: Should we support these? */ case CKA_HASH_OF_SUBJECT_PUBLIC_KEY: case CKA_HASH_OF_ISSUER_PUBLIC_KEY: - break; + return CKR_ATTRIBUTE_TYPE_INVALID; /* Not supported */ default: - break; + return CKR_ATTRIBUTE_TYPE_INVALID; }; - - return CKR_ATTRIBUTE_TYPE_INVALID; } PCCERT_CONTEXT diff --git a/ckcapi-cert.h b/ckcapi-cert.h index 0a7622c..28edd9d 100644 --- a/ckcapi-cert.h +++ b/ckcapi-cert.h @@ -46,10 +46,10 @@ CK_RV ckcapi_cert_find (CkCapiSession* sess, CK_OBJECT_CLASS cls, CK_ATTRIBUTE_PTR match, CK_ULONG count, CkCapiArray* arr); - -/* Called by trust stuff */ +/* Called by trust and key stuff */ CK_RV ckcapi_cert_certificate_get_bytes (PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr); + PCCERT_CONTEXT ckcapi_cert_object_data_get_certificate (CkCapiObjectData* objdata); #endif /* CRYPTOKI_CERT_H */ diff --git a/ckcapi-key.c b/ckcapi-key.c new file mode 100644 index 0000000..1ce057c --- /dev/null +++ b/ckcapi-key.c @@ -0,0 +1,1033 @@ +/* + * Copyright (C) 2008 Stef Walter + * + * This library 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 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "ckcapi.h" +#include "ckcapi-cert.h" +#include "ckcapi-key.h" +#include "ckcapi-object.h" +#include "ckcapi-session.h" +#include "ckcapi-token.h" +#include "x509-usages.h" + +/* + * These are the attributes expected by NSS on a + * private key object: + * + * CKA_ALWAYS_AUTHENTICATE + * CKA_ALWAYS_SENSITIVE + * CKA_CLASS + * CKA_DECRYPT + * CKA_DERIVE + * CKA_END_DATE + * CKA_EXTRACTABLE + * CKA_ID + * CKA_LABEL + * CKA_LOCAL + * CKA_KEY_TYPE + * CKA_KEY_GEN_MECHANISM + * CKA_MODIFIABLE + * CKA_NEVER_EXTRACTABLE + * CKA_PRIVATE + * CKA_SENSITIVE + * CKA_SIGN + * CKA_SIGN_RECOVER + * CKA_START_DATE + * CKA_SUBJECT + * CKA_TOKEN + * CKA_UNWRAP + * CKA_UNWRAP_TEMPLATE + * CKA_ALLOWED_MECHANISMS + * CKA_WRAP_WITH_TRUSTED + * + * Additional attributes for RSA keys: + * + * CKA_COEFFICIENT + * CKA_EXPONENT_1 + * CKA_EXPONENT_2 + * CKA_MODULUS + * CKA_PRIME_1 + * CKA_PRIME_2 + * CKA_PRIVATE_EXPONENT + * CKA_PUBLIC_EXPONENT + * + * Additional attributes for DSA keys: + * + * CKA_BASE + * CKA_PRIME + * CKA_SUBPRIME + * CKA_VALUE + */ + +typedef struct _KeyObject +{ + CkCapiObject obj; + + /* The raw key identifier */ + CRYPT_HASH_BLOB key_identifier; + + /* Together these form the unique key. Must be contiguous */ + CK_OBJECT_CLASS object_class; + BYTE tail_data[1]; +} +KeyObject; + +typedef struct _KeyObjectData +{ + CkCapiObjectData base; + CK_OBJECT_CLASS object_class; + CRYPT_INTEGER_BLOB key_identifier; + CRYPT_DATA_BLOB raw_public_key; + CRYPT_KEY_PROV_INFO* prov_info; +} +KeyObjectData; + +static CK_RV +load_raw_public_key(KeyObjectData* kdata) +{ + BOOL success = FALSE; + HCRYPTKEY key; + CK_RV ret; + DWORD error; + + ASSERT(kdata); + ASSERT(!kdata->raw_public_key.pbData); + + ret = ckcapi_key_object_data_get_handles(&kdata->base, NULL, &key); + if(ret != CKR_OK) + return ret; + + if(CryptExportKey(key, 0, PUBLICKEYBLOB, 0, NULL, &kdata->raw_public_key.cbData)) + { + kdata->raw_public_key.pbData = malloc(kdata->raw_public_key.cbData); + if(!kdata->raw_public_key.pbData) + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + else + { + if(CryptExportKey(key, 0, PUBLICKEYBLOB, 0, kdata->raw_public_key.pbData, + &kdata->raw_public_key.cbData)) + { + success = TRUE; + } + } + } + + CryptDestroyKey(key); + + if(success) + { + return CKR_OK; + } + else + { + error = GetLastError(); + if(error == NTE_BAD_KEY_STATE) + return CKR_ATTRIBUTE_SENSITIVE; + return ckcapi_winerr_to_ckr(error); + } +} + +static CK_RV +lookup_rsa_attribute(KeyObjectData* kdata, CK_ATTRIBUTE_PTR attr) +{ + PUBLICKEYSTRUC* header; + RSAPUBKEY* pubkey; + CK_ULONG number; + CK_RV ret; + + ASSERT(kdata); + ASSERT(attr); + + if(!kdata->raw_public_key.pbData) + { + ret = load_raw_public_key(kdata); + if(ret != CKR_OK) + return ret; + } + + header = (PUBLICKEYSTRUC*)kdata->raw_public_key.pbData; + if(!header->bType == PUBLICKEYBLOB) + return CKR_GENERAL_ERROR; + + pubkey = (RSAPUBKEY*)(header + 1); + if(!pubkey->magic == 0x31415352) + return CKR_GENERAL_ERROR; + + switch(attr->type) + { + case CKA_MODULUS_BITS: + number = pubkey->bitlen; + return ckcapi_return_data(attr, &number, sizeof(CK_ULONG)); + + case CKA_PUBLIC_EXPONENT: + return ckcapi_return_dword_as_bytes(attr, pubkey->pubexp); + + case CKA_MODULUS: + return ckcapi_return_reversed_data(attr, (pubkey + 1), + pubkey->bitlen / 8); + + case CKA_PRIVATE_EXPONENT: + case CKA_PRIME_1: + case CKA_PRIME_2: + case CKA_EXPONENT_1: + case CKA_EXPONENT_2: + case CKA_COEFFICIENT: + if(kdata->object_class == CKO_PRIVATE_KEY) + return CKR_ATTRIBUTE_SENSITIVE; + else + return CKR_ATTRIBUTE_TYPE_INVALID; + + default: + ASSERT(FALSE); + return CKR_ATTRIBUTE_TYPE_INVALID; + } +} + +static CK_RV +key_bool_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) +{ + KeyObjectData* kdata = (KeyObjectData*)objdata; + CK_BBOOL val; + CK_BBOOL is_private; + + ASSERT(objdata); + ASSERT(attr); + + is_private = (kdata->object_class == CKO_PRIVATE_KEY); + + switch(attr->type) + { + + /* + * Whether to authenticate before every use. + * - CAPI does all authentication + */ + case CKA_ALWAYS_AUTHENTICATE: + val = CK_FALSE; + break; + + /* + * Whether this key has always been sensitive. + * TODO: Can we detect this? + */ + case CKA_ALWAYS_SENSITIVE: + val = CK_FALSE; + break; + + /* + * Whether this key can be used to decrypt. + * - CKK_RSA but not CKK_DSA. + */ + case CKA_DECRYPT: + val = CK_TRUE; + break; + + /* + * Whether this key can be used to derive a session or + * other key. + * - Not true for CKK_RSA or CKK_DSA. + */ + case CKA_DERIVE: + val = CK_FALSE; + break; + + /* + * Whether this key can be exported or not. + * TODO: We may want to support this for public keys. + */ + case CKA_EXTRACTABLE: + val = CK_FALSE; + break; + + /* + * Whether this key was created on token. + * TODO: Can we implement this properly? + */ + case CKA_LOCAL: + val = CK_FALSE; + break; + + /* + * Whether this object is modifiable. + * - Keys are generally. never modifiable. + */ + case CKA_MODIFIABLE: + val = CK_FALSE; + break; + + /* + * Whether this key was ever extractable. + * TODO: Can we determine this? + */ + case CKA_NEVER_EXTRACTABLE: + val = CK_FALSE; + break; + + /* + * Whether this is a private object or not. + * - This 'private' means login before use. But maps + * well to private key use, since we're always logged in. + */ + case CKA_PRIVATE: + val = is_private; + break; + + /* + * Whether this is a sensitive object or not. + * - Private keys are sensitive, some attributes not + * readable. + */ + case CKA_SENSITIVE: + val = is_private; + break; + + /* + * Can this key sign stuff? + * - Private keys can sign. + */ + case CKA_SIGN: + val = is_private; + break; + + /* + * Can this key sign recoverable. + * - Private RSA keys can sign recoverable. + * TODO: When implementing DSA more logic needed. + */ + case CKA_SIGN_RECOVER: + val = is_private; + break; + + /* + * Is this stored on the token? + * - All CAPI objects are. + */ + case CKA_TOKEN: + val = CK_TRUE; + break; + + /* + * Can do a unwrap operation? + * - We don't implement this. + */ + case CKA_UNWRAP: + val = CK_FALSE; + break; + + /* + * Wrap, and unwrap stuff. + * - We don't implement this. + */ + case CKA_UNWRAP_TEMPLATE: + case CKA_WRAP_WITH_TRUSTED: + return CKR_ATTRIBUTE_TYPE_INVALID; + + + default: + return CKR_ATTRIBUTE_TYPE_INVALID; + }; + + return ckcapi_return_data(attr, &val, sizeof(CK_BBOOL)); +} + +static CK_RV +key_ulong_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) +{ + KeyObjectData* kdata = (KeyObjectData*)objdata; + CK_ULONG val; + + ASSERT(kdata); + ASSERT(attr); + + switch(attr->type) + { + + /* + * Object class. + */ + case CKA_CLASS: + val = kdata->object_class; + break; + + /* + * The key type. + * - Right now we only support (and load) RSA. + */ + case CKA_KEY_TYPE: + if(kdata->prov_info->dwProvType == PROV_RSA_FULL) + val = CKK_RSA; + else + val = CK_UNAVAILABLE_INFORMATION; + break; + + /* + * The key generation mechanism. + * TODO: We don't yet support key generation. + */ + case CKA_KEY_GEN_MECHANISM: + val = CK_UNAVAILABLE_INFORMATION; + break; + + /* + * The RSA modulus bits. + */ + case CKA_MODULUS_BITS: + if(kdata->prov_info->dwProvType == PROV_RSA_FULL) + return lookup_rsa_attribute(kdata, attr); + else + return CKR_ATTRIBUTE_TYPE_INVALID; + + default: + return CKR_ATTRIBUTE_TYPE_INVALID; + }; + + return ckcapi_return_data(attr, &val, sizeof(CK_ULONG)); +} + +static CK_RV +key_bytes_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) +{ + KeyObjectData* kdata = (KeyObjectData*)objdata; + CK_MECHANISM_TYPE allowed_mechanisms[] = { CKM_RSA_PKCS }; + WCHAR* label; + + ASSERT(kdata); + ASSERT(attr); + + switch(attr->type) + { + /* + * The ID of the key. This should match the ID we + * return for any matching certificates. + */ + case CKA_ID: + return ckcapi_return_data(attr, kdata->key_identifier.pbData, + kdata->key_identifier.cbData); + + /* + * The key label. + * - We use the container name. + */ + case CKA_LABEL: + label = kdata->prov_info->pwszContainerName; + if(!label) + label = L"Unnamed Key"; + return ckcapi_return_string(attr, label); + + /* + * The subject of the related certificate. + * TODO: Implement this lookup. + */ + case CKA_SUBJECT: + return ckcapi_return_data(attr, "", 0); + + /* + * Allowed mechanisms with this key. + * - RSA used with CKM_RSA + * TODO: Needs updating when DSA implemented. + */ + case CKA_ALLOWED_MECHANISMS: + return ckcapi_return_data(attr, &allowed_mechanisms, + sizeof(allowed_mechanisms)); + + /* + * Various RSA public attributes. + */ + case CKA_MODULUS: + case CKA_PUBLIC_EXPONENT: + case CKA_PRIVATE_EXPONENT: + case CKA_PRIME_1: + case CKA_PRIME_2: + case CKA_EXPONENT_1: + case CKA_EXPONENT_2: + case CKA_COEFFICIENT: + if(kdata->prov_info->dwProvType = PROV_RSA_FULL) + return lookup_rsa_attribute(kdata, attr); + else + return CKR_ATTRIBUTE_TYPE_INVALID; + + default: + return CKR_ATTRIBUTE_TYPE_INVALID; + }; +} + +static CK_RV +key_date_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) +{ + switch(attr->type) + { + /* + * Last date this key can be used. + * TODO: Does CAPI support this ability? + */ + case CKA_END_DATE: + case CKA_START_DATE: + return CKR_ATTRIBUTE_TYPE_INVALID; + + default: + return CKR_ATTRIBUTE_TYPE_INVALID; + }; +} + +static void +key_release(void* data) +{ + KeyObjectData* kdata = (KeyObjectData*)data; + ASSERT(kdata); + + ASSERT(kdata->key_identifier.pbData); + ASSERT(kdata->prov_info); + + free(kdata->key_identifier.pbData); + free(kdata->prov_info); + + free(kdata); +} + +static const CkCapiObjectDataVtable key_objdata_vtable = { + key_bool_attribute, + key_ulong_attribute, + key_bytes_attribute, + key_date_attribute, + key_release, +}; + +static CRYPT_KEY_PROV_INFO* +duplicate_prov_info(CRYPT_KEY_PROV_INFO* original) +{ + DWORD container_length, prov_length; + CRYPT_KEY_PROV_INFO* result; + DWORD length, i; + BYTE* at; + BYTE* end; + + if(!original) + return NULL; + + /* Go through and calculate the length */ + length = sizeof(CRYPT_KEY_PROV_INFO); + if(original->pwszContainerName) + { + container_length = (wcslen(original->pwszContainerName) + 1) * sizeof(WCHAR); + length += container_length; + } + + if(original->pwszProvName) + { + prov_length = (wcslen(original->pwszProvName) + 1) * sizeof(WCHAR); + length += prov_length; + } + + length = sizeof(CRYPT_KEY_PROV_PARAM) * original->cProvParam; + for(i = 0; i < original->cProvParam; ++i) + length += original->rgProvParam[i].cbData; + + /* Allocate a single block of memory for everything */ + at = (BYTE*)malloc(length); + if(!at) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return NULL; + } + + /* Copy in very carefully */ + end = at + length; + + memcpy(at, original, sizeof(CRYPT_KEY_PROV_INFO)); + result = (CRYPT_KEY_PROV_INFO*)at; + at += sizeof(CRYPT_KEY_PROV_INFO); + + if(result->pwszContainerName) + { + memcpy(at, result->pwszContainerName, container_length); + result->pwszContainerName = (LPWSTR)at; + at += container_length; + } + + if(result->pwszProvName) + { + memcpy(at, result->pwszProvName, prov_length); + result->pwszProvName = (LPWSTR)at; + at += prov_length; + } + + if(original->cProvParam) + { + memcpy(at, result->rgProvParam, sizeof(CRYPT_KEY_PROV_PARAM) * result->cProvParam); + result->rgProvParam = (CRYPT_KEY_PROV_PARAM*)at; + at += sizeof(CRYPT_KEY_PROV_PARAM) * result->cProvParam; + + for(i = 0; i < result->cProvParam; ++i) + { + memcpy(at, result->rgProvParam[i].pbData, result->rgProvParam[i].cbData); + result->rgProvParam[i].pbData = (BYTE*)at; + at += result->rgProvParam[i].cbData; + } + } + + ASSERT(at == end); + return result; +} + +static CkCapiObjectData* +key_alloc_data(CkCapiSession* sess, CkCapiObject* obj, CRYPT_KEY_PROV_INFO* prov_info) +{ + KeyObject* kobj = (KeyObject*)obj; + KeyObjectData* kdata; + + kdata = (KeyObjectData*)calloc(1, sizeof(KeyObjectData)); + if(!kdata) + return NULL; + + /* Allocate memory for key identifier */ + kdata->key_identifier.pbData = malloc(kobj->key_identifier.cbData); + if(!kdata->key_identifier.pbData) + { + free(kdata); + return NULL; + } + + /* Setup the object data */ + kdata->object_class = kobj->object_class; + kdata->prov_info = prov_info; + kdata->key_identifier.cbData = kobj->key_identifier.cbData; + memcpy(kdata->key_identifier.pbData, kobj->key_identifier.pbData, + kdata->key_identifier.cbData); + kdata->raw_public_key.pbData = NULL; + kdata->raw_public_key.cbData = 0; + + kdata->base.object = obj->id; + kdata->base.data_funcs = &key_objdata_vtable; + + return &(kdata->base); +} + +static BOOL WINAPI +load_key_property_info(PCRYPT_HASH_BLOB key_identifier, DWORD flags, + void* reserved, void* arg, DWORD n_props, DWORD* props, + void** datas, DWORD* n_datas) +{ + CRYPT_KEY_PROV_INFO** prov_info = (CRYPT_KEY_PROV_INFO**)arg; + DWORD i; + + /* + * Already got a provider info. This shouldn't happen + * but can occur if the same key is present twice. + */ + if(*prov_info) + return TRUE; + + /* Find the key provider info property */ + for(i = 0; i < n_props; ++i) + { + if(props[i] == CERT_KEY_PROV_INFO_PROP_ID) + { + *prov_info = duplicate_prov_info((CRYPT_KEY_PROV_INFO*)datas[i]); + break; + } + } + + return TRUE; +} + +static CK_RV +key_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData** objdata) +{ + KeyObject* kobj = (KeyObject*)obj; + CRYPT_KEY_PROV_INFO* prov_info = NULL; + + ASSERT(kobj); + ASSERT(objdata); + + /* Load the provider info */ + if(!CryptEnumKeyIdentifierProperties((CRYPT_HASH_BLOB*)&kobj->key_identifier, + CERT_KEY_PROV_INFO_PROP_ID, 0, NULL, NULL, + &prov_info, load_key_property_info)) + return ckcapi_winerr_to_ckr(GetLastError()); + + /* No provider info, bad news */ + if(!prov_info) + return CKR_GENERAL_ERROR; + + *objdata = key_alloc_data(sess, obj, prov_info); + if(!(*objdata)) + { + free(prov_info); + return CKR_HOST_MEMORY; + } + + return CKR_OK; +} + +static void +key_object_release(void* data) +{ + KeyObject* kobj = (KeyObject*)data; + ASSERT(kobj); + free(kobj); +} + +static const CkCapiObjectVtable key_object_vtable = { + key_load_data, + key_object_release, +}; + +static CK_RV +register_key_object(CkCapiSession* sess, CK_OBJECT_CLASS cls, + CRYPT_HASH_BLOB* key_identifier, CkCapiObject** obj) +{ + KeyObject* kobj; + CK_RV ret; + + ASSERT(obj); + ASSERT(key_identifier); + ASSERT(cls == CKO_PRIVATE_KEY || cls == CKO_PUBLIC_KEY); + + kobj = calloc(sizeof(KeyObject) + key_identifier->cbData, 1); + if(!kobj) + return CKR_HOST_MEMORY; + + kobj->obj.id = 0; + kobj->obj.obj_funcs = &key_object_vtable; + kobj->obj.unique_key = UNIQUE_KEY_AT(kobj, object_class); + kobj->obj.unique_len = UNIQUE_KEY_VAR_LEN(kobj, object_class, tail_data, + key_identifier->cbData); + + kobj->object_class = cls; + kobj->key_identifier.pbData = kobj->tail_data; + kobj->key_identifier.cbData = key_identifier->cbData; + memcpy(kobj->key_identifier.pbData, key_identifier->pbData, + kobj->key_identifier.cbData); + + ret = ckcapi_token_register_object(sess->slot, &(kobj->obj)); + if(ret != CKR_OK) + { + free(kobj); + return ret; + } + + ASSERT(kobj->obj.id != 0); + *obj = &(kobj->obj); + + return CKR_OK; +} + +typedef struct _EnumArguments +{ + CkCapiSession* sess; + CK_OBJECT_CLASS object_class; + CK_ATTRIBUTE_PTR match; + CK_ULONG count; + CkCapiArray* results; + CK_RV ret; +} +EnumArguments; + +static BOOL WINAPI +enum_key_property_info(PCRYPT_HASH_BLOB key_identifier, DWORD flags, + void* reserved, void* arg, DWORD n_props, DWORD* props, + void** datas, DWORD* n_datas) +{ + EnumArguments* args = (EnumArguments*)arg; + CRYPT_KEY_PROV_INFO* prov_info = NULL; + CkCapiObject *obj = NULL; + KeyObjectData kdata; + DWORD i; + + /* Find the key provider info property */ + for(i = 0; i < n_props; ++i) + { + if(props[i] == CERT_KEY_PROV_INFO_PROP_ID) + { + prov_info = (CRYPT_KEY_PROV_INFO*)datas[i]; + break; + } + } + + /* Strange key, skip */ + if(!prov_info) + return TRUE; + + /* Match the public key */ + kdata.prov_info = prov_info; + kdata.object_class = args->object_class; + kdata.base.object = 0; + kdata.base.data_funcs = &key_objdata_vtable; + + if(ckcapi_object_data_match(&kdata.base, args->match, args->count)) + { + args->ret = register_key_object(args->sess, args->object_class, key_identifier, &obj); + if(args->ret == CKR_OK) + { + ASSERT(obj); + ckcapi_array_append(args->results, obj->id); + } + } + + return TRUE; + +} + +static CK_RV +find_any_keys(CkCapiSession* sess, CK_OBJECT_CLASS cls, + CK_ATTRIBUTE_PTR match, CK_ULONG count, CkCapiArray* arr) +{ + CRYPT_HASH_BLOB find_id; + EnumArguments enum_args; + CK_ULONG i; + + /* Try to setup for an efficient search based on key id */ + memset(&find_id, 0, sizeof(find_id)); + for(i = 0; i < count; ++i) + { + if(!match[i].pValue || !match[i].ulValueLen) + continue; + if(match[i].type == CKA_ID) + { + find_id.cbData = match[i].ulValueLen; + find_id.pbData = match[i].pValue; + } + } + + enum_args.sess = sess; + enum_args.match = match; + enum_args.count = count; + enum_args.results = arr; + enum_args.object_class = cls; + enum_args.ret = CKR_OK; + + if(!CryptEnumKeyIdentifierProperties(find_id.cbData != 0 ? &find_id : NULL, + CERT_KEY_PROV_INFO_PROP_ID, 0, NULL, NULL, + &enum_args, enum_key_property_info)) + return ckcapi_winerr_to_ckr(GetLastError()); + + return enum_args.ret; +} + +static CK_RV +list_matching_certificates(CkCapiSession* sess, CK_ATTRIBUTE_PTR match, + CK_ULONG count, CkCapiArray* arr) +{ + CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; + CK_ATTRIBUTE search[3]; + CK_ULONG n_search = 0; + CK_ULONG i; + + /* The class */ + search[0].type = CKA_CLASS; + search[0].pValue = &cert_class; + search[0].ulValueLen = sizeof(CK_OBJECT_CLASS); + ++n_search; + + for(i = 0; i < count && n_search < 3; ++i) + { + /* + * This is the attributes that tie a certificate + * to key object, so try match certs with these + */ + if(match[i].type == CKA_ID) + { + search[n_search].type = match[i].type; + search[n_search].pValue = match[i].pValue; + search[n_search].ulValueLen = match[i].ulValueLen; + ++n_search; + } + } + + /* Do the certificate search */ + return ckcapi_cert_find(sess, CKO_CERTIFICATE, search, n_search, arr); +} + +static CK_RV +find_certificate_key(CkCapiSession* session, CK_OBJECT_CLASS cls, + CK_ATTRIBUTE_PTR match, CK_ULONG count, + PCCERT_CONTEXT cert, CkCapiArray* arr) +{ + CRYPT_KEY_PROV_INFO* prov_info; + CRYPT_HASH_BLOB key_identifier; + CkCapiObjectData* objdata; + KeyObjectData kdata; + CkCapiObject* obj; + DWORD prov_length; + CK_RV ret; + + /* Look up the key provider info and identifier */ + if(!CertGetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, NULL, &prov_length) || + !CertGetCertificateContextProperty(cert, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &key_identifier.cbData)) + return ckcapi_winerr_to_ckr(GetLastError()); + + /* We own the info memory */ + prov_info = malloc(prov_length); + if(!prov_info) + return CKR_HOST_MEMORY; + key_identifier.pbData = malloc(key_identifier.cbData); + if(!key_identifier.pbData) + { + free(prov_info); + return CKR_HOST_MEMORY; + } + + /* Lookup the key provider info and identifier */ + if(CertGetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, prov_info, &prov_length) && + CertGetCertificateContextProperty(cert, CERT_KEY_IDENTIFIER_PROP_ID, key_identifier.pbData, &key_identifier.cbData)) + { + kdata.object_class = cls; + kdata.prov_info = prov_info; + kdata.key_identifier = key_identifier; + kdata.base.object = 0; + kdata.base.data_funcs = &key_objdata_vtable; + + if(ckcapi_object_data_match(&kdata.base, match, count)) + { + ret = register_key_object(session, cls, &key_identifier, &obj); + if(ret == CKR_OK) + { + ASSERT(obj); + + /* Store away the object data for performance reasons */ + objdata = key_alloc_data(session, obj, prov_info); + if(objdata) + { + ckcapi_session_take_object_data(session, obj, objdata); + + /* Note these are used, and not to be freed */ + key_identifier.pbData = NULL; + key_identifier.cbData = 0; + prov_info = NULL; + } + + ckcapi_array_append(arr, obj->id); + } + } + } + + if(key_identifier.pbData) + free(key_identifier.pbData); + if(prov_info) + free(prov_info); + + return ret; +} + +static CK_RV +find_certificate_keys(CkCapiSession* session, CK_OBJECT_CLASS cls, + CK_ATTRIBUTE_PTR match, CK_ULONG count, CkCapiArray* arr) +{ + CK_OBJECT_HANDLE id; + CkCapiObjectData* certdata; + CkCapiArray* certarr; + PCCERT_CONTEXT cert; + CK_RV ret = CKR_OK; + CK_ULONG i; + + /* Get a list of all certificates */ + certarr = ckcapi_array_new(0, 1, sizeof(CK_OBJECT_HANDLE)); + if(!certarr) + return CKR_HOST_MEMORY; + ret = list_matching_certificates(session, match, count, certarr); + + /* Now match each of them against our criteria */ + if(ret == CKR_OK) + { + for(i = 0; i < certarr->len; ++i) + { + id = ckcapi_array_index(certarr, CK_OBJECT_HANDLE, i); + ASSERT(id); + + /* Get the certificate data for this certificate object */ + if(ckcapi_session_get_object_data_for(session, id, &certdata) != CKR_OK) + continue; + + /* Get the certificate context */ + cert = ckcapi_cert_object_data_get_certificate(certdata); + if(!cert) + continue; + + /* Remember we can have either or both keys for each certificate */ + ret = find_certificate_key(session, cls, match, count, cert, arr); + } + } + + ckcapi_array_free(certarr, TRUE); + return ret; +} + +CK_RV +ckcapi_key_find(CkCapiSession* sess, CK_OBJECT_CLASS cls, + CK_ATTRIBUTE_PTR match, CK_ULONG count, CkCapiArray* arr) +{ + CK_RV ret = CKR_OK; + + /* Is this somewhere we have all keys present? */ + if(ckcapi_token_get_flags(sess->slot) & CKCAPI_SLOT_ANYKEY) + { + if((cls == CKO_PRIVATE_KEY || cls == CKO_ANY) && ret == CKR_OK) + ret = find_any_keys(sess, CKO_PRIVATE_KEY, match, count, arr); + if((cls == CKO_PUBLIC_KEY || cls == CKO_ANY) && ret == CKR_OK) + ret = find_any_keys(sess, CKO_PUBLIC_KEY, match, count, arr); + } + + /* Otherwise we can only list the keys that have certificates */ + else + { + if((cls == CKO_PRIVATE_KEY || cls == CKO_ANY) && ret == CKR_OK) + ret = find_certificate_keys(sess, CKO_PRIVATE_KEY, match, count, arr); + if((cls == CKO_PUBLIC_KEY || cls == CKO_ANY) && ret == CKR_OK) + ret = find_certificate_keys(sess, CKO_PUBLIC_KEY, match, count, arr); + } + + return ret; +} + +CK_RV +ckcapi_key_object_data_get_handles (CkCapiObjectData* objdata, HCRYPTPROV* ret_prov, + HCRYPTKEY* ret_key) +{ + KeyObjectData* kdata = (KeyObjectData*)objdata; + HCRYPTPROV prov; + HCRYPTKEY key; + DWORD error; + + ASSERT(kdata); + + if(!CryptAcquireContextW(&prov, kdata->prov_info->pwszContainerName, + kdata->prov_info->pwszProvName, + kdata->prov_info->dwProvType, 0)) + { + return ckcapi_winerr_to_ckr(GetLastError()); + } + + if(!CryptGetUserKey(prov, kdata->prov_info->dwKeySpec, &key)) + { + error = GetLastError(); + CryptReleaseContext(prov, 0); + return ckcapi_winerr_to_ckr(error); + } + + if(ret_key) + *ret_key = key; + else + CryptDestroyKey(key); + + if(ret_prov) + *ret_prov = prov; + else + CryptReleaseContext(prov, 0); + + return CKR_OK; +} diff --git a/ckcapi-key.h b/ckcapi-key.h new file mode 100644 index 0000000..d09b30c --- /dev/null +++ b/ckcapi-key.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008 Stef Walter + * + * This library 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 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef CKCAPI_KEY_H +#define CKCAPI_KEY_H + +#include "ckcapi.h" + +/* Find key objects matching criteria */ +CK_RV ckcapi_key_find (CkCapiSession* sess, CK_OBJECT_CLASS cls, + CK_ATTRIBUTE_PTR match, CK_ULONG count, + CkCapiArray* arr); + +CK_RV ckcapi_key_object_data_get_handles (CkCapiObjectData* objdata, HCRYPTPROV* prov, + HCRYPTKEY* key); + +#endif /* CRYPTOKI_KEY_H */ diff --git a/ckcapi-trust.c b/ckcapi-trust.c index 98c0046..6107ac5 100644 --- a/ckcapi-trust.c +++ b/ckcapi-trust.c @@ -141,7 +141,7 @@ trust_bool_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) return CKR_ATTRIBUTE_TYPE_INVALID; }; - return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_BBOOL)); + return ckcapi_return_data(attr, &val, sizeof(CK_BBOOL)); } static CK_RV @@ -221,7 +221,7 @@ trust_ulong_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) return CKR_ATTRIBUTE_TYPE_INVALID; }; - return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_ULONG)); + return ckcapi_return_data(attr, &val, sizeof(CK_ULONG)); } static CK_RV diff --git a/ckcapi-util.c b/ckcapi-util.c index 5c0ab9b..3bd18a5 100644 --- a/ckcapi-util.c +++ b/ckcapi-util.c @@ -23,6 +23,23 @@ #include #include + +void +ckcapi_reverse_memory (void* data, size_t length) +{ + size_t end = length - 1; + size_t middle = length / 2; + unsigned char* buf = data; + size_t i; + + for (i = 0; i < middle; i++) + { + unsigned char tmp = buf[i]; + buf[i] = buf[end - i]; + buf[end - i] = tmp; + } +} + /* * Array code originially from Glib. * Modified extensively by Stef Walter diff --git a/ckcapi-util.h b/ckcapi-util.h index 1ea07f2..6ae74a0 100644 --- a/ckcapi-util.h +++ b/ckcapi-util.h @@ -22,6 +22,9 @@ #include + +void ckcapi_reverse_memory (void* data, size_t length); + /* -------------------------------------------------------------------------------- * ARRAYS */ diff --git a/ckcapi.c b/ckcapi.c index 9ea7815..75c133a 100644 --- a/ckcapi.c +++ b/ckcapi.c @@ -113,6 +113,7 @@ ckcapi_winerr_to_ckr(DWORD werr) switch(werr) { case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: return CKR_HOST_MEMORY; break; case NTE_NO_MEMORY: @@ -124,45 +125,42 @@ ckcapi_winerr_to_ckr(DWORD werr) case ERROR_INVALID_HANDLE: /* inputs, so if they are bad, the input */ case NTE_BAD_ALGID: /* data is bad */ case NTE_BAD_HASH: + case NTE_BAD_TYPE: + case NTE_BAD_PUBLIC_KEY: return CKR_DATA_INVALID; - break; case ERROR_BUSY: case NTE_FAIL: case NTE_BAD_UID: return CKR_DEVICE_ERROR; - break; default: return CKR_GENERAL_ERROR; }; } - CK_RV -ckcapi_return_data(CK_VOID_PTR dst, CK_ULONG_PTR dlen, - CK_VOID_PTR src, DWORD slen) +ckcapi_return_data(CK_ATTRIBUTE_PTR attr, CK_VOID_PTR src, DWORD slen) { /* Just asking for the length */ - if(!dst) + if(!attr->pValue) { - *dlen = slen; + attr->ulValueLen = slen; return CKR_OK; } /* Buffer is too short */ - if(slen > *dlen) + if(slen > attr->ulValueLen) { - *dlen = slen; + attr->ulValueLen = slen; return CKR_BUFFER_TOO_SMALL; } - *dlen = slen; - memcpy(dst, src, slen); + attr->ulValueLen = slen; + memcpy(attr->pValue, src, slen); return CKR_OK; } CK_RV -ckcapi_return_string(CK_VOID_PTR dst, CK_ULONG_PTR dlen, - WCHAR* string) +ckcapi_return_string(CK_ATTRIBUTE_PTR attr, WCHAR* string) { DWORD error; int result; @@ -175,11 +173,11 @@ ckcapi_return_string(CK_VOID_PTR dst, CK_ULONG_PTR dlen, * case this part. */ if(!string[0]) - return ckcapi_return_data(dst, dlen, "\0", 1); + return ckcapi_return_data(attr, "\0", 1); result = WideCharToMultiByte(CP_UTF8, 0, string, -1, - dst, dst ? *dlen : 0, NULL, NULL); - + attr->pValue, attr->pValue ? attr->ulValueLen : 0, + NULL, NULL); /* An error result somehow */ if(!result) @@ -193,7 +191,7 @@ ckcapi_return_string(CK_VOID_PTR dst, CK_ULONG_PTR dlen, NULL, 0, NULL, NULL); if(!result) return CKR_GENERAL_ERROR; - *dlen = result; + attr->ulValueLen = result; return CKR_BUFFER_TOO_SMALL; /* A strange zero length success */ @@ -206,7 +204,52 @@ ckcapi_return_string(CK_VOID_PTR dst, CK_ULONG_PTR dlen, } } - *dlen = result; + attr->ulValueLen = result; + return CKR_OK; +} + +CK_RV +ckcapi_return_dword_as_bytes(CK_ATTRIBUTE_PTR attr, DWORD value) +{ + int i; + CK_ULONG count = 0; + BOOL first = TRUE; + BYTE* at = attr->pValue; + CK_RV ret = CKR_OK; + + for(i = 0; i < sizeof(DWORD); i++) + { + BYTE digit = (BYTE)((value >> (((sizeof(DWORD)-1)*8))) & 0xFF); + value = value << 8; + + /* No leading zero */ + if (first && digit == 0) + continue; + + first = FALSE; + if(at) + { + if(count > attr->ulValueLen) + ret = CKR_BUFFER_TOO_SMALL; + else + *(at++) = digit; + } + + count++; + } + + attr->ulValueLen = count; + return ret; +} + +CK_RV +ckcapi_return_reversed_data(CK_ATTRIBUTE_PTR attr, CK_VOID_PTR data, CK_ULONG length) +{ + CK_RV ret = ckcapi_return_data(attr, data, length); + if(ret != CKR_OK || !attr->pValue) + return ret; + + ckcapi_reverse_memory(attr->pValue, attr->ulValueLen); return CKR_OK; } diff --git a/ckcapi.h b/ckcapi.h index 707d7f8..18f304f 100644 --- a/ckcapi.h +++ b/ckcapi.h @@ -88,16 +88,22 @@ CK_RV ckcapi_winerr_to_ckr (DWORD werr); * PKCS#11 codes when the buffer is too short, or the caller * just wants to know the length, etc. */ -CK_RV ckcapi_return_data (CK_VOID_PTR dst, CK_ULONG_PTR dlen, - CK_VOID_PTR src, DWORD slen); +CK_RV ckcapi_return_data (CK_ATTRIBUTE_PTR attr, + CK_VOID_PTR src, DWORD slen); /* * This stores a string in the output buffer with appropriate * PKCS#11 codes when the buffer is too short, or the caller * just wants to know the length, etc. */ -CK_RV ckcapi_return_string (CK_VOID_PTR dst, CK_ULONG_PTR dlen, - WCHAR* string); +CK_RV ckcapi_return_string (CK_ATTRIBUTE_PTR attr, + WCHAR* string); + +CK_RV ckcapi_return_dword_as_bytes (CK_ATTRIBUTE_PTR attr, + DWORD value); + +CK_RV ckcapi_return_reversed_data (CK_ATTRIBUTE_PTR attr, + CK_VOID_PTR data, CK_ULONG length); /* ------------------------------------------------------------------ */ diff --git a/ckcapi.vcproj b/ckcapi.vcproj index c2a77bb..5abc307 100644 --- a/ckcapi.vcproj +++ b/ckcapi.vcproj @@ -73,7 +73,7 @@ /> + + @@ -380,6 +384,10 @@ RelativePath=".\ckcapi-cert.h" > + + @@ -404,10 +412,6 @@ RelativePath="ckcapi.h" > - - -- cgit v1.2.3