#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: smime.c 1176 2008-09-29 21:16:42Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2013-2014 Eduardo Chappa * Copyright 2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ /* * This is based on a contribution from Jonathan Paisley * * File: smime.c * Author: paisleyj@dcs.gla.ac.uk * Date: 01/2001 */ #include "../pith/headers.h" #ifdef SMIME #include "../pith/osdep/canaccess.h" #include "../pith/helptext.h" #include "../pith/store.h" #include "../pith/status.h" #include "../pith/detach.h" #include "../pith/conf.h" #include "../pith/smkeys.h" #include "../pith/smime.h" #include "../pith/mailpart.h" #include "../pith/reply.h" #include "../pith/tempfile.h" #include "../pith/readfile.h" #include "../pith/remote.h" #include #include /* internal prototypes */ static void forget_private_keys(void); static int app_RAND_load_file(const char *file); static void openssl_extra_randomness(void); static int app_RAND_write_file(const char *file); static const char *openssl_error_string(void); static int load_private_key(PERSONAL_CERT *pcert); static void create_local_cache(char *h, char *base, BODY *b, int type); static long rfc822_output_func(void *b, char *string); static void setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename, char *smime_type); static BIO *body_to_bio(BODY *body); static BIO *bio_from_store(STORE_S *store); static STORE_S *get_part_contents(long msgno, const char *section); static PKCS7 *get_pkcs7_from_part(long msgno, const char *section); static int do_signature_verify(PKCS7 *p7, BIO *in, BIO *out, int silent); static int do_detached_signature_verify(BODY *b, long msgno, char *section); static PERSONAL_CERT *find_certificate_matching_pkcs7(PKCS7 *p7); static int do_decoding(BODY *b, long msgno, const char *section); static void free_smime_struct(SMIME_STUFF_S **smime); static void setup_storage_locations(void); static int copy_container_to_dir(WhichCerts which); static int do_fiddle_smime_message(BODY *b, long msgno, char *section); void setup_privatekey_storage(void); int smime_path(char *rpath, char *fpath, size_t len); int smime_extract_and_save_cert(PKCS7 *p7); int same_cert(X509 *, X509 *); CertList * certlist_from_personal_certs(PERSONAL_CERT *pc); #ifdef PASSFILE void load_key_and_cert(char *pathkeydir, char *pathcertdir, char **keyfile, char **certfile, EVP_PKEY **pkey, X509 **pcert); #endif /* PASSFILE */ STACK_OF(X509) *get_chain_for_cert(X509 *cert, int *error); EVP_PKEY *load_pkey_with_prompt(char *fpath, char *text, char *prompt); int (*pith_opt_smime_get_passphrase)(void); int (*pith_smime_import_certificate)(char *, char *, size_t); int (*pith_smime_enter_password)(char *prompt, char *, size_t); static X509_STORE *s_cert_store; /* State management for randomness functions below */ static int seeded = 0; static int egdsocket = 0; void * create_smime_sparep(SpareType stype, void *s) { SMIME_SPARE_S *rv; rv = fs_get(sizeof(SMIME_SPARE_S)); rv->sptype = stype; rv->data = s; return (void *) rv; } SpareType get_smime_sparep_type(void *s) { return ((SMIME_SPARE_S *)s)->sptype; } void * get_smime_sparep_data(void *s) { return ((SMIME_SPARE_S *)s)->data; } #ifdef PASSFILE /* * load key from pathkeydir and cert from pathcertdir. It chooses the first * key/certificate pair that matches. Delete pairs that you do not want used, * if you do not want them selected. All parameters must be non-null. * Memory freed by caller. */ void load_key_and_cert(char *pathkeydir, char *pathcertdir, char **keyfile, char **certfile, EVP_PKEY **pkey, X509 **pcert) { char buf[MAXPATH+1], pathkey[MAXPATH+1], prompt[MAILTMPLEN]; DIR *dirp; struct dirent *d; int b = 0; if(pathkeydir == NULL || pathcertdir == NULL || keyfile == NULL || pkey == NULL || certfile == NULL || pcert == NULL) return; *keyfile = NULL; *certfile = NULL; *pkey = NULL; *pcert = NULL; if((dirp = opendir(pathkeydir)) != NULL){ while(b == 0 && (d=readdir(dirp)) != NULL){ size_t ll; if((ll=strlen(d->d_name)) && ll > 4){ if(!strcmp(d->d_name+ll-4, ".key")){ strncpy(buf, d->d_name, sizeof(buf)); buf[sizeof(buf)-1] = '\0'; build_path(pathkey, pathkeydir, buf, sizeof(pathkey)); buf[strlen(buf)-4] = '\0'; snprintf(prompt, sizeof(prompt), _("Enter password of key <%s> to unlock password file: "), buf); if((*pkey = load_pkey_with_prompt(pathkey, NULL, prompt)) != NULL){ if(load_cert_for_key(pathcertdir, *pkey, certfile, pcert)){ b = 1; /* break */ *keyfile = cpystr(buf); } else { EVP_PKEY_free(*pkey); *pkey = NULL; q_status_message1(SM_ORDER, 0, 2, _("Cannot find certificate that matches key <%s>. Continuing..."), buf); } } } } } closedir(dirp); } } /* setup a key and certificate to encrypt and decrypt a password file. * These files will be saved in the .alpine-smime/.pwd directory, but its * location can be setup in the command line with the -pwdcertdir option. * Here are the rules: * * Check if the .alpine-smime/.pwd (or -pwdcertdir directory) exists, * if not create it. If we are successful, move to the next step * * - If the user has a key/cert pair, in the .alpine-smime/.pwd dir * setup is successful; * - if the user does not have a key/cert pair, look to see if * ps_global->smime->personal_certs is already setup, if so, use it. * - if ps_global->smime->personal_certs is not set up, see if we can * find a certificate/cert pair in the default locations at compilation * time. (~/.alpine-smime/private and ~/.alpine-smime/public * - if none of this is successful, create a key/certificate pair * (TODO: implement this) * - in any other case, setup is not successful. * * If setup is successful, setup ps_global->pwdcert. * If any of this fails, ps_global->pwdcert will be null. * Ok, that should do it. */ void setup_pwdcert(void **pwdcert) { int we_inited = 0; int setup_dir = 0; /* make it non zero if we know which dir to use */ struct stat sbuf; char pathdir[MAXPATH+1], pathkey[MAXPATH+1], fpath[MAXPATH+1], pathcert[MAXPATH+1]; char fpath2[MAXPATH+1], prompt[MAILTMPLEN]; char *keyfile, *certfile, *text; EVP_PKEY *pkey = NULL; X509 *pcert = NULL; PERSONAL_CERT *pc, *pc2 = NULL; if(pwdcert == NULL) return; if(ps_global->pwdcertdir){ if(our_stat(ps_global->pwdcertdir, &sbuf) == 0 && ((sbuf.st_mode & S_IFMT) == S_IFDIR)){ setup_dir++; strncpy(pathdir, ps_global->pwdcertdir, sizeof(pathdir)); pathdir[sizeof(pathdir)-1] = '\0'; } } else { smime_path(DF_PASSWORD_DIR, pathdir, sizeof(pathdir)); if(our_stat(pathdir, &sbuf) == 0){ if((sbuf.st_mode & S_IFMT) == S_IFDIR) setup_dir++; } else if(can_access(pathdir, ACCESS_EXISTS) != 0 && our_mkpath(pathdir, 0700) == 0) setup_dir++; } if(setup_dir == 0) return; load_key_and_cert(pathdir, pathdir, &keyfile, &certfile, &pkey, &pcert); if(certfile && keyfile){ pc = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT)); memset((void *)pc, 0, sizeof(PERSONAL_CERT)); pc->name = keyfile; pc->key = pkey; pc->cert = pcert; *pwdcert = (void *) pc; fs_give((void **)&certfile); return; } /* if the user gave a pwdcertdir and there is nothing there, do not * continue. Let the user initialize on their own this directory. */ if(ps_global->pwdcertdir != NULL) return; /* look to see if there are any certificates lying around, first * we try to load ps_global->smime to see if that has information * we can use. If we are the process filling the smime structure * we deinit at the end, since this might not do a full init. */ if(ps_global && ps_global->smime && !ps_global->smime->inited){ we_inited++; smime_init(); } /* at this point ps_global->smime->inited == 1 */ if(ps_global->smime && ps_global->smime->personal_certs != NULL){ pc = (PERSONAL_CERT *) ps_global->smime->personal_certs; if(ps_global->smime->privatetype == Directory){ build_path(pathkey, ps_global->smime->privatepath, pc->name, sizeof(pathkey)); strncat(pathkey, ".key", 4); pathkey[sizeof(pathkey)-1] = '\0'; text = NULL; } else if (ps_global->smime->privatetype == Container){ if(pc->keytext == NULL){ /* we should *never* be here, but just in case */ if(ps_global->smime->privatecontent != NULL){ char tmp[MAILTMPLEN], *s, *t, c; snprintf(tmp, sizeof(tmp), "%s%s", EMAILADDRLEADER, pc->name); tmp[sizeof(tmp)-1] = '\0'; if((s = strstr(ps_global->smime->privatecontent, tmp)) != NULL){ if((t = strstr(s+strlen(tmp), EMAILADDRLEADER)) != NULL){ c = *t; *t = '\0'; pc->keytext = cpystr(s + strlen(tmp) + 1); /* 1 = strlen("\n") */ *t = c; } else pc->keytext = cpystr(s + strlen(tmp) + 1); /* 1 = strlen("\n") */ } } } if(pc->keytext != NULL) /* we should go straigth here */ text = pc->keytext; } else if (ps_global->smime->privatetype == Keychain){ pathkey[0] = '\0'; /* no apple key chain support yet */ text = NULL; } if((pathkey && *pathkey) || text){ snprintf(prompt, sizeof(prompt), _("Enter password of key <%s> to unlock password file: "), pc->name); if((pkey = load_pkey_with_prompt(pathkey, text, prompt)) != NULL){ pc2 = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT)); memset((void *)pc2, 0, sizeof(PERSONAL_CERT)); pc2->name = cpystr(pc->name); pc2->key = pkey; pc2->cert = X509_dup(pc->cert); /* now copy the keys and certs, starting by the key... */ build_path(fpath, pathdir, pc->name, sizeof(fpath)); strncat(fpath, ".key", 4); fpath[sizeof(fpath)-1] = '\0'; if(our_stat(fpath, &sbuf) == 0){ /* if fpath exists */ if((sbuf.st_mode & S_IFMT) == S_IFREG) /* and is a regular file */ setup_dir++; /* we are done */ } else if(ps_global->smime->privatetype == Directory){ if(our_copy(fpath, pathkey) == 0) setup_dir++; } else if(ps_global->smime->privatetype == Container){ BIO *out; if((out = BIO_new_file(fpath, "w")) != NULL){ if(BIO_puts(out, pc->keytext) > 0) setup_dir++; BIO_free(out); } } else if(ps_global->smime->privatetype == Keychain){ /* add support for Apple Mac OS X */ } } /* successful copy of key, now continue with certificate */ if(setup_dir){ setup_dir = 0; build_path(pathkey, ps_global->smime->publicpath, pc->name, sizeof(pathkey)); strncat(pathkey, ".crt", 4); pathkey[sizeof(pathkey)-1] = '\0'; build_path(fpath, pathdir, pc->name, sizeof(fpath)); strncat(fpath, ".crt", 4); fpath[sizeof(fpath)-1] = '\0'; if(our_stat(fpath, &sbuf) == 0){ if((sbuf.st_mode & S_IFMT) == S_IFREG) setup_dir++; } else if(ps_global->smime->privatetype == Directory){ if(our_copy(fpath, pathkey) == 0) setup_dir++; } else if(ps_global->smime->privatetype == Container) { BIO *out; if((out = BIO_new_file(fpath, "w")) != NULL){ if(PEM_write_bio_X509(out, pc->cert)) setup_dir++; BIO_free(out); } } else if (ps_global->smime->privatetype == Keychain) { /* add support for Mac OS X */ } } if(setup_dir){ *pwdcert = (void *) pc2; return; } else if(pc2 != NULL) free_personal_certs(&pc2); } /* if (pathkey...) */ } /* if(ps_global->smime->personal_certs) */ if(setup_dir == 0){ /* PATHCERTDIR(Private) must be null, so create a path */ set_current_val(&ps_global->vars[V_PRIVATEKEY_DIR], TRUE, TRUE); smime_path(ps_global->VAR_PRIVATEKEY_DIR, pathkey, sizeof(pathkey)); /* PATHCERTDIR(Public) must be null, so create a path */ set_current_val(&ps_global->vars[V_PUBLICCERT_DIR], TRUE, TRUE); smime_path(ps_global->VAR_PUBLICCERT_DIR, pathcert, sizeof(pathcert)); /* BUG: this does not support local containers */ load_key_and_cert(pathkey, pathcert, &keyfile, &certfile, &pkey, &pcert); if(certfile && keyfile){ build_path(fpath, pathdir, keyfile, sizeof(fpath)); strncat(fpath, ".key", 4); fpath[sizeof(fpath)-1] = '\0'; build_path(fpath2, pathkey, keyfile, sizeof(fpath)); strncat(fpath2, ".key", 4); fpath2[sizeof(fpath2)-1] = '\0'; if(our_copy(fpath, fpath2) == 0) setup_dir++; if(setup_dir){ setup_dir = 0; build_path(fpath, pathdir, certfile, sizeof(fpath)); build_path(fpath2, pathcert, certfile, sizeof(fpath2)); if(our_copy(fpath, fpath2) == 0) setup_dir++; } } } if(keyfile && certfile){ pc = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT)); memset((void *)pc, 0, sizeof(PERSONAL_CERT)); pc->name = keyfile; pc->key = pkey; pc->cert = pcert; *pwdcert = (void *) pc; fs_give((void **)&certfile); return; } /* TODO: create self signed certificate q_status_message(SM_ORDER, 2, 2, _("No key/certificate pair found for password file encryption support")); */ if(we_inited) smime_deinit(); } #endif /* PASSFILE */ /* smime_expunge_cert. * Return values: < 0 there was an error. * >=0 the number of messages expunged */ int smime_expunge_cert(WhichCerts ctype) { int count, removed; CertList *cl, *dummy, *data; char *path, buf[MAXPATH+1]; char *contents; if(DATACERT(ctype)== NULL) return -1; /* data cert is the way we unify certificate management across * functions, but it is not where we really save the information in the * case ctype is equal to Private. What we will do is to update the * datacert, and in the case of ctype equal to Private use the updated * certdata to update the personal_certs data. */ path = PATHCERTDIR(ctype); if(path){ /* add a fake certificate at the beginning of the list */ dummy = fs_get(sizeof(CertList)); memset((void *)dummy, 0, sizeof(CertList)); dummy->next = DATACERT(ctype); for(cl = dummy, count = 0; cl && cl->next;){ if(cl->next->data.deleted == 0){ cl = cl->next; continue; } removed = 1; /* assume success */ if(SMHOLDERTYPE(ctype) == Directory){ build_path(buf, path, cl->next->name, sizeof(buf)); if(ctype == Private && strlen(buf) + strlen(EXTCERT(Private)) < sizeof(buf)){ strncat(buf, EXTCERT(Private), 4); buf[sizeof(buf)-1] = '\0'; } if(our_unlink(buf) < 0){ q_status_message1(SM_ORDER, 3, 3, _("Error removing certificate %s"), cl->next->name); cl = cl->next; removed = 0; } } else { /* SMHOLDERTYPE(ctype) == Container */ char *prefix= ctype == CACert ? CACERTSTORELEADER : EMAILADDRLEADER; char tmp[MAILTMPLEN], *s, *t; contents = CONTENTCERTLIST(ctype); snprintf(tmp, sizeof(tmp), "%s%s", prefix, cl->next->name); tmp[sizeof(tmp) - 1] = '\0'; if((s = strstr(contents, tmp)) != NULL){ if((t = strstr(s+strlen(tmp), prefix)) == NULL) *s = '\0'; else memmove(s, t, strlen(t)+1); fs_resize((void **)&contents, strlen(contents)+1); switch(ctype){ case Private: ps_global->smime->privatecontent = contents; break; case Public : ps_global->smime->publiccontent = contents; break; case CACert : ps_global->smime->cacontent = contents; break; default : break; } } else removed = 0; } if(removed > 0){ count++; /* count it! */ data = cl->next; cl->next = data->next; if(data->name) fs_give((void **)&data->name); fs_give((void **)&data); } } } else q_status_message(SM_ORDER, 3, 3, _("Error expunging certificate")); switch(ctype){ case Private: ps_global->smime->privatecertlist = dummy->next; break; case Public : ps_global->smime->publiccertlist = dummy->next; break; case CACert : ps_global->smime->cacertlist = dummy->next; break; default : break; } fs_give((void **)&dummy); if(SMHOLDERTYPE(ctype) == Container){ if(copy_dir_to_container(ctype, contents) < 0) count = 0; } if(count > 0){ q_status_message2(SM_ORDER, 3, 3, _("Removed %s certificate%s"), comatose(count), plural(count)); } else q_status_message(SM_ORDER, 3, 3, _("Error: No certificates were removed")); return count; } void mark_cert_deleted(WhichCerts ctype, int num, unsigned state) { CertList *cl; int i; for(cl = DATACERT(ctype), i = 0; cl != NULL && i < num; cl = cl->next, i++); cl->data.deleted = state; } unsigned get_cert_deleted(WhichCerts ctype, int num) { CertList *cl; int i; for(cl = DATACERT(ctype), i = 0; cl != NULL && i < num; cl = cl->next, i++); return (cl && cl->data.deleted) ? 1 : 0; } EVP_PKEY * load_pkey_with_prompt(char *fpath, char *text, char *prompt) { EVP_PKEY *pkey; int rc = 0; /* rc == 1, cancel, rc == 0 success */ char pass[MAILTMPLEN+1]; BIO *in; /* attempt to load with empty password */ in = text ? BIO_new_mem_buf(text, strlen(text)) : BIO_new_file(fpath, "r"); if(in != NULL){ pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, ""); BIO_free(in); if(pkey != NULL) return pkey; } if(pith_smime_enter_password) while(pkey == NULL && rc != 1){ in = text ? BIO_new_mem_buf(text, strlen(text)) : BIO_new_file(fpath, "r"); if(in != NULL){ do { rc = (*pith_smime_enter_password)(prompt, (char *)pass, sizeof(pass)); } while (rc!=0 && rc!=1 && rc>0); pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, (char *)pass); BIO_free(in); } else rc = 1; } return pkey; } int import_certificate(WhichCerts ctype) { int r = 1, rc; char filename[MAXPATH+1], full_filename[MAXPATH+1], buf[MAXPATH+1]; if(pith_smime_import_certificate == NULL){ q_status_message(SM_ORDER, 0, 2, _("import of certificates not implemented yet!")); return -1; } smime_init(); r = (*pith_smime_import_certificate)(filename, full_filename, sizeof(filename) - 20); ps_global->mangled_screen = 1; if(r < 0) return r; else if (ctype == Private){ char prompt[500], *s, *t; EVP_PKEY *key = NULL; if(!ps_global->smime->privatecertlist){ ps_global->smime->privatecertlist = fs_get(sizeof(CertList)); memset((void *)DATACERT(ctype), 0, sizeof(CertList)); } for(s = t = filename; (t = strstr(s, ".key")) != NULL; s = t + 1); if(s) *(s-1) = 0; snprintf(prompt, sizeof(prompt), _("Enter passphrase for <%s>: "), filename); prompt[sizeof(prompt)-1] = '\0'; if((key = load_pkey_with_prompt(full_filename, NULL, prompt)) != NULL){ if(SMHOLDERTYPE(ctype) == Directory){ build_path(buf, PATHCERTDIR(ctype), filename, sizeof(buf)); if(strcmp(buf + strlen(buf) - 4, EXTCERT(ctype)) != 0 && strlen(buf) + 4 < sizeof(buf)){ strncat(buf, EXTCERT(ctype), 4); buf[sizeof(buf)-1] = '\0'; } rc = our_copy(buf, full_filename); } else /* if(SMHOLDERTYPE(ctype) == Container){ */ rc = add_file_to_container(ctype, full_filename, NULL); if(rc == 0) q_status_message(SM_ORDER, 1, 3, _("Private key saved")); else q_status_message(SM_ORDER, 1, 3, _("Error saving private key")); if(ps_global->smime->publiccertlist) ps_global->smime->publiccertlist->data.renew = 1; } else q_status_message(SM_ORDER, 1, 3, _("Problem unlocking key (not a certificate and/or wrong password)")); } else if (ctype == CACert){ BIO *ins; X509 *cert; if((ins = BIO_new_file(full_filename, "r")) != NULL){ if((cert = PEM_read_bio_X509(ins, NULL, NULL, NULL)) != NULL){ if(SMHOLDERTYPE(ctype) == Directory){ build_path(buf, PATHCERTDIR(ctype), filename, sizeof(buf)); if(strcmp(buf + strlen(buf) - 4, ".crt") != 0 && strlen(buf) + 4 < sizeof(buf)){ strncat(buf, EXTCERT(ctype), 4); buf[sizeof(buf)-1] = '\0'; } rc = our_copy(buf, full_filename); } else /* if(SMHOLDERTYPE(ctype) == Container){ */ rc = add_file_to_container(ctype, full_filename, NULL); if(rc == 0) q_status_message(SM_ORDER, 1, 3, _("Certificate saved")); else q_status_message(SM_ORDER, 1, 3, _("Error saving certificate")); X509_free(cert); /* not needed anymore */ } else q_status_message(SM_ORDER, 1, 3, _("Error in certificate file (not a certificate?)")); BIO_free(ins); } renew_store(); } else { /* ctype == Public. save certificate, but first validate that it is one */ BIO *ins; X509 *cert; if((ins = BIO_new_file(full_filename, "r")) != NULL){ if((cert = PEM_read_bio_X509(ins, NULL, NULL, NULL)) != NULL){ if(SMHOLDERTYPE(ctype) == Directory){ char **email = get_x509_subject_email(cert); int i; for(i = 0; email[i] != NULL; i++){ save_cert_for(email[i], cert, Public); fs_give((void **)&email[i]); } fs_give((void **)email); } else /* if(SMHOLDERTYPE(ctype) == Container){ */ add_file_to_container(ctype, full_filename, NULL); X509_free(cert); if(ps_global->smime->publiccertlist) ps_global->smime->publiccertlist->data.renew = 1; } else q_status_message(SM_ORDER, 1, 3, _("Error in certificate file (not a certificate?)")); BIO_free(ins); } } if(DATACERT(ctype)) RENEWCERT(DATACERT(ctype)) = 1; return 0; } /* itype: information type to add: 0 - public, 1 - private. * Memory freed by caller */ BIO * print_private_key_information(char *email, int itype) { BIO *out; PERSONAL_CERT *pc; if(ps_global->smime == NULL || ps_global->smime->personal_certs == NULL || (itype != 0 && itype != 1)) return NULL; for(pc = ps_global->smime->personal_certs; pc != NULL && strcmp(pc->name, email) != 0; pc = pc->next); if(pc->key == NULL && !load_private_key(pc) && ps_global->smime && ps_global->smime->need_passphrase){ if (*pith_opt_smime_get_passphrase) (*pith_opt_smime_get_passphrase)(); load_private_key(pc); } if(pc->key == NULL) return NULL; out = BIO_new(BIO_s_mem()); if(itype == 0) /* 0 means public */ EVP_PKEY_print_public(out, pc->key, 0, NULL); else if (itype == 1) /* 1 means private */ EVP_PKEY_print_private(out, pc->key, 0, NULL); if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global)) forget_private_keys(); return out; } /* * Forget any cached private keys */ static void forget_private_keys(void) { PERSONAL_CERT *pcert; size_t len; volatile char *p; dprint((9, "forget_private_keys()")); if(ps_global->smime){ for(pcert=(PERSONAL_CERT *) ps_global->smime->personal_certs; pcert; pcert=pcert->next){ if(pcert->key){ EVP_PKEY_free(pcert->key); pcert->key = NULL; } } ps_global->smime->entered_passphrase = 0; len = sizeof(ps_global->smime->passphrase); p = ps_global->smime->passphrase; while(len-- > 0) *p++ = '\0'; } } /* modelled after signature_path in reply.c, but uses home dir instead of the * directory where the .pinerc is located, since according to documentation, * the .alpine-smime directories are subdirectories of the home directory */ int smime_path(char *rpath, char *fpath, size_t len) { *fpath = '\0'; if(rpath && *rpath){ size_t spl = strlen(rpath); if(IS_REMOTE(rpath)){ if(spl < len - 1) strncpy(fpath, rpath, len-1); fpath[len-1] = '\0'; } else if(is_absolute_path(rpath)){ strncpy(fpath, rpath, len-1); fpath[len-1] = '\0'; fnexpand(fpath, len); } else if(ps_global->VAR_OPER_DIR){ if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1) build_path(fpath, ps_global->VAR_OPER_DIR, rpath, len); } else if(ps_global->home_dir){ if(strlen(ps_global->home_dir) + spl < len - 1) build_path(fpath, ps_global->home_dir, rpath, len); } } return fpath && *fpath ? 1 : 0; } /* * taken from openssl/apps/app_rand.c */ static int app_RAND_load_file(const char *file) { char buffer[200]; if(file == NULL) file = RAND_file_name(buffer, sizeof buffer); else if(RAND_egd(file) > 0){ /* we try if the given filename is an EGD socket. if it is, we don't write anything back to the file. */ egdsocket = 1; return 1; } if(file == NULL || !RAND_load_file(file, -1)){ if(RAND_status() == 0){ dprint((1, "unable to load 'random state'\n")); dprint((1, "This means that the random number generator has not been seeded\n")); dprint((1, "with much random data.\n")); } return 0; } seeded = 1; return 1; } /* * copied and fiddled from imap/src/osdep/unix/auth_ssl.c */ static void openssl_extra_randomness(void) { #if !defined(WIN32) int fd; unsigned long i; char *tf = NULL; char tmp[MAXPATH]; struct stat sbuf; /* if system doesn't have /dev/urandom */ if(stat ("/dev/urandom", &sbuf)){ tmp[0] = '0'; tf = temp_nam(NULL, NULL); if(tf){ strncpy(tmp, tf, sizeof(tmp)); tmp[sizeof(tmp)-1] = '\0'; fs_give((void **) &tf); } if((fd = open(tmp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) i = (unsigned long) tmp; else{ unlink(tmp); /* don't need the file */ fstat(fd, &sbuf); /* get information about the file */ i = sbuf.st_ino; /* remember its inode */ close(fd); /* or its descriptor */ } /* not great but it'll have to do */ snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp), "%.80s%lx%lx%lx", tcp_serverhost (),i, (unsigned long) (time (0) ^ gethostid ()), (unsigned long) getpid ()); RAND_seed(tmp, strlen(tmp)); } #endif } /* taken from openssl/apps/app_rand.c */ static int app_RAND_write_file(const char *file) { char buffer[200]; if(egdsocket || !seeded) /* * If we did not manage to read the seed file, * we should not write a low-entropy seed file back -- * it would suppress a crucial warning the next time * we want to use it. */ return 0; if(file == NULL) file = RAND_file_name(buffer, sizeof buffer); if(file == NULL || !RAND_write_file(file)){ dprint((1, "unable to write 'random state'\n")); return 0; } return 1; } CertList * certlist_from_personal_certs(PERSONAL_CERT *pc) { CertList *cl; if(pc == NULL) return NULL; cl = fs_get(sizeof(CertList)); memset((void *)cl, 0, sizeof(CertList)); cl->name = cpystr(pc->name); cl->next = certlist_from_personal_certs(pc->next); return cl; } void renew_cert_data(CertList **data, WhichCerts ctype) { smime_init(); if(ctype == Private){ if(data){ PERSONAL_CERT *pc = (PERSONAL_CERT *)ps_global->smime->personal_certs; if(*data) free_certlist(data); free_personal_certs(&pc); setup_privatekey_storage(); *data = certlist_from_personal_certs((PERSONAL_CERT *)ps_global->smime->personal_certs); if(data && *data) RENEWCERT(*data) = 0; ps_global->smime->privatecertlist = *data; } if(ps_global->smime->privatecertlist) RENEWCERT(ps_global->smime->privatecertlist) = 0; } else { X509_LOOKUP *lookup = NULL; X509_STORE *store = NULL; if((store = X509_STORE_new()) != NULL){ if((lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) == NULL){ X509_STORE_free(store); store = NULL; } else{ free_certlist(data); if(SMHOLDERTYPE(ctype) == Directory) add_certs_in_dir(lookup, PATHCERTDIR(ctype), EXTCERT(ctype), data); else /* if(SMHOLDERTYPE(ctype) == Container) */ *data = mem_to_certlist(CONTENTCERTLIST(ctype), ctype); if(data && *data) RENEWCERT(*data) = 0; if(ctype == Public) ps_global->smime->publiccertlist = *data; else ps_global->smime->cacertlist = *data; } } } } void smime_reinit(void) { smime_deinit(); smime_init(); } /* Installed as an atexit() handler to save the random data */ void smime_deinit(void) { dprint((9, "smime_deinit()")); app_RAND_write_file(NULL); free_smime_struct(&ps_global->smime); } /* we renew the store when it has changed */ void renew_store(void) { if(ps_global->smime->inited){ if(s_cert_store != NULL) X509_STORE_free(s_cert_store); s_cert_store = get_ca_store(); } } /* Initialise openssl stuff if needed */ void smime_init(void) { if(F_OFF(F_DONT_DO_SMIME, ps_global) && !(ps_global->smime && ps_global->smime->inited)){ dprint((9, "smime_init()")); if(!ps_global->smime) ps_global->smime = new_smime_struct(); setup_storage_locations(); s_cert_store = get_ca_store(); OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); app_RAND_load_file(NULL); openssl_extra_randomness(); ps_global->smime->inited = 1; } ERR_clear_error(); } /* validate a certificate. Return value : 0 for no error, -1 for error. * In the latter case, set the openssl smime error in *error. */ int smime_validate_cert(X509 *cert, long *error) { X509_STORE_CTX *csc; ERR_clear_error(); *error = 0; if((csc = X509_STORE_CTX_new()) != NULL){ X509_STORE_set_flags(s_cert_store, 0); if(X509_STORE_CTX_init(csc,s_cert_store,cert,NULL) && X509_verify_cert(csc) <= 0) *error = X509_STORE_CTX_get_error(csc); X509_STORE_CTX_free(csc); } return *error ? -1 : 0; } PERSONAL_CERT * get_personal_certs(char *path) { PERSONAL_CERT *result = NULL; char buf2[MAXPATH]; struct dirent *d; DIR *dirp; ps_global->smime->privatepath = cpystr(path); dirp = opendir(path); if(dirp){ while((d=readdir(dirp)) != NULL){ X509 *cert; size_t ll; if((ll=strlen(d->d_name)) && ll > 4 && !strcmp(d->d_name+ll-4, ".key")){ /* copy file name to temp buffer */ strncpy(buf2, d->d_name, sizeof(buf2)-1); buf2[sizeof(buf2)-1] = '\0'; /* chop off ".key" trailier */ buf2[strlen(buf2)-4] = 0; /* Look for certificate */ cert = get_cert_for(buf2, Public); if(cert){ PERSONAL_CERT *pc; /* create a new PERSONAL_CERT, fill it in */ pc = (PERSONAL_CERT *) fs_get(sizeof(*pc)); pc->cert = cert; pc->name = cpystr(buf2); /* Try to load the key with an empty password */ pc->key = load_key(pc, ""); pc->next = result; result = pc; } } } closedir(dirp); } return result; } void setup_privatekey_storage(void) { char path[MAXPATH+1], *contents; int privatekeycontainer = 0; /* private keys in a container */ if(ps_global->VAR_PRIVATEKEY_CONTAINER && ps_global->VAR_PRIVATEKEY_CONTAINER[0]){ privatekeycontainer = 1; contents = NULL; path[0] = '\0'; if(!smime_path(ps_global->VAR_PRIVATEKEY_CONTAINER, path, MAXPATH)) privatekeycontainer = 0; if(privatekeycontainer && !IS_REMOTE(path) && ps_global->VAR_OPER_DIR && !in_dir(ps_global->VAR_OPER_DIR, path)){ q_status_message2(SM_ORDER | SM_DING, 3, 4, /* TRANSLATORS: First arg is the directory name, second is the file user wants to read but can't. */ _("Can't read file outside %s: %s"), ps_global->VAR_OPER_DIR, path); privatekeycontainer = 0; } if(privatekeycontainer && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE))) && !(contents = read_file(path, READ_FROM_LOCALE))) privatekeycontainer = 0; } if(privatekeycontainer && path[0]){ ps_global->smime->privatetype = Container; ps_global->smime->privatepath = cpystr(path); if(contents){ ps_global->smime->privatecontent = contents; ps_global->smime->personal_certs = mem_to_personal_certs(contents); } } } /* private keys in a directory of files */ if(!privatekeycontainer){ ps_global->smime->privatetype = Directory; path[0] = '\0'; if(!(smime_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH) && !IS_REMOTE(path))) ps_global->smime->privatetype = Nada; else if(can_access(path, ACCESS_EXISTS)){ if(our_mkpath(path, 0700)){ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path); ps_global->smime->privatetype = Nada; } } if(ps_global->smime->privatetype == Directory) ps_global->smime->personal_certs = get_personal_certs(path); } } static void setup_storage_locations(void) { int publiccertcontainer = 0, cacertcontainer = 0; char path[MAXPATH+1], *contents; if(!ps_global->smime) return; #ifdef APPLEKEYCHAIN if(F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){ ps_global->smime->publictype = Keychain; } else{ #endif /* APPLEKEYCHAIN */ /* Public certificates in a container */ if(ps_global->VAR_PUBLICCERT_CONTAINER && ps_global->VAR_PUBLICCERT_CONTAINER[0]){ publiccertcontainer = 1; contents = NULL; path[0] = '\0'; if(!smime_path(ps_global->VAR_PUBLICCERT_CONTAINER, path, MAXPATH)) publiccertcontainer = 0; if(publiccertcontainer && !IS_REMOTE(path) && ps_global->VAR_OPER_DIR && !in_dir(ps_global->VAR_OPER_DIR, path)){ q_status_message2(SM_ORDER | SM_DING, 3, 4, /* TRANSLATORS: First arg is the directory name, second is the file user wants to read but can't. */ _("Can't read file outside %s: %s"), ps_global->VAR_OPER_DIR, path); publiccertcontainer = 0; } if(publiccertcontainer && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE))) && !(contents = read_file(path, READ_FROM_LOCALE))) publiccertcontainer = 0; } if(publiccertcontainer && path[0]){ ps_global->smime->publictype = Container; ps_global->smime->publicpath = cpystr(path); if(contents){ ps_global->smime->publiccontent = contents; ps_global->smime->publiccertlist = mem_to_certlist(contents, Public); } } } /* Public certificates in a directory of files */ if(!publiccertcontainer){ ps_global->smime->publictype = Directory; path[0] = '\0'; if(!(smime_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH) && !IS_REMOTE(path))) ps_global->smime->publictype = Nada; else if(can_access(path, ACCESS_EXISTS)){ if(our_mkpath(path, 0700)){ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path); ps_global->smime->publictype = Nada; } } if(ps_global->smime->publictype == Directory) ps_global->smime->publicpath = cpystr(path); } #ifdef APPLEKEYCHAIN } #endif /* APPLEKEYCHAIN */ setup_privatekey_storage(); /* extra cacerts in a container */ if(ps_global->VAR_CACERT_CONTAINER && ps_global->VAR_CACERT_CONTAINER[0]){ cacertcontainer = 1; contents = NULL; path[0] = '\0'; if(!smime_path(ps_global->VAR_CACERT_CONTAINER, path, MAXPATH)) cacertcontainer = 0; if(cacertcontainer && !IS_REMOTE(path) && ps_global->VAR_OPER_DIR && !in_dir(ps_global->VAR_OPER_DIR, path)){ q_status_message2(SM_ORDER | SM_DING, 3, 4, /* TRANSLATORS: First arg is the directory name, second is the file user wants to read but can't. */ _("Can't read file outside %s: %s"), ps_global->VAR_OPER_DIR, path); cacertcontainer = 0; } if(cacertcontainer && (IS_REMOTE(path) || can_access(path, ACCESS_EXISTS) == 0)){ if(!(IS_REMOTE(path) && (contents = simple_read_remote_file(path, REMOTE_SMIME_SUBTYPE))) && !(contents = read_file(path, READ_FROM_LOCALE))) cacertcontainer = 0; } if(cacertcontainer && path[0]){ ps_global->smime->catype = Container; ps_global->smime->capath = cpystr(path); ps_global->smime->cacontent = contents; if(contents) ps_global->smime->cacertlist = mem_to_certlist(contents, CACert); } } if(!cacertcontainer){ ps_global->smime->catype = Directory; path[0] = '\0'; if(!(smime_path(ps_global->VAR_CACERT_DIR, path, MAXPATH) && !IS_REMOTE(path))) ps_global->smime->catype = Nada; else if(can_access(path, ACCESS_EXISTS)){ if(our_mkpath(path, 0700)){ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path); ps_global->smime->catype = Nada; } } if(ps_global->smime->catype == Directory) ps_global->smime->capath = cpystr(path); } } int copy_publiccert_dir_to_container(void) { return(copy_dir_to_container(Public, NULL)); } int copy_publiccert_container_to_dir(void) { return(copy_container_to_dir(Public)); } int copy_privatecert_dir_to_container(void) { return(copy_dir_to_container(Private, NULL)); } int copy_privatecert_container_to_dir(void) { return(copy_container_to_dir(Private)); } int copy_cacert_dir_to_container(void) { return(copy_dir_to_container(CACert, NULL)); } int copy_cacert_container_to_dir(void) { return(copy_container_to_dir(CACert)); } /* Add the contents of a file to a container. Do not check the content * of the file, just add it using the format for that container. The * caller must check the format, so that there is no data corruption * in the future. * return value: 0 - success, * != 0 - failure. */ int add_file_to_container(WhichCerts ctype, char *fpath, char *altname) { char *sep = (ctype == Public || ctype == Private) ? EMAILADDRLEADER : CACERTSTORELEADER; char *content = ctype == Public ? ps_global->smime->publiccontent : (ctype == Private ? ps_global->smime->privatecontent : ps_global->smime->cacontent); char *name; char *s; unsigned char c; struct stat sbuf; STORE_S *in = NULL; int rv = -1; /* assume error */ if(our_stat(fpath, &sbuf) < 0 || (in = so_get(FileStar, fpath, READ_ACCESS | READ_FROM_LOCALE)) == NULL) goto endadd; if(altname != NULL) name = altname; else if((name = strrchr(fpath, '/')) != NULL){ size_t ll; if((ll = strlen(++name)) > 4 && strucmp(name + ll - 4, EXTCERT(ctype)) == 0) name[ll-strlen(EXTCERT(ctype))] = '\0'; } else goto endadd; if(content){ fs_resize((void **)&content, strlen(content) + strlen(sep) + strlen(name) + sbuf.st_size + 3); /* 2 = \n + \n + \0*/ s = content; content += strlen(content); } else{ s = content = fs_get(strlen(sep) + strlen(name) + sbuf.st_size + 1); /* 2 = \n + \0 */ *content = '\0'; } strncat(content, sep, strlen(sep)); strncat(content, name, strlen(name)); content += strlen(content); *content++ = '\n'; while(so_readc(&c, in)) *content++ = (char) c; *content = '\0'; switch(ctype){ case Private: ps_global->smime->privatecontent = s; break; case Public : ps_global->smime->publiccontent = s; break; case CACert : ps_global->smime->cacontent = s; break; default : break; } rv = copy_dir_to_container(ctype, s); endadd: if(in) so_give(&in); return rv; } /* * returns 0 on success, -1 on failure * contents is an argument which tells this function to write the value * of this variable instead of reading the contents of the directory. * If the var contents is not null use its value as the value of the * container. */ int copy_dir_to_container(WhichCerts which, char *contents) { int ret = 0; BIO *bio_out = NULL, *bio_in = NULL; char srcpath[MAXPATH+1], dstpath[MAXPATH+1], emailaddr[MAXPATH], file[MAXPATH], line[4096]; char *tempfile = NULL, fpath[MAXPATH+1]; DIR *dirp; struct dirent *d; REMDATA_S *rd = NULL; char *configdir = NULL; char *configpath = NULL; char *configcontainer = NULL; char *filesuffix = NULL; char *ret_dir = NULL; dprint((9, "copy_dir_to_container(%s)", which==Public ? "Public" : which==Private ? "Private" : which==CACert ? "CACert" : "?")); smime_init(); srcpath[0] = '\0'; dstpath[0] = '\0'; file[0] = '\0'; emailaddr[0] = '\0'; if(which == Public){ configdir = ps_global->VAR_PUBLICCERT_DIR; configpath = ps_global->smime->publicpath; configcontainer = cpystr(DF_PUBLIC_CONTAINER); filesuffix = ".crt"; } else if(which == Private){ configdir = ps_global->VAR_PRIVATEKEY_DIR; configpath = ps_global->smime->privatepath; configcontainer = cpystr(DF_PRIVATE_CONTAINER); filesuffix = ".key"; } else if(which == CACert){ configdir = ps_global->VAR_CACERT_DIR; configpath = ps_global->smime->capath; configcontainer = cpystr(DF_CA_CONTAINER); filesuffix = ".crt"; } if(!(configdir && configdir[0])){ q_status_message(SM_ORDER, 3, 3, _("Directory not defined")); return -1; } if(!(configpath && configpath[0])){ #ifdef APPLEKEYCHAIN if(which == Public && F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){ q_status_message(SM_ORDER, 3, 3, _("Turn off the Keychain feature above first")); return -1; } #endif /* APPLEKEYCHAIN */ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined")); return -1; } if(!(filesuffix && strlen(filesuffix) == 4)){ return -1; } /* * If there is a legit directory to read from set up the * container file to write to. */ if(smime_path(configdir, srcpath, MAXPATH) && !IS_REMOTE(srcpath)){ if(IS_REMOTE(configpath)){ rd = rd_create_remote(RemImap, configpath, REMOTE_SMIME_SUBTYPE, NULL, "Error: ", _("Can't access remote smime configuration.")); if(!rd) return -1; (void) rd_read_metadata(rd); if(rd->access == MaybeRorW){ if(rd->read_status == 'R') rd->access = ReadOnly; else rd->access = ReadWrite; } if(rd->access != NoExists){ rd_check_remvalid(rd, 1L); /* * If the cached info says it is readonly but * it looks like it's been fixed now, change it to readwrite. */ if(rd->read_status == 'R'){ rd_check_readonly_access(rd); if(rd->read_status == 'W'){ rd->access = ReadWrite; rd->flags |= REM_OUTOFDATE; } else rd->access = ReadOnly; } } if(rd->flags & REM_OUTOFDATE){ if(rd_update_local(rd) != 0){ dprint((1, "copy_dir_to_container: rd_update_local failed\n")); rd_close_remdata(&rd); return -1; } } else rd_open_remote(rd); if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){ rd_close_remdata(&rd); return -1; } rd->flags |= DO_REMTRIM; strncpy(dstpath, rd->lf, sizeof(dstpath)-1); dstpath[sizeof(dstpath)-1] = '\0'; } else{ strncpy(dstpath, configpath, sizeof(dstpath)-1); dstpath[sizeof(dstpath)-1] = '\0'; } /* * dstpath is either the local Container file or the local cache file * for the remote Container file. */ tempfile = tempfile_in_same_dir(dstpath, "az", &ret_dir); } /* * If there is a legit directory to read from and a tempfile * to write to we continue. */ if(tempfile && (bio_out=BIO_new_file(tempfile, "w")) != NULL){ if(contents != NULL){ if(BIO_puts(bio_out, contents) < 0) ret = -1; } else { if((dirp = opendir(srcpath)) != NULL){ while((d=readdir(dirp)) && !ret){ size_t ll; if((ll=strlen(d->d_name)) && ll > 4 && !strcmp(d->d_name+ll-4, filesuffix)){ /* copy file name to temp buffer */ strncpy(emailaddr, d->d_name, sizeof(emailaddr)-1); emailaddr[sizeof(emailaddr)-1] = '\0'; /* chop off suffix trailier */ emailaddr[strlen(emailaddr)-4] = 0; /* * This is the separator between the contents of * different files. */ if(which == CACert){ if(!((BIO_puts(bio_out, CACERTSTORELEADER) > 0) && (BIO_puts(bio_out, emailaddr) > 0) && (BIO_puts(bio_out, "\n") > 0))) ret = -1; } else{ if(!((BIO_puts(bio_out, EMAILADDRLEADER) > 0) && (BIO_puts(bio_out, emailaddr) > 0) && (BIO_puts(bio_out, "\n") > 0))) ret = -1; } /* read then write contents of file */ build_path(file, srcpath, d->d_name, sizeof(file)); if(!(bio_in = BIO_new_file(file, "r"))) ret = -1; if(!ret){ int good_stuff = 0; while(BIO_gets(bio_in, line, sizeof(line)) > 0){ if(strncmp("-----BEGIN", line, strlen("-----BEGIN")) == 0) good_stuff = 1; if(good_stuff) BIO_puts(bio_out, line); if(strncmp("-----END", line, strlen("-----END")) == 0) good_stuff = 0; } } BIO_free(bio_in); } } closedir(dirp); } } BIO_free(bio_out); if(!ret){ if(ret_dir){ if(strlen(dstpath) + strlen(configcontainer) - strlen(ret_dir) + 1 < sizeof(dstpath)) snprintf(fpath, sizeof(fpath), "%s%c%s", dstpath, tempfile[strlen(ret_dir)], configcontainer); else ret = -1; } else ret = -1; if(!ret){ if(rename_file(tempfile, fpath) < 0){ q_status_message2(SM_ORDER, 3, 3, _("Can't rename %s to %s"), tempfile, fpath); ret = -1; } else q_status_message1(SM_ORDER, 3, 3, _("saved container to %s"), fpath); } /* if the container is remote, copy it */ if(!ret && IS_REMOTE(configpath)){ int e; char datebuf[200]; datebuf[0] = '\0'; if((e = rd_update_remote(rd, datebuf)) != 0){ if(e == -1){ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error opening temporary smime file %s: %s"), rd->lf, error_description(errno)); dprint((1, "write_remote_smime: error opening temp file %s\n", rd->lf ? rd->lf : "?")); } else{ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error copying to %s: %s"), rd->rn, error_description(errno)); dprint((1, "write_remote_smime: error copying from %s to %s\n", rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?")); } q_status_message(SM_ORDER | SM_DING, 5, 5, _("Copy of smime key to remote folder failed, NOT saved remotely")); } else{ rd_update_metadata(rd, datebuf); rd->read_status = 'W'; } rd_close_remdata(&rd); } } } if(tempfile) fs_give((void **) &tempfile); if(ret_dir) fs_give((void **) &ret_dir); if(configcontainer) fs_give((void **) &configcontainer); return ret; } /* * returns 0 on success, -1 on failure */ int copy_container_to_dir(WhichCerts which) { char path[MAXPATH+1], file[MAXPATH+1], buf[MAXPATH+1]; char iobuf[4096]; char *contents = NULL; char *leader = NULL; char *filesuffix = NULL; char *configdir = NULL; char *configpath = NULL; char *tempfile = NULL; char *p, *q, *line, *name, *certtext, *save_p; int len; BIO *in, *out; dprint((9, "copy_container_to_dir(%s)", which==Public ? "Public" : which==Private ? "Private" : which==CACert ? "CACert" : "?")); smime_init(); path[0] = '\0'; if(which == Public){ leader = EMAILADDRLEADER; contents = ps_global->smime->publiccontent; configdir = ps_global->VAR_PUBLICCERT_DIR; configpath = ps_global->smime->publicpath; filesuffix = ".crt"; if(!(configpath && configpath[0])){ #ifdef APPLEKEYCHAIN if(which == Public && F_ON(F_PUBLICCERTS_IN_KEYCHAIN, ps_global)){ q_status_message(SM_ORDER, 3, 3, _("Turn off the Keychain feature above first")); return -1; } #endif /* APPLEKEYCHAIN */ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined")); return -1; } fs_give((void **) &ps_global->smime->publicpath); path[0] = '\0'; if(!(smime_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH) && !IS_REMOTE(path))){ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined")); return -1; } if(can_access(path, ACCESS_EXISTS)){ if(our_mkpath(path, 0700)){ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path); return -1; } } ps_global->smime->publicpath = cpystr(path); configpath = ps_global->smime->publicpath; } else if(which == Private){ leader = EMAILADDRLEADER; contents = ps_global->smime->privatecontent; configdir = ps_global->VAR_PRIVATEKEY_DIR; configpath = ps_global->smime->privatepath; filesuffix = ".key"; if(!(configpath && configpath[0])){ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined")); return -1; } fs_give((void **) &ps_global->smime->privatepath); path[0] = '\0'; if(!(smime_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH) && !IS_REMOTE(path))){ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined")); return -1; } if(can_access(path, ACCESS_EXISTS)){ if(our_mkpath(path, 0700)){ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path); return -1; } } ps_global->smime->privatepath = cpystr(path); configpath = ps_global->smime->privatepath; } else if(which == CACert){ leader = CACERTSTORELEADER; contents = ps_global->smime->cacontent; configdir = ps_global->VAR_CACERT_DIR; configpath = ps_global->smime->capath; filesuffix = ".crt"; if(!(configpath && configpath[0])){ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined")); return -1; } fs_give((void **) &ps_global->smime->capath); path[0] = '\0'; if(!(smime_path(ps_global->VAR_CACERT_DIR, path, MAXPATH) && !IS_REMOTE(path))){ q_status_message(SM_ORDER, 3, 3, _("Directory is not defined")); return -1; } if(can_access(path, ACCESS_EXISTS)){ if(our_mkpath(path, 0700)){ q_status_message1(SM_ORDER, 3, 3, _("Can't create directory %s"), path); return -1; } } ps_global->smime->capath = cpystr(path); configpath = ps_global->smime->capath; } if(!(configdir && configdir[0])){ q_status_message(SM_ORDER, 3, 3, _("Directory not defined")); return -1; } if(!(configpath && configpath[0])){ q_status_message(SM_ORDER, 3, 3, _("Container path is not defined")); return -1; } if(!(filesuffix && strlen(filesuffix) == 4)){ return -1; } if(contents && *contents){ for(p = contents; *p != '\0';){ line = p; while(*p && *p != '\n') p++; save_p = NULL; if(*p == '\n'){ save_p = p; *p++ = '\0'; } if(strncmp(leader, line, strlen(leader)) == 0){ name = line + strlen(leader); certtext = p; if(strncmp("-----BEGIN", certtext, strlen("-----BEGIN")) == 0){ if((q = strstr(certtext, leader)) != NULL){ p = q; } else{ /* end of file */ q = certtext + strlen(certtext); p = q; } strncpy(buf, name, sizeof(buf)-5); buf[sizeof(buf)-5] = '\0'; strncat(buf, filesuffix, 5); build_path(file, configpath, buf, sizeof(file)); in = BIO_new_mem_buf(certtext, q-certtext); if(in){ tempfile = tempfile_in_same_dir(file, "az", NULL); out = NULL; if(tempfile) out = BIO_new_file(tempfile, "w"); if(out){ while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0) BIO_write(out, iobuf, len); BIO_free(out); if(rename_file(tempfile, file) < 0){ q_status_message2(SM_ORDER, 3, 3, _("Can't rename %s to %s"), tempfile, file); return -1; } fs_give((void **) &tempfile); } BIO_free(in); } } } if(save_p) *save_p = '\n'; } } return 0; } #ifdef APPLEKEYCHAIN int copy_publiccert_container_to_keychain(void) { /* NOT IMPLEMNTED */ return -1; } int copy_publiccert_keychain_to_container(void) { /* NOT IMPLEMNTED */ return -1; } #endif /* APPLEKEYCHAIN */ /* * Get a pointer to a string describing the most recent OpenSSL error. * It's statically allocated, so don't change or attempt to free it. */ static const char * openssl_error_string(void) { char *errs; const char *data = NULL; long errn; errn = ERR_peek_error_line_data(NULL, NULL, &data, NULL); errs = (char*) ERR_reason_error_string(errn); if(errs) return errs; else if(data) return data; return "unknown error"; } /* Return true if the body looks like a PKCS7 object */ int is_pkcs7_body(BODY *body) { int result; result = body->type==TYPEAPPLICATION && body->subtype && (strucmp(body->subtype,"pkcs7-mime")==0 || strucmp(body->subtype,"x-pkcs7-mime")==0 || strucmp(body->subtype,"pkcs7-signature")==0 || strucmp(body->subtype,"x-pkcs7-signature")==0); return result; } /* * Recursively stash a pointer to the decrypted data in our * manufactured body. * parameters: type: call of type 1, save the base and header for multipart messages call of type 0, do not save the base and header for multipart messages */ static void create_local_cache(char *h, char *base, BODY *b, int type) { if(b->type==TYPEMULTIPART){ PART *p; if(type == 1){ cpytxt(&b->mime.text, h+b->mime.offset, b->mime.text.size); cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes); } else if(type == 0){ /* * We don't really want to copy the real body contents. It shouldn't be * used, and in the case of a message with attachments, we'll be * duplicating the files multiple times. */ cpytxt(&b->contents.text, "BODY UNAVAILABLE", 16); for(p=b->nested.part; p; p=p->next) create_local_cache(h, base, (BODY *) p, type); } } else{ cpytxt(&b->mime.text, h+b->mime.offset, b->mime.text.size); cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes); } } static long rfc822_output_func(void *b, char *string) { BIO *bio = (BIO *) b; return(string ? *string ? (BIO_puts(bio, string) > 0 ? 1L : 0L) : (BIO_puts(bio, string) >= 0 ? 1L : 0L) : 0L); } /* * Attempt to load the private key for the given PERSONAL_CERT. * This sets the appropriate passphrase globals in order to * interact with the user correctly. */ static int load_private_key(PERSONAL_CERT *pcert) { if(!pcert->key){ /* Try empty password by default */ char *password = ""; if(ps_global->smime && (ps_global->smime->need_passphrase || ps_global->smime->entered_passphrase)){ /* We've already been in here and discovered we need a different password */ if(ps_global->smime->entered_passphrase) password = (char *) ps_global->smime->passphrase; /* already entered */ else return 0; } ERR_clear_error(); if(!(pcert->key = load_key(pcert, password))){ long err = ERR_get_error(); /* Couldn't load key... */ if(ps_global->smime && ps_global->smime->entered_passphrase){ /* The user got the password wrong maybe? */ if((ERR_GET_LIB(err)==ERR_LIB_EVP && ERR_GET_REASON(err)==EVP_R_BAD_DECRYPT) || (ERR_GET_LIB(err)==ERR_LIB_PEM && ERR_GET_REASON(err)==PEM_R_BAD_DECRYPT)) q_status_message(SM_ORDER | SM_DING, 4, 4, _("Incorrect passphrase")); else q_status_message1(SM_ORDER, 4, 4, _("Couldn't read key: %s"),(char*)openssl_error_string()); /* This passphrase is no good; forget it */ ps_global->smime->entered_passphrase = 0; } if(ps_global->smime){ /* Indicate to the UI that we need re-entry (see mailcmd.c:process_cmd())*/ ps_global->smime->need_passphrase = 1; if(ps_global->smime->passphrase_emailaddr){ int i; for(i = 0; ps_global->smime->passphrase_emailaddr[i] != NULL; i++) fs_give((void **)&ps_global->smime->passphrase_emailaddr[i]); fs_give((void **) ps_global->smime->passphrase_emailaddr); } ps_global->smime->passphrase_emailaddr = get_x509_subject_email(pcert->cert); } return 0; } else{ /* This key will be cached, so we won't be called again */ if(ps_global->smime){ ps_global->smime->entered_passphrase = 0; ps_global->smime->need_passphrase = 0; } } return 1; } return 0; } static void setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename, char *smime_type) { b->type = TYPEAPPLICATION; b->subtype = cpystr(type); b->encoding = ENCBINARY; b->description = cpystr(description); b->disposition.type = cpystr("attachment"); set_parameter(&b->disposition.parameter, "filename", filename); set_parameter(&b->parameter, "name", filename); if(smime_type && *smime_type) set_parameter(&b->parameter, "smime-type", smime_type); } /* * Look for a personal certificate matching the * given address */ PERSONAL_CERT * match_personal_cert_to_email(ADDRESS *a) { PERSONAL_CERT *pcert = NULL; char buf[MAXPATH]; char **email; int i, done; if(!a || !a->mailbox || !a->host) return NULL; snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host); if(ps_global->smime){ for(pcert=(PERSONAL_CERT *) ps_global->smime->personal_certs; pcert; pcert=pcert->next){ if(!pcert->cert) continue; email = get_x509_subject_email(pcert->cert); done = 0; if(email != NULL){ for(i = 0; email[i] && strucmp(email[i], buf) != 0; i++); if(email[i] != NULL) done++; for(i = 0; email[i] != NULL; i++) fs_give((void **)&email[i]); fs_give((void **)email); } if(done > 0) break; } } return pcert; } /* * Look for a personal certificate matching the from * (or reply_to? in the given envelope) */ PERSONAL_CERT * match_personal_cert(ENVELOPE *env) { PERSONAL_CERT *pcert; pcert = match_personal_cert_to_email(env->reply_to); if(!pcert) pcert = match_personal_cert_to_email(env->from); return pcert; } /* * Flatten the given body into its MIME representation. * Return the result in a BIO. */ static BIO * body_to_bio(BODY *body) { BIO *bio = NULL; int len; bio = BIO_new(BIO_s_mem()); if(!bio) return NULL; pine_encode_body(body); /* this attaches random boundary strings to multiparts */ pine_write_body_header(body, rfc822_output_func, bio); pine_rfc822_output_body(body, rfc822_output_func, bio); /* * Now need to truncate by two characters since the above * appends CRLF. */ if((len=BIO_ctrl_pending(bio)) > 1){ BUF_MEM *biobuf = NULL; BIO_get_mem_ptr(bio, &biobuf); if(biobuf){ BUF_MEM_grow(biobuf, len-2); /* remove CRLF */ } } return bio; } static BIO * bio_from_store(STORE_S *store) { BIO *ret = NULL; if(store && store->src == BioType && store->txt){ ret = (BIO *) store->txt; } return(ret); } /* * Encrypt file; given a path (char *) fp, replace the file * by an encrypted version of it. If (char *) text is not null, then * replace the text of (char *) fp by the encrypted version of (char *) text. * certpath is the FULL path to the file containing the certificate used for * encryption. */ int encrypt_file(char *fp, char *text, PERSONAL_CERT *pc) { const EVP_CIPHER *cipher = NULL; STACK_OF(X509) *encerts = NULL; BIO *out = NULL; PKCS7 *p7 = NULL; int rv = 0; if(pc == NULL) return 0; cipher = EVP_aes_256_cbc(); encerts = sk_X509_new_null(); sk_X509_push(encerts, X509_dup(pc->cert)); if(text){ if((out = BIO_new(BIO_s_mem())) == NULL) goto end; (void) BIO_reset(out); BIO_puts(out, text); } else{ if(!(out = BIO_new_file(fp, "rb"))) goto end; BIO_read_filename(out, fp); } if((p7 = PKCS7_encrypt(encerts, out, cipher, 0)) == NULL) goto end; BIO_set_close(out, BIO_CLOSE); BIO_free(out); if(!(out = BIO_new_file(fp, "w"))) goto end; BIO_reset(out); rv = PEM_write_bio_PKCS7(out, p7); BIO_flush(out); end: if(out != NULL) BIO_free(out); PKCS7_free(p7); sk_X509_pop_free(encerts, X509_free); return rv; } /* * Encrypt a message on the way out. Called from call_mailer in send.c * The body may be reallocated. */ int encrypt_outgoing_message(METAENV *header, BODY **bodyP) { PKCS7 *p7 = NULL; BIO *in = NULL; BIO *out = NULL; const EVP_CIPHER *cipher = NULL; STACK_OF(X509) *encerts = NULL; STORE_S *outs = NULL; PINEFIELD *pf; ADDRESS *a; BODY *body = *bodyP; BODY *newBody = NULL; int result = 0; X509 *cert; char buf[MAXPATH]; dprint((9, "encrypt_outgoing_message()")); smime_init(); cipher = EVP_aes_256_cbc(); encerts = sk_X509_new_null(); /* Look for a certificate for each of the recipients */ for(pf = header->local; pf && pf->name; pf = pf->next) if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr){ for(a=*pf->addr; a; a=a->next){ snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host); if((cert = get_cert_for(buf, Public)) != NULL){ sk_X509_push(encerts,cert); }else{ q_status_message2(SM_ORDER, 1, 1, _("Unable to find certificate for <%s@%s>"), a->mailbox, a->host); goto end; } } } /* add the sender's certificate so that they can decrypt the message too */ for(a=header->env->from; a ; a = a->next){ snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host); if((cert = get_cert_for(buf, Public)) != NULL && sk_X509_find(encerts, cert) == -1) sk_X509_push(encerts,cert); } in = body_to_bio(body); p7 = PKCS7_encrypt(encerts, in, cipher, 0); outs = so_get(BioType, NULL, EDIT_ACCESS); out = bio_from_store(outs); i2d_PKCS7_bio(out, p7); (void) BIO_flush(out); so_seek(outs, 0, SEEK_SET); newBody = mail_newbody(); newBody->type = TYPEAPPLICATION; newBody->subtype = cpystr("pkcs7-mime"); newBody->encoding = ENCBINARY; newBody->disposition.type = cpystr("attachment"); set_parameter(&newBody->disposition.parameter, "filename", "smime.p7m"); newBody->description = cpystr("S/MIME Encrypted Message"); set_parameter(&newBody->parameter, "smime-type", "enveloped-data"); set_parameter(&newBody->parameter, "name", "smime.p7m"); newBody->contents.text.data = (unsigned char *) outs; *bodyP = newBody; result = 1; end: BIO_free(in); PKCS7_free(p7); sk_X509_pop_free(encerts, X509_free); dprint((9, "encrypt_outgoing_message returns %d", result)); return result; } /* Get (and decode) the body of the given section of msg */ static STORE_S* get_part_contents(long msgno, const char *section) { long len; gf_io_t pc; STORE_S *store = NULL; char *err; store = so_get(CharStar, NULL, EDIT_ACCESS); if(store){ gf_set_so_writec(&pc,store); err = detach(ps_global->mail_stream, msgno, (char*) section, 0L, &len, pc, NULL, 0L); gf_clear_so_writec(store); so_seek(store, 0, SEEK_SET); if(err) so_give(&store); } return store; } static PKCS7 * get_pkcs7_from_part(long msgno,const char *section) { STORE_S *store = NULL; PKCS7 *p7 = NULL; BIO *in = NULL; store = get_part_contents(msgno, section); if(store){ if(store->src == CharStar){ int len; /* * We're reaching inside the STORE_S structure. We should * probably have a way to get the length, instead. */ len = (int) (store->eod - store->dp); in = BIO_new_mem_buf(store->txt, len); } else{ /* just copy it */ unsigned char c; in = BIO_new(BIO_s_mem()); (void) BIO_reset(in); so_seek(store, 0L, 0); while(so_readc(&c, store)){ BIO_write(in, &c, 1); } } if(in){ /* dump_bio_to_file(in, "/tmp/decoded-signature"); */ if((p7=d2i_PKCS7_bio(in,NULL)) == NULL){ /* error */ } BIO_free(in); } so_give(&store); } return p7; } int same_cert(X509 *x, X509 *cert) { char bufcert[256], bufx[256]; int rv = 0; get_fingerprint(cert, EVP_md5(), bufcert, sizeof(bufcert)); get_fingerprint(x, EVP_md5(), bufx, sizeof(bufx)); if(strcmp(bufx, bufcert) == 0) rv = 1; return rv; } /* extract and save certificates from a PKCS7 package. The ctype variable * tells us if we want to extract it to a public/ or a ca/ directory. The * later makes sense only for recoverable errors (errors that can be fixed * by saving to the ca/ directory before we verify the signature). * Return value: * 0 - no errors (in public/) no need to try again, * or validated self signed certificate (in ca/) * < 0 - certificate error is not recoverable, don't even think about it. */ int smime_extract_and_save_cert(PKCS7 *p7) { STACK_OF(X509) *signers; X509 *x, *cert; char **email; int i, j; if((signers = PKCS7_get0_signers(p7, NULL, 0)) == NULL) return -1; for(i = 0; i < sk_X509_num(signers); i++){ if((x = sk_X509_value(signers,i)) == NULL) continue; if((email = get_x509_subject_email(x)) != NULL){ for(j = 0; email[j] != NULL; j++){ if((cert = get_cert_for(email[j], Public)) == NULL || same_cert(x, cert) == 0) save_cert_for(email[j], x, Public); X509_free(cert); fs_give((void **) &email[i]); } fs_give((void **) email); } } sk_X509_free(signers); return 0; } /* * Try to verify a signature. * * p7 - the pkcs7 object to verify * in - the plain data to verify (NULL if not detached) * out - BIO to which to write the opaque data * silent - if non zero, do not print errors, only print success. */ static int do_signature_verify(PKCS7 *p7, BIO *in, BIO *out, int silent) { STACK_OF(X509) *otherCerts = NULL; CertList *cl; int result; int flags; const char *data; long err; if(!s_cert_store){ if(!silent) q_status_message(SM_ORDER | SM_DING, 2, 2, _("Couldn't verify S/MIME signature: No CA Certs were loaded")); return -1; } smime_extract_and_save_cert(p7); flags = F_ON(F_USE_CERT_STORE_ONLY, ps_global) ? PKCS7_NOINTERN : 0; if(ps_global->smime->publiccertlist == NULL){ renew_cert_data(&ps_global->smime->publiccertlist, Public); for(cl = ps_global->smime->publiccertlist; cl ; cl = cl->next){ if(cl->x509_cert == NULL){ char *s = strrchr(cl->name, '.'); *s = '\0'; cl->x509_cert = get_cert_for(cl->name, Public); *s = '.'; } } } if(ps_global->smime->publiccertlist){ otherCerts = sk_X509_new_null(); for(cl = ps_global->smime->publiccertlist; cl ; cl = cl->next) if(cl->x509_cert != NULL) sk_X509_push(otherCerts, X509_dup(cl->x509_cert)); } result = PKCS7_verify(p7, otherCerts, s_cert_store, in, out, flags); sk_X509_pop_free(otherCerts, X509_free); if(result){ q_status_message(SM_ORDER, 1, 1, _("S/MIME signature verified ok")); } else{ err = ERR_peek_error_line_data(NULL, NULL, &data, NULL); if(out && err==ERR_PACK(ERR_LIB_PKCS7,PKCS7_F_PKCS7_VERIFY,PKCS7_R_CERTIFICATE_VERIFY_ERROR)){ /* Retry verification so we can get the plain text */ /* Might be better to reimplement PKCS7_verify here? */ PKCS7_verify(p7, otherCerts, s_cert_store, in, out, PKCS7_NOVERIFY); } if (!silent) q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Couldn't verify S/MIME signature: %s"), (char*) openssl_error_string()); } return result; } void free_smime_body_sparep(void **sparep) { char *s; SIZEDTEXT *st; if(sparep && *sparep){ switch(get_smime_sparep_type(*sparep)){ case P7Type: PKCS7_free((PKCS7 *) get_smime_sparep_data(*sparep)); break; case CharType: s = (char *)get_smime_sparep_data(*sparep); fs_give((void **) &s); break; case SizedText : st = (SIZEDTEXT *)get_smime_sparep_data(*sparep); fs_give((void **) &st->data); fs_give((void **) &st); break; default : break; } ((SMIME_SPARE_S *)(*sparep))->data = NULL; fs_give(sparep); } } /* Big comment, explaining the mess that exists out there, and how we deal with it, and also how we solve the problems that are created this way. When Alpine sends a message, it constructs that message, computes the signature, but then it forgets the message it signed and reconstructs it again. Since it signs a message containing a notice about "mime aware tools", but it does not send that we do not include that in the part that is signed, and that takes care of much of the problems. Another problem is what is received from the servers. All servers tested seem to transmit the message that was signed intact and Alpine can check the signature correctly. That is not a problem. The problem arises when the message includes attachments. In this case different servers send different things, so it will be up to us to figure out what is the text that was actually signed. Confused? here is the story: When a message containing and attachment is sent by Alpine, UW-IMAP, Panda-IMAP, Gmail, and local reading of folders send exactly the message that was sent by Alpine, but GMX.com, Exchange, and probably other servers add a trailing \r\n in the message, so when validating the signature, these messages will not validate. There are several things that can be done. 1. Add a trailing \r\n to any message that contains attachments, sign that and send that. In this way, all messages will validate with all servers. 2. Compatibility mode: If a message has an attachment, contains a trailing \r\n and does not validate (sent by an earlier version of Alpine), remove the trailing \r\n and try to revalidate again. 3. We do not add \r\n to validate a message that we sent, because that would only work in Alpine, and not in any other client. That would not be a good thing to do. PART II Now we have to deal with encrypted and signed messages. The problem is that c-client makes all its pointers point to "on disk" content, but since we decrypted the data earlier, we have to make sure of two things. One is that we saved that data (so we do not have to decrypt it again) and second that we can use it. In order to save the data we use create_local_cache, so that we do not have to redecrypt the message. Once this is saved, c-client functions will find it and send it to us in mail_fetch_mime and mail_fetch_body. PART III When we are trying to verify messages with detached signatures, some imap servers send incorrect information in the mail_fetch_mime call. By incorrect I mean that this is not fetched directly from the message, but it is read from the message, processed, and then the processed part is sent to us, so this text might not agree with what is in the message, and so the validation of the signature might fail. However, the good news is that the message validates if saved to a local folder. This means that if normal validation does not work we can make it work by saving the message locally and validating that. This is implemented below, and causes delay in the display of the message. I am considering at this time not to do this automatically, but wait for the user to tell us to do it for them by means of a command available in the mail_view_screen. This might help in other situations, where a message is supposed to have an attachment, but it can not be seen in the processed text. Nevertheless, at this time, this is automatic, and is causing a delay in the processing of the message, but it is validating correctly all messages. PART IV When the user sends a message as encrypted and signed, this code used to encrypt first, and then sign the pkcs7 body, but it turns out that some other clients can not handle these messages. While we could argue that the other clients need to improve, we will support reading messages in both ways, and will send messages using this technique; that is, signed first, encrypted second. It seems that all tested clients support this way, so it should be safe to do so. */ /* * Given a multipart body of type multipart/signed, attempt to verify it. * Returns non-zero if the body was changed. */ static int do_detached_signature_verify(BODY *b, long msgno, char *section) { PKCS7 *p7 = NULL; BIO *in = NULL; PART *p; int result, modified_the_body = 0; int flag; /* 1 silent, 0 not silent */ unsigned long mimelen, bodylen; char newSec[100], *mimetext, *bodytext; char *what_we_did; SIZEDTEXT *st; dprint((9, "do_detached_signature_verify(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL")); smime_init(); /* if it was signed and then encrypted, use the decrypted text * to check the validity of the signature */ if(b->sparep){ if(get_smime_sparep_type(b->sparep) == SizedText){ /* bodytext includes mimetext */ st = (SIZEDTEXT *) get_smime_sparep_data(b->sparep); bodytext = (char *) st->data; bodylen = st->size; mimetext = NULL; mimelen = 0L; } } else{ snprintf(newSec, sizeof(newSec), "%s%s1", section ? section : "", (section && *section) ? "." : ""); mimetext = mail_fetch_mime(ps_global->mail_stream, msgno, (char*) newSec, &mimelen, 0); if(mimetext) bodytext = mail_fetch_body (ps_global->mail_stream, msgno, (char*) newSec, &bodylen, 0); if(mimetext == NULL || bodytext == NULL) return modified_the_body; } snprintf(newSec, sizeof(newSec), "%s%s2", section ? section : "", (section && *section) ? "." : ""); if((p7 = get_pkcs7_from_part(msgno, newSec)) == NULL || (in = BIO_new(BIO_s_mem())) == NULL) return modified_the_body; (void) BIO_reset(in); if(mimetext != NULL) BIO_write(in, mimetext, mimelen); BIO_write(in, bodytext, bodylen); /* Try compatibility with the past and check if this message * validates when we remove the last two characters. Silence * any failures first. */ flag = (bodylen <= 2 || strncmp(bodytext+bodylen-2, "\r\n", 2)) ? 0 : 1; if(((result = do_signature_verify(p7, in, NULL, flag)) == 0) && bodylen > 2 && (strncmp(bodytext+bodylen-2,"\r\n", 2) == 0)){ BUF_MEM *biobuf = NULL; BIO_get_mem_ptr(in, &biobuf); if(biobuf) BUF_MEM_grow(biobuf, mimelen + bodylen - 2); /* test one more time in case this is a remote connection and the * server massages the message to the point that it sends bogus * information. In this case, we fetch the message and we process * it by hand. */ flag = (mimelen == 0 || !IS_REMOTE(ps_global->mail_stream->mailbox)) ? 0 : 1; if((result = do_signature_verify(p7, in, NULL, flag)) == 0 && mimelen > 0 /* do not do this for encrypted messages */ && IS_REMOTE(ps_global->mail_stream->mailbox)){ char *fetch; unsigned long hlen, tlen; STORE_S *msg_so; BIO_free(in); if((in = BIO_new(BIO_s_mem())) != NULL && (fetch = mail_fetch_header(ps_global->mail_stream, msgno, NULL, NULL, &hlen, FT_PEEK)) != NULL && (msg_so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL && so_nputs(msg_so, fetch, (long) hlen) && (fetch = pine_mail_fetch_text(ps_global->mail_stream, msgno, NULL, &tlen, FT_PEEK)) != NULL && so_nputs(msg_so, fetch, tlen)){ STRING bs; char *h = (char *) so_text(msg_so); char *bstart = strstr(h, "\r\n\r\n"); ENVELOPE *env; BODY *body; bstart += 4; INIT(&bs, mail_string, bstart, tlen); rfc822_parse_msg_full(&env, &body, h, bstart-h, &bs, BADHOST, 0, 0); mail_free_envelope(&env); mail_free_body_part(&b->nested.part); b->nested.part = mail_body_section(body, section)->nested.part; create_local_cache(bstart, bstart, &b->nested.part->body, 1); modified_the_body = 1; snprintf(newSec, sizeof(newSec), "%s%s1", section ? section : "", (section && *section) ? "." : ""); mimetext = mail_fetch_mime(ps_global->mail_stream, msgno, (char*) newSec, &mimelen, 0); if(mimetext) bodytext = mail_fetch_body (ps_global->mail_stream, msgno, (char*) newSec, &bodylen, 0); if (mimetext == NULL || bodytext == NULL) return modified_the_body; snprintf(newSec, sizeof(newSec), "%s%s2", section ? section : "", (section && *section) ? "." : ""); if((p7 = get_pkcs7_from_part(msgno, newSec)) == NULL) return modified_the_body; (void) BIO_reset(in); BIO_write(in, mimetext, mimelen); BIO_write(in, bodytext, bodylen); so_give(&msg_so); if(((result = do_signature_verify(p7, in, NULL, 1)) == 0) && bodylen > 2 && (strncmp(bodytext+bodylen-2,"\r\n", 2) == 0)){ BUF_MEM *biobuf = NULL; BIO_get_mem_ptr(in, &biobuf); if(biobuf) BUF_MEM_grow(biobuf, mimelen + bodylen - 2); result = do_signature_verify(p7, in, NULL, 0); } } } } BIO_free(in); if(b->subtype) fs_give((void**) &b->subtype); b->subtype = cpystr(OUR_PKCS7_ENCLOSURE_SUBTYPE); b->encoding = ENC8BIT; if(b->description) fs_give ((void**) &b->description); what_we_did = result ? _("This message was cryptographically signed.") : _("This message was cryptographically signed but the signature could not be verified."); b->description = cpystr(what_we_did); b->sparep = create_smime_sparep(P7Type, p7); p = b->nested.part; /* p is signed plaintext */ if(p && p->next) mail_free_body_part(&p->next); /* hide the pkcs7 from the viewer */ modified_the_body = 1; return modified_the_body; } PERSONAL_CERT * find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri) { PERSONAL_CERT *x = NULL; if(ps_global->smime){ for(x = (PERSONAL_CERT *) ps_global->smime->personal_certs; x; x=x->next){ X509 *mine; mine = x->cert; if(!X509_NAME_cmp(ri->issuer_and_serial->issuer,mine->cert_info->issuer) && !ASN1_INTEGER_cmp(ri->issuer_and_serial->serial,mine->cert_info->serialNumber)){ break; } } } return x; } static PERSONAL_CERT * find_certificate_matching_pkcs7(PKCS7 *p7) { int i; STACK_OF(PKCS7_RECIP_INFO) *recips; PERSONAL_CERT *x = NULL; recips = p7->d.enveloped->recipientinfo; for(i=0; ikey == NULL ? -1 : 1; out = BIO_new(BIO_s_mem()); (void) BIO_reset(out); i = PKCS7_decrypt(p7, pc->key, pc->cert, out, 0); if(i == 0){ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"), (char*) openssl_error_string()); goto end; } BIO_get_mem_data(out, &tmp); text = cpystr(tmp); BIO_free(out); end: PKCS7_free(p7); return text; } /* * Try to decode (decrypt or verify a signature) a PKCS7 body * Returns non-zero if something was changed. */ static int do_decoding(BODY *b, long msgno, const char *section) { int modified_the_body = 0; BIO *out = NULL; PKCS7 *p7 = NULL; X509 *recip = NULL; EVP_PKEY *key = NULL; PERSONAL_CERT *pcert = NULL; char *what_we_did = ""; char null[1]; dprint((9, "do_decoding(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL")); null[0] = '\0'; smime_init(); /* * Extract binary data from part to an in-memory store */ if(b->sparep){ if(get_smime_sparep_type(b->sparep) == P7Type) p7 = (PKCS7*) get_smime_sparep_data(b->sparep); } else{ p7 = get_pkcs7_from_part(msgno, section && *section ? section : "1"); if(!p7){ q_status_message1(SM_ORDER, 2, 2, "Couldn't load PKCS7 object: %s", (char*) openssl_error_string()); goto end; } /* * Save the PKCS7 object for later dealings by the user interface. * It will be cleaned up when the body is garbage collected. */ b->sparep = create_smime_sparep(P7Type, p7); } dprint((1, "type_is_signed = %d, type_is_enveloped = %d", PKCS7_type_is_signed(p7), PKCS7_type_is_enveloped(p7))); if(PKCS7_type_is_signed(p7)){ int sigok; out = BIO_new(BIO_s_mem()); (void) BIO_reset(out); BIO_puts(out, "MIME-Version: 1.0\r\n"); /* needed so rfc822_parse_msg_full believes it's MIME */ sigok = do_signature_verify(p7, NULL, out, 0); what_we_did = sigok ? _("This message was cryptographically signed.") : _("This message was cryptographically signed but the signature could not be verified."); /* make sure it's null terminated */ BIO_write(out, null, 1); } else if(!PKCS7_type_is_enveloped(p7)){ q_status_message(SM_ORDER, 1, 1, "PKCS7 object not recognised."); goto end; } else{ /* It *is* enveloped */ int decrypt_result; what_we_did = _("This message was encrypted."); /* now need to find a cert that can decrypt this */ pcert = find_certificate_matching_pkcs7(p7); if(!pcert){ q_status_message(SM_ORDER, 3, 3, _("Couldn't find the certificate needed to decrypt.")); goto end; } recip = pcert->cert; if(!load_private_key(pcert) && ps_global->smime && ps_global->smime->need_passphrase && !ps_global->smime->already_auto_asked){ /* Couldn't load key with blank password, ask user */ ps_global->smime->already_auto_asked = 1; if(pith_opt_smime_get_passphrase){ (*pith_opt_smime_get_passphrase)(); load_private_key(pcert); } } key = pcert->key; if(!key) goto end; out = BIO_new(BIO_s_mem()); (void) BIO_reset(out); BIO_puts(out, "MIME-Version: 1.0\r\n"); decrypt_result = PKCS7_decrypt(p7, key, recip, out, 0); if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global)) forget_private_keys(); if(!decrypt_result){ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"), (char*) openssl_error_string()); goto end; } BIO_write(out, null, 1); } /* * We've now produced a flattened MIME object in BIO out. * It needs to be turned back into a BODY. */ if(out){ BODY *body; ENVELOPE *env; char *h = NULL; char *bstart; STRING s; BUF_MEM *bptr = NULL; BIO_get_mem_ptr(out, &bptr); if(bptr) h = bptr->data; /* look for start of body */ bstart = strstr(h, "\r\n\r\n"); if(!bstart){ q_status_message(SM_ORDER, 3, 3, _("Encrypted data couldn't be parsed.")); } else{ SIZEDTEXT *st; bstart += 4; /* skip over CRLF*2 */ INIT(&s, mail_string, bstart, strlen(bstart)); rfc822_parse_msg_full(&env, &body, h, bstart-h-2, &s, BADHOST, 0, 0); mail_free_envelope(&env); /* Don't care about this */ if(body->type == TYPEMULTIPART && !strucmp(body->subtype, "SIGNED")){ char *cookie = NULL; PARAMETER *param; for (param = body->parameter; param && !cookie; param = param->next) if (!strucmp (param->attribute,"BOUNDARY")) cookie = param->value; if(cookie != NULL){ st = fs_get(sizeof(SIZEDTEXT)); st->data = (void *) cpystr(bstart + strlen(cookie)+4); /* 4 = strlen("--\r\n") */ st->size = body->nested.part->next->body.mime.offset - 2*(strlen(cookie) + 4); body->sparep = create_smime_sparep(SizedText, (void *)st); } else q_status_message(SM_ORDER, 3, 3, _("Couldn't find cookie in attachment list.")); } body->mime.offset = 0; body->mime.text.size = 0; /* * Now convert original body (application/pkcs7-mime) * to a multipart body with one sub-part (the decrypted body). * Note that the sub-part may also be multipart! */ b->type = TYPEMULTIPART; if(b->subtype) fs_give((void**) &b->subtype); /* * This subtype is used in mailview.c to annotate the display of * encrypted or signed messages. We know for sure then that it's a PKCS7 * part because the sparep field is set to the PKCS7 object (see above). */ b->subtype = cpystr(OUR_PKCS7_ENCLOSURE_SUBTYPE); b->encoding = ENC8BIT; if(b->description) fs_give((void**) &b->description); b->description = cpystr(what_we_did); if(b->disposition.type) fs_give((void **) &b->disposition.type); if(b->contents.text.data) fs_give((void **) &b->contents.text.data); if(b->parameter) mail_free_body_parameter(&b->parameter); /* Allocate mem for the sub-part, and copy over the contents of our parsed body */ b->nested.part = fs_get(sizeof(PART)); b->nested.part->body = *body; b->nested.part->next = NULL; fs_give((void**) &body); /* * IMPORTANT BIT: set the body->contents.text.data elements to contain * the decrypted data. Otherwise, it'll try to load it from the original * data. Eek. */ create_local_cache(bstart-b->nested.part->body.mime.offset, bstart, &b->nested.part->body, 0); modified_the_body = 1; } } end: if(out) BIO_free(out); return modified_the_body; } /* * Recursively handle PKCS7 bodies in our message. * * Returns non-zero if some fiddling was done. */ static int do_fiddle_smime_message(BODY *b, long msgno, char *section) { int modified_the_body = 0; if(!b) return 0; dprint((9, "do_fiddle_smime_message(msgno=%ld type=%d subtype=%s section=%s)", msgno, b->type, b->subtype ? b->subtype : "NULL", (section && *section) ? section : (section != NULL) ? "Top" : "NULL")); if(is_pkcs7_body(b)){ if(do_decoding(b, msgno, section)){ /* * b should now be a multipart message: * fiddle it too in case it's been multiply-encrypted! */ /* fallthru */ modified_the_body = 1; } } if(b->type==TYPEMULTIPART || MIME_MSG(b->type, b->subtype)){ PART *p; int partNum; char newSec[100]; if(MIME_MULT_SIGNED(b->type, b->subtype)){ /* * Ahah. We have a multipart signed entity. * * Multipart/signed * part 1 (signed thing) * part 2 (the pkcs7 signature) * * We're going to convert that to * * Multipart/OUR_PKCS7_ENCLOSURE_SUBTYPE * part 1 (signed thing) * part 2 has been freed * * We also extract the signature from part 2 and save it * in the multipart body->sparep, and we add a description * in the multipart body->description. * * * The results of a decrypted message will be similar. It * will be * * Multipart/OUR_PKCS7_ENCLOSURE_SUBTYPE * part 1 (decrypted thing) */ modified_the_body += do_detached_signature_verify(b, msgno, section); } else if(MIME_MSG(b->type, b->subtype)){ modified_the_body += do_fiddle_smime_message(b->nested.msg->body, msgno, section); } else{ for(p=b->nested.part,partNum=1; p; p=p->next,partNum++){ /* Append part number to the section string */ snprintf(newSec, sizeof(newSec), "%s%s%d", section, *section ? "." : "", partNum); modified_the_body += do_fiddle_smime_message(&p->body, msgno, newSec); } } } return modified_the_body; } /* * Fiddle a message in-place by decrypting/verifying S/MIME entities. * Returns non-zero if something was changed. */ int fiddle_smime_message(BODY *b, long msgno) { return do_fiddle_smime_message(b, msgno, ""); } /********************************************************************************/ /* * Output a string in a distinctive style */ void gf_puts_uline(char *txt, gf_io_t pc) { pc(TAG_EMBED); pc(TAG_BOLDON); gf_puts(txt, pc); pc(TAG_EMBED); pc(TAG_BOLDOFF); } STACK_OF(X509) * get_chain_for_cert(X509 *cert, int *error) { STACK_OF(X509) *chain = NULL; X509_STORE_CTX *ctx; X509 *x, *xtmp; int rc; /* return code */ int level = -1; *error = 0; ERR_clear_error(); if((ctx = X509_STORE_CTX_new()) != NULL){ X509_STORE_set_flags(s_cert_store, 0); if(!X509_STORE_CTX_init(ctx, s_cert_store, cert, NULL)) *error = X509_STORE_CTX_get_error(ctx); else if((chain = sk_X509_new_null()) != NULL){ for(x = cert; ; x = xtmp){ if(++level > 0) sk_X509_push(chain, X509_dup(x)); rc = X509_STORE_CTX_get1_issuer(&xtmp, ctx, x); if(rc < 0) *error = 1; if(rc <= 0) break; if(!X509_check_issued(xtmp, xtmp)) break; } } if((*error && chain != NULL) || level == 0){ sk_X509_pop_free(chain, X509_free); chain = NULL; } X509_STORE_CTX_free(ctx); } return chain; } /* * Sign a message. Called from call_mailer in send.c. * * This takes the header for the outgoing message as well as a pointer * to the current body (which may be reallocated). */ int sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach) { STORE_S *outs = NULL; BODY *body = *bodyP; BODY *newBody = NULL; PART *p1 = NULL; PART *p2 = NULL; PERSONAL_CERT *pcert; BIO *in = NULL; BIO *out = NULL; PKCS7 *p7 = NULL; STACK_OF(X509) *chain; int result = 0, error; int flags = dont_detach ? 0 : PKCS7_DETACHED; dprint((9, "sign_outgoing_message()")); smime_init(); /* Look for a private key matching the sender address... */ pcert = match_personal_cert(header->env); if(!pcert){ q_status_message(SM_ORDER, 3, 3, _("Couldn't find the certificate needed to sign.")); goto end; } if(!load_private_key(pcert) && ps_global->smime && ps_global->smime->need_passphrase){ /* Couldn't load key with blank password, try again */ if(pith_opt_smime_get_passphrase){ (*pith_opt_smime_get_passphrase)(); load_private_key(pcert); } } if(!pcert->key) goto end; in = body_to_bio(body); chain = get_chain_for_cert(pcert->cert, &error); p7 = PKCS7_sign(pcert->cert, pcert->key, chain, in, flags); if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global)) forget_private_keys(); if(chain) sk_X509_pop_free(chain, X509_free); if(!p7){ q_status_message(SM_ORDER, 1, 1, _("Error creating signed object.")); goto end; } if(error) q_status_message(SM_ORDER, 1, 1, _("Not all certificates needed to verify signature included in signed message")); outs = so_get(BioType, NULL, EDIT_ACCESS); out = bio_from_store(outs); i2d_PKCS7_bio(out, p7); (void) BIO_flush(out); so_seek(outs, 0, SEEK_SET); if((flags&PKCS7_DETACHED)==0){ /* the simple case: the signed data is in the pkcs7 object */ newBody = mail_newbody(); setup_pkcs7_body_for_signature(newBody, "S/MIME Cryptographically Signed Message", "pkcs7-mime", "smime.p7m", "signed-data"); newBody->contents.text.data = (unsigned char *) outs; *bodyP = newBody; result = 1; } else{ /* * OK. * We have to create a new body as follows: * * multipart/signed; blah blah blah * reference to existing body * * pkcs7 object */ newBody = mail_newbody(); newBody->type = TYPEMULTIPART; newBody->subtype = cpystr("signed"); newBody->encoding = ENC7BIT; set_parameter(&newBody->parameter, "protocol", "application/pkcs7-signature"); set_parameter(&newBody->parameter, "micalg", "sha1"); p1 = mail_newbody_part(); p2 = mail_newbody_part(); /* * This is nasty. We're just copying the body in here, * but since our newBody is freed at the end of call_mailer, * we mustn't let this body (the original one) be freed twice. */ p1->body = *body; /* ARRGH. This is special cased at the end of call_mailer */ p1->next = p2; setup_pkcs7_body_for_signature(&p2->body, "S/MIME Cryptographic Signature", "pkcs7-signature", "smime.p7s", NULL); p2->body.contents.text.data = (unsigned char *) outs; newBody->nested.part = p1; *bodyP = newBody; result = 1; } end: PKCS7_free(p7); BIO_free(in); dprint((9, "sign_outgoing_message returns %d", result)); return result; } SMIME_STUFF_S * new_smime_struct(void) { SMIME_STUFF_S *ret = NULL; ret = (SMIME_STUFF_S *) fs_get(sizeof(*ret)); memset((void *) ret, 0, sizeof(*ret)); ret->publictype = Nada; return ret; } static void free_smime_struct(SMIME_STUFF_S **smime) { if(smime && *smime){ if((*smime)->passphrase_emailaddr){ int i; for(i = 0; (*smime)->passphrase_emailaddr[i] != NULL; i++) fs_give((void **) &(*smime)->passphrase_emailaddr[i]); fs_give((void **) (*smime)->passphrase_emailaddr); } if((*smime)->publicpath) fs_give((void **) &(*smime)->publicpath); if((*smime)->publiccertlist) free_certlist(&(*smime)->publiccertlist); if((*smime)->cacertlist) free_certlist(&(*smime)->cacertlist); if((*smime)->privatecertlist) free_certlist(&(*smime)->privatecertlist); if((*smime)->publiccontent) fs_give((void **) &(*smime)->publiccontent); if((*smime)->privatepath) fs_give((void **) &(*smime)->privatepath); if((*smime)->personal_certs){ PERSONAL_CERT *pc; pc = (PERSONAL_CERT *) (*smime)->personal_certs; free_personal_certs(&pc); (*smime)->personal_certs = NULL; } if((*smime)->privatecontent) fs_give((void **) &(*smime)->privatecontent); if((*smime)->capath) fs_give((void **) &(*smime)->capath); if((*smime)->cacontent) fs_give((void **) &(*smime)->cacontent); fs_give((void **) smime); } } #endif /* SMIME */