diff options
Diffstat (limited to 'pith')
-rw-r--r-- | pith/pine.hlp | 125 | ||||
-rw-r--r-- | pith/send.c | 8 | ||||
-rw-r--r-- | pith/smime.c | 206 |
3 files changed, 290 insertions, 49 deletions
diff --git a/pith/pine.hlp b/pith/pine.hlp index 3e713d7d..b296f024 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 60 2014-04-25 18:02:23 +Alpine Commit 61 2014-05-02 18:29:37 ============= h_news ================= <HTML> <HEAD> @@ -184,17 +184,25 @@ Additions include: <LI> Upgrade UW-IMAP to Panda IMAP from <A HREF="https://github.com/jonabbey/panda-imap">https://github.com/jonabbey/panda-imap</A>. <LI> S/MIME: Add screen to manage certificates. - <LI> S/MIME: sign messages using intermediate certificates when needed and possible. - <LI> S/MIME: validation of certificates for servers that modify signed content. + <LI> S/MIME: sign messages using intermediate certificates when needed + and possible. + <LI> S/MIME: validation of certificates for servers that modify signed + content. + <LI> S/MIME: signed and encrypted messages will be signed first and + encrypted second, so that they can be decoded by other clients. + <LI> S/MIME: add the sender certificate to the list of certificates in + encrypted messages to make it possible for the sender to decrypt + the message they sent. <LI> Add support to selective expunge through a subcommand of the select-apply commands. Read more in the <A HREF="h_index_cmd_expunge">help</A> for the expunge command. <LI> Pico: New subcommand of the search command, allows to reverse the direction of search. - <LI> If a password file is defined, and S/MIME is enabled, the key and - certificate used to encrypt the password file are saved in - the ~/.alpine-smime/.pwd directory, or in the directory specified - by the -pwdcertdir command line option. + <LI> Unix Alpine: If a password file is defined, and S/MIME is enabled, + the key and certificate used to encrypt the password file are saved + in the ~/.alpine-smime/.pwd directory, or in the directory specified + by the -pwdcertdir command line option. + <A HREF="h_password_file_support">Learn more</A>. <LI> Add /tls1, /tls1_1, /tls1_2 and /dtls1 to the definition of a server to use different ways to connect using ssl, for example {server.com/tls1} will attempt to connect to @@ -217,6 +225,9 @@ Additions include: <LI> Experimental: Write the content-type of a message in lowercase, as some non-compliant servers do not understand uppercase content-type, such as those of GMX.de. + <LI> Experimental: Do not send the RSET command before attempting + to send a message, as this causes a delay in some evily managed + servers. <LI> Opening a folder updates recent count in maildrops (this already works for other types of folders) <LI> Automatically redraw screen after opening an attachment @@ -283,7 +294,7 @@ Bugs that have been addressed include: $alpine_TCLINC instead of $alpine_TCLINC/tcl.h. Reported and fixed by Werner Scheinast. <LI> Move SSL configurations from UW-IMAP to configure script, and - update OpenSSL configuration for mac OSX. + update OpenSSL configuration for Mac OSX. <LI> Remove -lregex from linker flags when building --with-supplied-regex. </UL> <P> @@ -719,6 +730,104 @@ or instead you can find the Apache License, version 2.0 at the web URL: <End of Release Notes> </BODY> </HTML> +====== h_password_file_support ====== +<HTML> +<HEAD> +<TITLE>Encryption for Password File Support Explained</TITLE> +</HEAD> +<BODY> +<H1>Encryption for Password File Support Explained</H1> + +Index<BR> +<OL> +<LI><A HREF="#content">Explanation</A> +<LI><A HREF="#example">Example</A> +</OL> + +<P><A NAME="content">Unix Alpine Only.</A> + +<P> If your version of Alpine has been built with password file support +then you can use a special file to save your passwords, and avoid typing +them every time you open a connection to a remote server. + +<P> If your version of Alpine was built with SMIME support, and you have a +public certificate/private key pair, then Alpine will use such pair to +encrypt your password file. If you have more than one key/certificate +pair, Alpine will pick the first pair that it finds that works. You can also +select a pair, and the way to do this is explained below. + +<P> Once a pair has been chosen, it will be copied to the directory +~/.alpine-smime/.pwd, and from then on, Alpine will use the pair found in +that directory. The first time this process is done, this directory will +be created, a key/certificate pair will be copied to it, and this pair +will be used in the future to encrypt and decrypt your password file. You +can create this directory and copy any key/certificate pair there. You +can add a self-signed certificate there, if you like, and you can let +this certificate expire. This will not affect the encryption and decryption +of the password file. + +<P> If you prefer not to use the directory ~/.alpine-smime/.pwd to save +your key/certificate pair, you can specify a different one with the +-pwdcertdir command line option in Alpine. If the directory specified by +this option is not found or there is no valid key/certificate pair there, +Alpine will fail to encrypt and decrypt your password file. In other words, +Alpine will not initialize this directory for you. + +<P> Alpine does not care about the names of the key and certificates in +this directory, but the private key must have ".key" extension +and your public certificate must have the ".crt" extension. The +name of the private key will be used in the prompt when you are asked +to unlock your key to decrypt your password. + +<P><A NAME="example">An example follows</A> + +<P>Assume you have a private key called peter@address.com.key in your, +~/.alpine-smime/private directory, and a public certificate called +peter@address.com.crt in your ~/.alpine-smime/public directory, and these +are your only key/certificate pair. + +<P> When Alpine starts for the first time, without command line options, +it will check if the directory ~/.alpine-smime/.pwd exists, and if not, +it will create it. Then it will go through your keys and certificates and +find a pair that it can use, and copy the files peter@address.com.key, +and peter@address.com.crt to the ~/.alpine-smime/.pwd directory. Alternatively +you can do the same by copying these files by yourself. This can be done +with the sequence of commands + +<PRE> +mkdir ~/.alpine-smime/.pwd +cp ~/.alpine-smime/private/peter@address.com.key ~/.alpine-smime/.pwd +cp ~/.alpine-smime/public/peter@address.com.crt ~/.alpine-smime/.pwd +</PRE> + +<P> When Alpine starts, you will be asked the password to unlock your +private key with the prompt. + +<PRE> +Enter password of key <peter@address.com> to unlock password file: +</PRE> + +<P> If you prefer to use different names for your private and public keys +in the ~/.alpine-smime/.pwd directory, you can do so, but you must +preserve the extension of the files. For example, you can use the names +private_key.key and public_cert.crt instead. In this case, the prompt you +will see when you are asked to unlock your private key will read + +<PRE> +Enter password of key <private_key> to unlock password file: +</PRE> + +<P>Observe that you do not need to use an existing key/certificate pair, +and that you can create a new private key/public certificate pair to +encrypt and decrypt your password. However, once one is used, Alpine does +not provide a mechanism to switch the encryption and decryption files to +another key/certificate pair. This will be implemented in a future +release of Alpine. + +<P> +<End of help> +</BODY> +</HTML> ====== h_tls_failure_details ====== <HTML> <HEAD> diff --git a/pith/send.c b/pith/send.c index bd99d49a..4620d1aa 100644 --- a/pith/send.c +++ b/pith/send.c @@ -1768,12 +1768,12 @@ call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_serve result = 1; - if(ps_global->smime->do_encrypt) - result = encrypt_outgoing_message(header, &body); + if(ps_global->smime->do_sign) + result = sign_outgoing_message(header, &body, 0); /* need to free new body from encrypt if sign fails? */ - if(result && ps_global->smime->do_sign) - result = sign_outgoing_message(header, &body, ps_global->smime->do_encrypt); + if(result && ps_global->smime->do_encrypt) + result = encrypt_outgoing_message(header, &body); lmc.so = so; diff --git a/pith/smime.c b/pith/smime.c index 566e2be5..ce7b6a70 100644 --- a/pith/smime.c +++ b/pith/smime.c @@ -56,18 +56,19 @@ 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 *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); +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); @@ -89,6 +90,36 @@ static X509_STORE *s_cert_store; static int seeded = 0; static int egdsocket = 0; +typedef enum {P7Type, CharType, SizedText} SpareType; + +typedef struct smime_sparep_t { + SpareType sptype; + void *data; +} SMIME_SPARE_S; + +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 /* @@ -2041,7 +2072,7 @@ load_private_key(PERSONAL_CERT *pcert) static void -setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename) +setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *filename, char *smime_type) { b->type = TYPEAPPLICATION; b->subtype = cpystr(type); @@ -2052,6 +2083,8 @@ setup_pkcs7_body_for_signature(BODY *b, char *description, char *type, char *fil 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); } @@ -2238,6 +2271,8 @@ encrypt_outgoing_message(METAENV *header, BODY **bodyP) BODY *body = *bodyP; BODY *newBody = NULL; int result = 0; + X509 *cert; + char buf[MAXPATH]; dprint((9, "encrypt_outgoing_message()")); smime_init(); @@ -2250,13 +2285,9 @@ encrypt_outgoing_message(METAENV *header, BODY **bodyP) 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){ - X509 *cert; - char buf[MAXPATH]; - snprintf(buf, sizeof(buf), "%s@%s", a->mailbox, a->host); - cert = get_cert_for(buf, Public); - if(cert) + if((cert = get_cert_for(buf, Public)) != NULL) sk_X509_push(encerts,cert); else{ q_status_message2(SM_ORDER, 1, 1, @@ -2267,6 +2298,14 @@ encrypt_outgoing_message(METAENV *header, BODY **bodyP) } } + /* 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); @@ -2491,12 +2530,74 @@ do_signature_verify(PKCS7 *p7, BIO *in, BIO *out, int silent) void free_smime_body_sparep(void **sparep) { + char *s; + SIZEDTEXT *st; if(sparep && *sparep){ - PKCS7_free((PKCS7 *) (*sparep)); - *sparep = NULL; + 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); } } +/* return the mime header for this body part. Memory freed by caller */ +char * +smime_fetch_mime(BODY *b, MAILSTREAM *stream, long msgno, char * section, + unsigned long *mimelen) +{ + char *rv; + char newSec[100]; + + if(b->type == TYPEMULTIPART) + snprintf(newSec, sizeof(newSec), "%s%s1", section ? section : "", (section && *section) ? "." : ""); + else + strncpy(newSec, section, sizeof(newSec)); + newSec[sizeof(newSec)-1] = '\0'; + rv = mail_fetch_mime(stream, msgno, newSec, mimelen, 0); + return rv ? cpystr(rv) : NULL; +} + + +void +smime_write_body_header(BODY *b, MAILSTREAM *stream, long msgno, char *section, soutr_t f, void *s) +{ + char *rv; + char newSec[100]; + + if(b->nested.part == NULL) + return; + + if(b->nested.part->body.type == TYPEMULTIPART) + snprintf(newSec, sizeof(newSec), "%s%s1", section ? section : "", (section && *section) ? "." : ""); + else + strncpy(newSec, section, sizeof(newSec)); + newSec[sizeof(newSec)-1] = '\0'; + rv = mail_fetch_mime(stream, msgno, newSec, NULL, 0); + if(f && rv != NULL) + (*f)(s, rv); +} + +int +write_signed_body(BODY *b, MAILSTREAM *stream, long msgno, char *section, BIO *in) +{ + + smime_write_body_header(b, stream, msgno, section, rfc822_output_func, in); + + +} + + + /* 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. @@ -2569,7 +2670,7 @@ free_smime_body_sparep(void **sparep) * Given a multipart body of type multipart/signed, attempt to verify it. * Returns non-zero if the body was changed. */ -static int +int do_detached_signature_verify(BODY *b, long msgno, char *section) { PKCS7 *p7 = NULL; @@ -2579,29 +2680,44 @@ do_detached_signature_verify(BODY *b, long msgno, char *section) 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(); - 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 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 = 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; + 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; + if((p7 = get_pkcs7_from_part(msgno, newSec)) == NULL + || (in = BIO_new(BIO_s_mem())) == NULL) + return modified_the_body; (void) BIO_reset(in); - BIO_write(in, mimetext, mimelen); + if(mimetext != NULL) + BIO_write(in, mimetext, mimelen); BIO_write(in, bodytext, bodylen); /* Try compatibility with the past and check if this message @@ -2623,6 +2739,7 @@ do_detached_signature_verify(BODY *b, long msgno, char *section) * it by hand. */ if((result = do_signature_verify(p7, in, NULL, 1)) == 0 + && mimelen > 0 /* do not do this for encrypted messages */ && IS_REMOTE(ps_global->mail_stream->mailbox)){ char *fetch; unsigned long hlen, tlen; @@ -2645,7 +2762,7 @@ do_detached_signature_verify(BODY *b, long msgno, char *section) bstart += 4; INIT(&bs, mail_string, bstart, tlen); - rfc822_parse_msg_full(&env, &body, h, (bstart-h), &bs, BADHOST, 0, 0); + rfc822_parse_msg_full(&env, &body, h, bstart-h, &bs, BADHOST, 0, 0); mail_free_envelope(&env); mail_free_body_part(&b->nested.part); @@ -2654,6 +2771,7 @@ do_detached_signature_verify(BODY *b, long msgno, char *section) 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) @@ -2701,7 +2819,7 @@ do_detached_signature_verify(BODY *b, long msgno, char *section) b->description = cpystr(what_we_did); - b->sparep = p7; + b->sparep = create_smime_sparep(P7Type, p7); p = b->nested.part; @@ -2844,11 +2962,11 @@ do_decoding(BODY *b, long msgno, const char *section) * Extract binary data from part to an in-memory store */ - if(b->sparep){ /* already done */ - p7 = (PKCS7*) b->sparep; + 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", @@ -2860,7 +2978,7 @@ do_decoding(BODY *b, long msgno, const char *section) * Save the PKCS7 object for later dealings by the user interface. * It will be cleaned up when the body is garbage collected. */ - b->sparep = p7; + 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))); @@ -2927,8 +3045,7 @@ do_decoding(BODY *b, long msgno, const char *section) if(!decrypt_result){ q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"), (char*) openssl_error_string()); - goto end; - } + goto end; } BIO_write(out, null, 1); } @@ -2957,12 +3074,24 @@ do_decoding(BODY *b, long msgno, const char *section) 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); + 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; + 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); + } body->mime.offset = 0; body->mime.text.size = 0; @@ -3094,7 +3223,6 @@ do_fiddle_smime_message(BODY *b, long msgno, char *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); @@ -3141,6 +3269,7 @@ get_chain_for_cert(X509 *cert, int *error) X509_STORE_CTX *ctx; X509 *x, *xtmp; int rc; /* return code */ + int level = -1; *error = 0; ERR_clear_error(); @@ -3150,7 +3279,8 @@ get_chain_for_cert(X509 *cert, int *error) *error = X509_STORE_CTX_get_error(ctx); else if((chain = sk_X509_new_null()) != NULL){ for(x = cert; ; x = xtmp){ - sk_X509_push(chain, X509_dup(x)); + if(++level > 0) + sk_X509_push(chain, X509_dup(x)); rc = X509_STORE_CTX_get1_issuer(&xtmp, ctx, x); if(rc < 0) *error = 1; @@ -3160,8 +3290,10 @@ get_chain_for_cert(X509 *cert, int *error) break; } } - if(*error && chain != NULL) + if((*error && chain != NULL) || level == 0){ sk_X509_pop_free(chain, X509_free); + chain = NULL; + } X509_STORE_CTX_free(ctx); } return chain; @@ -3248,7 +3380,7 @@ sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach) newBody = mail_newbody(); - setup_pkcs7_body_for_signature(newBody, "S/MIME Cryptographically Signed Message", "pkcs7-mime", "smime.p7m"); + 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; @@ -3288,7 +3420,7 @@ sign_outgoing_message(METAENV *header, BODY **bodyP, int dont_detach) p1->next = p2; - setup_pkcs7_body_for_signature(&p2->body, "S/MIME Cryptographic Signature", "pkcs7-signature", "smime.p7s"); + 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; |