root/usr/src/lib/pkcs11/pkcs11_kernel/common/kernelSession.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.
 */

#include <pthread.h>
#include <errno.h>
#include <security/cryptoki.h>
#include <sys/crypto/ioctl.h>
#include "kernelGlobal.h"
#include "kernelSession.h"
#include "kernelSlot.h"
#include "kernelEmulate.h"

CK_RV
C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication,
    CK_NOTIFY Notify, CK_SESSION_HANDLE_PTR phSession)
{
        CK_RV rv = CKR_OK;
        kernel_slot_t   *pslot;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        /*
         * For legacy reasons, the CKF_SERIAL_SESSION bit must always
         * be set.
         */
        if (!(flags & CKF_SERIAL_SESSION))
                return (CKR_SESSION_PARALLEL_NOT_SUPPORTED);

        if (phSession == NULL)
                return (CKR_ARGUMENTS_BAD);

        if (slotID >= slot_count) {
                return (CKR_SLOT_ID_INVALID);
        }

        /*
         * Acquire the slot lock to protect sl_state and sl_sess_list.
         * These two fields need to be protected atomically, even though
         * "sl_sess_list" is updated in kernel_add_session().
         */
        pslot = slot_table[slotID];
        (void) pthread_mutex_lock(&pslot->sl_mutex);

        /* If SO is logged in the slot, only the RW session is allowed. */
        if ((pslot->sl_state == CKU_SO) && !(flags & CKF_RW_SESSION)) {
                (void) pthread_mutex_unlock(&pslot->sl_mutex);
                return (CKR_SESSION_READ_WRITE_SO_EXISTS);
        }

        /* Create a new session */
        rv = kernel_add_session(slotID, flags, pApplication, Notify,
            phSession);
        (void) pthread_mutex_unlock(&pslot->sl_mutex);
        return (rv);
}

CK_RV
C_CloseSession(CK_SESSION_HANDLE hSession)
{
        CK_RV rv;

        kernel_session_t *session_p;
        boolean_t ses_lock_held = B_FALSE;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        /*
         * Obtain the session pointer. Also, increment the session
         * reference count.
         */
        rv = handle2session(hSession, &session_p);
        if (rv != CKR_OK)
                return (rv);

        (void) pthread_mutex_lock(&session_p->session_mutex);
        ses_lock_held = B_TRUE;

        /*
         * Set SESSION_IS_CLOSING flag so any access to this
         * session will be rejected.
         */
        if (session_p->ses_close_sync & SESSION_IS_CLOSING) {
                REFRELE(session_p, ses_lock_held);
                return (CKR_SESSION_CLOSED);
        }
        session_p->ses_close_sync |= SESSION_IS_CLOSING;

        /*
         * Decrement the session reference count.
         * We hold the session lock, and REFRELE()
         * will release the session lock for us.
         */
        REFRELE(session_p, ses_lock_held);

        /*
         * Delete a session by calling kernel_delete_session() with
         * a session pointer and two boolean arguments. The 3rd argument
         * boolean value FALSE indicates that the caller does not
         * hold the slot lock.  The 4th argument boolean value B_FALSE
         * indicates that we want to delete all the objects completely.
         *
         * kernel_delete_session() will reset SESSION_IS_CLOSING
         * flag after it is done.
         */
        kernel_delete_session(session_p->ses_slotid, session_p, B_FALSE,
            B_FALSE);
        return (rv);
}


CK_RV
C_CloseAllSessions(CK_SLOT_ID slotID)
{
        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        /* Delete all the sessions and release the allocated resources */
        kernel_delete_all_sessions(slotID, B_FALSE);

        return (CKR_OK);
}

/*
 * Utility routine to get CK_STATE value for a session.
 * The caller should not be holding the session lock.
 */
static CK_STATE
get_ses_state(kernel_session_t *session_p)
{
        CK_STATE state;
        kernel_slot_t *pslot;

        pslot = slot_table[session_p->ses_slotid];
        (void) pthread_mutex_lock(&pslot->sl_mutex);

        if (pslot->sl_state == CKU_PUBLIC) {
                state = (session_p->ses_RO) ?
                    CKS_RO_PUBLIC_SESSION : CKS_RW_PUBLIC_SESSION;
        } else if (pslot->sl_state == CKU_USER) {
                state = (session_p->ses_RO) ?
                    CKS_RO_USER_FUNCTIONS : CKS_RW_USER_FUNCTIONS;
        } else if (pslot->sl_state == CKU_SO) {
                state = CKS_RW_SO_FUNCTIONS;
        }

        (void) pthread_mutex_unlock(&pslot->sl_mutex);

        return (state);
}


CK_RV
C_GetSessionInfo(CK_SESSION_HANDLE hSession, CK_SESSION_INFO_PTR pInfo)
{
        kernel_session_t *session_p;
        CK_RV rv;
        boolean_t ses_lock_held = B_FALSE;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        if (pInfo == NULL)
                return (CKR_ARGUMENTS_BAD);

        /*
         * Obtain the session pointer. Also, increment the session
         * reference count.
         */
        rv = handle2session(hSession, &session_p);
        if (rv != CKR_OK)
                return (rv);

        /* Provide information for the specified session */
        pInfo->slotID = session_p->ses_slotid;
        pInfo->flags = session_p->flags;
        pInfo->ulDeviceError = 0;
        pInfo->state = get_ses_state(session_p);

        /*
         * Decrement the session reference count.
         */
        REFRELE(session_p, ses_lock_held);

        return (CKR_OK);
}

/*
 * Save the state in pOperationState. The data format is:
 * 1. Total length (including this field)
 * 2. session state
 * 3. crypto_active_op_t structure
 * 4. digest_buf_t's data buffer contents
 */
static CK_RV
kernel_get_operationstate(kernel_session_t *session_p, CK_STATE ses_state,
    CK_BYTE_PTR pOperationState, CK_ULONG_PTR pulOperationStateLen)
{
        int op_data_len = 0;
        CK_BYTE_PTR dst;
        digest_buf_t *bufp;

        if (!(session_p->digest.flags & CRYPTO_EMULATE)) {
                /*
                 * Return CKR_OPERATION_NOT_INITIALIZED if the slot
                 * is capable of C_GetOperationState(). Return
                 * CKR_FUNCTION_NOT_SUPPORTED otherwise.
                 *
                 * We return these codes because some clients
                 * check the return code to determine if C_GetOperationState()
                 * is supported.
                 */
                if (slot_table[session_p->ses_slotid]->sl_flags &
                    CRYPTO_LIMITED_HASH_SUPPORT)
                        return (CKR_OPERATION_NOT_INITIALIZED);
                else
                        return (CKR_FUNCTION_NOT_SUPPORTED);
        }

        /*
         * XXX Need to support this case in future.
         * This is the case where we exceeded SLOT_HASH_MAX_INDATA_LEN and
         * hence started using libmd. SLOT_HASH_MAX_INDATA_LEN is at least
         * 64K for current crypto framework providers and web servers
         * do not need to clone digests that big for SSL operations.
         */
        if (session_p->digest.flags & CRYPTO_EMULATE_USING_SW) {
                return (CKR_STATE_UNSAVEABLE);
        }

        /* Check to see if this is an unsupported operation. */
        if (session_p->encrypt.flags & CRYPTO_OPERATION_ACTIVE ||
            session_p->decrypt.flags & CRYPTO_OPERATION_ACTIVE ||
            session_p->sign.flags & CRYPTO_OPERATION_ACTIVE ||
            session_p->verify.flags & CRYPTO_OPERATION_ACTIVE) {
                return (CKR_STATE_UNSAVEABLE);
        }

        /* Check to see if digest operation is active. */
        if (!(session_p->digest.flags & CRYPTO_OPERATION_ACTIVE)) {
                return (CKR_OPERATION_NOT_INITIALIZED);
        }

        bufp = session_p->digest.context;

        op_data_len =  sizeof (int);
        op_data_len +=  sizeof (CK_STATE);
        op_data_len += sizeof (crypto_active_op_t);
        op_data_len += bufp->indata_len;

        if (pOperationState == NULL_PTR) {
                *pulOperationStateLen = op_data_len;
                return (CKR_OK);
        } else {
                if (*pulOperationStateLen < op_data_len) {
                        *pulOperationStateLen = op_data_len;
                        return (CKR_BUFFER_TOO_SMALL);
                }
        }

        dst = pOperationState;

        /* Save total length */
        bcopy(&op_data_len, dst, sizeof (int));
        dst += sizeof (int);

        /* Save session state */
        bcopy(&ses_state, dst, sizeof (CK_STATE));
        dst += sizeof (CK_STATE);

        /* Save crypto_active_op_t */
        bcopy(&session_p->digest, dst, sizeof (crypto_active_op_t));
        dst += sizeof (crypto_active_op_t);

        /* Save the data buffer */
        bcopy(bufp->buf, dst, bufp->indata_len);

        *pulOperationStateLen = op_data_len;
        return (CKR_OK);
}

CK_RV
C_GetOperationState(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pOperationState,
    CK_ULONG_PTR pulOperationStateLen)
{
        CK_RV rv;
        CK_STATE ses_state;
        kernel_session_t *session_p;
        boolean_t ses_lock_held = B_TRUE;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        if (pulOperationStateLen == NULL_PTR)
                return (CKR_ARGUMENTS_BAD);

        /*
         * Obtain the session pointer. Also, increment the session
         * reference count.
         */
        rv = handle2session(hSession, &session_p);
        if (rv != CKR_OK)
                return (rv);

        ses_state = get_ses_state(session_p);

        (void) pthread_mutex_lock(&session_p->session_mutex);
        rv = kernel_get_operationstate(session_p, ses_state,
            pOperationState, pulOperationStateLen);

        REFRELE(session_p, ses_lock_held);
        return (rv);
}

/*
 * Restore the state from pOperationState. The data format is:
 * 1. Total length (including this field)
 * 2. session state
 * 3. crypto_active_op_t structure
 * 4. digest_buf_t's data buffer contents
 */
static CK_RV
kernel_set_operationstate(kernel_session_t *session_p, CK_STATE ses_state,
    CK_BYTE_PTR pOperationState, CK_ULONG ulOperationStateLen,
    CK_OBJECT_HANDLE hEncryptionKey, CK_OBJECT_HANDLE hAuthenticationKey)
{
        CK_RV rv;
        CK_BYTE_PTR src;
        CK_STATE src_ses_state;
        int expected_len, indata_len;
        digest_buf_t *bufp;
        crypto_active_op_t tmp_op;

        if ((hAuthenticationKey != 0) || (hEncryptionKey != 0))
                return (CKR_KEY_NOT_NEEDED);

        src = pOperationState;

        /* Get total length field */
        bcopy(src, &expected_len, sizeof (int));
        if (ulOperationStateLen < expected_len)
                return (CKR_SAVED_STATE_INVALID);

        /* compute the data buffer length */
        indata_len = expected_len - sizeof (int) -
            sizeof (CK_STATE) - sizeof (crypto_active_op_t);
        if (indata_len > SLOT_HASH_MAX_INDATA_LEN(session_p))
                return (CKR_SAVED_STATE_INVALID);
        src += sizeof (int);

        /* Get session state */
        bcopy(src, &src_ses_state, sizeof (CK_STATE));
        if (ses_state != src_ses_state)
                return (CKR_SAVED_STATE_INVALID);
        src += sizeof (CK_STATE);

        /*
         * Restore crypto_active_op_t. We need to use a temporary
         * buffer to avoid modifying the source session's buffer.
         */
        bcopy(src, &tmp_op, sizeof (crypto_active_op_t));
        if (tmp_op.flags & CRYPTO_EMULATE_USING_SW)
                return (CKR_SAVED_STATE_INVALID);
        session_p->digest.mech = tmp_op.mech;
        session_p->digest.flags = tmp_op.flags;
        src += sizeof (crypto_active_op_t);

        /* This routine reuses the session's existing buffer if possible */
        rv = emulate_buf_init(session_p, indata_len, OP_DIGEST);
        if (rv != CKR_OK)
                return (rv);
        bufp = session_p->digest.context;
        bufp->indata_len = indata_len;

        /* Restore the data buffer */
        bcopy(src, bufp->buf, bufp->indata_len);

        return (CKR_OK);
}


CK_RV
C_SetOperationState(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pOperationState,
    CK_ULONG ulOperationStateLen, CK_OBJECT_HANDLE hEncryptionKey,
    CK_OBJECT_HANDLE hAuthenticationKey)
{
        CK_RV rv;
        CK_STATE ses_state;
        kernel_session_t *session_p;
        boolean_t ses_lock_held = B_TRUE;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        if ((pOperationState == NULL_PTR) ||
            (ulOperationStateLen == 0))
                return (CKR_ARGUMENTS_BAD);

        rv = handle2session(hSession, &session_p);
        if (rv != CKR_OK)
                return (rv);

        ses_state = get_ses_state(session_p);

        (void) pthread_mutex_lock(&session_p->session_mutex);

        rv = kernel_set_operationstate(session_p, ses_state,
            pOperationState, ulOperationStateLen,
            hEncryptionKey, hAuthenticationKey);

        REFRELE(session_p, ses_lock_held);
        return (rv);
}


CK_RV
C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType,
    CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen)
{
        CK_RV   rv = CKR_OK;
        kernel_session_t *session_p;
        kernel_slot_t   *pslot;
        boolean_t ses_lock_held = B_FALSE;
        crypto_login_t  c_login;
        int r;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        if ((userType != CKU_SO) && (userType != CKU_USER)) {
                return (CKR_USER_TYPE_INVALID);
        }

        /*
         * Obtain the session pointer. Also, increment the session
         * reference count.
         */
        rv = handle2session(hSession, &session_p);
        if (rv != CKR_OK)
                return (rv);

        /* Acquire the slot lock */
        pslot = slot_table[session_p->ses_slotid];
        (void) pthread_mutex_lock(&pslot->sl_mutex);

        /* Check if the slot is logged in already */
        if ((pslot->sl_state == CKU_USER) || (pslot->sl_state == CKU_SO)) {
                rv = CKR_USER_ALREADY_LOGGED_IN;
                goto clean_exit;
        }

        /* To login as SO, every session in this slot needs to be R/W */
        if (userType == CKU_SO) {
                kernel_session_t  *sp;
                boolean_t       found;

                found = B_FALSE;
                sp = pslot->sl_sess_list;
                while (sp) {
                        /*
                         * Need not to lock individual sessions before
                         * accessing their "ses_RO" and "next" fields,
                         * because they are always accessed under the
                         * slot's mutex protection.
                         */
                        if (sp->ses_RO) {
                                found = B_TRUE;
                                break;
                        }
                        sp = sp->next;
                }

                if (found) {
                        rv = CKR_SESSION_READ_ONLY_EXISTS;
                        goto clean_exit;
                }
        }

        /* Now make the ioctl call; no need to acquire the session lock. */
        c_login.co_session = session_p->k_session;
        c_login.co_user_type = userType;
        c_login.co_pin_len = ulPinLen;
        c_login.co_pin = (char *)pPin;

        while ((r = ioctl(kernel_fd, CRYPTO_LOGIN, &c_login)) < 0) {
                if (errno != EINTR)
                        break;
        }
        if (r < 0) {
                rv = CKR_FUNCTION_FAILED;
        } else {
                rv = crypto2pkcs11_error_number(c_login.co_return_value);
        }

        if (rv == CKR_OK) {
                /* Set the slot's session state. */
                pslot->sl_state = userType;
        }

clean_exit:

        REFRELE(session_p, ses_lock_held);
        (void) pthread_mutex_unlock(&pslot->sl_mutex);
        return (rv);
}


CK_RV
C_Logout(CK_SESSION_HANDLE hSession)
{
        CK_RV   rv = CKR_OK;
        kernel_session_t *session_p;
        kernel_slot_t   *pslot;
        boolean_t ses_lock_held = B_FALSE;
        crypto_logout_t  c_logout;
        int r;

        if (!kernel_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        /*
         * Obtain the session pointer. Also, increment the session
         * reference count.
         */
        rv = handle2session(hSession, &session_p);
        if (rv != CKR_OK)
                return (rv);

        /* Acquire the slot lock. */
        pslot = slot_table[session_p->ses_slotid];
        (void) pthread_mutex_lock(&pslot->sl_mutex);

        /* Check if the user or SO was logged in  */
        if (pslot->sl_state == CKU_PUBLIC) {
                rv = CKR_USER_NOT_LOGGED_IN;
                goto clean_exit;
        }

        /* Now make the ioctl call. No need to acquire the session lock. */
        c_logout.cl_session = session_p->k_session;
        while ((r = ioctl(kernel_fd, CRYPTO_LOGOUT, &c_logout)) < 0) {
                if (errno != EINTR)
                        break;
        }
        if (r < 0) {
                rv = CKR_FUNCTION_FAILED;
        } else {
                rv = crypto2pkcs11_error_number(c_logout.cl_return_value);
        }

        if (rv != CKR_OK) {
                goto clean_exit;
        }

        /*
         * If this slot was logged in as USER previously, we need to clean up
         * all private object wrappers in library for this slot.
         */
        kernel_cleanup_pri_objects_in_slot(pslot, session_p);

        if (rv == CKR_OK) {
                /* Reset the slot's session state. */
                pslot->sl_state = CKU_PUBLIC;
        }

clean_exit:
        REFRELE(session_p, ses_lock_held);
        (void) pthread_mutex_unlock(&pslot->sl_mutex);
        return (rv);
}