From 8dd51fe8c5a782b437c645e6e763bac37718807a Mon Sep 17 00:00:00 2001 From: Packit Date: Aug 20 2020 13:34:21 +0000 Subject: Apply patch opencryptoki-3.14.0-cd40f4b7cb1b502ca754b9bfb307d934285709a9-PIN-conversion-tool.patch patch_name: opencryptoki-3.14.0-cd40f4b7cb1b502ca754b9bfb307d934285709a9-PIN-conversion-tool.patch location_in_specfile: 5 present_in_specfile: true --- diff --git a/configure.ac b/configure.ac index 3775019..7c42a97 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 51d23d3..6102340 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 0000000..b17d10b --- /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 ae56340..64142e2 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 c30dd1a..068fdf3 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 3e4ba3b..1bdbe64 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 0000000..d3074d5 --- /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 0000000..248559c --- /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 0000000..e90a5c9 --- /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 0000000..dc4582e --- /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 640308f..01e152d 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