1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
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"
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"
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.
60 setup_certs_backup_by_type(WhichCerts ctype
)
62 int rv
= 0; /* assume success */
66 char p
[MAXPATH
+1]; /* path to where the backup is */
67 char buf
[MAXPATH
+1], buf2
[MAXPATH
+1];
71 struct dirent
*df
; /* file in the directory */
76 return rv
; /* remove when this function is complete */
78 if(SMHOLDERTYPE(ctype
) == Directory
){
79 d
= PATHCERTDIR(ctype
);
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
);
84 if(our_stat(p
, &sbuf
) < 0){
85 if(our_mkpath(p
, 0700) != 0)
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)
94 else if((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
)
97 strncpy(p
, buf2
, MAXPATH
);
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
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;
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;
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)
127 } else if((sbuf
.st_mode
& S_IFMT
) == S_IFREG
)
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
);
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
);
152 for (cl2
= data
; cl2
&& cl2
->next
; cl2
= cl2
->next
);
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
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 */
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
)
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)
195 /* ok, if it is not in the list, and it is a certificate. Add it */
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 */
204 cert
= fs_get(sizeof(CertList
));
205 memset((void *)cert
, 0, sizeof(CertList
));
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 */
218 for (cl2
= data
; cl2
&& cl2
->next
; cl2
= cl2
->next
);
227 /* here we must check it is a key of some cert....*/
230 default: alpine_panic("Bad ctype (1)");
236 /* Now that we are here, we have all the information in the backup
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 */
254 smime_get_cn(X509_NAME
*subject
)
258 e
= X509_NAME_get_entry(subject
, X509_NAME_entry_count(subject
)-1);
260 X509_NAME_get_text_by_OBJ(subject
, e
->object
, buf
, sizeof(buf
));
265 compare_certs_by_name(const void *data1
, const void *data2
)
270 CertList
*cl1
= *(CertList
**) data1
;
271 CertList
*cl2
= *(CertList
**) data2
;
274 if((s
= strchr(cl1
->name
, '@')) != NULL
){
279 if((s
= strchr(cl2
->name
, '@')) != NULL
){
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
] = '@';
292 resort_certificates(CertList
**data
, WhichCerts ctype
)
295 CertList
*cl
= *data
;
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';
308 cll
= fs_get(i
*sizeof(CertList
*));
309 for(cl
= *data
, i
= 0; cl
; cl
= cl
->next
, i
++)
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
;
325 get_fingerprint(X509
*cert
, const EVP_MD
*type
, char *buf
, size_t maxLen
, char *s
)
327 unsigned char md
[128];
333 X509_digest(cert
, type
, md
, &len
);
337 for(i
=0; i
<len
; i
++){
341 if(i
!= 0 && s
&& *s
)
344 snprintf(b
, maxLen
- (b
-buf
), "%02x", md
[i
]);
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
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 */
388 smime_get_date(ASN1_GENERALIZEDTIME
*tm
)
390 BIO
*mb
= BIO_new(BIO_s_mem());
392 char date
[MAILTMPLEN
];
393 char buf
[MAILTMPLEN
];
394 char *m
, *d
, *t
, *y
, *z
;
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"
409 d
= strchr(iobuf
, ' ');
411 while(*d
== ' ') d
++;
412 t
= strchr(d
+1, ' ');
414 while(*t
== ' ') t
++;
415 y
= strchr(t
+1, ' ');
417 while(*y
== ' ') y
++;
418 z
= strchr(y
+1, ' ');
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
);
433 snprintf(buf
, sizeof(buf
), "%s/%s/%s", m
, d
, y
+ strlen(y
) - 2);
434 buf
[sizeof(buf
)-1] = '\0';
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
)
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
);
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
);
483 for (cl
= *cdata
; cl
&& cl
->next
; cl
= cl
->next
);
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.
509 X509_STORE
*store
= NULL
;
511 dprint((9, "get_ca_store()"));
513 if(!(store
=X509_STORE_new())){
514 dprint((9, "X509_STORE_new() failed"));
518 if(!(lookup
=X509_STORE_add_lookup(store
, X509_LOOKUP_file()))){
519 dprint((9, "X509_STORE_add_lookup() failed"));
520 X509_STORE_free(store
);
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
);
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
);
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
);
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
);
556 load_key(PERSONAL_CERT
*pc
, char *pass
, int flag
)
559 EVP_PKEY
*key
= NULL
;
560 char buf
[MAXPATH
], file
[MAXPATH
];
562 if(!(ps_global
->smime
&& pc
&& pc
->name
))
565 if(ps_global
->smime
->privatetype
== Container
){
568 if(pc
->keytext
&& (q
= strstr(pc
->keytext
, "-----END")) != NULL
){
569 while(*q
&& *q
!= '\n')
575 if((in
= BIO_new_mem_buf(pc
->keytext
, q
-pc
->keytext
)) != NULL
){
576 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
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")))
591 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
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.
606 get_x509_subject_email(X509
*x
)
608 char **result
= NULL
;
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
));
617 X509_email_free(emails
);
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
634 save_cert_for(char *email
, X509
*cert
, WhichCerts ctype
)
636 if(!ps_global
->smime
|| ctype
== Private
)
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
){
646 SecCertificateRef secCertificateRef
;
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
,
661 CSSM_CERT_ENCODING_DER
,
662 &secCertificateRef
))){
664 /* add it to the default keychain */
665 if(!(rc
=SecCertificateAddToKeychain(secCertificateRef
, NULL
))){
668 else if(rc
== errSecDuplicateItem
){
669 dprint((9, "save_cert_for: certificate for %s already in keychain", email
));
672 dprint((9, "SecCertificateAddToKeychain failed"));
676 dprint((9, "SecCertificateCreateFromData failed"));
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
;
690 char *upath
= PATHCERTDIR(ctype
);
691 char *tempfile
= NULL
;
693 CertList
*clist
= DATACERT(ctype
);
695 add_to_end_of_certlist(&clist
, email
, X509_dup(cert
));
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;
707 if(IS_REMOTE(upath
)){
708 rd
= rd_create_remote(RemImap
, upath
, REMOTE_SMIME_SUBTYPE
,
710 _("Can't access remote smime configuration."));
715 (void) rd_read_metadata(rd
);
717 if(rd
->access
== MaybeRorW
){
718 if(rd
->read_status
== 'R')
719 rd
->access
= ReadOnly
;
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
;
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
);
754 if(rd
->access
!= ReadWrite
|| rd_remote_is_readonly(rd
)){
755 rd_close_remdata(&rd
);
759 rd
->flags
|= DO_REMTRIM
;
761 strncpy(path
, rd
->lf
, sizeof(path
)-1);
762 path
[sizeof(path
)-1] = '\0';
765 strncpy(path
, upath
, sizeof(path
)-1);
766 path
[sizeof(path
)-1] = '\0';
769 tempfile
= tempfile_in_same_dir(path
, "az", &ret_dir
);
771 if(certlist_to_file(tempfile
, DATACERT(ctype
)))
775 if(IS_REMOTE(upath
)){
776 strncpy(fpath
, rd
->lf
, sizeof(fpath
));
777 fpath
[sizeof(fpath
)-1] = '\0';
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);
789 fs_give((void **)&ret_dir
);
792 if(rename_file(tempfile
, fpath
) < 0){
793 q_status_message2(SM_ORDER
, 3, 3,
794 _("Can't rename %s to %s"), tempfile
, fpath
);
799 if(!err
&& IS_REMOTE(upath
)){
805 we_cancel
= busy_cue(_("Copying to remote smime container"), NULL
, 1);
806 if((e
= rd_update_remote(rd
, datebuf
)) != 0){
808 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
809 _("Error opening temporary smime file %s: %s"),
810 rd
->lf
, error_description(errno
));
812 "write_remote_smime: error opening temp file %s\n",
813 rd
->lf
? rd
->lf
: "?"));
816 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
817 _("Error copying to %s: %s"),
818 rd
->rn
, error_description(errno
));
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"));
828 rd_update_metadata(rd
, datebuf
);
829 rd
->read_status
= 'W';
832 rd_close_remdata(&rd
);
838 fs_give((void **) &tempfile
);
841 else if(SMHOLDERTYPE(ctype
) == Directory
){
842 char *path
= PATHCERTDIR(ctype
);
843 char certfilename
[MAXPATH
];
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");
852 PEM_write_bio_X509(bio_out
, cert
);
854 q_status_message1(SM_ORDER
, 1, 1, _("Saved certificate for <%s>"), email
);
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.
868 get_cert_for(char *email
, WhichCerts ctype
, int tolower
)
870 char certfilename
[MAXPATH
];
871 char emailaddr
[MAXPATH
];
875 if(!ps_global
->smime
)
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) */
887 emailstrclean(emailaddr
);
889 if(ps_global
->smime
->publictype
== Keychain
){
893 SecKeychainItemRef itemRef
= nil
;
894 SecKeychainAttributeList attrList
;
895 SecKeychainAttribute attrib
;
896 SecKeychainSearchRef searchRef
= nil
;
899 /* low-level form of MacOS data */
900 memset((void *) &certData
, 0, sizeof(certData
));
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
,
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
926 if(!d2i_X509(&cert
, &(certData
.Data
), certData
.Length
)){
927 dprint((9, "d2i_X509 failed"));
931 dprint((9, "SecCertificateGetData failed"));
934 else if(rc
== errSecItemNotFound
){
935 dprint((9, "get_cert_for: Public cert for %s not found", emailaddr
));
938 dprint((9, "SecKeychainSearchCopyNext failed"));
942 dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
946 CFRelease(searchRef
);
948 #endif /* APPLEKEYCHAIN */
950 else if(SMHOLDERTYPE(ctype
) == Container
){
953 for(cl
= DATACERT(ctype
); cl
; cl
= cl
->next
){
954 if(cl
->name
&& !strucmp(emailaddr
, cl
->name
))
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
);
971 /* could check email addr in cert matches */
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
)
996 char buf
[MAXPATH
+1], pathcert
[MAXPATH
+1];
998 if(pathdir
== NULL
|| pkey
== NULL
)
1001 if(certfile
) *certfile
= NULL
;
1002 if(pcert
) *pcert
= NULL
;
1004 if((dirp
= opendir(pathdir
)) != NULL
){
1005 while(rv
== 0 && (d
=readdir(dirp
)) != NULL
){
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){
1017 if(certfile
) *certfile
= cpystr(buf
);
1018 if(pcert
) *pcert
= x
;
1035 mem_to_personal_certs(char *contents
)
1037 PERSONAL_CERT
*result
= NULL
;
1038 char *p
, *q
, *line
, *name
, *keytext
, *save_p
;
1041 if(contents
&& *contents
){
1042 for(p
= contents
; *p
!= '\0';){
1045 while(*p
&& *p
!= '\n')
1054 if(strncmp(EMAILADDRLEADER
, line
, strlen(EMAILADDRLEADER
)) == 0){
1055 name
= line
+ strlen(EMAILADDRLEADER
);
1056 cert
= get_cert_for(name
, Public
, 1);
1059 /* advance p past this record */
1060 if((q
= strstr(keytext
, "-----END")) != NULL
){
1061 while(*q
&& *q
!= '\n')
1071 q_status_message(SM_ORDER
| SM_DING
, 3, 3, _("Error in privatekey container, missing END"));
1077 pc
= (PERSONAL_CERT
*) fs_get(sizeof(*pc
));
1079 pc
->name
= cpystr(name
);
1080 pc
->keytext
= keytext
; /* a pointer into contents */
1082 pc
->key
= load_key(pc
, "", SM_NORMALCERT
);
1099 mem_to_certlist(char *contents
, WhichCerts ctype
)
1101 CertList
*ret
= NULL
;
1102 char *p
, *q
, *line
, *name
, *certtext
, *save_p
;
1105 char *sep
= (ctype
== Public
|| ctype
== Private
)
1106 ? EMAILADDRLEADER
: CACERTSTORELEADER
;
1108 if(contents
&& *contents
){
1109 for(p
= contents
; *p
!= '\0';){
1112 while(*p
&& *p
!= '\n')
1121 if(strncmp(sep
, line
, strlen(sep
)) == 0){
1122 name
= line
+ strlen(sep
);
1124 certtext
= strstr(p
, "-----BEGIN");
1125 if(certtext
!= NULL
){
1126 if((q
= strstr(certtext
, sep
)) != NULL
)
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
);
1137 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, _("Error in %scert container, missing BEGIN, certtext=%s"), ctype
== Public
? _("public") : _("ca"), p
);
1142 add_to_end_of_certlist(&ret
, name
, cert
);
1150 resort_certificates(&ret
, ctype
);
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
;
1166 int len
, failed
= 0;
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';){
1184 while(*p
&& *p
!= '\n')
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
){
1201 else{ /* end of file */
1202 q
= certtext
+ strlen(certtext
);
1206 in
= BIO_new_mem_buf(certtext
, q
-certtext
);
1208 tempfile
= temp_nam(NULL
, "az");
1209 out
= tempfile
!= NULL
? BIO_new_file(tempfile
, "w") : NULL
;
1211 while((len
= BIO_read(in
, iobuf
, sizeof(iobuf
))) > 0)
1212 BIO_write(out
, iobuf
, len
);
1215 if(!X509_LOOKUP_load_file(lookup
, tempfile
, X509_FILETYPE_PEM
))
1219 if(tempfile
!= NULL
){
1221 fs_give((void **) &tempfile
);
1229 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext
);
1234 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing separator, line=%s"), line
);
1247 certlist_to_file(char *filename
, CertList
*certlist
)
1250 BIO
*bio_out
= NULL
;
1253 if(filename
&& (bio_out
=BIO_new_file(filename
, "w")) != NULL
){
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)))
1262 if(!PEM_write_bio_X509(bio_out
, (X509
*) cl
->x509_cert
))
1275 add_to_end_of_certlist(CertList
**cl
, char *name
, X509
*cert
)
1277 CertList
*new, *clp
;
1278 char buf
[MAILTMPLEN
];
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
);
1299 for(clp
= (*cl
); clp
->next
; clp
= clp
->next
)
1308 free_certlist(CertList
**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
);
1318 fs_give((void **) &(*cl
)->data
.md5
);
1321 fs_give((void **) &(*cl
)->name
);
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
);
1337 free_personal_certs(PERSONAL_CERT
**pc
)
1340 free_personal_certs(&(*pc
)->next
);
1342 fs_give((void **) &(*pc
)->name
);
1345 fs_give((void **) &(*pc
)->name
);
1348 X509_free((*pc
)->cert
);
1351 EVP_PKEY_free((*pc
)->key
);
1353 fs_give((void **) pc
);