diff options
Diffstat (limited to 'utils/open-isns/pki.c')
-rw-r--r-- | utils/open-isns/pki.c | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/utils/open-isns/pki.c b/utils/open-isns/pki.c new file mode 100644 index 0000000..f3af922 --- /dev/null +++ b/utils/open-isns/pki.c @@ -0,0 +1,536 @@ +/* + * PKI related functions + * + * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com> + */ + +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <fcntl.h> +#include "isns.h" +#include "security.h" +#include "util.h" +#include "config.h" + +#ifdef WITH_SECURITY + +/* versions prior to 9.6.8 didn't seem to have these */ +#if OPADDRCONFIGENSSL_VERSION_NUMBER < 0x00906080L +# define EVP_MD_CTX_init(c) do { } while (0) +# define EVP_MD_CTX_cleanup(c) do { } while (0) +#endif +#if OPADDRCONFIGENSSL_VERSION_NUMBER < 0x00906070L +# define i2d_DSA_PUBKEY i2d_DSA_PUBKEY_backwards + +static int i2d_DSA_PUBKEY_backwards(DSA *, unsigned char **); +#endif + +static int isns_openssl_init = 0; + +static int isns_dsasig_verify(isns_security_t *ctx, + isns_principal_t *peer, + buf_t *pdu, + const struct isns_authblk *); +static int isns_dsasig_sign(isns_security_t *ctx, + isns_principal_t *peer, + buf_t *pdu, + struct isns_authblk *); +static EVP_PKEY *isns_dsasig_load_private_pem(isns_security_t *ctx, + const char *filename); +static EVP_PKEY *isns_dsasig_load_public_pem(isns_security_t *ctx, + const char *filename); +static DSA * isns_dsa_load_params(const char *); + + +/* + * Create a DSA security context + */ +isns_security_t * +isns_create_dsa_context(void) +{ + isns_security_t *ctx; + + if (!isns_openssl_init) { + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + isns_openssl_init = 1; + } + + ctx = isns_calloc(1, sizeof(*ctx)); + + ctx->is_name = "DSA"; + ctx->is_type = ISNS_AUTH_TYPE_SHA1_DSA; + ctx->is_replay_window = isns_config.ic_auth.replay_window; + ctx->is_timestamp_jitter = isns_config.ic_auth.timestamp_jitter; + + ctx->is_verify = isns_dsasig_verify; + ctx->is_sign = isns_dsasig_sign; + ctx->is_load_private = isns_dsasig_load_private_pem; + ctx->is_load_public = isns_dsasig_load_public_pem; + + isns_debug_auth("Created DSA authentication context\n"); + return ctx; +} + +/* + * DSA signature generation and verification + */ +static void +isns_message_digest(EVP_MD_CTX *md, const buf_t *pdu, + const struct isns_authblk *blk) +{ + uint64_t stamp; + + EVP_DigestUpdate(md, buf_head(pdu), buf_avail(pdu)); + + /* The RFC doesn't say which pieces of the + * message should be hashed. + * We make an educated guess. + */ + stamp = htonll(blk->iab_timestamp); + EVP_DigestUpdate(md, &stamp, sizeof(stamp)); +} + +static void +isns_dsasig_report_errors(const char *msg, isns_print_fn_t *fn) +{ + unsigned long code; + + fn("%s - OpenSSL errors follow:\n", msg); + while ((code = ERR_get_error()) != 0) + fn("> %s: %s\n", + ERR_func_error_string(code), + ERR_reason_error_string(code)); +} + +int +isns_dsasig_sign(isns_security_t *ctx, + isns_principal_t *peer, + buf_t *pdu, + struct isns_authblk *blk) +{ + static unsigned char signature[1024]; + unsigned int sig_len = sizeof(signature); + EVP_MD_CTX md_ctx; + EVP_PKEY *pkey; + int err; + + if ((pkey = peer->is_key) == NULL) + return 0; + + if (pkey->type != EVP_PKEY_DSA) { + isns_debug_message( + "Incompatible public key (spi=%s)\n", + peer->is_name); + return 0; + } + if (EVP_PKEY_size(pkey) > sizeof(signature)) { + isns_error("isns_dsasig_sign: signature buffer too small\n"); + return 0; + } + if (pkey->pkey.dsa->priv_key == NULL) { + isns_error("isns_dsasig_sign: oops, seems to be a public key\n"); + return 0; + } + + isns_debug_auth("Signing messages with spi=%s, DSA/%u\n", + peer->is_name, EVP_PKEY_bits(pkey)); + + EVP_MD_CTX_init(&md_ctx); + EVP_SignInit(&md_ctx, EVP_dss1()); + isns_message_digest(&md_ctx, pdu, blk); + err = EVP_SignFinal(&md_ctx, + signature, &sig_len, + pkey); + EVP_MD_CTX_cleanup(&md_ctx); + + if (err == 0) { + isns_dsasig_report_errors("EVP_SignFinal failed", isns_error); + return 0; + } + + blk->iab_sig = signature; + blk->iab_sig_len = sig_len; + return 1; +} + +int +isns_dsasig_verify(isns_security_t *ctx, + isns_principal_t *peer, + buf_t *pdu, + const struct isns_authblk *blk) +{ + EVP_MD_CTX md_ctx; + EVP_PKEY *pkey; + int err; + + if ((pkey = peer->is_key) == NULL) + return 0; + + if (pkey->type != EVP_PKEY_DSA) { + isns_debug_message( + "Incompatible public key (spi=%s)\n", + peer->is_name); + return 0; + } + + EVP_MD_CTX_init(&md_ctx); + EVP_VerifyInit(&md_ctx, EVP_dss1()); + isns_message_digest(&md_ctx, pdu, blk); + err = EVP_VerifyFinal(&md_ctx, + blk->iab_sig, blk->iab_sig_len, + pkey); + EVP_MD_CTX_cleanup(&md_ctx); + + if (err == 0) { + isns_debug_auth("*** Incorrect signature ***\n"); + return 0; + } + if (err < 0) { + isns_dsasig_report_errors("EVP_VerifyFinal failed", isns_error); + return 0; + } + + isns_debug_message("Good signature from %s\n", + peer->is_name?: "<server>"); + return 1; +} + +EVP_PKEY * +isns_dsasig_load_private_pem(isns_security_t *ctx, const char *filename) +{ + EVP_PKEY *pkey; + FILE *fp; + + if (!(fp = fopen(filename, "r"))) { + isns_error("Unable to open DSA keyfile %s: %m\n", + filename); + return 0; + } + + pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + fclose(fp); + return pkey; +} + +EVP_PKEY * +isns_dsasig_load_public_pem(isns_security_t *ctx, const char *filename) +{ + EVP_PKEY *pkey; + FILE *fp; + + if (!(fp = fopen(filename, "r"))) { + isns_error("Unable to open DSA keyfile %s: %m\n", + filename); + return 0; + } + + pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (pkey == NULL) { + isns_dsasig_report_errors("Error loading DSA public key", + isns_error); + } + + fclose(fp); + return pkey; +} + +EVP_PKEY * +isns_dsa_decode_public(const void *ptr, size_t len) +{ + const unsigned char *der = ptr; + EVP_PKEY *evp; + DSA *dsa; + + /* Assigning ptr to a temporary variable avoids a silly + * compiled warning about type-punning. */ + dsa = d2i_DSA_PUBKEY(NULL, &der, len); + if (dsa == NULL) + return NULL; + + evp = EVP_PKEY_new(); + EVP_PKEY_assign_DSA(evp, dsa); + return evp; +} + +int +isns_dsa_encode_public(EVP_PKEY *pkey, void **ptr, size_t *len) +{ + int bytes; + + *ptr = NULL; + bytes = i2d_DSA_PUBKEY(pkey->pkey.dsa, (unsigned char **) ptr); + if (bytes < 0) + return 0; + + *len = bytes; + return 1; +} + +EVP_PKEY * +isns_dsa_load_public(const char *name) +{ + return isns_dsasig_load_public_pem(NULL, name); +} + +int +isns_dsa_store_private(const char *name, EVP_PKEY *key) +{ + FILE *fp; + int rv, fd; + + if ((fd = open(name, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) { + isns_error("Cannot save DSA key to %s: %m\n", name); + return 0; + } + + if (!(fp = fdopen(fd, "w"))) { + isns_error("fdopen(%s): %m\n", name); + close(fd); + return 0; + } + + rv = PEM_write_PrivateKey(fp, key, NULL, NULL, 0, 0, NULL); + fclose(fp); + + if (rv == 0) + isns_dsasig_report_errors("Failed to store private key", + isns_error); + + return rv; +} + +int +isns_dsa_store_public(const char *name, EVP_PKEY *key) +{ + FILE *fp; + int rv; + + if (!(fp = fopen(name, "w"))) { + isns_error("Unable to open %s: %m\n", name); + return 0; + } + + rv = PEM_write_PUBKEY(fp, key); + fclose(fp); + + if (rv == 0) + isns_dsasig_report_errors("Failed to store public key", + isns_error); + + return rv; +} + + +/* + * DSA key generation + */ +EVP_PKEY * +isns_dsa_generate_key(void) +{ + EVP_PKEY *pkey; + DSA *dsa = NULL; + + if (!(dsa = isns_dsa_load_params(isns_config.ic_dsa.param_file))) + goto failed; + + if (!DSA_generate_key(dsa)) { + isns_dsasig_report_errors("Failed to generate DSA key", + isns_error); + goto failed; + } + + pkey = EVP_PKEY_new(); + EVP_PKEY_assign_DSA(pkey, dsa); + return pkey; + +failed: + if (dsa) + DSA_free(dsa); + return NULL; +} + +DSA * +isns_dsa_load_params(const char *filename) +{ + FILE *fp; + DSA *dsa; + + if (!filename) { + isns_error("Cannot generate key - no DSA parameter file\n"); + return NULL; + } + if (!(fp = fopen(filename, "r"))) { + isns_error("Unable to open %s: %m\n", filename); + return NULL; + } + + dsa = PEM_read_DSAparams(fp, NULL, NULL, NULL); + fclose(fp); + + if (dsa == NULL) { + isns_dsasig_report_errors("Error loading DSA parameters", + isns_error); + } + + return dsa; +} + +static void +isns_dsa_param_gen_callback(int stage, int index, void *dummy) +{ + if (stage == 0) + write(1, "+", 1); + else if (stage == 1) + write(1, ".", 1); + else if (stage == 2) + write(1, "/", 1); +} + +int +isns_dsa_init_params(const char *filename) +{ + FILE *fp; + DSA *dsa; + + if (access(filename, R_OK) == 0) + return 1; + + isns_mkdir_recursive(isns_dirname(filename)); + if (!(fp = fopen(filename, "w"))) { + isns_error("Unable to open %s: %m\n", filename); + return 0; + } + + isns_notice("Generating DSA parameters; this may take a while\n"); + dsa = DSA_generate_parameters(1024, NULL, 0, + NULL, NULL, isns_dsa_param_gen_callback, NULL); + write(1, "\n", 1); + + if (dsa == NULL) { + isns_dsasig_report_errors("Error generating DSA parameters", + isns_error); + fclose(fp); + return 0; + } + + if (!PEM_write_DSAparams(fp, dsa)) { + isns_dsasig_report_errors("Error writing DSA parameters", + isns_error); + DSA_free(dsa); + fclose(fp); + return 0; + } + DSA_free(dsa); + fclose(fp); + return 1; +} + +/* + * Make sure the authentication key is present. + */ +int +isns_dsa_init_key(const char *filename) +{ + char pubkey_path[1024]; + EVP_PKEY *pkey; + + isns_mkdir_recursive(isns_dirname(filename)); + snprintf(pubkey_path, sizeof(pubkey_path), + "%s.pub", filename); + if (access(filename, R_OK) == 0 + && access(pubkey_path, R_OK) == 0) + return 1; + + if (!(pkey = isns_dsa_generate_key())) { + isns_error("Failed to generate AuthKey\n"); + return 0; + } + + if (!isns_dsa_store_private(filename, pkey)) { + isns_error("Unable to write private key to %s\n", filename); + return 0; + } + isns_notice("Stored private key in %s\n", filename); + + if (!isns_dsa_store_public(pubkey_path, pkey)) { + isns_error("Unable to write public key to %s\n", pubkey_path); + return 0; + } + isns_notice("Stored private key in %s\n", pubkey_path); + + return 1; +} + +/* + * Simple keystore - this is a flat directory, with + * public key files using the SPI as their name. + */ +typedef struct isns_simple_keystore isns_simple_keystore_t; +struct isns_simple_keystore { + isns_keystore_t sc_base; + char * sc_dirpath; +}; + +/* + * Load a DSA key from the cert store + * In fact, this will load RSA keys as well. + */ +static EVP_PKEY * +__isns_simple_keystore_find(isns_keystore_t *store_base, + const char *name, size_t namelen) +{ + isns_simple_keystore_t *store = (isns_simple_keystore_t *) store_base; + char pathname[PATH_MAX]; + + /* Refuse to open key files with names + * that refer to parent directories */ + if (memchr(name, '/', namelen) || name[0] == '.') + return NULL; + + snprintf(pathname, sizeof(pathname), + "%s/%.*s", store->sc_dirpath, + (int) namelen, name); + if (access(pathname, R_OK) < 0) + return NULL; + return isns_dsasig_load_public_pem(NULL, pathname); +} + +isns_keystore_t * +isns_create_simple_keystore(const char *dirname) +{ + isns_simple_keystore_t *store; + + store = isns_calloc(1, sizeof(*store)); + store->sc_base.ic_name = "simple key store"; + store->sc_base.ic_find = __isns_simple_keystore_find; + store->sc_dirpath = isns_strdup(dirname); + + return (isns_keystore_t *) store; +} + +#if OPADDRCONFIGENSSL_VERSION_NUMBER < 0x00906070L +#undef i2d_DSA_PUBKEY + +int +i2d_DSA_PUBKEY_backwards(DSA *dsa, unsigned char **ptr) +{ + unsigned char *buf; + int len; + + len = i2d_DSA_PUBKEY(dsa, NULL); + if (len < 0) + return 0; + + *ptr = buf = OPENSSL_malloc(len); + return i2d_DSA_PUBKEY(dsa, &buf); +} +#endif + +#endif /* WITH_SECURITY */ |