* S/MIME: When reading a local certificate, Alpine converts the name of
[alpine.git] / pith / smkeys.c
blobce7ad8737a4077c45b1bb23dcb2cd1d32e8e1258
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2015 Eduardo Chappa
8 * Copyright 2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
20 * This is based on a contribution from Jonathan Paisley, see smime.c
24 #include "../pith/headers.h"
26 #ifdef SMIME
28 #include "../pith/status.h"
29 #include "../pith/conf.h"
30 #include "../pith/remote.h"
31 #include "../pith/tempfile.h"
32 #include "../pith/busy.h"
33 #include "../pith/osdep/lstcmpnt.h"
34 #include "../pith/util.h"
35 #include "../pith/mailindx.h"
36 #include "../pith/readfile.h"
37 #include "smkeys.h"
39 #ifdef APPLEKEYCHAIN
40 #include <Security/SecKeychain.h>
41 #include <Security/SecKeychainItem.h>
42 #include <Security/SecKeychainSearch.h>
43 #include <Security/SecCertificate.h>
44 #endif /* APPLEKEYCHAIN */
47 /* internal prototypes */
48 static char *emailstrclean(char *string);
49 static int mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup);
50 int compare_certs_by_name(const void *data1, const void *data2);
52 #define SMIME_BACKUP_DIR ".backup"
53 #define MAX_TRY_BACKUP 100
55 /* return value: 0 - success, -1 error
56 * Call this function after setting up paths in ps_global->smime
57 * and reading certificates names in certlist.
59 int
60 setup_certs_backup_by_type(WhichCerts ctype)
62 int rv = 0; /* assume success */
63 int len;
64 int i, done;
65 char *d;
66 char p[MAXPATH+1]; /* path to where the backup is */
67 char buf[MAXPATH+1], buf2[MAXPATH+1];
68 struct stat sbuf;
69 CertList *data, *cl;
70 DIR *dirp;
71 struct dirent *df; /* file in the directory */
72 CertList *cert, *cl2;
73 X509 *x;
74 BIO *in;
76 return rv; /* remove when this function is complete */
78 if(SMHOLDERTYPE(ctype) == Directory){
79 d = PATHCERTDIR(ctype);
80 if(d != NULL){
81 len = strlen(d) + strlen(S_FILESEP) + strlen(SMIME_BACKUP_DIR) + 1;
82 snprintf(p, MAXPATH, "%s%s%s", d, S_FILESEP, SMIME_BACKUP_DIR);
83 p[MAXPATH] = '\0';
84 if(our_stat(p, &sbuf) < 0){
85 if(our_mkpath(p, 0700) != 0)
86 return -1;
87 } else if((sbuf.st_mode & S_IFMT) != S_IFDIR){
88 for(i = 0, done = 0; done == 0 && i < MAX_TRY_BACKUP; i++){
89 snprintf(buf2, len+2, "%s%d", p, i);
90 if(our_stat(buf2, &sbuf) < 0){
91 if(our_mkpath(buf2, 0700) == 0)
92 done++;
94 else if((sbuf.st_mode & S_IFMT) == S_IFDIR)
95 done++;
96 if(done){
97 strncpy(p, buf2, MAXPATH);
98 p[MAXPATH] = '\0';
101 if(done == 0)
102 return -1;
104 /* if we are here, we have a backup directory where to
105 * backup certificates/keys, so now we will go
106 * through the list of certificates and back them up
107 * if we need to.
109 data = BACKUPDATACERT(ctype);
110 for(cl = DATACERT(ctype); cl; cl = cl->next){
111 char clname[MAXPATH+1];
113 snprintf(clname, MAXPATH, "%s%s", cl->name, ctype == Private ? ".key" : "");
114 clname[MAXPATH] = '\0';
115 len = strlen(d) + strlen(clname) + 2;
116 if(len < MAXPATH){
117 snprintf(buf, len, "%s%s%s", d, S_FILESEP, clname);
118 buf[sizeof(buf)-1] = '\0';
119 len = strlen(p) + strlen(clname) + strlen(cl->data.md5) + 3;
120 if(len < MAXPATH){
121 snprintf(buf2, len, "%s%s%s.%s", p, S_FILESEP, clname, cl->data.md5);
122 buf2[sizeof(buf2)-1] = '\0';
123 done = 0; /* recycle done: it means we have a file that may be a certifificate*/
124 if(stat(buf2, &sbuf) < 0){
125 if (our_copy(buf2, buf) == 0)
126 done++;
127 } else if((sbuf.st_mode & S_IFMT) == S_IFREG)
128 done++;
130 if(done){
131 switch(ctype){
132 case Public:
133 case CACert:
134 if((in = BIO_new_file(buf2, "r"))!=0){
135 cert = fs_get(sizeof(CertList));
136 memset((void *)cert, 0, sizeof(CertList));
137 cert->x509_cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
138 if(cl->data.date_from != NULL)
139 cert->data.date_from = cpystr(cl->data.date_from);
140 if(cl->data.date_to != NULL)
141 cert->data.date_to = cpystr(cl->data.date_to);
142 if(cl->data.md5 != NULL)
143 cert->data.md5 = cpystr(cl->data.md5);
144 if(cl->cn != NULL)
145 cert->cn = cpystr(cl->cn);
146 snprintf(buf2, len, "%s.%s", cl->name, cl->data.md5);
147 buf2[sizeof(buf2)-1] = '\0';
148 cert->name = cpystr(buf2);
149 if(data == NULL)
150 data = cert;
151 else{
152 for (cl2 = data; cl2 && cl2->next; cl2 = cl2->next);
153 cl2->next = cert;
155 BIO_free(in);
157 break;
159 case Private: break;
160 default: alpine_panic("Bad ctype (0)");
166 /* if we are here, it means we just loaded the backup variable with
167 * a copy of the data that comes from the certlist not coming from
168 * backup. Now we are going to load the contents of the .backup
169 * directory.
172 /* Here is the plan: read the backup directory (in the variable "p")
173 * and attempt to add it. If already there, skip it; otherwise continue
176 if((dirp = opendir(p)) != NULL){
177 while((df=readdir(dirp)) != NULL){
178 if(df->d_name && *df->d_name == '.') /* no hidden files here */
179 continue;
181 /* make sure that we have a file */
182 snprintf(buf2, sizeof(buf2), "%s%s%s", p, S_FILESEP, df->d_name);
183 buf2[sizeof(buf2)-1] = '\0';
184 if(our_stat(buf2, &sbuf) == 0
185 && (sbuf.st_mode & S_IFMT) != S_IFREG)
186 continue;
188 /* make sure it is not already in the list */
189 for(cl = data; cl; cl = cl->next)
190 if(strcmp(cl->name, df->d_name) == 0)
191 break;
192 if(cl != NULL)
193 continue;
195 /* ok, if it is not in the list, and it is a certificate. Add it */
196 switch(ctype){
197 case Public:
198 case CACert:
199 if((in = BIO_new_file(buf2, "r"))!=0){
200 x = PEM_read_bio_X509(in, NULL, NULL, NULL);
201 if(x && x->cert_info){ /* for now copy this information */
202 X509_NAME_ENTRY *e;
204 cert = fs_get(sizeof(CertList));
205 memset((void *)cert, 0, sizeof(CertList));
206 cert->x509_cert = x;
207 cert->data.date_from = smime_get_date(x->cert_info->validity->notBefore);
208 cert->data.date_to = smime_get_date(x->cert_info->validity->notAfter);
209 get_fingerprint(x, EVP_md5(), buf, sizeof(buf), NULL);
210 cert->data.md5 = cpystr(buf);
211 cert->name = cpystr(df->d_name);
212 cert->cn = smime_get_cn(x->cert_info->subject);
213 /* we will use the cert->data.md5 variable to find a backup
214 certificate, not the name */
215 if(data == NULL)
216 data = cert;
217 else{
218 for (cl2 = data; cl2 && cl2->next; cl2 = cl2->next);
219 cl2->next = cert;
222 BIO_free(in);
224 break;
226 case Private:
227 /* here we must check it is a key of some cert....*/
228 break;
230 default: alpine_panic("Bad ctype (1)");
231 } /* end switch */
233 closedir(dirp);
236 /* Now that we are here, we have all the information in the backup
237 * directory
240 switch(ctype){
241 case Public : ps_global->smime->backuppubliccertlist = data; break;
242 case Private: ps_global->smime->backupprivatecertlist = data; break;
243 case CACert : ps_global->smime->backupcacertlist = data; break;
244 default : alpine_panic("Bad ctype (n)");
247 } else if(SMHOLDERTYPE(ctype) == Container){
249 } /* else APPLEKEYCHAIN */
250 return rv;
253 char *
254 smime_get_cn(X509_NAME *subject)
256 char buf[256];
257 X509_NAME_ENTRY *e;
258 e = X509_NAME_get_entry(subject, X509_NAME_entry_count(subject)-1);
259 if(e)
260 X509_NAME_get_text_by_OBJ(subject, e->object, buf, sizeof(buf));
261 return cpystr(buf);
265 compare_certs_by_name(const void *data1, const void *data2)
267 int rv, i, j;
268 char *s;
270 CertList *cl1 = *(CertList **) data1;
271 CertList *cl2 = *(CertList **) data2;
273 i = j = -1;
274 if((s = strchr(cl1->name, '@')) != NULL){
275 i = s - cl1->name;
276 *s = '\0';
279 if((s = strchr(cl2->name, '@')) != NULL){
280 j = s - cl2->name;
281 *s = '\0';
284 if((rv = strucmp(cl1->name, cl2->name)) == 0)
285 rv = strucmp(cl1->name + i + 1, cl2->name + j + 1);
286 if(i >= 0) cl1->name[i] = '@';
287 if(j >= 0) cl2->name[j] = '@';
288 return rv;
291 void
292 resort_certificates(CertList **data, WhichCerts ctype)
294 int i, j;
295 CertList *cl = *data;
296 CertList **cll;
297 char *s, *t;
299 if(cl == NULL)
300 return;
302 for(i = 0; cl; cl = cl->next, i++)
303 if(SMHOLDERTYPE(ctype) == Directory && ctype != Private){
304 for(t = s = cl->name; (t = strstr(s, ".crt")) != NULL; s = t+1);
305 if (s) *(s-1) = '\0';
307 j = i;
308 cll = fs_get(i*sizeof(CertList *));
309 for(cl = *data, i = 0; cl; cl = cl->next, i++)
310 cll[i] = cl;
311 qsort((void *)cll, j, sizeof(CertList *), compare_certs_by_name);
312 for(i = 0; i < j - 1; i++){
313 cll[i]->next = cll[i+1];
314 if(SMHOLDERTYPE(ctype) == Directory && ctype != Private)
315 cll[i]->name[strlen(cll[i]->name)]= '.'; /* restore ".crt" part */
317 if(SMHOLDERTYPE(ctype) == Directory && ctype != Private)
318 cll[j-1]->name[strlen(cll[j-1]->name)]= '.'; /* restore ".crt" part */
319 cll[j-1]->next = NULL;
320 *data = cll[0];
324 void
325 get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen, char *s)
327 unsigned char md[128];
328 char *b;
329 unsigned int len, i;
331 len = sizeof(md);
333 X509_digest(cert, type, md, &len);
335 b = buf;
336 *b = 0;
337 for(i=0; i<len; i++){
338 if(b-buf+3>=maxLen)
339 break;
341 if(i != 0 && s && *s)
342 *b++ = *s;
344 snprintf(b, maxLen - (b-buf), "%02x", md[i]);
345 b+=2;
351 * Remove leading whitespace, trailing whitespace and convert
352 * to lowercase. Also remove slash characters
354 * Args: s, -- The string to clean
356 * Result: the cleaned string
358 static char *
359 emailstrclean(char *string)
361 char *s = string, *sc = NULL, *p = NULL;
363 for(; *s; s++){ /* single pass */
364 if(!isspace((unsigned char) (*s))){
365 p = NULL; /* not start of blanks */
366 if(!sc) /* first non-blank? */
367 sc = string; /* start copying */
369 else if(!p) /* it's OK if sc == NULL */
370 p = sc; /* start of blanks? */
372 if(sc && *s!='/' && *s!='\\') /* if copying, copy */
373 *sc++ = isupper((unsigned char) (*s))
374 ? (unsigned char) tolower((unsigned char) (*s))
375 : (unsigned char) (*s);
378 if(p) /* if ending blanks */
379 *p = '\0'; /* tie off beginning */
380 else if(!sc) /* never saw a non-blank */
381 *string = '\0'; /* so tie whole thing off */
383 return(string);
387 char *
388 smime_get_date(ASN1_GENERALIZEDTIME *tm)
390 BIO *mb = BIO_new(BIO_s_mem());
391 char iobuf[4096];
392 char date[MAILTMPLEN];
393 char buf[MAILTMPLEN];
394 char *m, *d, *t, *y, *z;
395 struct date smd;
396 struct tm smtm;
398 (void) BIO_reset(mb);
399 if(ASN1_TIME_print(mb, tm) == 0)
400 return cpystr(_("Invalid"));
402 (void) BIO_flush(mb);
403 BIO_read(mb, iobuf, sizeof(iobuf));
405 /* openssl returns the date in the format:
406 * "MONTH (as name) DAY (as number) TIME(hh:mm:ss) YEAR GMT"
408 m = iobuf;
409 d = strchr(iobuf, ' ');
410 *d++ = '\0';
411 while(*d == ' ') d++;
412 t = strchr(d+1, ' ');
413 *t++ = '\0';
414 while(*t == ' ') t++;
415 y = strchr(t+1, ' ');
416 *y++ = '\0';
417 while(*y == ' ') y++;
418 z = strchr(y+1, ' ');
419 *z++ = '\0';
420 while(*z == ' ') z++;
422 snprintf(date, sizeof(date), "%s %s %s %s (%s)", d, m, y, t, z);
423 date[sizeof(date)-1] = '\0';
424 if(F_ON(F_DATES_TO_LOCAL,ps_global)){
425 parse_date(convert_date_to_local(date), &smd);
426 memset(&smtm, 0, sizeof(smtm));
427 smtm.tm_year = smd.year - 1900;
428 smtm.tm_mon = MIN(MAX(smd.month-1, 0), 11);
429 smtm.tm_mday = MIN(MAX(smd.day, 1), 31);
430 our_strftime(buf, sizeof(buf), "%x", &smtm);
432 else
433 snprintf(buf, sizeof(buf), "%s/%s/%s", m, d, y + strlen(y) - 2);
434 buf[sizeof(buf)-1] = '\0';
436 return cpystr(buf);
440 * Add a lookup for each "*.crt*" file in the given directory.
443 add_certs_in_dir(X509_LOOKUP *lookup, char *path, char *ext, CertList **cdata)
445 char buf[MAXPATH];
446 struct direct *d;
447 DIR *dirp;
448 CertList *cert, *cl;
449 int ret = 0;
451 if((dirp = opendir(path)) != NULL){
452 while(!ret && (d=readdir(dirp)) != NULL){
453 if(srchrstr(d->d_name, ext)){
454 build_path(buf, path, d->d_name, sizeof(buf));
456 if(!X509_LOOKUP_load_file(lookup, buf, X509_FILETYPE_PEM)){
457 q_status_message1(SM_ORDER, 3, 3, _("Error loading file %s"), buf);
458 ret = -1;
459 } else {
460 if(cdata){
461 BIO *in;
462 X509 *x;
464 cert = fs_get(sizeof(CertList));
465 memset((void *)cert, 0, sizeof(CertList));
466 cert->name = cpystr(d->d_name);
467 /* read buf into a bio and fill the CertData structure */
468 if((in = BIO_new_file(buf, "r"))!=0){
469 x = PEM_read_bio_X509(in, NULL, NULL, NULL);
470 if(x && x->cert_info){
471 cert->data.date_from = smime_get_date(x->cert_info->validity->notBefore);
472 cert->data.date_to = smime_get_date(x->cert_info->validity->notAfter);
473 get_fingerprint(x, EVP_md5(), buf, sizeof(buf), NULL);
474 cert->data.md5 = cpystr(buf);
475 cert->cn = smime_get_cn(x->cert_info->subject);
476 X509_free(x);
478 BIO_free(in);
480 if(*cdata == NULL)
481 *cdata = cert;
482 else{
483 for (cl = *cdata; cl && cl->next; cl = cl->next);
484 cl->next = cert;
493 closedir(dirp);
496 return ret;
501 * Get an X509_STORE. This consists of the system
502 * certs directory and any certificates in the user's
503 * ~/.alpine-smime/ca directory.
505 X509_STORE *
506 get_ca_store(void)
508 X509_LOOKUP *lookup;
509 X509_STORE *store = NULL;
511 dprint((9, "get_ca_store()"));
513 if(!(store=X509_STORE_new())){
514 dprint((9, "X509_STORE_new() failed"));
515 return store;
518 if(!(lookup=X509_STORE_add_lookup(store, X509_LOOKUP_file()))){
519 dprint((9, "X509_STORE_add_lookup() failed"));
520 X509_STORE_free(store);
521 return NULL;
524 if(ps_global->smime && ps_global->smime->catype == Container
525 && ps_global->smime->cacontent){
527 if(!mem_add_extra_cacerts(ps_global->smime->cacontent, lookup)){
528 X509_STORE_free(store);
529 return NULL;
532 else if(ps_global->smime && ps_global->smime->catype == Directory
533 && ps_global->smime->capath){
534 if(add_certs_in_dir(lookup, ps_global->smime->capath, ".crt", &ps_global->smime->cacertlist) < 0){
535 X509_STORE_free(store);
536 return NULL;
538 resort_certificates(&ps_global->smime->cacertlist, CACert);
541 if(!(lookup=X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()))){
542 X509_STORE_free(store);
543 return NULL;
546 #ifdef SMIME_SSLCERTS
547 dprint((9, "get_ca_store(): adding cacerts from %s", SMIME_SSLCERTS));
548 X509_LOOKUP_add_dir(lookup, SMIME_SSLCERTS, X509_FILETYPE_PEM);
549 #endif
551 return store;
555 EVP_PKEY *
556 load_key(PERSONAL_CERT *pc, char *pass, int flag)
558 BIO *in;
559 EVP_PKEY *key = NULL;
560 char buf[MAXPATH], file[MAXPATH];
562 if(!(ps_global->smime && pc && pc->name))
563 return key;
565 if(ps_global->smime->privatetype == Container){
566 char *q;
568 if(pc->keytext && (q = strstr(pc->keytext, "-----END")) != NULL){
569 while(*q && *q != '\n')
570 q++;
572 if(*q == '\n')
573 q++;
575 if((in = BIO_new_mem_buf(pc->keytext, q-pc->keytext)) != NULL){
576 key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass);
577 BIO_free(in);
581 else if(ps_global->smime->privatetype == Directory){
582 /* filename is path/name.key */
583 strncpy(buf, pc->name, sizeof(buf)-5);
584 buf[sizeof(buf)-5] = '\0';
585 strncat(buf, ".key", 5);
586 build_path(file, ps_global->smime->privatepath, buf, sizeof(file));
588 if(!(in = BIO_new_file(file, "r")))
589 return NULL;
591 key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass);
592 BIO_free(in);
595 return key;
599 #include <openssl/x509v3.h>
601 * This newer version is from Adrian Vogel. It looks for the email
602 * address not only in the email address field, but also in an
603 * X509v3 extension field, Subject Altenative Name.
605 char **
606 get_x509_subject_email(X509 *x)
608 char **result = NULL;
609 int i, n;
610 STACK_OF(OPENSSL_STRING) *emails = X509_get1_email(x);
611 if ((n = sk_OPENSSL_STRING_num(emails)) > 0) {
612 result = fs_get((n+1)*sizeof(char *));
613 for(i = 0; i < n; i++)
614 result[i] = cpystr(sk_OPENSSL_STRING_value(emails, i));
615 result[i] = NULL;
617 X509_email_free(emails);
618 return result;
623 * Save the certificate for the given email address in
624 * ~/.alpine-smime/public.
626 * Should consider the security hazards in making a file with
627 * the email address that has come from the certificate.
629 * The argument email is destroyed.
631 * args: ctype says where the user wants to save the certificate
633 void
634 save_cert_for(char *email, X509 *cert, WhichCerts ctype)
636 if(!ps_global->smime || ctype == Private)
637 return;
639 dprint((9, "save_cert_for(%s, %s)", email ? email : "?", ctype == Public ? _("Public") : ctype == Private ? _("Private") : "CACert"));
640 emailstrclean(email);
642 if(ps_global->smime->publictype == Keychain){
643 #ifdef APPLEKEYCHAIN
645 OSStatus rc;
646 SecCertificateRef secCertificateRef;
647 CSSM_DATA certData;
649 memset((void *) &certData, 0, sizeof(certData));
650 memset((void *) &secCertificateRef, 0, sizeof(secCertificateRef));
652 /* convert OpenSSL X509 cert data to MacOS certData */
653 if((certData.Length = i2d_X509(cert, &(certData.Data))) > 0){
656 * Put that certData into a SecCertificateRef.
657 * Version 3 should work for versions 1-3.
659 if(!(rc=SecCertificateCreateFromData(&certData,
660 CSSM_CERT_X_509v3,
661 CSSM_CERT_ENCODING_DER,
662 &secCertificateRef))){
664 /* add it to the default keychain */
665 if(!(rc=SecCertificateAddToKeychain(secCertificateRef, NULL))){
666 /* ok */
668 else if(rc == errSecDuplicateItem){
669 dprint((9, "save_cert_for: certificate for %s already in keychain", email));
671 else{
672 dprint((9, "SecCertificateAddToKeychain failed"));
675 else{
676 dprint((9, "SecCertificateCreateFromData failed"));
679 else{
680 dprint((9, "i2d_X509 failed"));
683 #endif /* APPLEKEYCHAIN */
685 else if(SMHOLDERTYPE(ctype) == Container){
686 REMDATA_S *rd = NULL;
687 char *ret_dir = NULL;
688 char path[MAXPATH];
689 char fpath[MAXPATH];
690 char *upath = PATHCERTDIR(ctype);
691 char *tempfile = NULL;
692 int err = 0;
693 CertList *clist = DATACERT(ctype);
695 add_to_end_of_certlist(&clist, email, X509_dup(cert));
697 switch(ctype){
698 case Private: ps_global->smime->privatecertlist = clist; break;
699 case Public : ps_global->smime->publiccertlist = clist; break;
700 case CACert : ps_global->smime->cacertlist = clist; break;
701 default: break;
704 if(!upath)
705 return;
707 if(IS_REMOTE(upath)){
708 rd = rd_create_remote(RemImap, upath, REMOTE_SMIME_SUBTYPE,
709 NULL, "Error: ",
710 _("Can't access remote smime configuration."));
711 if(!rd){
712 return;
715 (void) rd_read_metadata(rd);
717 if(rd->access == MaybeRorW){
718 if(rd->read_status == 'R')
719 rd->access = ReadOnly;
720 else
721 rd->access = ReadWrite;
724 if(rd->access != NoExists){
726 rd_check_remvalid(rd, 1L);
729 * If the cached info says it is readonly but
730 * it looks like it's been fixed now, change it to readwrite.
732 if(rd->read_status == 'R'){
733 rd_check_readonly_access(rd);
734 if(rd->read_status == 'W'){
735 rd->access = ReadWrite;
736 rd->flags |= REM_OUTOFDATE;
738 else
739 rd->access = ReadOnly;
743 if(rd->flags & REM_OUTOFDATE){
744 if(rd_update_local(rd) != 0){
746 dprint((1, "save_cert_for: rd_update_local failed\n"));
747 rd_close_remdata(&rd);
748 return;
751 else
752 rd_open_remote(rd);
754 if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
755 rd_close_remdata(&rd);
756 return;
759 rd->flags |= DO_REMTRIM;
761 strncpy(path, rd->lf, sizeof(path)-1);
762 path[sizeof(path)-1] = '\0';
764 else{
765 strncpy(path, upath, sizeof(path)-1);
766 path[sizeof(path)-1] = '\0';
769 tempfile = tempfile_in_same_dir(path, "az", &ret_dir);
770 if(tempfile){
771 if(certlist_to_file(tempfile, DATACERT(ctype)))
772 err++;
774 if(!err && ret_dir){
775 if(IS_REMOTE(upath)){
776 strncpy(fpath, rd->lf, sizeof(fpath));
777 fpath[sizeof(fpath)-1] = '\0';
779 else{
780 if(strlen(path) + strlen(tempfile) - strlen(ret_dir) + 1 < sizeof(path))
781 snprintf(fpath, sizeof(fpath), "%s%c%s",
782 path, tempfile[strlen(ret_dir)], tempfile + strlen(ret_dir) + 1);
783 else
784 err++;
787 else err++;
789 fs_give((void **)&ret_dir);
791 if(!err){
792 if(rename_file(tempfile, fpath) < 0){
793 q_status_message2(SM_ORDER, 3, 3,
794 _("Can't rename %s to %s"), tempfile, fpath);
795 err++;
799 if(!err && IS_REMOTE(upath)){
800 int e, we_cancel;
801 char datebuf[200];
803 datebuf[0] = '\0';
805 we_cancel = busy_cue(_("Copying to remote smime container"), NULL, 1);
806 if((e = rd_update_remote(rd, datebuf)) != 0){
807 if(e == -1){
808 q_status_message2(SM_ORDER | SM_DING, 3, 5,
809 _("Error opening temporary smime file %s: %s"),
810 rd->lf, error_description(errno));
811 dprint((1,
812 "write_remote_smime: error opening temp file %s\n",
813 rd->lf ? rd->lf : "?"));
815 else{
816 q_status_message2(SM_ORDER | SM_DING, 3, 5,
817 _("Error copying to %s: %s"),
818 rd->rn, error_description(errno));
819 dprint((1,
820 "write_remote_smime: error copying from %s to %s\n",
821 rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
824 q_status_message(SM_ORDER | SM_DING, 5, 5,
825 _("Copy of smime cert to remote folder failed, changes NOT saved remotely"));
827 else{
828 rd_update_metadata(rd, datebuf);
829 rd->read_status = 'W';
832 rd_close_remdata(&rd);
834 if(we_cancel)
835 cancel_busy_cue(-1);
838 fs_give((void **) &tempfile);
841 else if(SMHOLDERTYPE(ctype) == Directory){
842 char *path = PATHCERTDIR(ctype);
843 char certfilename[MAXPATH];
844 BIO *bio_out;
846 build_path(certfilename, path, email, sizeof(certfilename));
847 strncat(certfilename, ".crt", sizeof(certfilename)-1-strlen(certfilename));
848 certfilename[sizeof(certfilename)-1] = 0;
850 bio_out = BIO_new_file(certfilename, "w");
851 if(bio_out){
852 PEM_write_bio_X509(bio_out, cert);
853 BIO_free(bio_out);
854 q_status_message1(SM_ORDER, 1, 1, _("Saved certificate for <%s>"), email);
856 else{
857 q_status_message1(SM_ORDER, 1, 1, _("Couldn't save certificate for <%s>"), email);
864 * Try to retrieve the certificate for the given email address.
865 * The caller should free the cert.
867 X509 *
868 get_cert_for(char *email, WhichCerts ctype, int tolower)
870 char certfilename[MAXPATH];
871 char emailaddr[MAXPATH];
872 X509 *cert = NULL;
873 BIO *in;
875 if(!ps_global->smime)
876 return cert;
878 dprint((9, "get_cert_for(%s, %s)", email ? email : "?", "none yet"));
880 if(ctype == Private) /* there is no private certificate info */
881 ctype = Public; /* return public information instead */
882 strncpy(emailaddr, email, sizeof(emailaddr)-1);
883 emailaddr[sizeof(emailaddr)-1] = 0;
885 /* clean it up (lowercase, space removal) */
886 if(tolower)
887 emailstrclean(emailaddr);
889 if(ps_global->smime->publictype == Keychain){
890 #ifdef APPLEKEYCHAIN
892 OSStatus rc;
893 SecKeychainItemRef itemRef = nil;
894 SecKeychainAttributeList attrList;
895 SecKeychainAttribute attrib;
896 SecKeychainSearchRef searchRef = nil;
897 CSSM_DATA certData;
899 /* low-level form of MacOS data */
900 memset((void *) &certData, 0, sizeof(certData));
902 attrList.count = 1;
903 attrList.attr = &attrib;
905 /* kSecAlias means email address for a certificate */
906 attrib.tag = kSecAlias;
907 attrib.data = emailaddr;
908 attrib.length = strlen(attrib.data);
910 /* Find the certificate in the default keychain */
911 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
912 kSecCertificateItemClass,
913 &attrList,
914 &searchRef))){
916 if(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef))){
918 /* extract the data portion of the certificate */
919 if(!(rc=SecCertificateGetData((SecCertificateRef) itemRef, &certData))){
922 * Convert it from MacOS form to OpenSSL form.
923 * The input is certData from above and the output
924 * is the X509 *cert.
926 if(!d2i_X509(&cert, &(certData.Data), certData.Length)){
927 dprint((9, "d2i_X509 failed"));
930 else{
931 dprint((9, "SecCertificateGetData failed"));
934 else if(rc == errSecItemNotFound){
935 dprint((9, "get_cert_for: Public cert for %s not found", emailaddr));
937 else{
938 dprint((9, "SecKeychainSearchCopyNext failed"));
941 else{
942 dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
945 if(searchRef)
946 CFRelease(searchRef);
948 #endif /* APPLEKEYCHAIN */
950 else if(SMHOLDERTYPE(ctype) == Container){
951 CertList *cl;
953 for(cl = DATACERT(ctype); cl; cl = cl->next){
954 if(cl->name && !strucmp(emailaddr, cl->name))
955 break;
958 if(cl)
959 cert = X509_dup((X509 *) cl->x509_cert);
961 else if(SMHOLDERTYPE(ctype) == Directory){
962 build_path(certfilename, PATHCERTDIR(ctype), emailaddr, sizeof(certfilename));
963 strncat(certfilename, EXTCERT(ctype), sizeof(certfilename)-1-strlen(certfilename));
964 certfilename[sizeof(certfilename)-1] = 0;
966 if((in = BIO_new_file(certfilename, "r"))!=0){
968 cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
970 if(cert){
971 /* could check email addr in cert matches */
974 BIO_free(in);
978 return cert;
982 * load_cert_for_key finds a certificate in pathdir that matches a private key
983 * pkey. It returns its name in certfile, and the certificate in *pcert.
984 * return value: success: different from zero, failure 0. If both certfile
985 * and pcert are NULL, this function returns if there is certificate that
986 * matches the given key.
989 load_cert_for_key(char *pathdir, EVP_PKEY *pkey, char **certfile, X509 **pcert)
991 DIR *dirp;
992 struct dirent *d;
993 int rv = 0;
994 BIO *in;
995 X509 *x;
996 char buf[MAXPATH+1], pathcert[MAXPATH+1];
998 if(pathdir == NULL || pkey == NULL)
999 return 0;
1001 if(certfile) *certfile = NULL;
1002 if(pcert) *pcert = NULL;
1004 if((dirp = opendir(pathdir)) != NULL){
1005 while(rv == 0 && (d=readdir(dirp)) != NULL){
1006 size_t ll;
1008 if((ll=strlen(d->d_name)) && ll > 4){
1009 if(!strcmp(d->d_name+ll-4, ".crt")){
1010 strncpy(buf, d->d_name, sizeof(buf));
1011 buf[sizeof(buf)-1] = '\0';
1012 build_path(pathcert, pathdir, buf, sizeof(pathcert));
1013 if((in = BIO_new_file(pathcert, "r")) != NULL){
1014 if((x = PEM_read_bio_X509(in, NULL, NULL, NULL)) != NULL){
1015 if(X509_check_private_key(x, pkey) > 0){
1016 rv = 1;
1017 if(certfile) *certfile = cpystr(buf);
1018 if(pcert) *pcert = x;
1020 else
1021 X509_free(x);
1023 BIO_free(in);
1028 closedir(dirp);
1030 return rv;
1034 PERSONAL_CERT *
1035 mem_to_personal_certs(char *contents)
1037 PERSONAL_CERT *result = NULL;
1038 char *p, *q, *line, *name, *keytext, *save_p;
1039 X509 *cert = NULL;
1041 if(contents && *contents){
1042 for(p = contents; *p != '\0';){
1043 line = p;
1045 while(*p && *p != '\n')
1046 p++;
1048 save_p = NULL;
1049 if(*p == '\n'){
1050 save_p = p;
1051 *p++ = '\0';
1054 if(strncmp(EMAILADDRLEADER, line, strlen(EMAILADDRLEADER)) == 0){
1055 name = line + strlen(EMAILADDRLEADER);
1056 cert = get_cert_for(name, Public, 1);
1057 keytext = p;
1059 /* advance p past this record */
1060 if((q = strstr(keytext, "-----END")) != NULL){
1061 while(*q && *q != '\n')
1062 q++;
1064 if(*q == '\n')
1065 q++;
1067 p = q;
1069 else{
1070 p = p + strlen(p);
1071 q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error in privatekey container, missing END"));
1074 if(cert){
1075 PERSONAL_CERT *pc;
1077 pc = (PERSONAL_CERT *) fs_get(sizeof(*pc));
1078 pc->cert = cert;
1079 pc->name = cpystr(name);
1080 pc->keytext = keytext; /* a pointer into contents */
1082 pc->key = load_key(pc, "", SM_NORMALCERT);
1084 pc->next = result;
1085 result = pc;
1089 if(save_p)
1090 *save_p = '\n';
1094 return result;
1098 CertList *
1099 mem_to_certlist(char *contents, WhichCerts ctype)
1101 CertList *ret = NULL;
1102 char *p, *q, *line, *name, *certtext, *save_p;
1103 X509 *cert = NULL;
1104 BIO *in;
1105 char *sep = (ctype == Public || ctype == Private)
1106 ? EMAILADDRLEADER : CACERTSTORELEADER;
1108 if(contents && *contents){
1109 for(p = contents; *p != '\0';){
1110 line = p;
1112 while(*p && *p != '\n')
1113 p++;
1115 save_p = NULL;
1116 if(*p == '\n'){
1117 save_p = p;
1118 *p++ = '\0';
1121 if(strncmp(sep, line, strlen(sep)) == 0){
1122 name = line + strlen(sep);
1123 cert = NULL;
1124 certtext = strstr(p, "-----BEGIN");
1125 if(certtext != NULL){
1126 if((q = strstr(certtext, sep)) != NULL)
1127 p = q;
1128 else
1129 p = q = certtext+strlen(certtext);
1131 if((in = BIO_new_mem_buf(certtext, q-certtext)) != 0){
1132 cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
1133 BIO_free(in);
1136 else{
1137 q_status_message2(SM_ORDER | SM_DING, 3, 3, _("Error in %scert container, missing BEGIN, certtext=%s"), ctype == Public ? _("public") : _("ca"), p);
1138 p = p + strlen(p);
1141 if(name && cert)
1142 add_to_end_of_certlist(&ret, name, cert);
1145 if(save_p)
1146 *save_p = '\n';
1149 if(ret != NULL)
1150 resort_certificates(&ret, ctype);
1152 return ret;
1157 * Add the CACert Container contents into the CACert store.
1159 * Returns > 0 for success, 0 for failure
1162 mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup)
1164 char *p, *q, *line, *certtext, *save_p;
1165 BIO *in, *out;
1166 int len, failed = 0;
1167 char *tempfile;
1168 char iobuf[4096];
1171 * The most straight-forward way to do this is to write
1172 * the container contents to a temp file and then load the
1173 * contents of the file with X509_LOOKUP_load_file(), like
1174 * is done in add_certs_in_dir(). What we don't know is if
1175 * each file should consist of one cacert or if they can all
1176 * just be jammed together into one file. To be safe, we'll use
1177 * one file per and do each in a separate operation.
1180 if(contents && *contents){
1181 for(p = contents; *p != '\0';){
1182 line = p;
1184 while(*p && *p != '\n')
1185 p++;
1187 save_p = NULL;
1188 if(*p == '\n'){
1189 save_p = p;
1190 *p++ = '\0';
1193 /* look for separator line */
1194 if(strncmp(CACERTSTORELEADER, line, strlen(CACERTSTORELEADER)) == 0){
1195 /* certtext is the content that should go in a file */
1196 certtext = strstr(p, "-----BEGIN");
1197 if(certtext != NULL){
1198 if((q = strstr(certtext, CACERTSTORELEADER)) != NULL){
1199 p = q;
1201 else{ /* end of file */
1202 q = certtext + strlen(certtext);
1203 p = q;
1206 in = BIO_new_mem_buf(certtext, q-certtext);
1207 if(in){
1208 tempfile = temp_nam(NULL, "az");
1209 out = tempfile != NULL ? BIO_new_file(tempfile, "w") : NULL;
1210 if(out != NULL){
1211 while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
1212 BIO_write(out, iobuf, len);
1214 BIO_free(out);
1215 if(!X509_LOOKUP_load_file(lookup, tempfile, X509_FILETYPE_PEM))
1216 failed++;
1219 if(tempfile != NULL){
1220 unlink(tempfile);
1221 fs_give((void **) &tempfile);
1224 BIO_free(in);
1227 else{
1228 p = p + strlen(p);
1229 q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext);
1232 else{
1233 p = p + strlen(p);
1234 q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in cacert container, missing separator, line=%s"), line);
1237 if(save_p)
1238 *save_p = '\n';
1242 return(!failed);
1247 certlist_to_file(char *filename, CertList *certlist)
1249 CertList *cl;
1250 BIO *bio_out = NULL;
1251 int ret = -1;
1253 if(filename && (bio_out=BIO_new_file(filename, "w")) != NULL){
1254 ret = 0;
1255 for(cl = certlist; cl; cl = cl->next){
1256 if(cl->name && cl->name[0] && cl->x509_cert){
1257 if(!((BIO_puts(bio_out, EMAILADDRLEADER) > 0)
1258 && (BIO_puts(bio_out, cl->name) > 0)
1259 && (BIO_puts(bio_out, "\n") > 0)))
1260 ret = -1;
1262 if(!PEM_write_bio_X509(bio_out, (X509 *) cl->x509_cert))
1263 ret = -1;
1267 BIO_free(bio_out);
1270 return ret;
1274 void
1275 add_to_end_of_certlist(CertList **cl, char *name, X509 *cert)
1277 CertList *new, *clp;
1278 char buf[MAILTMPLEN];
1280 if(!cl)
1281 return;
1283 new = (CertList *) fs_get(sizeof(*new));
1284 memset((void *) new, 0, sizeof(*new));
1285 new->x509_cert = cert;
1286 new->name = name ? cpystr(name) : NULL;
1287 if(cert && cert->cert_info){
1288 new->data.date_from = smime_get_date(cert->cert_info->validity->notBefore);
1289 new->data.date_to = smime_get_date(cert->cert_info->validity->notAfter);
1290 get_fingerprint(cert, EVP_md5(), buf, sizeof(buf), NULL);
1291 new->data.md5 = cpystr(buf);
1292 new->cn = smime_get_cn(cert->cert_info->subject);
1295 if(!*cl){
1296 *cl = new;
1298 else{
1299 for(clp = (*cl); clp->next; clp = clp->next)
1302 clp->next = new;
1307 void
1308 free_certlist(CertList **cl)
1310 if(cl && *cl){
1311 if((*cl)->data.date_from)
1312 fs_give((void **) &(*cl)->data.date_from);
1314 if((*cl)->data.date_to)
1315 fs_give((void **) &(*cl)->data.date_to);
1317 if((*cl)->data.md5)
1318 fs_give((void **) &(*cl)->data.md5);
1320 if((*cl)->name)
1321 fs_give((void **) &(*cl)->name);
1323 if((*cl)->cn)
1324 fs_give((void **) &(*cl)->cn);
1326 if((*cl)->x509_cert)
1327 X509_free((X509 *) (*cl)->x509_cert);
1329 free_certlist(&(*cl)->next);
1331 fs_give((void **) cl);
1336 void
1337 free_personal_certs(PERSONAL_CERT **pc)
1339 if(pc && *pc){
1340 free_personal_certs(&(*pc)->next);
1341 if((*pc)->name)
1342 fs_give((void **) &(*pc)->name);
1344 if((*pc)->name)
1345 fs_give((void **) &(*pc)->name);
1347 if((*pc)->cert)
1348 X509_free((*pc)->cert);
1350 if((*pc)->key)
1351 EVP_PKEY_free((*pc)->key);
1353 fs_give((void **) pc);
1357 #endif /* SMIME */