/** * Copyright (C) 2021 Graham Leggett * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * redwax_p11kit - PKCS11 routines for munching certificates * * * TODO: * * - There is a lot of information in certificates that are duplicated in * keys, and vice versa. We allow certs to be added before keys, keys to * be added before certs, make sure that sanity prevails when doing this. * * - Support PINs being read from a PINfile. */ #include #include #include #include "config.h" #include "redwax-tool.h" #include "redwax_util.h" #if HAVE_LIBGEN_H #include #endif #include #if HAVE_P11_KIT_MODULES_LOAD_AND_INITIALIZE #include #include #include #ifndef CKA_PUBLIC_KEY_INFO #define CKA_PUBLIC_KEY_INFO 0x00000129UL #endif #define MODULE_EXT ".so" /* values for CKA_CERTIFICATE_CATEGORY v2.20 */ #ifndef CK_CERTIFICATE_CATEGORY typedef CK_ULONG CK_CERTIFICATE_CATEGORY; #define CK_CERTIFICATE_CATEGORY_UNSPECIFIED 0UL #define CK_CERTIFICATE_CATEGORY_TOKEN_USER 1UL #define CK_CERTIFICATE_CATEGORY_AUTHORITY 2UL #define CK_CERTIFICATE_CATEGORY_OTHER_ENTITY 3UL #endif typedef struct redwax_pkcs11_session_t { CK_FUNCTION_LIST *module; CK_SESSION_HANDLE session; } redwax_pkcs11_session_t; CK_FUNCTION_LIST **global_modules; module p11kit_module; static const char *pkcs11_errstr(CK_RV rv) { switch (rv) { case CKR_OK: /* 0x00000000UL */ return "CKR_OK"; case CKR_CANCEL: /* 0x00000001UL */ return "CKR_CANCEL"; case CKR_HOST_MEMORY: /* 0x00000002UL */ return "CKR_HOST_MEMORY"; case CKR_SLOT_ID_INVALID: /* 0x00000003UL */ return "CKR_SLOT_ID_INVALID"; case CKR_GENERAL_ERROR: /* 0x00000005UL */ return "CKR_GENERAL_ERROR"; case CKR_FUNCTION_FAILED: /* 0x00000006UL */ return "CKR_FUNCTION_FAILED"; case CKR_ARGUMENTS_BAD: /* 0x00000007UL */ return "CKR_ARGUMENTS_BAD"; case CKR_NO_EVENT: /* 0x00000008UL */ return "CKR_NO_EVENT"; case CKR_NEED_TO_CREATE_THREADS: /* 0x00000009UL */ return "CKR_NEED_TO_CREATE_THREADS"; case CKR_CANT_LOCK: /* 0x0000000AUL */ return "CKR_CANT_LOCK"; case CKR_ATTRIBUTE_READ_ONLY: /* 0x00000010UL */ return "CKR_ATTRIBUTE_READ_ONLY"; case CKR_ATTRIBUTE_SENSITIVE: /* 0x00000011UL */ return "CKR_ATTRIBUTE_SENSITIVE"; case CKR_ATTRIBUTE_TYPE_INVALID: /* 0x00000012UL */ return "CKR_ATTRIBUTE_TYPE_INVALID"; case CKR_ATTRIBUTE_VALUE_INVALID: /* 0x00000013UL */ return "CKR_ATTRIBUTE_VALUE_INVALID"; #ifdef CKR_COPY_PROHIBITED case CKR_COPY_PROHIBITED: /* 0x0000001AUL */ return "CKR_COPY_PROHIBITED"; #else case 0x0000001AUL: /* 0x0000001AUL */ return "CKR_COPY_PROHIBITED"; #endif #ifdef CKR_ACTION_PROHIBITED case CKR_ACTION_PROHIBITED: /* 0x0000001BUL */ return "CKR_ACTION_PROHIBITED"; #else case 0x0000001BUL: /* 0x0000001BUL */ return "CKR_ACTION_PROHIBITED"; #endif case CKR_DATA_INVALID: /* 0x00000020UL */ return "CKR_DATA_INVALID"; case CKR_DATA_LEN_RANGE: /* 0x00000021UL */ return "CKR_DATA_LEN_RANGE"; case CKR_DEVICE_ERROR: /* 0x00000030UL */ return "CKR_DEVICE_ERROR"; case CKR_DEVICE_MEMORY: /* 0x00000031UL */ return "CKR_DEVICE_MEMORY"; case CKR_DEVICE_REMOVED: /* 0x00000032UL */ return "CKR_DEVICE_REMOVED"; case CKR_ENCRYPTED_DATA_INVALID: /* 0x00000040UL */ return "CKR_ENCRYPTED_DATA_INVALID"; case CKR_ENCRYPTED_DATA_LEN_RANGE: /* 0x00000041UL */ return "CKR_ENCRYPTED_DATA_LEN_RANGE"; case CKR_FUNCTION_CANCELED: /* 0x00000050UL */ return "CKR_FUNCTION_CANCELED"; case CKR_FUNCTION_NOT_PARALLEL: /* 0x00000051UL */ return "CKR_FUNCTION_NOT_PARALLEL"; case CKR_FUNCTION_NOT_SUPPORTED: /* 0x00000054UL */ return "CKR_FUNCTION_NOT_SUPPORTED"; case CKR_KEY_HANDLE_INVALID: /* 0x00000060UL */ return "CKR_KEY_HANDLE_INVALID"; case CKR_KEY_SIZE_RANGE: /* 0x00000062UL */ return "CKR_KEY_SIZE_RANGE"; case CKR_KEY_TYPE_INCONSISTENT: /* 0x00000063UL */ return "CKR_KEY_TYPE_INCONSISTENT"; case CKR_KEY_NOT_NEEDED: /* 0x00000064UL */ return "CKR_KEY_NOT_NEEDED"; case CKR_KEY_CHANGED: /* 0x00000065UL */ return "CKR_KEY_CHANGED"; case CKR_KEY_NEEDED: /* 0x00000066UL */ return "CKR_KEY_NEEDED"; case CKR_KEY_INDIGESTIBLE: /* 0x00000067UL */ return "CKR_KEY_INDIGESTIBLE"; case CKR_KEY_FUNCTION_NOT_PERMITTED: /* 0x00000068UL */ return "CKR_KEY_FUNCTION_NOT_PERMITTED"; case CKR_KEY_NOT_WRAPPABLE: /* 0x00000069UL */ return "CKR_KEY_NOT_WRAPPABLE"; case CKR_KEY_UNEXTRACTABLE: /* 0x0000006AUL */ return "CKR_KEY_UNEXTRACTABLE"; case CKR_MECHANISM_INVALID: /* 0x00000070UL */ return "CKR_MECHANISM_INVALID"; case CKR_MECHANISM_PARAM_INVALID: /* 0x00000071UL */ return "CKR_MECHANISM_PARAM_INVALID"; case CKR_OBJECT_HANDLE_INVALID: /* 0x00000082UL */ return "CKR_OBJECT_HANDLE_INVALID"; case CKR_OPERATION_ACTIVE: /* 0x00000090UL */ return "CKR_OPERATION_ACTIVE"; case CKR_OPERATION_NOT_INITIALIZED: /* 0x00000091UL */ return "CKR_OPERATION_NOT_INITIALIZED"; case CKR_PIN_INCORRECT: /* 0x000000A0UL */ return "CKR_PIN_INCORRECT"; case CKR_PIN_INVALID: /* 0x000000A1UL */ return "CKR_PIN_INVALID"; case CKR_PIN_LEN_RANGE: /* 0x000000A2UL */ return "CKR_PIN_LEN_RANGE"; case CKR_PIN_EXPIRED: /* 0x000000A3UL */ return "CKR_PIN_EXPIRED"; case CKR_PIN_LOCKED: /* 0x000000A4UL */ return "CKR_PIN_LOCKED"; case CKR_SESSION_CLOSED: /* 0x000000B0UL */ return "CKR_SESSION_CLOSED"; case CKR_SESSION_COUNT: /* 0x000000B1UL */ return "CKR_SESSION_COUNT"; case CKR_SESSION_HANDLE_INVALID: /* 0x000000B3UL */ return "CKR_SESSION_HANDLE_INVALID"; case CKR_SESSION_PARALLEL_NOT_SUPPORTED: /* 0x000000B4UL */ return "CKR_SESSION_PARALLEL_NOT_SUPPORTED"; case CKR_SESSION_READ_ONLY: /* 0x000000B5UL */ return "CKR_SESSION_READ_ONLY"; case CKR_SESSION_EXISTS: /* 0x000000B6UL */ return "CKR_SESSION_EXISTS"; case CKR_SESSION_READ_ONLY_EXISTS: /* 0x000000B7UL */ return "CKR_SESSION_READ_ONLY_EXISTS"; case CKR_SESSION_READ_WRITE_SO_EXISTS: /* 0x000000B8UL */ return "CKR_SESSION_READ_WRITE_SO_EXISTS"; case CKR_SIGNATURE_INVALID: /* 0x000000C0UL */ return "CKR_SIGNATURE_INVALID"; case CKR_SIGNATURE_LEN_RANGE: /* 0x000000C1UL */ return "CKR_SIGNATURE_LEN_RANGE"; case CKR_TEMPLATE_INCOMPLETE: /* 0x000000D0UL */ return "CKR_TEMPLATE_INCOMPLETE"; case CKR_TEMPLATE_INCONSISTENT: /* 0x000000D1UL */ return "CKR_TEMPLATE_INCONSISTENT"; case CKR_TOKEN_NOT_PRESENT: /* 0x000000E0UL */ return "CKR_TOKEN_NOT_PRESENT"; case CKR_TOKEN_NOT_RECOGNIZED: /* 0x000000E1UL */ return "CKR_TOKEN_NOT_RECOGNIZED"; case CKR_TOKEN_WRITE_PROTECTED: /* 0x000000E2UL */ return "CKR_TOKEN_WRITE_PROTECTED"; case CKR_UNWRAPPING_KEY_HANDLE_INVALID: /* 0x000000F0UL */ return "CKR_UNWRAPPING_KEY_HANDLE_INVALID"; case CKR_UNWRAPPING_KEY_SIZE_RANGE: /* 0x000000F1UL */ return "CKR_UNWRAPPING_KEY_SIZE_RANGE"; case CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT: /* 0x000000F2UL */ return "CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT"; case CKR_USER_ALREADY_LOGGED_IN: /* 0x00000100UL */ return "CKR_USER_ALREADY_LOGGED_IN"; case CKR_USER_NOT_LOGGED_IN: /* 0x00000101UL */ return "CKR_USER_NOT_LOGGED_IN"; case CKR_USER_PIN_NOT_INITIALIZED: /* 0x00000102UL */ return "CKR_USER_PIN_NOT_INITIALIZED"; case CKR_USER_TYPE_INVALID: /* 0x00000103UL */ return "CKR_USER_TYPE_INVALID"; case CKR_USER_ANOTHER_ALREADY_LOGGED_IN: /* 0x00000104UL */ return "CKR_USER_ANOTHER_ALREADY_LOGGED_IN"; case CKR_USER_TOO_MANY_TYPES: /* 0x00000105UL */ return "CKR_USER_TOO_MANY_TYPES"; case CKR_WRAPPED_KEY_INVALID: /* 0x00000110UL */ return "CKR_WRAPPED_KEY_INVALID"; case CKR_WRAPPED_KEY_LEN_RANGE: /* 0x00000112UL */ return "CKR_WRAPPED_KEY_LEN_RANGE"; case CKR_WRAPPING_KEY_HANDLE_INVALID: /* 0x00000113UL */ return "CKR_WRAPPING_KEY_HANDLE_INVALID"; case CKR_WRAPPING_KEY_SIZE_RANGE: /* 0x00000114UL */ return "CKR_WRAPPING_KEY_SIZE_RANGE"; case CKR_WRAPPING_KEY_TYPE_INCONSISTENT: /* 0x00000115UL */ return "CKR_WRAPPING_KEY_TYPE_INCONSISTENT"; case CKR_RANDOM_SEED_NOT_SUPPORTED: /* 0x00000120UL */ return "CKR_RANDOM_SEED_NOT_SUPPORTED"; case CKR_RANDOM_NO_RNG: /* 0x00000121UL */ return "CKR_RANDOM_NO_RNG"; case CKR_DOMAIN_PARAMS_INVALID: /* 0x00000130UL */ return "CKR_DOMAIN_PARAMS_INVALID"; #ifdef CKR_CURVE_NOT_SUPPORTED case CKR_CURVE_NOT_SUPPORTED: /* 0x00000140UL */ return "CKR_CURVE_NOT_SUPPORTED"; #else case 0x00000140UL: /* 0x00000140UL */ return "CKR_CURVE_NOT_SUPPORTED"; #endif case CKR_BUFFER_TOO_SMALL: /* 0x00000150UL */ return "CKR_BUFFER_TOO_SMALL"; case CKR_SAVED_STATE_INVALID: /* 0x00000160UL */ return "CKR_SAVED_STATE_INVALID"; case CKR_INFORMATION_SENSITIVE: /* 0x00000170UL */ return "CKR_INFORMATION_SENSITIVE"; case CKR_STATE_UNSAVEABLE: /* 0x00000180UL */ return "CKR_STATE_UNSAVEABLE"; case CKR_CRYPTOKI_NOT_INITIALIZED: /* 0x00000190UL */ return "CKR_CRYPTOKI_NOT_INITIALIZED"; case CKR_CRYPTOKI_ALREADY_INITIALIZED: /* 0x00000191UL */ return "CKR_CRYPTOKI_ALREADY_INITIALIZED"; case CKR_MUTEX_BAD: /* 0x000001A0UL */ return "CKR_MUTEX_BAD"; case CKR_MUTEX_NOT_LOCKED: /* 0x000001A1UL */ return "CKR_MUTEX_NOT_LOCKED"; case CKR_FUNCTION_REJECTED: /* 0x00000200UL */ return "CKR_FUNCTION_REJECTED"; case CKR_VENDOR_DEFINED: /* 0x80000000UL */ return "CKR_VENDOR_DEFINED"; } return "Unknown error"; } static apr_status_t cleanup_logout(void *dummy) { if (dummy) { redwax_pkcs11_session_t *s = dummy; s->module->C_Logout(s->session); } return APR_SUCCESS; } static apr_status_t cleanup_session(void *dummy) { if (dummy) { redwax_pkcs11_session_t *s = dummy; s->module->C_CloseSession(s->session); } return APR_SUCCESS; } static apr_status_t cleanup_module(void *dummy) { if (dummy) { CK_FUNCTION_LIST *module = dummy; p11_kit_module_finalize(module); p11_kit_module_release(module); } return APR_SUCCESS; } static apr_status_t cleanup_p11kit(void *dummy) { if (dummy) { CK_FUNCTION_LIST **modules = dummy; p11_kit_modules_finalize_and_release(modules); } return APR_SUCCESS; } static apr_status_t cleanup_p11kituri(void *dummy) { if (dummy) { P11KitUri *uri = dummy; p11_kit_uri_free(uri); } return APR_SUCCESS; } static apr_status_t cleanup_free(void *dummy) { if (dummy) { free(dummy); } return APR_SUCCESS; } static apr_status_t redwax_p11kit_initialise(redwax_tool_t *r) { global_modules = p11_kit_modules_load_and_initialize(0); apr_pool_cleanup_register(r->pool, global_modules, cleanup_p11kit, apr_pool_cleanup_null); return APR_SUCCESS; } static void redwax_pkcs11_add_attribute(apr_array_header_t *attrs, CK_ATTRIBUTE_TYPE type, CK_VOID_PTR pValue, CK_ULONG ulValueLen) { CK_ATTRIBUTE_PTR attr = apr_array_push(attrs); attr->type = type; attr->pValue = pValue; attr->ulValueLen = ulValueLen; } static apr_status_t redwax_pkcs11_write_cert(redwax_tool_t *r, P11KitUri *parsed, CK_FUNCTION_LIST *module, CK_TOKEN_INFO *tokenInfo, CK_SESSION_HANDLE session, const redwax_certificate_t *cert, const char *label) { CK_OBJECT_CLASS certificateClass = CKO_CERTIFICATE; CK_CERTIFICATE_TYPE cert_type; CK_BBOOL true = CK_TRUE; CK_BBOOL trusted = CK_FALSE; CK_OBJECT_HANDLE cert_obj; CK_ATTRIBUTE_PTR attr; int ret; attr = p11_kit_uri_get_attribute(parsed, CKA_CLASS); if (attr) { CK_OBJECT_CLASS *clazz = attr->pValue; if (*clazz != CKO_CERTIFICATE) { redwax_print_error(r, "pkcs11-out: could not write certificate to '%s', " "URL specified a type other than 'cert', skipping.\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); return APR_BADARG; } } if (cert->len) { apr_array_header_t *template = apr_array_make(r->pool, 10, sizeof(CK_ATTRIBUTE)); redwax_pkcs11_add_attribute(template, CKA_CLASS, (void *)&certificateClass, sizeof(certificateClass)); redwax_pkcs11_add_attribute(template, CKA_TOKEN, (void *)&true, sizeof(true)); /* LABEL comes first from the URL... */ attr = p11_kit_uri_get_attribute(parsed, CKA_LABEL); if (attr) { redwax_pkcs11_add_attribute(template, CKA_LABEL, attr->pValue, attr->ulValueLen); } /* ...otherwise LABEL comes from command line... */ else if (label) { redwax_pkcs11_add_attribute(template, CKA_LABEL, (void *)label, strlen(label)); } /* ...otherwise LABEL comes from the matching key... */ else if (cert->common.klabel_der) { redwax_pkcs11_add_attribute(template, CKA_LABEL, (void *)cert->common.klabel_der, cert->common.klabel_len); } /* ...otherwise LABEL comes from the original input key... */ else if (cert->label) { redwax_pkcs11_add_attribute(template, CKA_LABEL, (void *)cert->label, cert->label_len); } /* ...otherwise LABEL is generated from the CN of the subject */ else if (cert->common.glabel) { redwax_pkcs11_add_attribute(template, CKA_LABEL, (void *)cert->common.glabel, cert->common.glabel_len); } /* CKA_CERTIFICATE_TYPE */ switch(cert->common.type) { case REDWAX_CERTIFICATE_X509: cert_type = CKC_X_509; if (cert->x509) { redwax_certificate_x509_t *x509 = cert->x509; /* id from URL has top priority */ attr = p11_kit_uri_get_attribute(parsed, CKA_ID); if (attr) { redwax_pkcs11_add_attribute(template, CKA_ID, attr->pValue, attr->ulValueLen); // fixme add debug statements } /* otherwise use the ID that matches the key */ else if (x509->kid_len) { redwax_pkcs11_add_attribute(template, CKA_ID, (void *)x509->kid_der, x509->kid_len); } /* otherwise keep the original ID */ else if (cert->id_len) { redwax_pkcs11_add_attribute(template, CKA_ID, (void *)cert->id_der, cert->id_len); } /* failing that use the subject key identifier */ else if (x509->skid_len) { redwax_pkcs11_add_attribute(template, CKA_ID, (void *)x509->skid_der, x509->skid_len); } /* last resort, use generated ID */ else if (x509->gid_len) { redwax_pkcs11_add_attribute(template, CKA_ID, (void *)x509->gid_der, x509->gid_len); } if (x509->subject_len) { redwax_pkcs11_add_attribute(template, CKA_SUBJECT, (void *)x509->subject_der, x509->subject_len); } if (x509->issuer_len) { redwax_pkcs11_add_attribute(template, CKA_ISSUER, (void *)x509->issuer_der, x509->issuer_len); } if (x509->serial_len) { redwax_pkcs11_add_attribute(template, CKA_SERIAL_NUMBER, (void *)x509->serial_der, x509->serial_len); } if (cert->len) { redwax_pkcs11_add_attribute(template, CKA_VALUE, (void*) cert->der, cert->len); } } break; default: redwax_print_error(r, "pkcs11-out: could not write certificate to '%s', " "certificate not recognised as X509.\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); break; } redwax_pkcs11_add_attribute(template, CKA_CERTIFICATE_TYPE, (void *)&cert_type, sizeof(cert_type)); /* CKA_TRUSTED */ if (cert->common.trusted) { trusted = CK_TRUE; } redwax_pkcs11_add_attribute(template, CKA_TRUSTED, (void *)&trusted, sizeof(trusted)); #if 0 /* CKA_CERTIFICATE_CATEGORY */ /* handle category - if the category is a user token, but there is * no corresponding key, we are an end entity */ if (cert->common.category) { CK_CERTIFICATE_CATEGORY category = CK_CERTIFICATE_CATEGORY_UNSPECIFIED; /* check for key */ switch (cert->common.category) { case REDWAX_CERTIFICATE_UNSPECIFIED: category = CK_CERTIFICATE_CATEGORY_UNSPECIFIED; break; case REDWAX_CERTIFICATE_END_ENTITY: // FIXME - if a matching key is present, we are a token user if (0) { /* has matching key */ category = CK_CERTIFICATE_CATEGORY_TOKEN_USER; } else { category = CK_CERTIFICATE_CATEGORY_OTHER_ENTITY; } break; case REDWAX_CERTIFICATE_INTERMEDIATE: category = CK_CERTIFICATE_CATEGORY_AUTHORITY; break; case REDWAX_CERTIFICATE_ROOT: category = CK_CERTIFICATE_CATEGORY_AUTHORITY; break; case REDWAX_CERTIFICATE_TRUSTED: category = CK_CERTIFICATE_CATEGORY_AUTHORITY; break; } redwax_pkcs11_add_attribute(template, CKA_CERTIFICATE_CATEGORY, (void *)&category, sizeof(CK_CERTIFICATE_CATEGORY)); } #endif /* CKA_PUBLIC_KEY_INFO */ if (cert->common.subjectpublickeyinfo_len) { redwax_pkcs11_add_attribute(template, CKA_PUBLIC_KEY_INFO, (void*) cert->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_len); } ret = module->C_CreateObject(session, (CK_ATTRIBUTE_PTR)template->elts, template->nelts, &cert_obj); if (ret != CKR_OK) { redwax_print_error(r, "pkcs11-out: could not write certificate to '%s': %s\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EACCES; } } return APR_SUCCESS; } static apr_status_t redwax_pkcs11_write_key(redwax_tool_t *r, P11KitUri *parsed, CK_FUNCTION_LIST *module, CK_TOKEN_INFO *tokenInfo, CK_SESSION_HANDLE session, const redwax_key_t *key, const char *label) { CK_OBJECT_CLASS publicKeyClass = CKO_PUBLIC_KEY; // https://github.com/OpenSC/OpenSC/blob/master/src/tools/pkcs11-tool.c CK_OBJECT_CLASS privateKeyClass = CKO_PRIVATE_KEY; CK_BBOOL true = CK_TRUE; CK_BBOOL false = CK_FALSE; CK_OBJECT_HANDLE key_obj; CK_ATTRIBUTE_PTR attr; int ret; attr = p11_kit_uri_get_attribute(parsed, CKA_CLASS); if (attr) { CK_OBJECT_CLASS *clazz = attr->pValue; if (*clazz != CKO_PRIVATE_KEY) { redwax_print_error(r, "pkcs11-out: could not write key to '%s', " "URL specified a type other than 'private', skipping.\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); return APR_BADARG; } } if (key->len) { apr_array_header_t *publicTemplate = apr_array_make(r->pool, 10, sizeof(CK_ATTRIBUTE)); apr_array_header_t *privateTemplate = apr_array_make(r->pool, 10, sizeof(CK_ATTRIBUTE)); redwax_pkcs11_add_attribute(publicTemplate, CKA_CLASS, (void *)&publicKeyClass, sizeof(publicKeyClass)); redwax_pkcs11_add_attribute(privateTemplate, CKA_CLASS, (void *)&privateKeyClass, sizeof(privateKeyClass)); redwax_pkcs11_add_attribute(publicTemplate, CKA_TOKEN, (void *)&true, sizeof(true)); redwax_pkcs11_add_attribute(privateTemplate, CKA_TOKEN, (void *)&true, sizeof(true)); redwax_pkcs11_add_attribute(publicTemplate, CKA_PRIVATE, (void *)&false, sizeof(false)); redwax_pkcs11_add_attribute(privateTemplate, CKA_PRIVATE, (void *)&true, sizeof(true)); /* imported private keys are outside the token already, by default mark the * keys non-sensitive and extractable. */ redwax_pkcs11_add_attribute(privateTemplate, CKA_SENSITIVE, (void *)&false, sizeof(false)); redwax_pkcs11_add_attribute(privateTemplate, CKA_EXTRACTABLE, (void *)&true, sizeof(true)); /* ID comes from the URL... */ attr = p11_kit_uri_get_attribute(parsed, CKA_ID); if (attr) { redwax_pkcs11_add_attribute(publicTemplate, CKA_ID, attr->pValue, attr->ulValueLen); redwax_pkcs11_add_attribute(privateTemplate, CKA_ID, attr->pValue, attr->ulValueLen); } /* ...otherwise ID comes from a matching certificate... */ else if (key->common.cid_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_ID, (void *)key->common.cid_der, key->common.cid_len); redwax_pkcs11_add_attribute(privateTemplate, CKA_ID, (void *)key->common.cid_der, key->common.cid_len); } /* ...otherwise ID comes from the input key... */ else if (key->common.id_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_ID, (void *)key->common.id_der, key->common.id_len); redwax_pkcs11_add_attribute(privateTemplate, CKA_ID, (void *)key->common.id_der, key->common.id_len); } /* ...failing all of that, ID is generated from public key */ else if (key->common.gid_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_ID, (void *)key->common.gid_der, key->common.gid_len); redwax_pkcs11_add_attribute(privateTemplate, CKA_ID, (void *)key->common.gid_der, key->common.gid_len); } /* LABEL comes from the URL... */ attr = p11_kit_uri_get_attribute(parsed, CKA_LABEL); if (attr) { redwax_pkcs11_add_attribute(publicTemplate, CKA_LABEL, attr->pValue, attr->ulValueLen); redwax_pkcs11_add_attribute(privateTemplate, CKA_LABEL, attr->pValue, attr->ulValueLen); } /* ...otherwise LABEL comes from the command line... */ else if (label) { redwax_pkcs11_add_attribute(publicTemplate, CKA_LABEL, (void *)label, strlen(label)); redwax_pkcs11_add_attribute(privateTemplate, CKA_LABEL, (void *)label, strlen(label)); } /* ...otherwise LABEL comes from a matching certificate... */ else if (key->common.clabel_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_LABEL, (void *)key->common.clabel_der, key->common.clabel_len); redwax_pkcs11_add_attribute(privateTemplate, CKA_LABEL, (void *)key->common.clabel_der, key->common.clabel_len); } /* ...otherwise LABEL comes from the input key. */ else if (key->label_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_LABEL, (void *)key->label, key->label_len); redwax_pkcs11_add_attribute(privateTemplate, CKA_LABEL, (void *)key->label, key->label_len); } if (key->common.subject_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_SUBJECT, (void *)key->common.subject_der, key->common.subject_len); } /* CKA_CERTIFICATE_TYPE */ switch(key->common.type) { case REDWAX_KEY_RSA: if (key->rsa) { CK_ULONG key_type = CKK_RSA; redwax_pkcs11_add_attribute(publicTemplate, CKA_KEY_TYPE, (void*) &key_type, sizeof(key_type)); redwax_pkcs11_add_attribute(privateTemplate, CKA_KEY_TYPE, (void*) &key_type, sizeof(key_type)); /* Do we support CKA_DERIVE or CKA_ALLOWED_MECHANISMS? */ /* public / private */ if (key->rsa->modulus_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_MODULUS, key->rsa->modulus, key->rsa->modulus_len); } if (key->rsa->modulus_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_MODULUS, key->rsa->modulus, key->rsa->modulus_len); } if (key->rsa->public_exponent_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_PUBLIC_EXPONENT, key->rsa->public_exponent, key->rsa->public_exponent_len); } if (key->rsa->public_exponent_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_PUBLIC_EXPONENT, key->rsa->public_exponent, key->rsa->public_exponent_len); } /* private only */ if (key->rsa->private_exponent_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_PRIVATE_EXPONENT, key->rsa->private_exponent, key->rsa->private_exponent_len); } if (key->rsa->prime_1_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_PRIME_1, key->rsa->prime_1, key->rsa->prime_1_len); } if (key->rsa->prime_2_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_PRIME_2, key->rsa->prime_2, key->rsa->prime_2_len); } if (key->rsa->exponent_1_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_EXPONENT_1, key->rsa->exponent_1, key->rsa->exponent_1_len); } if (key->rsa->exponent_2_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_EXPONENT_2, key->rsa->exponent_2, key->rsa->exponent_2_len); } if (key->rsa->coefficient_len) { redwax_pkcs11_add_attribute(privateTemplate, CKA_COEFFICIENT, key->rsa->coefficient, key->rsa->coefficient_len); } } break; default: redwax_print_error(r, "pkcs11-out: could not write private key to '%s', " "we only support RSA keys right now.\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); break; } /* CKA_PUBLIC_KEY_INFO */ if (key->common.subjectpublickeyinfo_len) { redwax_pkcs11_add_attribute(publicTemplate, CKA_PUBLIC_KEY_INFO, (void*) key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len); redwax_pkcs11_add_attribute(privateTemplate, CKA_PUBLIC_KEY_INFO, (void*) key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len); } #if 0 ret = module->C_CreateObject(session, (CK_ATTRIBUTE_PTR) publicTemplate->elts, publicTemplate->nelts, &key_obj); if (ret != CKR_OK) { redwax_print_error(r, "pkcs11-out: could not write public key to '%s': %s\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EACCES; } #endif /* first, try create the object including the CKA_PUBLIC_KEY_INFO */ ret = module->C_CreateObject(session, (CK_ATTRIBUTE_PTR) privateTemplate->elts, privateTemplate->nelts, &key_obj); if (ret == CKR_ATTRIBUTE_TYPE_INVALID) { /* otherwise, try create the object excluding the CKA_PUBLIC_KEY_INFO */ ret = module->C_CreateObject(session, (CK_ATTRIBUTE_PTR) privateTemplate->elts, privateTemplate->nelts - 1, &key_obj); } if (ret != CKR_OK) { redwax_print_error(r, "pkcs11-out: could not write private key to '%s': %s\n", redwax_pstrntrim(r->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EACCES; } } return APR_SUCCESS; } static apr_status_t redwax_p11kit_handle_token_login(redwax_tool_t *r, apr_pool_t *pool, P11KitUri *parsed, CK_FUNCTION_LIST *module, CK_TOKEN_INFO *tokenInfo, CK_SLOT_ID_PTR slot_id, CK_SESSION_HANDLE session, const char *direction, apr_hash_t *secrets, const char **pin) { redwax_pkcs11_session_t *s; const char *urlPIN = p11_kit_uri_get_pin_value(parsed); const char *tokenPIN = NULL; CK_UTF8CHAR *userPIN = NULL; CK_RV ret; apr_status_t status; apr_ssize_t userPIN_len = 0; // urlPIN = ""; /* until further notice */ if (pin) { *pin = NULL; } /* support a pinpad reader */ if (tokenInfo->flags & CKF_PROTECTED_AUTHENTICATION_PATH) { /* userPIN is null */ /* skip all protected login on command completion */ if (r->complete) { return APR_SUCCESS; } while (1) { ret = module->C_Login(session, CKU_USER, userPIN, userPIN_len); if (ret == CKR_OK) { break; } else if (ret == CKR_PIN_INCORRECT) { redwax_print_error(r, "%s: login to '%s' with user PIN " "on pinpad, try again: %s\n", direction, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); continue; } if (ret != CKR_OK) { redwax_print_error(r, "%s: login to '%s' with user PIN " "on pinpad failed: %s\n", direction, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EGENERAL; } } } /* otherwise grab the PIN from the URL */ else if (urlPIN) { /* skip command completion if any pin problems */ if (r->complete && (tokenInfo->flags & (CKF_USER_PIN_COUNT_LOW | CKF_USER_PIN_FINAL_TRY | CKF_USER_PIN_LOCKED | CKF_USER_PIN_TO_BE_CHANGED))) { return APR_SUCCESS; } userPIN_len = strlen(urlPIN); userPIN = apr_pmemdup(pool, urlPIN, userPIN_len); #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(pool, userPIN, userPIN_len); #endif /* try once */ ret = module->C_Login(session, CKU_USER, userPIN, userPIN_len); if (ret != CKR_OK) { redwax_print_error(r, "%s: login to '%s' with URL PIN " "failed, skipping: %s\n", direction, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EGENERAL; } else if (pin) { *pin = apr_pstrndup(pool, (const char *)userPIN, userPIN_len); } } /* otherwise see if there is a pinfile */ else if (secrets) { /* skip command completion if any pin problems */ if (r->complete && (tokenInfo->flags & (CKF_USER_PIN_COUNT_LOW | CKF_USER_PIN_FINAL_TRY | CKF_USER_PIN_LOCKED | CKF_USER_PIN_TO_BE_CHANGED))) { return APR_SUCCESS; } /* if a pinfile exists, we try to log in to listed tokens, but we * make no attempt to log in to anything not listed - the intention * is we've been told explicitly which tokens to log in with, anything * outside the list stays public. */ if ((tokenPIN = apr_hash_get(secrets, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), APR_HASH_KEY_STRING))) { userPIN_len = strlen(tokenPIN); userPIN = apr_pmemdup(pool, tokenPIN, userPIN_len); #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(pool, userPIN, userPIN_len); #endif /* try once */ ret = module->C_Login(session, CKU_USER, userPIN, userPIN_len); if (ret != CKR_OK) { redwax_print_error(r, "%s: login to '%s' with token PIN " "failed, skipping: %s\n", direction, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EGENERAL; } else if (pin) { *pin = apr_pstrndup(pool, (const char *)userPIN, userPIN_len); } } } /* otherwise load from stdin */ else { const char *prompt = apr_psprintf(r->pool, "Enter user PIN for %s: ", redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); apr_size_t min = tokenInfo->ulMinPinLen; apr_size_t max = tokenInfo->ulMaxPinLen; char *buf = apr_pcalloc(pool, max + 2); #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(pool, buf, max + 2); #endif /* skip command completion */ if (r->complete) { return APR_SUCCESS; } while (1) { int len; if (max <= 0) { /* maximum length says no password allowed */ break; } status = apr_password_get(prompt, buf, &max); if (APR_ENAMETOOLONG == status) { redwax_print_error(r, "%s: user PIN was longer than %" APR_SSIZE_T_FMT ", try again.\n", direction, max); continue; } if (APR_SUCCESS != status) { redwax_print_error(r, "%s: could not read user PIN: %pm\n", direction, &status); return status; } len = strlen(buf); if (len < min) { redwax_print_error(r, "%s: user PIN was shorter than %" APR_SSIZE_T_FMT " characters, try again.\n", direction, min); continue; } else if (len > max) { redwax_print_error(r, "%s: user PIN was longer than %" APR_SSIZE_T_FMT " characters, try again.\n", direction, max); continue; } userPIN_len = len; userPIN = apr_pmemdup(pool, buf, len); #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(pool, userPIN, userPIN_len); #endif ret = module->C_Login(session, CKU_USER, userPIN, userPIN_len); if (ret == CKR_OK) { if (pin) { *pin = apr_pstrndup(pool, (const char *)userPIN, userPIN_len); } break; } else if (ret == CKR_PIN_INCORRECT) { redwax_print_error(r, "%s: login to '%s' with user PIN failed, try again: %s\n", direction, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); continue; } if (ret != CKR_OK) { redwax_print_error(r, "%s: login to '%s' with user PIN failed: %s\n", direction, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); return APR_EGENERAL; } } } s = apr_pcalloc(pool, sizeof(redwax_pkcs11_session_t)); s->module = module; s->session = session; apr_pool_cleanup_register(pool, s, cleanup_logout, apr_pool_cleanup_null); return APR_SUCCESS; } static CK_RV redwax_p11kit_read_attributes(apr_pool_t *pool, CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, CK_ATTRIBUTE_PTR template, CK_ULONG template_len) { CK_ULONG i; int ret; ret = module->C_GetAttributeValue(session, object, template, template_len); if (ret == CKR_OK || ret == CKR_ATTRIBUTE_SENSITIVE || ret == CKR_ATTRIBUTE_TYPE_INVALID) { for (i = 0; i < template_len; i++) { if (CK_UNAVAILABLE_INFORMATION != template[i].ulValueLen) { template[i].pValue = apr_palloc(pool, template[i].ulValueLen); } } ret = module->C_GetAttributeValue(session, object, &template[0], template_len); } return ret; } static int redwax_pk11_key_exists(redwax_tool_t *r, CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, const unsigned char *subjectpublickeyinfo_der, apr_size_t subjectpublickeyinfo_len, const unsigned char **id_der, apr_size_t *id_len, const unsigned char **label_der, apr_size_t *label_len, apr_pool_t *pool) { int ret; /* * For each key, check if the key already exists at the target * location. * * If the key exists, read the ID off the card. We will use the * existing ID for any matching certificates. */ CK_OBJECT_CLASS clazz = CKO_PRIVATE_KEY; CK_ATTRIBUTE template[] = { { CKA_CLASS, &clazz, sizeof(clazz) }, { CKA_PUBLIC_KEY_INFO, (void*) subjectpublickeyinfo_der, subjectpublickeyinfo_len } }; int template_len = 2; ret = module->C_FindObjectsInit(session, template, template_len); if (ret == CKR_OK) { CK_OBJECT_HANDLE object; CK_ULONG object_count; CK_ATTRIBUTE id_template[] = { {CKA_ID, NULL_PTR, 0}, {CKA_LABEL, NULL_PTR, 0} }; int id_template_len = 2; ret = module->C_FindObjects(session, &object, 1, &object_count); if (ret == CKR_OK && object_count == 1) { ret = redwax_p11kit_read_attributes(pool, module, session, object, id_template, id_template_len); if (ret == CKR_OK) { /* overwrite our ID (if present) with the ID already present */ *id_der = id_template[0].pValue; *id_len = id_template[0].ulValueLen; /* overwrite our label (if present) with the label already present */ *label_der = id_template[1].pValue; *label_len = id_template[1].ulValueLen; } module->C_FindObjectsFinal (session); return 1; } module->C_FindObjectsFinal (session); } return 0; } static int redwax_pk11_cert_exists(redwax_tool_t *r, CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, const unsigned char *der, apr_size_t len, const unsigned char **id_der, apr_size_t *id_len, const unsigned char **label_der, apr_size_t *label_len, apr_pool_t *pool) { int ret; /* * For each certificate, check if the certificate already exists at the target * location. * * If the certificate exists, read the ID off the card. We will use the * existing ID for any matching certificates. */ CK_OBJECT_CLASS clazz = CKO_CERTIFICATE; CK_ATTRIBUTE template[] = { { CKA_CLASS, &clazz, sizeof(clazz) }, { CKA_VALUE, (void*) der, len } }; int template_len = 2; ret = module->C_FindObjectsInit(session, template, template_len); if (ret == CKR_OK) { CK_OBJECT_HANDLE object; CK_ULONG object_count; CK_ATTRIBUTE id_template[] = { {CKA_ID, NULL_PTR, 0}, {CKA_LABEL, NULL_PTR, 0} }; int id_template_len = 2; ret = module->C_FindObjects(session, &object, 1, &object_count); if (ret == CKR_OK && object_count == 1) { ret = redwax_p11kit_read_attributes(pool, module, session, object, id_template, id_template_len); if (ret == CKR_OK) { /* overwrite our ID (if present) with the ID already present */ *id_der = id_template[0].pValue; *id_len = id_template[0].ulValueLen; /* overwrite our label (if present) with the label already present */ *label_der = id_template[1].pValue; *label_len = id_template[1].ulValueLen; } module->C_FindObjectsFinal (session); return 1; } module->C_FindObjectsFinal (session); } return 0; } static void redwax_pk11_scan_cert(redwax_tool_t *r, redwax_certificate_t *cert, redwax_key_t *key) { if (key->common.subjectpublickeyinfo_len == cert->common.subjectpublickeyinfo_len && !memcmp(key->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len)) { /* otherwise keep the original ID */ if (cert->id_len) { key->common.cid_der = cert->id_der; key->common.cid_len = cert->id_len; } /* failing that use the subject key identifier */ else if (cert->x509 && cert->x509->skid_len) { key->common.cid_der = cert->x509->skid_der; key->common.cid_len = cert->x509->skid_len; } /* last resort, use generated ID */ else if (cert->x509 && cert->x509->gid_len) { key->common.cid_der = cert->x509->gid_der; key->common.cid_len = cert->x509->gid_len; } /* LABEL comes from the original input key... */ if (cert->label) { key->common.clabel_der = (void *)cert->label; key->common.clabel_len = cert->label_len; } /* ...otherwise LABEL is generated from the CN of the subject */ else if (cert->common.glabel) { key->common.clabel_der = (void *)cert->common.glabel; key->common.clabel_len = cert->common.glabel_len; } } } static void redwax_pk11_scan_certs(redwax_tool_t *r, redwax_key_t *key) { int i; for (i = 0; i < r->certs_out->nelts; i++) { redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, redwax_certificate_t); redwax_pk11_scan_cert(r, cert, key); } for (i = 0; i < r->intermediates_out->nelts; i++) { redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, redwax_certificate_t); redwax_pk11_scan_cert(r, cert, key); } for (i = 0; i < r->trusted_out->nelts; i++) { redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, redwax_certificate_t); redwax_pk11_scan_cert(r, cert, key); } } static apr_status_t redwax_p11kit_handle_slot(redwax_tool_t *r, P11KitUri *parsed, CK_FUNCTION_LIST *module, CK_TOKEN_INFO *tokenInfo, CK_SLOT_ID_PTR slot_id, apr_hash_t *secrets) { apr_pool_t *pool; CK_SESSION_HANDLE session; const char *label; redwax_pkcs11_session_t *s; int ret; apr_status_t status; int i; apr_pool_create(&pool, r->pool); ret = module->C_OpenSession(*slot_id, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &session); if (ret != CKR_OK) { redwax_print_error(r, "pkcs11-out: could not open session to token '%s' " "(model '%s', manufacturer '%s'), skipping: %s\n", redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), redwax_pstrntrim(pool, (const char*) tokenInfo->model, sizeof(tokenInfo->model)), redwax_pstrntrim(pool, (const char*) tokenInfo->manufacturerID, sizeof(tokenInfo->manufacturerID)), pkcs11_errstr(ret)); apr_pool_destroy(pool); return APR_EGENERAL; } s = apr_pcalloc(pool, sizeof(redwax_pkcs11_session_t)); s->module = module; s->session = session; apr_pool_cleanup_register(pool, s, cleanup_session, apr_pool_cleanup_null); status = redwax_p11kit_handle_token_login(r, pool, parsed, module, tokenInfo, slot_id, session, "pkcs11-out", secrets, NULL); if (status != APR_SUCCESS) { return status; } label = r->label_out; if (r->key_out) { for (i = 0; i < r->keys_out->nelts; i++) { redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, redwax_key_t); const unsigned char *id_der = NULL; apr_size_t id_len = 0; const unsigned char *label_der = NULL; apr_size_t label_len = 0; if (r->auto_out && redwax_pk11_key_exists(r, module, session, key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len, &id_der, &id_len, &label_der, &label_len, pool)) { redwax_print_error(r, "pkcs11-out: key with id '%s' / label '%s' already exists, skipping.\n", redwax_pencode_base16_binary(pool, id_der, id_len, REDWAX_ENCODE_LOWER, NULL), redwax_pstrntrim(pool, (const char*) label_der, label_len)); continue; } redwax_print_error(r, "pkcs11-out: key\n"); /* walk the certificates to see if there is a match, if so, we grab * the ID and LABEL from that certificate. */ redwax_pk11_scan_certs(r, key); status = redwax_pkcs11_write_key(r, parsed, module, tokenInfo, session, key, label); if (status != APR_SUCCESS) { return status; } /* we use the label once and once only */ label = NULL; } } label = r->label_out; if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, redwax_certificate_t); if (r->auto_out && cert->x509) { const unsigned char *id_der = NULL; apr_size_t id_len = 0; const unsigned char *label_der = NULL; apr_size_t label_len = 0; redwax_pk11_key_exists(r, module, session, cert->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_len, &cert->x509->kid_der, &cert->x509->kid_len, &cert->common.klabel_der, &cert->common.klabel_len, cert->pool); if (redwax_pk11_cert_exists(r, module, session, cert->der, cert->len, &id_der, &id_len, &label_der, &label_len, pool)) { redwax_print_error(r, "pkcs11-out: certificate '%s' with id '%s' / label '%s' already exists, skipping.\n", cert->common.subject, redwax_pencode_base16_binary(pool, id_der, id_len, REDWAX_ENCODE_LOWER, NULL), redwax_pstrntrim(pool, (const char*) label_der, label_len)); continue; } } redwax_print_error(r, "pkcs11-out: certificate: %s\n", cert->common.subject); status = redwax_pkcs11_write_cert(r, parsed, module, tokenInfo, session, cert, label); if (status != APR_SUCCESS) { return status; } /* we use the label once and once only */ label = NULL; } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, redwax_certificate_t); if (r->auto_out && cert->x509) { const unsigned char *id_der = NULL; apr_size_t id_len = 0; const unsigned char *label_der = NULL; apr_size_t label_len = 0; redwax_pk11_key_exists(r, module, session, cert->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_len, &cert->x509->kid_der, &cert->x509->kid_len, &cert->common.klabel_der, &cert->common.klabel_len, cert->pool); if (redwax_pk11_cert_exists(r, module, session, cert->der, cert->len, &id_der, &id_len, &label_der, &label_len, pool)) { redwax_print_error(r, "pkcs11-out: intermediate '%s' with id '%s' / label '%s' already exists, skipping.\n", cert->common.subject, redwax_pencode_base16_binary(pool, id_der, id_len, REDWAX_ENCODE_LOWER, NULL), redwax_pstrntrim(pool, (const char*) label_der, label_len)); continue; } } redwax_print_error(r, "pkcs11-out: intermediate: %s\n", cert->common.subject); status = redwax_pkcs11_write_cert(r, parsed, module, tokenInfo, session, cert, label); if (status != APR_SUCCESS) { return status; } } } if (r->root_out || r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, redwax_certificate_t); if (r->auto_out && cert->x509) { const unsigned char *id_der = NULL; apr_size_t id_len = 0; const unsigned char *label_der = NULL; apr_size_t label_len = 0; redwax_pk11_key_exists(r, module, session, cert->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_len, &cert->x509->kid_der, &cert->x509->kid_len, &cert->common.klabel_der, &cert->common.klabel_len, cert->pool); if (redwax_pk11_cert_exists(r, module, session, cert->der, cert->len, &id_der, &id_len, &label_der, &label_len, pool)) { redwax_print_error(r, "pkcs11-out: trusted '%s' with id '%s' / label '%s' already exists, skipping.\n", cert->common.subject, redwax_pencode_base16_binary(pool, id_der, id_len, REDWAX_ENCODE_LOWER, NULL), redwax_pstrntrim(pool, (const char*) label_der, label_len)); continue; } } redwax_print_error(r, "pkcs11-out: trusted: %s\n", cert->common.subject); status = redwax_pkcs11_write_cert(r, parsed, module, tokenInfo, session, cert, label); if (status != APR_SUCCESS) { return status; } } } apr_pool_destroy(pool); return APR_SUCCESS; } static const char *redwax_p11kit_origin(redwax_tool_t *r, apr_pool_t *pool, CK_FUNCTION_LIST *module, CK_TOKEN_INFO *tokenInfo, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, const unsigned char **id_der, apr_size_t *id_len, const char **label, apr_size_t *label_len, const char *pin) { P11KitUri *origin_uri; CK_TOKEN_INFO_PTR ck_token_info; char *origin; CK_ATTRIBUTE template[] = { {CKA_ID, NULL_PTR, 0}, {CKA_LABEL, NULL_PTR, 0}, {CKA_CLASS, NULL_PTR, 0} }; int template_len = 3; int ret; origin_uri = p11_kit_uri_new(); apr_pool_cleanup_register(pool, origin_uri, cleanup_p11kituri, apr_pool_cleanup_null); ret = redwax_p11kit_read_attributes(pool, module, session, object, template, template_len); if (ret != CKR_OK) { return NULL; } /* set token */ ck_token_info = p11_kit_uri_get_token_info(origin_uri); memcpy(ck_token_info->label, tokenInfo->label, sizeof(tokenInfo->label)); /* set id */ p11_kit_uri_set_attribute(origin_uri, &template[0]); if (id_der && id_len) { *id_der = template[0].pValue; *id_len = template[0].ulValueLen; } /* set label */ p11_kit_uri_set_attribute(origin_uri, &template[1]); if (label && label_len) { *label = redwax_pstrntrim(pool, (const char*) template[1].pValue, template[1].ulValueLen); *label_len = strlen(*label); } /* set type */ p11_kit_uri_set_attribute(origin_uri, &template[2]); /* set pin-value */ if (pin) { p11_kit_uri_set_pin_value(origin_uri, pin); } if (P11_KIT_URI_OK == p11_kit_uri_format(origin_uri, P11_KIT_URI_FOR_ANY, &origin)) { apr_pool_cleanup_register(pool, origin, cleanup_free, apr_pool_cleanup_null); return origin; } return NULL; } static void redwax_p11kit_check_cert(redwax_tool_t *r, redwax_certificate_t *cert, apr_pool_t *pool) { if (cert->x509) { redwax_certificate_x509_t *x509 = cert->x509; if (!cert->id_der && !x509->skid_der) { redwax_print_error(r, "pkcs11-in: certificate on token '%s' has no ID and no Subject Key " "Identifier, and is therefore unlikely to be found by most software. " "Reading certificate anyway.\n", cert->token); } else if (!cert->id_der && x509->skid_der) { redwax_print_error(r, "pkcs11-in: certificate on token '%s' has no ID, with Subject Key " "Identifier '%s' present, and is therefore unlikely to be found by " "most software. Reading certificate anyway.\n", cert->token, redwax_pencode_base16_binary(pool, cert->id_der, cert->id_len, REDWAX_ENCODE_LOWER, NULL)); } else if (cert->id_der && x509->skid_der && (cert->id_len != x509->skid_len || memcmp(cert->id_der, x509->skid_der, cert->id_len))) { redwax_print_error(r, "pkcs11-in: certificate on token '%s' has ID '%s', but different " "Subject Key Identifier '%s', and is therefore unlikely to be found " "by most software. Reading certificate anyway.\n", cert->token, redwax_pencode_base16_binary(pool, cert->id_der, cert->id_len, REDWAX_ENCODE_LOWER, NULL), redwax_pencode_base16_binary(pool, x509->skid_der, x509->skid_len, REDWAX_ENCODE_LOWER, NULL)); } } } static apr_status_t redwax_p11kit_read_slot(redwax_tool_t *r, P11KitUri *parsed, CK_FUNCTION_LIST *module, CK_TOKEN_INFO *tokenInfo, CK_SLOT_ID_PTR slot_id, apr_hash_t *secrets) { apr_pool_t *pool; CK_SESSION_HANDLE session; CK_ATTRIBUTE_PTR attrs; CK_ULONG n_attrs; CK_OBJECT_HANDLE object; CK_ULONG object_count; const char *pin; redwax_pkcs11_session_t *s; int ret; apr_pool_create(&pool, r->pool); ret = module->C_OpenSession(*slot_id, CKF_SERIAL_SESSION, NULL, NULL, &session); if (ret != CKR_OK) { redwax_print_error(r, "pkcs11-in: could not open session to token '%s' " "(model '%s', manufacturer '%s'), skipping: %s\n", redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), redwax_pstrntrim(pool, (const char*) tokenInfo->model, sizeof(tokenInfo->model)), redwax_pstrntrim(pool, (const char*) tokenInfo->manufacturerID, sizeof(tokenInfo->manufacturerID)), pkcs11_errstr(ret)); apr_pool_destroy(pool); return APR_EGENERAL; } s = apr_pcalloc(pool, sizeof(redwax_pkcs11_session_t)); s->module = module; s->session = session; apr_pool_cleanup_register(pool, s, cleanup_session, apr_pool_cleanup_null); if (r->key_in) { apr_status_t status; status = redwax_p11kit_handle_token_login(r, pool, parsed, module, tokenInfo, slot_id, session, "pkcs11-in", secrets, &pin); if (status != APR_SUCCESS) { apr_pool_destroy(pool); return status; } } attrs = p11_kit_uri_get_attributes (parsed, &n_attrs); ret = module->C_FindObjectsInit(session, attrs, n_attrs); if (ret != CKR_OK) { redwax_print_debug(r, "pkcs11-in: could not find objects on '%s', skipping: %s\n", redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), pkcs11_errstr(ret)); apr_pool_destroy(pool); return APR_EGENERAL; } while (1) { CK_OBJECT_CLASS clazz; CK_ATTRIBUTE class_template[] = { {CKA_CLASS, NULL_PTR, 0} }; int class_template_len = 1; ret = module->C_FindObjects(session, &object, 1, &object_count); if (ret != CKR_OK || object_count == 0) { break; } ret = redwax_p11kit_read_attributes(pool, module, session, object, class_template, class_template_len); if (ret != CKR_OK) { /* ignore anything we cannot read */ continue; } clazz = *(CK_OBJECT_CLASS_PTR)class_template[0].pValue; /* 4.6 Certificate objects */ if (CKO_CERTIFICATE == clazz) { CK_CERTIFICATE_TYPE type; CK_ATTRIBUTE type_template[] = { { CKA_CERTIFICATE_TYPE, NULL_PTR, 0} }; int type_template_len = 1; ret = redwax_p11kit_read_attributes(pool, module, session, object, type_template, type_template_len); if (ret != CKR_OK) { /* ignore anything we cannot read */ continue; } type = *(CK_CERTIFICATE_TYPE *)type_template[0].pValue; /* 4.6.3 X.509 public key certificate objects */ /* 4.6.5 X.509 attribute certificate objects */ if (CKC_X_509 == type || CKC_X_509_ATTR_CERT == type) { CK_ATTRIBUTE cert_template[] = { { CKA_VALUE, NULL_PTR, 0 }, { CKA_TRUSTED, NULL_PTR, 0 }, { CKA_LABEL, NULL_PTR, 0 } }; int cert_template_len = 3; apr_pool_t *p; apr_pool_create(&p, r->pool); redwax_certificate_t *cert = apr_pcalloc(p, sizeof(redwax_certificate_t)); cert->pool = p; ret = redwax_p11kit_read_attributes(cert->pool, module, session, object, cert_template, cert_template_len); if (ret == CKR_OK || ret == CKR_ATTRIBUTE_SENSITIVE || ret == CKR_ATTRIBUTE_TYPE_INVALID) { CK_BBOOL trusted = (sizeof(CK_BBOOL) == cert_template[1].ulValueLen) ? *(CK_BBOOL*) cert_template[1].pValue : 0; cert->common.type = REDWAX_CERTIFICATE_X509; if (CK_UNAVAILABLE_INFORMATION != cert_template[0].ulValueLen) { cert->der = cert_template[0].pValue; cert->len = cert_template[0].ulValueLen; } if (CK_UNAVAILABLE_INFORMATION != cert_template[2].ulValueLen) { cert->label = cert_template[2].pValue; cert->label_len = cert_template[2].ulValueLen; } rt_run_normalise_certificate(r, cert, 1); cert->origin = redwax_p11kit_origin(r, cert->pool, module, tokenInfo, session, object, &cert->id_der, &cert->id_len, &cert->label, &cert->label_len, NULL); cert->token = redwax_pstrntrim(cert->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)); cert->token_len = strlen(cert->token); redwax_p11kit_check_cert(r, cert, pool); if (REDWAX_CERTIFICATE_ROOT == cert->common.category && trusted) { cert->common.category = REDWAX_CERTIFICATE_TRUSTED; } switch (cert->common.category) { case REDWAX_CERTIFICATE_END_ENTITY: { redwax_certificate_t *c = apr_array_push(r->certs_in); memcpy(c, cert, sizeof(*cert)); redwax_print_error(r, "pkcs11-in: certificate: %s\n", cert->common.subject); break; } case REDWAX_CERTIFICATE_INTERMEDIATE: { redwax_certificate_t *c = apr_array_push(r->intermediates_in); memcpy(c, cert, sizeof(*cert)); redwax_print_error(r, "pkcs11-in: intermediate: %s\n", cert->common.subject); break; } case REDWAX_CERTIFICATE_ROOT: { redwax_certificate_t *c = apr_array_push(r->intermediates_in); memcpy(c, cert, sizeof(*cert)); redwax_print_error(r, "pkcs11-in: root: %s\n", cert->common.subject); break; } case REDWAX_CERTIFICATE_TRUSTED: { redwax_certificate_t *c = apr_array_push(r->trusted_in); memcpy(c, cert, sizeof(*cert)); redwax_print_error(r, "pkcs11-in: trusted: %s\n", cert->common.subject); break; } default: { redwax_print_debug(r, "pkcs11-in: unrecognised " "certificate, skipped: %s\n", cert->common.subject); break; } } } } /* 4.6.4 WTLS public key certificate objects */ else if (CKC_WTLS == type) { redwax_print_debug(r, "pkcs11-in: WTLS certificate found on '%s', skipping\n", redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); } /* all other certs */ else { redwax_print_debug(r, "pkcs11-in: Certificate '%s' with type %d not understood, skipping\n", redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)), (int)type); } } else if (CKO_PUBLIC_KEY == clazz) { /* we ignore public keys for now */ } else if (CKO_PRIVATE_KEY == clazz) { CK_KEY_TYPE type; CK_ATTRIBUTE type_template[] = { {CKA_KEY_TYPE, NULL_PTR, 0} }; int type_template_len = 1; ret = redwax_p11kit_read_attributes(pool, module, session, object, type_template, type_template_len); if (ret != CKR_OK) { /* ignore anything we cannot read */ continue; } type = *(CK_KEY_TYPE *)type_template[0].pValue; /* 4.9.1 RSA private key objects */ if (CKK_RSA == type) { redwax_key_t *key; CK_ATTRIBUTE key_template[] = { { CKA_MODULUS, NULL_PTR, 0 }, { CKA_PUBLIC_EXPONENT, NULL_PTR, 0 }, { CKA_PRIVATE_EXPONENT, NULL_PTR, 0 }, { CKA_PRIME_1, NULL_PTR, 0 }, { CKA_PRIME_2, NULL_PTR, 0 }, { CKA_EXPONENT_1, NULL_PTR, 0 }, { CKA_EXPONENT_2, NULL_PTR, 0 }, { CKA_COEFFICIENT, NULL_PTR, 0 } }; int key_template_len = 8; key = apr_array_push(r->keys_in); apr_pool_create(&key->pool, r->keys_in->pool); key->common.type = REDWAX_KEY_RSA; ret = redwax_p11kit_read_attributes(key->pool, module, session, object, key_template, key_template_len); if (ret == CKR_OK || ret == CKR_ATTRIBUTE_SENSITIVE || ret == CKR_ATTRIBUTE_TYPE_INVALID) { key->rsa = apr_pcalloc(key->pool, sizeof(redwax_key_rsa_t)); if (CK_UNAVAILABLE_INFORMATION != key_template[0].ulValueLen) { key->rsa->modulus_len = key_template[0].ulValueLen; key->rsa->modulus = key_template[0].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->modulus, key->rsa->modulus_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[1].ulValueLen) { key->rsa->public_exponent_len = key_template[1].ulValueLen; key->rsa->public_exponent = key_template[1].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->public_exponent, key->rsa->public_exponent_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[2].ulValueLen) { key->rsa->private_exponent_len = key_template[2].ulValueLen; key->rsa->private_exponent = key_template[2].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->private_exponent, key->rsa->private_exponent_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[3].ulValueLen) { key->rsa->prime_1_len = key_template[3].ulValueLen; key->rsa->prime_1 = key_template[3].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->prime_1, key->rsa->prime_1_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[4].ulValueLen) { key->rsa->prime_2_len = key_template[4].ulValueLen; key->rsa->prime_2 = key_template[4].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->prime_2, key->rsa->prime_2_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[5].ulValueLen) { key->rsa->exponent_1_len = key_template[5].ulValueLen; key->rsa->exponent_1 = key_template[5].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->exponent_1, key->rsa->exponent_1_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[6].ulValueLen) { key->rsa->exponent_2_len = key_template[6].ulValueLen; key->rsa->exponent_2 = key_template[6].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->exponent_2, key->rsa->exponent_2_len); #endif } if (CK_UNAVAILABLE_INFORMATION != key_template[7].ulValueLen) { key->rsa->coefficient_len = key_template[7].ulValueLen; key->rsa->coefficient = key_template[7].pValue; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(key->pool, key->rsa->coefficient, key->rsa->coefficient_len); #endif } rt_run_normalise_key(r, key, 1); redwax_print_error(r, "pkcs11-in: private key: %s\n", redwax_pencode_base16_binary(pool, key->common.id_der, key->common.id_len, REDWAX_ENCODE_LOWER, NULL)); } key->origin = redwax_p11kit_origin(r, key->pool, module, tokenInfo, session, object, &key->common.id_der, &key->common.id_len, &key->label, &key->label_len, pin); key->token = redwax_pstrntrim(key->pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label)); key->token_len = strlen(key->token); } else { redwax_print_debug(r, "pkcs11-in: Private key type %d on '%s' not understood, skipping\n", (int)type, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); } } else { redwax_print_debug(r, "pkcs11-in: Object %lu found on '%s', skipping\n", clazz, redwax_pstrntrim(pool, (const char*) tokenInfo->label, sizeof(tokenInfo->label))); } /* all other object types are ignored for now */ } module->C_FindObjectsFinal (session); apr_pool_destroy(pool); return APR_SUCCESS; } static apr_status_t redwax_p11kit_process_pkcs11_in(redwax_tool_t *r, const char *url, apr_hash_t *secrets) { apr_pool_t *pool; P11KitUri *parsed; CK_FUNCTION_LIST **modules; const char *module_name; const char *module_path; int i, j; int ret; apr_status_t status; apr_pool_create(&pool, r->pool); /* start with some url parsing */ parsed = p11_kit_uri_new(); apr_pool_cleanup_register(r->pool, parsed, cleanup_p11kituri, apr_pool_cleanup_null); ret = p11_kit_uri_parse(url, P11_KIT_URI_FOR_ANY, parsed); switch (ret) { case P11_KIT_URI_OK: { break; } case P11_KIT_URI_BAD_SCHEME: case P11_KIT_URI_BAD_SYNTAX: case P11_KIT_URI_BAD_VERSION: case P11_KIT_URI_BAD_ENCODING: default: /* url prefix bad, giving up */ redwax_print_error(r, "pkcs11-in: url '%s' could not be parsed, giving up: %s\n", url, p11_kit_uri_message(ret)); r->rc = APR_BADARG; return APR_BADARG; } /* now for some module loading */ module_name = p11_kit_uri_get_module_name(parsed); module_path = p11_kit_uri_get_module_path(parsed); if (module_name || module_path) { const char *mod; modules = apr_pcalloc(r->pool, 2 * sizeof(CK_FUNCTION_LIST *)); if (module_path) { mod = apr_pstrcat(r->pool, module_path, "/", module_name, MODULE_EXT, NULL); } else { mod = apr_pstrcat(r->pool, module_name, MODULE_EXT, NULL); } CK_FUNCTION_LIST *module = p11_kit_module_load(mod, 0); if (module) { p11_kit_module_initialize(module); apr_pool_cleanup_register(r->pool, module, cleanup_module, apr_pool_cleanup_null); modules[0] = module; } else { redwax_print_error(r, "pkcs11-in: module '%s' could not be loaded, giving up\n", mod); return APR_EDSOOPEN; } } else if (r->pkcs11_in.pkcs11_modules->nelts) { modules = apr_pcalloc(r->pool, (r->pkcs11_in.pkcs11_modules->nelts + 1) * sizeof(CK_FUNCTION_LIST *)); for (i = 0, j = 0; i < r->pkcs11_in.pkcs11_modules->nelts; i++) { const char *mod = APR_ARRAY_IDX(r->pkcs11_in.pkcs11_modules, i, const char *); CK_FUNCTION_LIST *module = p11_kit_module_load(mod, 0); if (module) { p11_kit_module_initialize(module); apr_pool_cleanup_register(r->pool, module, cleanup_module, apr_pool_cleanup_null); modules[j++] = module; } else { redwax_print_error(r, "pkcs11-in: module '%s' could not be loaded, giving up\n", mod); return APR_EDSOOPEN; } } } else { modules = global_modules; } /* match the modules to the url */ for (i = 0; modules[i]; i++) { CK_INFO info; CK_ULONG ulCount; CK_SLOT_ID_PTR pSlotList; CK_SLOT_INFO slotInfo; CK_TOKEN_INFO tokenInfo; ret = modules[i]->C_GetInfo(&info); if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_module_info(parsed, &info)) { continue; } /* match slots to the url */ ret = modules[i]->C_GetSlotList(CK_FALSE, NULL_PTR, &ulCount); if (ret != CKR_OK) { return APR_EGENERAL; } if (!ulCount) { continue; } pSlotList = apr_palloc(pool, ulCount * sizeof(CK_SLOT_ID)); ret = modules[i]->C_GetSlotList(CK_FALSE, pSlotList, &ulCount); for (j = 0; j < ulCount; j++) { ret = modules[i]->C_GetSlotInfo(pSlotList[j], &slotInfo); if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_slot_info(parsed, &slotInfo)) { continue; } /* match tokens to the url */ ret = modules[i]->C_GetTokenInfo(pSlotList[j], &tokenInfo); if (ret == CKR_TOKEN_NOT_PRESENT) { redwax_print_debug(r, "pkcs11-in: token in slot '%lu' not present, skipping.\n", pSlotList[j]); continue; } else if (ret != CKR_OK) { return APR_EGENERAL; } if (!(tokenInfo.flags & CKF_TOKEN_INITIALIZED)) { redwax_print_error(r, "pkcs11-in: token in slot '%lu' not yet initialised, skipping.\n", pSlotList[j]); continue; } if (!p11_kit_uri_match_token_info(parsed, &tokenInfo)) { continue; } status = redwax_p11kit_read_slot(r, parsed, modules[i], &tokenInfo, &pSlotList[j], secrets); if (status != APR_SUCCESS) { return status; } } } return APR_SUCCESS; } static apr_status_t redwax_p11kit_process_pkcs11_out(redwax_tool_t *r, const char *url, apr_hash_t *secrets) { apr_pool_t *pool; P11KitUri *parsed; CK_FUNCTION_LIST **modules; const char *module_name; const char *module_path; int i, j; int ret; apr_status_t status; apr_pool_create(&pool, r->pool); /* start with some url parsing */ parsed = p11_kit_uri_new(); apr_pool_cleanup_register(r->pool, parsed, cleanup_p11kituri, apr_pool_cleanup_null); ret = p11_kit_uri_parse(url, P11_KIT_URI_FOR_ANY, parsed); switch (ret) { case P11_KIT_URI_OK: { break; } case P11_KIT_URI_BAD_SCHEME: case P11_KIT_URI_BAD_SYNTAX: case P11_KIT_URI_BAD_VERSION: case P11_KIT_URI_BAD_ENCODING: default: /* url prefix bad, giving up */ redwax_print_error(r, "pkcs11-out: url '%s' could not be parsed, giving up: %s\n", url, p11_kit_uri_message(ret)); r->rc = APR_BADARG; return APR_BADARG; } /* now for some module loading */ module_name = p11_kit_uri_get_module_name(parsed); module_path = p11_kit_uri_get_module_path(parsed); if (module_name || module_path) { const char *mod; modules = apr_pcalloc(r->pool, 2 * sizeof(CK_FUNCTION_LIST *)); if (module_path) { mod = apr_pstrcat(r->pool, module_path, "/", module_name, MODULE_EXT, NULL); } else { mod = apr_pstrcat(r->pool, module_name, MODULE_EXT, NULL); } CK_FUNCTION_LIST *module = p11_kit_module_load(mod, 0); if (module) { p11_kit_module_initialize(module); apr_pool_cleanup_register(r->pool, module, cleanup_module, apr_pool_cleanup_null); modules[0] = module; } else { redwax_print_error(r, "pkcs11-out: module '%s' could not be loaded, giving up\n", mod); return APR_EDSOOPEN; } } else if (r->pkcs11_out.pkcs11_modules->nelts) { modules = apr_pcalloc(r->pool, (r->pkcs11_out.pkcs11_modules->nelts + 1) * sizeof(CK_FUNCTION_LIST *)); for (i = 0, j = 0; i < r->pkcs11_out.pkcs11_modules->nelts; i++) { const char *mod = APR_ARRAY_IDX(r->pkcs11_out.pkcs11_modules, i, const char *); CK_FUNCTION_LIST *module = p11_kit_module_load(mod, 0); if (module) { p11_kit_module_initialize(module); apr_pool_cleanup_register(r->pool, module, cleanup_module, apr_pool_cleanup_null); modules[j++] = module; } else { redwax_print_error(r, "pkcs11-out: module '%s' could not be loaded, giving up\n", mod); return APR_EDSOOPEN; } } } else { modules = global_modules; } /* match the modules to the url */ for (i = 0; modules[i]; i++) { CK_INFO info; CK_ULONG ulCount; CK_SLOT_ID_PTR pSlotList; CK_SLOT_INFO slotInfo; CK_TOKEN_INFO tokenInfo; ret = modules[i]->C_GetInfo(&info); if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_module_info(parsed, &info)) { continue; } /* match slots to the url */ ret = modules[i]->C_GetSlotList(CK_FALSE, NULL_PTR, &ulCount); if (ret != CKR_OK) { return APR_EGENERAL; } if (!ulCount) { continue; } pSlotList = apr_palloc(pool, ulCount * sizeof(CK_SLOT_ID)); ret = modules[i]->C_GetSlotList(CK_FALSE, pSlotList, &ulCount); for (j = 0; j < ulCount; j++) { ret = modules[i]->C_GetSlotInfo(pSlotList[j], &slotInfo); if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_slot_info(parsed, &slotInfo)) { continue; } /* match tokens to the url */ ret = modules[i]->C_GetTokenInfo(pSlotList[j], &tokenInfo); if (ret == CKR_TOKEN_NOT_PRESENT) { redwax_print_debug(r, "pkcs11-out: token in slot '%lu' not present, skipping.\n", pSlotList[j]); continue; } else if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_token_info(parsed, &tokenInfo)) { continue; } if (!(tokenInfo.flags & CKF_TOKEN_INITIALIZED)) { redwax_print_error(r, "pkcs11-out: token in slot '%lu' not yet initialised, skipping.\n", pSlotList[j]); continue; } /* handle the slot */ status = redwax_p11kit_handle_slot(r, parsed, modules[i], &tokenInfo, &pSlotList[j], secrets); if (status != APR_SUCCESS) { return status; } } } return APR_SUCCESS; } static void redwax_pk11kit_tokenise_uri(apr_pool_t *p, const char *uri, const char **prefix, const char **name, const char **val, int *query) { if (strncmp(uri, "pkcs11:", strlen("pkcs11:"))) { /* bad url, skip */ *prefix = NULL; *name = NULL; *val = NULL; } else { const char *offset, *pairs; /* skip past the scheme */ pairs = uri + strlen("pkcs11:"); offset = strchr(pairs, '?'); if (offset) { *query = 1; pairs = offset + 1; offset = strrchr(pairs, '&'); if (offset) { pairs = offset + 1; } } else { *query = 0; offset = strrchr(pairs, ';'); if (offset) { pairs = offset + 1; } } offset = strchr(pairs, '='); if (offset) { *prefix = apr_pstrndup(p, uri, pairs - uri); *name = apr_pstrndup(p, pairs, offset - pairs); *val = offset + 1; } else { *prefix = apr_pstrndup(p, uri, pairs - uri); *name = pairs; *val = NULL; } } } static void redwax_pk11kit_add_name(redwax_tool_t *r, const char *match, const char *prefix, const char *name, apr_hash_t *urls) { const char *entry = apr_pstrcat(r->pool, prefix, name, "=", NULL); if (!strncmp(match, name, strlen(match))) { apr_hash_set(urls, entry, APR_HASH_KEY_STRING, entry); } } static void redwax_p11kit_add_url(redwax_tool_t *r, apr_pool_t *pool, const char *match, const char *prefix, const char *name, const char *val, apr_hash_t *urls) { const char *v = redwax_pescape_path(pool, val); const char *entry = apr_pstrcat(r->pool, prefix, name, v, NULL); if (!strncmp(match, v, strlen(match))) { apr_hash_set(urls, entry, APR_HASH_KEY_STRING, entry); } } static void redwax_p11kit_add_bytes(redwax_tool_t *r, apr_pool_t *pool, const char *match, const char *prefix, const char *name, const char *val, apr_ssize_t len, apr_hash_t *urls) { apr_size_t size; char *v; const char *entry; redwax_urlescape_all(NULL, val, len, &size); v = apr_palloc(pool, size); redwax_urlescape_all(v, val, len, NULL); entry = apr_pstrcat(r->pool, prefix, name, v, NULL); if (!strncmp(match, v, strlen(match))) { apr_hash_set(urls, entry, APR_HASH_KEY_STRING, entry); } } static apr_status_t redwax_p11kit_complete_pkcs11(redwax_tool_t *r, const char *url, apr_hash_t *urls) { apr_pool_t *pool; P11KitUri *parsed; const char *prefix, *name, *val; int query; int ret; apr_pool_create(&pool, r->pool); redwax_pk11kit_tokenise_uri(pool, url, &prefix, &name, &val, &query); if (!prefix) { if (!strncmp(url, "pkcs11:", strlen(url))) { apr_hash_set(urls, "pkcs11:", APR_HASH_KEY_STRING, "pkcs11:"); } return APR_SUCCESS; } parsed = p11_kit_uri_new(); apr_pool_cleanup_register(r->pool, parsed, cleanup_p11kituri, apr_pool_cleanup_null); ret = p11_kit_uri_parse(prefix, P11_KIT_URI_FOR_ANY, parsed); switch (ret) { case P11_KIT_URI_OK: { break; } case P11_KIT_URI_BAD_SCHEME: case P11_KIT_URI_BAD_SYNTAX: case P11_KIT_URI_BAD_VERSION: case P11_KIT_URI_BAD_ENCODING: default: /* url prefix bad, given up */ return APR_BADARG; } if (name && !val) { /* * pk11-token = "token" "=" *pk11-pchar * pk11-manuf = "manufacturer" "=" *pk11-pchar * pk11-serial = "serial" "=" *pk11-pchar * pk11-model = "model" "=" *pk11-pchar * pk11-lib-manuf = "library-manufacturer" "=" *pk11-pchar * pk11-lib-desc = "library-description" "=" *pk11-pchar * pk11-lib-ver = "library-version" "=" 1*DIGIT [ "." 1*DIGIT ] * pk11-object = "object" "=" *pk11-pchar * pk11-type = "type" "=" ( "public" / "private" / "cert" / * "secret-key" / "data" ) * pk11-id = "id" "=" *pk11-pchar * pk11-slot-manuf = "slot-manufacturer" "=" *pk11-pchar * pk11-slot-desc = "slot-description" "=" *pk11-pchar * pk11-slot-id = "slot-id" "=" 1*DIGIT * * pk11-pin-source = "pin-source" "=" *pk11-qchar * pk11-pin-value = "pin-value" "=" *pk11-qchar * pk11-module-name = "module-name" "=" *pk11-qchar * pk11-module-path = "module-path" "=" *pk11-qchar */ if (!query) { CK_INFO_PTR ck_info = p11_kit_uri_get_module_info(parsed); CK_SLOT_INFO_PTR ck_slot_info = p11_kit_uri_get_slot_info(parsed); CK_SLOT_ID ck_slot_id = p11_kit_uri_get_slot_id(parsed); CK_TOKEN_INFO_PTR ck_token_info = p11_kit_uri_get_token_info(parsed); CK_ATTRIBUTE_PTR cka_label = p11_kit_uri_get_attribute(parsed, CKA_LABEL); CK_ATTRIBUTE_PTR cka_class = p11_kit_uri_get_attribute(parsed, CKA_CLASS); CK_ATTRIBUTE_PTR cka_id = p11_kit_uri_get_attribute(parsed, CKA_ID); if (!ck_token_info->label[0]) { redwax_pk11kit_add_name(r, name, prefix, "token", urls); } if (!ck_token_info->manufacturerID[0]) { redwax_pk11kit_add_name(r, name, prefix, "manufacturer", urls); } if (!ck_token_info->serialNumber[0]) { redwax_pk11kit_add_name(r, name, prefix, "serial", urls); } if (!ck_token_info->model[0]) { redwax_pk11kit_add_name(r, name, prefix, "model", urls); } if (!ck_info->manufacturerID[0]) { redwax_pk11kit_add_name(r, name, prefix, "library-manufacturer", urls); } if (!ck_info->libraryDescription[0]) { redwax_pk11kit_add_name(r, name, prefix, "library-description", urls); } if (ck_info->libraryVersion.major == 0xff && ck_info->libraryVersion.minor == 0xff) { redwax_pk11kit_add_name(r, name, prefix, "library-version", urls); } if (!cka_label) { redwax_pk11kit_add_name(r, name, prefix, "object", urls); } if (!cka_class) { redwax_pk11kit_add_name(r, name, prefix, "type", urls); } if (!cka_id) { redwax_pk11kit_add_name(r, name, prefix, "id", urls); } if (!ck_slot_info->manufacturerID[0]) { redwax_pk11kit_add_name(r, name, prefix, "slot-manufacturer", urls); } if (!ck_slot_info->slotDescription[0]) { redwax_pk11kit_add_name(r, name, prefix, "slot-description", urls); } if (ck_slot_id == 0xFFFFFFFFFFFFFFFF) { redwax_pk11kit_add_name(r, name, prefix, "slot-id", urls); } } else { const char *module_name = p11_kit_uri_get_module_name(parsed); const char *module_path = p11_kit_uri_get_module_path(parsed); if (!module_name) { redwax_pk11kit_add_name(r, name, prefix, "module-name", urls); } if (!module_path) { redwax_pk11kit_add_name(r, name, prefix, "module-path", urls); } } } else if (name && val) { CK_FUNCTION_LIST **modules; int i, j; /* grab the modules */ if (r->pkcs11_out.pkcs11_modules->nelts) { modules = apr_pcalloc(r->pool, (r->pkcs11_out.pkcs11_modules->nelts + 1) * sizeof(CK_FUNCTION_LIST *)); for (i = 0, j = 0; i < r->pkcs11_out.pkcs11_modules->nelts; i++) { const char *mod = APR_ARRAY_IDX(r->pkcs11_out.pkcs11_modules, i, const char *); CK_FUNCTION_LIST *module = p11_kit_module_load(mod, 0); if (module) { p11_kit_module_initialize(module); apr_pool_cleanup_register(r->pool, module, cleanup_module, apr_pool_cleanup_null); modules[j++] = module; } } } else { modules = global_modules; } /* first step, let's go through each module */ for (i = 0; modules[i]; i++) { CK_INFO info; CK_ULONG ulCount; CK_SLOT_ID_PTR pSlotList; CK_SLOT_INFO slotInfo; CK_TOKEN_INFO tokenInfo; ret = modules[i]->C_GetInfo(&info); if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_module_info(parsed, &info)) { continue; } if (!strcmp(name, "library-manufacturer")) { redwax_p11kit_add_url(r, pool, val, prefix, "library-manufacturer=", redwax_pstrntrim(pool, (const char*) info.manufacturerID, sizeof(info.manufacturerID)), urls); } else if (!strcmp(name, "library-description")) { redwax_p11kit_add_url(r, pool, val, prefix, "library-description=", redwax_pstrntrim(pool, (const char*) info.libraryDescription, sizeof(info.libraryDescription)), urls); } else if (!strcmp(name, "library-version")) { redwax_p11kit_add_url(r, pool, val, prefix, "library-version=", info.libraryVersion.minor ? apr_psprintf(pool, "%d.%d", info.libraryVersion.major, info.libraryVersion.minor) : apr_psprintf(pool, "%d", info.libraryVersion.major), urls); } /* second step, let's go through each slot */ ret = modules[i]->C_GetSlotList(CK_FALSE, NULL_PTR, &ulCount); if (ret != CKR_OK) { return APR_EGENERAL; } if (!ulCount) { continue; } pSlotList = apr_palloc(pool, ulCount * sizeof(CK_SLOT_ID)); ret = modules[i]->C_GetSlotList(CK_FALSE, pSlotList, &ulCount); for (j = 0; j < ulCount; j++) { ret = modules[i]->C_GetSlotInfo(pSlotList[j], &slotInfo); if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_slot_info(parsed, &slotInfo)) { continue; } if (!strcmp(name, "slot-manufacturer")) { redwax_p11kit_add_url(r, pool, val, prefix, "slot-manufacturer=", redwax_pstrntrim(pool, (const char*) slotInfo.manufacturerID, sizeof(slotInfo.manufacturerID)), urls); } else if (!strcmp(name, "slot-description")) { redwax_p11kit_add_url(r, pool, val, prefix, "slot-description=", redwax_pstrntrim(pool, (const char*) slotInfo.slotDescription, sizeof(slotInfo.slotDescription)), urls); } else if (!strcmp(name, "slot-id")) { redwax_p11kit_add_url(r, pool, val, prefix, "slot-id=", apr_psprintf(pool, "%lu", pSlotList[j]), urls); } /* third step, handle token in the slot */ ret = modules[i]->C_GetTokenInfo(pSlotList[j], &tokenInfo); if (ret == CKR_TOKEN_NOT_PRESENT) { continue; } else if (ret != CKR_OK) { return APR_EGENERAL; } if (!p11_kit_uri_match_token_info(parsed, &tokenInfo)) { continue; } if (!strcmp(name, "manufacturer")) { redwax_p11kit_add_url(r, pool, val, prefix, "manufacturer=", redwax_pstrntrim(pool, (const char*) tokenInfo.manufacturerID, sizeof(tokenInfo.manufacturerID)), urls); } else if (!strcmp(name, "model")) { redwax_p11kit_add_url(r, pool, val, prefix, "model=", redwax_pstrntrim(pool, (const char*) tokenInfo.model, sizeof(tokenInfo.model)), urls); } else if (!strcmp(name, "serial")) { redwax_p11kit_add_url(r, pool, val, prefix, "serial=", redwax_pstrntrim(pool, (const char*) tokenInfo.serialNumber, sizeof(tokenInfo.serialNumber)), urls); } else if (!strcmp(name, "token")) { redwax_p11kit_add_url(r, pool, val, prefix, "token=", redwax_pstrntrim(pool, (const char*) tokenInfo.label, sizeof(tokenInfo.label)), urls); } else if (!strcmp(name, "type")) { redwax_p11kit_add_url(r, pool, val, prefix, "type=", "cert", urls); redwax_p11kit_add_url(r, pool, val, prefix, "type=", "data", urls); redwax_p11kit_add_url(r, pool, val, prefix, "type=", "private", urls); redwax_p11kit_add_url(r, pool, val, prefix, "type=", "public", urls); redwax_p11kit_add_url(r, pool, val, prefix, "type=", "secret-key", urls); } else if (!strcmp(name, "id") || !strcmp(name, "object")) { CK_SESSION_HANDLE session; CK_OBJECT_HANDLE object; CK_ULONG object_count; ret = modules[i]->C_OpenSession(pSlotList[j], CKF_SERIAL_SESSION, NULL, NULL, &session); if (ret != CKR_OK) { continue; } ret = modules[i]->C_FindObjectsInit (session, NULL_PTR, 0); if (ret != CKR_OK) { modules[i]->C_CloseSession (session); continue; } while (1) { CK_ATTRIBUTE template[] = { {CKA_ID, NULL_PTR, 0}, {CKA_LABEL, NULL_PTR, 0}, {CKA_CLASS, NULL_PTR, 0} }; ret = modules[i]->C_FindObjects(session, &object, 1, &object_count); if (ret != CKR_OK || object_count == 0) { break; } ret = modules[i]->C_GetAttributeValue(session, object, &template[0], 3); if (ret == CKR_OK) { template[0].pValue = apr_palloc(pool, template[0].ulValueLen); template[1].pValue = apr_palloc(pool, template[1].ulValueLen); template[2].pValue = apr_palloc(pool, template[2].ulValueLen); ret = modules[i]->C_GetAttributeValue(session, object, &template[0], 3); if (ret == CKR_OK) { if (!p11_kit_uri_match_attributes(parsed, &template[0], 3)) { continue; } if (!strcmp(name, "id")) { redwax_p11kit_add_bytes(r, pool, val, prefix, "id=", (const char*) template[0].pValue, template[0].ulValueLen, urls); } if (!strcmp(name, "object")) { redwax_p11kit_add_url(r, pool, val, prefix, "object=", redwax_pstrntrim(r->pool, (const char*) template[1].pValue, template[1].ulValueLen), urls); } } } } modules[i]->C_FindObjectsFinal (session); modules[i]->C_CloseSession (session); } } } } return APR_SUCCESS; } static apr_status_t redwax_p11kit_complete_pkcs11_module(redwax_tool_t *r, const char *mod, redwax_token_quoted_e quoted) { apr_dir_t *thedir; apr_finfo_t dirent; const char *rootpath, *filepath; char *dir, *base, *prefix; apr_status_t status; int base_len; int abs; filepath = mod; if (APR_SUCCESS == apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_NATIVE, r->pool)) { abs = 1; } else { abs = 0; } if (!abs) { base = apr_pstrdup(r->pool, mod); dir = P11_MODULE_PATH; prefix = ""; } else if (mod[strlen(mod) - 1] == '/') { base = ""; dir = apr_pstrdup(r->pool, mod); prefix = dir; } else { base = basename(apr_pstrdup(r->pool, mod)); dir = dirname(apr_pstrdup(r->pool, mod)); if (!strcmp(dir, "/")) { prefix = "/"; } else { prefix = apr_pstrcat(r->pool, dir, "/", NULL); } } base_len = strlen(base); if ((status = apr_dir_open(&thedir, dir, r->pool)) != APR_SUCCESS) { return status; } do { status = apr_dir_read(&dirent, APR_FINFO_TYPE | APR_FINFO_NAME | APR_FINFO_WPROT, thedir); if (APR_STATUS_IS_INCOMPLETE(status)) { continue; /* ignore un-stat()able files */ } else if (status != APR_SUCCESS) { break; } if (!strncmp(dirent.name, ".", 1)) { continue; } if (!strcmp(dirent.name, ".") || !strcmp(dirent.name, "..")) { continue; } switch (dirent.filetype) { case APR_LNK: case APR_REG: { const char *ext = strrchr(dirent.name, '.'); if ((!strncmp(base, dirent.name, base_len)) && (ext && (!strcasecmp(ext, ".so") || !strcasecmp(ext, ".dll") || !strcasecmp(ext, ".dylib")))) { char *path; if (APR_SUCCESS == apr_filepath_merge(&path, dir, dirent.name, APR_FILEPATH_SECUREROOT, r->pool)) { CK_FUNCTION_LIST *module = p11_kit_module_load(path, 0); if (module) { apr_file_printf(r->out, "%s \n", redwax_pescape_echo_quoted(r->pool, apr_pstrcat(r->pool, prefix, dirent.name, NULL), quoted, 1)); p11_kit_module_release(module); } } } break; } case APR_DIR: { if (abs && !strncmp(base, dirent.name, base_len)) { apr_file_printf(r->out, "%s/\n", redwax_pescape_echo_quoted(r->pool, apr_pstrcat(r->pool, prefix, dirent.name, NULL), quoted, 0)); } break; } default: continue; } } while (1); apr_dir_close(thedir); return APR_SUCCESS; } void redwax_add_default_p11kit_hooks() { rt_hook_initialise(redwax_p11kit_initialise, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_pkcs11_in(redwax_p11kit_process_pkcs11_in, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_pkcs11_out(redwax_p11kit_process_pkcs11_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_pkcs11_in(redwax_p11kit_complete_pkcs11, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_pkcs11_out(redwax_p11kit_complete_pkcs11, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_pkcs11_module_in(redwax_p11kit_complete_pkcs11_module, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_pkcs11_module_out(redwax_p11kit_complete_pkcs11_module, NULL, NULL, APR_HOOK_MIDDLE); } #else void redwax_add_default_p11kit_hooks() { } #endif REDWAX_DECLARE_MODULE(p11kit) = { STANDARD_MODULE_STUFF, redwax_add_default_p11kit_hooks /* register hooks */ };