diff --git a/.gitignore b/.gitignore index 3e095fa..7229299 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/opencryptoki-3.12.1.tar.gz +SOURCES/opencryptoki-3.14.0.tar.gz diff --git a/.opencryptoki.metadata b/.opencryptoki.metadata index 5177ce9..9259d12 100644 --- a/.opencryptoki.metadata +++ b/.opencryptoki.metadata @@ -1 +1 @@ -8cb8804fe7bbd306d16ca714f62c54927fc3c3d8 SOURCES/opencryptoki-3.12.1.tar.gz +9ddd1bbe34992707b20b314645fd92d35cb298ef SOURCES/opencryptoki-3.14.0.tar.gz diff --git a/SOURCES/opencryptoki-3.14.0-cd40f4b7cb1b502ca754b9bfb307d934285709a9-PIN-conversion-tool.patch b/SOURCES/opencryptoki-3.14.0-cd40f4b7cb1b502ca754b9bfb307d934285709a9-PIN-conversion-tool.patch new file mode 100644 index 0000000..ef02e85 --- /dev/null +++ b/SOURCES/opencryptoki-3.14.0-cd40f4b7cb1b502ca754b9bfb307d934285709a9-PIN-conversion-tool.patch @@ -0,0 +1,3575 @@ +diff --git a/configure.ac b/configure.ac +index 289c5052..c1f24eb9 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -189,6 +189,12 @@ AC_ARG_ENABLE([p11sak], + [], + [enable_p11sak=yes]) + ++dnl --- pkcstok_migrate ++AC_ARG_ENABLE([pkcstok_migrate], ++ AS_HELP_STRING([--enable-pkcstok_migrate],[build pkcstok_migrate tool @<:@default=enabled@:>@]), ++ [], ++ [enable_pkcstok_migrate=yes]) ++ + dnl --- + dnl --- Check for external software + dnl --- Define what to check based on enabled features +@@ -592,6 +598,9 @@ AM_CONDITIONAL([ENABLE_PKCSEP11_SESSION], [test "x$enable_pkcsep11_session" = "x + dnl --- enable_p11sak + AM_CONDITIONAL([ENABLE_P11SAK], [test "x$enable_p11sak" = "xyes"]) + ++dnl --- enable_pkcstok_migrate ++AM_CONDITIONAL([ENABLE_PKCSTOK_MIGRATE], [test "x$enable_pkcstok_migrate" = "xyes"]) ++ + dnl --- enable_locks + if test "x$enable_locks" != "xno"; then + enable_locks=yes +@@ -625,6 +634,7 @@ AC_CONFIG_FILES([Makefile \ + man/man1/p11sak.1 \ + man/man1/pkcsep11_migrate.1 \ + man/man1/pkcsep11_session.1 \ ++ man/man1/pkcstok_migrate.1 \ + man/man5/opencryptoki.conf.5 \ + man/man7/opencryptoki.7 \ + man/man8/pkcsslotd.8]) +@@ -639,6 +649,7 @@ echo " Library build: $enable_library" + echo " Systemd service: $enable_systemd" + echo " Build with locks: $enable_locks" + echo " Build p11sak tool: $enable_p11sak" ++echo " token migrate tool: $enable_pkcstok_migrate" + echo + echo "Enabled token types:" + echo " ICA token: $enable_icatok" +diff --git a/man/man1/man1.mk b/man/man1/man1.mk +index 51d23d39..6102340f 100644 +--- a/man/man1/man1.mk ++++ b/man/man1/man1.mk +@@ -1,5 +1,9 @@ + man1_MANS += man/man1/pkcsconf.1 man/man1/pkcsicsf.1 + ++if ENABLE_PKCSTOK_MIGRATE ++man1_MANS += man/man1/pkcstok_migrate.1 ++endif ++ + if ENABLE_PKCSEP11_MIGRATE + man1_MANS += man/man1/pkcsep11_migrate.1 + endif +diff --git a/man/man1/pkcstok_migrate.1.in b/man/man1/pkcstok_migrate.1.in +new file mode 100644 +index 00000000..b17d10b8 +--- /dev/null ++++ b/man/man1/pkcstok_migrate.1.in +@@ -0,0 +1,69 @@ ++.\" pkcstok_migrate.1 ++.\" ++.\" Copyright IBM Corp. 2020 ++.\" See LICENSE for details. ++.\" ++.TH PKCSTOK_MIGRATE 1 "June 2020" "@PACKAGE_VERSION@" "openCryptoki" ++.SH NAME ++pkcstok_migrate \- utility to migrate an ICA, CCA, Soft, or EP11 token repository ++to the FIPS compliant format introduced with openCryptoki 3.12. ++ ++.SH SYNOPSIS ++\fBpkcstok_migrate\fP [\fB-h\fP] ++.br ++\fBpkcstok_migrate\fP \fB--slotid\fP \fIslot-number\fP \fB--datastore\fP \fIdatastore\fP ++\fB--confdir\fP \fIconfdir\fP [\fB--sopin\fP \fIsopin\fP] [\fB--userpin\fP ++\fIuserpin\fP] [\fB--verbose\fP \fIlevel\fP] ++ ++.SH DESCRIPTION ++Convert all objects inside a token repository to the new format introduced with ++version 3.12. All encrypted data inside the new format is stored using FIPS ++compliant methods. The new format affects the token's master key files (MK_SO ++and MK_USER), the NVTOK.DAT, and the token object files in the TOK_OBJ folder. ++ ++While using this tool no process using the token to be migrated must be running. ++Especially the pkcsslotd must be stopped before running this tool. ++ ++The tool creates a backup of the token repository to be migrated, and performs ++all migration actions on this backup, leaving the original repository folder ++completely untouched. The backup folder is located in the same directory as the ++original repository and is suffixed with _PKCSTOK_MIGRATE_TMP. ++ ++After a successful migration, the original repository is renamed with a suffix ++of _BAK and the backup folder is renamed to the original repository name, so ++that the migrated repository can immediately be used. The old folder may be ++deleted by the user manually later. ++ ++After a successful migration, the tool adds parameter 'tokversion = 3.12' to the ++token's slot configuration in the opencryptoki.conf file. The original config ++file is still available as opencryptoki.conf_BAK and may be removed by the user ++manually. ++ ++After an unsuccessful migration, the original repository is still available ++unchanged. ++ ++.SH "OPTIONS SUMMARY" ++.IP "\fB--slotid -s\fP \fISLOT-NUMBER\fP" 10 ++specifies the token slot number of the token repository to be migrated ++.IP "\fB--datastore -d\fP \fIDATASTORE\fP" 10 ++specifies the directory of the token repository to be migrated. ++.IP "\fB--confdir -c\fP \fICONFDIR\fP" 10 ++specifies the directory where the opencryptoki.conf file is located. ++.IP "\fB--sopin -p\fP \fISOPIN\fP" 10 ++specifies the SO pin. If not specified, the SO pin is prompted. ++.IP "\fB--userpin -u\fP \fIUSERPIN\fP" 10 ++specifies the user pin. If not specified, the user pin is prompted. ++.IP "\fB--verbose -v\fP \fILEVEL\fP" 10 ++specifies the verbose level: \fInone\fP, error, warn, info, devel, debug ++.IP "\fB--help -h\fP" 10 ++show usage information ++ ++.SH SEE ALSO ++.PD 0 ++.TP ++\fBpkcsconf\fP(1), ++.TP ++\fBopencryptoki\fP(7), ++.TP ++\fBpkcsslotd\fP(8). ++.PD +diff --git a/rpm/opencryptoki.spec b/rpm/opencryptoki.spec +index ae563406..64142e23 100644 +--- a/rpm/opencryptoki.spec ++++ b/rpm/opencryptoki.spec +@@ -239,8 +239,10 @@ exit 0 + %{_sbindir}/pkcsconf + %{_sbindir}/pkcsslotd + %{_sbindir}/p11sak ++%{_sbindir}/pkcstok_migrate + %{_mandir}/man1/pkcsconf.1* + %{_mandir}/man1/p11sak.1* ++%{_mandir}/man1/pkcstok_migrate.1* + %{_mandir}/man5/%{name}.conf.5* + %{_mandir}/man7/%{name}.7* + %{_mandir}/man8/pkcsslotd.8* +diff --git a/usr/lib/common/loadsave.c b/usr/lib/common/loadsave.c +index c30dd1ab..068fdf36 100644 +--- a/usr/lib/common/loadsave.c ++++ b/usr/lib/common/loadsave.c +@@ -1701,7 +1701,7 @@ static CK_RV aes_256_gcm_seal(unsigned char *out, + || EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, 1) != 1 + || EVP_CipherUpdate(ctx, NULL, &outlen, aad, aadlen) != 1 + || EVP_CipherUpdate(ctx, out, &outlen, in, inlen) != 1 +- || EVP_CipherFinal_ex(ctx, out, &outlen) != 1 ++ || EVP_CipherFinal_ex(ctx, out + outlen, &outlen) != 1 + || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) != 1) { + TRACE_ERROR("%s\n", ock_err(ERR_GENERAL_ERROR)); + rc = ERR_GENERAL_ERROR; +@@ -1741,7 +1741,7 @@ static CK_RV aes_256_gcm_unseal(unsigned char *out, + || EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, 0) != 1 + || EVP_CipherUpdate(ctx, NULL, &outlen, aad, aadlen) != 1 + || EVP_CipherUpdate(ctx, out, &outlen, in, inlen) != 1 +- || EVP_CipherFinal_ex(ctx, out, &outlen) != 1) { ++ || EVP_CipherFinal_ex(ctx, out + outlen, &outlen) != 1) { + TRACE_ERROR("%s\n", ock_err(ERR_GENERAL_ERROR)); + rc = ERR_GENERAL_ERROR; + goto done; +@@ -1759,6 +1759,7 @@ static CK_RV aes_256_wrap(unsigned char out[40], + { + CK_RV rc; + int outlen; ++ unsigned char buffer[40 + EVP_MAX_BLOCK_LENGTH]; + + EVP_CIPHER_CTX *ctx = NULL; + +@@ -1772,13 +1773,14 @@ static CK_RV aes_256_wrap(unsigned char out[40], + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + + if (EVP_CipherInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, NULL, 1) != 1 +- || EVP_CipherUpdate(ctx, out, &outlen, in, 32) != 1 +- || EVP_CipherFinal_ex(ctx, out, &outlen) != 1) { ++ || EVP_CipherUpdate(ctx, buffer, &outlen, in, 32) != 1 ++ || EVP_CipherFinal_ex(ctx, buffer + outlen, &outlen) != 1) { + TRACE_ERROR("%s\n", ock_err(ERR_GENERAL_ERROR)); + rc = ERR_GENERAL_ERROR; + goto done; + } + ++ memcpy(out, buffer, 40); + rc = CKR_OK; + done: + EVP_CIPHER_CTX_free(ctx); +@@ -1791,6 +1793,7 @@ static CK_RV aes_256_unwrap(unsigned char key[32], + { + CK_RV rc; + int outlen; ++ unsigned char buffer[32 + EVP_MAX_BLOCK_LENGTH]; + + EVP_CIPHER_CTX *ctx = NULL; + +@@ -1804,13 +1807,14 @@ static CK_RV aes_256_unwrap(unsigned char key[32], + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + + if (EVP_CipherInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, NULL, 0) != 1 +- || EVP_CipherUpdate(ctx, key, &outlen, in, 40) != 1 +- || EVP_CipherFinal_ex(ctx, key, &outlen) != 1) { ++ || EVP_CipherUpdate(ctx, buffer, &outlen, in, 40) != 1 ++ || EVP_CipherFinal_ex(ctx, buffer + outlen, &outlen) != 1) { + TRACE_ERROR("%s\n", ock_err(ERR_GENERAL_ERROR)); + rc = ERR_GENERAL_ERROR; + goto done; + } + ++ memcpy(key, buffer, 32); + rc = CKR_OK; + done: + EVP_CIPHER_CTX_free(ctx); +diff --git a/usr/lib/common/obj_mgr.c b/usr/lib/common/obj_mgr.c +index 3e4ba3b6..1bdbe64c 100644 +--- a/usr/lib/common/obj_mgr.c ++++ b/usr/lib/common/obj_mgr.c +@@ -1751,6 +1751,12 @@ CK_RV object_mgr_set_attribute_values(STDLL_TokData_t *tokdata, + save_token_object(tokdata, obj); + + if (priv_obj) { ++ if (tokdata->global_shm->num_priv_tok_obj == 0) { ++ TRACE_DEVEL("%s\n", ock_err(ERR_OBJECT_HANDLE_INVALID)); ++ rc = CKR_OBJECT_HANDLE_INVALID; ++ XProcUnLock(tokdata); ++ goto done; ++ } + rc = object_mgr_search_shm_for_obj(tokdata->global_shm-> + priv_tok_objs, 0, + tokdata->global_shm-> +@@ -1765,6 +1771,12 @@ CK_RV object_mgr_set_attribute_values(STDLL_TokData_t *tokdata, + + entry = &tokdata->global_shm->priv_tok_objs[index]; + } else { ++ if (tokdata->global_shm->num_publ_tok_obj == 0) { ++ TRACE_DEVEL("%s\n", ock_err(ERR_OBJECT_HANDLE_INVALID)); ++ rc = CKR_OBJECT_HANDLE_INVALID; ++ XProcUnLock(tokdata); ++ goto done; ++ } + rc = object_mgr_search_shm_for_obj(tokdata->global_shm-> + publ_tok_objs, 0, + tokdata->global_shm-> +@@ -1846,6 +1858,10 @@ CK_RV object_mgr_del_from_shm(OBJECT *obj, LW_SHM_TYPE *global_shm) + priv = object_is_private(obj); + + if (priv) { ++ if (global_shm->num_priv_tok_obj == 0) { ++ TRACE_DEVEL("%s\n", ock_err(ERR_OBJECT_HANDLE_INVALID)); ++ return CKR_OBJECT_HANDLE_INVALID; ++ } + rc = object_mgr_search_shm_for_obj(global_shm->priv_tok_objs, + 0, global_shm->num_priv_tok_obj - 1, + obj, &index); +@@ -1886,6 +1902,10 @@ CK_RV object_mgr_del_from_shm(OBJECT *obj, LW_SHM_TYPE *global_shm) + sizeof(TOK_OBJ_ENTRY)); + } + } else { ++ if (global_shm->num_publ_tok_obj == 0) { ++ TRACE_DEVEL("%s\n", ock_err(ERR_OBJECT_HANDLE_INVALID)); ++ return CKR_OBJECT_HANDLE_INVALID; ++ } + rc = object_mgr_search_shm_for_obj(global_shm->publ_tok_objs, + 0, global_shm->num_publ_tok_obj - 1, + obj, &index); +diff --git a/usr/lib/common/pkcs_utils.c b/usr/lib/common/pkcs_utils.c +new file mode 100644 +index 00000000..d3074d5c +--- /dev/null ++++ b/usr/lib/common/pkcs_utils.c +@@ -0,0 +1,477 @@ ++/* ++ * COPYRIGHT (c) International Business Machines Corp. 2020 ++ * ++ * This program is provided under the terms of the Common Public License, ++ * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this ++ * software constitutes recipient's acceptance of CPL-1.0 terms which can be ++ * found in the file LICENSE file or at ++ * https://opensource.org/licenses/cpl1.0.php ++ */ ++ ++/* ++ * Some routines that are shared between the pkcs utilities in usr/sbin. ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "defs.h" ++#include "host_defs.h" ++ ++#define OCK_TOOL ++#include "pkcs_utils.h" ++ ++extern pkcs_trace_level_t trace_level; ++ ++void pkcs_trace(pkcs_trace_level_t level, const char *file, int line, ++ const char *fmt, ...) ++{ ++ va_list ap; ++ const char *fmt_pre; ++ char buf[1024]; ++ char *pbuf; ++ int buflen, len; ++ ++ if (level > trace_level) ++ return; ++ ++ pbuf = buf; ++ buflen = sizeof(buf); ++ ++ /* add file line */ ++ switch (level) { ++ case TRACE_LEVEL_NONE: ++ fmt_pre = ""; ++ break; ++ case TRACE_LEVEL_ERROR: ++ fmt_pre = "[%s:%d] ERROR: "; ++ break; ++ case TRACE_LEVEL_WARNING: ++ fmt_pre = "[%s:%d] WARN: "; ++ break; ++ case TRACE_LEVEL_INFO: ++ fmt_pre = "[%s:%d] INFO: "; ++ break; ++ case TRACE_LEVEL_DEVEL: ++ fmt_pre = "[%s:%d] DEVEL: "; ++ break; ++ case TRACE_LEVEL_DEBUG: ++ fmt_pre = "[%s:%d] DEBUG: "; ++ break; ++ default: ++ return; ++ } ++ snprintf(pbuf, buflen, fmt_pre, file, line); ++ ++ len = strlen(buf); ++ pbuf = buf + len; ++ buflen = sizeof(buf) - len; ++ ++ va_start(ap, fmt); ++ vsnprintf(pbuf, buflen, fmt, ap); ++ va_end(ap); ++ ++ printf("%s", buf); ++} ++ ++int compute_hash(int hash_type, int buf_size, char *buf, char *digest) ++{ ++ EVP_MD_CTX *md_ctx = NULL; ++ unsigned int result_size; ++ int rc; ++ ++ md_ctx = EVP_MD_CTX_create(); ++ ++ switch (hash_type) { ++ case HASH_SHA1: ++ rc = EVP_DigestInit(md_ctx, EVP_sha1()); ++ break; ++ case HASH_MD5: ++ rc = EVP_DigestInit(md_ctx, EVP_md5()); ++ break; ++ default: ++ rc = -1; ++ goto done; ++ } ++ ++ if (rc != 1) { ++ TRACE_ERROR("EVP_DigestInit() failed: rc = %d\n", rc); ++ rc = -1; ++ goto done; ++ } ++ ++ rc = EVP_DigestUpdate(md_ctx, buf, buf_size); ++ if (rc != 1) { ++ TRACE_ERROR("EVP_DigestUpdate() failed: rc = %d\n", rc); ++ rc = -1; ++ goto done; ++ } ++ ++ result_size = EVP_MD_CTX_size(md_ctx); ++ rc = EVP_DigestFinal(md_ctx, (unsigned char *) digest, &result_size); ++ if (rc != 1) { ++ TRACE_ERROR("EVP_DigestFinal() failed: rc = %d\n", rc); ++ rc = -1; ++ goto done; ++ } ++ ++ rc = 0; ++ ++done: ++ ++ EVP_MD_CTX_destroy(md_ctx); ++ ++ return rc; ++} ++ ++CK_RV local_rng(CK_BYTE *output, CK_ULONG bytes) ++{ ++ int ranfd; ++ int rlen; ++ unsigned int totallen = 0; ++ ++ ranfd = open("/dev/prandom", 0); ++ if (ranfd < 0) ++ ranfd = open("/dev/urandom", 0); ++ if (ranfd >= 0) { ++ do { ++ rlen = read(ranfd, output + totallen, bytes - totallen); ++ totallen += rlen; ++ } while (totallen < bytes); ++ close(ranfd); ++ return CKR_OK; ++ } ++ ++ return CKR_FUNCTION_FAILED; ++} ++ ++CK_RV aes_256_wrap(unsigned char out[40], const unsigned char in[32], ++ const unsigned char kek[32]) ++{ ++ CK_RV rc; ++ int outlen; ++ unsigned char buffer[40 + EVP_MAX_BLOCK_LENGTH]; ++ ++ EVP_CIPHER_CTX *ctx = NULL; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ TRACE_ERROR("EVP_CIPHER_CTX_new failed.\n"); ++ rc = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); ++ ++ if (EVP_CipherInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, NULL, 1) != 1 ++ || EVP_CipherUpdate(ctx, buffer, &outlen, in, 32) != 1 ++ || EVP_CipherFinal_ex(ctx, buffer + outlen, &outlen) != 1) { ++ TRACE_ERROR("EVP_Cipher funcs failed\n"); ++ rc = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ memcpy(out, buffer, 40); ++ rc = CKR_OK; ++done: ++ EVP_CIPHER_CTX_free(ctx); ++ return rc; ++} ++ ++CK_RV aes_256_unwrap(unsigned char key[32], const unsigned char in[40], ++ const unsigned char kek[32]) ++{ ++ CK_RV rc; ++ int outlen; ++ unsigned char buffer[32 + EVP_MAX_BLOCK_LENGTH]; ++ ++ EVP_CIPHER_CTX *ctx = NULL; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ TRACE_ERROR("EVP_CIPHER_CTX_new failed\n"); ++ rc = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); ++ ++ if (EVP_CipherInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, NULL, 0) != 1 ++ || EVP_CipherUpdate(ctx, buffer, &outlen, in, 40) != 1 ++ || EVP_CipherFinal_ex(ctx, buffer + outlen, &outlen) != 1) { ++ rc = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ memcpy(key, buffer, 32); ++ rc = CKR_OK; ++done: ++ EVP_CIPHER_CTX_free(ctx); ++ return rc; ++} ++ ++CK_RV aes_256_gcm_seal(unsigned char *out, unsigned char tag[16], ++ const unsigned char *aad, size_t aadlen, ++ const unsigned char *in, size_t inlen, ++ const unsigned char key[32], ++ const unsigned char iv[12]) ++{ ++ CK_RV rc; ++ int outlen; ++ ++ EVP_CIPHER_CTX *ctx = NULL; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ TRACE_ERROR("EVP_CIPHER_CTX_new failed\n"); ++ rc = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ if (EVP_CipherInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL, -1) != 1 ++ || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL) != 1 ++ || EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, 1) != 1 ++ || EVP_CipherUpdate(ctx, NULL, &outlen, aad, aadlen) != 1 ++ || EVP_CipherUpdate(ctx, out, &outlen, in, inlen) != 1 ++ || EVP_CipherFinal_ex(ctx, out + outlen, &outlen) != 1 ++ || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) != 1) { ++ TRACE_ERROR("EVP_Cipher funcs failed\n"); ++ rc = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ rc = CKR_OK; ++done: ++ EVP_CIPHER_CTX_free(ctx); ++ return rc; ++} ++ ++int get_pin(char **pin, size_t *pinlen) ++{ ++ struct termios old, new; ++ int nread; ++ char *buff = NULL; ++ size_t buflen; ++ int rc = 0; ++ ++ /* turn echoing off */ ++ if (tcgetattr(fileno(stdin), &old) != 0) ++ return -1; ++ ++ new = old; ++ new.c_lflag &= ~ECHO; ++ if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) ++ return -1; ++ ++ /* read the pin ++ * Note: getline will allocate memory for buff. free it when done. ++ */ ++ nread = getline(&buff, &buflen, stdin); ++ if (nread == -1) { ++ rc = -1; ++ goto done; ++ } ++ ++ /* Restore terminal */ ++ (void) tcsetattr(fileno(stdin), TCSAFLUSH, &old); ++ ++ /* start a newline */ ++ printf("\n"); ++ fflush(stdout); ++ ++ /* Allocate PIN. ++ * Note: nread includes carriage return. ++ * Replace with terminating NULL. ++ */ ++ *pin = (char *) malloc(nread); ++ if (*pin == NULL) { ++ rc = -ENOMEM; ++ goto done; ++ } ++ ++ /* strip the carriage return since not part of pin. */ ++ buff[nread - 1] = '\0'; ++ memcpy(*pin, buff, nread); ++ /* don't include the terminating null in the pinlen */ ++ *pinlen = nread - 1; ++ ++done: ++ if (buff) ++ free(buff); ++ ++ return rc; ++} ++ ++/** ++ * Verify that SO PIN and user PIN are correct by comparing their SHA-1 ++ * values with the stored hashes in NVTOK.DAT. ++ */ ++int verify_pins(char *data_store, char *sopin, unsigned long sopinlen, ++ char *userpin, unsigned long userpinlen) ++{ ++ TOKEN_DATA td; ++ char fname[PATH_MAX]; ++ char pin_sha[SHA1_HASH_SIZE]; ++ FILE *fp = NULL; ++ int ret; ++ int tdnew; ++ struct stat stbuf; ++ size_t tdlen; ++ int fd; ++ ++ /* read the NVTOK.DAT */ ++ snprintf(fname, PATH_MAX, "%s/NVTOK.DAT", data_store); ++ fp = fopen((char *) fname, "r"); ++ if (!fp) { ++ TRACE_ERROR("Cannot not open %s: %s\n", fname, strerror(errno)); ++ return -1; ++ } ++ ++ fd = fileno(fp); ++ if ((fstat(fd, &stbuf) != 0) || (!S_ISREG(stbuf.st_mode))) { ++ ret = -1; ++ goto done; ++ } ++ ++ if (stbuf.st_size == sizeof(TOKEN_DATA_OLD)) { ++ /* old data store/pin format */ ++ tdnew = 0; ++ tdlen = sizeof(TOKEN_DATA_OLD); ++ } else if (stbuf.st_size == sizeof(TOKEN_DATA)) { ++ /* new data store/pin format */ ++ tdnew = 1; ++ tdlen = sizeof(TOKEN_DATA); ++ } else { ++ TRACE_ERROR("%s has an invalid size of %ld bytes. Neither old nor new token format.\n", ++ fname, stbuf.st_size); ++ ret = -1; ++ goto done; ++ } ++ ++ ret = fread(&td, tdlen, 1, fp); ++ if (ret != 1) { ++ TRACE_ERROR("Could not read %s: %s\n", fname, strerror(errno)); ++ ret = -1; ++ goto done; ++ } ++ ++ if (tdnew == 0) { ++ /* Now compute the SHAs for the SO and USER pins entered. ++ * Compare with the SHAs for SO and USER PINs saved in ++ * NVTOK.DAT to verify. ++ */ ++ ++ if (sopin != NULL) { ++ ret = compute_sha1(sopin, sopinlen, pin_sha); ++ if (ret) { ++ TRACE_ERROR("Failed to compute sha for SO.\n"); ++ goto done; ++ } ++ ++ if (memcmp(td.so_pin_sha, pin_sha, SHA1_HASH_SIZE) != 0) { ++ TRACE_ERROR("SO PIN is incorrect.\n"); ++ ret = -1; ++ goto done; ++ } ++ } ++ ++ if (userpin != NULL) { ++ ret = compute_sha1(userpin, userpinlen, pin_sha); ++ if (ret) { ++ TRACE_ERROR("Failed to compute sha for USER.\n"); ++ goto done; ++ } ++ ++ if (memcmp(td.user_pin_sha, pin_sha, SHA1_HASH_SIZE) != 0) { ++ TRACE_ERROR("USER PIN is incorrect.\n"); ++ ret = -1; ++ goto done; ++ } ++ } ++ } else if (tdnew == 1) { ++ if (sopin != NULL) { ++ unsigned char so_login_key[32]; ++ ++ ret = PKCS5_PBKDF2_HMAC(sopin, sopinlen, ++ td.dat.so_login_salt, 64, ++ td.dat.so_login_it, EVP_sha512(), ++ 256 / 8, so_login_key); ++ if (ret != 1) { ++ TRACE_ERROR("PBKDF2 failed.\n"); ++ goto done; ++ } ++ ++ if (CRYPTO_memcmp(td.dat.so_login_key, so_login_key, 32) != 0) { ++ TRACE_ERROR("USER PIN is incorrect.\n"); ++ ret = -1; ++ goto done; ++ } ++ } ++ if (userpin != NULL) { ++ unsigned char user_login_key[32]; ++ ++ ret = PKCS5_PBKDF2_HMAC(userpin, userpinlen, ++ td.dat.user_login_salt, 64, ++ td.dat.user_login_it, EVP_sha512(), ++ 256 / 8, user_login_key); ++ if (ret != 1) { ++ TRACE_ERROR("PBKDF2 failed.\n"); ++ goto done; ++ } ++ ++ if (CRYPTO_memcmp(td.dat.user_login_key, user_login_key, 32) != 0) { ++ TRACE_ERROR("USER PIN is incorrect.\n"); ++ ret = -1; ++ goto done; ++ } ++ } ++ } else { ++ TRACE_ERROR("Unknown token format.\n"); ++ ret = -1; ++ goto done; ++ } ++ ret = 0; ++ ++done: ++ /* clear out the hash */ ++ memset(pin_sha, 0, SHA1_HASH_SIZE); ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++void set_perm(int file) ++{ ++ struct group *grp; ++ ++ // Set absolute permissions or rw-rw---- ++ fchmod(file, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); ++ ++ grp = getgrnam("pkcs11"); // Obtain the group id ++ if (grp) { ++ // set ownership to root, and pkcs11 group ++ if (fchown(file, getuid(), grp->gr_gid) != 0) { ++ goto error; ++ } ++ } else { ++ goto error; ++ } ++ ++ return; ++ ++error: ++ TRACE_DEVEL("Unable to set permissions on file.\n"); ++} +diff --git a/usr/lib/common/pkcs_utils.h b/usr/lib/common/pkcs_utils.h +new file mode 100644 +index 00000000..248559cc +--- /dev/null ++++ b/usr/lib/common/pkcs_utils.h +@@ -0,0 +1,81 @@ ++/* ++ * COPYRIGHT (c) International Business Machines Corp. 2020 ++ * ++ * This program is provided under the terms of the Common Public License, ++ * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this ++ * software constitutes recipient's acceptance of CPL-1.0 terms which can be ++ * found in the file LICENSE file or at ++ * https://opensource.org/licenses/cpl1.0.php ++ */ ++ ++#ifndef PKCS_UTILS_H ++#define PKCS_UTILS_H ++ ++#include "pkcs11types.h" ++ ++#define MASTER_KEY_SIZE 24 ++#define MASTER_KEY_SIZE_CCA 64 ++#define MAX_MASTER_KEY_SIZE MASTER_KEY_SIZE_CCA ++ ++#define MK_FILE_SIZE_00 48 ++#define MK_FILE_SIZE_00_CCA 88 ++ ++#define HASH_SHA1 1 ++#define HASH_MD5 2 ++ ++#define compute_sha1(a,b,c) compute_hash((HASH_SHA1),(b),(a),(c)) ++#define compute_md5(a,b,c) compute_hash(HASH_MD5,(b),(a),(c)) ++ ++int compute_hash(int hash_type, int buf_size, char *buf, char *digest); ++ ++CK_RV local_rng(CK_BYTE *output, CK_ULONG bytes); ++ ++CK_RV aes_256_wrap(unsigned char out[40], const unsigned char in[32], ++ const unsigned char kek[32]); ++ ++CK_RV aes_256_unwrap(unsigned char key[32], const unsigned char in[40], ++ const unsigned char kek[32]); ++ ++CK_RV aes_256_gcm_seal(unsigned char *out, unsigned char tag[16], ++ const unsigned char *aad, size_t aadlen, ++ const unsigned char *in, size_t inlen, ++ const unsigned char key[32], ++ const unsigned char iv[12]); ++ ++int get_pin(char **pin, size_t *pinlen); ++ ++int verify_pins(char *data_store, char *sopin, unsigned long sopinlen, ++ char *userpin, unsigned long userpinlen); ++ ++void set_perm(int file); ++ ++#ifdef OCK_TOOL ++/* Log levels */ ++typedef enum { ++ TRACE_LEVEL_NONE = 0, ++ TRACE_LEVEL_ERROR, ++ TRACE_LEVEL_WARNING, ++ TRACE_LEVEL_INFO, ++ TRACE_LEVEL_DEVEL, ++ TRACE_LEVEL_DEBUG ++} pkcs_trace_level_t; ++ ++void pkcs_trace(pkcs_trace_level_t level, const char * file, int line, ++ const char *fmt, ...) ++ __attribute__ ((format(printf, 4, 5))); ++ ++#define TRACE_NONE(...) \ ++ pkcs_trace(TRACE_LEVEL_NONE, __FILE__, __LINE__, __VA_ARGS__) ++#define TRACE_ERROR(...) \ ++ pkcs_trace(TRACE_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) ++#define TRACE_WARN(...) \ ++ pkcs_trace(TRACE_LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__) ++#define TRACE_INFO(...) \ ++ pkcs_trace(TRACE_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) ++#define TRACE_DEVEL(...) \ ++ pkcs_trace(TRACE_LEVEL_DEVEL, __FILE__, __LINE__, __VA_ARGS__) ++#define TRACE_DEBUG(...) \ ++ pkcs_trace(TRACE_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) ++#endif /* OCK_TOOL */ ++ ++#endif +diff --git a/usr/sbin/pkcstok_migrate/pkcstok_migrate.c b/usr/sbin/pkcstok_migrate/pkcstok_migrate.c +new file mode 100644 +index 00000000..e90a5c91 +--- /dev/null ++++ b/usr/sbin/pkcstok_migrate/pkcstok_migrate.c +@@ -0,0 +1,2686 @@ ++/* ++ * COPYRIGHT (c) International Business Machines Corp. 2020 ++ * ++ * This program is provided under the terms of the Common Public License, ++ * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this ++ * software constitutes recipient's acceptance of CPL-1.0 terms which can be ++ * found in the file LICENSE file or at ++ * https://opensource.org/licenses/cpl1.0.php ++ */ ++ ++/* ++ * pkcstok_migrate - A tool for migrating ICA, CCA, Soft, and EP11 token ++ * repositories to 3.12 format. ++ * ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "sw_crypt.h" ++#include "defs.h" ++#include "host_defs.h" ++#include "local_types.h" ++#include "h_extern.h" ++ ++#define OCK_TOOL ++#include "pkcs_utils.h" ++ ++ ++#define TOKVERSION_00 0x00000000 ++#define TOKVERSION_312 0x0003000C ++ ++#define INVALID_TOKEN "unknown/unsupported" ++ ++#define HEADER_LEN 64 ++#define FOOTER_LEN 16 ++ ++#define PKCSTOK_MIGRATE_MAX_PATH_LEN (PATH_MAX - 200) ++ ++pkcs_trace_level_t trace_level = TRACE_LEVEL_NONE; ++ ++/** ++ * Make a 3.12 format OBJECT_PUB: ++ * ++ * struct OBJECT_PUB { ++ * //-------------- <--+ ++ * u32 tokversion; | 16-byte header ++ * u8 private_flag; | ++ * u8 reserved[7]; | ++ * u32 object_len; | ++ * //-------------- <--+ ++ * u8 object[object_len]; | body ++ * //-------------- <--+ ++ * }; ++ */ ++static CK_RV make_OBJECT_PUB_312(char **obj_new, unsigned int *obj_new_len, ++ unsigned char *clear, unsigned int clear_len) ++{ ++ struct { ++ uint32_t tokversion; ++ uint8_t private_flag; ++ uint8_t reserved[7]; ++ uint32_t object_len; ++ } header; ++ uint32_t total_len; ++ char *object; ++ CK_RV ret; ++ ++ *obj_new = NULL; ++ *obj_new_len = 0; ++ ++ /* Check parms */ ++ if (!clear || clear_len == 0) { ++ TRACE_ERROR("Error in parms.\n"); ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* Allocate memory for new OBJECT_PUB */ ++ total_len = sizeof(header) + clear_len; ++ object = malloc(total_len); ++ if (object == NULL) { ++ TRACE_ERROR("cannot malloc %d bytes.\n", total_len); ++ ret = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ /* Setup object */ ++ memset(&header, 0, sizeof(header)); ++ header.tokversion = 0x0003000C; ++ header.private_flag = 0x00; ++ header.object_len = clear_len; ++ memcpy(object, &header, sizeof(header)); ++ memcpy(object + sizeof(header), clear, clear_len); ++ ++ *obj_new = object; ++ *obj_new_len = total_len; ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * This function migrates the public obj to the current format. ++ */ ++static CK_RV migrate_public_token_object(const char *data_store, const char *name, ++ unsigned char *data, unsigned long len) ++{ ++ const char *tokobj = "TOK_OBJ"; ++ char fname[PATH_MAX + 1 + strlen(tokobj) + 1 + strlen(name) + 1]; ++ char *obj_new = NULL; ++ unsigned int obj_new_len; ++ FILE *fp = NULL; ++ CK_RV ret = 0; ++ ++ /* Create new public object */ ++ ret = make_OBJECT_PUB_312(&obj_new, &obj_new_len, data, len); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create an OBJECT_PUB_312, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Setup file name for new object */ ++ sprintf(fname, "%s/%s/%s", data_store, tokobj, name); ++ fp = fopen((char *) fname, "w"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp)); ++ ++ /* Save new object */ ++ if (fwrite(obj_new, obj_new_len, 1, fp) != 1) { ++ TRACE_ERROR("fwrite(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ free(obj_new); ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * Make a 3.12 format OBJECT_PRIV: ++ * ++ * struct OBJECT_PRIV { ++ * u32 total_len; ++ * --- auth ------- <--+ ++ * u32 tokversion | 64-byte header ++ * u8 private_flag | ++ * u8 reserved[3] | ++ * u8 key_wrapped[40] | ++ * u8 iv[12] | ++ * u32 object_len | ++ * --- auth+enc --- <--+ ++ * u8 object[object_len] | body ++ * ---------------- <--+ ++ * u8 tag[16] | 16-byte footer ++ * ---------------- <--+ ++ * } ++ */ ++static CK_RV make_OBJECT_PRIV_312(unsigned char **obj_new, unsigned int *obj_new_len, ++ const char *name, unsigned char *clear, ++ unsigned int clear_len, const CK_BYTE *masterkey) ++{ ++ struct { ++ uint32_t tokversion; ++ uint8_t private_flag; ++ uint8_t reserved[3]; ++ uint8_t key_wrapped[40]; ++ uint8_t iv[12]; ++ uint32_t object_len; ++ } header; ++ unsigned char *object; ++ CK_BYTE obj_key[32]; ++ uint32_t total_len; ++ CK_RV ret; ++ ++ *obj_new = NULL; ++ *obj_new_len = 0; ++ ++ /* Check parms */ ++ if (!name || !clear || clear_len == 0 || !masterkey) { ++ TRACE_ERROR("Error in parms.\n"); ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* An obj name has by definition 8 chars */ ++ if (strlen(name) != 8) { ++ TRACE_ERROR("obj name %s does not have 8 chars, OBJ.IDX probably corrupted.\n", ++ name); ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* Allocate memory for new OBJECT_PRIV */ ++ total_len = sizeof(header) + clear_len + FOOTER_LEN; ++ object = malloc(total_len); ++ if (object == NULL) { ++ TRACE_ERROR("cannot malloc %d bytes.\n", total_len); ++ ret = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ /* Create new object key */ ++ ret = local_rng(obj_key, 32); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("local_rng failed with ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Setup header */ ++ memset(&header, 0, sizeof(header)); ++ header.tokversion = 0x0003000C; ++ header.private_flag = 0x01; ++ ret = aes_256_wrap(header.key_wrapped, obj_key, masterkey); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("aes_256_wrap failed with ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ memcpy(header.iv, name, 8); ++ header.iv[8] = 0; ++ header.iv[9] = 0; ++ header.iv[10] = 0; ++ header.iv[11] = 1; ++ header.object_len = clear_len; ++ memcpy(object, &header, HEADER_LEN); ++ ++ /* Encrypt body */ ++ ret = aes_256_gcm_seal(object + HEADER_LEN, /* ciphertext */ ++ object + HEADER_LEN + clear_len, /* tag */ ++ object, HEADER_LEN, /* aad */ ++ clear, clear_len, /* plaintext */ ++ obj_key, /* key */ ++ header.iv /* iv */); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("aes_256_gcm_seal failed with rc=%08lX.\n", ret); ++ goto done; ++ } ++ ++ *obj_new = object; ++ *obj_new_len = total_len; ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Decrypts the given version 0.0 private object with given old masterkey. ++ * ++ * struct OBJECT_PRIV { ++ * u32 total_len; ++ * u8 private_flag; ++ * //--- enc --- <- enc_old starts here ++ * u32 object_len; ++ * u8 object[object_len]; ++ * u8 sha1[20]; ++ * u8 padding[padding_len]; ++ * }; ++ */ ++static CK_RV decrypt_OBJECT_PRIV_00(unsigned char **clear, unsigned int *clear_len, ++ unsigned char *enc_old, unsigned int enc_len, ++ const CK_BYTE *masterkey_old) ++{ ++ CK_ULONG_32 obj_data_len_32; ++ CK_BYTE hash_sha[SHA1_HASH_SIZE]; ++ CK_BYTE des3_key[MAX_MASTER_KEY_SIZE]; ++ unsigned char *tmp_clear, *raw_clear; ++ CK_ULONG tmp_clear_len; ++ CK_RV ret; ++ ++ *clear = NULL; ++ *clear_len = 0; ++ ++ /* Allocate storage for clear output */ ++ tmp_clear = malloc(enc_len); ++ if (!tmp_clear) { ++ TRACE_ERROR("Cannot malloc %d bytes, errno=%s.\n", ++ enc_len, strerror(errno)); ++ ret = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ /* Decrypt old object */ ++ memcpy(des3_key, masterkey_old, MAX_MASTER_KEY_SIZE); ++ ret = sw_des3_cbc_decrypt(enc_old, enc_len, tmp_clear, &tmp_clear_len, ++ (CK_BYTE *)"10293847", des3_key); ++ if (ret) { ++ TRACE_ERROR("sw_des3_cbc_decrypt failed with ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ /* Validate the length */ ++ memcpy(&obj_data_len_32, tmp_clear, sizeof(CK_ULONG_32)); ++ if (obj_data_len_32 >= enc_len) { ++ TRACE_ERROR("Decrypted object data length %d inconsistent\n", ++ obj_data_len_32); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Validate the hash */ ++ ret = compute_sha1((char *)(tmp_clear + sizeof(CK_ULONG_32)), ++ obj_data_len_32, (char *)hash_sha); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("compute_sha1 failed with ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ if (memcmp(tmp_clear + sizeof(CK_ULONG_32) + obj_data_len_32, hash_sha, ++ SHA1_HASH_SIZE) != 0) { ++ TRACE_ERROR("Stored hash does not match with newly calculated hash.\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* At this point, tmp_clear points to the full decrypted obj data: ++ * | 4 bytes len | clear obj[obj_data_len_32] | 20 bytes sha | padding | ++ * But the caller only wants clear obj[obj_data_len_32]. ++ */ ++ raw_clear = malloc(obj_data_len_32); ++ if (!raw_clear) { ++ TRACE_ERROR("Cannot malloc %d bytes, errno=%s.\n", enc_len, strerror(errno)); ++ ret = CKR_HOST_MEMORY; ++ goto done; ++ } ++ memcpy(raw_clear, tmp_clear + sizeof(CK_ULONG_32), obj_data_len_32); ++ ++ *clear = raw_clear; ++ *clear_len = (unsigned int)obj_data_len_32; ++ ++ ret = CKR_OK; ++ ++done: ++ ++ free(tmp_clear); ++ return ret; ++} ++ ++/** ++ * This function migrates the private obj to the current format. ++ */ ++static CK_RV migrate_private_token_object(const char *data_store, const char *name, ++ unsigned char *data, unsigned long len, ++ const CK_BYTE *masterkey_old, ++ const CK_BYTE *masterkey_new) ++{ ++ const char *tokobj = "TOK_OBJ"; ++ char fname[PATH_MAX + 1 + strlen(tokobj) + 1 + strlen(name) + 1]; ++ unsigned char *clear = NULL; ++ unsigned int clear_len; ++ unsigned char *obj_new = NULL; ++ unsigned int obj_new_len; ++ FILE *fp = NULL; ++ CK_RV ret; ++ ++ /* Decrypt old object */ ++ ret = decrypt_OBJECT_PRIV_00(&clear, &clear_len, data, len, masterkey_old); ++ if (ret != 0) { ++ TRACE_ERROR("Cannot decrypt old object with old masterkey, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Create new object */ ++ ret = make_OBJECT_PRIV_312(&obj_new, &obj_new_len, name, clear, clear_len, ++ masterkey_new); ++ if (ret != 0) { ++ TRACE_ERROR("make_OBJECT_PRIV_312 failed with ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Create file name for new object */ ++ sprintf(fname, "%s/%s/%s", data_store, tokobj, name); ++ fp = fopen((char *)fname, "w"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp)); ++ ++ /* Save new object */ ++ if (fwrite(obj_new, obj_new_len, 1, fp) != 1) { ++ TRACE_ERROR("fwrite(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) { ++ fclose(fp); ++ fp = NULL; ++ } ++ free(clear); ++ free(obj_new); ++ ++ return ret; ++} ++ ++/** ++ * Reads a format 0.0 object: ++ * ++ * struct OBJECT { ++ * u32 total_len; <- indicates old or new format ++ * u8 private_flag; ++ * u8 object; <- can be public or private ++ * }; ++ * ++ * The total_len field has been already read to decide whether this ++ * object is old or new. Its value is passed via the size parm. ++ */ ++static CK_RV read_object_00(FILE *fp, const char *name, unsigned int size, ++ unsigned char **obj, unsigned int *obj_len, ++ CK_BBOOL *obj_priv) ++{ ++ CK_BBOOL priv; ++ size_t read_size; ++ unsigned char *buf = NULL; ++ CK_RV ret; ++ ++ *obj = NULL; ++ *obj_len = 0; ++ ++ /* Check parms */ ++ if (!fp || !name) { ++ TRACE_ERROR("Arguments bad.\n"); ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* Read 1-char private flag */ ++ read_size = fread(&priv, sizeof(CK_BBOOL), 1, fp); ++ if (read_size != 1) { ++ TRACE_ERROR("Cannot read private flag from old object %s.\n", name); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Allocate buffer for obj */ ++ size = size - sizeof(CK_ULONG_32) - sizeof(CK_BBOOL); ++ buf = malloc(size); ++ if (!buf) { ++ TRACE_ERROR("Cannot malloc %d bytes for object %s.\n", size, name); ++ ret = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ /* Read obj into buf */ ++ read_size = fread((char *)buf, 1, size, fp); ++ if (read_size != size) { ++ TRACE_ERROR("Cannot read old object %s.\n", name); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ *obj = buf; ++ *obj_len = size; ++ *obj_priv = priv; ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Reads the token object given by name from given data_store and returns ++ * pointers to the object, its length, and indications whether the object ++ * is in 0.0 or 3.12 format and whether it's private. ++ */ ++static CK_RV read_object(const char *data_store, const char *name, ++ unsigned char **obj, unsigned int *obj_len, ++ CK_ULONG *version, CK_BBOOL *obj_priv) ++{ ++ char fname[PATH_MAX]; ++ unsigned int size = 0; ++ size_t read_size; ++ FILE *fp; ++ CK_RV ret; ++ ++ *obj = NULL; ++ *obj_len = 0; ++ ++ /* Open token object file */ ++ snprintf((char *) fname, sizeof(fname), "%s/TOK_OBJ/", data_store); ++ strcat((char *) fname, (char *) name); ++ fp = fopen((char *) fname, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Read 32-bit size field */ ++ read_size = fread(&size, sizeof(CK_ULONG_32), 1, fp); ++ if (read_size != 1) { ++ TRACE_ERROR("Cannot read %ld bytes from %s, read_size = %ld. " ++ "Object probably empty or corrupted.\n", ++ sizeof(CK_ULONG_32), name, read_size); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Check if this object is old or current */ ++ if (size == TOKVERSION_312) { ++ TRACE_INFO("%s is already in current format, nothing to do.\n", name); ++ ret = CKR_OK; ++ *version = TOKVERSION_312; ++ goto done; ++ } ++ ++ /* Read old object */ ++ *version = TOKVERSION_00; ++ ret = read_object_00(fp, name, size, obj, obj_len, obj_priv); ++ if (ret != 0) { ++ TRACE_ERROR("Cannot read old object %s.\n", name); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * Migrate the token objects from old to new format. Some of the token objects ++ * may be in old and some may be in new format if a previous migration run ++ * was interrupted. ++ */ ++static CK_RV migrate_token_objects(const char *data_store, const CK_BYTE *masterkey_old, ++ const CK_BYTE *masterkey_new, ++ const CK_BYTE *so_wrap_key, ++ const CK_BYTE *user_wrap_key) ++{ ++ const char *tokobj = "TOK_OBJ"; ++ const char *objidx = "OBJ.IDX"; ++ FILE *fp = NULL; ++ unsigned char *obj = NULL; ++ unsigned int obj_len; ++ char tmp[PATH_MAX]; ++ char iname[PATH_MAX + 1 + strlen(tokobj) + 1 + strlen(objidx) + 1]; ++ CK_BBOOL priv; ++ CK_ULONG version; ++ int count = 0, scount = 0; ++ CK_RV ret; ++ ++ /* Check parms */ ++ if (!data_store || !masterkey_old || !masterkey_new || !so_wrap_key ++ || !user_wrap_key) { ++ TRACE_ERROR("Invalid parms.\n"); ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* Open index file OBJ.IDX */ ++ sprintf(iname, "%s/%s/%s", data_store, tokobj, objidx); ++ fp = fopen((char *)iname, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", iname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Migrate items from OBJ.IDX */ ++ while (fgets(tmp, PATH_MAX, fp)) { ++ tmp[strlen(tmp) - 1] = 0; ++ ret = read_object(data_store, tmp, &obj, &obj_len, &version, &priv); ++ if (ret == 0 && version == TOKVERSION_00) { ++ if (priv) { ++ ret = migrate_private_token_object(data_store, tmp, ++ obj, obj_len, masterkey_old, masterkey_new); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot migrate private object %s, continuing ... \n", tmp); ++ } else ++ scount++; ++ } else { ++ ret = migrate_public_token_object(data_store, tmp, ++ obj, obj_len); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot migrate public object %s, continuing ... \n", tmp); ++ } else ++ scount++; ++ } ++ } ++ ++ if (obj) { ++ free(obj); ++ obj = NULL; ++ } ++ count++; ++ } ++ ++ /* OBJ.IDX must be at eof here */ ++ if (!feof(fp)) { ++ TRACE_WARN("OBJ.IDX is not at eof after object %s, should not happen.\n", ++ tmp); ++ } ++ ++ /* Close OBJ.IDX */ ++ fclose(fp); ++ ++ ret = CKR_OK; ++ ++ TRACE_NONE("Migrated %d object(s) out of %d object(s).\n", scount, count); ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * loads the new aes256 masterkey. ++ * The new format defines the MK to be an AES-256 key. Its unencrypted format ++ * are just the 32 key bytes. Its encrypted format is a 40 byte key blob ++ */ ++static CK_RV load_masterkey_312(const char *data_store, const char *mkfile, ++ const char *pin, TOKEN_DATA *tokdata, ++ CK_BYTE *masterkey) ++{ ++ FILE *fp = NULL; ++ CK_RV ret; ++ int rc; ++ char fname[PATH_MAX]; ++ unsigned char inbuf[40]; ++ unsigned char wrap_key[32]; ++ ++ /* Open file */ ++ memset(fname, 0, PATH_MAX); ++ snprintf(fname, PATH_MAX, "%s/%s", data_store, mkfile); ++ fp = fopen(fname, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Read wrapped key from file */ ++ rc = fread(inbuf, sizeof(inbuf), 1, fp); ++ if (rc != 1) { ++ TRACE_ERROR("Cannot read %ld bytes from %s.\n", sizeof(inbuf), fname); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Derive wrapping key from pin and public info in TOKEN_DATA */ ++ if (strstr(mkfile,"MK_SO")) { ++ rc = PKCS5_PBKDF2_HMAC(pin, strlen(pin), ++ tokdata->dat.so_wrap_salt, 64, ++ tokdata->dat.so_wrap_it, EVP_sha512(), ++ 256 / 8, wrap_key); ++ } else { ++ rc = PKCS5_PBKDF2_HMAC(pin, strlen(pin), ++ tokdata->dat.user_wrap_salt, 64, ++ tokdata->dat.user_wrap_it, EVP_sha512(), ++ 256 / 8, wrap_key); ++ } ++ if (rc != 1) { ++ TRACE_INFO("PKCS5_PBKDF2_HMAC returned rc=%08X.\n", rc); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Decrypt buffer with pin-related wrapping key */ ++ rc = aes_256_unwrap(masterkey, inbuf, wrap_key); ++ if (rc != CKR_OK) { ++ TRACE_ERROR("aes_256_unwrap failed with rc=%08X.\n", rc); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * loads the old des3 masterkey from given file with given PIN. ++ * The format is: ++ * ++ * struct MK { ++ * u8 MK [24 or 64 (cca)]; ++ * u8 sha1 [20]; ++ * u8 padding[4]; ++ * }; ++ */ ++static CK_RV load_masterkey_00(const char *mkfile, const char *pin, ++ CK_BYTE *masterkey) ++{ ++ CK_BYTE des3_key[3 * DES_KEY_SIZE]; ++ char hash_sha[SHA1_HASH_SIZE]; ++ char pin_md5_hash[MD5_HASH_SIZE]; ++ unsigned char *cipher = NULL; ++ unsigned char *clear = NULL; ++ unsigned long cipher_len, clear_len; ++ CK_ULONG master_key_len = 0L; ++ int file_size = 0; ++ ++ CK_RV ret; ++ int rc; ++ FILE *fp = NULL; ++ ++ fp = fopen(mkfile, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", mkfile, strerror(errno)); ++ return CKR_FUNCTION_FAILED; ++ } ++ ++ /* Determine the master key length */ ++ fseek(fp, 0L, SEEK_END); ++ file_size = ftell(fp); ++ switch (file_size) { ++ case MK_FILE_SIZE_00_CCA: /* CCA token */ ++ master_key_len = MASTER_KEY_SIZE_CCA; ++ break; ++ case MK_FILE_SIZE_00: /* All other tokens */ ++ master_key_len = MASTER_KEY_SIZE; ++ break; ++ default: ++ /* Unknown MK format, should not occur. */ ++ TRACE_ERROR("%s has an unknown file size of %d bytes. Should be either 48 or 88 bytes.\n", ++ mkfile, file_size); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ rewind(fp); ++ ++ /* Read file contents */ ++ clear_len = cipher_len = ++ (master_key_len + SHA1_HASH_SIZE + ++ (DES_BLOCK_SIZE - 1)) & ~(DES_BLOCK_SIZE - 1); ++ ++ cipher = malloc(cipher_len); ++ clear = malloc(clear_len); ++ if (cipher == NULL || clear == NULL) { ++ ret = CKR_HOST_MEMORY; ++ goto done; ++ } ++ ++ rc = fread(cipher, cipher_len, 1, fp); ++ if (rc != 1) { ++ TRACE_ERROR("Cannot read %ld bytes from %s\n", cipher_len, mkfile); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Decrypt the masterkey */ ++ ret = compute_md5((char *)pin, strlen(pin), pin_md5_hash); ++ if (ret) { ++ TRACE_ERROR("Error calculating MD5 of PIN, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ memcpy(des3_key, pin_md5_hash, MD5_HASH_SIZE); ++ memcpy(des3_key + MD5_HASH_SIZE, pin_md5_hash, DES_KEY_SIZE); ++ ++ ret = sw_des3_cbc_decrypt(cipher, cipher_len, clear, ++ &clear_len, (unsigned char *) "12345678", ++ des3_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("failed to decrypt master key file after read, ret=%08lX\n", ret); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* compare the hashes to verify integrity */ ++ ret = compute_sha1((char *)clear, master_key_len, hash_sha); ++ if (ret) { ++ TRACE_ERROR("Failed to compute sha1 for masterkey, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ if (memcmp(hash_sha, clear + master_key_len, SHA1_HASH_SIZE) != 0) { ++ TRACE_ERROR("%s appears to be tampered! Cannot migrate.\n", mkfile); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ memcpy(masterkey, clear, master_key_len); ++ ret = 0; ++ ++done: ++ if (fp) ++ fclose(fp); ++ free(clear); ++ free(cipher); ++ ++ return ret; ++} ++ ++/** ++ * Remove any spaces from given string ++ */ ++static void remove_spaces(char *s) ++{ ++ int i, count = 0; ++ ++ for (i = 0; s[i]; i++) { ++ if (s[i] != ' ') ++ s[count++] = s[i]; ++ } ++ ++ s[count] = '\0'; ++} ++ ++/** ++ * gets the stdll name from the given opencryptoki.conf file. ++ */ ++static CK_RV get_stdll_name(FILE *fp, char *dll_name) ++{ ++ char line[PATH_MAX]; ++ ++ while (fgets(line, sizeof(line), fp)) { ++ remove_spaces(line); ++ if (strstr(line, "stdll=")) { ++ sscanf(line, "stdll=%s", dll_name); ++ return CKR_OK; ++ } ++ } ++ ++ return CKR_FUNCTION_FAILED; ++} ++ ++/** ++ * Check if the given conf_dir exists and contains the opencryptoki.conf. ++ */ ++static CK_BBOOL conffile_exists(const char *conf_dir) ++{ ++ char fname[PATH_MAX]; ++ struct stat statbuf; ++ DIR *dir; ++ ++ TRACE_INFO("Checking if config file exists in %s ...\n", conf_dir); ++ dir = opendir(conf_dir); ++ if (dir == NULL) { ++ TRACE_INFO("Cannot open %s.\n", conf_dir); ++ return CK_FALSE; ++ } ++ ++ /* Check if opencryptoki.conf exists */ ++ memset(fname, 0, PATH_MAX); ++ snprintf(fname, PATH_MAX, "%s/opencryptoki.conf", conf_dir); ++ if (stat(fname, &statbuf) != 0) { ++ TRACE_INFO("Cannot find %s.\n", fname); ++ closedir(dir); ++ return CK_FALSE; ++ } ++ closedir(dir); ++ ++ return CK_TRUE; ++} ++ ++/** ++ * Check if the given data_store directory exists. ++ */ ++static CK_BBOOL datastore_exists(const char *data_store) ++{ ++ DIR *dir; ++ ++ TRACE_INFO("Checking if datastore %s exists ...\n", data_store); ++ dir = opendir(data_store); ++ if (dir == NULL) { ++ TRACE_INFO("Cannot open %s.\n", data_store); ++ return CK_FALSE; ++ } ++ closedir(dir); ++ ++ return CK_TRUE; ++} ++ ++/** ++ * Check if the data store is empty, which is the case when ++ * there are no entries in OBJ.IDX. ++ */ ++static CK_BBOOL datastore_empty(const char *data_store) ++{ ++ char fname[PATH_MAX]; ++ struct stat statbuf; ++ ++ TRACE_INFO("Checking if data store is empty ...\n"); ++ ++ /* Check if OBJ.IDX exists */ ++ memset(fname, 0, PATH_MAX); ++ snprintf(fname, PATH_MAX, "%s/TOK_OBJ/OBJ.IDX", data_store); ++ if (stat(fname, &statbuf) != 0) { ++ TRACE_INFO("Cannot find %s, data store probably empty.\n", fname); ++ return CK_TRUE; ++ } ++ ++ /* Check if OBJ.IDX is empty */ ++ if (statbuf.st_size == 0) { ++ TRACE_INFO("OBJ.IDX file is empty. Thus no objects to migrate.\n"); ++ return CK_TRUE; ++ } ++ ++ return CK_FALSE; ++} ++ ++/** ++ * ++ */ ++static CK_RV load_MK_SO_00(const char *data_store, const char *sopin, ++ CK_BYTE *masterkey) ++{ ++ const char *mkso = "MK_SO"; ++ char fname[PATH_MAX + 1 + strlen(mkso) + 1]; ++ CK_RV ret; ++ ++ /* Get masterkey from MK_SO. This also verifies SO PIN is correct */ ++ memset(masterkey, 0, MAX_MASTER_KEY_SIZE); ++ sprintf(fname, "%s/%s", data_store, mkso); ++ ret = load_masterkey_00(fname, sopin, masterkey); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load old masterkey from MK_SO, ret=%08lX.\n", ret); ++ // We cannot do anything more here, even when some objs are still old. ++ // We would need the old key in order to open an old obj. ++ } ++ ++ return ret; ++} ++ ++/** ++ * ++ */ ++static CK_RV load_MK_USER_00(const char *data_store, const char *userpin, ++ CK_BYTE *masterkey) ++{ ++ const char *mkuser = "MK_USER"; ++ char fname[PATH_MAX + 1 + strlen(mkuser) + 1]; ++ CK_RV ret; ++ ++ /* Get masterkey from MK_USER. This also verifies user PIN is correct */ ++ memset(masterkey, 0, MAX_MASTER_KEY_SIZE); ++ sprintf(fname, "%s/%s", data_store, mkuser); ++ ret = load_masterkey_00(fname, userpin, masterkey); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load old masterkey from MK_USER, ret=%08lX.\n", ret); ++ } ++ ++ return ret; ++} ++ ++/** ++ * ++ */ ++static CK_RV load_MK_SO_312(const char *data_store, const char *sopin, ++ TOKEN_DATA *tokdata, CK_BYTE *masterkey) ++{ ++ CK_RV ret; ++ ++ /* Get masterkey from MK_SO_312. This also verifies SO PIN is correct */ ++ memset(masterkey, 0, MAX_MASTER_KEY_SIZE); ++ ret = load_masterkey_312(data_store, "MK_SO_312", sopin, tokdata, masterkey); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load masterkey from MK_SO_312, ret=%08lX.\n", ret); ++ } ++ ++ return ret; ++} ++ ++/** ++ * ++ */ ++static CK_RV load_MK_USER_312(const char *data_store, const char *userpin, ++ TOKEN_DATA *tokdata, CK_BYTE *masterkey) ++{ ++ CK_RV ret; ++ ++ /* Get masterkey from MK_USER_312. This also verifies user PIN is correct */ ++ memset(masterkey, 0, MAX_MASTER_KEY_SIZE); ++ ret = load_masterkey_312(data_store, "MK_USER_312", userpin, tokdata, masterkey); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load masterkey from MK_USER_312, ret=%08lX.\n", ret); ++ } ++ ++ return ret; ++} ++ ++/** ++ * Loads the NVTOK.DAT and returns the TOKEN_DATA struct. ++ */ ++static CK_RV load_NVTOK_DAT(const char *data_store, const char *nvtok_name, ++ TOKEN_DATA *td) ++{ ++ char fname[PATH_MAX]; ++ struct stat stbuf; ++ int fd; ++ size_t tdlen; ++ FILE *fp = NULL; ++ CK_RV ret; ++ ++ /* Check parms */ ++ if (!data_store || !nvtok_name || !td) { ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* Read the NVTOK.DAT */ ++ snprintf(fname, PATH_MAX, "%s/%s", data_store, nvtok_name); ++ fp = fopen((char *) fname, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ return CKR_FUNCTION_FAILED; ++ } ++ ++ fd = fileno(fp); ++ if ((fstat(fd, &stbuf) != 0) || (!S_ISREG(stbuf.st_mode))) { ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Check if this NVTOK.DAT is old or new */ ++ if (stbuf.st_size == sizeof(TOKEN_DATA_OLD)) { ++ /* old data store/pin format */ ++ tdlen = sizeof(TOKEN_DATA_OLD); ++ } else if (stbuf.st_size == sizeof(TOKEN_DATA)) { ++ /* new data store/pin format */ ++ tdlen = sizeof(TOKEN_DATA); ++ } else { ++ TRACE_ERROR("%s has an invalid size of %ld bytes. Neither old nor new token format.\n", ++ fname, stbuf.st_size); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Read TOKEN_DATA */ ++ ret = fread(td, tdlen, 1, fp); ++ if (ret != 1) { ++ TRACE_ERROR("Cannot read %s, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * Strip trailing chars from given string. ++ */ ++static char *strip_trailing_chars(char *s, int slen, char c) ++{ ++ int i; ++ ++ if (!s || slen == 0) ++ return s; ++ ++ for (i = slen - 1; i >= 0; i--) { ++ if (s[i] == c) ++ s[i] = '\0'; ++ else ++ break; ++ } ++ ++ return s; ++} ++ ++/** ++ * Read the token info from NVTOK.DAT. ++ */ ++static CK_RV get_token_info(const char *data_store, CK_TOKEN_INFO_32 *tokinfo) ++{ ++ TOKEN_DATA tokdata; ++ CK_RV ret; ++ ++ TRACE_INFO("Reading token info from NVTOK.DAT ...\n"); ++ ++ ret = load_NVTOK_DAT(data_store, "NVTOK.DAT", &tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load NVTOK.DAT, datastore inconsistent.\n"); ++ return ret; ++ } ++ ++ memcpy(tokinfo, &(tokdata.token_info), sizeof(CK_TOKEN_INFO_32)); ++ ++ return CKR_OK; ++} ++ ++/** ++ * returns true, if the given string begins with the given char, ++ * false otherwise. The string may contain leading spaces. ++ */ ++static CK_BBOOL begins_with(const char *str, const char c) ++{ ++ size_t i; ++ ++ for (i = 0; i < strlen(str); i++) { ++ if (str[i] == ' ') ++ continue; ++ else if (str[i] == c) ++ return CK_TRUE; ++ else ++ return CK_FALSE; ++ } ++ ++ return CK_FALSE; ++} ++ ++/** ++ * Identify the token that belongs to the given slot ID. ++ */ ++static CK_RV identify_token(CK_SLOT_ID slot_id, char *conf_dir, char *dll_name) ++{ ++ char conf_file[PATH_MAX]; ++ char line[80], parm[80]; ++ FILE *fp; ++ CK_RV ret; ++ ++ TRACE_INFO("Identifying the token that belongs to slot %ld ...\n", slot_id); ++ ++ /* Open conf file */ ++ snprintf(conf_file, PATH_MAX, "%s/%s", conf_dir, "opencryptoki.conf"); ++ fp = fopen(conf_file, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", conf_file, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Get stdll name */ ++ ret = CKR_FUNCTION_FAILED; ++ snprintf(parm, sizeof(parm), "slot %ld", slot_id); ++ while (fgets(line, sizeof(line), fp)) { ++ if (!begins_with(line, '#') && strstr(line, parm)) { ++ ret = get_stdll_name(fp, dll_name); ++ goto done; ++ } ++ } ++ ++done: ++ ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * derives the SO wrap key from the given SO pin and given public ++ * info in NVTOK.DAT. ++ */ ++static CK_RV derive_so_wrap_key_312(const char *sopin, TOKEN_DATA *tokdata, ++ CK_BYTE *so_wrap_key) ++{ ++ CK_RV ret; ++ ++ ret = PKCS5_PBKDF2_HMAC(sopin, strlen(sopin), ++ tokdata->dat.so_wrap_salt, 64, ++ tokdata->dat.so_wrap_it, EVP_sha512(), ++ 256 / 8, so_wrap_key); ++ if (ret != 1) { ++ TRACE_ERROR("PBKDF2 failed.\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * derives the user wrap key from the given user pin and given public ++ * info in NVTOK.DAT. ++ */ ++static CK_RV derive_user_wrap_key_312(const char *userpin, TOKEN_DATA *tokdata, ++ CK_BYTE *user_wrap_key) ++{ ++ CK_RV ret; ++ ++ ret = PKCS5_PBKDF2_HMAC(userpin, strlen(userpin), ++ tokdata->dat.user_wrap_salt, 64, ++ tokdata->dat.user_wrap_it, EVP_sha512(), ++ 256 / 8, user_wrap_key); ++ if (ret != 1) { ++ TRACE_ERROR("PBKDF2 failed.\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Activates the new repository by deleting the old MK_USER, MK_SO, and ++ * NVTOK.DAT, and renaming the new MK_SO_312, MK_USER_312, NVTOK.DAT_312 to ++ * their normal names. ++ */ ++static CK_RV cleanup_repository_backup(const char *data_store) ++{ ++ static char *names[] = { "MK_SO", "MK_USER", "NVTOK.DAT" }; ++ int num_names = sizeof(names) / sizeof(char *); ++ char fname1[PATH_MAX + 9 + 1]; // satisfy compiler warning ++ char fname2[PATH_MAX + 1 + 1]; // satisfy compiler warning ++ int i, rc; ++ CK_RV ret; ++ ++ /* Delete old files */ ++ for (i = 0; i < num_names; i++) { ++ snprintf(fname1, sizeof(fname1), "%s/%s", data_store, names[i]); ++ rc = remove(fname1); ++ if (rc) { ++ TRACE_ERROR("Cannot delete old file %s.\n", fname1); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ } ++ ++ /* Rename new files */ ++ for (i = 0; i < num_names; i++) { ++ snprintf(fname1, sizeof(fname1), "%s/%s_312", data_store, names[i]); ++ snprintf(fname2, sizeof(fname2), "%s/%s", data_store, names[i]); ++ rc = rename(fname1, fname2); ++ if (rc) { ++ TRACE_ERROR("Cannot rename new file %s.\n", fname1); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Migrates the repository. This process may be interrupted at any time and ++ * must be able to resume until the complete repository is successfully ++ * migrated. This especially requires to keep the old keys until ++ * everything is done. ++ */ ++static CK_RV migrate_repository(const char *data_store, const char *sopin, ++ const char *userpin) ++{ ++ CK_BYTE so_masterkey_old[MAX_MASTER_KEY_SIZE]; ++ CK_BYTE so_masterkey_new[MAX_MASTER_KEY_SIZE]; ++ CK_BYTE user_masterkey_old[MAX_MASTER_KEY_SIZE]; ++ CK_BYTE user_masterkey_new[MAX_MASTER_KEY_SIZE]; ++ CK_BYTE so_wrap_key[32]; ++ CK_BYTE user_wrap_key[32]; ++ CK_RV ret; ++ TOKEN_DATA tokdata; ++ ++ TRACE_INFO("Migrating the repository ...\n"); ++ ++ /* Load NVTOK.DAT_312, which was either created before or exists from a ++ * previous interrupted run. So tokdata definitely contains the 3.12 ++ * extension. ++ */ ++ ret = load_NVTOK_DAT(data_store, "NVTOK.DAT_312", &tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load NVTOK.DAT_312, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ ret = load_MK_SO_00(data_store, sopin, so_masterkey_old); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load old masterkey, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ ret = load_MK_USER_00(data_store, userpin, user_masterkey_old); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load old masterkey, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ if (memcmp(so_masterkey_old, user_masterkey_old, MAX_MASTER_KEY_SIZE) != 0) { ++ TRACE_ERROR("MK_SO and MK_USER are inconsistent, got different MKs.\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = load_MK_SO_312(data_store, sopin, &tokdata, so_masterkey_new); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load new masterkey from MK_SO_312, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ ret = load_MK_USER_312(data_store, userpin, &tokdata, user_masterkey_new); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load new masterkey from MK_USER_312, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ if (memcmp(so_masterkey_new, user_masterkey_new, MAX_MASTER_KEY_SIZE) != 0) { ++ TRACE_ERROR("MK_SO_312 and MK_USER_312 are inconsistent, got different MKs.\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* This function needs a new NVTOK.DAT with the public salt and icount */ ++ ret = derive_so_wrap_key_312(sopin, &tokdata, so_wrap_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create new so_wrap_key, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* This function needs a new NVTOK.DAT with the public salt and icount */ ++ ret = derive_user_wrap_key_312(userpin, &tokdata, user_wrap_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create new user_wrap_key, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Now do the migration */ ++ ret = migrate_token_objects(data_store, so_masterkey_old, so_masterkey_new, ++ so_wrap_key, user_wrap_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Migrating token objects failed with ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Remove temp files in backup */ ++ ret = cleanup_repository_backup(data_store); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cleanup repository backup failed with ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * creates a MK_USER_312 file containing the new user MK. ++ */ ++static CK_RV create_MK_USER_312(const char *data_store, const char *userpin, ++ const CK_BYTE *masterkey, ++ TOKEN_DATA *tokdata) ++{ ++ const char *mkuser = "MK_USER_312"; ++ char fname[PATH_MAX + 1 + strlen(mkuser) + 1]; ++ CK_BYTE user_wrap_key[32]; ++ CK_BYTE outbuf[40]; ++ FILE *fp = NULL; ++ size_t rv; ++ CK_RV ret; ++ ++ /* Create user wrap key */ ++ ret = derive_user_wrap_key_312(userpin, tokdata, (unsigned char *)&user_wrap_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot derive user wrap key, ret=%08lX.\n", ret); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Wrap user MK with user_wrap_key */ ++ ret = aes_256_wrap(outbuf, masterkey, user_wrap_key); ++ if (ret != CKR_OK) ++ goto done; ++ ++ /* Create file MK_USER_312 */ ++ sprintf(fname, "%s/%s", data_store, mkuser); ++ fp = fopen(fname, "w"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp)); ++ ++ rv = fwrite(outbuf, sizeof(outbuf), 1, fp); ++ if (rv != 1) { ++ TRACE_ERROR("fwrite(%s) failed, errno=%s.\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * creates a MK_SO_312 file containing the new SO MK. ++ */ ++static CK_RV create_MK_SO_312(const char *data_store, const char *sopin, ++ const CK_BYTE *masterkey, ++ TOKEN_DATA *tokdata) ++{ ++ const char *mkso = "MK_SO_312"; ++ char fname[PATH_MAX + 1 + strlen(mkso) + 1]; ++ CK_BYTE outbuf[40]; ++ CK_BYTE so_wrap_key[32]; ++ FILE *fp = NULL; ++ size_t rv; ++ CK_RV ret; ++ ++ /* Derive so wrap key from sopin and tokdata */ ++ ret = derive_so_wrap_key_312(sopin, tokdata, so_wrap_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot derive new so wrap key, ret=%08lX.\n", ret); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Wrap masterkey with SO_wrap_key */ ++ ret = aes_256_wrap(outbuf, masterkey, so_wrap_key); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot wrap masterkey with so wrap key, ret=%08lX.\n", ret); ++ goto done; ++ } ++ ++ /* Create file MK_SO_312 */ ++ sprintf(fname, "%s/%s", data_store, mkso); ++ fp = fopen(fname, "w"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ rv = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp)); ++ ++ rv = fwrite(outbuf, sizeof(outbuf), 1, fp); ++ if (rv != 1) { ++ TRACE_ERROR("fwrite(%s) failed, errno=%s.\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * reads the old NVTOK.DAT and returns its contents: ++ */ ++static CK_RV read_NVTOK_DAT_00(const char *data_store, TOKEN_DATA *tokdata) ++{ ++ FILE *fp; ++ const char *nvtok = "NVTOK.DAT"; ++ char fname[PATH_MAX + 1 + strlen(nvtok) + 1]; ++ struct stat stbuf; ++ int fd; ++ CK_RV ret; ++ ++ /* Check parms */ ++ if (!data_store || !tokdata) { ++ return CKR_ARGUMENTS_BAD; ++ } ++ ++ /* Read the old NVTOK.DAT */ ++ sprintf(fname, "%s/%s", data_store, nvtok); ++ fp = fopen((char *) fname, "r"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ return CKR_FUNCTION_FAILED; ++ } ++ ++ fd = fileno(fp); ++ if ((fstat(fd, &stbuf) != 0) || (!S_ISREG(stbuf.st_mode))) { ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Ensure that this NVTOK.DAT is in fact old */ ++ if (stbuf.st_size != sizeof(TOKEN_DATA_OLD)) { ++ TRACE_ERROR("%s has an invalid size of %ld bytes.\n", fname, stbuf.st_size); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Read TOKEN_DATA_OLD */ ++ ret = fread(tokdata, sizeof(TOKEN_DATA_OLD), 1, fp); ++ if (ret != 1) { ++ TRACE_ERROR("Cannot read %s, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * creates the additions for new format. ++ */ ++static CK_RV create_TOKEN_DATA_VERSION(const char *sopin, const char *userpin, ++ TOKEN_DATA *tokdata) ++{ ++ CK_RV ret; ++ int rc; ++ ++ tokdata->dat.version = TOKVERSION_312; ++ ++ tokdata->dat.so_login_it = SO_KDF_LOGIN_IT; ++ memcpy(tokdata->dat.so_login_salt, SO_KDF_LOGIN_PURPOSE, 32); ++ ret = local_rng(tokdata->dat.so_login_salt + 32, 32); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("local_rng returned %lX\n", ret); ++ goto done; ++ } ++ rc = PKCS5_PBKDF2_HMAC(sopin, strlen(sopin), ++ tokdata->dat.so_login_salt, 64, ++ tokdata->dat.so_login_it, EVP_sha512(), ++ 256 / 8, tokdata->dat.so_login_key); ++ if (rc != 1) { ++ TRACE_ERROR("Error: PKCS5_PBKDF2_HMAC\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ tokdata->dat.so_wrap_it = SO_KDF_WRAP_IT; ++ memcpy(tokdata->dat.so_wrap_salt, SO_KDF_WRAP_PURPOSE, 32); ++ ret = local_rng(tokdata->dat.so_wrap_salt + 32, 32); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("local_rng returned %lX\n", ret); ++ goto done; ++ } ++ ++ tokdata->dat.user_login_it = USER_KDF_LOGIN_IT; ++ memcpy(tokdata->dat.user_login_salt, USER_KDF_LOGIN_PURPOSE, 32); ++ ret = local_rng(tokdata->dat.user_login_salt + 32, 32); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("local_rng returned %lX\n", ret); ++ goto done; ++ } ++ rc = PKCS5_PBKDF2_HMAC(userpin, strlen(userpin), ++ tokdata->dat.user_login_salt, 64, ++ tokdata->dat.user_login_it, EVP_sha512(), ++ 256 / 8, tokdata->dat.user_login_key); ++ if (rc != 1) { ++ TRACE_ERROR("Error: PKCS5_PBKDF2_HMAC\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ tokdata->dat.user_wrap_it = USER_KDF_WRAP_IT; ++ memcpy(tokdata->dat.user_wrap_salt, USER_KDF_WRAP_PURPOSE, 32); ++ ret = local_rng(tokdata->dat.user_wrap_salt + 32, 32); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("local_rng returned %lX\n", ret); ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Creates the new NVTOK.DAT which now contains the public salt and iteration ++ * count values that are necessary for re-deriving the pin-related ++ * wrapping keys. ++ */ ++static CK_RV create_NVTOK_DAT_312(const char *data_store, const char *sopin, ++ const char *userpin, TOKEN_DATA *tokdata) ++{ ++ const char *nvtok = "NVTOK.DAT_312"; ++ char fname[PATH_MAX + 1 + strlen(nvtok) + 1]; ++ FILE *fp = NULL; ++ CK_RV ret; ++ size_t rc; ++ ++ /* Check parms */ ++ if (!data_store || !sopin || !userpin || !tokdata) { ++ TRACE_ERROR("invalid parms.\n"); ++ ret = CKR_ARGUMENTS_BAD; ++ goto done; ++ } ++ ++ /* Create new file NVTOK.DAT_312 */ ++ sprintf(fname, "%s/%s", data_store, nvtok); ++ fp = fopen(fname, "w"); ++ if (!fp) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp)); ++ ++ /* Get contents from old NVTOK.DAT */ ++ ret = read_NVTOK_DAT_00(data_store, tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot read old NVTOK.DAT, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ /* Write old part into NVTOK.DAT_312 */ ++ rc = fwrite(tokdata, sizeof(TOKEN_DATA_OLD), 1, fp); ++ if (rc != 1) { ++ TRACE_ERROR("fwrite(%s) failed, errno=%s.\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Create additions for new format */ ++ ret = create_TOKEN_DATA_VERSION(sopin, userpin, tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create TOKEN_DATA_VERSION struct, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ /* Append TOKEN_DATA_VERSION to NVTOK.DAT_312 */ ++ rc = fwrite(&(tokdata->dat), sizeof(TOKEN_DATA_VERSION), 1, fp); ++ if (rc != 1) { ++ TRACE_ERROR("fwrite(%s) failed, errno=%s.\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp) ++ fclose(fp); ++ ++ return ret; ++} ++ ++/** ++ * Creates new token keys MK_USER_312 and MK_SO_312. The old keys in ++ * MK_USER and MK_SO are kept until the migration is fully completed. ++ * Then the old keys are deleted and the new keys are renamed. ++ */ ++static CK_RV create_token_keys_312(const char *data_store, const char *sopin, ++ const char *userpin) ++{ ++ unsigned char masterkey[32]; ++ TOKEN_DATA tokdata; ++ CK_RV ret = CKR_OK; ++ ++ TRACE_INFO("Creating new v3.12 MK_SO, MK_USER, and NVTOK.DAT ...\n"); ++ ++ /* Create master key */ ++ ret = local_rng(masterkey, 32); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create master key, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ ret = create_NVTOK_DAT_312(data_store, sopin, userpin, &tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create NVTOK.DAT_312, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ ret = create_MK_SO_312(data_store, sopin, masterkey, &tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create MK_SO_312, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ ret = create_MK_USER_312(data_store, userpin, masterkey, &tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot create MK_USER_312, ret=%08lX\n", ret); ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Count the objs in the data_store and return the number of total objs ++ * and number of old objs. ++ */ ++static CK_RV count_objects(const char *data_store, unsigned int *num_objs, ++ unsigned int *num_old_objs) ++{ ++ char tmp[PATH_MAX], iname[PATH_MAX]; ++ unsigned char *obj = NULL; ++ unsigned int obj_len; ++ CK_ULONG version; ++ CK_BBOOL priv; ++ FILE *fp; ++ CK_RV ret; ++ ++ *num_objs = 0; ++ *num_old_objs = 0; ++ ++ /* Open index file OBJ.IDX */ ++ snprintf(iname, sizeof(iname), "%s/TOK_OBJ/OBJ.IDX", data_store); ++ fp = fopen((char *) iname, "r"); ++ if (!fp) { ++ TRACE_INFO("Cannot open %s, datastore probably empty.\n", iname); ++ ret = CKR_OK; ++ goto done; ++ } ++ ++ /* Count objects and old objects */ ++ while (fgets(tmp, PATH_MAX, fp)) { ++ tmp[strlen(tmp) - 1] = 0; ++ (*num_objs)++; ++ ret = read_object(data_store, tmp, &obj, &obj_len, &version, &priv); ++ if (ret == 0 && version == TOKVERSION_00) ++ (*num_old_objs)++; ++ if (obj) { ++ free(obj); ++ obj = NULL; ++ } ++ } ++ ++ /* OBJ.IDX must be at eof here */ ++ if (!feof(fp)) { ++ TRACE_WARN("OBJ.IDX is not at eof after object %s, should not happen.\n", ++ tmp); ++ } ++ ++ fclose(fp); ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Set parameter "*new" to true if the NVTOK.DAT in the given data store ++ * is on 3.12 level, or false otherwise. ++ */ ++static CK_RV NVTOK_DAT_is_312(const char *data_store, CK_BBOOL *new) ++{ ++ CK_RV ret; ++ char fname[PATH_MAX]; ++ struct stat stbuf; ++ int fd; ++ FILE *fp = NULL; ++ ++ *new = CK_FALSE; ++ ++ /* Read the NVTOK.DAT */ ++ snprintf(fname, PATH_MAX, "%s/NVTOK.DAT", data_store); ++ fp = fopen((char *)fname, "r"); ++ if (!fp) { ++ TRACE_ERROR("Cannot open %s, errno=%s\n", fname, strerror(errno)); ++ return CKR_FUNCTION_FAILED; ++ } ++ ++ fd = fileno(fp); ++ if ((fstat(fd, &stbuf) != 0) || (!S_ISREG(stbuf.st_mode))) { ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Check if this NVTOK.DAT is old or new */ ++ if (stbuf.st_size == sizeof(TOKEN_DATA_OLD)) { ++ *new = CK_FALSE; ++ } else if (stbuf.st_size == sizeof(TOKEN_DATA)) { ++ *new = CK_TRUE; ++ } else { ++ TRACE_ERROR("%s has an invalid size of %ld bytes. Neither old nor new token format.\n", ++ fname, stbuf.st_size); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Check if the data store is in 3.12 format. ++ */ ++static CK_RV datastore_is_312(const char *data_store, const char *sopin, ++ const char *userpin, CK_BBOOL *new) ++{ ++ CK_RV ret; ++ CK_BYTE masterkey_so[32]; ++ CK_BYTE masterkey_user[32]; ++ unsigned int num_objs = 0, num_old_objs = 0; ++ TOKEN_DATA tokdata; ++ ++ *new = CK_FALSE; ++ ++ TRACE_INFO("Checking if data store is already in 3.12 format ...\n"); ++ ++ /* Check if NVTOK.DAT is new */ ++ ret = NVTOK_DAT_is_312(data_store, new); ++ if (ret != CKR_OK) { ++ warnx("Cannot determine if NVTOK.DAT has an old or new format."); ++ warnx("Note that generic token formats cannot be migrated."); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ if (*new == CK_FALSE) { ++ ret = CKR_OK; ++ goto done; ++ } ++ ++ /* NVTOK.DAT is already new, now check if we can read the keys */ ++ ret = load_NVTOK_DAT(data_store, "NVTOK.DAT", &tokdata); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot load NVTOK.DAT, datastore inconsistent, ret=%08lX\n", ret); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = load_masterkey_312(data_store, "MK_SO", sopin, &tokdata, masterkey_so); ++ if (ret != CKR_OK) { ++ TRACE_INFO("Cannot load new MK from MK_SO, datastore probably old.\n"); ++ goto done; ++ } ++ ++ ret = load_masterkey_312(data_store, "MK_USER", userpin, &tokdata, masterkey_user); ++ if (ret != CKR_OK) { ++ TRACE_INFO("Cannot load new MK from MK_USER, datastore probably old.\n"); ++ goto done; ++ } ++ ++ if (memcmp(masterkey_so, masterkey_user, 32) != 0) { ++ TRACE_ERROR("MKs from MK_SO and MK_USER don't match, datastore inconsistent.\n"); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = count_objects(data_store, &num_objs, &num_old_objs); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("cannot count objects in %s.\n", data_store); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ TRACE_INFO("Found %d objects total, %d old objects.\n", num_objs, num_old_objs); ++ if (num_old_objs > 0) ++ TRACE_WARN("Note that the old objects are not usable anymore, because " ++ "we don't have the corresponding old masterkey!\n"); ++ ++ if (num_objs > 0 && num_old_objs == 0) ++ *new = CK_TRUE; ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Switch to new repository by deleting the old repository and renaming ++ * the backup folder to the original data store name. ++ */ ++static CK_RV switch_to_new_repository(const char *data_store_old, ++ const char *data_store_new) ++{ ++ char fname1[PATH_MAX]; ++ CK_RV ret; ++ int rc = -1; ++ ++ TRACE_INFO("Switching to new repository ...\n"); ++ ++ /* Rename original repository folder */ ++ snprintf(fname1, sizeof(fname1), "%s_BAK", data_store_old); ++ rc = rename(data_store_old, fname1); ++ if (rc) { ++ TRACE_ERROR("Cannot rename %s, errno=%s.\n", data_store_old, strerror(rc)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Rename backup folder */ ++ rc = rename(data_store_new, data_store_old); ++ if (rc) { ++ TRACE_ERROR("Cannot rename %s, errno=%s.\n", data_store_new, strerror(rc)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Inserts the new tokversion parm in the token's slot configuration, e.g. ++ * ++ * slot 2 ++ * { ++ * stdll = libpkcs11_cca.so ++ * tokversion = 3.12 ++ * } ++ */ ++static CK_RV update_opencryptoki_conf(CK_SLOT_ID slot_id, char *location) ++{ ++ const char *parm = "tokversion = 3.12\n"; ++ char dst_file[PATH_MAX], src_file[PATH_MAX], fname[PATH_MAX+20], line[PATH_MAX]; ++ char slot[32]; ++ FILE *fp_r = NULL, *fp_w = NULL; ++ CK_RV ret; ++ int rc; ++ ++ TRACE_INFO("Updating config file ...\n"); ++ ++ /* Open current conf file for read */ ++ snprintf(src_file, PATH_MAX, "%s/%s", location, "opencryptoki.conf"); ++ fp_r = fopen(src_file, "r"); ++ if (!fp_r) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", src_file, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Open new conf file for write */ ++ snprintf(dst_file, PATH_MAX, "%s/%s", location, "opencryptoki.conf_new"); ++ fp_w = fopen(dst_file, "w"); ++ if (!fp_w) { ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", dst_file, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp_w)); ++ ++ /* Insert/replace tokversion parm in new file */ ++ snprintf(slot, sizeof(slot), "slot %ld", slot_id); ++ while (fgets(line, sizeof(line), fp_r)) { ++ if (!begins_with(line, '#') && strstr(line, slot)) { ++ fputs(line, fp_w); // write "slot n" ++ /* insert tokversion before '}' */ ++ while (fgets(line, sizeof(line), fp_r)) { ++ if (strstr(line, "}")) { ++ fputs(parm, fp_w); ++ fputs(line, fp_w); ++ break; ++ } ++ /* Don't write existing tokversion to new file */ ++ if (!strstr(line, "tokversion")) ++ fputs(line, fp_w); ++ } ++ } else ++ fputs(line, fp_w); ++ } ++ ++ fclose(fp_r); ++ fclose(fp_w); ++ fp_r = NULL; ++ fp_w = NULL; ++ ++ /* Rename old conf file */ ++ snprintf(fname, sizeof(fname), "%s_BAK", src_file); ++ rc = rename(src_file, fname); ++ if (rc) { ++ TRACE_ERROR("Cannot rename %s\n", src_file); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Rename new file */ ++ rc = rename(dst_file, src_file); ++ if (rc) { ++ TRACE_ERROR("Cannot rename %s.\n", dst_file); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ if (fp_r) ++ fclose(fp_r); ++ if (fp_w) ++ fclose(fp_w); ++ ++ return ret; ++ ++} ++ ++/** ++ * Copy a file given by name from a src folder to a dst folder. ++ */ ++static CK_RV file_copy(char *dst, const char *src, const char *name) ++{ ++ char dst_file[PATH_MAX], src_file[PATH_MAX], buf[4096]; ++ FILE *fp_r = NULL, *fp_w = NULL; ++ size_t written; ++ CK_RV ret; ++ ++ snprintf(dst_file, PATH_MAX, "%s/%s", dst, name); ++ snprintf(src_file, PATH_MAX, "%s/%s", src, name); ++ ++ fp_r = fopen(src_file, "r"); ++ if (!fp_r) { ++ warnx("fopen(%s) failed, errno=%s", src_file, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ fp_w = fopen(dst_file, "w"); ++ if (!fp_w) { ++ warnx("fopen(%s) failed, errno=%s", dst_file, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ set_perm(fileno(fp_w)); ++ ++ while (!feof(fp_r)) { ++ size_t bytes = fread(buf, 1, sizeof(buf), fp_r); ++ if (bytes) { // can be zero, if file empty ++ written = fwrite(buf, 1, bytes, fp_w); ++ if (written != bytes) { ++ warnx("fwrite(%s) failed, errno=%s", dst_file, ++ strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ } ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ if (fp_r) ++ fclose(fp_r); ++ if (fp_w) ++ fclose(fp_w); ++ ++ return ret; ++} ++ ++/** ++ * Change the group owner of the given directory to 'pkcs11'. ++ */ ++static CK_RV change_owner(char *dir) ++{ ++ struct group* grp; ++ CK_RV ret; ++ ++ /* Set group owner */ ++ grp = getgrnam("pkcs11"); ++ if (grp) { ++ if (chown(dir, -1, grp->gr_gid)) { ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ } else { ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Fix group permissions (see man 2 mkdir for details) */ ++ if (chmod(dir, 0770)) { ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ return ret; ++} ++ ++/** ++ * Copy the given src folder to the given dst folder including all ++ * subdirectories and files. ++ */ ++static CK_RV folder_copy(char *dst, const char *src) ++{ ++ char d[PATH_MAX], s[PATH_MAX]; ++ struct dirent *entry; ++ CK_RV ret; ++ DIR *dir; ++ ++ /* Open src */ ++ dir = opendir(src); ++ if (dir == NULL) { ++ TRACE_ERROR("Cannot open %s\n", src); ++ return CKR_FUNCTION_FAILED; ++ } ++ ++ /* Create dst */ ++ if (mkdir(dst, 0) != 0) { ++ TRACE_ERROR("Cannot create %s\n", dst); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Change group owner and set permissions */ ++ ret = change_owner(dst); ++ if (ret != CKR_OK) { ++ TRACE_ERROR("Cannot change owner and permissions for %s\n", dst); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Copy folder recursively, skip the "." and ".." entries */ ++ while ((entry = readdir(dir)) != NULL) { ++ if (entry->d_type == DT_DIR) { ++ if (strncmp(entry->d_name, ".", 1) != 0) { ++ snprintf(d, PATH_MAX, "%s/%s", dst, entry->d_name); ++ snprintf(s, PATH_MAX, "%s/%s", src, entry->d_name); ++ ret = folder_copy(d, s); ++ if (ret != CKR_OK) ++ goto done; ++ } ++ } else { ++ ret = file_copy(dst, src, entry->d_name); ++ if (ret != CKR_OK) ++ goto done; ++ } ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ closedir(dir); ++ ++ return ret; ++} ++ ++/** ++ * Remove the given folder and all of its contents. ++ */ ++static CK_RV folder_delete(const char *folder) ++{ ++ DIR *dir; ++ char fname[PATH_MAX]; ++ size_t len, path_len; ++ struct stat statbuf; ++ struct dirent *ent; ++ CK_RV ret = CKR_OK; ++ ++ dir = opendir(folder); ++ if (!dir) { ++ TRACE_INFO("Folder %s doesn't exist.\n", folder); ++ return CKR_OK; ++ } ++ ++ path_len = strlen(folder); ++ while (!ret && (ent = readdir(dir))) { ++ if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) ++ continue; ++ len = path_len + strlen(ent->d_name) + 2; ++ snprintf(fname, len, "%s/%s", folder, ent->d_name); ++ if (!stat(fname, &statbuf)) { ++ if (S_ISDIR(statbuf.st_mode)) { ++ ret = folder_delete(fname); ++ if (ret != CKR_OK) ++ goto done; ++ } else { ++ ret = remove(fname); ++ if (ret != CKR_OK) ++ goto done; ++ } ++ } else { ++ /* stat failed */ ++ TRACE_ERROR("Cannot stat %s, errno=%s.\n", fname, strerror(errno)); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ } ++ ++ ret = CKR_OK; ++ ++done: ++ ++ closedir(dir); ++ if (ret == CKR_OK) ++ rmdir(folder); ++ ++ return ret; ++} ++ ++/** ++ * Backs up the given data_store to data_store_PKCSTOK_MIGRATE_TMP. ++ * All folders and files are recursively created and copied. ++ * Remove the backup if it already exists so that we always have ++ * a clean backup. ++ * The calling routine ensures that data_store does not end with a '/' ! ++ */ ++static CK_RV backup_repository(const char *data_store) ++{ ++ char dst[PATH_MAX]; ++ CK_RV ret = CKR_OK; ++ ++ TRACE_INFO("Creating data store backup ...\n"); ++ ++ memset(dst, 0, PATH_MAX); ++ snprintf(dst, PATH_MAX, "%s_PKCSTOK_MIGRATE_TMP", data_store); ++ ret = folder_delete(dst); ++ if (ret != CKR_OK) { ++ warnx("Fatal error: cannot delete old backup: %s", dst); ++ return CKR_FUNCTION_FAILED; ++ } ++ ++ return folder_copy(dst, data_store); ++} ++ ++/** ++ * Checks if the pkcsslotd is running. ++ */ ++static CK_BBOOL pkcsslotd_running(void) ++{ ++ DIR *dir; ++ FILE *fp; ++ struct dirent* ent; ++ char* endptr; ++ char buf[PATH_MAX]; ++ char fname[PATH_MAX]; ++ ++ TRACE_INFO("Checking if pkcsslotd is running ...\n"); ++ if (!(dir = opendir("/proc"))) { ++ TRACE_WARN("Cannot open /proc, i.e. cannot check if pkcsslotd is running.\n"); ++ return CK_TRUE; ++ } ++ ++ while ((ent = readdir(dir)) != NULL) { ++ /* if endptr is not a null character, the directory is not ++ * entirely numeric, so ignore it */ ++ long lpid = strtol(ent->d_name, &endptr, 10); ++ if (*endptr != '\0') { ++ continue; ++ } ++ ++ /* try to open the cmdline file */ ++ snprintf(fname, sizeof(fname), "/proc/%ld/cmdline", lpid); ++ fp = fopen(fname, "r"); ++ if (!fp) { ++ warnx("fopen(%s) failed, errno=%s", fname, strerror(errno)); ++ return CK_TRUE; ++ } ++ ++ /* check the first token in the file: the program pathname */ ++ if (fgets(buf, sizeof(buf), fp) != NULL) { ++ char* first = strtok(buf, " "); ++ if (!first) { ++ TRACE_WARN("Cannot read program name from %s, i.e. cannot check if pkcsslotd is running.\n", ++ fname); ++ return CK_TRUE; ++ } ++ if (strstr(first, "pkcsslotd") != NULL) { ++ fclose(fp); ++ closedir(dir); ++ return CK_TRUE; ++ } ++ } ++ fclose(fp); ++ } ++ ++ closedir(dir); ++ return CK_FALSE; ++} ++ ++/** ++ * ++ */ ++static CK_BBOOL token_invalid(const char *dll) ++{ ++ if (strcmp(dll, INVALID_TOKEN) == 0) ++ return CK_TRUE; ++ else ++ return CK_FALSE; ++} ++ ++/** ++ * returns the token name related to the given stdll name for the ++ * 4 supported tokens. ++ */ ++static const char *dll2name(const char *dll) ++{ ++ static char *dlls[] = { ++ "libpkcs11_ica.so", "libpkcs11_cca.so", ++ "libpkcs11_sw.so", "libpkcs11_ep11.so" ++ }; ++ static char *names[] = { ++ "ICA", "CCA", "Soft", "EP11" ++ }; ++ int i, num_tokens = sizeof(names) / sizeof(char *); ++ ++ for (i = 0; i < num_tokens; i++) { ++ if (strcmp(dll, dlls[i]) == 0) ++ return names[i]; ++ } ++ ++ return INVALID_TOKEN; ++} ++ ++/** ++ * translates the given verbose level string into a numeric verbose level. ++ * Returns -1 if the string is invalid. ++ */ ++static int verbose_str2level(char *str) ++{ ++ const char *tlevel[] = {"none", "error", "warn", "info", "devel", "debug"}; ++ const int num = sizeof(tlevel) / sizeof(char *); ++ int i; ++ ++ for (i = 0; i < num; i++) { ++ if (strcmp(str, tlevel[i]) == 0) { ++ return i; ++ } ++ } ++ ++ return -1; ++} ++ ++static void usage(char *progname) ++{ ++ printf(" Help:\t\t\t\t%s -h\n", progname); ++ printf(" -h, --help \t\t\tShow this help\n\n"); ++ printf(" Options:\n"); ++ printf(" -s, --slotid SLOTID\t\tPKCS slot number (required)\n"); ++ printf(" -d, --datastore DATASTORE\ttoken datastore location (required)\n"); ++ printf(" -c, --confdir CONFDIR\t\tlocation of opencryptoki.conf (required)\n"); ++ printf(" -u, --userpin USERPIN\t\ttoken user pin (prompted if not specified)\n"); ++ printf(" -p, --sopin SOPIN\t\ttoken SO pin (prompted if not specified)\n"); ++ printf(" -v, --verbose LEVEL\t\tset verbose level (optional):\n"); ++ printf("\t\t\t\tnone (default), error, warn, info, devel, debug\n"); ++ return; ++} ++ ++int main(int argc, char **argv) ++{ ++ CK_RV ret = 0; ++ int opt = 0, vlevel = -1; ++ CK_SLOT_ID slot_id = 0; ++ CK_BBOOL slot_id_specified = CK_FALSE; ++ size_t sopinlen, userpinlen, buflen = 0; ++ ssize_t num_chars; ++ char *data_store = NULL, *data_store_old = NULL, *conf_dir = NULL; ++ char *sopin = NULL, *userpin = NULL, *verbose = NULL; ++ char *buff = NULL; ++ char dll_name[PATH_MAX]; ++ CK_TOKEN_INFO_32 tokinfo; ++ CK_BBOOL new; ++ ++ static const struct option long_opts[] = { ++ {"datastore", required_argument, NULL, 'd'}, ++ {"confdir", required_argument, NULL, 'c'}, ++ {"slotid", required_argument, NULL, 's'}, ++ {"userpin", required_argument, NULL, 'u'}, ++ {"sopin", required_argument, NULL, 'p'}, ++ {"verbose", required_argument, NULL, 'v'}, ++ {"help", no_argument, NULL, 'h'}, ++ {0, 0, 0, 0} ++ }; ++ ++ while ((opt = getopt_long(argc, argv, "d:c:s:u:p:v:h", long_opts, NULL)) != -1) { ++ switch (opt) { ++ case 'd': ++ data_store = strdup(optarg); ++ break; ++ case 'c': ++ conf_dir = strdup(optarg); ++ break; ++ case 's': ++ slot_id = atoi(optarg); ++ slot_id_specified = CK_TRUE; ++ break; ++ case 'u': ++ userpin = strdup(optarg); ++ userpinlen = strlen(userpin); ++ break; ++ case 'p': ++ sopin = strdup(optarg); ++ sopinlen = strlen(sopin); ++ break; ++ case 'v': ++ verbose = strdup(optarg); ++ vlevel = verbose_str2level(verbose); ++ if (vlevel < 0) { ++ warnx("Invalid verbose level '%s' specified.", verbose); ++ usage(argv[0]); ++ exit(1); ++ } ++ break; ++ case 'h': ++ usage(argv[0]); ++ exit(0); ++ default: ++ warnx("pkcstok_migrate: Parameters are required."); ++ usage(argv[0]); ++ exit(1); ++ } ++ } ++ ++ if (argc == 1) { ++ usage(argv[0]); ++ exit(1); ++ } ++ ++ printf("\npkcstok_migrate:\n"); ++ printf("Summary of input parameters:\n"); ++ if (data_store) { ++ strip_trailing_chars(data_store, strlen(data_store), '/'); ++ printf(" datastore = %s \n", data_store); ++ } ++ if (conf_dir) { ++ strip_trailing_chars(conf_dir, strlen(conf_dir), '/'); ++ printf(" confdir = %s \n", conf_dir); ++ } ++ if (slot_id_specified) ++ printf(" slot ID = %ld\n", slot_id); ++ if (userpin) ++ printf(" user PIN specified\n"); ++ if (sopin) ++ printf(" SO PIN specified\n"); ++ if (vlevel >= 0) { ++ trace_level = vlevel; ++ printf(" verbose level = %s\n", verbose); ++ } ++ printf("\n"); ++ ++ /* Slot ID must be given */ ++ if (!slot_id_specified) { ++ warnx("Slot ID must be specified."); ++ goto done; ++ } ++ ++ /* Datastore must be given */ ++ if (data_store == NULL) { ++ warnx("Data store path must be specified."); ++ goto done; ++ } ++ ++ /* Limit datastore path length because of appended suffixes */ ++ if (strlen(data_store) > PKCSTOK_MIGRATE_MAX_PATH_LEN) { ++ warnx("Datastore path (%ld characters) is too long (max = %d).\n", ++ strlen(data_store), PKCSTOK_MIGRATE_MAX_PATH_LEN); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Location of opencryptoki.conf must be specified. */ ++ if (conf_dir == NULL) { ++ warnx("Location of opencryptoki.conf must be specified."); ++ goto done; ++ } ++ ++ /* Limit path to config file because of appended suffixes */ ++ if (strlen(conf_dir) > PKCSTOK_MIGRATE_MAX_PATH_LEN) { ++ warnx("Path to config file (%ld characters) is too long (max = %d).\n", ++ strlen(conf_dir), PKCSTOK_MIGRATE_MAX_PATH_LEN); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Check if given data_store exists */ ++ if (!datastore_exists(data_store)) { ++ ret = CKR_FUNCTION_FAILED; ++ warnx("Datastore %s does not exist.", data_store); ++ goto done; ++ } ++ ++ /* Check if given conf_dir exists and contains opencryptoki.conf */ ++ if (!conffile_exists(conf_dir)) { ++ ret = CKR_FUNCTION_FAILED; ++ warnx("%s does not exist or does not contain opencryptoki.conf", conf_dir); ++ goto done; ++ } ++ ++ /* Check if pkcsslotd is running */ ++ if (pkcsslotd_running()) { ++ warnx("Please stop pkcsslotd before running this utility."); ++ ret = CKR_FUNCTION_FAILED; ++ goto done; ++ } ++ ++ /* Identify token related to given slot ID */ ++ ret = identify_token(slot_id, conf_dir, dll_name); ++ if (ret != CKR_OK) { ++ warnx("Cannot identify a token related to given slot ID %ld", slot_id); ++ goto done; ++ } ++ ++ /* Check if DLL name from conf file is a known and migratable token */ ++ printf("Slot ID %ld points to DLL name %s, which is a %s token.\n", ++ slot_id, dll_name, dll2name(dll_name)); ++ if (token_invalid(dll2name(dll_name))) { ++ warnx("Please check your input."); ++ goto done; ++ } ++ ++ /* Check if there are token objects to migrate */ ++ if (datastore_empty(data_store)) { ++ ret = CKR_OK; ++ warnx("Datastore %s is empty, no objects to migrate.", data_store); ++ goto done; ++ } ++ ++ /* Get token info from NVTOK.DAT */ ++ ret = get_token_info(data_store, &tokinfo); ++ if (ret != CKR_OK) { ++ warnx("Cannot get the token label from NVTOK.DAT"); ++ goto done; ++ } ++ ++ /* Check with user if ok to migrate this token, or quit if token not migratable */ ++ printf("Data store %s points to this token info:\n", data_store); ++ printf(" label : %.*s\n", 32, tokinfo.label); ++ printf(" manufacturerID : %.*s\n", 32, tokinfo.manufacturerID); ++ printf(" model : %.*s\n", 16, tokinfo.model); ++ printf(" serialNumber : %.*s\n", 16, tokinfo.serialNumber); ++ printf(" hardwareVersion : %i.%i\n", tokinfo.hardwareVersion.major, tokinfo.hardwareVersion.minor); ++ printf(" firmwareVersion : %i.%i\n", tokinfo.firmwareVersion.major, tokinfo.firmwareVersion.minor); ++ printf("Migrate this token with given slot ID? y/n\n"); ++ num_chars = getline(&buff, &buflen, stdin); ++ if (num_chars < 0 || strncmp(buff, "y", 1) != 0) { ++ printf("ok, let's quit.\n"); ++ goto done; ++ } ++ ++ /* Get the SO pin to authorize migration */ ++ if (!sopin) { ++ printf("Enter the SO PIN: "); ++ fflush(stdout); ++ ret = get_pin(&sopin, &sopinlen); ++ if (ret != 0) { ++ warnx("Could not get SO PIN."); ++ goto done; ++ } ++ } ++ ++ /* Get the USER pin to authorize migration */ ++ if (!userpin) { ++ printf("Enter the USER PIN: "); ++ fflush(stdout); ++ ret = get_pin(&userpin, &userpinlen); ++ if (ret != 0) { ++ warnx("Could not get USER PIN."); ++ goto done; ++ } ++ } ++ ++ /* Verify the SO and USER PINs entered against NVTOK.DAT. */ ++ ret = verify_pins(data_store, sopin, sopinlen, userpin, userpinlen); ++ if (ret) { ++ warnx("Could not verify pins."); ++ goto done; ++ } ++ ++ /* Check if data store is already new */ ++ ret = datastore_is_312(data_store, sopin, userpin, &new); ++ if (ret == 0 && new) { ++ printf("Data store %s is already in new format.\n", data_store); ++ ret = update_opencryptoki_conf(slot_id, conf_dir); ++ if (ret != CKR_OK) ++ warnx("Failed to update opencryptoki.conf, you must do this manually."); ++ goto done; ++ } ++ ++ /* Backup repository if not already done */ ++ ret = backup_repository(data_store); ++ if (ret != CKR_OK) { ++ warnx("Failed to create backup."); ++ goto done; ++ } ++ ++ /* Perform all actions on the backup */ ++ char data_store_new[PATH_MAX]; ++ data_store_old = data_store; ++ snprintf(data_store_new, PATH_MAX, "%s_PKCSTOK_MIGRATE_TMP", data_store_old); ++ ++ /* Create new temp token keys, which exist in parallel to the old ones ++ * until the migration is fully completed. */ ++ ret = create_token_keys_312(data_store_new, sopin, userpin); ++ if (ret != CKR_OK) { ++ warnx("Failed to create new token keys."); ++ goto done; ++ } ++ ++ /* Migrate repository */ ++ ret = migrate_repository(data_store_new, sopin, userpin); ++ if (ret != CKR_OK) { ++ warnx("Failed to migrate repository."); ++ goto done; ++ } ++ ++ /* Switch to new repository */ ++ ret = switch_to_new_repository(data_store_old, data_store_new); ++ if (ret != CKR_OK) { ++ warnx("Switch to new repository failed."); ++ goto done; ++ } ++ ++ /* Now insert new 'tokversion=3.12' parm in opencryptoki.conf */ ++ ret = update_opencryptoki_conf(slot_id, conf_dir); ++ if (ret != CKR_OK) { ++ warnx("Failed to update opencryptoki.conf, you must do this manually."); ++ goto done; ++ } ++ ++ printf("Pre-migration data backed up at '%s_BAK'\n", data_store_old); ++ printf("Config file backed up at '%s/opencryptoki.conf_BAK'\n", conf_dir); ++ printf("Remove these backups manually after testing the new repository.\n"); ++ ++ ret = CKR_OK; ++ ++done: ++ ++ free(buff); ++ free(sopin); ++ free(userpin); ++ free(data_store); ++ free(conf_dir); ++ free(verbose); ++ ++ if (ret == CKR_OK) { ++ printf("pkcstok_migrate finished successfully.\n"); ++ return EXIT_SUCCESS; ++ } else { ++ printf("pkcstok_migrate finished with warnings/errors.\n"); ++ return EXIT_FAILURE; ++ } ++} +diff --git a/usr/sbin/pkcstok_migrate/pkcstok_migrate.mk b/usr/sbin/pkcstok_migrate/pkcstok_migrate.mk +new file mode 100644 +index 00000000..dc4582e5 +--- /dev/null ++++ b/usr/sbin/pkcstok_migrate/pkcstok_migrate.mk +@@ -0,0 +1,22 @@ ++sbin_PROGRAMS += usr/sbin/pkcstok_migrate/pkcstok_migrate ++noinst_HEADERS += misc/mech_types.h ++noinst_HEADERS += usr/lib/common/defs.h ++noinst_HEADERS += usr/lib/common/host_defs.h ++noinst_HEADERS += usr/include/local_types.h ++noinst_HEADERS += usr/lib/common/h_extern.h ++noinst_HEADERS += usr/lib/common/pkcs_utils.h ++ ++usr_sbin_pkcstok_migrate_pkcstok_migrate_LDFLAGS = -lcrypto -ldl ++ ++usr_sbin_pkcstok_migrate_pkcstok_migrate_CFLAGS = \ ++ -DSTDLL_NAME=\"pkcstok_migrate\" \ ++ -I${srcdir}/usr/include \ ++ -I${srcdir}/usr/lib/common \ ++ -I${srcdir}/usr/sbin/pkcstok_migrate ++ ++usr_sbin_pkcstok_migrate_pkcstok_migrate_SOURCES = \ ++ usr/lib/common/p11util.c \ ++ usr/lib/common/sw_crypt.c \ ++ usr/lib/common/trace.c \ ++ usr/lib/common/pkcs_utils.c \ ++ usr/sbin/pkcstok_migrate/pkcstok_migrate.c +diff --git a/usr/sbin/sbin.mk b/usr/sbin/sbin.mk +index 640308f5..01e152df 100644 +--- a/usr/sbin/sbin.mk ++++ b/usr/sbin/sbin.mk +@@ -13,6 +13,9 @@ endif + if ENABLE_P11SAK + include usr/sbin/p11sak/p11sak.mk + endif ++if ENABLE_PKCSTOK_MIGRATE ++include usr/sbin/pkcstok_migrate/pkcstok_migrate.mk ++endif + + include usr/sbin/pkcsslotd/pkcsslotd.mk + include usr/sbin/pkcsconf/pkcsconf.mk diff --git a/SOURCES/opencryptoki-3.14.0-crash-in-c_setpin.patch b/SOURCES/opencryptoki-3.14.0-crash-in-c_setpin.patch new file mode 100644 index 0000000..0ea8ccd --- /dev/null +++ b/SOURCES/opencryptoki-3.14.0-crash-in-c_setpin.patch @@ -0,0 +1,63 @@ +diff -up opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_openssl.c.me opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_openssl.c +--- opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_openssl.c.me 2020-05-26 08:51:32.714189399 -0400 ++++ opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_openssl.c 2020-05-26 08:52:16.429412060 -0400 +@@ -57,7 +57,7 @@ void openssl_print_errors() + } + #endif + +-RSA *openssl_gen_key() ++RSA *openssl_gen_key(STDLL_TokData_t *tokdata) + { + RSA *rsa; + int rc, counter = 0; +@@ -66,7 +66,7 @@ RSA *openssl_gen_key() + BIGNUM *bne; + #endif + +- token_specific_rng(NULL, (CK_BYTE *) buf, 32); ++ token_specific_rng(tokdata, (CK_BYTE *) buf, 32); + RAND_seed(buf, 32); + + regen_rsa_key: +diff -up opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.c.me opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.c +--- opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.c.me 2020-05-26 08:52:26.351235628 -0400 ++++ opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.c 2020-05-26 08:53:15.928354051 -0400 +@@ -159,8 +159,6 @@ CK_RV token_specific_rng(STDLL_TokData_t + TSS_HTPM hTPM; + BYTE *random_bytes = NULL; + +- UNUSED(tokdata); +- + rc = Tspi_Context_GetTpmObject(tpm_data->tspContext, &hTPM); + if (rc) { + TRACE_ERROR("Tspi_Context_GetTpmObject: %x\n", rc); +@@ -1389,7 +1387,7 @@ CK_RV token_create_private_tree(STDLL_To + unsigned char n[256], p[256]; + + /* all sw generated keys are 2048 bits */ +- if ((rsa = openssl_gen_key()) == NULL) ++ if ((rsa = openssl_gen_key(tokdata)) == NULL) + return CKR_HOST_MEMORY; + + if (openssl_get_modulus_and_prime(rsa, &size_n, n, &size_p, p) != 0) { +@@ -1467,7 +1465,7 @@ CK_RV token_create_public_tree(STDLL_Tok + unsigned char n[256], p[256]; + + /* all sw generated keys are 2048 bits */ +- if ((rsa = openssl_gen_key()) == NULL) ++ if ((rsa = openssl_gen_key(tokdata)) == NULL) + return CKR_HOST_MEMORY; + + if (openssl_get_modulus_and_prime(rsa, &size_n, n, &size_p, p) != 0) { +diff -up opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.h.me opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.h +--- opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.h.me 2020-05-26 08:53:20.281276648 -0400 ++++ opencryptoki-3.14.0/usr/lib/tpm_stdll/tpm_specific.h 2020-05-26 08:54:08.356421779 -0400 +@@ -56,7 +56,7 @@ + /* retry count for generating software RSA keys */ + #define KEYGEN_RETRY 5 + +-RSA *openssl_gen_key(); ++RSA *openssl_gen_key(STDLL_TokData_t *); + int openssl_write_key(STDLL_TokData_t *, RSA *, char *, CK_BYTE *); + CK_RV openssl_read_key(STDLL_TokData_t *, char *, CK_BYTE *, RSA **); + int openssl_get_modulus_and_prime(RSA *, unsigned int *, unsigned char *, diff --git a/SOURCES/opencryptoki-3.14.0-missing-p11sak-tool-a94436937b6364c53219fb3c7922439f403e8d5e.patch b/SOURCES/opencryptoki-3.14.0-missing-p11sak-tool-a94436937b6364c53219fb3c7922439f403e8d5e.patch new file mode 100644 index 0000000..5c7bddf --- /dev/null +++ b/SOURCES/opencryptoki-3.14.0-missing-p11sak-tool-a94436937b6364c53219fb3c7922439f403e8d5e.patch @@ -0,0 +1,22 @@ +commit a94436937b6364c53219fb3c7922439f403e8d5e +Author: Harald Freudenberger +Date: Wed May 27 07:30:33 2020 +0200 + + Fix missing entries for p11sak tool in template spec file + + Signed-off-by: Harald Freudenberger + +diff --git a/rpm/opencryptoki.spec b/rpm/opencryptoki.spec +index fa4b9899..ae563406 100644 +--- a/rpm/opencryptoki.spec ++++ b/rpm/opencryptoki.spec +@@ -238,7 +238,9 @@ exit 0 + %{_unitdir}/pkcsslotd.service + %{_sbindir}/pkcsconf + %{_sbindir}/pkcsslotd ++%{_sbindir}/p11sak + %{_mandir}/man1/pkcsconf.1* ++%{_mandir}/man1/p11sak.1* + %{_mandir}/man5/%{name}.conf.5* + %{_mandir}/man7/%{name}.7* + %{_mandir}/man8/pkcsslotd.8* diff --git a/SOURCES/opencryptoki-50a8a8806059647a3e446fd129995af61ec54867.patch b/SOURCES/opencryptoki-50a8a8806059647a3e446fd129995af61ec54867.patch deleted file mode 100644 index 3032fb6..0000000 --- a/SOURCES/opencryptoki-50a8a8806059647a3e446fd129995af61ec54867.patch +++ /dev/null @@ -1,33 +0,0 @@ -commit 50a8a8806059647a3e446fd129995af61ec54867 -Author: Ingo Franzki -Date: Tue Dec 3 14:58:26 2019 +0100 - - EP11: Fix EC-uncompress buffer length - - Function ec_uncompress_public_key() expects the size of the output - buffer in out_pubkey to be specified in the out_len parameter. - However, variable pubkey_len is uninitialized when calling - ec_uncompress_public_key(), so this may result in CKR_BUFFER_TOO_SMALL - dependent on the value of pubkey_len. - Fix this by setting pubkey_len to the size of the public key buffer - allocated above. - - Signed-off-by: Ingo Franzki - -diff --git a/usr/lib/ep11_stdll/ep11_specific.c b/usr/lib/ep11_stdll/ep11_specific.c -index 38b6708f..10dfe4e0 100644 ---- a/usr/lib/ep11_stdll/ep11_specific.c -+++ b/usr/lib/ep11_stdll/ep11_specific.c -@@ -2034,9 +2034,10 @@ static CK_RV import_EC_key(STDLL_TokData_t * tokdata, SESSION * sess, - rc = get_ecsiglen(ec_key_obj, &privkey_len); - if (rc != CKR_OK) - goto import_EC_key_end; -- privkey_len /= 2; /* Public key is half the size of an EC signature */ -+ privkey_len /= 2; /* private key is half the size of an EC signature */ - -- pubkey = (CK_BYTE *)malloc(1 + 2 * privkey_len); -+ pubkey_len = 1 + 2 * privkey_len; -+ pubkey = (CK_BYTE *)malloc(pubkey_len); - if (pubkey == NULL) { - rc = CKR_HOST_MEMORY; - goto import_EC_key_end; diff --git a/SPECS/opencryptoki.spec b/SPECS/opencryptoki.spec index e739bb5..9b65dfe 100644 --- a/SPECS/opencryptoki.spec +++ b/SPECS/opencryptoki.spec @@ -1,7 +1,7 @@ Name: opencryptoki Summary: Implementation of the PKCS#11 (Cryptoki) specification v2.11 -Version: 3.12.1 -Release: 2%{?dist} +Version: 3.14.0 +Release: 3%{?dist} License: CPL Group: System Environment/Base URL: https://github.com/opencryptoki/opencryptoki @@ -10,7 +10,12 @@ Source0: https://github.com/opencryptoki/%{name}/archive/v%{version}/%{name}-%{ Patch0: opencryptoki-3.11.0-group.patch # bz#1373833, change tmpfiles snippets from /var/lock/* to /run/lock/* Patch1: opencryptoki-3.11.0-lockdir.patch -Patch2: opencryptoki-50a8a8806059647a3e446fd129995af61ec54867.patch +# bz#1780293, fix regression, segfault in C_SetPin +Patch2: opencryptoki-3.14.0-crash-in-c_setpin.patch +# Fix missing entries for p11sak tool in template spec file +Patch3: opencryptoki-3.14.0-missing-p11sak-tool-a94436937b6364c53219fb3c7922439f403e8d5e.patch +# bz#1780294, PIN conversion tool +Patch4: opencryptoki-3.14.0-cd40f4b7cb1b502ca754b9bfb307d934285709a9-PIN-conversion-tool.patch Requires(pre): coreutils BuildRequires: gcc BuildRequires: openssl-devel @@ -244,8 +249,12 @@ fi %config(noreplace) %{_sysconfdir}/%{name}/%{name}.conf %{_tmpfilesdir}/%{name}.conf %{_unitdir}/pkcsslotd.service +%{_sbindir}/p11sak +%{_sbindir}/pkcstok_migrate %{_sbindir}/pkcsconf %{_sbindir}/pkcsslotd +%{_mandir}/man1/p11sak.1* +%{_mandir}/man1/pkcstok_migrate.1* %{_mandir}/man1/pkcsconf.1* %{_mandir}/man5/%{name}.conf.5* %{_mandir}/man7/%{name}.7* @@ -327,6 +336,19 @@ fi %changelog +* Mon Jun 15 2020 Than Ngo - 3.14.0-3 +- Resolves: #1780294, PIN conversion tool + +* Tue May 26 2020 Than Ngo - 3.14.0-2 +- Related: #1780293, fix regression, segfault in C_SetPin + +* Tue May 19 2020 Than Ngo - 3.14.0-1 +- Resolves: #1723863 - ep11 token: Enhanced Support +- Resolves: #1780285 - ep11 token: Support for new IBM Z hardware z15 +- Resolves: #1780293 - rebase to 3.14.0 +- Resolves: #1800549 - key management tool: list keys function + -Resolves: #1800555 - key management tool: random key generation function + * Fri Dec 13 2019 Than Ngo - 3.12.1-2 - Resolves: #1782445, EP11: Fix EC-uncompress buffer length