/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Module: keystore.c
* Description: This module contains the structure definitions for processing
* package keystore files.
*/
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <libintl.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pkcs12.h>
#include <openssl/asn1.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/safestack.h>
#include <openssl/stack.h>
#include "p12lib.h"
#include "pkgerr.h"
#include "keystore.h"
#include "pkglib.h"
#include "pkglibmsgs.h"
typedef struct keystore_t {
boolean_t dirty;
boolean_t new;
char *path;
char *passphrase;
/* truststore handles */
int cafd;
STACK_OF(X509) *cacerts;
char *capath;
/* user certificate handles */
STACK_OF(X509) *clcerts;
char *clpath;
/* private key handles */
STACK_OF(EVP_PKEY) *pkeys;
char *keypath;
} keystore_t;
/* local routines */
static keystore_t *new_keystore(void);
static void free_keystore(keystore_t *);
static boolean_t verify_keystore_integrity(PKG_ERR *, keystore_t *);
static boolean_t check_password(PKCS12 *, char *);
static boolean_t resolve_paths(PKG_ERR *, char *, char *,
long, keystore_t *);
static boolean_t lock_keystore(PKG_ERR *, long, keystore_t *);
static boolean_t unlock_keystore(PKG_ERR *, keystore_t *);
static boolean_t read_keystore(PKG_ERR *, keystore_t *,
keystore_passphrase_cb);
static boolean_t write_keystore(PKG_ERR *, keystore_t *,
keystore_passphrase_cb);
static boolean_t write_keystore_file(PKG_ERR *, char *, PKCS12 *);
static boolean_t clear_keystore_file(PKG_ERR *, char *);
static PKCS12 *read_keystore_file(PKG_ERR *, char *);
static char *get_time_string(ASN1_TIME *);
/* locking routines */
static boolean_t restore_keystore_file(PKG_ERR *, char *);
static int file_lock(int, int, int);
static int file_unlock(int);
static boolean_t file_lock_test(int, int);
static boolean_t file_empty(char *);
static boolean_t get_keystore_passwd(PKG_ERR *err, PKCS12 *p12,
keystore_passphrase_cb cb, keystore_t *keystore);
static boolean_t wait_restore(int, char *, char *, char *);
#define KEYSTORE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
/* wait on other keystore access for 1 minute before giving up */
#define LOCK_TIMEOUT 60
/*
* print_certs - prints certificates out of a keystore, to a file.
*
* Arguments:
* err - Error object to append errors to
* keystore - Keystore on which to operate
* alias - Name of certificate to print, NULL means print all
* format - Format in which to print certificates
* outfile - Where to print certificates
*
* Returns:
* 0 - Success
* non-zero - Failure, errors added to err
*/
int
print_certs(PKG_ERR *err, keystore_handle_t keystore_h, char *alias,
keystore_encoding_format_t format, FILE *outfile)
{
int i;
X509 *cert;
char *fname = NULL;
boolean_t found = B_FALSE;
keystore_t *keystore = keystore_h;
if (keystore->clcerts != NULL) {
/* print out each client cert */
for (i = 0; i < sk_X509_num(keystore->clcerts); i++) {
cert = sk_X509_value(keystore->clcerts, i);
(void) sunw_get_cert_fname(GETDO_COPY, cert,
&fname);
if (fname == NULL) {
/* no name recorded, keystore is corrupt */
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_NO_ALIAS),
get_subject_display_name(cert));
return (1);
}
if ((alias != NULL) && (!streq(alias, fname))) {
/* name does not match, skip it */
(void) OPENSSL_free(fname);
fname = NULL;
continue;
} else {
found = B_TRUE;
(void) print_cert(err, cert, format,
fname, B_FALSE, outfile);
(void) OPENSSL_free(fname);
fname = NULL;
}
}
}
if (fname != NULL) {
(void) OPENSSL_free(fname);
fname = NULL;
}
if (keystore->cacerts != NULL) {
/* print out each trusted cert */
for (i = 0; i < sk_X509_num(keystore->cacerts); i++) {
cert = sk_X509_value(keystore->cacerts, i);
(void) sunw_get_cert_fname(GETDO_COPY,
cert, &fname);
if (fname == NULL) {
/* no name recorded, keystore is corrupt */
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_NO_ALIAS),
get_subject_display_name(cert));
return (1);
}
if ((alias != NULL) && (!streq(alias, fname))) {
/* name does not match, skip it */
(void) OPENSSL_free(fname);
fname = NULL;
continue;
} else {
found = B_TRUE;
(void) print_cert(err, cert, format,
fname, B_TRUE, outfile);
(void) OPENSSL_free(fname);
fname = NULL;
}
}
}
if (fname != NULL) {
(void) OPENSSL_free(fname);
fname = NULL;
}
if (found) {
return (0);
} else {
/* no certs printed */
if (alias != NULL) {
pkgerr_add(err, PKGERR_NOALIASMATCH,
gettext(ERR_KEYSTORE_NOCERT),
alias, keystore->path);
} else {
pkgerr_add(err, PKGERR_NOPUBKEY,
gettext(ERR_KEYSTORE_NOPUBCERTS),
keystore->path);
pkgerr_add(err, PKGERR_NOCACERT,
gettext(ERR_KEYSTORE_NOCACERTS),
keystore->path);
}
return (1);
}
}
/*
* print_cert - prints a single certificate, to a file
*
* Arguments:
* err - Error object to append errors to
* x - The certificate to print
* alias - Name of certificate to print
* format - Format in which to print certificate
* outfile - Where to print certificate
*
* Returns:
* 0 - Success
* non-zero - Failure, errors added to err
*/
int print_cert(PKG_ERR *err, X509 *x,
keystore_encoding_format_t format, char *alias, boolean_t is_trusted,
FILE *outfile)
{
char *vdb_str;
char *vda_str;
char vd_str[ATTR_MAX];
int ret = 0;
char *cn_str, *icn_str, *typ_str;
char *tmp;
char *md5_fp;
char *sha1_fp;
int len;
/* need to localize the word "Fingerprint", hence these pointers */
char md5_label[ATTR_MAX];
char sha1_label[ATTR_MAX];
if (is_trusted) {
typ_str = gettext(MSG_KEYSTORE_TRUSTED);
} else {
typ_str = gettext(MSG_KEYSTORE_UNTRUSTED);
}
if ((cn_str = get_subject_display_name(x)) == NULL) {
cn_str = gettext(MSG_KEYSTORE_UNKNOWN);
}
if ((icn_str = get_issuer_display_name(x)) == NULL) {
icn_str = gettext(MSG_KEYSTORE_UNKNOWN);
}
vdb_str = xstrdup(get_time_string(X509_get_notBefore(x)));
vda_str = xstrdup(get_time_string(X509_get_notAfter(x)));
if (((len = snprintf(vd_str, ATTR_MAX, "<%s> - <%s>",
vdb_str, vda_str)) < 0) || (len >= ATTR_MAX)) {
pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), vdb_str);
ret = 1;
goto cleanup;
}
if ((tmp = get_fingerprint(x, EVP_md5())) == NULL) {
md5_fp = gettext(MSG_KEYSTORE_UNKNOWN);
} else {
/*
* make a copy, otherwise the next call to get_fingerprint
* will overwrite this one
*/
md5_fp = xstrdup(tmp);
}
if ((tmp = get_fingerprint(x, EVP_sha1())) == NULL) {
sha1_fp = gettext(MSG_KEYSTORE_UNKNOWN);
} else {
sha1_fp = xstrdup(tmp);
}
(void) snprintf(md5_label, ATTR_MAX, "%s %s",
OBJ_nid2sn(EVP_MD_type(EVP_md5())),
/* i18n: 14 characters max */
gettext(MSG_KEYSTORE_FP));
(void) snprintf(sha1_label, ATTR_MAX, "%s %s",
OBJ_nid2sn(EVP_MD_type(EVP_sha1())),
/* i18n: 14 characters max */
gettext(MSG_KEYSTORE_FP));
switch (format) {
case KEYSTORE_FORMAT_PEM:
(void) PEM_write_X509(outfile, x);
break;
case KEYSTORE_FORMAT_DER:
(void) i2d_X509_fp(outfile, x);
break;
case KEYSTORE_FORMAT_TEXT:
(void) fprintf(outfile, "%18s: %s\n",
/* i18n: 18 characters max */
gettext(MSG_KEYSTORE_AL), alias);
(void) fprintf(outfile, "%18s: %s\n",
/* i18n: 18 characters max */
gettext(MSG_KEYSTORE_CN), cn_str);
(void) fprintf(outfile, "%18s: %s\n",
/* i18n: 18 characters max */
gettext(MSG_KEYSTORE_TY), typ_str);
(void) fprintf(outfile, "%18s: %s\n",
/* i18n: 18 characters max */
gettext(MSG_KEYSTORE_IN), icn_str);
(void) fprintf(outfile, "%18s: %s\n",
/* i18n: 18 characters max */
gettext(MSG_KEYSTORE_VD), vd_str);
(void) fprintf(outfile, "%18s: %s\n", md5_label, md5_fp);
(void) fprintf(outfile, "%18s: %s\n", sha1_label, sha1_fp);
(void) fprintf(outfile, "\n");
break;
default:
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL),
__FILE__, __LINE__);
ret = 1;
goto cleanup;
}
cleanup:
if (md5_fp != NULL)
free(md5_fp);
if (sha1_fp != NULL)
free(sha1_fp);
if (vda_str != NULL)
free(vda_str);
if (vdb_str != NULL)
free(vdb_str);
return (ret);
}
/*
* open_keystore - Initialize new keystore object for
* impending access.
*
* Arguments:
* err - Error object to append errors to
* keystore_file - Base filename or directory of keystore
* app - Application making request
* passwd - Password used to decrypt keystore
* flags - Control flags used to control access mode and behavior
* result - Resulting keystore object stored here on success
*
* Returns:
* 0 - Success - result contains a pointer to the opened keystore
* non-zero - Failure, errors added to err
*/
int
open_keystore(PKG_ERR *err, char *keystore_file, char *app,
keystore_passphrase_cb cb, long flags, keystore_handle_t *result)
{
int ret = 0;
keystore_t *tmpstore;
tmpstore = new_keystore();
tmpstore->dirty = B_FALSE;
tmpstore->new = B_FALSE;
tmpstore->path = xstrdup(keystore_file);
if (!resolve_paths(err, keystore_file, app, flags, tmpstore)) {
/* unable to determine keystore paths */
pkgerr_add(err, PKGERR_CORRUPT, gettext(ERR_KEYSTORE_REPAIR),
keystore_file);
ret = 1;
goto cleanup;
}
if (!verify_keystore_integrity(err, tmpstore)) {
/* unable to repair keystore */
pkgerr_add(err, PKGERR_CORRUPT, gettext(ERR_KEYSTORE_REPAIR),
keystore_file);
ret = 1;
goto cleanup;
}
if (!lock_keystore(err, flags, tmpstore)) {
pkgerr_add(err, PKGERR_LOCKED, gettext(ERR_KEYSTORE_LOCKED),
keystore_file);
ret = 1;
goto cleanup;
}
/* now that we have locked the keystore, go ahead and read it */
if (!read_keystore(err, tmpstore, cb)) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_PARSE),
keystore_file);
ret = 1;
goto cleanup;
}
*result = tmpstore;
tmpstore = NULL;
cleanup:
if (tmpstore != NULL)
free_keystore(tmpstore);
return (ret);
}
/*
* new_keystore - Allocates and initializes a Keystore object
*
* Arguments:
* NONE
*
* Returns:
* NULL - out of memory
* otherwise, returns a pointer to the newly allocated object,
* which should be freed with free_keystore() when no longer
* needed.
*/
static keystore_t
*new_keystore(void)
{
keystore_t *tmpstore;
if ((tmpstore = (keystore_t *)malloc(sizeof (keystore_t))) == NULL) {
return (NULL);
}
tmpstore->dirty = B_FALSE;
tmpstore->new = B_FALSE;
tmpstore->path = NULL;
tmpstore->passphrase = NULL;
tmpstore->cafd = -1;
tmpstore->cacerts = NULL;
tmpstore->capath = NULL;
tmpstore->clcerts = NULL;
tmpstore->clpath = NULL;
tmpstore->pkeys = NULL;
tmpstore->keypath = NULL;
return (tmpstore);
}
/*
* free_keystore - Deallocates a Keystore object
*
* Arguments:
* keystore - The keystore to deallocate
*
* Returns:
* NONE
*/
static void
free_keystore(keystore_t *keystore)
{
if (keystore->path != NULL)
free(keystore->path);
if (keystore->capath != NULL)
free(keystore->capath);
if (keystore->passphrase != NULL)
free(keystore->passphrase);
if (keystore->clpath != NULL)
free(keystore->clpath);
if (keystore->keypath != NULL)
free(keystore->keypath);
if (keystore->pkeys != NULL) {
sk_EVP_PKEY_pop_free(keystore->pkeys,
sunw_evp_pkey_free);
}
if (keystore->clcerts != NULL)
sk_X509_free(keystore->clcerts);
if (keystore->cacerts != NULL)
sk_X509_free(keystore->cacerts);
free(keystore);
}
/*
* close_keystore - Writes keystore to disk if needed, then
* unlocks and closes keystore.
*
* Arguments:
* err - Error object to append errors to
* keystore - Keystore which should be closed
* passwd - Password used to encrypt keystore
*
* Returns:
* 0 - Success - keystore is committed to disk, and unlocked
* non-zero - Failure, errors added to err
*/
int
close_keystore(PKG_ERR *err, keystore_handle_t keystore_h,
keystore_passphrase_cb cb)
{
int ret = 0;
keystore_t *keystore = keystore_h;
if (keystore->dirty) {
/* write out the keystore first */
if (!write_keystore(err, keystore, cb)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->path);
ret = 1;
goto cleanup;
}
}
if (!unlock_keystore(err, keystore)) {
pkgerr_add(err, PKGERR_UNLOCK, gettext(ERR_KEYSTORE_UNLOCK),
keystore->path);
ret = 1;
goto cleanup;
}
free_keystore(keystore);
cleanup:
return (ret);
}
/*
* merge_ca_cert - Adds a trusted certificate (trust anchor) to a keystore.
* certificate checked for validity dates and non-duplicity.
*
* Arguments:
* err - Error object to add errors to
* cacert - Certificate which to merge into keystore
* keystore - The keystore into which the certificate is merged
*
* Returns:
* 0 - Success - Certificate passes validity, and
* is merged into keystore
* non-zero - Failure, errors recorded in err
*/
int
merge_ca_cert(PKG_ERR *err, X509 *cacert, keystore_handle_t keystore_h)
{
int ret = 0;
X509 *existing = NULL;
char *fname;
keystore_t *keystore = keystore_h;
/* check validity dates */
if (check_cert(err, cacert) != 0) {
ret = 1;
goto cleanup;
}
/* create the certificate's friendlyName */
fname = get_subject_display_name(cacert);
if (sunw_set_fname(fname, NULL, cacert) != 0) {
pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM));
ret = 1;
goto cleanup;
}
/* merge certificate into the keystore */
if (keystore->cacerts == NULL) {
/* no existing truststore, so make a new one */
if ((keystore->cacerts = sk_X509_new_null()) == NULL) {
pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM));
ret = 1;
goto cleanup;
}
} else {
/* existing truststore, make sure there's no duplicate */
if (sunw_find_fname(fname, NULL, keystore->cacerts,
NULL, &existing) < 0) {
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL),
__FILE__, __LINE__);
ERR_print_errors_fp(stderr);
ret = 1;
goto cleanup;
/* could not search properly! */
}
if (existing != NULL) {
/* whoops, found one already */
pkgerr_add(err, PKGERR_DUPLICATE,
gettext(ERR_KEYSTORE_DUPLICATECERT), fname);
ret = 1;
goto cleanup;
}
}
(void) sk_X509_push(keystore->cacerts, cacert);
keystore->dirty = B_TRUE;
cleanup:
if (existing != NULL)
X509_free(existing);
return (ret);
}
/*
* find_key_cert_pair - Searches a keystore for a matching
* public key certificate and private key, given an alias.
*
* Arguments:
* err - Error object to add errors to
* ks - Keystore to search
* alias - Name to used to match certificate's alias
* key - Resulting key is placed here
* cert - Resulting cert is placed here
*
* Returns:
* 0 - Success - Matching cert/key pair placed in key and cert.
* non-zero - Failure, errors recorded in err
*/
int
find_key_cert_pair(PKG_ERR *err, keystore_handle_t ks_h, char *alias,
EVP_PKEY **key, X509 **cert)
{
X509 *tmpcert = NULL;
EVP_PKEY *tmpkey = NULL;
int ret = 0;
int items_found;
keystore_t *ks = ks_h;
if (key == NULL || cert == NULL) {
pkgerr_add(err, PKGERR_NOPUBKEY,
gettext(ERR_KEYSTORE_NOPUBCERTS), ks->path);
ret = 1;
goto cleanup;
}
if (ks->clcerts == NULL) {
/* no public certs */
pkgerr_add(err, PKGERR_NOPUBKEY,
gettext(ERR_KEYSTORE_NOCERTS), ks->path);
ret = 1;
goto cleanup;
}
if (ks->pkeys == NULL) {
/* no private keys */
pkgerr_add(err, PKGERR_NOPRIVKEY,
gettext(ERR_KEYSTORE_NOKEYS), ks->path);
ret = 1;
goto cleanup;
}
/* try the easy case first */
if ((sk_EVP_PKEY_num(ks->pkeys) == 1) &&
(sk_X509_num(ks->clcerts) == 1)) {
tmpkey = sk_EVP_PKEY_value(ks->pkeys, 0);
tmpcert = sk_X509_value(ks->clcerts, 0);
if (sunw_check_keys(tmpcert, tmpkey)) {
/*
* only one private key and public key cert, and they
* match, so use them
*/
*key = tmpkey;
tmpkey = NULL;
*cert = tmpcert;
tmpcert = NULL;
goto cleanup;
}
}
/* Attempt to find the right pair given the alias */
items_found = sunw_find_fname(alias, ks->pkeys, ks->clcerts,
&tmpkey, &tmpcert);
if ((items_found < 0) ||
(items_found & (FOUND_PKEY | FOUND_CERT)) == 0) {
/* no key/cert pair found. bail. */
pkgerr_add(err, PKGERR_BADALIAS,
gettext(ERR_KEYSTORE_NOMATCH), alias);
ret = 1;
goto cleanup;
}
/* success */
*key = tmpkey;
tmpkey = NULL;
*cert = tmpcert;
tmpcert = NULL;
cleanup:
if (tmpcert != NULL)
(void) X509_free(tmpcert);
if (tmpkey != NULL)
sunw_evp_pkey_free(tmpkey);
return (ret);
}
/*
* find_ca_certs - Searches a keystore for trusted certificates
*
* Arguments:
* err - Error object to add errors to
* ks - Keystore to search
* cacerts - resulting set of trusted certs are placed here
*
* Returns:
* 0 - Success - trusted cert list returned in cacerts
* non-zero - Failure, errors recorded in err
*/
int
find_ca_certs(PKG_ERR *err, keystore_handle_t ks_h, STACK_OF(X509) **cacerts)
{
keystore_t *ks = ks_h;
/* easy */
if (cacerts == NULL) {
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL), __FILE__, __LINE__);
return (1);
}
*cacerts = ks->cacerts;
return (0);
}
/*
* find_cl_certs - Searches a keystore for user certificates
*
* Arguments:
* err - Error object to add errors to
* ks - Keystore to search
* cacerts - resulting set of user certs are placed here
*
* No matching of any kind is performed.
* Returns:
* 0 - Success - trusted cert list returned in cacerts
* non-zero - Failure, errors recorded in err
*/
/* ARGSUSED */
int
find_cl_certs(PKG_ERR *err, keystore_handle_t ks_h, STACK_OF(X509) **clcerts)
{
keystore_t *ks = ks_h;
/* easy */
*clcerts = ks->clcerts;
return (0);
}
/*
* merge_cert_and_key - Adds a user certificate and matching
* private key to a keystore.
* certificate checked for validity dates and non-duplicity.
*
* Arguments:
* err - Error object to add errors to
* cert - Certificate which to merge into keystore
* key - matching private key to 'cert'
* alias - Name which to store the cert and key under
* keystore - The keystore into which the certificate is merged
*
* Returns:
* 0 - Success - Certificate passes validity, and
* is merged into keystore, along with key
* non-zero - Failure, errors recorded in err
*/
int
merge_cert_and_key(PKG_ERR *err, X509 *cert, EVP_PKEY *key, char *alias,
keystore_handle_t keystore_h)
{
X509 *existingcert = NULL;
EVP_PKEY *existingkey = NULL;
int ret = 0;
keystore_t *keystore = keystore_h;
/* check validity dates */
if (check_cert(err, cert) != 0) {
ret = 1;
goto cleanup;
}
/* set the friendlyName of the key and cert to the supplied alias */
if (sunw_set_fname(alias, key, cert) != 0) {
pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM));
ret = 1;
goto cleanup;
}
/* merge certificate and key into the keystore */
if (keystore->clcerts == NULL) {
/* no existing truststore, so make a new one */
if ((keystore->clcerts = sk_X509_new_null()) == NULL) {
pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM));
ret = 1;
goto cleanup;
}
} else {
/* existing certstore, make sure there's no duplicate */
if (sunw_find_fname(alias, NULL, keystore->clcerts,
NULL, &existingcert) < 0) {
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL),
__FILE__, __LINE__);
ERR_print_errors_fp(stderr);
ret = 1;
goto cleanup;
/* could not search properly! */
}
if (existingcert != NULL) {
/* whoops, found one already */
pkgerr_add(err, PKGERR_DUPLICATE,
gettext(ERR_KEYSTORE_DUPLICATECERT), alias);
ret = 1;
goto cleanup;
}
}
if (keystore->pkeys == NULL) {
/* no existing keystore, so make a new one */
if ((keystore->pkeys = sk_EVP_PKEY_new_null()) == NULL) {
pkgerr_add(err, PKGERR_NOMEM, gettext(ERR_MEM));
ret = 1;
goto cleanup;
}
} else {
/* existing keystore, so make sure there's no duplicate entry */
if (sunw_find_fname(alias, keystore->pkeys, NULL,
&existingkey, NULL) < 0) {
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL),
__FILE__, __LINE__);
ERR_print_errors_fp(stderr);
ret = 1;
goto cleanup;
/* could not search properly! */
}
if (existingkey != NULL) {
/* whoops, found one already */
pkgerr_add(err, PKGERR_DUPLICATE,
gettext(ERR_KEYSTORE_DUPLICATEKEY), alias);
ret = 1;
goto cleanup;
}
}
(void) sk_X509_push(keystore->clcerts, cert);
(void) sk_EVP_PKEY_push(keystore->pkeys, key);
keystore->dirty = B_TRUE;
cleanup:
if (existingcert != NULL)
(void) X509_free(existingcert);
if (existingkey != NULL)
(void) sunw_evp_pkey_free(existingkey);
return (ret);
}
/*
* delete_cert_and_keys - Deletes one or more certificates
* and matching private keys from a keystore.
*
* Arguments:
* err - Error object to add errors to
* ks - The keystore from which certs and keys are deleted
* alias - Name which to search for certificates and keys
* to delete
*
* Returns:
* 0 - Success - All trusted certs which match 'alias'
* are deleted. All user certificates
* which match 'alias' are deleted, along
* with the matching private key.
* non-zero - Failure, errors recorded in err
*/
int
delete_cert_and_keys(PKG_ERR *err, keystore_handle_t ks_h, char *alias)
{
X509 *existingcert;
EVP_PKEY *existingkey;
int i;
char *fname = NULL;
boolean_t found = B_FALSE;
keystore_t *ks = ks_h;
/* delete any and all client certs with the supplied name */
if (ks->clcerts != NULL) {
for (i = 0; i < sk_X509_num(ks->clcerts); i++) {
existingcert = sk_X509_value(ks->clcerts, i);
if (sunw_get_cert_fname(GETDO_COPY,
existingcert, &fname) >= 0) {
if (streq(fname, alias)) {
/* match, so nuke it */
existingcert =
sk_X509_delete(ks->clcerts, i);
X509_free(existingcert);
existingcert = NULL;
found = B_TRUE;
}
(void) OPENSSL_free(fname);
fname = NULL;
}
}
if (sk_X509_num(ks->clcerts) <= 0) {
/* we deleted all the client certs */
sk_X509_free(ks->clcerts);
ks->clcerts = NULL;
}
}
/* and now the private keys */
if (ks->pkeys != NULL) {
for (i = 0; i < sk_EVP_PKEY_num(ks->pkeys); i++) {
existingkey = sk_EVP_PKEY_value(ks->pkeys, i);
if (sunw_get_pkey_fname(GETDO_COPY,
existingkey, &fname) >= 0) {
if (streq(fname, alias)) {
/* match, so nuke it */
existingkey =
sk_EVP_PKEY_delete(ks->pkeys, i);
sunw_evp_pkey_free(existingkey);
existingkey = NULL;
found = B_TRUE;
}
(void) OPENSSL_free(fname);
fname = NULL;
}
}
if (sk_EVP_PKEY_num(ks->pkeys) <= 0) {
/* we deleted all the private keys */
sk_EVP_PKEY_free(ks->pkeys);
ks->pkeys = NULL;
}
}
/* finally, remove any trust anchors that match */
if (ks->cacerts != NULL) {
for (i = 0; i < sk_X509_num(ks->cacerts); i++) {
existingcert = sk_X509_value(ks->cacerts, i);
if (sunw_get_cert_fname(GETDO_COPY,
existingcert, &fname) >= 0) {
if (streq(fname, alias)) {
/* match, so nuke it */
existingcert =
sk_X509_delete(ks->cacerts, i);
X509_free(existingcert);
existingcert = NULL;
found = B_TRUE;
}
(void) OPENSSL_free(fname);
fname = NULL;
}
}
if (sk_X509_num(ks->cacerts) <= 0) {
/* we deleted all the CA certs */
sk_X509_free(ks->cacerts);
ks->cacerts = NULL;
}
}
if (found) {
ks->dirty = B_TRUE;
return (0);
} else {
/* no certs or keys deleted */
pkgerr_add(err, PKGERR_NOALIASMATCH,
gettext(ERR_KEYSTORE_NOCERTKEY),
alias, ks->path);
return (1);
}
}
/*
* check_cert - Checks certificate validity. This routine
* checks that the current time falls within the period
* of validity for the cert.
*
* Arguments:
* err - Error object to add errors to
* cert - The certificate to check
*
* Returns:
* 0 - Success - Certificate checks out
* non-zero - Failure, errors and reasons recorded in err
*/
int
check_cert(PKG_ERR *err, X509 *cert)
{
char currtimestr[ATTR_MAX];
time_t currtime;
char *r;
/* get current time */
if ((currtime = time(NULL)) == (time_t)-1) {
pkgerr_add(err, PKGERR_TIME, gettext(ERR_CURR_TIME));
return (1);
}
(void) strlcpy(currtimestr, ctime(&currtime), ATTR_MAX);
/* trim whitespace from end of time string */
for (r = (currtimestr + strlen(currtimestr) - 1); isspace(*r); r--) {
*r = '\0';
}
/* check validity of cert */
switch (sunw_check_cert_times(CHK_BOTH, cert)) {
case CHKERR_TIME_OK:
/* Current time meets requested checks */
break;
case CHKERR_TIME_BEFORE_BAD:
/* 'not before' field is invalid */
case CHKERR_TIME_AFTER_BAD:
/* 'not after' field is invalid */
pkgerr_add(err, PKGERR_TIME, gettext(ERR_CERT_TIME_BAD));
return (1);
case CHKERR_TIME_IS_BEFORE:
/* Current time is before 'not before' */
case CHKERR_TIME_HAS_EXPIRED:
/*
* Ignore expiration time since the trust cert used to
* verify the certs used to sign Sun patches is already
* expired. Once the patches get resigned with the new
* cert we will check expiration against the time the
* patch was signed and not the time it is installed.
*/
return (0);
default:
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL),
__FILE__, __LINE__);
return (1);
}
/* all checks ok */
return (0);
}
/*
* check_cert - Checks certificate validity. This routine
* checks everything that check_cert checks, and additionally
* verifies that the private key and corresponding public
* key are indeed a pair.
*
* Arguments:
* err - Error object to add errors to
* cert - The certificate to check
* key - the key to check
* Returns:
* 0 - Success - Certificate checks out
* non-zero - Failure, errors and reasons recorded in err
*/
int
check_cert_and_key(PKG_ERR *err, X509 *cert, EVP_PKEY *key)
{
/* check validity dates */
if (check_cert(err, cert) != 0) {
return (1);
}
/* check key pair match */
if (sunw_check_keys(cert, key) == 0) {
pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MISMATCHED_KEYS),
get_subject_display_name(cert));
return (1);
}
/* all checks OK */
return (0);
}
/* ------------------ private functions ---------------------- */
/*
* verify_keystore_integrity - Searches for the remnants
* of a failed or aborted keystore modification, and
* cleans up the files, retstores the keystore to a known
* state.
*
* Arguments:
* err - Error object to add errors to
* keystore_file - Base directory or filename of keystore
* app - Application making request
*
* Returns:
* 0 - Success - Keystore is restored, or untouched in the
* case that cleanup was unnecessary
* non-zero - Failure, errors and reasons recorded in err
*/
static boolean_t
verify_keystore_integrity(PKG_ERR *err, keystore_t *keystore)
{
if (keystore->capath != NULL) {
if (!restore_keystore_file(err, keystore->capath)) {
return (B_FALSE);
}
}
if (keystore->clpath != NULL) {
if (!restore_keystore_file(err, keystore->clpath)) {
return (B_FALSE);
}
}
if (keystore->keypath != NULL) {
if (!restore_keystore_file(err, keystore->keypath)) {
return (B_FALSE);
}
}
return (B_TRUE);
}
/*
* restore_keystore_file - restores a keystore file to
* a known state.
*
* Keystore files can possibly be corrupted by a variety
* of error conditions during reading/writing. This
* routine, along with write_keystore_file, tries to
* maintain keystore integrity by writing the files
* out in a particular order, minimizing the time period
* that the keystore is in an indeterminate state.
*
* With the current implementation, there are some failures
* that are wholly unrecoverable, such as disk corruption.
* These routines attempt to minimize the risk, but not
* eliminate it. When better, atomic operations are available
* (such as a trued atabase with commit, rollback, and
* guaranteed atomicity), this implementation should use that.
*
* Arguments:
* err - Error object to add errors to
* keystore_file - keystore file path to restore.
*
* Returns:
* 0 - Success - Keystore file is restored, or untouched in the
* case that cleanup was unnecessary
* non-zero - Failure, errors and reasons recorded in err
*/
/* ARGSUSED */
static boolean_t
restore_keystore_file(PKG_ERR *err, char *keystore_file)
{
char newpath[MAXPATHLEN];
char backuppath[MAXPATHLEN];
int newfd;
struct stat buf;
int len;
if (((len = snprintf(newpath, MAXPATHLEN, "%s.new",
keystore_file)) < 0) ||
(len >= ATTR_MAX)) {
pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), keystore_file);
return (B_FALSE);
}
if (((len = snprintf(backuppath, MAXPATHLEN, "%s.bak",
keystore_file)) < 0) ||
(len >= ATTR_MAX)) {
pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), keystore_file);
return (B_FALSE);
}
if ((newfd = open(newpath, O_RDWR|O_NONBLOCK, 0)) != -1) {
if (fstat(newfd, &buf) != -1) {
if (S_ISREG(buf.st_mode)) {
/*
* restore the file, waiting on it
* to be free for locking, or for
* it to disappear
*/
if (!wait_restore(newfd, keystore_file,
newpath, backuppath)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_WRITE),
newpath, strerror(errno));
(void) close(newfd);
return (B_FALSE);
} else {
return (B_TRUE);
}
} else {
/* "new" file is not a regular file */
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_NOT_REG), newpath);
(void) close(newfd);
return (B_FALSE);
}
} else {
/* couldn't stat "new" file */
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_WRITE), newpath,
strerror(errno));
(void) close(newfd);
return (B_FALSE);
}
} else {
/* "new" file doesn't exist */
return (B_TRUE);
}
}
static boolean_t
wait_restore(int newfd, char *keystore_file,
char *origpath, char *backuppath)
{
struct stat buf;
FILE *newstream;
PKCS12 *p12;
(void) alarm(LOCK_TIMEOUT);
if (file_lock(newfd, F_WRLCK, 1) == -1) {
/* could not lock file */
(void) alarm(0);
return (B_FALSE);
}
(void) alarm(0);
if (fstat(newfd, &buf) != -1) {
if (S_ISREG(buf.st_mode)) {
/*
* The new file still
* exists, with no
* owner. It must be
* the result of an
* aborted update.
*/
newstream = fdopen(newfd, "r");
if ((p12 =
d2i_PKCS12_fp(newstream,
NULL)) != NULL) {
/*
* The file
* appears
* complete.
* Replace the
* exsisting
* keystore
* file with
* this one
*/
(void) rename(keystore_file, backuppath);
(void) rename(origpath, keystore_file);
PKCS12_free(p12);
} else {
/* The file is not complete. Remove it */
(void) remove(origpath);
}
/* remove backup file */
(void) remove(backuppath);
(void) fclose(newstream);
(void) close(newfd);
return (B_TRUE);
} else {
/*
* new file exists, but is not a
* regular file
*/
(void) close(newfd);
return (B_FALSE);
}
} else {
/*
* could not stat file. Unless
* the reason was that the file
* is now gone, this is an error
*/
if (errno != ENOENT) {
(void) close(newfd);
return (B_FALSE);
}
/*
* otherwise, file is gone. The process
* that held the lock must have
* successfully cleaned up and
* exited with a valid keystore
* state
*/
(void) close(newfd);
return (B_TRUE);
}
}
/*
* resolve_paths - figure out if we are dealing with a single-file
* or multi-file keystore
*
* The flags tell resolve_paths how to behave:
*
* KEYSTORE_PATH_SOFT
* If the keystore file does not exist at <base>/<app> then
* use <base> as the path to the keystore. This can be used,
* for example, to access an app-specific keystore iff it
* exists, otherwise revert back to an app-generic keystore.
*
* KEYSTORE_PATH_HARD
* Always use the keystore located at <keystore_path>/<app>.
* In read/write mode, if the files do not exist, then
* they will be created. This is used to avoid falling
* back to an app-generic keystore path when the app-specific
* one does not exist.
*
* Arguments:
* err - Error object to add errors to
* keystore_file - base keystore file path to lock
* app - Application making requests
* flags - Control flags (see above description)
* keystore - object which is being locked
*
* Returns:
* B_TRUE - Success - Keystore file is locked, paths to
* appropriate files placed in keystore.
* B_FALSE - Failure, errors and reasons recorded in err
*/
static boolean_t
resolve_paths(PKG_ERR *err, char *keystore_file, char *app,
long flags, keystore_t *keystore)
{
char storepath[PATH_MAX];
struct stat buf;
boolean_t multi = B_FALSE;
int fd1, fd2, len;
/*
* figure out whether we are dealing with a single-file keystore
* or a multi-file keystore
*/
if (app != NULL) {
if (((len = snprintf(storepath, PATH_MAX, "%s/%s",
keystore_file, app)) < 0) ||
(len >= ATTR_MAX)) {
pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN),
keystore_file);
return (B_FALSE);
}
if (((fd1 = open(storepath, O_NONBLOCK|O_RDONLY)) == -1) ||
(fstat(fd1, &buf) == -1) ||
!S_ISDIR(buf.st_mode)) {
/*
* app-specific does not exist
* fallback to app-generic, if flags say we can
*/
if ((flags & KEYSTORE_PATH_MASK) ==
KEYSTORE_PATH_SOFT) {
if (((fd2 = open(keystore_file,
O_NONBLOCK|O_RDONLY)) != -1) &&
(fstat(fd2, &buf) != -1)) {
if (S_ISDIR(buf.st_mode)) {
/*
* app-generic dir
* exists, so use it
* as a multi-file
* keystore
*/
multi = B_TRUE;
app = NULL;
} else if (S_ISREG(buf.st_mode)) {
/*
* app-generic file exists, so
* use it as a single file ks
*/
multi = B_FALSE;
app = NULL;
}
}
}
}
if (fd1 != -1)
(void) close(fd1);
if (fd2 != -1)
(void) close(fd2);
} else {
if (((fd1 = open(keystore_file,
O_NONBLOCK|O_RDONLY)) != -1) &&
(fstat(fd1, &buf) != -1) &&
S_ISDIR(buf.st_mode)) {
/*
* app-generic dir exists, so use
* it as a multi-file keystore
*/
multi = B_TRUE;
}
if (fd1 != -1)
(void) close(fd1);
}
if (app != NULL) {
/* app-specific keystore */
(void) snprintf(storepath, PATH_MAX, "%s/%s/%s",
keystore_file, app, TRUSTSTORE);
keystore->capath = xstrdup(storepath);
(void) snprintf(storepath, PATH_MAX, "%s/%s/%s",
keystore_file, app, CERTSTORE);
keystore->clpath = xstrdup(storepath);
(void) snprintf(storepath, PATH_MAX, "%s/%s/%s",
keystore_file, app, KEYSTORE);
keystore->keypath = xstrdup(storepath);
} else {
/* app-generic keystore */
if (!multi) {
/* single-file app-generic keystore */
keystore->capath = xstrdup(keystore_file);
keystore->keypath = NULL;
keystore->clpath = NULL;
} else {
/* multi-file app-generic keystore */
(void) snprintf(storepath, PATH_MAX, "%s/%s",
keystore_file, TRUSTSTORE);
keystore->capath = xstrdup(storepath);
(void) snprintf(storepath, PATH_MAX, "%s/%s",
keystore_file, CERTSTORE);
keystore->clpath = xstrdup(storepath);
(void) snprintf(storepath, PATH_MAX, "%s/%s",
keystore_file, KEYSTORE);
keystore->keypath = xstrdup(storepath);
}
}
return (B_TRUE);
}
/*
* lock_keystore - Locks a keystore for shared (read-only)
* or exclusive (read-write) access.
*
* The flags tell lock_keystore how to behave:
*
* KEYSTORE_ACCESS_READONLY
* opens keystore read-only. Attempts to modify results in an error
*
* KEYSTORE_ACCESS_READWRITE
* opens keystore read-write
*
* KEYSTORE_PATH_SOFT
* If the keystore file does not exist at <base>/<app> then
* use <base> as the path to the keystore. This can be used,
* for example, to access an app-specific keystore iff it
* exists, otherwise revert back to an app-generic keystore.
*
* KEYSTORE_PATH_HARD
* Always use the keystore located at <keystore_path>/<app>.
* In read/write mode, if the files do not exist, then
* they will be created. This is used to avoid falling
* back to an app-generic keystore path when the app-specific
* one does not exist.
*
* Arguments:
* err - Error object to add errors to
* flags - Control flags (see above description)
* keystore - object which is being locked
*
* Returns:
* 0 - Success - Keystore file is locked, paths to
* appropriate files placed in keystore.
* non-zero - Failure, errors and reasons recorded in err
*/
static boolean_t
lock_keystore(PKG_ERR *err, long flags, keystore_t *keystore)
{
boolean_t ret = B_TRUE;
struct stat buf;
switch (flags & KEYSTORE_ACCESS_MASK) {
case KEYSTORE_ACCESS_READONLY:
if ((keystore->cafd =
open(keystore->capath, O_NONBLOCK|O_RDONLY)) == -1) {
if (errno == ENOENT) {
/*
* no keystore. try to create an
* empty one so we can lock on it and
* prevent others from gaining
* exclusive access. It will be
* deleted when the keystore is closed.
*/
if ((keystore->cafd =
open(keystore->capath,
O_NONBLOCK|O_RDWR|O_CREAT|O_EXCL,
S_IRUSR|S_IWUSR)) == -1) {
pkgerr_add(err, PKGERR_READ,
gettext(ERR_NO_KEYSTORE),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
pkgerr_add(err, PKGERR_READ,
gettext(ERR_KEYSTORE_OPEN),
keystore->capath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
}
if (fstat(keystore->cafd, &buf) != -1) {
if (S_ISREG(buf.st_mode)) {
if (file_lock(keystore->cafd, F_RDLCK,
0) == -1) {
pkgerr_add(err, PKGERR_LOCKED,
gettext(ERR_KEYSTORE_LOCKED_READ),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
/* ca file not a regular file! */
pkgerr_add(err, PKGERR_READ,
gettext(ERR_NOT_REG),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
pkgerr_add(err, PKGERR_READ,
gettext(ERR_KEYSTORE_OPEN),
keystore->capath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
break;
case KEYSTORE_ACCESS_READWRITE:
if ((keystore->cafd = open(keystore->capath,
O_RDWR|O_NONBLOCK)) == -1) {
/* does not exist. try to create an empty one */
if (errno == ENOENT) {
if ((keystore->cafd =
open(keystore->capath,
O_NONBLOCK|O_RDWR|O_CREAT|O_EXCL,
S_IRUSR|S_IWUSR)) == -1) {
pkgerr_add(err, PKGERR_READ,
gettext(ERR_KEYSTORE_WRITE),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
pkgerr_add(err, PKGERR_READ,
gettext(ERR_KEYSTORE_OPEN),
keystore->capath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
}
if (fstat(keystore->cafd, &buf) != -1) {
if (S_ISREG(buf.st_mode)) {
if (file_lock(keystore->cafd, F_WRLCK,
0) == -1) {
pkgerr_add(err, PKGERR_LOCKED,
gettext(ERR_KEYSTORE_LOCKED),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
/* ca file not a regular file! */
pkgerr_add(err, PKGERR_READ,
gettext(ERR_NOT_REG),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
pkgerr_add(err, PKGERR_READ,
gettext(ERR_KEYSTORE_OPEN),
keystore->capath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
break;
default:
pkgerr_add(err, PKGERR_INTERNAL,
gettext(ERR_KEYSTORE_INTERNAL),
__FILE__, __LINE__);
ret = B_FALSE;
goto cleanup;
}
cleanup:
if (!ret) {
if (keystore->cafd > 0) {
(void) file_unlock(keystore->cafd);
(void) close(keystore->cafd);
keystore->cafd = -1;
}
if (keystore->capath != NULL)
free(keystore->capath);
if (keystore->clpath != NULL)
free(keystore->clpath);
if (keystore->keypath != NULL)
free(keystore->keypath);
keystore->capath = NULL;
keystore->clpath = NULL;
keystore->keypath = NULL;
}
return (ret);
}
/*
* unlock_keystore - Unocks a keystore
*
* Arguments:
* err - Error object to add errors to
* keystore - keystore object to unlock
* Returns:
* 0 - Success - Keystore files are unlocked, files are closed,
* non-zero - Failure, errors and reasons recorded in err
*/
/* ARGSUSED */
static boolean_t
unlock_keystore(PKG_ERR *err, keystore_t *keystore)
{
/*
* Release lock on the CA file.
* Delete file if it is empty
*/
if (file_empty(keystore->capath)) {
(void) remove(keystore->capath);
}
(void) file_unlock(keystore->cafd);
(void) close(keystore->cafd);
return (B_TRUE);
}
/*
* read_keystore - Reads keystore files of disk, parses
* into internal structures.
*
* Arguments:
* err - Error object to add errors to
* keystore - keystore object to read into
* cb - callback to get password, if required
* Returns:
* 0 - Success - Keystore files are read, and placed
* into keystore structure.
* non-zero - Failure, errors and reasons recorded in err
*/
static boolean_t
read_keystore(PKG_ERR *err, keystore_t *keystore, keystore_passphrase_cb cb)
{
boolean_t ret = B_TRUE;
PKCS12 *p12 = NULL;
boolean_t ca_empty;
boolean_t have_passwd = B_FALSE;
boolean_t cl_empty = B_TRUE;
boolean_t key_empty = B_TRUE;
ca_empty = file_empty(keystore->capath);
if (keystore->clpath != NULL)
cl_empty = file_empty(keystore->clpath);
if (keystore->keypath != NULL)
key_empty = file_empty(keystore->keypath);
if (ca_empty && cl_empty && key_empty) {
keystore->new = B_TRUE;
}
if (!ca_empty) {
/* first read the ca file */
if ((p12 = read_keystore_file(err,
keystore->capath)) == NULL) {
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT), keystore->capath);
ret = B_FALSE;
goto cleanup;
}
/* Get password, using callback if necessary */
if (!have_passwd) {
if (!get_keystore_passwd(err, p12, cb, keystore)) {
ret = B_FALSE;
goto cleanup;
}
have_passwd = B_TRUE;
}
/* decrypt and parse keystore file */
if (sunw_PKCS12_contents(p12, keystore->passphrase,
&keystore->pkeys, &keystore->cacerts) < 0) {
/* could not parse the contents */
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT), keystore->capath);
ret = B_FALSE;
goto cleanup;
}
PKCS12_free(p12);
p12 = NULL;
} else {
/*
* truststore is empty, so we don't have any trusted
* certs
*/
keystore->cacerts = NULL;
}
/*
* if there is no cl file or key file, use the cl's and key's found
* in the ca file
*/
if (keystore->clpath == NULL && !ca_empty) {
if (sunw_split_certs(keystore->pkeys, keystore->cacerts,
&keystore->clcerts, NULL) < 0) {
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT), keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
/*
* files are in separate files. read keys out of the keystore
* certs out of the certstore, if they are not empty
*/
if (!cl_empty) {
if ((p12 = read_keystore_file(err,
keystore->clpath)) == NULL) {
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT),
keystore->clpath);
ret = B_FALSE;
goto cleanup;
}
/* Get password, using callback if necessary */
if (!have_passwd) {
if (!get_keystore_passwd(err, p12, cb,
keystore)) {
ret = B_FALSE;
goto cleanup;
}
have_passwd = B_TRUE;
}
if (check_password(p12,
keystore->passphrase) == B_FALSE) {
/*
* password in client cert file
* is different than
* the one in the other files!
*/
pkgerr_add(err, PKGERR_BADPASS,
gettext(ERR_MISMATCHPASS),
keystore->clpath,
keystore->capath, keystore->path);
ret = B_FALSE;
goto cleanup;
}
if (sunw_PKCS12_contents(p12, keystore->passphrase,
NULL, &keystore->clcerts) < 0) {
/* could not parse the contents */
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT),
keystore->clpath);
ret = B_FALSE;
goto cleanup;
}
PKCS12_free(p12);
p12 = NULL;
} else {
keystore->clcerts = NULL;
}
if (!key_empty) {
if ((p12 = read_keystore_file(err,
keystore->keypath)) == NULL) {
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT),
keystore->keypath);
ret = B_FALSE;
goto cleanup;
}
/* Get password, using callback if necessary */
if (!have_passwd) {
if (!get_keystore_passwd(err, p12, cb,
keystore)) {
ret = B_FALSE;
goto cleanup;
}
have_passwd = B_TRUE;
}
if (check_password(p12,
keystore->passphrase) == B_FALSE) {
pkgerr_add(err, PKGERR_BADPASS,
gettext(ERR_MISMATCHPASS),
keystore->keypath,
keystore->capath, keystore->path);
ret = B_FALSE;
goto cleanup;
}
if (sunw_PKCS12_contents(p12, keystore->passphrase,
&keystore->pkeys, NULL) < 0) {
/* could not parse the contents */
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT),
keystore->keypath);
ret = B_FALSE;
goto cleanup;
}
PKCS12_free(p12);
p12 = NULL;
} else {
keystore->pkeys = NULL;
}
}
cleanup:
if (p12 != NULL)
PKCS12_free(p12);
return (ret);
}
/*
* get_keystore_password - retrieves pasword used to
* decrypt PKCS12 structure.
*
* Arguments:
* err - Error object to add errors to
* p12 - PKCS12 structure which returned password should
* decrypt
* cb - callback to collect password.
* keystore - The keystore in which the PKCS12 structure
* will eventually populate.
* Returns:
* B_TRUE - success.
* keystore password is set in keystore->passphrase.
* B_FALSE - failure, errors logged
*/
static boolean_t
get_keystore_passwd(PKG_ERR *err, PKCS12 *p12, keystore_passphrase_cb cb,
keystore_t *keystore)
{
char *passwd;
char passbuf[KEYSTORE_PASS_MAX + 1];
keystore_passphrase_data data;
/* see if no password is the right password */
if (check_password(p12, "") == B_TRUE) {
passwd = "";
} else if (check_password(p12, NULL) == B_TRUE) {
passwd = NULL;
} else {
/* oops, it's encrypted. get password */
data.err = err;
if (cb(passbuf, KEYSTORE_PASS_MAX, 0,
&data) == -1) {
/* could not get password */
return (B_FALSE);
}
if (check_password(p12, passbuf) == B_FALSE) {
/* wrong password */
pkgerr_add(err, PKGERR_BADPASS,
gettext(ERR_BADPASS));
return (B_FALSE);
}
/*
* make copy of password buffer, since it
* goes away upon return
*/
passwd = xstrdup(passbuf);
}
keystore->passphrase = passwd;
return (B_TRUE);
}
/*
* write_keystore - Writes keystore files to disk
*
* Arguments:
* err - Error object to add errors to
* keystore - keystore object to write from
* passwd - password used to encrypt keystore
* Returns:
* 0 - Success - Keystore contents are written out to
* the same locations as read from
* non-zero - Failure, errors and reasons recorded in err
*/
static boolean_t
write_keystore(PKG_ERR *err, keystore_t *keystore,
keystore_passphrase_cb cb)
{
PKCS12 *p12 = NULL;
boolean_t ret = B_TRUE;
keystore_passphrase_data data;
char passbuf[KEYSTORE_PASS_MAX + 1];
if (keystore->capath != NULL && keystore->clpath == NULL &&
keystore->keypath == NULL) {
/*
* keystore is a file.
* just write out a single file
*/
if ((keystore->pkeys == NULL) &&
(keystore->clcerts == NULL) &&
(keystore->cacerts == NULL)) {
if (!clear_keystore_file(err, keystore->capath)) {
/*
* no keys or certs to write out, so
* blank the ca file. we do not
* delete it since it is used as a
* lock by lock_keystore() in
* subsequent invocations
*/
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
} else {
/*
* if the keystore is being created for the first time,
* prompt for a passphrase for encryption
*/
if (keystore->new) {
data.err = err;
if (cb(passbuf, KEYSTORE_PASS_MAX,
1, &data) == -1) {
ret = B_FALSE;
goto cleanup;
}
} else {
/*
* use the one used when the keystore
* was read
*/
(void) strlcpy(passbuf, keystore->passphrase,
KEYSTORE_PASS_MAX);
}
p12 = sunw_PKCS12_create(passbuf, keystore->pkeys,
keystore->clcerts, keystore->cacerts);
if (p12 == NULL) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_FORM),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
if (!write_keystore_file(err, keystore->capath, p12)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
}
} else {
/* files are seprate. Do one at a time */
/*
* if the keystore is being created for the first time,
* prompt for a passphrase for encryption
*/
if (keystore->new && ((keystore->pkeys != NULL) ||
(keystore->clcerts != NULL) ||
(keystore->cacerts != NULL))) {
data.err = err;
if (cb(passbuf, KEYSTORE_PASS_MAX,
1, &data) == -1) {
ret = B_FALSE;
goto cleanup;
}
} else {
/* use the one used when the keystore was read */
(void) strlcpy(passbuf, keystore->passphrase,
KEYSTORE_PASS_MAX);
}
/* do private keys first */
if (keystore->pkeys != NULL) {
p12 = sunw_PKCS12_create(passbuf, keystore->pkeys,
NULL, NULL);
if (p12 == NULL) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_FORM),
keystore->keypath);
ret = B_FALSE;
goto cleanup;
}
if (!write_keystore_file(err, keystore->keypath,
p12)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->keypath);
ret = B_FALSE;
goto cleanup;
}
PKCS12_free(p12);
} else {
if ((remove(keystore->keypath) != 0) &&
(errno != ENOENT)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_REMOVE),
keystore->keypath);
ret = B_FALSE;
goto cleanup;
}
}
/* do user certs next */
if (keystore->clcerts != NULL) {
p12 = sunw_PKCS12_create(passbuf, NULL,
keystore->clcerts, NULL);
if (p12 == NULL) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_FORM),
keystore->clpath);
ret = B_FALSE;
goto cleanup;
}
if (!write_keystore_file(err, keystore->clpath, p12)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->clpath);
ret = B_FALSE;
goto cleanup;
}
PKCS12_free(p12);
} else {
if ((remove(keystore->clpath) != 0) &&
(errno != ENOENT)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_REMOVE),
keystore->clpath);
ret = B_FALSE;
goto cleanup;
}
}
/* finally do CA cert file */
if (keystore->cacerts != NULL) {
p12 = sunw_PKCS12_create(passbuf, NULL,
NULL, keystore->cacerts);
if (p12 == NULL) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_FORM),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
if (!write_keystore_file(err, keystore->capath, p12)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
PKCS12_free(p12);
p12 = NULL;
} else {
/*
* nothing to write out, so truncate the file
* (it will be deleted during close_keystore)
*/
if (!clear_keystore_file(err, keystore->capath)) {
pkgerr_add(err, PKGERR_WRITE,
gettext(ERR_KEYSTORE_WRITE),
keystore->capath);
ret = B_FALSE;
goto cleanup;
}
}
}
cleanup:
if (p12 != NULL)
PKCS12_free(p12);
return (ret);
}
/*
* clear_keystore_file - Clears (zeros out) a keystore file.
*
* Arguments:
* err - Error object to add errors to
* dest - Path of keystore file to zero out.
* Returns:
* 0 - Success - Keystore file is truncated to zero length
* non-zero - Failure, errors and reasons recorded in err
*/
static boolean_t
clear_keystore_file(PKG_ERR *err, char *dest)
{
int fd;
struct stat buf;
fd = open(dest, O_RDWR|O_NONBLOCK);
if (fd == -1) {
/* can't open for writing */
pkgerr_add(err, PKGERR_WRITE, gettext(MSG_OPEN),
errno);
return (B_FALSE);
}
if ((fstat(fd, &buf) == -1) || !S_ISREG(buf.st_mode)) {
/* not a regular file */
(void) close(fd);
pkgerr_add(err, PKGERR_WRITE, gettext(ERR_NOT_REG),
dest);
return (B_FALSE);
}
if (ftruncate(fd, 0) == -1) {
(void) close(fd);
pkgerr_add(err, PKGERR_WRITE, gettext(ERR_WRITE),
dest, strerror(errno));
return (B_FALSE);
}
(void) close(fd);
return (B_TRUE);
}
/*
* write_keystore_file - Writes keystore file to disk.
*
* Keystore files can possibly be corrupted by a variety
* of error conditions during reading/writing. This
* routine, along with restore_keystore_file, tries to
* maintain keystore integity by writing the files
* out in a particular order, minimizing the time period
* that the keystore is in an indeterminate state.
*
* With the current implementation, there are some failures
* that are wholly unrecoverable, such as disk corruption.
* These routines attempt to minimize the risk, but not
* eliminate it. When better, atomic operations are available
* (such as a true database with commit, rollback, and
* guaranteed atomicity), this implementation should use that.
*
*
* Arguments:
* err - Error object to add errors to
* dest - Destination filename
* contents - Contents to write to the file
* Returns:
* 0 - Success - Keystore contents are written out to
* the destination.
* non-zero - Failure, errors and reasons recorded in err
*/
static boolean_t
write_keystore_file(PKG_ERR *err, char *dest, PKCS12 *contents)
{
FILE *newfile = NULL;
boolean_t ret = B_TRUE;
char newpath[MAXPATHLEN];
char backuppath[MAXPATHLEN];
struct stat buf;
int fd;
(void) snprintf(newpath, MAXPATHLEN, "%s.new", dest);
(void) snprintf(backuppath, MAXPATHLEN, "%s.bak", dest);
if ((fd = open(newpath, O_CREAT|O_EXCL|O_WRONLY|O_NONBLOCK,
S_IRUSR|S_IWUSR)) == -1) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN),
newpath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
if (fstat(fd, &buf) == -1) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN),
newpath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
if (!S_ISREG(buf.st_mode)) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_NOT_REG),
newpath);
ret = B_FALSE;
goto cleanup;
}
if ((newfile = fdopen(fd, "w")) == NULL) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN),
newpath, strerror(errno));
ret = B_FALSE;
goto cleanup;
}
if (i2d_PKCS12_fp(newfile, contents) == 0) {
pkgerr_add(err, PKGERR_WRITE, gettext(ERR_KEYSTORE_WRITE),
newpath);
ret = B_FALSE;
goto cleanup;
}
/* flush, then close */
(void) fflush(newfile);
(void) fclose(newfile);
newfile = NULL;
/* now back up the original file */
(void) rename(dest, backuppath);
/* put new one in its place */
(void) rename(newpath, dest);
/* remove backup */
(void) remove(backuppath);
cleanup:
if (newfile != NULL)
(void) fclose(newfile);
if (fd != -1)
(void) close(fd);
return (ret);
}
/*
* read_keystore_file - Reads single keystore file
* off disk in PKCS12 format.
*
* Arguments:
* err - Error object to add errors to
* file - File path to read
* Returns:
* PKCS12 contents of file, or NULL if an error occurred.
* errors recorded in 'err'.
*/
static PKCS12
*read_keystore_file(PKG_ERR *err, char *file)
{
int fd;
struct stat buf;
FILE *newfile;
PKCS12 *p12 = NULL;
if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN),
file, strerror(errno));
goto cleanup;
}
if (fstat(fd, &buf) == -1) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN),
file, strerror(errno));
goto cleanup;
}
if (!S_ISREG(buf.st_mode)) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_NOT_REG),
file);
goto cleanup;
}
if ((newfile = fdopen(fd, "r")) == NULL) {
pkgerr_add(err, PKGERR_READ, gettext(ERR_KEYSTORE_OPEN),
file, strerror(errno));
goto cleanup;
}
if ((p12 = d2i_PKCS12_fp(newfile, NULL)) == NULL) {
pkgerr_add(err, PKGERR_CORRUPT,
gettext(ERR_KEYSTORE_CORRUPT), file);
goto cleanup;
}
cleanup:
if (newfile != NULL)
(void) fclose(newfile);
if (fd != -1)
(void) close(fd);
return (p12);
}
/*
* Locks the specified file.
*/
static int
file_lock(int fd, int type, int wait)
{
struct flock lock;
lock.l_type = type;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (!wait) {
if (file_lock_test(fd, type)) {
/*
* The caller would have to wait to get the
* lock on this file.
*/
return (-1);
}
}
return (fcntl(fd, F_SETLKW, &lock));
}
/*
* Returns FALSE if the file is not locked; TRUE
* otherwise.
*/
static boolean_t
file_lock_test(int fd, int type)
{
struct flock lock;
lock.l_type = type;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(fd, F_GETLK, &lock) != -1) {
if (lock.l_type != F_UNLCK) {
/*
* The caller would have to wait to get the
* lock on this file.
*/
return (B_TRUE);
}
}
/*
* The file is not locked.
*/
return (B_FALSE);
}
/*
* Unlocks the specified file.
*/
static int
file_unlock(int fd)
{
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
return (fcntl(fd, F_SETLK, &lock));
}
/*
* Determines if file has a length of 0 or not
*/
static boolean_t
file_empty(char *path)
{
struct stat buf;
/* file is empty if size = 0 or it doesn't exist */
if (lstat(path, &buf) == 0) {
if (buf.st_size == 0) {
return (B_TRUE);
}
} else {
if (errno == ENOENT) {
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* Name: get_time_string
* Description: Generates a human-readable string from an ASN1_TIME
*
* Arguments: intime - The time to convert
*
* Returns : A pointer to a static string representing the passed-in time.
*/
static char
*get_time_string(ASN1_TIME *intime)
{
static char time[ATTR_MAX];
BIO *mem;
char *p;
if (intime == NULL) {
return (NULL);
}
if ((mem = BIO_new(BIO_s_mem())) == NULL) {
return (NULL);
}
if (ASN1_TIME_print(mem, intime) == 0) {
(void) BIO_free(mem);
return (NULL);
}
if (BIO_gets(mem, time, ATTR_MAX) <= 0) {
(void) BIO_free(mem);
return (NULL);
}
(void) BIO_free(mem);
/* trim the end of the string */
for (p = time + strlen(time) - 1; isspace(*p); p--) {
*p = '\0';
}
return (time);
}
/*
* check_password - do various password checks to see if the current password
* will work or we need to prompt for a new one.
*
* Arguments:
* pass - password to check
*
* Returns:
* B_TRUE - Password is OK.
* B_FALSE - Password not valid.
*/
static boolean_t
check_password(PKCS12 *p12, char *pass)
{
boolean_t ret = B_TRUE;
/*
* If password is zero length or NULL then try verifying both cases
* to determine which password is correct. The reason for this is that
* under PKCS#12 password based encryption no password and a zero
* length password are two different things...
*/
/* Check the mac */
if (pass == NULL || *pass == '\0') {
if (PKCS12_verify_mac(p12, NULL, 0) == 0 &&
PKCS12_verify_mac(p12, "", 0) == 0)
ret = B_FALSE;
} else if (PKCS12_verify_mac(p12, pass, -1) == 0) {
ret = B_FALSE;
}
return (ret);
}