/* * Copyright (c) 2005 - 2006 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "hx_locl.h" RCSID("$Id$"); struct ks_file { hx509_certs certs; }; struct header { char *header; char *value; struct header *next; }; static int add_headers(struct header **headers, const char *header, const char *value) { struct header *h; h = calloc(1, sizeof(*h)); if (h == NULL) return ENOMEM; h->header = strdup(header); if (h->header == NULL) { free(h); return ENOMEM; } h->value = strdup(value); if (h->value == NULL) { free(h->header); free(h); return ENOMEM; } h->next = *headers; *headers = h; return 0; } static void free_headers(struct header *headers) { struct header *h; while (headers) { h = headers; headers = headers->next; free(h->header); free(h->value); free(h); } } static const char * find_header(const struct header *headers, const char *header) { while(headers) { if (strcmp(header, headers->header) == 0) return headers->value; headers = headers->next; } return NULL; } /* * */ static int parse_certificate(hx509_context context, const char *fn, struct hx509_collector *c, const struct header *headers, const void *data, size_t len) { hx509_cert cert; Certificate t; size_t size; int ret; ret = decode_Certificate(data, len, &t, &size); if (ret) { hx509_set_error_string(context, 0, ret, "Failed to parse certificate in %s", fn); return ret; } ret = hx509_cert_init(context, &t, &cert); free_Certificate(&t); if (ret) return ret; ret = _hx509_collector_certs_add(context, c, cert); hx509_cert_free(cert); return ret; } static int try_decrypt(hx509_context context, struct hx509_collector *collector, const AlgorithmIdentifier *alg, const EVP_CIPHER *c, const void *ivdata, const void *password, size_t passwordlen, const void *cipher, size_t len) { heim_octet_string clear; size_t keylen; void *key; int ret; keylen = EVP_CIPHER_key_length(c); key = malloc(keylen); if (key == NULL) { hx509_clear_error_string(context); return ENOMEM; } ret = EVP_BytesToKey(c, EVP_md5(), ivdata, password, passwordlen, 1, key, NULL); if (ret <= 0) { hx509_set_error_string(context, 0, HX509_CRYPTO_INTERNAL_ERROR, "Failed to do string2key for private key"); return HX509_CRYPTO_INTERNAL_ERROR; } clear.data = malloc(len); if (clear.data == NULL) { hx509_set_error_string(context, 0, ENOMEM, "Out of memory to decrypt for private key"); ret = ENOMEM; goto out; } clear.length = len; { EVP_CIPHER_CTX ctx; EVP_CIPHER_CTX_init(&ctx); EVP_CipherInit_ex(&ctx, c, NULL, key, ivdata, 0); EVP_Cipher(&ctx, clear.data, cipher, len); EVP_CIPHER_CTX_cleanup(&ctx); } ret = _hx509_collector_private_key_add(context, collector, alg, NULL, &clear, NULL); memset(clear.data, 0, clear.length); free(clear.data); out: memset(key, 0, keylen); free(key); return ret; } static int parse_rsa_private_key(hx509_context context, const char *fn, struct hx509_collector *c, const struct header *headers, const void *data, size_t len) { int ret = 0; const char *enc; enc = find_header(headers, "Proc-Type"); if (enc) { const char *dek; char *type, *iv; ssize_t ssize, size; void *ivdata; const EVP_CIPHER *cipher; const struct _hx509_password *pw; hx509_lock lock; int i, decrypted = 0; lock = _hx509_collector_get_lock(c); if (lock == NULL) { hx509_set_error_string(context, 0, HX509_ALG_NOT_SUPP, "Failed to get password for " "password protected file %s", fn); return HX509_ALG_NOT_SUPP; } if (strcmp(enc, "4,ENCRYPTED") != 0) { hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED, "RSA key encrypted in unknown method %s " "in file", enc, fn); hx509_clear_error_string(context); return HX509_PARSING_KEY_FAILED; } dek = find_header(headers, "DEK-Info"); if (dek == NULL) { hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED, "Encrypted RSA missing DEK-Info"); return HX509_PARSING_KEY_FAILED; } type = strdup(dek); if (type == NULL) { hx509_clear_error_string(context); return ENOMEM; } iv = strchr(type, ','); if (iv) *iv++ = '\0'; size = strlen(iv); ivdata = malloc(size); if (ivdata == NULL) { hx509_clear_error_string(context); free(type); return ENOMEM; } cipher = EVP_get_cipherbyname(type); if (cipher == NULL) { free(ivdata); hx509_set_error_string(context, 0, HX509_ALG_NOT_SUPP, "RSA key encrypted with " "unsupported cipher: %s", type); free(type); return HX509_ALG_NOT_SUPP; } #define PKCS5_SALT_LEN 8 ssize = hex_decode(iv, ivdata, size); free(type); type = NULL; iv = NULL; if (ssize < 0 || ssize < PKCS5_SALT_LEN || ssize < EVP_CIPHER_iv_length(cipher)) { free(ivdata); hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED, "Salt have wrong length in RSA key file"); return HX509_PARSING_KEY_FAILED; } pw = _hx509_lock_get_passwords(lock); if (pw != NULL) { const void *password; size_t passwordlen; for (i = 0; i < pw->len; i++) { password = pw->val[i]; passwordlen = strlen(password); ret = try_decrypt(context, c, hx509_signature_rsa(), cipher, ivdata, password, passwordlen, data, len); if (ret == 0) { decrypted = 1; break; } } } if (!decrypted) { hx509_prompt prompt; char password[128]; memset(&prompt, 0, sizeof(prompt)); prompt.prompt = "Password for keyfile: "; prompt.type = HX509_PROMPT_TYPE_PASSWORD; prompt.reply.data = password; prompt.reply.length = sizeof(password); ret = hx509_lock_prompt(lock, &prompt); if (ret == 0) ret = try_decrypt(context, c, hx509_signature_rsa(), cipher, ivdata, password, strlen(password), data, len); /* XXX add password to lock password collection ? */ memset(password, 0, sizeof(password)); } free(ivdata); } else { heim_octet_string keydata; keydata.data = rk_UNCONST(data); keydata.length = len; ret = _hx509_collector_private_key_add(context, c, hx509_signature_rsa(), NULL, &keydata, NULL); } return ret; } struct pem_formats { const char *name; int (*func)(hx509_context, const char *, struct hx509_collector *, const struct header *, const void *, size_t); } formats[] = { { "CERTIFICATE", parse_certificate }, { "RSA PRIVATE KEY", parse_rsa_private_key } }; static int parse_pem_file(hx509_context context, const char *fn, struct hx509_collector *c, int *found_data) { struct header *headers = NULL; char *type = NULL; void *data = NULL; size_t len = 0; char buf[1024]; int ret; FILE *f; enum { BEFORE, SEARCHHEADER, INHEADER, INDATA, DONE } where; where = BEFORE; *found_data = 0; if ((f = fopen(fn, "r")) == NULL) { hx509_set_error_string(context, 0, ENOENT, "Failed to open PEM file \"%s\": %s", fn, strerror(errno)); return ENOENT; } ret = 0; while (fgets(buf, sizeof(buf), f) != NULL) { char *p; int i; i = strcspn(buf, "\n"); if (buf[i] == '\n') { buf[i] = '\0'; if (i > 0) i--; } if (buf[i] == '\r') { buf[i] = '\0'; if (i > 0) i--; } switch (where) { case BEFORE: if (strncmp("-----BEGIN ", buf, 11) == 0) { type = strdup(buf + 11); if (type == NULL) break; p = strchr(type, '-'); if (p) *p = '\0'; *found_data = 1; where = SEARCHHEADER; } break; case SEARCHHEADER: p = strchr(buf, ':'); if (p == NULL) { where = INDATA; goto indata; } /* FALLTHOUGH */ case INHEADER: if (buf[0] == '\0') { where = INDATA; break; } p = strchr(buf, ':'); if (p) { *p++ = '\0'; while (isspace((int)*p)) p++; add_headers(&headers, buf, p); } break; case INDATA: indata: if (strncmp("-----END ", buf, 9) == 0) { where = DONE; break; } p = malloc(i); i = base64_decode(buf, p); data = erealloc(data, len + i); memcpy(((char *)data) + len, p, i); free(p); len += i; break; case DONE: abort(); } if (where == DONE) { int j; for (j = 0; j < sizeof(formats)/sizeof(formats[0]); j++) { const char *q = formats[j].name; if (strcasecmp(type, q) == 0) { ret = (*formats[j].func)(context, fn, c, headers, data, len); break; } } if (j == sizeof(formats)/sizeof(formats[0])) { ret = HX509_UNSUPPORTED_OPERATION; hx509_set_error_string(context, 0, ret, "Found no matching PEM format for %s", type); } free(data); data = NULL; len = 0; free(type); type = NULL; where = BEFORE; free_headers(headers); headers = NULL; if (ret) break; } } fclose(f); if (where != BEFORE) { hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED, "File ends before end of PEM end tag"); ret = HX509_PARSING_KEY_FAILED; } if (data) free(data); if (type) free(type); if (headers) free_headers(headers); return ret; } /* * */ static int file_init(hx509_context context, hx509_certs certs, void **data, int flags, const char *residue, hx509_lock lock) { char *files = NULL, *p, *pnext; struct ks_file *f = NULL; struct hx509_collector *c; int ret; *data = NULL; if (lock == NULL) lock = _hx509_empty_lock; c = _hx509_collector_alloc(context, lock); if (c == NULL) return ENOMEM; f = calloc(1, sizeof(*f)); if (f == NULL) { hx509_clear_error_string(context); ret = ENOMEM; goto out; } files = strdup(residue); if (files == NULL) { hx509_clear_error_string(context); ret = ENOMEM; goto out; } for (p = files; p != NULL; p = pnext) { int found_data; pnext = strchr(p, ','); if (pnext) *pnext++ = '\0'; ret = parse_pem_file(context, p, c, &found_data); if (ret) goto out; if (!found_data) { size_t length; void *ptr; ret = _hx509_map_file(p, &ptr, &length, NULL); if (ret) { hx509_clear_error_string(context); goto out; } ret = parse_certificate(context, p, c, NULL, ptr, length); _hx509_unmap_file(ptr, length); if (ret) goto out; } } ret = _hx509_collector_collect(context, c, &f->certs); out: if (ret == 0) *data = f; else free(f); free(files); _hx509_collector_free(c); return ret; } static int file_free(hx509_certs certs, void *data) { struct ks_file *f = data; hx509_certs_free(&f->certs); free(f); return 0; } static int file_iter_start(hx509_context context, hx509_certs certs, void *data, void **cursor) { struct ks_file *f = data; return hx509_certs_start_seq(context, f->certs, cursor); } static int file_iter(hx509_context context, hx509_certs certs, void *data, void *iter, hx509_cert *cert) { struct ks_file *f = data; return hx509_certs_next_cert(context, f->certs, iter, cert); } static int file_iter_end(hx509_context context, hx509_certs certs, void *data, void *cursor) { struct ks_file *f = data; return hx509_certs_end_seq(context, f->certs, cursor); } static struct hx509_keyset_ops keyset_file = { "FILE", 0, file_init, file_free, NULL, NULL, file_iter_start, file_iter, file_iter_end }; void _hx509_ks_file_register(hx509_context context) { _hx509_ks_register(context, &keyset_file); }