/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2020 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include "softGlobal.h" #include "softSession.h" #include "softObject.h" #include "softOps.h" #include "softKeystore.h" #include "softKeystoreUtil.h" CK_ULONG soft_session_cnt = 0; /* the number of opened sessions */ CK_ULONG soft_session_rw_cnt = 0; /* the number of opened R/W sessions */ #define DIGEST_MECH_OK(_m_) ((_m_) == CKM_MD5 || (_m_) == CKM_SHA_1) /* * Delete all the sessions. First, obtain the global session * list lock. Then start to delete one session at a time. * Release the global session list lock before returning to * caller. */ CK_RV soft_delete_all_sessions(boolean_t force) { CK_RV rv = CKR_OK; CK_RV rv1; soft_session_t *session_p; soft_session_t *session_p1; /* Acquire the global session list lock */ (void) pthread_mutex_lock(&soft_sessionlist_mutex); session_p = soft_session_list; /* Delete all the sessions in the session list */ while (session_p) { session_p1 = session_p->next; /* * Delete a session by calling soft_delete_session() * with a session pointer and a boolean arguments. * Boolean value TRUE is used to indicate that the * caller holds the lock on the global session list. * */ rv1 = soft_delete_session(session_p, force, B_TRUE); /* Record the very first error code */ if (rv == CKR_OK) { rv = rv1; } session_p = session_p1; } /* No session left */ soft_session_list = NULL; /* Release the global session list lock */ (void) pthread_mutex_unlock(&soft_sessionlist_mutex); return (rv); } /* * Create a new session struct, and add it to the session linked list. * * This function will acquire the global session list lock, and release * it after adding the session to the session linked list. */ CK_RV soft_add_session(CK_FLAGS flags, CK_VOID_PTR pApplication, CK_NOTIFY notify, CK_ULONG *sessionhandle_p) { soft_session_t *new_sp = NULL; /* Allocate a new session struct */ new_sp = calloc(1, sizeof (soft_session_t)); if (new_sp == NULL) { return (CKR_HOST_MEMORY); } new_sp->magic_marker = SOFTTOKEN_SESSION_MAGIC; new_sp->pApplication = pApplication; new_sp->Notify = notify; new_sp->flags = flags; new_sp->state = CKS_RO_PUBLIC_SESSION; new_sp->object_list = NULL; new_sp->ses_refcnt = 0; new_sp->ses_close_sync = 0; (void) pthread_mutex_lock(&soft_giant_mutex); if (soft_slot.authenticated) { (void) pthread_mutex_unlock(&soft_giant_mutex); if (flags & CKF_RW_SESSION) { new_sp->state = CKS_RW_USER_FUNCTIONS; } else { new_sp->state = CKS_RO_USER_FUNCTIONS; } } else { (void) pthread_mutex_unlock(&soft_giant_mutex); if (flags & CKF_RW_SESSION) { new_sp->state = CKS_RW_PUBLIC_SESSION; } else { new_sp->state = CKS_RO_PUBLIC_SESSION; } } /* Initialize the lock for the newly created session */ if (pthread_mutex_init(&new_sp->session_mutex, NULL) != 0) { free(new_sp); return (CKR_CANT_LOCK); } (void) pthread_cond_init(&new_sp->ses_free_cond, NULL); /* Acquire the global session list lock */ (void) pthread_mutex_lock(&soft_sessionlist_mutex); /* Generate a unique session handle. */ do { arc4random_buf(&new_sp->handle, sizeof (new_sp->handle)); if (new_sp->handle == CK_INVALID_HANDLE) continue; } while (avl_find(&soft_session_tree, new_sp, NULL) != NULL); avl_add(&soft_session_tree, new_sp); *sessionhandle_p = new_sp->handle; /* Insert the new session in front of session list */ if (soft_session_list == NULL) { soft_session_list = new_sp; new_sp->next = NULL; new_sp->prev = NULL; } else { soft_session_list->prev = new_sp; new_sp->next = soft_session_list; new_sp->prev = NULL; soft_session_list = new_sp; } ++soft_session_cnt; if (flags & CKF_RW_SESSION) ++soft_session_rw_cnt; if (soft_session_cnt == 1) /* * This is the first session to be opened, so we can set * validate the public token objects in token list now. */ soft_validate_token_objects(B_TRUE); /* Release the global session list lock */ (void) pthread_mutex_unlock(&soft_sessionlist_mutex); return (CKR_OK); } /* * This function adds the to-be-freed session to a linked list. * When the number of sessions queued in the linked list reaches the * maximum threshold MAX_SES_TO_BE_FREED, it will free the first * session (FIFO) in the list. */ void session_delay_free(soft_session_t *sp) { soft_session_t *tmp; (void) pthread_mutex_lock(&ses_delay_freed.ses_to_be_free_mutex); /* Add the newly deleted session at the end of the list */ sp->next = NULL; if (ses_delay_freed.first == NULL) { ses_delay_freed.last = sp; ses_delay_freed.first = sp; } else { ses_delay_freed.last->next = sp; ses_delay_freed.last = sp; } if (++ses_delay_freed.count >= MAX_SES_TO_BE_FREED) { /* * Free the first session in the list only if * the total count reaches maximum threshold. */ ses_delay_freed.count--; tmp = ses_delay_freed.first->next; free(ses_delay_freed.first); ses_delay_freed.first = tmp; } (void) pthread_mutex_unlock(&ses_delay_freed.ses_to_be_free_mutex); } /* * Delete a session: * - Remove the session from the session linked list. * Holding the lock on the global session list is needed to do this. * - Release all the objects created by the session. * * The boolean argument lock_held is used to indicate that whether * the caller of this function holds the lock on the global session * list or not. * - When called by soft_delete_all_sessions(), which is called by * C_Finalize() or C_CloseAllSessions() -- the lock_held = TRUE. * - When called by C_CloseSession() -- the lock_held = FALSE. * * When the caller does not hold the lock on the global session * list, this function will acquire that lock in order to proceed, * and also release that lock before returning to caller. */ CK_RV soft_delete_session(soft_session_t *session_p, boolean_t force, boolean_t lock_held) { /* * Check to see if the caller holds the lock on the global * session list. If not, we need to acquire that lock in * order to proceed. */ if (!lock_held) { /* Acquire the global session list lock */ (void) pthread_mutex_lock(&soft_sessionlist_mutex); } /* * Remove the session from the session linked list first. */ if (soft_session_list == session_p) { /* Session is the first one in the list */ if (session_p->next) { soft_session_list = session_p->next; session_p->next->prev = NULL; } else { /* Session is the only one in the list */ soft_session_list = NULL; } } else { /* Session is not the first one in the list */ if (session_p->next) { /* Session is in the middle of the list */ session_p->prev->next = session_p->next; session_p->next->prev = session_p->prev; } else { /* Session is the last one in the list */ session_p->prev->next = NULL; } } avl_remove(&soft_session_tree, session_p); --soft_session_cnt; if (session_p->flags & CKF_RW_SESSION) --soft_session_rw_cnt; if (!lock_held) { /* * If the global session list lock is obtained by * this function, then release that lock after * removing the session from session linked list. * We want the releasing of the objects of the * session, and freeing of the session itself to * be done without holding the global session list * lock. */ (void) pthread_mutex_unlock(&soft_sessionlist_mutex); } /* Acquire the individual session lock */ (void) pthread_mutex_lock(&session_p->session_mutex); /* * Make sure another thread hasn't freed the session. */ if (session_p->magic_marker != SOFTTOKEN_SESSION_MAGIC) { (void) pthread_mutex_unlock(&session_p->session_mutex); return (CKR_OK); } /* * The deletion of a session must be blocked when the session * reference count is not zero. This means if any session related * operation starts prior to the session close operation gets in, * the session closing thread must wait for the non-closing * operation to be completed before it can proceed the close * operation. * * Unless we are being forced to shut everything down, this only * happens if the libraries _fini() is running not of someone * explicitly called C_Finalize(). */ if (force) session_p->ses_refcnt = 0; while (session_p->ses_refcnt != 0) { /* * We set the SESSION_REFCNT_WAITING flag before we put * this closing thread in a wait state, so other non-closing * operation thread will signal to wake it up only when * the session reference count becomes zero and this flag * is set. */ session_p->ses_close_sync |= SESSION_REFCNT_WAITING; (void) pthread_cond_wait(&session_p->ses_free_cond, &session_p->session_mutex); } session_p->ses_close_sync &= ~SESSION_REFCNT_WAITING; /* * Remove all the objects created in this session. */ soft_delete_all_objects_in_session(session_p, force); /* * Mark session as no longer valid. This can only be done after all * objects created by this session are free'd since the marker is * still needed in the process of removing objects from the session. */ session_p->magic_marker = 0; (void) pthread_cond_destroy(&session_p->ses_free_cond); /* In case application did not call Final */ if (session_p->digest.context != NULL) free(session_p->digest.context); if (session_p->encrypt.context != NULL) /* * 1st B_TRUE: encrypt * 2nd B_TRUE: caller is holding session_mutex. */ soft_crypt_cleanup(session_p, B_TRUE, B_TRUE); if (session_p->decrypt.context != NULL) /* * 1st B_FALSE: decrypt * 2nd B_TRUE: caller is holding session_mutex. */ soft_crypt_cleanup(session_p, B_FALSE, B_TRUE); if (session_p->sign.context != NULL) free(session_p->sign.context); if (session_p->verify.context != NULL) free(session_p->verify.context); if (session_p->find_objects.context != NULL) { find_context_t *fcontext; fcontext = (find_context_t *)session_p->find_objects.context; free(fcontext->objs_found); free(fcontext); } /* Reset SESSION_IS_CLOSING flag. */ session_p->ses_close_sync &= ~SESSION_IS_CLOSING; (void) pthread_mutex_unlock(&session_p->session_mutex); /* Destroy the individual session lock */ (void) pthread_mutex_destroy(&session_p->session_mutex); /* Delay freeing the session */ session_delay_free(session_p); return (CKR_OK); } /* * This function is used to type cast a session handle to a pointer to * the session struct. Also, it does the following things: * 1) Check to see if the session struct is tagged with a session * magic number. This is to detect when an application passes * a bogus session pointer. * 2) Acquire the lock on the designated session. * 3) Check to see if the session is in the closing state that another * thread is performing. * 4) Increment the session reference count by one. This is to prevent * this session from being closed by other thread. * 5) Release the lock held on the designated session. */ CK_RV handle2session(CK_SESSION_HANDLE hSession, soft_session_t **session_p) { soft_session_t *sp; soft_session_t node; /* * No need to hold soft_sessionlist_mutex as we are * just reading the value and 32-bit reads are atomic. */ if (all_sessions_closing) { return (CKR_SESSION_CLOSED); } (void) memset(&node, 0, sizeof (node)); node.handle = hSession; (void) pthread_mutex_lock(&soft_sessionlist_mutex); sp = avl_find(&soft_session_tree, &node, NULL); if ((sp == NULL) || (sp->magic_marker != SOFTTOKEN_SESSION_MAGIC)) { (void) pthread_mutex_unlock(&soft_sessionlist_mutex); return (CKR_SESSION_HANDLE_INVALID); } (void) pthread_mutex_lock(&sp->session_mutex); (void) pthread_mutex_unlock(&soft_sessionlist_mutex); if (sp->ses_close_sync & SESSION_IS_CLOSING) { (void) pthread_mutex_unlock(&sp->session_mutex); return (CKR_SESSION_CLOSED); } /* Increment session ref count. */ sp->ses_refcnt++; (void) pthread_mutex_unlock(&sp->session_mutex); *session_p = sp; return (CKR_OK); } /* * The format to be saved in the pOperationState will be: * 1. internal_op_state_t * 2. crypto_active_op_t * 3. actual context of the active operation */ CK_RV soft_get_operationstate(soft_session_t *session_p, CK_BYTE_PTR pOperationState, CK_ULONG_PTR pulOperationStateLen) { internal_op_state_t *p_op_state; CK_ULONG op_data_len = 0; CK_RV rv = CKR_OK; if (pulOperationStateLen == NULL) return (CKR_ARGUMENTS_BAD); (void) pthread_mutex_lock(&session_p->session_mutex); /* Check to see if encrypt operation is active. */ if (session_p->encrypt.flags & CRYPTO_OPERATION_ACTIVE) { rv = CKR_STATE_UNSAVEABLE; goto unlock_session; } /* Check to see if decrypt operation is active. */ if (session_p->decrypt.flags & CRYPTO_OPERATION_ACTIVE) { rv = CKR_STATE_UNSAVEABLE; goto unlock_session; } /* Check to see if sign operation is active. */ if (session_p->sign.flags & CRYPTO_OPERATION_ACTIVE) { rv = CKR_STATE_UNSAVEABLE; goto unlock_session; } /* Check to see if verify operation is active. */ if (session_p->verify.flags & CRYPTO_OPERATION_ACTIVE) { rv = CKR_STATE_UNSAVEABLE; goto unlock_session; } /* Check to see if digest operation is active. */ if (session_p->digest.flags & CRYPTO_OPERATION_ACTIVE) { op_data_len = sizeof (internal_op_state_t) + sizeof (crypto_active_op_t); switch (session_p->digest.mech.mechanism) { case CKM_MD5: op_data_len += sizeof (MD5_CTX); break; case CKM_SHA_1: op_data_len += sizeof (SHA1_CTX); break; default: rv = CKR_STATE_UNSAVEABLE; goto unlock_session; } if (pOperationState == NULL_PTR) { *pulOperationStateLen = op_data_len; goto unlock_session; } else { if (*pulOperationStateLen < op_data_len) { *pulOperationStateLen = op_data_len; rv = CKR_BUFFER_TOO_SMALL; goto unlock_session; } } /* Save internal_op_state_t */ /* LINTED E_BAD_PTR_CAST_ALIGN */ p_op_state = (internal_op_state_t *)pOperationState; p_op_state->op_len = op_data_len; p_op_state->op_active = DIGEST_OP; p_op_state->op_session_state = session_p->state; /* Save crypto_active_op_t */ (void) memcpy((CK_BYTE *)pOperationState + sizeof (internal_op_state_t), &session_p->digest, sizeof (crypto_active_op_t)); switch (session_p->digest.mech.mechanism) { case CKM_MD5: /* Save MD5_CTX for the active digest operation */ (void) memcpy((CK_BYTE *)pOperationState + sizeof (internal_op_state_t) + sizeof (crypto_active_op_t), session_p->digest.context, sizeof (MD5_CTX)); break; case CKM_SHA_1: /* Save SHA1_CTX for the active digest operation */ (void) memcpy((CK_BYTE *)pOperationState + sizeof (internal_op_state_t) + sizeof (crypto_active_op_t), session_p->digest.context, sizeof (SHA1_CTX)); break; default: rv = CKR_STATE_UNSAVEABLE; } } else { rv = CKR_OPERATION_NOT_INITIALIZED; goto unlock_session; } *pulOperationStateLen = op_data_len; unlock_session: (void) pthread_mutex_unlock(&session_p->session_mutex); return (rv); } static CK_BYTE_PTR alloc_digest(CK_ULONG mech) { CK_BYTE_PTR ret_val; switch (mech) { case CKM_MD5: ret_val = (CK_BYTE_PTR) malloc(sizeof (MD5_CTX)); break; case CKM_SHA_1: ret_val = (CK_BYTE_PTR) malloc(sizeof (SHA1_CTX)); break; default: ret_val = NULL; } return (ret_val); } /* * The format to be restored from the pOperationState will be: * 1. internal_op_state_t * 2. crypto_active_op_t * 3. actual context of the saved operation */ CK_RV soft_set_operationstate(soft_session_t *session_p, CK_BYTE_PTR pOperationState, CK_ULONG ulOperationStateLen, CK_OBJECT_HANDLE hEncryptionKey, CK_OBJECT_HANDLE hAuthenticationKey) { CK_RV rv = CKR_OK; internal_op_state_t *p_op_state; crypto_active_op_t *p_active_op; CK_ULONG offset = 0; CK_ULONG mech; void *free_it = NULL; /* LINTED E_BAD_PTR_CAST_ALIGN */ p_op_state = (internal_op_state_t *)pOperationState; if (p_op_state->op_len != ulOperationStateLen) { /* * The supplied data length does not match with * the saved data length. */ return (CKR_SAVED_STATE_INVALID); } if (p_op_state->op_active != DIGEST_OP) return (CKR_SAVED_STATE_INVALID); if ((hAuthenticationKey != 0) || (hEncryptionKey != 0)) { return (CKR_KEY_NOT_NEEDED); } offset = sizeof (internal_op_state_t); /* LINTED E_BAD_PTR_CAST_ALIGN */ p_active_op = (crypto_active_op_t *)(pOperationState + offset); offset += sizeof (crypto_active_op_t); mech = p_active_op->mech.mechanism; if (!DIGEST_MECH_OK(mech)) { return (CKR_SAVED_STATE_INVALID); } /* * We may reuse digest.context in case the digest mechanisms (the one, * which belongs to session and the operation, which we are restoring) * are the same. If digest mechanisms are different, we have to release * the digest context, which belongs to session and allocate a new one. */ (void) pthread_mutex_lock(&session_p->session_mutex); if (session_p->state != p_op_state->op_session_state) { /* * The supplied session state does not match with * the saved session state. */ rv = CKR_SAVED_STATE_INVALID; goto unlock_session; } if (session_p->digest.context && (session_p->digest.mech.mechanism != mech)) { free_it = session_p->digest.context; session_p->digest.context = NULL; } if (session_p->digest.context == NULL) { session_p->digest.context = alloc_digest(mech); if (session_p->digest.context == NULL) { /* * put back original context into session in case * allocation of new context has failed. */ session_p->digest.context = free_it; free_it = NULL; rv = CKR_HOST_MEMORY; goto unlock_session; } } /* Restore crypto_active_op_t */ session_p->digest.mech.mechanism = mech; session_p->digest.flags = p_active_op->flags; switch (mech) { case CKM_MD5: /* Restore MD5_CTX from the saved digest operation */ (void) memcpy((CK_BYTE *)session_p->digest.context, (CK_BYTE *)pOperationState + offset, sizeof (MD5_CTX)); break; case CKM_SHA_1: /* Restore SHA1_CTX from the saved digest operation */ (void) memcpy((CK_BYTE *)session_p->digest.context, (CK_BYTE *)pOperationState + offset, sizeof (SHA1_CTX)); break; default: /* never reached */ rv = CKR_SAVED_STATE_INVALID; } unlock_session: (void) pthread_mutex_unlock(&session_p->session_mutex); if (free_it != NULL) free(free_it); return (rv); } CK_RV soft_login(CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen) { /* * Authenticate the input PIN. */ return (soft_verify_pin(pPin, ulPinLen)); } void soft_logout(void) { /* * Delete all the private token objects from the "token_object_list". */ soft_delete_all_in_core_token_objects(PRIVATE_TOKEN); return; } void soft_acquire_all_session_mutexes(soft_session_t *session_p) { /* Iterate through sessions acquiring all mutexes */ while (session_p) { soft_object_t *object_p; (void) pthread_mutex_lock(&session_p->session_mutex); object_p = session_p->object_list; /* Lock also all objects related to session */ while (object_p) { (void) pthread_mutex_lock(&object_p->object_mutex); object_p = object_p->next; } session_p = session_p->next; } } void soft_release_all_session_mutexes(soft_session_t *session_p) { /* Iterate through sessions releasing all mutexes */ while (session_p) { /* * N.B. Ideally, should go in opposite order to guarantee * lock-order requirements but there is no tail pointer. */ soft_object_t *object_p = session_p->object_list; /* Unlock also all objects related to session */ while (object_p) { (void) pthread_mutex_unlock(&object_p->object_mutex); object_p = object_p->next; } (void) pthread_mutex_unlock(&session_p->session_mutex); session_p = session_p->next; } }