/*
* 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, 2011, Oracle and/or its affiliates. All rights reserved.
*/
#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.
* Return (CK_STATE)-1 if pslot->sl_state is invalid.
*/
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;
} else { /* invalid */
state = (CK_STATE)-1;
}
(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);
if (pInfo->state == (CK_STATE)-1) {
rv = CKR_ARGUMENTS_BAD;
}
/*
* Decrement the session reference count.
*/
REFRELE(session_p, ses_lock_held);
return (rv);
}
/*
* 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);
if (ses_state == (CK_STATE)-1) {
REFRELE(session_p, ses_lock_held);
return (CKR_ARGUMENTS_BAD);
}
(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);
if (ses_state == (CK_STATE)-1) {
REFRELE(session_p, ses_lock_held);
return (CKR_ARGUMENTS_BAD);
}
(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);
}