From e9554c597f7f33c6ebebaa47087b4db878a59913 Mon Sep 17 00:00:00 2001 From: Eduardo Chappa Date: Sun, 9 Mar 2014 14:26:50 -0600 Subject: * Forwarding messages with attachments of content-type multipart, failed when attempting to sign it, with and "Error writing pipe" error. * Using a .pinerc file outside the home directory made Alpine not find the .alpine-smime directory with certificates. * Configuration screen for S/MIME adds ability to manage certificates. (currently available to users who manage certificates in directories, not in containers, which will be available in the next alpha release.) --- alpine/alpine.c | 2 + alpine/confscroll.c | 2 +- alpine/conftype.h | 4 + alpine/keymenu.c | 88 +++++++- alpine/keymenu.h | 16 +- alpine/mailcmd.c | 40 ++++ alpine/mailcmd.h | 2 + alpine/smime.c | 463 ++++++++++++++++++++++++++++++++++++++---- alpine/smime.h | 1 + pith/conftype.h | 43 +++- pith/filter.c | 2 +- pith/options.h | 9 + pith/pine.hlp | 261 +++++++++++++++++++++++- pith/smime.c | 573 ++++++++++++++++++++++++++++++++++++++++++---------- pith/smime.h | 9 + pith/smkeys.c | 188 ++++++++++++++--- pith/smkeys.h | 11 +- 17 files changed, 1531 insertions(+), 183 deletions(-) diff --git a/alpine/alpine.c b/alpine/alpine.c index a6882145..8266c30e 100644 --- a/alpine/alpine.c +++ b/alpine/alpine.c @@ -188,7 +188,9 @@ main(int argc, char **argv) pith_opt_pretty_feature_name = pretty_feature_name; pith_opt_closing_stream = titlebar_stream_closing; pith_opt_current_expunged = mm_expunged_current; + pith_enter_password = alpine_get_password; #ifdef SMIME + pith_smime_import_certificate = smime_import_certificate; pith_opt_smime_get_passphrase = smime_get_passphrase; #endif #ifdef ENABLE_LDAP diff --git a/alpine/confscroll.c b/alpine/confscroll.c index 1e295c96..5ca2702b 100644 --- a/alpine/confscroll.c +++ b/alpine/confscroll.c @@ -3147,7 +3147,7 @@ update_option_screen(struct pine *ps, OPT_SCREEN_S *screen, Pos *cursor_pos) } value = (ctmp->flags & CF_INHERIT) ? INHERIT : ctmp->value; - dprint((1, "value = %s", ctmp->value)); + if(value){ char *p; int i, j; diff --git a/alpine/conftype.h b/alpine/conftype.h index fee9a75d..ccd5c4b6 100644 --- a/alpine/conftype.h +++ b/alpine/conftype.h @@ -60,6 +60,10 @@ typedef struct conf_line { struct flag_table **ftbl; /* address of start of table */ struct flag_table *fp; /* pointer into table for each row */ } f; + struct smime_data { + WhichCerts ctype; + int deleted; + } s; struct context_and_screen { CONTEXT_S *ct; CONT_SCR_S *cs; diff --git a/alpine/keymenu.c b/alpine/keymenu.c index 9ccba791..3e90f1b7 100644 --- a/alpine/keymenu.c +++ b/alpine/keymenu.c @@ -4,8 +4,8 @@ static char rcsid[] = "$Id: keymenu.c 1074 2008-06-04 00:08:43Z hubert@u.washing /* * ======================================================================== - * Copyright 2006-2008 University of Washington * Copyright 2013-2014 Eduardo Chappa + * Copyright 2006-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. @@ -2617,6 +2617,92 @@ struct key config_smime_helper_keys[] = ENDKEY_MENU}; INST_KEY_MENU(config_smime_helper_keymenu, config_smime_helper_keys); +struct key config_smime_manage_certs_menu_keys[] = + {HELP_MENU, + WHEREIS_MENU, + EXIT_SETUP_MENU, + {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE}, + {"I", N_("Import Cert"), {MC_IMPORT,1,{'i'}}, KS_NONE}, + NULL_MENU, + PREV_MENU, + NEXT_MENU, + PREVPAGE_MENU, + NEXTPAGE_MENU, + HOMEKEY_MENU, + ENDKEY_MENU}; +INST_KEY_MENU(config_smime_manage_certs_menu_keymenu, config_smime_manage_certs_menu_keys); + +struct key config_smime_add_certs_keys[] = + {HELP_MENU, + NULL_MENU, + EXIT_SETUP_MENU, + {"I", N_("Import Cert"), {MC_IMPORT,3,{'i', ctrl('M'), ctrl('J')}}, KS_NONE}, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU}; +INST_KEY_MENU(config_smime_add_certs_keymenu, config_smime_add_certs_keys); + +struct key config_smime_manage_certs_work_keys[] = + {HELP_MENU, + OTHER_MENU, + EXIT_SETUP_MENU, + {"V", "[" N_("View Info") "]", {MC_CHOICE,3,{'v',ctrl('M'),ctrl('J')}}, KS_NONE}, + {"I", N_("Import Cert"), {MC_IMPORT,1,{'i'}}, KS_NONE}, + NULL_MENU, + {"D", N_("Delete"), {MC_DELETE,1,{'d'}}, KS_NONE}, + {"U", N_("Undelete"), {MC_UNDELETE,1,{'u'}}, KS_NONE}, + {"X", N_("Expunge"), {MC_EXPUNGE,1,{'x'}}, KS_NONE}, + NULL_MENU, + NULL_MENU, + WHEREIS_MENU, + + PREV_MENU, + NEXT_MENU, + PREVPAGE_MENU, + NEXTPAGE_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + HOMEKEY_MENU, + ENDKEY_MENU}; +INST_KEY_MENU(config_smime_manage_certs_work_keymenu, config_smime_manage_certs_work_keys); + +struct key smime_certificate_info_keys[] = + {HELP_MENU, + OTHER_MENU, + {"E",N_("Exit Viewer"),{MC_EXIT,1,{'e'}},KS_EXITMODE}, + {"T",N_("Trust Cert"), {MC_TRUST,1,{'t'}},KS_NONE}, + {"D",N_("Delete"), {MC_DELETE,1,{'d'}},KS_NONE}, + {"U",N_("Undelete"), {MC_UNDELETE,1,{'u'}},KS_NONE}, + {"B",N_("Public Key"), {MC_PUBLIC,1,{'b'}},KS_NONE}, + {"R",N_("Private Key"),{MC_PRIVATE,1,{'r'}},KS_NONE}, + NULL_MENU, + NULL_MENU, + FWDEMAIL_MENU, + {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}, + + HELP_MENU, + OTHER_MENU, + PREVPAGE_MENU, + NEXTPAGE_MENU, + PRYNTTXT_MENU, + WHEREIS_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + HOMEKEY_MENU, + ENDKEY_MENU}; +INST_KEY_MENU(smime_certificate_info_keymenu, smime_certificate_info_keys); + /* * Internal prototypes diff --git a/alpine/keymenu.h b/alpine/keymenu.h index 0ce6b14c..a4cee7f3 100644 --- a/alpine/keymenu.h +++ b/alpine/keymenu.h @@ -216,6 +216,12 @@ struct key_menu { #define MC_QUOTA 803 #define MC_ADDHEADER 804 + +/* Commands for S/MIME screens */ +#define MC_TRUST 900 +#define MC_PUBLIC 901 +#define MC_PRIVATE 902 + /* * Some standard Key/Command Bindings */ @@ -549,7 +555,9 @@ struct key_menu { #define SMIME_PARENT_KEY 2 #define DECRYPT_KEY (VIEW_PIPE_KEY + 7) #define SECURITY_KEY (DECRYPT_KEY + 1) - +#define TRUST_KEY 3 +#define PUBLIC_KEY 6 +#define PRIVATE_KEY 7 extern struct key_menu cancel_keymenu, ab_keymenu, @@ -647,7 +655,11 @@ extern struct key_menu cancel_keymenu, take_export_keymenu_sm, take_export_keymenu_lm, config_smime_helper_keymenu, - smime_info_keymenu; + config_smime_add_certs_keymenu, + smime_info_keymenu, + config_smime_manage_certs_menu_keymenu, + config_smime_manage_certs_work_keymenu, + smime_certificate_info_keymenu; extern struct key rev_msg_keys[]; diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c index 1d252a38..a9fca5da 100644 --- a/alpine/mailcmd.c +++ b/alpine/mailcmd.c @@ -304,6 +304,46 @@ static ESCKEY_S flag_text_opt[] = { {-1, 0, NULL, NULL} }; +void +alpine_get_password(char *prompt, char *pass, size_t len) +{ + int rc, flags; + do { /* transform this to a (*pith_)() function */ + flags = OE_PASSWD | OE_DISALLOW_HELP; + pass[0] = '\0'; + rc = optionally_enter(pass, -FOOTER_ROWS(ps_global), 0, len, + prompt, NULL, NO_HELP, &flags); + } while (rc!=0 && rc!=1 && rc>0); +} + +int smime_import_certificate(char *filename, char *full_filename, size_t len) +{ + int r = 1; + static HISTORY_S *history = NULL; + static ESCKEY_S eopts[] = { + {ctrl('T'), 10, "^T", N_("To Files")}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}}; + + if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ + eopts[r].ch = ctrl('I'); + eopts[r].rval = 11; + eopts[r].name = "TAB"; + eopts[r].label = N_("Complete"); + } + + eopts[++r].ch = -1; + + filename[0] = '\0'; + full_filename[0] = '\0'; + + r = get_export_filename(ps_global, filename, NULL, full_filename, + len, "certificate", "IMPORT", eopts, NULL, + -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history); + + return r; +} + /*---------------------------------------------------------------------- The giant switch on the commands for index and viewing diff --git a/alpine/mailcmd.h b/alpine/mailcmd.h index 7f15d1f6..d26d321c 100644 --- a/alpine/mailcmd.h +++ b/alpine/mailcmd.h @@ -63,6 +63,8 @@ typedef enum {View, MsgIndx, ThrdIndx} CmdWhere; /* exported protoypes */ +void alpine_get_password(char *, char *, size_t); +int smime_import_certificate(char *, char *, size_t); int process_cmd(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere, int *); char *pretty_command(UCS); void bogus_command(UCS, char *); diff --git a/alpine/smime.c b/alpine/smime.c index b0bf16de..6d4ea49c 100644 --- a/alpine/smime.c +++ b/alpine/smime.c @@ -4,6 +4,7 @@ static char rcsid[] = "$Id: smime.c 1074 2008-06-04 00:08:43Z hubert@u.washingto /* * ======================================================================== + * Copyright 2013-2014 Eduardo Chappa * Copyright 2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,20 +42,24 @@ static char rcsid[] = "$Id: smime.c 1074 2008-06-04 00:08:43Z hubert@u.washingto #include "setup.h" #include "smime.h" - /* internal prototypes */ void format_smime_info(int pass, BODY *body, long msgno, gf_io_t pc); void print_separator_line(int percent, int ch, gf_io_t pc); void output_cert_info(X509 *cert, gf_io_t pc); void output_X509_NAME(X509_NAME *name, gf_io_t pc); void side_by_side(STORE_S *left, STORE_S *right, gf_io_t pc); -void get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen); STORE_S *wrap_store(STORE_S *in, int width); void smime_config_init_display(struct pine *, CONF_S **, CONF_S **); void revert_to_saved_smime_config(struct pine *ps, SAVED_CONFIG_S *vsave); SAVED_CONFIG_S *save_smime_config_vars(struct pine *ps); void free_saved_smime_config(struct pine *ps, SAVED_CONFIG_S **vsavep); int smime_helper_tool(struct pine *, int, CONF_S **, unsigned); +int smime_public_certs_tool(struct pine *, int, CONF_S **, unsigned); +void manage_certificates(struct pine *, WhichCerts); +void smime_manage_certs_init (struct pine *, CONF_S **, CONF_S **, WhichCerts, int); +void display_certificate_information(struct pine *, X509 *, char *, WhichCerts); +int manage_certs_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags); +int manage_certificate_info_tool(int, MSGNO_S *, SCROLL_S *); /* @@ -456,6 +461,7 @@ side_by_side(STORE_S *left, STORE_S *right, gf_io_t pc) STORE_S *right_wrapped; char buf_l[256]; char buf_r[256]; + char *l, *r; char *b; int i; int w = ps_global->ttyo->screen_cols/2 - 1; @@ -471,10 +477,9 @@ side_by_side(STORE_S *left, STORE_S *right, gf_io_t pc) for(;;){ - if(!so_fgets(left_wrapped, buf_l, sizeof(buf_l))) - break; - - if(!so_fgets(right_wrapped, buf_r, sizeof(buf_r))) + l = so_fgets(left_wrapped, buf_l, sizeof(buf_l)); + r = so_fgets(right_wrapped, buf_r, sizeof(buf_r)); + if(l == NULL && r == NULL) break; for(i=0, b=buf_l; i=maxLen) - break; - - if(i != 0) - *b++ = ':'; - - snprintf(b, maxLen - (b-buf), "%02x", md[i]); - b+=2; - } -} - - /* * Wrap the text in the given store to the given width. * A new store is created for the result. @@ -779,9 +758,11 @@ smime_config_init_display(struct pine *ps, CONF_S **ctmp, CONF_S **first_line) new_confline(ctmp); (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE; + for(i = 0; i < sizeof(tmp) && i < (ps->ttyo ? ps->ttyo->screen_cols : sizeof(tmp)); i++) + tmp[i] = '-'; new_confline(ctmp); (*ctmp)->flags |= CF_NOSELECT; - (*ctmp)->value = cpystr("---------------------------------------------------------------------------"); + (*ctmp)->value = cpystr(tmp); new_confline(ctmp); (*ctmp)->flags |= CF_NOSELECT; @@ -789,7 +770,7 @@ smime_config_init_display(struct pine *ps, CONF_S **ctmp, CONF_S **first_line) new_confline(ctmp); (*ctmp)->flags |= CF_NOSELECT; - (*ctmp)->value = cpystr("---------------------------------------------------------------------------"); + (*ctmp)->value = cpystr(tmp); new_confline(ctmp); (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE; @@ -867,8 +848,405 @@ smime_config_init_display(struct pine *ps, CONF_S **ctmp, CONF_S **first_line) (*ctmp)->varmem = 8; #endif /* APPLEKEYCHAIN */ + + new_confline(ctmp)->var = vtmp; + (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE; + + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT; + (*ctmp)->value = cpystr(tmp); + + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT; + (*ctmp)->value = cpystr(_("Manage your own certificates")); + + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT; + (*ctmp)->value = cpystr(tmp); + + new_confline(ctmp)->var = vtmp; + (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE; + + /* manage public certificates */ + new_confline(ctmp); + (*ctmp)->tool = smime_helper_tool; + (*ctmp)->keymenu = &config_smime_manage_certs_menu_keymenu; + (*ctmp)->help = h_config_smime_public_certificates; + (*ctmp)->value = cpystr(_("Manage Public Certificates")); + (*ctmp)->varmem = 9; + + /* manage private keys */ + new_confline(ctmp); + (*ctmp)->tool = smime_helper_tool; + (*ctmp)->keymenu = &config_smime_manage_certs_menu_keymenu; + (*ctmp)->help = h_config_smime_private_keys; + (*ctmp)->value = cpystr(_("Manage Private Keys")); + (*ctmp)->varmem = 10; + + /* manage Certificate Authorities */ + new_confline(ctmp); + (*ctmp)->tool = smime_helper_tool; + (*ctmp)->keymenu = &config_smime_manage_certs_menu_keymenu; + (*ctmp)->help = h_config_smime_certificate_authorities; + (*ctmp)->value = cpystr(_("Manage Certificate Authorities")); + (*ctmp)->varmem = 11; +} + +void display_certificate_information(struct pine *ps, X509 *cert, char *email, WhichCerts ctype) +{ + STORE_S *store; + SCROLL_S scrollargs; + int cmd, offset, i; + int pub_cert, priv_cert, new_store; + long error; + BIO *out = NULL; + + cmd = offset = pub_cert = priv_cert = 0; + new_store = 1; + ps->next_screen = SCREEN_FUN_NULL; + do { + /* MC_PRIVATE and MC_PUBLIC cancel each other, + * they can not be active at the same time + */ + switch(cmd){ + case MC_PRIVATE: + pub_cert = 0; + priv_cert = ++priv_cert % 2; + smime_certificate_info_keymenu.keys[PUBLIC_KEY].label = N_("Public Key"); + smime_certificate_info_keymenu.keys[PRIVATE_KEY].label = priv_cert ? N_("No Priv Key") : N_("Pivate Key"); + break; + + case MC_PUBLIC: + priv_cert = 0; + pub_cert = ++pub_cert % 2; + smime_certificate_info_keymenu.keys[PRIVATE_KEY].label = priv_cert ? N_("No Priv Key") : N_("Pivate Key"); + smime_certificate_info_keymenu.keys[PUBLIC_KEY].label = N_("Public Key"); + break; + + case MC_TRUST: + save_cert_for(email, cert, CACert); + renew_store(); + break; + + case MC_DELETE: + if (get_cert_deleted(ctype, email) != 0) + q_status_message(SM_ORDER, 1, 3, _("Certificate already deleted")); + else{ + mark_cert_deleted(ctype, email, 1); + q_status_message(SM_ORDER, 1, 3, _("Certificate marked deleted")); + } + break; + + case MC_UNDELETE: + if (get_cert_deleted(ctype, email) != 0) + q_status_message(SM_ORDER, 1, 3, _("Certificate not marked deleted")); + else{ + mark_cert_deleted(ctype, email, 0); + q_status_message(SM_ORDER, 1, 3, _("Certificate will not be deleted")); + } + break; + + default: break; + } + + if((pub_cert || priv_cert) + && (out = print_private_key_information(email, priv_cert)) == NULL) + q_status_message(SM_ORDER, 1, 3, _("Problem Reading Private Certificate Information")); + + if(new_store){ + store = so_get(CharStar, NULL, EDIT_ACCESS); + view_writec_init(store, NULL, HEADER_ROWS(ps), + HEADER_ROWS(ps) + ps->ttyo->screen_rows - (HEADER_ROWS(ps)+ FOOTER_ROWS(ps))); + + snprintf(tmp_20k_buf, SIZEOF_20KBUF,"%s", _("Certificate Information")); + gf_puts_uline(tmp_20k_buf, view_writec); + gf_puts(NEWLINE, view_writec); + print_separator_line(100, '-', view_writec); + + output_cert_info(cert, view_writec); + gf_puts(NEWLINE, view_writec); + + if(smime_validate_cert(cert, &error) < 0){ + const char *errorp = X509_verify_cert_error_string(error); + snprintf(tmp_20k_buf, SIZEOF_20KBUF,_("Error validating certificate: %s"), errorp); + } else + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s", _("Certificate validated without errors")); + + gf_puts_uline(tmp_20k_buf, view_writec); + gf_puts(NEWLINE, view_writec); + + if(out != NULL){ /* print private key information */ + unsigned char ch[2]; + + gf_puts(NEWLINE, view_writec); + ch[1] = '\0'; + while(BIO_read(out, ch, 1) >= 1) + gf_puts(ch, view_writec); + gf_puts(NEWLINE, view_writec); + q_status_message1(SM_ORDER, 1, 3, _("%s information shown at bottom of certificate information"), pub_cert ? _("Public") : _("Private")); + BIO_free_all(out); + out = NULL; + } + view_writec_destroy(); + new_store = 0; + } + + memset(&scrollargs, 0, sizeof(SCROLL_S)); + + scrollargs.text.text = so_text(store); + scrollargs.text.src = CharStar; + scrollargs.text.desc = "certificate information"; + scrollargs.body_valid = 1; + + if(offset){ /* resize? preserve paging! */ + scrollargs.start.on = Offset; + scrollargs.start.loc.offset = offset; + scrollargs.body_valid = 0; + offset = 0L; + } + + scrollargs.use_indexline_color = 1; + + scrollargs.bar.title = _("CERTIFICATE INFORMATION"); + scrollargs.proc.tool = manage_certificate_info_tool; + scrollargs.resize_exit = 1; + scrollargs.help.text = h_certificate_information; + scrollargs.help.title = _("HELP FOR MESSAGE TEXT VIEW"); + scrollargs.keys.what = FirstMenu; + scrollargs.keys.menu = &smime_certificate_info_keymenu; + setbitmap(scrollargs.keys.bitmap); + if(ctype != Public || error != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) + clrbitn(TRUST_KEY, scrollargs.keys.bitmap); + if(ctype != Private){ + clrbitn(PUBLIC_KEY, scrollargs.keys.bitmap); + clrbitn(PRIVATE_KEY, scrollargs.keys.bitmap); + } + + cmd = scrolltool(&scrollargs); + + switch(cmd){ + case MC_RESIZE : + case MC_PRIVATE: + case MC_PUBLIC : if(scrollargs.start.on == Offset) + offset = scrollargs.start.loc.offset; + new_store = 1; + default: break; + } + if(new_store) + so_give(&store); + } while (cmd != MC_EXIT); + ps->mangled_screen = 1; +} + +/* + * This is silly, we just need this function so that we can tell scrolltool + * that some commands are recognized. We use scrolltool because we do not + * want to rewrite output_cert_info. + */ +int +manage_certificate_info_tool(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms) +{ + int rv; + switch(cmd){ + case MC_DELETE: + case MC_UNDELETE: + case MC_PRIVATE: + case MC_PUBLIC: + case MC_TRUST: rv = 1; break; + default: rv = 0; break; + } + return rv; +} + + +int +manage_certs_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags) +{ + int rv = 0; + char *file, *ext; + char tmp[200], buf[MAXPATH], *path; + X509 *cert = NULL; + BIO *in; + WhichCerts ctype = (*cl)->d.s.ctype; + SMIME_STUFF_S *smime = ps->smime; + + switch(cmd){ + case MC_CHOICE: + if(PATHCERTDIR(ctype) == NULL) + return 0; + + if((cert = get_cert_for((*cl)->value+3, ctype)) == NULL){ + q_status_message(SM_ORDER, 1, 3, _("Problem Reading Certificate")); + rv = 0; + } + else{ + display_certificate_information(ps, cert, (*cl)->value+3, ctype); + rv = 10 + (*cl)->varmem; + } + break; + + case MC_DELETE: + if ((*cl)->d.s.deleted != 0) + q_status_message(SM_ORDER, 1, 3, _("Certificate already deleted")); + else{ + (*cl)->d.s.deleted = 1; + rv = 10 + (*cl)->varmem; /* forces redraw */ + mark_cert_deleted(ctype, (*cl)->value+3, 1); + q_status_message(SM_ORDER, 1, 3, _("Certificate marked deleted")); + } + break; + + case MC_UNDELETE: + if ((*cl)->d.s.deleted == 0) + q_status_message(SM_ORDER, 1, 3, _("Certificate not marked deleted")); + else{ + (*cl)->d.s.deleted = 0; + mark_cert_deleted(ctype, (*cl)->value+3, 0); + rv = 10 + (*cl)->varmem; /* forces redraw */ + q_status_message(SM_ORDER, 1, 3, _("Certificate will not be deleted")); + } + break; + + case MC_EXPUNGE: + { CertList *cl; + + for(cl = DATACERT(ctype); cl != NULL && DELETEDCERT(cl) == 0; cl = cl->next); + if(cl != NULL && DELETEDCERT(cl) != 0) + smime_expunge_cert(ctype); + else + q_status_message(SM_ORDER, 3, 3, _("No certificates marked deleted")); + rv = 10; /* forces redraw */ + break; + } + case MC_IMPORT: + import_certificate(ctype); + rv = 10; /* forces redraw */ + break; + + case MC_EXIT: + rv = config_exit_cmd(flags); + break; + + default: + rv = -1; + break; + } + + BIO_free(in); + X509_free(cert); + return rv; +} + +void smime_manage_certs_init(struct pine *ps, CONF_S **ctmp, CONF_S **first_line, WhichCerts ctype, int fline) +{ + char tmp[200]; + char *ext; + CertList *data; + int i; + + smime_init(); + + data = DATACERT(ctype); + ext = EXTCERT(ctype); + + if(data == NULL || RENEWCERT(data)) + renew_cert_data(&data, ctype); + + for(i = 0; i < sizeof(tmp) && i < (ps->ttyo ? ps->ttyo->screen_cols : sizeof(tmp)); i++) + tmp[i] = '-'; + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT; + (*ctmp)->value = cpystr(tmp); + + (*ctmp)->keymenu = &config_text_keymenu; + + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT; + sprintf(tmp, _("List of %scertificates"), ctype == Public ? _("public ") + : (ctype == Private ? _("private ") + : (ctype == CACert ? _("certificate authority ") : "unknown (?) "))); + (*ctmp)->value = cpystr(tmp); + + for(i = 0; i < sizeof(tmp) && i < (ps->ttyo ? ps->ttyo->screen_cols : sizeof(tmp)); i++) + tmp[i] = '-'; + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT; + (*ctmp)->value = cpystr(tmp); + + new_confline(ctmp); + (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE; + + if(data){ + CertList *cl; int i; + PERSONAL_CERT *pc; + + for(cl = data, i = 0; cl; cl = cl->next) + if(cl->name){ + char *s, *t; + + new_confline(ctmp); + (*ctmp)->d.s.ctype = ctype; + (*ctmp)->d.s.deleted = get_cert_deleted(ctype, cl->name); + (*ctmp)->tool = manage_certs_tool; + (*ctmp)->keymenu = &config_smime_manage_certs_work_keymenu; + (*ctmp)->varmem = i++; + (*ctmp)->help = ctype == Public ? h_config_smime_manage_public_menu + : (ctype == Private ? h_config_smime_manage_private_menu + : h_config_smime_manage_cacerts_menu); + snprintf(tmp, sizeof(tmp), " %s\t%s", (*ctmp)->d.s.deleted ? "D" : " ", cl->name); + (*ctmp)->value = cpystr(tmp); + for(s = (*ctmp)->value; s && (t = strstr(s, ext)) != NULL; s = t+1); + if(s) *(s-1) = '\0'; + if(i == fline+1 && first_line && !*first_line) + *first_line = *ctmp; + } + } + else { + new_confline(ctmp); + (*ctmp)->d.s.ctype = ctype; + (*ctmp)->tool = manage_certs_tool; + (*ctmp)->keymenu = &config_smime_add_certs_keymenu; + (*ctmp)->value = cpystr(_(" \tNo certificates found, press \"RETURN\" to add one.")); + if(first_line && !*first_line) + *first_line = *ctmp; + } } +void manage_certificates(struct pine *ps, WhichCerts ctype) +{ + OPT_SCREEN_S screen; + int ew, readonly_warning = 0, rv = 10, fline; + + dprint((9, "manage_certificates(ps, %s)", ctype == Public ? _("Public") : (ctype == Private ? _("Private") : (ctype == CACert ? _("certificate authority") : _("unknown"))))); + ps->next_screen = SCREEN_FUN_NULL; + + do { + CONF_S *ctmp = NULL, *first_line = NULL; + + fline = rv >= 10 ? rv - 10 : 0; + + smime_init(); + + smime_manage_certs_init(ps, &ctmp, &first_line, ctype, fline); + + if(ctmp == NULL){ + ps->mangled_screen = 1; + smime_deinit(); + return; + } + + memset(&screen, 0, sizeof(screen)); + screen.deferred_ro_warning = readonly_warning; + rv = conf_scroll_screen(ps, &screen, first_line, + _("MANAGE CERTIFICATES"), + /* TRANSLATORS: Print something1 using something2. + configuration is something1 */ + _("configuration"), 0); + } while (rv != 0); + + ps->mangled_screen = 1; + smime_deinit(); +} int smime_helper_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags) @@ -968,6 +1346,10 @@ smime_helper_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags) break; #endif /* APPLEKEYCHAIN */ + case 9: manage_certificates(ps, Public) ; break; + case 10: manage_certificates(ps, Private); break; + case 11: manage_certificates(ps, CACert) ; break; + default: rv = -1; break; @@ -979,6 +1361,15 @@ smime_helper_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags) rv = config_exit_cmd(flags); break; + case MC_IMPORT: + { WhichCerts ctype; + /* keep this selection consistent with the codes above */ + ctype = (*cl)->varmem == 9 ? Public + : ((*cl)->varmem == 10 ? Private : CACert); + rv = import_certificate(ctype); + } + break; + default: rv = -1; break; diff --git a/alpine/smime.h b/alpine/smime.h index 048a497c..d0367e0e 100644 --- a/alpine/smime.h +++ b/alpine/smime.h @@ -25,6 +25,7 @@ /* exported protoypes */ int smime_get_passphrase(void); +int smime_certificate_error_ask(int error); void smime_info_screen(struct pine *ps); void smime_config_screen(struct pine *, int edit_exceptions); int smime_related_var(struct pine *, struct variable *); diff --git a/pith/conftype.h b/pith/conftype.h index 091481d1..38097f9e 100644 --- a/pith/conftype.h +++ b/pith/conftype.h @@ -2,8 +2,8 @@ * $Id: conftype.h 1155 2008-08-21 18:33:21Z hubert@u.washington.edu $ * * ======================================================================== - * Copyright 2006-2008 University of Washington * Copyright 2013-2014 Eduardo Chappa + * Copyright 2006-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. @@ -670,11 +670,19 @@ typedef enum {Main, Post, None} EditWhich; typedef enum {Directory, Container, Keychain, Nada} SmimeHolderType; +typedef enum {Public, Private, CACert} WhichCerts; + +typedef struct certdata { + unsigned deleted:1; /* certificate is marked deleted */ + unsigned renew:1; /* we must renew this list, set at top cert */ +} CertData; + typedef struct certlist { char *name; void *x509_cert; /* this is type (X509 *) */ + CertData data; struct certlist *next; -}CertList; +} CertList; typedef struct smime_stuff { unsigned inited:1; @@ -690,25 +698,52 @@ typedef struct smime_stuff { * If we are using the Container type it is easiest if we * read in and maintain a list of certs and then write them * out all at once. For Directory type we just leave the data - * in the individual files and read or write the individual - * files when needed, so we don't have a list of all the certs. + * in the individual files and read the list of files in the + * directory. */ SmimeHolderType publictype; char *publicpath; char *publiccontent; + CertList *publiccertdata; CertList *publiccertlist; SmimeHolderType privatetype; char *privatepath; char *privatecontent; + CertList *privatecertdata; void *personal_certs; /* this is type (PERSONAL_CERT *) */ SmimeHolderType catype; char *capath; char *cacontent; + CertList *cacertdata; + CertList *cacertlist; } SMIME_STUFF_S; +#define DATACERT(X) (((X) == Public ? ps_global->smime->publiccertdata \ + : ((X) == Private ? ps_global->smime->privatecertdata \ + : ((X) == CACert ? ps_global->smime->cacertdata : NULL)))) + +#define PATHCERTDIR(X) (((X) == Public ? ps_global->smime->publicpath \ + : ((X) == Private ? ps_global->smime->privatepath \ + : ((X) == CACert ? ps_global->smime->capath : NULL)))) + +#define SMHOLDERTYPE(X) (((X) == Public ? ps_global->smime->publictype \ + : ((X) == Private ? ps_global->smime->privatetype \ + : ((X) == CACert ? ps_global->smime->catype : Nada)))) + +#define SMCERTLIST(X) (((X) == Public ? ps_global->smime->publiccertlist \ + : ((X) == Private ? ps_global->smime->privatecertdata \ + : ((X) == CACert ? ps_global->smime->cacertlist : NULL)))) + +#define EXTCERT(X) (((X) == Public ? ".crt" \ + : ((X) == Private ? ".key" \ + : ((X) == CACert ? ".crt" : NULL)))) + +#define DELETEDCERT(X) ((X)->data.deleted) +#define RENEWCERT(X) ((X)->data.renew) + #endif /* SMIME */ diff --git a/pith/filter.c b/pith/filter.c index 2800a3ba..d22b1215 100644 --- a/pith/filter.c +++ b/pith/filter.c @@ -11284,7 +11284,7 @@ gf_local_nvtnl(FILTER_S *f, int flg) GF_PUTC(f->next, '\015'); GF_PUTC(f->next, '\012'); } - else + else if(c != '\015') /* do not copy isolated \015 into source */ GF_PUTC(f->next, c); } diff --git a/pith/options.h b/pith/options.h index 0d5412a1..c452b97b 100644 --- a/pith/options.h +++ b/pith/options.h @@ -224,5 +224,14 @@ extern char *(*pith_opt_user_agent_prefix)(void); */ extern int (*pith_opt_smime_get_passphrase)(void); +/* + * Required call to interface for input of file to import. + */ +extern int (*pith_smime_import_certificate)(char *, char *, size_t); + +/* + * Required call to ask user to enter a password, with a given char * prompt + */ +extern void (*pith_enter_password)(char *, char *, size_t); #endif /* PITH_OPTIONS_INCLUDED */ diff --git a/pith/pine.hlp b/pith/pine.hlp index 66299dfa..6231a466 100644 --- a/pith/pine.hlp +++ b/pith/pine.hlp @@ -140,7 +140,7 @@ with help text for the config screen and the composer that didn't have any reasonable place to be called from. Dummy change to get revision in pine.hlp ============= h_revision ================= -Alpine Commit 47 2014-02-20 00:06:00 +Alpine Commit 48 2014-03-09 14:26:45 ============= h_news ================= @@ -187,6 +187,9 @@ Additions include: direction of search.
  • Upgrade UW-IMAP to Panda IMAP from https://github.com/jonabbey/panda-imap. +
  • S/MIME: When a signed message is received from a user whose + certificate has not been imported, should the certificate fail to + validate, the user will be shown a warning, and asked how to proceed.
  • Replace tabs by spaces in From and Subject fields to control for size in screen of these fields. Change only in index screen display.
  • Add support to selective expunge through a subcommand of the @@ -247,6 +250,7 @@ Bugs that have been addressed include:
  • S/MIME: Forwarding messages with multipart content-type failed to be signed with "Error writing pipe" message. Reported by Andreas Schamanek and Stefan Mueller. +
  • S/MIME: Certificates are lost when using a pinerc file outside of the home directory.
  • Crash when tcp connection to NNTP server was lost after connection had been established, but lost immediately afterwards.
  • WebAlpine: add _GNU_SOURCE to make pubcookie build. @@ -3834,6 +3838,9 @@ There are also additional details on
  • S/MIME: Transfer Private Keys to Directory
  • S/MIME: Transfer Public Certs to Container
  • S/MIME: Transfer Public Certs to Directory +
  • S/MIME: Manage Public Certificates +
  • S/MIME: Manage Private Keys +
  • S/MIME: Manage Certificate Authorities
  • Sort Command
  • Spell Check Command
  • Suspend Command @@ -34028,8 +34035,6 @@ Some limitations:
  • Because the implementation currently uses OpenSSL, there is only a very limited integration with the Mac OS Keychain (the storing and access of public certificates). -
  • There is no way to view or manipulate the lists of certificates from - within Alpine.

    The S/MIME configuration screen is reached by going to the Main Menu and typing @@ -34789,6 +34794,256 @@ the Keychain to store your public certs. <End of help on this topic> +====== h_config_smime_public_certificates ===== + + +S/MIME: Manage Public Certificates + + +

    S/MIME: Manage Public Certificates

    + +UNIX Alpine only. +

    +This menu item allows you to manage your public certificates, this +may include your own public certificate, but it normally includes +certificates of people you correspond with. These certificates are +saved by Alpine automatically when they are found in signed messages +that you receive. This interface allows you to manage them, by +giving you the option to delete them, or trust them (in the case +of self-signed certificates). + +

    +Please note that Alpine will not validate a message that was sent to you +using a self-signed certificate, unless you decide to trust that certificate. +Internally, a certificate is trusted by copying it to the +Certificate Authorities +collection. If you decide that you want to stop trusting a self-signed +certificate, you must delete such certificate from such collection. +

    +The I Import command available in this screen allows you to +import a command to this collection. +

    +

    +

    +

    +<End of help on this topic> + + +====== h_config_smime_private_keys ===== + + +S/MIME: Manage Private Keys + + +

    S/MIME: Manage Private Keys

    + +UNIX Alpine only. +

    +This option allows you to manage your private key. Normally a person has only +one key, in the same way that a person only has one valid passport, or ID card, +at any given time. This option allows you to manage private keys. You can +delete them or import them. Additionally, you can view information +about your public certificate, such as the issuer and the dates of validity +of such certificate, among others. + +

    +If you have more than one e-mail address for which you want to use the +same private key, you must add all those addresses to the private key at +the moment that the key is generated. When you receive a signed message using +a key generated for several e-mail addresses, Alpine will save a +certificate for each e-mail address included in such certificate. +

    +The I Import command available in this screen allows you to +import a command to this collection. +

    +

    +

    +

    +<End of help on this topic> + + +====== h_config_smime_certificate_authorities ===== + + +S/MIME: Manage Certificate Authorities + + +

    S/MIME: Manage Certificate Authorities

    + +UNIX Alpine only. +

    +This collection contains certificates that are needed to validate the +certificate of another person, and therefore contains certificates that +you trust. Typically a certificate is signed by another entity, called a +certificate authority. This option allows you to manage which certificates +you trust, allowing you to import them and to delete them or view information +about each certificate, such as the issuer and the dates of validity +of such certificate. +

    +The I Import command available in this screen allows you to +import a command to this collection. +

    +

    +

    +

    +<End of help on this topic> + + +====== h_certificate_information ===== + + +S/MIME: Certificate Information Screen + + +

    S/MIME: Certificate Information Screen

    + +UNIX Alpine only. +

    +The CERTIFICATE INFORMATION screen shows you information contained in a certificate +such as its owner, e-mail address, issuer, and interval of validity, +among others. +

    +In the case of public certificates, this screen shows you if there was a +failure when attempting to validate such message. If the certificate is +self-signed, then the T Trust command will be available, which +you can use to trust such certificate and make Alpine not fail validating +signatures signed with such certificate. +

    +You can also mark a certificate deleted, with the D command, or +remove the deleted mark with the U undelete command. +

    +In the case of your private key, Alpine shows you the information +from your public key. Additionally, Alpine allows you to see public +and private information about your key, with the B and +R commands respectively. +

    +

    +

    +<End of help on this topic> + + +====== h_config_smime_manage_public_menu ===== + + +S/MIME: Menu of Commands to Manage Public Certificates + + +

    S/MIME: Commands that Manage Public Certificates

    + +UNIX Alpine only. +

    +This screen allows you to manage your public certificates. Available commands and +a short description of what they do follows. +

      +
    • I Imports a public certificate to this collection. +
    • V View information about a certificate such as the name of the person the +certificate was issued to, its dates of validity, and validity status. +
    • D Marks a certificate deleted. +
    • U Removes the deletion mark on a certificate. +
    • X Removes all certificates marked deleted permanently (cannot be undone). +
    • T This command is only available for self-signed certificates, and allows you to +trust a certificate by copying it to the collection of trusted certificates. +
    +

    +All commands provide feedback to let you know about their success or failure. +

    +

    +

    +

    +<End of help on this topic> + + +====== h_config_smime_manage_private_menu ===== + + +S/MIME: Menu of Commands to Manage Private Keys + + +

    S/MIME: Commands that Manage Private Keys

    + +UNIX Alpine only. +

    +This screen allows you to manage your private key. Available commands and +a short description of what they do follows. +

      +
    • I Imports a new public key to this collection. +
    • V View information about the public certificate corresponding to this +key. +
    • D Marks a key to be deleted. +
    • U Removes the deletion mark on a key. +
    • X Removes all keys marked deleted permanently (cannot be undone). +Note that expunging a private key does not remove the public key, which must +be removed separately. +
    +

    +All commands provide feedback to let you know about their success or failure. +

    +

    +

    +

    +<End of help on this topic> + + +====== h_config_smime_manage_cacerts_menu ===== + + +S/MIME: Menu of Commands to Manage Certificate Authorities + + +

    S/MIME: Commands that Manage Certificate Authorities

    + +UNIX Alpine only. +

    +This screen allows you to manage your collection of certificates that you +trust. Available commands and a short description of what they do follows. +

      +
    • I Imports a trusted certificate to this collection. This is +done by reading the certificate and validating it. Once a certificate +is found to be valid, it is saved, adding the extension ".crt" +to the certificate, if necessary. +
    • V View information about this certificate, such as its issuer +and validity dates. +
    • D Marks a certificate to be deleted. +
    • U Removes the deletion mark on a certificate. +
    • X Removes all certificates marked deleted permanently (cannot be undone). +
    +

    +All commands provide feedback to let you know about their success or failure. +

    +

    +

    +

    +<End of help on this topic> + + ====== h_config_lame_list_mode ===== diff --git a/pith/smime.c b/pith/smime.c index 469a381e..e691ae68 100644 --- a/pith/smime.c +++ b/pith/smime.c @@ -45,20 +45,15 @@ static char rcsid[] = "$Id: smime.c 1176 2008-09-29 21:16:42Z hubert@u.washingto #include - -typedef enum {Public, Private, CACert} WhichCerts; - - /* 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 void smime_init(void); 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); static long rfc822_output_func(void *b, char *string); -static int load_private_key(PERSONAL_CERT *pcert); static void setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename); static BIO *body_to_bio(BODY *body); @@ -73,10 +68,14 @@ static void free_smime_struct(SMIME_STUFF_S **smime); static void setup_storage_locations(void); static int copy_dir_to_container(WhichCerts which); static int copy_container_to_dir(WhichCerts which); - +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); int (*pith_opt_smime_get_passphrase)(void); - +int (*pith_smime_import_certificate)(char *, char *, size_t); +char *(*pith_enter_password)(char *prompt, char *, size_t); static X509_STORE *s_cert_store; @@ -85,6 +84,189 @@ static int seeded = 0; static int egdsocket = 0; +int +import_certificate(WhichCerts ctype) +{ + int r = 1; + 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!")); + + smime_init(); + + r = (*pith_smime_import_certificate)(filename, full_filename, sizeof(filename) - 20); + if(r < 0){ + switch(r){ + default: + case -1: + cmd_cancelled("Import certificate"); + break; + + case -2: + q_status_message1(SM_ORDER, 0, 2, + _("Can't import certificate outside of %s"), + ps_global->VAR_OPER_DIR); + break; + } + } else if (ctype == Private){ + char prompt[500], *s, *t; + char pass[MAILTMPLEN+1]; + BIO *in; + EVP_PKEY *key = NULL; + PERSONAL_CERT *pc; + + if(!ps_global->smime->privatecertdata){ + ps_global->smime->privatecertdata = fs_get(sizeof(CertList)); + memset((void *)DATACERT(ctype), 0, sizeof(CertList)); + } + + if(!(in = BIO_new_file(full_filename, "r"))) + return -1; + + 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>: "), s ? s : filename); + + pass[0] = '\0'; + if(pith_enter_password) + (*pith_enter_password)(prompt, (char *)pass, sizeof(pass)); + + if((key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass)) != NULL){ + if(SMHOLDERTYPE(ctype) == Directory){ + STORE_S *in_cert, *out_cert; + char c; + + build_path(buf, PATHCERTDIR(ctype), filename, sizeof(buf)); + if(strcmp(buf + strlen(buf) - 4, EXTCERT(ctype)) != 0 && strlen(buf) + 4 < sizeof(buf)) + strcat(buf, EXTCERT(ctype)); + + in_cert = so_get(FileStar, full_filename, READ_ACCESS | READ_FROM_LOCALE); + out_cert = so_get(FileStar, buf, WRITE_ACCESS | WRITE_TO_LOCALE); + + if(in_cert != NULL && out_cert != NULL){ + while(so_readc(&c, in_cert) > 0) + so_writec(c, out_cert); + q_status_message(SM_ORDER, 1, 3, _("Certificate saved")); + } + else + q_status_message(SM_ORDER, 1, 3, _("Error saving certificate")); + + so_give(&in_cert); + so_give(&out_cert); + } + if(ps_global->smime->publiccertdata) + ps_global->smime->publiccertdata->data.renew = 1; + } + else + q_status_message(SM_ORDER, 1, 3, _("Problem unlocking key (not a certificate and/or wrong password)")); + BIO_free(in); + } 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){ + STORE_S *in_cert, *out_cert; + char c; + + build_path(buf, PATHCERTDIR(ctype), filename, sizeof(buf)); + if(strcmp(buf + strlen(buf) - 4, ".crt") != 0 && strlen(buf) + 4 < sizeof(buf)) + strcat(buf, EXTCERT(ctype)); + + in_cert = so_get(FileStar, full_filename, READ_ACCESS | READ_FROM_LOCALE); + out_cert = so_get(FileStar, buf, WRITE_ACCESS | WRITE_TO_LOCALE); + + if(in_cert != NULL && out_cert != NULL){ + while(so_readc(&c, in_cert) > 0) + so_writec(c, out_cert); + q_status_message(SM_ORDER, 1, 3, _("Certificate saved")); + } + else + q_status_message(SM_ORDER, 1, 3, _("Error saving certificate")); + + so_give(&in_cert); + so_give(&out_cert); + } + 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){ + 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); + X509_free(cert); + if(ps_global->smime->publiccertdata) + ps_global->smime->publiccertdata->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; + ps_global->mangled_screen = 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 */ @@ -116,6 +298,41 @@ forget_private_keys(void) } } +/* 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); + + *fpath = '\0'; + 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 @@ -216,6 +433,66 @@ app_RAND_write_file(const char *file) 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); + ps_global->smime->personal_certs = (void *) get_personal_certs(ps_global->smime->privatepath); + *data = certlist_from_personal_certs((PERSONAL_CERT *)ps_global->smime->personal_certs); + if(data && *data) + RENEWCERT(*data) = 0; + ps_global->smime->privatecertdata = (CertList *) *data; + } + if(ps_global->smime->privatecertdata) + RENEWCERT(ps_global->smime->privatecertdata) = 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{ + if(PATHCERTDIR(ctype)){ + free_certlist(data); + add_certs_in_dir(lookup, PATHCERTDIR(ctype), EXTCERT(ctype), data); + if(data && *data) + RENEWCERT(*data) = 0; + if(ctype == Public) + ps_global->smime->publiccertdata = *data; + else + ps_global->smime->cacertdata = *data; + } + } + } +} + + /* Installed as an atexit() handler to save the random data */ void @@ -226,9 +503,18 @@ smime_deinit(void) 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 */ -static void +void smime_init(void) { if(F_OFF(F_DONT_DO_SMIME, ps_global) && !(ps_global->smime && ps_global->smime->inited)){ @@ -254,6 +540,73 @@ smime_init(void) } +/* 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; +} + + static void setup_storage_locations(void) { @@ -275,7 +628,7 @@ setup_storage_locations(void) publiccertcontainer = 1; contents = NULL; path[0] = '\0'; - if(!signature_path(ps_global->VAR_PUBLICCERT_CONTAINER, path, MAXPATH)) + if(!smime_path(ps_global->VAR_PUBLICCERT_CONTAINER, path, MAXPATH)) publiccertcontainer = 0; if(publiccertcontainer && !IS_REMOTE(path) @@ -313,7 +666,7 @@ setup_storage_locations(void) ps_global->smime->publictype = Directory; path[0] = '\0'; - if(!(signature_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH) + 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)){ @@ -337,7 +690,7 @@ setup_storage_locations(void) privatekeycontainer = 1; contents = NULL; path[0] = '\0'; - if(!signature_path(ps_global->VAR_PRIVATEKEY_CONTAINER, path, MAXPATH)) + if(!smime_path(ps_global->VAR_PRIVATEKEY_CONTAINER, path, MAXPATH)) privatekeycontainer = 0; if(privatekeycontainer && !IS_REMOTE(path) @@ -377,7 +730,7 @@ setup_storage_locations(void) ps_global->smime->privatetype = Directory; path[0] = '\0'; - if(!(signature_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH) + 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)){ @@ -387,52 +740,8 @@ setup_storage_locations(void) } } - if(ps_global->smime->privatetype == Directory){ - 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); - - 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); - } - } - - ps_global->smime->personal_certs = result; + if(ps_global->smime->privatetype == Directory) + ps_global->smime->personal_certs = get_personal_certs(path); } /* extra cacerts in a container */ @@ -441,7 +750,7 @@ setup_storage_locations(void) cacertcontainer = 1; contents = NULL; path[0] = '\0'; - if(!signature_path(ps_global->VAR_CACERT_CONTAINER, path, MAXPATH)) + if(!smime_path(ps_global->VAR_CACERT_CONTAINER, path, MAXPATH)) cacertcontainer = 0; if(cacertcontainer && !IS_REMOTE(path) @@ -474,7 +783,7 @@ setup_storage_locations(void) ps_global->smime->catype = Directory; path[0] = '\0'; - if(!(signature_path(ps_global->VAR_CACERT_DIR, path, MAXPATH) + 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)){ @@ -598,7 +907,7 @@ copy_dir_to_container(WhichCerts which) * If there is a legit directory to read from set up the * container file to write to. */ - if(signature_path(configdir, srcpath, MAXPATH) && !IS_REMOTE(srcpath)){ + if(smime_path(configdir, srcpath, MAXPATH) && !IS_REMOTE(srcpath)){ if(IS_REMOTE(configpath)){ rd = rd_create_remote(RemImap, configpath, REMOTE_SMIME_SUBTYPE, @@ -829,7 +1138,7 @@ copy_container_to_dir(WhichCerts which) fs_give((void **) &ps_global->smime->publicpath); path[0] = '\0'; - if(!(signature_path(ps_global->VAR_PUBLICCERT_DIR, path, MAXPATH) + 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; @@ -859,7 +1168,7 @@ copy_container_to_dir(WhichCerts which) fs_give((void **) &ps_global->smime->privatepath); path[0] = '\0'; - if(!(signature_path(ps_global->VAR_PRIVATEKEY_DIR, path, MAXPATH) + 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; @@ -889,7 +1198,7 @@ copy_container_to_dir(WhichCerts which) fs_give((void **) &ps_global->smime->capath); path[0] = '\0'; - if(!(signature_path(ps_global->VAR_CACERT_DIR, path, MAXPATH) + 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; @@ -1342,7 +1651,7 @@ encrypt_file(char *fp, char *text) cipher = EVP_aes_256_cbc(); encerts = sk_X509_new_null(); - if((cert = get_cert_for(pcert->name)) != NULL) + if((cert = get_cert_for(pcert->name, Public)) != NULL) sk_X509_push(encerts, cert); else goto end; @@ -1414,7 +1723,7 @@ encrypt_outgoing_message(METAENV *header, BODY **bodyP) snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host); - cert = get_cert_for(buf); + cert = get_cert_for(buf, Public); if(cert) sk_X509_push(encerts,cert); else{ @@ -1545,6 +1854,88 @@ get_pkcs7_from_part(long msgno,const char *section) 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/) + * 1 - self signed certificate was saved in public/, try again to validate it + * 2 - user was prompted about self-signed certificate, but certificate was not saved + * < 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, rv; + long error; + + if((signers = PKCS7_get0_signers(p7, NULL, 0)) == NULL) + return -1; + + rv = 0; /* assume no error */ + 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); + + if(smime_validate_cert(cert, &error) < 0){ + const char *error_string = X509_verify_cert_error_string(error); + dprint((1, "Certificate verify error code %lu for <%s>", error, email[j])); + switch(error){ + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + rv = 2; /* recoverable error */ + break; + default : rv = -1; /* not recoverable error */ + break; + } + sprintf(tmp_20k_buf, "%s <%s>: %s", _("Error in certificate for "), email[j], error_string); + q_status_message(SM_ORDER | SM_DING, 2, 2, tmp_20k_buf); +#if 0 + if(pith_opt_action_certificate_error) + switch((*pith_opt_action_certificate_error)(cert_error)){ + case 1 : save_cert_for(email[j], x, CACert); + renew_store(); + rv = 0; + break; + case 0 : + default : break; + } +#endif + } + X509_free(cert); + } + fs_give((void **) &email[i]); + } + fs_give((void **) email); + } + sk_X509_free(signers); + + return rv; +} /* * Try to verify a signature. @@ -1558,15 +1949,10 @@ static int do_signature_verify(PKCS7 *p7, BIO *in, BIO *out, int silent) { STACK_OF(X509) *otherCerts = NULL; - int result; + int result; const char *data; long err; -#if 0 - if (in) - dump_bio_to_file(in,"/tmp/verified-data"); -#endif - 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")); @@ -1574,6 +1960,8 @@ do_signature_verify(PKCS7 *p7, BIO *in, BIO *out, int silent) return -1; } + smime_extract_and_save_cert(p7); + result = PKCS7_verify(p7, otherCerts, s_cert_store, in, out, 0); if(result){ @@ -1593,34 +1981,6 @@ do_signature_verify(PKCS7 *p7, BIO *in, BIO *out, int silent) _("Couldn't verify S/MIME signature: %s"), (char*) openssl_error_string()); } - /* now try to extract the certificates of any signers */ - { - STACK_OF(X509) *signers; - int i; - - if((signers = PKCS7_get0_signers(p7, NULL, 0)) != NULL) - for(i=0; ikey) == NULL) goto end; - recip = get_cert_for(pcert->name); + recip = get_cert_for(pcert->name, Public); out = BIO_new(BIO_s_mem()); (void) BIO_reset(out); @@ -2383,6 +2743,15 @@ free_smime_struct(SMIME_STUFF_S **smime) if((*smime)->publiccertlist) free_certlist(&(*smime)->publiccertlist); + if((*smime)->publiccertdata) + free_certlist(&(*smime)->publiccertdata); + + if((*smime)->privatecertdata) + free_certlist(&(*smime)->privatecertdata); + + if((*smime)->cacertdata) + free_certlist(&(*smime)->cacertdata); + if((*smime)->publiccontent) fs_give((void **) &(*smime)->publiccontent); diff --git a/pith/smime.h b/pith/smime.h index 5a89b372..a8ca8dd2 100644 --- a/pith/smime.h +++ b/pith/smime.h @@ -2,6 +2,7 @@ * $Id: smime.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $ * * ======================================================================== + * Copyright 2013-2014 Eduardo Chappa * Copyright 2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +32,7 @@ /* exported protoypes */ +int smime_validate_cert(X509 *cert, long *error); int encrypt_file(char *fp, char *text); char *decrypt_file(char *fp, int *rv); int is_pkcs7_body(BODY *b); @@ -40,7 +42,13 @@ void free_smime_body_sparep(void **sparep); int sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach); void gf_puts_uline(char *txt, gf_io_t pc); PERSONAL_CERT *find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri); +PERSONAL_CERT *get_personal_certs(char *path); +void smime_init(void); void smime_deinit(void); +void renew_store(void); +void renew_cert_data(CertList **data, WhichCerts ctype); +BIO *print_private_key_information(char *email, int itype); + SMIME_STUFF_S *new_smime_struct(void); int copy_publiccert_dir_to_container(void); int copy_publiccert_container_to_dir(void); @@ -52,6 +60,7 @@ int copy_cacert_container_to_dir(void); int copy_publiccert_container_to_keychain(void); int copy_publiccert_keychain_to_container(void); #endif /* APPLEKEYCHAIN */ +int import_certificate(WhichCerts); #endif /* PITH_SMIME_INCLUDED */ diff --git a/pith/smkeys.c b/pith/smkeys.c index e815a59a..7aac6e4c 100644 --- a/pith/smkeys.c +++ b/pith/smkeys.c @@ -4,6 +4,7 @@ static char rcsid[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washingt /* * ======================================================================== + * Copyright 2013-2014 Eduardo Chappa * Copyright 2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +31,7 @@ static char rcsid[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washingt #include "../pith/tempfile.h" #include "../pith/busy.h" #include "../pith/osdep/lstcmpnt.h" +#include "../pith/util.h" #include "smkeys.h" #ifdef APPLEKEYCHAIN @@ -42,10 +44,121 @@ static char rcsid[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washingt /* internal prototypes */ static char *emailstrclean(char *string); -static int add_certs_in_dir(X509_LOOKUP *lookup, char *path); static int certlist_to_file(char *filename, CertList *certlist); static int mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup); +/* smime_expunge_cert. + * Return values: < 0 there was an error. + * >=0 the number of messages expunged + */ +int +smime_expunge_cert(WhichCerts ctype) +{ + int count; + CertList *cl, *dummy, *data; + char *path, *ext, buf[MAXPATH+1]; + + 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); + ext = EXTCERT(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; + } + + build_path(buf, path, cl->next->name, sizeof(buf)); + if(ctype == Private && strlen(buf) + strlen(EXTCERT(Private)) < sizeof(buf)) + strcat(buf, EXTCERT(Private)); + + if(our_unlink(buf) < 0) + q_status_message1(SM_ORDER, 3, 3, _("Error removing certificate %s"), cl->next->name); + else { + 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_message1(SM_ORDER, 3, 3, _("Error removing certificate %s"), cl->name); + + switch(ctype){ + case Private: ps_global->smime->privatecertdata = dummy->next; break; + case Public : ps_global->smime->publiccertdata = dummy->next; break; + case CACert : ps_global->smime->cacertdata = dummy->next; break; + default : break; + } + fs_give((void **)&dummy); + 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, char *email, unsigned state) +{ + CertList *cl; + char tmp[200]; + + snprintf(tmp, sizeof(tmp), "%s%s", email, ctype == Private ? "" : ".crt"); + tmp[sizeof(tmp)-1] = '\0'; + for(cl = DATACERT(ctype); cl != NULL && strcmp(cl->name, tmp); cl = cl->next); + cl->data.deleted = state; +} + +unsigned +get_cert_deleted(WhichCerts ctype, char *email) +{ + CertList *cl; + + for(cl = DATACERT(ctype); cl != NULL && strcmp(cl->name, email); cl = cl->next); + return (cl && cl->data.deleted) ? 1 : 0; +} + +void +get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen) +{ + unsigned char md[128]; + char *b; + unsigned int len, i; + + len = sizeof(md); + + X509_digest(cert, type, md, &len); + + b = buf; + *b = 0; + for(i=0; i=maxLen) + break; + + if(i != 0) + *b++ = ':'; + + snprintf(b, maxLen - (b-buf), "%02x", md[i]); + b+=2; + } +} + /* * Remove leading whitespace, trailing whitespace and convert @@ -87,24 +200,36 @@ emailstrclean(char *string) /* * Add a lookup for each "*.crt" file in the given directory. */ -static int -add_certs_in_dir(X509_LOOKUP *lookup, char *path) +int +add_certs_in_dir(X509_LOOKUP *lookup, char *path, char *ext, CertList **cdata) { char buf[MAXPATH]; struct direct *d; DIR *dirp; + CertList *cert, *cl; int ret = 0; - dirp = opendir(path); - if(dirp){ - + if((dirp = opendir(path)) != NULL){ while(!ret && (d=readdir(dirp)) != NULL){ - if(srchrstr(d->d_name, ".crt")){ + if(srchrstr(d->d_name, ext)){ build_path(buf, path, d->d_name, sizeof(buf)); if(!X509_LOOKUP_load_file(lookup, buf, X509_FILETYPE_PEM)){ q_status_message1(SM_ORDER, 3, 3, _("Error loading file %s"), buf); ret = -1; + } else { + if(cdata){ + cert = fs_get(sizeof(CertList)); + memset((void *)cert, 0, sizeof(CertList)); + cert->name = cpystr(d->d_name); + if(*cdata == NULL) + *cdata = cert; + else{ + for (cl = *cdata; cl && cl->next; cl = cl->next); + cl->next = cert; + } + } + } } @@ -151,7 +276,7 @@ get_ca_store(void) } else if(ps_global->smime && ps_global->smime->catype == Directory && ps_global->smime->capath){ - if(add_certs_in_dir(lookup, ps_global->smime->capath) < 0){ + if(add_certs_in_dir(lookup, ps_global->smime->capath, ".crt", &ps_global->smime->cacertdata) < 0){ X509_STORE_free(store); return NULL; } @@ -298,14 +423,16 @@ get_x509_subject_email(X509 *x) * the email address that has come from the certificate. * * The argument email is destroyed. + * + * args: ctype says where the user wants to save the certificate */ void -save_cert_for(char *email, X509 *cert) +save_cert_for(char *email, X509 *cert, WhichCerts ctype) { - if(!ps_global->smime) + if(!ps_global->smime || ctype == Private) return; - dprint((9, "save_cert_for(%s)", email ? email : "?")); + dprint((9, "save_cert_for(%s, %s)", email ? email : "?", ctype == Public ? _("Public") : ctype == Private ? _("Private") : "CACert")); emailstrclean(email); if(ps_global->smime->publictype == Keychain){ @@ -354,20 +481,22 @@ save_cert_for(char *email, X509 *cert) else if(ps_global->smime->publictype == Container){ REMDATA_S *rd = NULL; char path[MAXPATH]; + char *upath = PATHCERTDIR(ctype); char *tempfile = NULL; int err = 0; add_to_end_of_certlist(&ps_global->smime->publiccertlist, email, X509_dup(cert)); - if(!ps_global->smime->publicpath) + if(!upath) return; - if(IS_REMOTE(ps_global->smime->publicpath)){ - rd = rd_create_remote(RemImap, ps_global->smime->publicpath, REMOTE_SMIME_SUBTYPE, + if(IS_REMOTE(upath)){ + rd = rd_create_remote(RemImap, upath, REMOTE_SMIME_SUBTYPE, NULL, "Error: ", _("Can't access remote smime configuration.")); - if(!rd) + if(!rd){ return; + } (void) rd_read_metadata(rd); @@ -419,7 +548,7 @@ save_cert_for(char *email, X509 *cert) path[sizeof(path)-1] = '\0'; } else{ - strncpy(path, ps_global->smime->publicpath, sizeof(path)-1); + strncpy(path, upath, sizeof(path)-1); path[sizeof(path)-1] = '\0'; } @@ -436,7 +565,7 @@ save_cert_for(char *email, X509 *cert) } } - if(!err && IS_REMOTE(ps_global->smime->publicpath)){ + if(!err && IS_REMOTE(upath)){ int e, we_cancel; char datebuf[200]; @@ -479,11 +608,11 @@ save_cert_for(char *email, X509 *cert) } } else if(ps_global->smime->publictype == Directory){ + char *path = PATHCERTDIR(ctype); char certfilename[MAXPATH]; BIO *bio_out; - build_path(certfilename, ps_global->smime->publicpath, - email, sizeof(certfilename)); + build_path(certfilename, path, email, sizeof(certfilename)); strncat(certfilename, ".crt", sizeof(certfilename)-1-strlen(certfilename)); certfilename[sizeof(certfilename)-1] = 0; @@ -505,7 +634,7 @@ save_cert_for(char *email, X509 *cert) * The caller should free the cert. */ X509 * -get_cert_for(char *email) +get_cert_for(char *email, WhichCerts ctype) { char *path; char certfilename[MAXPATH]; @@ -516,8 +645,10 @@ get_cert_for(char *email) if(!ps_global->smime) return cert; - dprint((9, "get_cert_for(%s)", email ? email : "?")); + dprint((9, "get_cert_for(%s, %s)", email ? email : "?", "none yet")); + if(ctype == Private) /* there is no private certificate info */ + ctype = Public; /* return public information instead */ strncpy(emailaddr, email, sizeof(emailaddr)-1); emailaddr[sizeof(emailaddr)-1] = 0; @@ -585,23 +716,20 @@ get_cert_for(char *email) #endif /* APPLEKEYCHAIN */ } - else if(ps_global->smime->publictype == Container){ - if(ps_global->smime->publiccertlist){ + else if(SMHOLDERTYPE(ctype) == Container){ CertList *cl; - for(cl = ps_global->smime->publiccertlist; cl; cl = cl->next){ + for(cl = SMCERTLIST(ctype); cl; cl = cl->next){ if(cl->name && !strucmp(emailaddr, cl->name)) break; } if(cl) cert = X509_dup((X509 *) cl->x509_cert); - } } - else if(ps_global->smime->publictype == Directory){ - path = ps_global->smime->publicpath; - build_path(certfilename, path, emailaddr, sizeof(certfilename)); - strncat(certfilename, ".crt", sizeof(certfilename)-1-strlen(certfilename)); + else if(SMHOLDERTYPE(ctype) == Directory){ + build_path(certfilename, PATHCERTDIR(ctype), emailaddr, sizeof(certfilename)); + strncat(certfilename, EXTCERT(ctype), sizeof(certfilename)-1-strlen(certfilename)); certfilename[sizeof(certfilename)-1] = 0; if((in = BIO_new_file(certfilename, "r"))!=0){ @@ -642,7 +770,7 @@ mem_to_personal_certs(char *contents) if(strncmp(EMAILADDRLEADER, line, strlen(EMAILADDRLEADER)) == 0){ name = line + strlen(EMAILADDRLEADER); - cert = get_cert_for(name); + cert = get_cert_for(name, Public); keytext = p; /* advance p past this record */ diff --git a/pith/smkeys.h b/pith/smkeys.h index d3c9031f..818f46ac 100644 --- a/pith/smkeys.h +++ b/pith/smkeys.h @@ -2,6 +2,7 @@ * $Id: smkeys.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $ * * ======================================================================== + * Copyrighr 2013-2014 Eduardo Chappa * Copyright 2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,10 +45,11 @@ typedef struct personal_cert { /* exported protoypes */ +int add_certs_in_dir(X509_LOOKUP *lookup, char *path, char *ext, CertList **cdata); X509_STORE *get_ca_store(void); PERSONAL_CERT *get_personal_certs(char *d); -X509 *get_cert_for(char *email); -void save_cert_for(char *email, X509 *cert); +X509 *get_cert_for(char *email, WhichCerts ctype); +void save_cert_for(char *email, X509 *cert, WhichCerts ctype); char **get_x509_subject_email(X509 *x); EVP_PKEY *load_key(PERSONAL_CERT *pc, char *pass); CertList *mem_to_certlist(char *contents); @@ -55,7 +57,10 @@ void add_to_end_of_certlist(CertList **cl, char *name, X509 *cert); void free_certlist(CertList **cl); PERSONAL_CERT *mem_to_personal_certs(char *contents); void free_personal_certs(PERSONAL_CERT **pc); - +void get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen); +void mark_cert_deleted(WhichCerts ctype, char *email, unsigned state); +unsigned get_cert_deleted(WhichCerts ctype, char *email); +int smime_expunge_cert(WhichCerts ctype); #endif /* PITH_SMKEYS_INCLUDED */ #endif /* SMIME */ -- cgit v1.2.3-54-g00ecf