root/usr/src/lib/pkcs11/pkcs11_softtoken/common/softSession.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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <pthread.h>
#include <security/cryptoki.h>
#include "softGlobal.h"
#include "softSession.h"
#include "softObject.h"
#include "softKeystore.h"
#include "softKeystoreUtil.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;

        if (!softtoken_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 (slotID != SOFTTOKEN_SLOTID)
                return (CKR_SLOT_ID_INVALID);

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

        /*
         * softtoken has no limit on the number of concurrent sessions
         * that the token allows. No need to check to see if the
         * token has too many sessions already open.
         */

        /* Create a new session */
        rv = soft_add_session(flags, pApplication, Notify, phSession);

        return (rv);

}

CK_RV
C_CloseSession(CK_SESSION_HANDLE hSession)
{

        CK_RV rv;

        soft_session_t *session_p;
        boolean_t lock_held = B_TRUE;

        if (!softtoken_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);
        /*
         * Set SESSION_IS_CLOSING flag so any access to this
         * session will be rejected.
         */
        if (session_p->ses_close_sync & SESSION_IS_CLOSING) {
                SES_REFRELE(session_p, 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 SES_REFRELE()
         * will release the session lock for us.
         */
        SES_REFRELE(session_p, lock_held);

        /*
         * Delete a session by calling soft_delete_session() with
         * a session pointer and a boolean arguments. Boolean
         * value FALSE is used to indicate that the caller does not
         * hold the lock on the global session list and also that
         * this is not a forced session close but an explicit request.
         *
         * soft_delete_session() will reset SESSION_IS_CLOSING
         * flag after it is done.
         */
        rv = soft_delete_session(session_p, B_FALSE, B_FALSE);

        if (soft_session_cnt == 0) {
                /* Clean up private token objects from the token object list */
                soft_delete_all_in_core_token_objects(PRIVATE_TOKEN);
                /*
                 * Invalidate public token object handles instead of
                 * deleting them.
                 */
                soft_validate_token_objects(B_FALSE);
                (void) pthread_mutex_lock(&soft_giant_mutex);
                soft_slot.authenticated = 0;
                soft_slot.userpin_change_needed = 0;
                (void) pthread_mutex_unlock(&soft_giant_mutex);
        }

        return (rv);
}


CK_RV
C_CloseAllSessions(CK_SLOT_ID slotID)
{

        CK_RV rv = CKR_OK;

        if (!softtoken_initialized)
                return (CKR_CRYPTOKI_NOT_INITIALIZED);

        if (slotID != SOFTTOKEN_SLOTID)
                return (CKR_SLOT_ID_INVALID);

        /* Acquire the global session list lock */
        (void) pthread_mutex_lock(&soft_sessionlist_mutex);
        /*
         * Set all_sessions_closing flag so any access to any
         * existing sessions will be rejected.
         */
        all_sessions_closing = 1;
        (void) pthread_mutex_unlock(&soft_sessionlist_mutex);

        /* Delete all the sessions and release the allocated resources */
        rv = soft_delete_all_sessions(B_FALSE);

        /* Clean up private token objects from the token object list */
        soft_delete_all_in_core_token_objects(PRIVATE_TOKEN);

        /* Invalidate public token object handles instead of deleting them */
        soft_validate_token_objects(B_FALSE);

        (void) pthread_mutex_lock(&soft_giant_mutex);
        soft_slot.authenticated = 0;
        soft_slot.userpin_change_needed = 0;
        (void) pthread_mutex_unlock(&soft_giant_mutex);

        (void) pthread_mutex_lock(&soft_sessionlist_mutex);
        /* Reset all_sessions_closing flag. */
        all_sessions_closing = 0;
        (void) pthread_mutex_unlock(&soft_sessionlist_mutex);

        return (rv);
}

CK_RV
C_GetSessionInfo(CK_SESSION_HANDLE hSession, CK_SESSION_INFO_PTR pInfo)
{

        soft_session_t *session_p;
        CK_RV rv;
        boolean_t lock_held = B_TRUE;

        if (!softtoken_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);

        if (pInfo == NULL) {
                lock_held = B_FALSE;
                rv = CKR_ARGUMENTS_BAD;
                goto clean_exit;
        }

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

        /* Provide information for the specified session */
        pInfo->slotID = SOFTTOKEN_SLOTID;
        pInfo->state = session_p->state;
        pInfo->flags = session_p->flags;
        pInfo->ulDeviceError = 0;

clean_exit:
        /*
         * Decrement the session reference count.
         * We hold the session lock, and SES_REFRELE()
         * will release the session lock for us.
         */
        SES_REFRELE(session_p, lock_held);

        return (rv);
}


CK_RV
C_GetOperationState(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pOperationState,
    CK_ULONG_PTR pulOperationStateLen)
{
        soft_session_t *session_p;
        CK_RV rv;
        boolean_t lock_held = B_FALSE;

        if (!softtoken_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);

        /*
         * Only check if pulOperationStateLen is NULL_PTR.
         * No need to check if pOperationState is NULL_PTR because
         * application might just ask for the length of buffer to hold
         * the OperationState.
         */
        if (pulOperationStateLen == NULL_PTR) {
                rv = CKR_ARGUMENTS_BAD;
                goto clean_exit;
        }

        rv = soft_get_operationstate(session_p, pOperationState,
            pulOperationStateLen);

clean_exit:
        SES_REFRELE(session_p, lock_held);
        return (rv);

}


CK_RV
C_SetOperationState(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pOperationState,
    CK_ULONG ulOperationStateLen, CK_OBJECT_HANDLE hEncryptionKey,
    CK_OBJECT_HANDLE hAuthenticationKey)
{
        soft_session_t *session_p;
        CK_RV rv;
        boolean_t lock_held = B_FALSE;

        if (!softtoken_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);

        if ((pOperationState == NULL_PTR) ||
            (ulOperationStateLen == 0)) {
                rv = CKR_ARGUMENTS_BAD;
                goto clean_exit;
        }

        rv = soft_set_operationstate(session_p, pOperationState,
            ulOperationStateLen, hEncryptionKey, hAuthenticationKey);

clean_exit:
        SES_REFRELE(session_p, lock_held);
        return (rv);
}

CK_RV
C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR_PTR pPin,
    CK_ULONG ulPinLen)
{

        soft_session_t *session_p, *sp;
        CK_RV rv;
        boolean_t lock_held = B_FALSE;

        if (!softtoken_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);

        /* Check the load status of keystore */
        if (!soft_keystore_status(KEYSTORE_LOAD)) {
                SES_REFRELE(session_p, lock_held);
                return (CKR_DEVICE_REMOVED);
        }

        if (userType != CKU_USER) {
                SES_REFRELE(session_p, lock_held);
                return (CKR_USER_TYPE_INVALID);
        }

        if ((ulPinLen < MIN_PIN_LEN) || (ulPinLen > MAX_PIN_LEN)) {
                SES_REFRELE(session_p, lock_held);
                return (CKR_PIN_LEN_RANGE);
        }

        if (pPin == NULL_PTR) {
                /*
                 * We don't support CKF_PROTECTED_AUTHENTICATION_PATH
                 */
                SES_REFRELE(session_p, lock_held);
                return (CKR_ARGUMENTS_BAD);
        }

        (void) pthread_mutex_lock(&soft_giant_mutex);
        if (soft_slot.authenticated) {
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                SES_REFRELE(session_p, lock_held);
                return (CKR_USER_ALREADY_LOGGED_IN);
        }

        rv = soft_login(pPin, ulPinLen);
        if (rv == CKR_OK) {
                if (soft_slot.userpin_change_needed) {
                        /*
                         * This is the special case when the PIN is never
                         * initialized in the keystore, which will always
                         * return CKR_OK with "userpin_change_needed" set.
                         */
                        (void) pthread_mutex_unlock(&soft_giant_mutex);
                        SES_REFRELE(session_p, lock_held);
                        return (rv);
                }

                soft_slot.authenticated = 1;
                (void) pthread_mutex_unlock(&soft_giant_mutex);
        } else {
                (void) pthread_mutex_unlock(&soft_giant_mutex);
                SES_REFRELE(session_p, lock_held);
                return (rv);
        }

        /*
         * Load all the private token objects from keystore.
         */
        rv = soft_get_token_objects_from_keystore(PRI_TOKENOBJS);
        if (rv != CKR_OK) {
                SES_REFRELE(session_p, lock_held);
                return (rv);
        }

        /* Acquire the global session list lock */
        (void) pthread_mutex_lock(&soft_sessionlist_mutex);

        sp = soft_session_list;

        while (sp) {
                (void) pthread_mutex_lock(&sp->session_mutex);

                if (sp->flags & CKF_RW_SESSION) {
                        sp->state = CKS_RW_USER_FUNCTIONS;
                } else {
                        sp->state = CKS_RO_USER_FUNCTIONS;
                }
                (void) pthread_mutex_unlock(&sp->session_mutex);
                sp = sp->next;
        }

        (void) pthread_mutex_unlock(&soft_sessionlist_mutex);

        SES_REFRELE(session_p, lock_held);
        return (rv);

}

CK_RV
C_Logout(CK_SESSION_HANDLE hSession)
{

        soft_session_t *session_p, *sp;
        CK_RV rv;
        boolean_t lock_held = B_FALSE;

        if (!softtoken_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(&soft_giant_mutex);
        if (!soft_slot.authenticated) {
                if (!soft_slot.userpin_change_needed) {
                        /*
                         * Only if the PIN has been initialized in the keystore.
                         */
                        (void) pthread_mutex_unlock(&soft_giant_mutex);
                        SES_REFRELE(session_p, lock_held);
                        return (CKR_USER_NOT_LOGGED_IN);
                } else {
                        soft_slot.userpin_change_needed = 0;
                        (void) pthread_mutex_unlock(&soft_giant_mutex);
                        SES_REFRELE(session_p, lock_held);
                        return (CKR_OK);
                }
        }

        soft_logout();
        soft_slot.authenticated = 0;
        (void) pthread_mutex_unlock(&soft_giant_mutex);

        /* Acquire the global session list lock */
        (void) pthread_mutex_lock(&soft_sessionlist_mutex);

        sp = soft_session_list;

        while (sp) {
                (void) pthread_mutex_lock(&sp->session_mutex);

                if (sp->flags & CKF_RW_SESSION) {
                        sp->state = CKS_RW_PUBLIC_SESSION;
                } else {
                        sp->state = CKS_RO_PUBLIC_SESSION;
                }
                (void) pthread_mutex_unlock(&sp->session_mutex);
                sp = sp->next;
        }

        (void) pthread_mutex_unlock(&soft_sessionlist_mutex);

        SES_REFRELE(session_p, lock_held);
        return (rv);

}