/*
* 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) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
*/
#include <stdio.h>
#include <malloc.h>
#include <memory.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cryptoutil.h>
#include <unistd.h>
#include <utmpx.h>
#include <pthread.h>
#include <pwd.h>
#include <sha2.h>
#include <security/cryptoki.h>
#include <aes_impl.h>
#include <sys/avl.h>
#include "kmsSession.h"
#include "kmsGlobal.h"
#include "kmsObject.h"
static CK_RV
GetPKCS11StatusFromAgentStatus(KMS_AGENT_STATUS status);
static char keystore_path[BUFSIZ];
static boolean_t keystore_path_initialized = B_FALSE;
static time_t last_objlist_mtime = 0;
pthread_mutex_t flock_mutex = PTHREAD_MUTEX_INITIALIZER;
static boolean_t initialized = B_FALSE;
static struct flock fl = {
0,
0,
0,
0,
0,
0,
{0, 0, 0, 0}
};
#define KEYSTORE_PATH "/var/user/"
#define ALTERNATE_KEYSTORE_PATH "KMSTOKEN_DIR"
#define KMS_PROFILE_FILENAME "profile.cfg"
#define KMS_DATAUNIT_DESCRIPTION "Oracle PKCS11/KMS"
#define KMS_ATTR_DESC_PFX "PKCS#11v2.20: "
#define KMSTOKEN_CONFIG_FILENAME "kmstoken.cfg"
#define KMSTOKEN_LABELLIST_FILENAME "objlabels.lst"
static void
kms_parse_labels(avl_tree_t *, char *, int);
static void
kms_hash_string(char *label, uchar_t *hash)
{
SHA2_CTX ctx;
SHA2Init(SHA256, &ctx);
SHA2Update(&ctx, label, strlen(label));
SHA2Final(hash, &ctx);
}
static char *
get_username(char *username, int len)
{
struct passwd pwd, *user_info;
long buflen;
char *pwdbuf = NULL;
bzero(username, len);
buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
if (buflen == -1)
return (username); /* should not happen */
pwdbuf = calloc(1, buflen);
if (pwdbuf == NULL)
return (username); /* zero-ed earlier */
user_info = getpwuid_r(getuid(), &pwd, pwdbuf, buflen);
if (user_info != NULL)
(void) strlcpy(username, user_info->pw_name, len);
free(pwdbuf);
return (username);
}
static char *
kms_get_keystore_path()
{
char *env_val;
char username[sizeof (((struct utmpx *)0)->ut_user)];
if (!keystore_path_initialized) {
env_val = getenv(ALTERNATE_KEYSTORE_PATH);
bzero(keystore_path, sizeof (keystore_path));
/*
* If it isn't set or is set to the empty string use the
* default location. We need to check for the empty string
* because some users "unset" environment variables by giving
* them no value, this isn't the same thing as removing it
* from the environment.
*/
if ((env_val == NULL) || (strcmp(env_val, "") == 0)) {
/*
* Alternate path not specified,
* use /var/user/$USER/kms
*/
(void) snprintf(keystore_path,
sizeof (keystore_path), "%s/%s/kms",
KEYSTORE_PATH,
get_username(username, sizeof (username)));
} else {
(void) strlcpy(keystore_path, env_val,
sizeof (keystore_path));
}
keystore_path_initialized = B_TRUE;
}
return (keystore_path);
}
static char *
get_non_comment_line(char *cfgbuf, size_t cfglen, char *buf, size_t buflen)
{
char *s = cfgbuf;
char *end = cfgbuf + cfglen;
char *f;
/* Skip over blank lines CR/LF */
while (s < end && (isspace(*s) || (*s == '#'))) {
/* check for comment sign */
if (*s == '#') {
/* skip the rest of the line */
while (*s != '\n' && *s != '\r' && s < end)
s++;
}
if ((s < end) && isspace(*s))
s++;
}
if (s < end) {
char save, *e;
f = s; /* mark the beginning. */
/* Find the end of the line and null terminate it. */
while (*s != '\n' && *s != '\r' && s < end) s++;
save = *s;
*s = 0x00;
(void) strncpy(buf, f, buflen);
*s = save;
/* Strip trailing whitespace */
f = buf;
e = f + strlen(buf) - 1;
while (e >= f && isspace(*e)) {
*e = 0x00;
e--;
}
} else {
/* If we reached the end, return NULL */
s = NULL;
}
done:
return (s);
}
static int
flock_fd(int fd, int cmd, pthread_mutex_t *mutex)
{
int ret = 0;
(void) pthread_mutex_lock(mutex);
fl.l_type = cmd;
while ((ret = fcntl(fd, F_SETLKW, &fl)) == -1) {
if (errno != EINTR)
break;
}
(void) pthread_mutex_unlock(mutex);
return (ret);
}
/*
* Open the keystore description file in the specified mode.
*/
static int
open_and_lock_file(char *filename, int cmd, mode_t mode,
pthread_mutex_t *mutex)
{
int fd;
fd = open_nointr(filename, mode|O_NONBLOCK);
if (fd < 0)
return (fd);
if (flock_fd(fd, cmd, mutex)) {
if (fd > 0)
(void) close(fd);
return (-1);
}
return (fd);
}
static int
kms_slurp_fd(int fd, char *buf, size_t buflen)
{
int n, total = 0;
do {
n = readn_nointr(fd, &buf[total], buflen - total);
if (n != (buflen - total))
break;
else
total += n;
} while (total < buflen);
return (total);
}
static int
kms_slurp_file(char *file, char *buf, size_t buflen)
{
int fd, total = 0;
fd = open_and_lock_file(file, F_RDLCK, O_RDONLY, &flock_mutex);
if (fd == -1)
return (-1);
total = kms_slurp_fd(fd, buf, buflen);
if (flock_fd(fd, F_UNLCK, &flock_mutex))
total = -1;
(void) close(fd);
return (total);
}
/*
* The KMS token is considered "initialized" if the file with the token
* configuration information is present.
*/
CK_BBOOL
kms_is_initialized()
{
CK_BBOOL rv;
char *ksdir;
char cfgfile_path[BUFSIZ];
struct stat statp;
ksdir = kms_get_keystore_path();
if (ksdir == NULL)
return (CKR_FUNCTION_FAILED);
(void) snprintf(cfgfile_path, sizeof (cfgfile_path),
"%s/%s", ksdir, KMSTOKEN_CONFIG_FILENAME);
if (stat(cfgfile_path, &statp))
rv = FALSE;
else
rv = TRUE;
return (rv);
}
static CK_RV
kms_read_config_data(char *path, kms_cfg_info_t *cfginfo)
{
CK_RV rv = CKR_OK;
char *cfgbuf = NULL;
char *ptr;
char buf[BUFSIZ];
size_t buflen = 0, remain;
struct stat statp;
if (path == NULL || cfginfo == NULL)
return (CKR_ARGUMENTS_BAD);
if (stat(path, &statp) == -1) {
return (CKR_FUNCTION_FAILED);
}
cfgbuf = calloc(1, statp.st_size);
if (cfgbuf == NULL)
return (CKR_HOST_MEMORY);
buflen = kms_slurp_file(path, cfgbuf, statp.st_size);
if (buflen != statp.st_size) {
free(cfgbuf);
return (CKR_FUNCTION_FAILED);
}
remain = buflen;
ptr = cfgbuf;
ptr = get_non_comment_line(ptr, remain,
cfginfo->name, sizeof (cfginfo->name));
if (ptr == NULL) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
remain = buflen - (ptr - cfgbuf);
ptr = get_non_comment_line(ptr, remain,
cfginfo->agentId, sizeof (cfginfo->agentId));
if (ptr == 0) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
remain = buflen - (ptr - cfgbuf);
ptr = get_non_comment_line(ptr, remain,
cfginfo->agentAddr, sizeof (cfginfo->agentAddr));
if (ptr == 0) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
remain = buflen - (ptr - cfgbuf);
ptr = get_non_comment_line(ptr, remain, buf, sizeof (buf));
if (ptr == 0) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
cfginfo->transTimeout = atoi(buf);
remain = buflen - (ptr - cfgbuf);
ptr = get_non_comment_line(ptr, remain, buf, sizeof (buf));
if (ptr == 0) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
cfginfo->failoverLimit = atoi(buf);
remain = buflen - (ptr - cfgbuf);
ptr = get_non_comment_line(ptr, remain, buf, sizeof (buf));
if (ptr == 0) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
cfginfo->discoveryFreq = atoi(buf);
remain = buflen - (ptr - cfgbuf);
ptr = get_non_comment_line(ptr, remain, buf, sizeof (buf));
if (ptr == 0) {
rv = CKR_FUNCTION_FAILED;
goto done;
}
cfginfo->securityMode = atoi(buf);
done:
if (cfgbuf != NULL)
free(cfgbuf);
return (rv);
}
CK_BBOOL
kms_is_pin_set()
{
CK_BBOOL rv = TRUE;
kms_cfg_info_t kmscfg;
struct stat statp;
char *ksdir;
char filepath[BUFSIZ];
ksdir = kms_get_keystore_path();
if (ksdir == NULL)
return (FALSE);
(void) snprintf(filepath, sizeof (filepath),
"%s/%s", ksdir, KMSTOKEN_CONFIG_FILENAME);
if ((rv = kms_read_config_data(filepath, &kmscfg)))
return (FALSE);
/*
* The PK12 file is only established once the user has enrolled
* and is thus considered having a PIN set.
*/
(void) snprintf(filepath, sizeof (filepath),
"%s/%s/%s", ksdir, kmscfg.name, CLIENT_PK12_FILE);
if (stat(filepath, &statp))
rv = FALSE; /* file doesn't exist. */
else
rv = TRUE; /* File exists, PIN is set */
return (rv);
}
void
kms_clear_label_list(avl_tree_t *tree)
{
void *cookie = NULL;
objlabel_t *node;
while ((node = avl_destroy_nodes(tree, &cookie)) != NULL) {
free(node->label);
free(node);
}
}
void
add_label_node(avl_tree_t *tree, char *label)
{
avl_index_t where;
objlabel_t *node;
objlabel_t *newnode;
int i;
if (tree == NULL || label == NULL)
return;
/* Remove trailing CR */
i = strlen(label) - 1;
while (i > 0 && label[i] == '\n')
label[i--] = 0x00;
newnode = calloc(1, sizeof (objlabel_t));
newnode->label = (char *)strdup(label);
if (newnode->label == NULL) {
free(newnode);
return;
}
/* see if this entry already exists */
node = avl_find(tree, newnode, &where);
if (node == NULL) {
avl_insert(tree, newnode, where);
} else {
/* It's a dup, don't add it */
free(newnode->label);
free(newnode);
/*
* If its a dup that was already marked for deletion,
* remove it.
*/
if (node->flags & DELETE_FLAG) {
avl_remove(tree, node);
free(node->label);
free(node);
}
}
}
static void
kms_parse_labels(avl_tree_t *tree, char *buf, int buflen)
{
int remain;
char *ptr;
char buffer[BUFSIZ];
/*
* Read each line and add it as a label node.
* We don't need to clear the label list because
* "add_label_node" checks for dups.
*/
remain = buflen;
ptr = buf;
while (remain > 0) {
ptr = get_non_comment_line(ptr, remain,
buffer, sizeof (buffer));
if (ptr == NULL) {
return;
}
add_label_node(tree, buffer);
remain = buflen - (ptr - buf);
}
}
CK_RV
kms_reload_labels(kms_session_t *sp)
{
CK_RV rv = CKR_OK;
char *cfgbuf = NULL;
size_t buflen;
struct stat statp;
char *ksdir;
char labelfile[BUFSIZ];
ksdir = kms_get_keystore_path();
if (ksdir == NULL)
return (CKR_GENERAL_ERROR);
(void) snprintf(labelfile, sizeof (labelfile),
"%s/%s", ksdir, KMSTOKEN_LABELLIST_FILENAME);
bzero(&statp, sizeof (statp));
if (stat(labelfile, &statp) == -1) {
if (errno == ENOENT) {
FILE *fp;
/* Create it */
fp = fopen(labelfile, "w");
if (fp == NULL)
return (CKR_GENERAL_ERROR);
(void) fclose(fp);
/* Re-stat to get current times */
if (stat(labelfile, &statp) == -1)
return (CKR_GENERAL_ERROR);
last_objlist_mtime = statp.st_mtime;
}
}
/* if nothing to read or the timestamp hasnt changed, return */
if (statp.st_size == 0 || statp.st_mtime == last_objlist_mtime) {
return (CKR_OK);
}
cfgbuf = calloc(1, statp.st_size);
if (cfgbuf == NULL)
return (CKR_HOST_MEMORY);
buflen = kms_slurp_file(labelfile, cfgbuf, statp.st_size);
if (buflen != statp.st_size) {
free(cfgbuf);
return (CKR_FUNCTION_FAILED);
}
kms_parse_labels(&sp->objlabel_tree, cfgbuf, buflen);
end:
if (cfgbuf)
free(cfgbuf);
return (rv);
}
static CK_RV
kms_get_object_label(kms_object_t *obj, char *label, int len)
{
CK_RV rv = CKR_OK;
CK_ATTRIBUTE stLabel;
bzero(label, len);
stLabel.type = CKA_LABEL;
stLabel.pValue = label;
stLabel.ulValueLen = len;
/*
* The caller MUST provide a CKA_LABEL when deleting.
*/
rv = kms_get_attribute(obj, &stLabel);
return (rv);
}
/*
* Retrieve a data unit associated with the label.
*/
static CK_RV
kms_get_data_unit(kms_session_t *session, char *label,
KMSAgent_DataUnit *pDataUnit)
{
KMS_AGENT_STATUS status;
const utf8cstr pDescription = KMS_DATAUNIT_DESCRIPTION;
uchar_t externalUniqueId[SHA256_DIGEST_LENGTH];
/* Find the data unit that holds the key */
kms_hash_string(label, externalUniqueId);
status = KMSAgent_RetrieveDataUnitByExternalUniqueID(
&session->kmsProfile,
(const unsigned char *)externalUniqueId,
sizeof (externalUniqueId),
label,
pDescription,
pDataUnit);
if (status != KMS_AGENT_STATUS_OK) {
return (GetPKCS11StatusFromAgentStatus(status));
}
return (CKR_OK);
}
static CK_RV
kms_decode_description(char *description, kms_object_t *pKey)
{
CK_RV rv = CKR_OK;
char *ptr;
uint32_t keylen;
u_longlong_t boolattrs;
/* If it doesn't start with the expected prefix, return */
if (strncmp(description, KMS_ATTR_DESC_PFX,
strlen(KMS_ATTR_DESC_PFX)))
return (rv);
ptr = description + strlen(KMS_ATTR_DESC_PFX);
/*
* Decode as follows:
* CK_OBJECT_CLASS (2 bytes)
* CK_KEY_TYPE (2 bytes)
* CKA_VALUE_LEN (4 bytes)
* CK_CERTIFICATE_TYPE (2 bytes - not used)
* CK_MECHANISM_TYPE (4 bytes)
* boolean attributes (3 bytes)
* extra attributes (1 byte)
* non-boolean attributes
*/
if (sscanf(ptr,
"%02lx%02lx%02x00%04lx%06llx00",
&pKey->class,
&pKey->key_type,
&keylen,
&pKey->mechanism,
&boolattrs) != 5)
/* We didn't get the full set of attributes */
rv = CKR_ATTRIBUTE_TYPE_INVALID;
pKey->bool_attr_mask = boolattrs;
return (rv);
}
/*
* Create a new PKCS#11 object record for the KMSAgent_Key.
*/
static CK_RV
kms_new_key_object(
char *label,
KMSAgent_DataUnit *dataUnit,
KMSAgent_Key *pKey,
kms_object_t **pObj)
{
CK_RV rv = CKR_OK;
CK_BBOOL bTrue = B_TRUE;
CK_BBOOL bFalse = B_FALSE;
CK_KEY_TYPE keytype = CKK_AES;
CK_OBJECT_CLASS class = CKO_SECRET_KEY;
CK_ULONG keylen;
kms_object_t *newObj;
CK_ATTRIBUTE template[] = {
{CKA_TOKEN, NULL, sizeof (bTrue)}, /* 0 */
{CKA_LABEL, NULL, 0}, /* 1 */
{CKA_KEY_TYPE, NULL, sizeof (keytype)}, /* 2 */
{CKA_CLASS, NULL, sizeof (class)}, /* 3 */
{CKA_VALUE, NULL, NULL}, /* 4 */
{CKA_VALUE_LEN, NULL, NULL}, /* 5 */
{CKA_PRIVATE, NULL, sizeof (bTrue)}, /* 6 */
{CKA_ENCRYPT, NULL, sizeof (bTrue)}, /* 7 */
{CKA_DECRYPT, NULL, sizeof (bTrue)}, /* 8 */
};
keylen = (CK_ULONG)pKey->m_iKeyLength;
template[0].pValue = &bTrue;
template[1].pValue = label;
template[1].ulValueLen = strlen(label);
template[2].pValue = &keytype;
template[3].pValue = &class;
template[4].pValue = pKey->m_acKey;
template[4].ulValueLen = pKey->m_iKeyLength;
template[5].pValue = &keylen;
template[5].ulValueLen = sizeof (keylen);
template[6].pValue = &bTrue;
if (pKey->m_iKeyState == KMS_KEY_STATE_ACTIVE_PROTECT_AND_PROCESS) {
template[7].pValue = &bTrue; /* CKA_ENCRYPT */
template[8].pValue = &bTrue; /* CKA_DECRYPT */
} else { /* Can only be one of the decrypt-only states. */
template[7].pValue = &bFalse; /* !CKA_ENCRYPT */
template[8].pValue = &bTrue; /* CKA_DECRYPT */
}
newObj = kms_new_object();
if (newObj == NULL)
return (CKR_HOST_MEMORY);
/*
* Decode the DataUnit description field to find various
* object attributes.
*/
rv = kms_decode_description(dataUnit->m_acDescription, newObj);
if (rv) {
free(newObj);
return (rv);
}
/*
* Set the template keytype and class according to the
* data parsed from the description.
*/
if (newObj->key_type)
keytype = newObj->key_type;
if (newObj->class)
class = newObj->class;
rv = kms_build_object(template, 9, newObj);
if (rv) {
free(newObj);
return (rv);
}
newObj->bool_attr_mask |= TOKEN_BOOL_ON;
*pObj = newObj;
return (rv);
}
static CK_RV
kms_get_data_unit_keys(kms_session_t *sp, KMSAgent_DataUnit *dataUnit,
KMSAgent_ArrayOfKeys **keylist, int *numkeys)
{
CK_RV rv = CKR_OK;
KMSAgent_ArrayOfKeys *kmskeys = NULL;
KMS_AGENT_STATUS status;
int keysLeft = 0;
status = KMSAgent_RetrieveDataUnitKeys(
&sp->kmsProfile, dataUnit,
KMS_MAX_PAGE_SIZE, 0,
(int * const)&keysLeft,
NULL, /* KeyID */
&kmskeys);
if (status != KMS_AGENT_STATUS_OK) {
return (GetPKCS11StatusFromAgentStatus(status));
}
if (keylist != NULL && kmskeys != NULL)
*keylist = kmskeys;
if (numkeys != NULL && kmskeys != NULL)
*numkeys = kmskeys->m_iSize;
if (keylist == NULL && kmskeys != NULL)
KMSAgent_FreeArrayOfKeys(kmskeys);
return (rv);
}
/*
* Retrieve a key from KMS. We can't use "RetrieveKey" because
* we don't know the key id. Instead get all keys associated
* with our data unit (there should be only 1.
*/
CK_RV
KMS_RetrieveKeyObj(kms_session_t *sp, char *label, kms_object_t **pobj)
{
CK_RV rv = CKR_OK;
KMSAgent_DataUnit dataUnit;
KMSAgent_ArrayOfKeys *kmsKeys = NULL;
KMSAgent_Key *pKey;
rv = kms_get_data_unit(sp, label, &dataUnit);
if (rv != CKR_OK)
return (rv);
if (dataUnit.m_iDataUnitState == KMS_DATA_UNIT_STATE_NO_KEY ||
dataUnit.m_iDataUnitState == KMS_DATA_UNIT_STATE_SHREDDED) {
/* No keys available. CKR_OK is still valid status */
*pobj = NULL;
return (CKR_OK);
}
rv = kms_get_data_unit_keys(sp, &dataUnit, &kmsKeys, NULL);
if (rv != CKR_OK)
return (rv);
/* No keys is a valid response */
if (kmsKeys == NULL || kmsKeys->m_iSize == 0) {
*pobj = NULL;
return (CKR_OK);
}
pKey = &kmsKeys->m_pKeys[0];
/*
* Make sure the key is in a useable state.
*/
if (pKey->m_iKeyState == KMS_KEY_STATE_ACTIVE_PROTECT_AND_PROCESS ||
pKey->m_iKeyState == KMS_KEY_STATE_ACTIVE_PROCESS_ONLY ||
pKey->m_iKeyState == KMS_KEY_STATE_DEACTIVATED ||
pKey->m_iKeyState == KMS_KEY_STATE_COMPROMISED) {
rv = kms_new_key_object(label, &dataUnit, pKey, pobj);
} else {
*pobj = NULL;
rv = CKR_KEY_HANDLE_INVALID;
}
KMSAgent_FreeArrayOfKeys(kmsKeys);
return (rv);
}
CK_RV
KMS_RefreshObjectList(kms_session_t *sp, kms_slot_t *pslot)
{
kms_object_t *pObj;
char label[BUFSIZ];
CK_RV rv;
objlabel_t *node;
rv = kms_reload_labels(sp);
if (rv != CKR_OK)
return (rv);
/*
* If an object is not in the list, reload it from KMS.
*/
node = avl_first(&sp->objlabel_tree);
while (node != NULL) {
boolean_t found = FALSE;
/* Search object list for matching object */
pObj = pslot->sl_tobj_list;
/* Skip nodes that are marked for deletion */
if (node->flags & DELETE_FLAG) {
node = AVL_NEXT(&sp->objlabel_tree, node);
continue;
}
while (pObj != NULL && !found) {
(void) pthread_mutex_lock(&pObj->object_mutex);
if ((rv = kms_get_object_label(pObj, label,
sizeof (label))) != CKR_OK) {
(void) pthread_mutex_unlock(
&pObj->object_mutex);
return (rv);
}
(void) pthread_mutex_unlock(&pObj->object_mutex);
found = (strcmp(label, node->label) == 0);
pObj = pObj->next;
}
if (!found) {
/*
* Fetch KMS key and prepend it to the
* token object list for the slot.
*/
rv = KMS_RetrieveKeyObj(sp, node->label, &pObj);
if (rv == CKR_OK && pObj != NULL) {
if (pslot->sl_tobj_list == NULL) {
pslot->sl_tobj_list = pObj;
pObj->prev = NULL;
pObj->next = NULL;
} else {
pObj->next = pslot->sl_tobj_list;
pObj->prev = NULL;
pslot->sl_tobj_list = pObj;
}
}
}
node = AVL_NEXT(&sp->objlabel_tree, node);
}
return (rv);
}
CK_RV
KMS_Initialize(void)
{
char *ksdir;
struct stat fn_stat;
KMS_AGENT_STATUS kmsrv;
ksdir = kms_get_keystore_path();
if (ksdir == NULL)
return (CKR_GENERAL_ERROR);
/*
* If the keystore directory doesn't exist, create it.
*/
if ((stat(ksdir, &fn_stat) != 0) && (errno == ENOENT)) {
if (mkdir(ksdir, S_IRUSR|S_IWUSR|S_IXUSR) < 0) {
if (errno != EEXIST)
return (CKR_GENERAL_ERROR);
}
}
if ((kmsrv = KMSAgent_InitializeLibrary(ksdir, FALSE)) !=
KMS_AGENT_STATUS_OK) {
return (GetPKCS11StatusFromAgentStatus(kmsrv));
}
initialized = B_TRUE;
return (CKR_OK);
}
CK_RV
KMS_Finalize()
{
if (!initialized)
return (CKR_OK);
last_objlist_mtime = 0;
if ((KMSAgent_FinalizeLibrary() == KMS_AGENT_STATUS_OK)) {
initialized = B_FALSE;
return (CKR_OK);
}
return (CKR_FUNCTION_FAILED);
}
CK_RV
KMS_ChangeLocalPWD(kms_session_t *session,
const char *pOldPassword,
const char *pNewPassword)
{
KMS_AGENT_STATUS status;
status = KMSAgent_ChangeLocalPWD(
&session->kmsProfile,
(char * const)pOldPassword,
(char * const)pNewPassword);
return (GetPKCS11StatusFromAgentStatus(status));
}
CK_RV
KMS_GetConfigInfo(kms_cfg_info_t *cfginfo)
{
CK_RV rv = CKR_OK;
char cfgfile_path[BUFSIZ];
char *ksdir = kms_get_keystore_path();
if (ksdir == NULL)
return (CKR_GENERAL_ERROR);
(void) snprintf(cfgfile_path, sizeof (cfgfile_path),
"%s/%s", ksdir, KMSTOKEN_CONFIG_FILENAME);
rv = kms_read_config_data(cfgfile_path, cfginfo);
return (rv);
}
CK_RV
KMS_LoadProfile(KMSClientProfile *profile,
kms_cfg_info_t *kmscfg,
const char *pPassword,
size_t iPasswordLength)
{
KMS_AGENT_STATUS status;
CK_RV rv;
char *sPassword;
char cfgfile_path[BUFSIZ];
char *ksdir;
sPassword = calloc(1, iPasswordLength + 1);
if (sPassword == NULL)
return (CKR_FUNCTION_FAILED);
(void) memcpy(sPassword, pPassword, iPasswordLength);
ksdir = kms_get_keystore_path();
if (ksdir == NULL) {
free(sPassword);
return (CKR_GENERAL_ERROR);
}
(void) snprintf(cfgfile_path, sizeof (cfgfile_path),
"%s/%s", ksdir, KMSTOKEN_CONFIG_FILENAME);
if ((rv = kms_read_config_data(cfgfile_path, kmscfg))) {
free(sPassword);
return (rv);
}
/* First, try to load existing profile */
status = KMSAgent_LoadProfile(
profile,
kmscfg->name,
kmscfg->agentId,
sPassword,
kmscfg->agentAddr,
kmscfg->transTimeout,
kmscfg->failoverLimit,
kmscfg->discoveryFreq,
kmscfg->securityMode);
free(sPassword);
return (GetPKCS11StatusFromAgentStatus(status));
}
static CK_RV
GetPKCS11StatusFromAgentStatus(KMS_AGENT_STATUS status)
{
switch (status) {
case KMS_AGENT_STATUS_OK:
return (CKR_OK);
case KMS_AGENT_STATUS_NO_MEMORY:
return (CKR_HOST_MEMORY);
case KMS_AGENT_STATUS_INVALID_PARAMETER:
return (CKR_ARGUMENTS_BAD);
case KMS_AGENT_STATUS_PROFILE_NOT_LOADED:
return (CKR_CRYPTOKI_NOT_INITIALIZED);
case KMS_AGENT_STATUS_PROFILE_ALREADY_LOADED:
return (CKR_USER_ANOTHER_ALREADY_LOGGED_IN);
case KMS_AGENT_STATUS_FIPS_KAT_AES_KEYWRAP_ERROR:
case KMS_AGENT_STATUS_FIPS_KAT_AES_ECB_ERROR:
case KMS_AGENT_STATUS_FIPS_KAT_HMAC_SHA1_ERROR:
return (CKR_DEVICE_ERROR);
case KMS_AGENT_STATUS_ACCESS_DENIED:
case KMS_AGENT_LOCAL_AUTH_FAILURE:
return (CKR_PIN_INCORRECT);
case KMS_AGENT_STATUS_GENERIC_ERROR:
case KMS_AGENT_STATUS_KMS_NO_READY_KEYS:
case KMS_AGENT_STATUS_KMS_UNAVAILABLE:
case KMS_AGENT_STATUS_NO_FIPS_KMAS_AVAILABLE:
case KMS_AGENT_STATUS_SERVER_BUSY:
case KMS_AGENT_STATUS_EXTERNAL_UNIQUE_ID_EXISTS:
case KMS_AGENT_STATUS_DATA_UNIT_ID_NOT_FOUND_EXTERNAL_ID_EXISTS:
case KMS_AGENT_STATUS_KEY_DOES_NOT_EXIST:
case KMS_AGENT_STATUS_KEY_DESTROYED:
case KMS_AGENT_AES_KEY_UNWRAP_ERROR:
case KMS_AGENT_AES_KEY_WRAP_SETUP_ERROR:
case KMS_AGENT_STATUS_KEY_CALLOUT_FAILURE:
default:
return (CKR_GENERAL_ERROR);
}
}
void
KMS_UnloadProfile(KMSClientProfile *kmsProfile)
{
(void) KMSAgent_UnloadProfile(kmsProfile);
}
/*
* kms_update_label_file
*
* KMS doesn't provide an API to allow one to query for available
* data units (which map 1-1 to keys). To allow for PKCS11 to
* query for a list of available objects, we keep a local list
* and update it when an object is added or deleted.
*/
CK_RV
kms_update_label_file(kms_session_t *sp)
{
CK_RV rv = CKR_OK;
objlabel_t *node;
char *ksdir, labelfile[BUFSIZ];
FILE *fp;
int fd;
struct stat statp;
ksdir = kms_get_keystore_path();
if (ksdir == NULL)
return (CKR_GENERAL_ERROR);
(void) snprintf(labelfile, sizeof (labelfile),
"%s/%s", ksdir, KMSTOKEN_LABELLIST_FILENAME);
/* Open the label file with a WRITE lock, but for READ/WRITE access */
fd = open_and_lock_file(labelfile, F_WRLCK, O_RDWR | O_CREAT,
&flock_mutex);
if (fd == -1)
return (CKR_GENERAL_ERROR);
if (fstat(fd, &statp) == -1) {
(void) flock_fd(fd, F_UNLCK, &flock_mutex);
(void) close(fd);
return (CKR_GENERAL_ERROR);
}
if (statp.st_size > 0 && last_objlist_mtime != statp.st_mtime) {
/* Re-read the label file and merge the list. */
char *buf = malloc(statp.st_size);
int bytes;
if (buf == NULL) {
(void) flock_fd(fd, F_UNLCK, &flock_mutex);
(void) close(fd);
return (CKR_GENERAL_ERROR);
}
bytes = kms_slurp_fd(fd, buf, statp.st_size);
/* merge the label list */
kms_parse_labels(&sp->objlabel_tree, buf, bytes);
free(buf);
}
/* Truncate the file in preparation for updating it */
if (ftruncate(fd, 0) == -1) {
(void) flock_fd(fd, F_UNLCK, &flock_mutex);
(void) close(fd);
return (CKR_GENERAL_ERROR);
}
(void) lseek(fd, 0, SEEK_SET);
fp = fdopen(fd, "w+");
if (fp == NULL) {
(void) flock_fd(fd, F_UNLCK, &flock_mutex);
(void) close(fd);
return (CKR_GENERAL_ERROR);
}
node = avl_first(&sp->objlabel_tree);
while (node != NULL) {
/* remove nodes marked for deletion */
if (node->flags & DELETE_FLAG) {
objlabel_t *tnode = node;
/* get the next one */
node = AVL_NEXT(&sp->objlabel_tree, node);
/* remove and free the old node data */
avl_remove(&sp->objlabel_tree, tnode);
free(tnode->label);
free(tnode);
continue;
} else if (node->label != NULL)
(void) fprintf(fp, "%s\n", node->label);
node = AVL_NEXT(&sp->objlabel_tree, node);
}
/* Update the last mtime */
(void) fflush(fp);
if (fstat(fd, &statp) == 0)
last_objlist_mtime = statp.st_mtime;
(void) flock_fd(fd, F_UNLCK, &flock_mutex);
(void) fclose(fp);
return (rv);
}
/*
* Destroy a key in the KMS by disassociating an entire data unit.
* The KMSAgent API does not have an interface for destroying an
* individual key.
*/
CK_RV
KMS_DestroyKey(kms_session_t *session, kms_object_t *i_oKey)
{
CK_RV rv;
KMSAgent_DataUnit oDataUnit;
KMS_AGENT_STATUS status;
char label[BUFSIZ];
objlabel_t labelnode, *tnode;
avl_index_t where = 0;
/*
* The caller MUST provide a CKA_LABEL when deleting.
*/
(void) pthread_mutex_lock(&i_oKey->object_mutex);
if ((rv = kms_get_object_label(i_oKey, label, sizeof (label)))) {
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (rv);
}
rv = kms_get_data_unit(session, label, &oDataUnit);
if (rv != CKR_OK)
return (rv);
status = KMSAgent_DisassociateDataUnitKeys(
&session->kmsProfile, &oDataUnit);
/*
* Remove the label from the label list and update
* the file that tracks active keys.
*/
bzero(&labelnode, sizeof (labelnode));
labelnode.label = label;
if ((tnode = avl_find(&session->objlabel_tree,
&labelnode, &where)) != NULL)
/* Mark it for deletion */
tnode->flags |= DELETE_FLAG;
/* rewrite the list of labels to disk */
rv = kms_update_label_file(session);
if (rv)
/* Ignore error here */
rv = CKR_OK;
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (GetPKCS11StatusFromAgentStatus(status));
}
void
kms_encode_attributes(kms_object_t *pKey, char *attrstr, int len)
{
char *ptr;
bzero(attrstr, len);
(void) strlcpy(attrstr, KMS_ATTR_DESC_PFX, len);
ptr = attrstr + strlen(attrstr);
/*
* Encode as follows:
* CK_OBJECT_CLASS (2 bytes)
* CK_KEY_TYPE (2 bytes)
* CKA_VALUE_LEN (4 bytes)
* CK_CERTIFICATE_TYPE (2 bytes - not used)
* CK_MECHANISM_TYPE (4 bytes)
* boolean attributes (3 bytes)
* extra attributes (1 byte)
* non-boolean attributes
*/
(void) snprintf(ptr, len - strlen(attrstr),
"%02lx%02lx%02x00%04lx%06x00",
pKey->class,
pKey->key_type,
32,
pKey->mechanism,
(uint32_t)(pKey->bool_attr_mask & 0x00FFFFFF));
}
CK_RV
KMS_GenerateKey(kms_session_t *session, kms_object_t *i_oKey)
{
CK_RV rv;
CK_ATTRIBUTE stLabel;
KMSAgent_DataUnit oDataUnit;
KMSAgent_Key oKey;
KMS_AGENT_STATUS status;
char label[128];
uchar_t externalUniqueId[SHA256_DIGEST_LENGTH];
char pDescription[KMS_MAX_DESCRIPTION + 1];
(void) pthread_mutex_lock(&i_oKey->object_mutex);
stLabel.type = CKA_LABEL;
stLabel.pValue = label;
stLabel.ulValueLen = sizeof (label);
/*
* The caller MUST provide a CKA_LABEL for storing in the KMS.
*/
if ((rv = kms_get_attribute(i_oKey, &stLabel)) != CKR_OK) {
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (rv);
}
label[stLabel.ulValueLen] = '\0';
kms_hash_string(label, externalUniqueId);
/* Encode attributes in Description */
kms_encode_attributes(i_oKey, pDescription,
sizeof (pDescription));
status = KMSAgent_CreateDataUnit(
&session->kmsProfile,
(const unsigned char *)externalUniqueId,
sizeof (externalUniqueId),
label, /* externalTag */
pDescription,
&oDataUnit);
/*
* If the DataUnit exists, check to see if it has any keys.
* If it has no keys, then it is OK to continue.
*/
if (status == KMS_AGENT_STATUS_EXTERNAL_UNIQUE_ID_EXISTS) {
int numkeys = 0;
rv = kms_get_data_unit(session, label, &oDataUnit);
if (rv != CKR_OK)
return (rv);
rv = kms_get_data_unit_keys(session,
&oDataUnit, NULL, &numkeys);
if (rv != CKR_OK || numkeys > 0)
/*
* This would be better if there were PKCS#11
* error codes for duplicate objects or
* something like that.
*/
return (CKR_ARGUMENTS_BAD);
/* If no keys associated with data unit, continue */
status = KMS_AGENT_STATUS_OK;
}
if (status != KMS_AGENT_STATUS_OK) {
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (GetPKCS11StatusFromAgentStatus(status));
}
status = KMSAgent_CreateKey(&session->kmsProfile,
&oDataUnit, "", &oKey);
if (status != KMS_AGENT_STATUS_OK) {
/*
* Clean up the old data unit.
*/
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (GetPKCS11StatusFromAgentStatus(status));
}
/*
* KMS Agent only creates AES-256 keys, so ignore what the user
* requested at this point.
*/
OBJ_SEC_VALUE(i_oKey) = malloc(oKey.m_iKeyLength);
if (OBJ_SEC_VALUE(i_oKey) == NULL) {
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (CKR_HOST_MEMORY);
}
(void) memcpy(OBJ_SEC_VALUE(i_oKey), oKey.m_acKey,
oKey.m_iKeyLength);
OBJ_SEC_VALUE_LEN(i_oKey) = oKey.m_iKeyLength;
/*
* Add the label to the local list of available objects
*/
add_label_node(&session->objlabel_tree, label);
rv = kms_update_label_file(session);
(void) pthread_mutex_unlock(&i_oKey->object_mutex);
return (GetPKCS11StatusFromAgentStatus(status));
}