root/usr/src/lib/pkcs11/pkcs11_softtoken/common/softSessionUtil.c
/*
 * 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 <md5.h>
#include <pthread.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/sha1.h>
#include <security/cryptoki.h>
#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;
        }
}