* Add the ability to change the private key and certificates used
[alpine.git] / pith / smkeys.c
blob415bc190f31021ddfec61bd0b0e6b9278aa38c21
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-2016 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);
53 CertList *
54 smime_X509_to_cert_info(X509 *x, char *name)
56 CertList *cert;
57 char buf[MAXPATH+1];
59 if(x == NULL) return NULL;
61 cert = fs_get(sizeof(CertList));
62 memset((void *)cert, 0, sizeof(CertList));
63 cert->x509_cert = x;
64 cert->name = name ? cpystr(name) : NULL;
65 if(x && x->cert_info){
66 cert->data.date_from = smime_get_date(x->cert_info->validity->notBefore);
67 cert->data.date_to = smime_get_date(x->cert_info->validity->notAfter);
68 cert->cn = smime_get_cn(x->cert_info->subject);
70 get_fingerprint(x, EVP_md5(), buf, sizeof(buf), NULL);
71 cert->data.md5 = cpystr(buf);
73 return cert;
76 #define SMIME_BACKUP_DIR ".backup"
77 #define MAX_TRY_BACKUP 100
79 /* return value: 0 - success, -1 error
80 * Call this function after setting up paths in ps_global->smime
81 * and reading certificates names in certlist.
83 int
84 setup_certs_backup_by_type(WhichCerts ctype)
86 int rv = 0; /* assume success */
87 int len;
88 int i, done;
89 char *d;
90 char p[MAXPATH+1]; /* path to where the backup is */
91 char buf[MAXPATH+1], buf2[MAXPATH+1];
92 struct stat sbuf;
93 CertList *data, *cl;
94 DIR *dirp;
95 struct dirent *df; /* file in the directory */
96 CertList *cert, *cl2;
97 X509 *x;
98 BIO *in;
100 return rv; /* remove when this function is complete */
102 if(SMHOLDERTYPE(ctype) == Directory){
103 d = PATHCERTDIR(ctype);
104 if(d != NULL){
105 len = strlen(d) + strlen(S_FILESEP) + strlen(SMIME_BACKUP_DIR) + 1;
106 snprintf(p, MAXPATH, "%s%s%s", d, S_FILESEP, SMIME_BACKUP_DIR);
107 p[MAXPATH] = '\0';
108 if(our_stat(p, &sbuf) < 0){
109 if(our_mkpath(p, 0700) != 0)
110 return -1;
111 } else if((sbuf.st_mode & S_IFMT) != S_IFDIR){
112 for(i = 0, done = 0; done == 0 && i < MAX_TRY_BACKUP; i++){
113 snprintf(buf2, len+2, "%s%d", p, i);
114 if(our_stat(buf2, &sbuf) < 0){
115 if(our_mkpath(buf2, 0700) == 0)
116 done++;
118 else if((sbuf.st_mode & S_IFMT) == S_IFDIR)
119 done++;
120 if(done){
121 strncpy(p, buf2, MAXPATH);
122 p[MAXPATH] = '\0';
125 if(done == 0)
126 return -1;
128 /* if we are here, we have a backup directory where to
129 * backup certificates/keys, so now we will go
130 * through the list of certificates and back them up
131 * if we need to.
133 data = BACKUPDATACERT(ctype);
134 for(cl = DATACERT(ctype); cl; cl = cl->next){
135 char clname[MAXPATH+1];
137 snprintf(clname, MAXPATH, "%s%s", cl->name, ctype == Private ? ".key" : "");
138 clname[MAXPATH] = '\0';
139 len = strlen(d) + strlen(clname) + 2;
140 if(len < MAXPATH){
141 snprintf(buf, len, "%s%s%s", d, S_FILESEP, clname);
142 buf[sizeof(buf)-1] = '\0';
143 len = strlen(p) + strlen(clname) + strlen(cl->data.md5) + 3;
144 if(len < MAXPATH){
145 snprintf(buf2, len, "%s%s%s.%s", p, S_FILESEP, clname, cl->data.md5);
146 buf2[sizeof(buf2)-1] = '\0';
147 done = 0; /* recycle done: it means we have a file that may be a certifificate*/
148 if(stat(buf2, &sbuf) < 0){
149 if (our_copy(buf2, buf) == 0)
150 done++;
151 } else if((sbuf.st_mode & S_IFMT) == S_IFREG)
152 done++;
154 if(done){
155 switch(ctype){
156 case Public:
157 case CACert:
158 if((in = BIO_new_file(buf2, "r"))!=0){
159 cert = fs_get(sizeof(CertList));
160 memset((void *)cert, 0, sizeof(CertList));
161 cert->x509_cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
162 if(cl->data.date_from != NULL)
163 cert->data.date_from = cpystr(cl->data.date_from);
164 if(cl->data.date_to != NULL)
165 cert->data.date_to = cpystr(cl->data.date_to);
166 if(cl->data.md5 != NULL)
167 cert->data.md5 = cpystr(cl->data.md5);
168 if(cl->cn != NULL)
169 cert->cn = cpystr(cl->cn);
170 snprintf(buf2, len, "%s.%s", cl->name, cl->data.md5);
171 buf2[sizeof(buf2)-1] = '\0';
172 cert->name = cpystr(buf2);
173 if(data == NULL)
174 data = cert;
175 else{
176 for (cl2 = data; cl2 && cl2->next; cl2 = cl2->next);
177 cl2->next = cert;
179 BIO_free(in);
181 break;
183 case Private: break;
184 default: alpine_panic("Bad ctype (0)");
190 /* if we are here, it means we just loaded the backup variable with
191 * a copy of the data that comes from the certlist not coming from
192 * backup. Now we are going to load the contents of the .backup
193 * directory.
196 /* Here is the plan: read the backup directory (in the variable "p")
197 * and attempt to add it. If already there, skip it; otherwise continue
200 if((dirp = opendir(p)) != NULL){
201 while((df=readdir(dirp)) != NULL){
202 if(df->d_name && *df->d_name == '.') /* no hidden files here */
203 continue;
205 /* make sure that we have a file */
206 snprintf(buf2, sizeof(buf2), "%s%s%s", p, S_FILESEP, df->d_name);
207 buf2[sizeof(buf2)-1] = '\0';
208 if(our_stat(buf2, &sbuf) == 0
209 && (sbuf.st_mode & S_IFMT) != S_IFREG)
210 continue;
212 /* make sure it is not already in the list */
213 for(cl = data; cl; cl = cl->next)
214 if(strcmp(cl->name, df->d_name) == 0)
215 break;
216 if(cl != NULL)
217 continue;
219 /* ok, if it is not in the list, and it is a certificate. Add it */
220 switch(ctype){
221 case Public:
222 case CACert:
223 if((in = BIO_new_file(buf2, "r"))!=0){
224 x = PEM_read_bio_X509(in, NULL, NULL, NULL);
225 if(x && x->cert_info){ /* for now copy this information */
226 cert = smime_X509_to_cert_info(x, df->d_name);
227 /* we will use the cert->data.md5 variable to find a backup
228 certificate, not the name */
229 cert->next = data;
230 data = cert;
232 BIO_free(in);
234 break;
236 case Private:
237 /* here we must check it is a key of some cert....*/
238 break;
240 default: alpine_panic("Bad ctype (1)");
241 } /* end switch */
243 closedir(dirp);
246 /* Now that we are here, we have all the information in the backup
247 * directory
250 switch(ctype){
251 case Public : ps_global->smime->backuppubliccertlist = data; break;
252 case Private: ps_global->smime->backupprivatecertlist = data; break;
253 case CACert : ps_global->smime->backupcacertlist = data; break;
254 default : alpine_panic("Bad ctype (n)");
257 } else if(SMHOLDERTYPE(ctype) == Container){
259 } /* else APPLEKEYCHAIN */
260 return rv;
263 char *
264 smime_get_cn(X509_NAME *subject)
266 char buf[256];
267 X509_NAME_ENTRY *e;
268 e = X509_NAME_get_entry(subject, X509_NAME_entry_count(subject)-1);
269 if(e)
270 X509_NAME_get_text_by_OBJ(subject, e->object, buf, sizeof(buf));
271 return cpystr(buf);
275 compare_certs_by_name(const void *data1, const void *data2)
277 int rv, i, j;
278 char *s;
280 CertList *cl1 = *(CertList **) data1;
281 CertList *cl2 = *(CertList **) data2;
283 i = j = -1;
284 if((s = strchr(cl1->name, '@')) != NULL){
285 i = s - cl1->name;
286 *s = '\0';
289 if((s = strchr(cl2->name, '@')) != NULL){
290 j = s - cl2->name;
291 *s = '\0';
294 if((rv = strucmp(cl1->name, cl2->name)) == 0)
295 rv = strucmp(cl1->name + i + 1, cl2->name + j + 1);
296 if(i >= 0) cl1->name[i] = '@';
297 if(j >= 0) cl2->name[j] = '@';
298 return rv;
301 void
302 resort_certificates(CertList **data, WhichCerts ctype)
304 int i, j;
305 CertList *cl = *data;
306 CertList **cll;
307 char *s, *t;
309 if(cl == NULL)
310 return;
312 for(i = 0; cl; cl = cl->next, i++)
313 if(SMHOLDERTYPE(ctype) == Directory && ctype != Private){
314 for(t = s = cl->name; (t = strstr(s, ".crt")) != NULL; s = t+1);
315 if (s) *(s-1) = '\0';
317 j = i;
318 cll = fs_get(i*sizeof(CertList *));
319 for(cl = *data, i = 0; cl; cl = cl->next, i++)
320 cll[i] = cl;
321 qsort((void *)cll, j, sizeof(CertList *), compare_certs_by_name);
322 for(i = 0; i < j - 1; i++){
323 cll[i]->next = cll[i+1];
324 if(SMHOLDERTYPE(ctype) == Directory && ctype != Private)
325 cll[i]->name[strlen(cll[i]->name)]= '.'; /* restore ".crt" part */
327 if(SMHOLDERTYPE(ctype) == Directory && ctype != Private)
328 cll[j-1]->name[strlen(cll[j-1]->name)]= '.'; /* restore ".crt" part */
329 cll[j-1]->next = NULL;
330 *data = cll[0];
334 void
335 get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen, char *s)
337 unsigned char md[128];
338 char *b;
339 unsigned int len, i;
341 len = sizeof(md);
343 X509_digest(cert, type, md, &len);
345 b = buf;
346 *b = 0;
347 for(i=0; i<len; i++){
348 if(b-buf+3>=maxLen)
349 break;
351 if(i != 0 && s && *s)
352 *b++ = *s;
354 snprintf(b, maxLen - (b-buf), "%02x", md[i]);
355 b+=2;
361 * Remove leading whitespace, trailing whitespace and convert
362 * to lowercase. Also remove slash characters
364 * Args: s, -- The string to clean
366 * Result: the cleaned string
368 static char *
369 emailstrclean(char *string)
371 char *s = string, *sc = NULL, *p = NULL;
373 for(; *s; s++){ /* single pass */
374 if(!isspace((unsigned char) (*s))){
375 p = NULL; /* not start of blanks */
376 if(!sc) /* first non-blank? */
377 sc = string; /* start copying */
379 else if(!p) /* it's OK if sc == NULL */
380 p = sc; /* start of blanks? */
382 if(sc && *s!='/' && *s!='\\') /* if copying, copy */
383 *sc++ = isupper((unsigned char) (*s))
384 ? (unsigned char) tolower((unsigned char) (*s))
385 : (unsigned char) (*s);
388 if(p) /* if ending blanks */
389 *p = '\0'; /* tie off beginning */
390 else if(!sc) /* never saw a non-blank */
391 *string = '\0'; /* so tie whole thing off */
393 return(string);
397 char *
398 smime_get_date(ASN1_GENERALIZEDTIME *tm)
400 BIO *mb = BIO_new(BIO_s_mem());
401 char iobuf[4096];
402 char date[MAILTMPLEN];
403 char buf[MAILTMPLEN];
404 char *m, *d, *t, *y, *z;
405 struct date smd;
406 struct tm smtm;
408 (void) BIO_reset(mb);
409 if(ASN1_TIME_print(mb, tm) == 0)
410 return cpystr(_("Invalid"));
412 (void) BIO_flush(mb);
413 BIO_read(mb, iobuf, sizeof(iobuf));
415 /* openssl returns the date in the format:
416 * "MONTH (as name) DAY (as number) TIME(hh:mm:ss) YEAR GMT"
418 m = iobuf;
419 d = strchr(iobuf, ' ');
420 *d++ = '\0';
421 while(*d == ' ') d++;
422 t = strchr(d+1, ' ');
423 *t++ = '\0';
424 while(*t == ' ') t++;
425 y = strchr(t+1, ' ');
426 *y++ = '\0';
427 while(*y == ' ') y++;
428 z = strchr(y+1, ' ');
429 *z++ = '\0';
430 while(*z == ' ') z++;
432 snprintf(date, sizeof(date), "%s %s %s %s (%s)", d, m, y, t, z);
433 date[sizeof(date)-1] = '\0';
434 if(F_ON(F_DATES_TO_LOCAL,ps_global)){
435 parse_date(convert_date_to_local(date), &smd);
436 memset(&smtm, 0, sizeof(smtm));
437 smtm.tm_year = smd.year - 1900;
438 smtm.tm_mon = MIN(MAX(smd.month-1, 0), 11);
439 smtm.tm_mday = MIN(MAX(smd.day, 1), 31);
440 our_strftime(buf, sizeof(buf), "%x", &smtm);
442 else
443 snprintf(buf, sizeof(buf), "%s/%s/%s", m, d, y + strlen(y) - 2);
444 buf[sizeof(buf)-1] = '\0';
446 return cpystr(buf);
450 * Add a lookup for each "*.crt*" file in the given directory.
453 add_certs_in_dir(X509_LOOKUP *lookup, char *path, char *ext, CertList **cdata)
455 char buf[MAXPATH];
456 struct direct *d;
457 DIR *dirp;
458 CertList *cert, *cl;
459 int ret = 0;
461 if((dirp = opendir(path)) != NULL){
462 while(!ret && (d=readdir(dirp)) != NULL){
463 if(srchrstr(d->d_name, ext)){
464 build_path(buf, path, d->d_name, sizeof(buf));
466 if(!X509_LOOKUP_load_file(lookup, buf, X509_FILETYPE_PEM)){
467 q_status_message1(SM_ORDER, 3, 3, _("Error loading file %s"), buf);
468 ret = -1;
469 } else {
470 if(cdata){
471 BIO *in;
472 X509 *x;
474 cert = fs_get(sizeof(CertList));
475 memset((void *)cert, 0, sizeof(CertList));
476 cert->name = cpystr(d->d_name);
477 /* read buf into a bio and fill the CertData structure */
478 if((in = BIO_new_file(buf, "r"))!=0){
479 x = PEM_read_bio_X509(in, NULL, NULL, NULL);
480 if(x && x->cert_info){
481 cert->data.date_from = smime_get_date(x->cert_info->validity->notBefore);
482 cert->data.date_to = smime_get_date(x->cert_info->validity->notAfter);
483 get_fingerprint(x, EVP_md5(), buf, sizeof(buf), NULL);
484 cert->data.md5 = cpystr(buf);
485 cert->cn = smime_get_cn(x->cert_info->subject);
486 X509_free(x);
488 BIO_free(in);
490 if(*cdata == NULL)
491 *cdata = cert;
492 else{
493 for (cl = *cdata; cl && cl->next; cl = cl->next);
494 cl->next = cert;
503 closedir(dirp);
506 return ret;
511 * Get an X509_STORE. This consists of the system
512 * certs directory and any certificates in the user's
513 * ~/.alpine-smime/ca directory.
515 X509_STORE *
516 get_ca_store(void)
518 X509_LOOKUP *lookup;
519 X509_STORE *store = NULL;
521 dprint((9, "get_ca_store()"));
523 if(!(store=X509_STORE_new())){
524 dprint((9, "X509_STORE_new() failed"));
525 return store;
528 if(!(lookup=X509_STORE_add_lookup(store, X509_LOOKUP_file()))){
529 dprint((9, "X509_STORE_add_lookup() failed"));
530 X509_STORE_free(store);
531 return NULL;
534 if(ps_global->smime && ps_global->smime->catype == Container
535 && ps_global->smime->cacontent){
537 if(!mem_add_extra_cacerts(ps_global->smime->cacontent, lookup)){
538 X509_STORE_free(store);
539 return NULL;
542 else if(ps_global->smime && ps_global->smime->catype == Directory
543 && ps_global->smime->capath){
544 if(add_certs_in_dir(lookup, ps_global->smime->capath, ".crt", &ps_global->smime->cacertlist) < 0){
545 X509_STORE_free(store);
546 return NULL;
548 resort_certificates(&ps_global->smime->cacertlist, CACert);
551 if(!(lookup=X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()))){
552 X509_STORE_free(store);
553 return NULL;
556 #ifdef SMIME_SSLCERTS
557 dprint((9, "get_ca_store(): adding cacerts from %s", SMIME_SSLCERTS));
558 X509_LOOKUP_add_dir(lookup, SMIME_SSLCERTS, X509_FILETYPE_PEM);
559 #endif
561 return store;
565 EVP_PKEY *
566 load_key(PERSONAL_CERT *pc, char *pass, int flag)
568 BIO *in;
569 EVP_PKEY *key = NULL;
570 char buf[MAXPATH], file[MAXPATH];
572 if(!(ps_global->smime && pc && pc->name))
573 return key;
575 if(ps_global->smime->privatetype == Container){
576 char *q;
578 if(pc->keytext && (q = strstr(pc->keytext, "-----END")) != NULL){
579 while(*q && *q != '\n')
580 q++;
582 if(*q == '\n')
583 q++;
585 if((in = BIO_new_mem_buf(pc->keytext, q-pc->keytext)) != NULL){
586 key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass);
587 BIO_free(in);
591 else if(ps_global->smime->privatetype == Directory){
592 /* filename is path/name.key */
593 strncpy(buf, pc->name, sizeof(buf)-5);
594 buf[sizeof(buf)-5] = '\0';
595 strncat(buf, ".key", 5);
596 build_path(file, ps_global->smime->privatepath, buf, sizeof(file));
598 if(!(in = BIO_new_file(file, "r")))
599 return NULL;
601 key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass);
602 BIO_free(in);
605 return key;
609 #include <openssl/x509v3.h>
611 * This newer version is from Adrian Vogel. It looks for the email
612 * address not only in the email address field, but also in an
613 * X509v3 extension field, Subject Altenative Name.
615 char **
616 get_x509_subject_email(X509 *x)
618 char **result = NULL;
619 int i, n;
620 STACK_OF(OPENSSL_STRING) *emails = X509_get1_email(x);
621 if ((n = sk_OPENSSL_STRING_num(emails)) > 0) {
622 result = fs_get((n+1)*sizeof(char *));
623 for(i = 0; i < n; i++)
624 result[i] = cpystr(sk_OPENSSL_STRING_value(emails, i));
625 result[i] = NULL;
627 X509_email_free(emails);
628 return result;
633 * Save the certificate for the given email address in
634 * ~/.alpine-smime/public.
636 * Should consider the security hazards in making a file with
637 * the email address that has come from the certificate.
639 * The argument email is destroyed.
641 * args: ctype says where the user wants to save the certificate
643 void
644 save_cert_for(char *email, X509 *cert, WhichCerts ctype)
646 if(!ps_global->smime || ctype == Private)
647 return;
649 dprint((9, "save_cert_for(%s, %s)", email ? email : "?", ctype == Public ? _("Public") : ctype == Private ? _("Private") : "CACert"));
650 emailstrclean(email);
652 if(ps_global->smime->publictype == Keychain){
653 #ifdef APPLEKEYCHAIN
655 OSStatus rc;
656 SecCertificateRef secCertificateRef;
657 CSSM_DATA certData;
659 memset((void *) &certData, 0, sizeof(certData));
660 memset((void *) &secCertificateRef, 0, sizeof(secCertificateRef));
662 /* convert OpenSSL X509 cert data to MacOS certData */
663 if((certData.Length = i2d_X509(cert, &(certData.Data))) > 0){
666 * Put that certData into a SecCertificateRef.
667 * Version 3 should work for versions 1-3.
669 if(!(rc=SecCertificateCreateFromData(&certData,
670 CSSM_CERT_X_509v3,
671 CSSM_CERT_ENCODING_DER,
672 &secCertificateRef))){
674 /* add it to the default keychain */
675 if(!(rc=SecCertificateAddToKeychain(secCertificateRef, NULL))){
676 /* ok */
678 else if(rc == errSecDuplicateItem){
679 dprint((9, "save_cert_for: certificate for %s already in keychain", email));
681 else{
682 dprint((9, "SecCertificateAddToKeychain failed"));
685 else{
686 dprint((9, "SecCertificateCreateFromData failed"));
689 else{
690 dprint((9, "i2d_X509 failed"));
693 #endif /* APPLEKEYCHAIN */
695 else if(SMHOLDERTYPE(ctype) == Container){
696 REMDATA_S *rd = NULL;
697 char *ret_dir = NULL;
698 char path[MAXPATH];
699 char fpath[MAXPATH];
700 char *upath = PATHCERTDIR(ctype);
701 char *tempfile = NULL;
702 int err = 0;
703 CertList *clist = DATACERT(ctype);
705 add_to_end_of_certlist(&clist, email, X509_dup(cert));
707 switch(ctype){
708 case Private: ps_global->smime->privatecertlist = clist; break;
709 case Public : ps_global->smime->publiccertlist = clist; break;
710 case CACert : ps_global->smime->cacertlist = clist; break;
711 default: break;
714 if(!upath)
715 return;
717 if(IS_REMOTE(upath)){
718 rd = rd_create_remote(RemImap, upath, REMOTE_SMIME_SUBTYPE,
719 NULL, "Error: ",
720 _("Can't access remote smime configuration."));
721 if(!rd){
722 return;
725 (void) rd_read_metadata(rd);
727 if(rd->access == MaybeRorW){
728 if(rd->read_status == 'R')
729 rd->access = ReadOnly;
730 else
731 rd->access = ReadWrite;
734 if(rd->access != NoExists){
736 rd_check_remvalid(rd, 1L);
739 * If the cached info says it is readonly but
740 * it looks like it's been fixed now, change it to readwrite.
742 if(rd->read_status == 'R'){
743 rd_check_readonly_access(rd);
744 if(rd->read_status == 'W'){
745 rd->access = ReadWrite;
746 rd->flags |= REM_OUTOFDATE;
748 else
749 rd->access = ReadOnly;
753 if(rd->flags & REM_OUTOFDATE){
754 if(rd_update_local(rd) != 0){
756 dprint((1, "save_cert_for: rd_update_local failed\n"));
757 rd_close_remdata(&rd);
758 return;
761 else
762 rd_open_remote(rd);
764 if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
765 rd_close_remdata(&rd);
766 return;
769 rd->flags |= DO_REMTRIM;
771 strncpy(path, rd->lf, sizeof(path)-1);
772 path[sizeof(path)-1] = '\0';
774 else{
775 strncpy(path, upath, sizeof(path)-1);
776 path[sizeof(path)-1] = '\0';
779 tempfile = tempfile_in_same_dir(path, "az", &ret_dir);
780 if(tempfile){
781 if(certlist_to_file(tempfile, DATACERT(ctype)))
782 err++;
784 if(!err && ret_dir){
785 if(IS_REMOTE(upath)){
786 strncpy(fpath, rd->lf, sizeof(fpath));
787 fpath[sizeof(fpath)-1] = '\0';
789 else{
790 if(strlen(path) + strlen(tempfile) - strlen(ret_dir) + 1 < sizeof(path))
791 snprintf(fpath, sizeof(fpath), "%s%c%s",
792 path, tempfile[strlen(ret_dir)], tempfile + strlen(ret_dir) + 1);
793 else
794 err++;
797 else err++;
799 fs_give((void **)&ret_dir);
801 if(!err){
802 if(rename_file(tempfile, fpath) < 0){
803 q_status_message2(SM_ORDER, 3, 3,
804 _("Can't rename %s to %s"), tempfile, fpath);
805 err++;
809 if(!err && IS_REMOTE(upath)){
810 int e, we_cancel;
811 char datebuf[200];
813 datebuf[0] = '\0';
815 we_cancel = busy_cue(_("Copying to remote smime container"), NULL, 1);
816 if((e = rd_update_remote(rd, datebuf)) != 0){
817 if(e == -1){
818 q_status_message2(SM_ORDER | SM_DING, 3, 5,
819 _("Error opening temporary smime file %s: %s"),
820 rd->lf, error_description(errno));
821 dprint((1,
822 "write_remote_smime: error opening temp file %s\n",
823 rd->lf ? rd->lf : "?"));
825 else{
826 q_status_message2(SM_ORDER | SM_DING, 3, 5,
827 _("Error copying to %s: %s"),
828 rd->rn, error_description(errno));
829 dprint((1,
830 "write_remote_smime: error copying from %s to %s\n",
831 rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
834 q_status_message(SM_ORDER | SM_DING, 5, 5,
835 _("Copy of smime cert to remote folder failed, changes NOT saved remotely"));
837 else{
838 rd_update_metadata(rd, datebuf);
839 rd->read_status = 'W';
842 rd_close_remdata(&rd);
844 if(we_cancel)
845 cancel_busy_cue(-1);
848 fs_give((void **) &tempfile);
851 else if(SMHOLDERTYPE(ctype) == Directory){
852 char *path = PATHCERTDIR(ctype);
853 char certfilename[MAXPATH];
854 BIO *bio_out;
856 build_path(certfilename, path, email, sizeof(certfilename));
857 strncat(certfilename, ".crt", sizeof(certfilename)-1-strlen(certfilename));
858 certfilename[sizeof(certfilename)-1] = 0;
860 bio_out = BIO_new_file(certfilename, "w");
861 if(bio_out){
862 PEM_write_bio_X509(bio_out, cert);
863 BIO_free(bio_out);
864 q_status_message1(SM_ORDER, 1, 1, _("Saved certificate for <%s>"), email);
866 else{
867 q_status_message1(SM_ORDER, 1, 1, _("Couldn't save certificate for <%s>"), email);
874 * Try to retrieve the certificate for the given email address.
875 * The caller should free the cert.
877 X509 *
878 get_cert_for(char *email, WhichCerts ctype, int tolower)
880 char certfilename[MAXPATH];
881 char emailaddr[MAXPATH];
882 X509 *cert = NULL;
883 BIO *in;
885 if(ctype == Password){
886 build_path(certfilename, PATHCERTDIR(ctype), email, sizeof(certfilename));
887 strncat(certfilename, EXTCERT(Public), sizeof(certfilename)-1-strlen(certfilename));
888 certfilename[sizeof(certfilename)-1] = 0;
890 if((in = BIO_new_file(certfilename, "r"))!=0){
892 cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
894 if(cert){
895 /* could check email addr in cert matches */
898 BIO_free(in);
901 return cert;
904 if(!ps_global->smime)
905 return cert;
907 dprint((9, "get_cert_for(%s, %s)", email ? email : "?", "none yet"));
909 if(ctype == Private) /* there is no private certificate info */
910 ctype = Public; /* return public information instead */
911 strncpy(emailaddr, email, sizeof(emailaddr)-1);
912 emailaddr[sizeof(emailaddr)-1] = 0;
914 /* clean it up (lowercase, space removal) */
915 if(tolower)
916 emailstrclean(emailaddr);
918 if(ps_global->smime->publictype == Keychain){
919 #ifdef APPLEKEYCHAIN
921 OSStatus rc;
922 SecKeychainItemRef itemRef = nil;
923 SecKeychainAttributeList attrList;
924 SecKeychainAttribute attrib;
925 SecKeychainSearchRef searchRef = nil;
926 CSSM_DATA certData;
928 /* low-level form of MacOS data */
929 memset((void *) &certData, 0, sizeof(certData));
931 attrList.count = 1;
932 attrList.attr = &attrib;
934 /* kSecAlias means email address for a certificate */
935 attrib.tag = kSecAlias;
936 attrib.data = emailaddr;
937 attrib.length = strlen(attrib.data);
939 /* Find the certificate in the default keychain */
940 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
941 kSecCertificateItemClass,
942 &attrList,
943 &searchRef))){
945 if(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef))){
947 /* extract the data portion of the certificate */
948 if(!(rc=SecCertificateGetData((SecCertificateRef) itemRef, &certData))){
951 * Convert it from MacOS form to OpenSSL form.
952 * The input is certData from above and the output
953 * is the X509 *cert.
955 if(!d2i_X509(&cert, &(certData.Data), certData.Length)){
956 dprint((9, "d2i_X509 failed"));
959 else{
960 dprint((9, "SecCertificateGetData failed"));
963 else if(rc == errSecItemNotFound){
964 dprint((9, "get_cert_for: Public cert for %s not found", emailaddr));
966 else{
967 dprint((9, "SecKeychainSearchCopyNext failed"));
970 else{
971 dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
974 if(searchRef)
975 CFRelease(searchRef);
977 #endif /* APPLEKEYCHAIN */
979 else if(SMHOLDERTYPE(ctype) == Container){
980 CertList *cl;
982 for(cl = DATACERT(ctype); cl; cl = cl->next){
983 if(cl->name && !strucmp(emailaddr, cl->name))
984 break;
987 if(cl)
988 cert = X509_dup((X509 *) cl->x509_cert);
990 else if(SMHOLDERTYPE(ctype) == Directory){
991 build_path(certfilename, PATHCERTDIR(ctype), emailaddr, sizeof(certfilename));
992 strncat(certfilename, EXTCERT(ctype), sizeof(certfilename)-1-strlen(certfilename));
993 certfilename[sizeof(certfilename)-1] = 0;
995 if((in = BIO_new_file(certfilename, "r"))!=0){
997 cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
999 if(cert){
1000 /* could check email addr in cert matches */
1003 BIO_free(in);
1007 return cert;
1011 * load_cert_for_key finds a certificate in pathdir that matches a private key
1012 * pkey. It returns its name in certfile, and the certificate in *pcert.
1013 * return value: success: different from zero, failure 0. If both certfile
1014 * and pcert are NULL, this function returns if there is certificate that
1015 * matches the given key.
1018 load_cert_for_key(char *pathdir, EVP_PKEY *pkey, char **certfile, X509 **pcert)
1020 DIR *dirp;
1021 struct dirent *d;
1022 int rv = 0;
1023 BIO *in;
1024 X509 *x;
1025 char buf[MAXPATH+1], pathcert[MAXPATH+1];
1027 if(pathdir == NULL || pkey == NULL)
1028 return 0;
1030 if(certfile) *certfile = NULL;
1031 if(pcert) *pcert = NULL;
1033 if((dirp = opendir(pathdir)) != NULL){
1034 while(rv == 0 && (d=readdir(dirp)) != NULL){
1035 size_t ll;
1037 if((ll=strlen(d->d_name)) && ll > 4){
1038 if(!strcmp(d->d_name+ll-4, ".crt")){
1039 strncpy(buf, d->d_name, sizeof(buf));
1040 buf[sizeof(buf)-1] = '\0';
1041 build_path(pathcert, pathdir, buf, sizeof(pathcert));
1042 if((in = BIO_new_file(pathcert, "r")) != NULL){
1043 if((x = PEM_read_bio_X509(in, NULL, NULL, NULL)) != NULL){
1044 if(X509_check_private_key(x, pkey) > 0){
1045 rv = 1;
1046 if(certfile) *certfile = cpystr(buf);
1047 if(pcert) *pcert = x;
1049 else
1050 X509_free(x);
1052 BIO_free(in);
1057 closedir(dirp);
1059 return rv;
1063 PERSONAL_CERT *
1064 mem_to_personal_certs(char *contents)
1066 PERSONAL_CERT *result = NULL;
1067 char *p, *q, *line, *name, *keytext, *save_p;
1068 X509 *cert = NULL;
1070 if(contents && *contents){
1071 for(p = contents; *p != '\0';){
1072 line = p;
1074 while(*p && *p != '\n')
1075 p++;
1077 save_p = NULL;
1078 if(*p == '\n'){
1079 save_p = p;
1080 *p++ = '\0';
1083 if(strncmp(EMAILADDRLEADER, line, strlen(EMAILADDRLEADER)) == 0){
1084 name = line + strlen(EMAILADDRLEADER);
1085 cert = get_cert_for(name, Public, 1);
1086 keytext = p;
1088 /* advance p past this record */
1089 if((q = strstr(keytext, "-----END")) != NULL){
1090 while(*q && *q != '\n')
1091 q++;
1093 if(*q == '\n')
1094 q++;
1096 p = q;
1098 else{
1099 p = p + strlen(p);
1100 q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error in privatekey container, missing END"));
1103 if(cert){
1104 PERSONAL_CERT *pc;
1106 pc = (PERSONAL_CERT *) fs_get(sizeof(*pc));
1107 pc->cert = cert;
1108 pc->name = cpystr(name);
1109 pc->keytext = keytext; /* a pointer into contents */
1111 pc->key = load_key(pc, "", SM_NORMALCERT);
1113 pc->next = result;
1114 result = pc;
1118 if(save_p)
1119 *save_p = '\n';
1123 return result;
1127 CertList *
1128 mem_to_certlist(char *contents, WhichCerts ctype)
1130 CertList *ret = NULL;
1131 char *p, *q, *line, *name, *certtext, *save_p;
1132 X509 *cert = NULL;
1133 BIO *in;
1134 char *sep = (ctype == Public || ctype == Private)
1135 ? EMAILADDRLEADER : CACERTSTORELEADER;
1137 if(contents && *contents){
1138 for(p = contents; *p != '\0';){
1139 line = p;
1141 while(*p && *p != '\n')
1142 p++;
1144 save_p = NULL;
1145 if(*p == '\n'){
1146 save_p = p;
1147 *p++ = '\0';
1150 if(strncmp(sep, line, strlen(sep)) == 0){
1151 name = line + strlen(sep);
1152 cert = NULL;
1153 certtext = strstr(p, "-----BEGIN");
1154 if(certtext != NULL){
1155 if((q = strstr(certtext, sep)) != NULL)
1156 p = q;
1157 else
1158 p = q = certtext+strlen(certtext);
1160 if((in = BIO_new_mem_buf(certtext, q-certtext)) != 0){
1161 cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
1162 BIO_free(in);
1165 else{
1166 q_status_message2(SM_ORDER | SM_DING, 3, 3, _("Error in %scert container, missing BEGIN, certtext=%s"), ctype == Public ? _("public") : _("ca"), p);
1167 p = p + strlen(p);
1170 if(name && cert)
1171 add_to_end_of_certlist(&ret, name, cert);
1174 if(save_p)
1175 *save_p = '\n';
1178 if(ret != NULL)
1179 resort_certificates(&ret, ctype);
1181 return ret;
1186 * Add the CACert Container contents into the CACert store.
1188 * Returns > 0 for success, 0 for failure
1191 mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup)
1193 char *p, *q, *line, *certtext, *save_p;
1194 BIO *in, *out;
1195 int len, failed = 0;
1196 char *tempfile;
1197 char iobuf[4096];
1200 * The most straight-forward way to do this is to write
1201 * the container contents to a temp file and then load the
1202 * contents of the file with X509_LOOKUP_load_file(), like
1203 * is done in add_certs_in_dir(). What we don't know is if
1204 * each file should consist of one cacert or if they can all
1205 * just be jammed together into one file. To be safe, we'll use
1206 * one file per and do each in a separate operation.
1209 if(contents && *contents){
1210 for(p = contents; *p != '\0';){
1211 line = p;
1213 while(*p && *p != '\n')
1214 p++;
1216 save_p = NULL;
1217 if(*p == '\n'){
1218 save_p = p;
1219 *p++ = '\0';
1222 /* look for separator line */
1223 if(strncmp(CACERTSTORELEADER, line, strlen(CACERTSTORELEADER)) == 0){
1224 /* certtext is the content that should go in a file */
1225 certtext = strstr(p, "-----BEGIN");
1226 if(certtext != NULL){
1227 if((q = strstr(certtext, CACERTSTORELEADER)) != NULL){
1228 p = q;
1230 else{ /* end of file */
1231 q = certtext + strlen(certtext);
1232 p = q;
1235 in = BIO_new_mem_buf(certtext, q-certtext);
1236 if(in){
1237 tempfile = temp_nam(NULL, "az");
1238 out = tempfile != NULL ? BIO_new_file(tempfile, "w") : NULL;
1239 if(out != NULL){
1240 while((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
1241 BIO_write(out, iobuf, len);
1243 BIO_free(out);
1244 if(!X509_LOOKUP_load_file(lookup, tempfile, X509_FILETYPE_PEM))
1245 failed++;
1248 if(tempfile != NULL){
1249 unlink(tempfile);
1250 fs_give((void **) &tempfile);
1253 BIO_free(in);
1256 else{
1257 p = p + strlen(p);
1258 q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext);
1261 else{
1262 p = p + strlen(p);
1263 q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error in cacert container, missing separator, line=%s"), line);
1266 if(save_p)
1267 *save_p = '\n';
1271 return(!failed);
1276 certlist_to_file(char *filename, CertList *certlist)
1278 CertList *cl;
1279 BIO *bio_out = NULL;
1280 int ret = -1;
1282 if(filename && (bio_out=BIO_new_file(filename, "w")) != NULL){
1283 ret = 0;
1284 for(cl = certlist; cl; cl = cl->next){
1285 if(cl->name && cl->name[0] && cl->x509_cert){
1286 if(!((BIO_puts(bio_out, EMAILADDRLEADER) > 0)
1287 && (BIO_puts(bio_out, cl->name) > 0)
1288 && (BIO_puts(bio_out, "\n") > 0)))
1289 ret = -1;
1291 if(!PEM_write_bio_X509(bio_out, (X509 *) cl->x509_cert))
1292 ret = -1;
1296 BIO_free(bio_out);
1299 return ret;
1303 void
1304 add_to_end_of_certlist(CertList **cl, char *name, X509 *cert)
1306 CertList *new;
1308 if(!cl)
1309 return;
1311 new = smime_X509_to_cert_info(cert, name);
1312 new->next = *cl;
1313 *cl = new;
1317 void
1318 free_certlist(CertList **cl)
1320 if(cl && *cl){
1321 if((*cl)->data.date_from)
1322 fs_give((void **) &(*cl)->data.date_from);
1324 if((*cl)->data.date_to)
1325 fs_give((void **) &(*cl)->data.date_to);
1327 if((*cl)->data.md5)
1328 fs_give((void **) &(*cl)->data.md5);
1330 if((*cl)->name)
1331 fs_give((void **) &(*cl)->name);
1333 if((*cl)->cn)
1334 fs_give((void **) &(*cl)->cn);
1336 if((*cl)->x509_cert)
1337 X509_free((X509 *) (*cl)->x509_cert);
1339 free_certlist(&(*cl)->next);
1341 fs_give((void **) cl);
1346 void
1347 free_personal_certs(PERSONAL_CERT **pc)
1349 if(pc && *pc){
1350 free_personal_certs(&(*pc)->next);
1351 if((*pc)->name)
1352 fs_give((void **) &(*pc)->name);
1354 if((*pc)->name)
1355 fs_give((void **) &(*pc)->name);
1357 if((*pc)->cert)
1358 X509_free((*pc)->cert);
1360 if((*pc)->key)
1361 EVP_PKEY_free((*pc)->key);
1363 fs_give((void **) pc);
1367 #endif /* SMIME */