/*
* COPYRIGHT (c) International Business Machines Corp. 2012-2017
*
* 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
*/
/*
* OpenCryptoki ICSF token configuration tool.
*
*/
#define _GNU_SOURCE
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <termios.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include "icsf.h"
#include "slotmgr.h"
#include "pbkdf.h"
#include "defs.h"
#define CFG_ADD 0x0001
#define CFG_LIST 0x0002
#define CFG_BINDDN 0x0004
#define CFG_CERT 0x0008
#define CFG_PRIVKEY 0x0010
#define CFG_CACERT 0x0020
#define CFG_URI 0x0040
#define CFG_MECH 0x0080
#define CFG_MECH_SASL 0x0100
#define CFG_MECH_SIMPLE 0x0200
#define MAX_RECORDS 10
#define SALT_SIZE 16
#define SASL "sasl"
#define SLOT "slot"
#define TMPSIZ 64
#define LINESIZ 512
#define TOKBUF 2056
#define STDLL "libpkcs11_icsf.so"
LDAP *ld;
char *binddn = NULL;
char *uri = NULL;
char *mech = NULL;
char *cert = NULL;
char *cacert = NULL;
char *privkey = NULL;
unsigned long flags = 0;
void usage(char *progname)
{
printf("usage:\t%s [-h] [ -l | -a token-name] [-b BINDDN]"
" [-c client-cert-file] [-C CA-cert-file] [-k key] [-u URI]"
" [-m MECHANISM]\n", progname);
printf("\t-a add specified token\n");
printf("\t-b the distinguish name to bind for simple mode\n");
printf("\t-C the CA certificate file for SASL mode\n");
printf("\t-c the client certificate file for SASL mode\n");
printf("\t-h show this help\n");
printf("\t-k the client private key file for SASL mode\n");
printf("\t-l list available tokens\n");
printf("\t-m the authentication mechanism, "
"it can be 'simple' or 'sasl'\n");
printf("\t-u the URI to connect to\n");
exit(-1);
}
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;
}
int get_start_slot(void)
{
FILE *fp;
char temp[LINESIZ];
int num;
int found = -1;
struct stat statbuf;
/* if file doesn't exist, then first slot will be 0. */
if ((stat(OCK_CONFIG, &statbuf) < 0) && (errno == ENOENT))
return 0;
fp = fopen(OCK_CONFIG, "r");
if (fp == NULL) {
fprintf(stderr, "open failed, line %d: %s\n",
__LINE__, strerror(errno));
return -1;
}
/* step thru config file and get biggest slot number used */
while (fgets(temp, LINESIZ, fp) != NULL) {
if (strstr(temp, SLOT) != NULL) {
if (sscanf(temp, "%*s %d", &num) == 1)
if (num > found)
found = num;
}
}
/* bump up the slot number, since this will be next new slot entry.
* if it was an empty file or a file with no slot entries,
* then next new slot entry will be 0.
*/
fclose(fp);
return ++found;
}
int remove_file(char *filename)
{
struct stat statbuf;
/* if file exists, then remove it */
if ((stat(filename, &statbuf) < 0) && (errno == ENOENT)) {
if (unlink(filename) == -1) {
fprintf(stderr, "unlink failed for %s, line %d: %s\n",
filename, __LINE__, strerror(errno));
return -1;
}
}
return 0;
}
static void add_token_config_entry(FILE *fp, const char *key, const char *value)
{
if (!key || !value)
return;
fprintf(fp, "%s = \"%s\"\n", key, value);
}
int add_token_config(const char *configname,
struct icsf_token_record token, int slot)
{
FILE *tfp;
int rc = 0;
/* create the token config file */
tfp = fopen(configname, "w");
if (tfp == NULL) {
fprintf(stderr, "fopen failed, line %d: %s\n",
__LINE__, strerror(errno));
return -1;
}
/* add the info */
fprintf(tfp, "slot %d {\n", slot);
add_token_config_entry(tfp, "TOKEN_NAME", token.name);
add_token_config_entry(tfp, "TOKEN_MANUFACTURE", token.manufacturer);
add_token_config_entry(tfp, "TOKEN_MODEL", token.model);
add_token_config_entry(tfp, "TOKEN_SERIAL", token.serial);
add_token_config_entry(tfp, "MECH", (flags & CFG_MECH_SIMPLE)
? "SIMPLE" : "SASL");
/* add BIND info */
if (memcmp(mech, "simple", strlen("simple")) == 0) {
add_token_config_entry(tfp, "BINDDN", binddn);
add_token_config_entry(tfp, "URI", uri);
} else {
add_token_config_entry(tfp, "URI", uri);
add_token_config_entry(tfp, "CERT", cert);
add_token_config_entry(tfp, "CACERT", cacert);
add_token_config_entry(tfp, "KEY", privkey);
}
fprintf(tfp, "}\n");
fflush(tfp);
if (ferror(tfp) != 0) {
fprintf(stderr, "failed to add token named, %s\n", token.name);
rc = -1;
}
fclose(tfp);
return rc;
}
int config_add_slotinfo(int num_of_slots, struct icsf_token_record *tokens)
{
struct stat statbuf;
FILE *fp;
int start_slot = -1;
char configname[LINESIZ];
int i, rc;
/* get the starting slot for next entry */
start_slot = get_start_slot();
if (start_slot == -1)
return -1;
/* open the config file. if it doesn't exist, create it */
if ((stat(OCK_CONFIG, &statbuf) == -1) && (errno == ENOENT))
/* doesn't exist, create it */
fp = fopen(OCK_CONFIG, "w");
else
fp = fopen(OCK_CONFIG, "a");
if (fp == NULL) {
fprintf(stderr, "open failed, line %d: %s\n",
__LINE__, strerror(errno));
return -1;
}
/* For each token in the list do,
* - Create a slot entry in ock config file that contains
* the stdll and token config name.
* - Create a token config file that contains the token info
* from the ICSF and the BIND authentication info.
*/
for (i = 0; i < num_of_slots; i++) {
/* create the config name using the token's name */
memset(configname, 0, sizeof(configname));
snprintf(configname, sizeof(configname), "%s/%s.conf",
OCK_CONFDIR, tokens[i].name);
/* write the token info to the token's config file */
rc = add_token_config(configname, tokens[i], start_slot);
if (rc == -1) {
fprintf(stderr, "failed to add %s token.\n", tokens[i].name);
/* skip adding this entry */
continue;
}
/* add the slot entry to the ock config file */
fprintf(fp, "\nslot %d {\n", start_slot);
fprintf(fp, "stdll = %s\n", STDLL);
fprintf(fp, "confname = %s\n", configname);
fprintf(fp, "}\n");
fflush(fp);
if (ferror(fp) != 0) {
fprintf(stderr, "Failed to add an entry for %s token: "
"%s\n", tokens[i].name, strerror(errno));
remove_file(configname);
continue;
}
/* bump the slot number */
start_slot++;
}
fclose(fp);
return 0;
}
int list_tokens(void)
{
size_t i, tokenCount = MAX_RECORDS;
struct icsf_token_record *previous = NULL;
struct icsf_token_record tokens[MAX_RECORDS];
int rc, num_seen = 0;
do {
/* get the token list from remote z/OS host */
rc = icsf_list_tokens(ld, NULL, previous, tokens, &tokenCount);
if (ICSF_RC_IS_ERROR(rc))
return -1;
for (i = 0; i < tokenCount; i++) {
printf("Token #: %d\n"
"Token name: %s\n"
"Manufacturer: %s\n"
"Model: %s\n"
"Serial: %s\n"
"Read-only: %s\n\n",
num_seen, tokens[i].name,
tokens[i].manufacturer,
tokens[i].model, tokens[i].serial,
ICSF_IS_TOKEN_READ_ONLY(tokens[i].flags) ? "yes" : "no");
num_seen++;
}
if (tokenCount)
previous = &tokens[tokenCount - 1];
} while (tokenCount);
return 0;
}
int lookup_name(char *name, struct icsf_token_record *found)
{
size_t i, tokenCount = MAX_RECORDS;
struct icsf_token_record *previous = NULL;
struct icsf_token_record tokens[MAX_RECORDS];
int rc;
do {
/* get the token list from remote z/OS host */
rc = icsf_list_tokens(ld, NULL, previous, tokens, &tokenCount);
if (ICSF_RC_IS_ERROR(rc)) {
fprintf(stderr, "Could not get list of tokens.\n");
found = NULL;
return -1;
}
for (i = 0; i < tokenCount; i++) {
if (strncasecmp(name, tokens[i].name,
sizeof(tokens[i].name)) == 0) {
memcpy(found, &tokens[i], sizeof(struct icsf_token_record));
return 0;
}
}
if (tokenCount)
previous = &tokens[tokenCount - 1];
} while (tokenCount);
/* if we get here, we could not find the token in the list. */
found = NULL;
return -1;
}
void remove_racf_file(void)
{
char fname[PATH_MAX];
/* remove the so and user files */
snprintf(fname, sizeof(fname), "%s/RACF", ICSF_CONFIG_PATH);
remove_file(fname);
}
int retrieve_all(void)
{
size_t tokenCount;
struct icsf_token_record *previous = NULL;
struct icsf_token_record tokens[MAX_RECORDS];
int rc;
/* since pkcsslotd can only manage
* NUMBER_SLOTS_MANAGED at a time, use this as
* the maxiumum amount of tokens to retrieve...
*/
tokenCount = NUMBER_SLOTS_MANAGED;
rc = icsf_list_tokens(ld, NULL, previous, tokens, &tokenCount);
if (ICSF_RC_IS_ERROR(rc)) {
fprintf(stderr, "Could not get list of tokens.\n");
return -1;
}
/* add slot and token entry(ies) */
rc = config_add_slotinfo(tokenCount, tokens);
if (rc) {
fprintf(stderr, "Could not add list of tokens.\n");
return -1;
}
return 0;
}
int secure_racf_passwd(char *racfpwd, unsigned int len)
{
char *sopin = NULL;
unsigned char masterkey[AES_KEY_SIZE_256];
char fname[PATH_MAX];
int rc;
size_t sopinlen;
/* get the SO PIN */
printf("Enter the SO PIN: ");
fflush(stdout);
rc = get_pin(&sopin, &sopinlen);
if (rc != 0) {
fprintf(stderr, "Could not get SO PIN.\n");
rc = -1;
goto cleanup;
}
/* generate a masterkey */
if ((get_randombytes(masterkey, AES_KEY_SIZE_256)) != CKR_OK) {
fprintf(stderr, "Could not generate masterkey.\n");
rc = -1;
goto cleanup;
}
/* use the master key to secure the racf passwd */
rc = secure_racf((CK_BYTE *)racfpwd, len, masterkey, AES_KEY_SIZE_256);
if (rc != 0) {
fprintf(stderr, "Failed to secure racf passwd.\n");
rc = -1;
goto cleanup;
}
/* now secure the master key with a derived key */
/* first get the filename to put the encrypted masterkey */
snprintf(fname, sizeof(fname), "%s/MK_SO", ICSF_CONFIG_PATH);
rc = secure_masterkey(masterkey, AES_KEY_SIZE_256, (CK_BYTE *)sopin,
strlen(sopin), fname);
if (rc != 0) {
fprintf(stderr, "Failed to secure masterkey.\n");
/* remove the racf file */
remove_racf_file();
rc = -1;
goto cleanup;
}
cleanup:
if (sopin)
free(sopin);
return rc;
}
int main(int argc, char **argv)
{
char *racfpwd = NULL;
size_t racflen;
char *tokenname = NULL;
int c;
int rc = 0;
struct icsf_token_record found_token;
while ((c = getopt(argc, argv, "hla:b:u:m:k:c:C:")) != (-1)) {
switch (c) {
case 'a':
flags |= CFG_ADD;
if ((tokenname = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
break;
case 'l':
flags |= CFG_LIST;
break;
case 'b':
flags |= CFG_BINDDN;
if ((binddn = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
break;
case 'c':
flags |= CFG_CERT;
if ((cert = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
break;
case 'k':
flags |= CFG_PRIVKEY;
if ((privkey = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
break;
case 'C':
flags |= CFG_CACERT;
if ((cacert = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
break;
case 'u':
flags |= CFG_URI;
if ((uri = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
break;
case 'm':
flags |= CFG_MECH;
if ((mech = strdup(optarg)) == NULL) {
rc = -1;
fprintf(stderr, "strdup failed: line %d\n", __LINE__);
goto cleanup;
}
if (memcmp(mech, SASL, sizeof(SASL)) == 0)
flags |= CFG_MECH_SASL;
else
flags |= CFG_MECH_SIMPLE;
break;
case 'h':
default:
usage(argv[0]);
break;
}
}
/* Noticed that if a user misses an argument after an option,
* sometimes getopt misses it.
* For example, pkcsiscf -a -m -b xxxx -u xxxx"
* To catch these anomalies, check that optind == argc.
*/
if (optind != argc)
usage(argv[0]);
/* If there were no options, print usage. */
if ((!flags) || (!(flags & CFG_ADD) && !(flags & CFG_LIST)))
usage(argv[0]);
/* Currently, do not allow user to add a list of tokens.
* When ready to support multiple icsf tokens, this
* check can be removed.
*/
if ((flags & CFG_ADD) && !(memcmp(tokenname, "all", strlen(tokenname))))
usage(argv[0]);
/* If add, then must specify a mechanism and a name */
if ((flags & CFG_ADD) && (!(flags & CFG_MECH) || tokenname == NULL))
usage(argv[0]);
/* If list, then must specify a mechanism */
if ((flags & CFG_LIST) && !(flags & CFG_MECH))
usage(argv[0]);
/* Cannot add and list at the same time */
if ((flags & CFG_LIST) && (flags & CFG_ADD))
usage(argv[0]);
/* May only specify one mechanism */
if ((flags & CFG_MECH_SASL) && (flags & CFG_MECH_SIMPLE))
usage(argv[0]);
/* Cannot specify bind DN with SASL */
if ((flags & CFG_MECH_SASL) && (flags & CFG_BINDDN))
usage(argv[0]);
/* Cannot specify certs or key with SIMPLE */
if ((flags & CFG_MECH_SIMPLE)
&& (flags & (CFG_CERT | CFG_PRIVKEY | CFG_CACERT)))
usage(argv[0]);
/* get racf password if needed */
if ((flags & CFG_ADD) || (flags & CFG_LIST)) {
if (flags & CFG_MECH_SIMPLE) {
printf("Enter the RACF passwd: ");
fflush(stdout);
rc = get_pin(&racfpwd, &racflen);
if (rc != 0) {
fprintf(stderr, "Could not get RACF passwd.\n");
return -1;
}
/* bind to ldap server */
rc = icsf_login(&ld, uri, binddn, racfpwd);
} else {
rc = icsf_sasl_login(&ld, uri, NULL, NULL, NULL, NULL);
}
if (rc) {
fprintf(stderr, "Failed to bind to the ldap server.\n");
goto cleanup;
}
}
/* Add token(s) */
if (flags & CFG_ADD) {
if (memcmp(tokenname, "all", strlen(tokenname)) == 0) {
rc = retrieve_all();
if (rc) {
fprintf(stderr, "Could not add the list of " "tokens.\n");
goto cleanup;
}
} else {
/* add only the specified tokenname.
* first, find it in the list.
*/
rc = lookup_name(tokenname, &found_token);
if (rc != 0) {
fprintf(stderr,
"Could not find %s in token list.\n", tokenname);
rc = -1;
goto cleanup;
}
/* add the entry */
rc = config_add_slotinfo(1, &found_token);
if (rc != 0)
goto cleanup;
}
if (flags & CFG_MECH_SIMPLE) {
/* when using simple auth, secure racf passwd. */
rc = secure_racf_passwd(racfpwd, strlen(racfpwd));
if (rc != 0)
goto cleanup;
}
}
if (flags & CFG_LIST) {
/* print the list of available tokens */
rc = list_tokens();
if (rc != 0)
fprintf(stderr, "Could not get full list of tokens.\n");
}
cleanup:
if (ld)
icsf_logout(ld);
if (tokenname)
free(tokenname);
if (binddn)
free(binddn);
if (cert)
free(cert);
if (privkey)
free(privkey);
if (cacert)
free(cacert);
if (uri)
free(uri);
if (mech)
free(mech);
if (racfpwd)
free(racfpwd);
return rc;
}