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-2014 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"
38 #include <Security/SecKeychain.h>
39 #include <Security/SecKeychainItem.h>
40 #include <Security/SecKeychainSearch.h>
41 #include <Security/SecCertificate.h>
42 #endif /* APPLEKEYCHAIN */
45 /* internal prototypes */
46 static char *emailstrclean(char *string
);
47 static int mem_add_extra_cacerts(char *contents
, X509_LOOKUP
*lookup
);
50 get_fingerprint(X509
*cert
, const EVP_MD
*type
, char *buf
, size_t maxLen
)
52 unsigned char md
[128];
58 X509_digest(cert
, type
, md
, &len
);
69 snprintf(b
, maxLen
- (b
-buf
), "%02x", md
[i
]);
76 * Remove leading whitespace, trailing whitespace and convert
77 * to lowercase. Also remove slash characters
79 * Args: s, -- The string to clean
81 * Result: the cleaned string
84 emailstrclean(char *string
)
86 char *s
= string
, *sc
= NULL
, *p
= NULL
;
88 for(; *s
; s
++){ /* single pass */
89 if(!isspace((unsigned char) (*s
))){
90 p
= NULL
; /* not start of blanks */
91 if(!sc
) /* first non-blank? */
92 sc
= string
; /* start copying */
94 else if(!p
) /* it's OK if sc == NULL */
95 p
= sc
; /* start of blanks? */
97 if(sc
&& *s
!='/' && *s
!='\\') /* if copying, copy */
98 *sc
++ = isupper((unsigned char) (*s
))
99 ? (unsigned char) tolower((unsigned char) (*s
))
100 : (unsigned char) (*s
);
103 if(p
) /* if ending blanks */
104 *p
= '\0'; /* tie off beginning */
105 else if(!sc
) /* never saw a non-blank */
106 *string
= '\0'; /* so tie whole thing off */
113 * Add a lookup for each "*.crt" file in the given directory.
116 add_certs_in_dir(X509_LOOKUP
*lookup
, char *path
, char *ext
, CertList
**cdata
)
124 if((dirp
= opendir(path
)) != NULL
){
125 while(!ret
&& (d
=readdir(dirp
)) != NULL
){
126 if(srchrstr(d
->d_name
, ext
)){
127 build_path(buf
, path
, d
->d_name
, sizeof(buf
));
129 if(!X509_LOOKUP_load_file(lookup
, buf
, X509_FILETYPE_PEM
)){
130 q_status_message1(SM_ORDER
, 3, 3, _("Error loading file %s"), buf
);
134 cert
= fs_get(sizeof(CertList
));
135 memset((void *)cert
, 0, sizeof(CertList
));
136 cert
->name
= cpystr(d
->d_name
);
140 for (cl
= *cdata
; cl
&& cl
->next
; cl
= cl
->next
);
158 * Get an X509_STORE. This consists of the system
159 * certs directory and any certificates in the user's
160 * ~/.alpine-smime/ca directory.
166 X509_STORE
*store
= NULL
;
168 dprint((9, "get_ca_store()"));
170 if(!(store
=X509_STORE_new())){
171 dprint((9, "X509_STORE_new() failed"));
175 if(!(lookup
=X509_STORE_add_lookup(store
, X509_LOOKUP_file()))){
176 dprint((9, "X509_STORE_add_lookup() failed"));
177 X509_STORE_free(store
);
181 if(ps_global
->smime
&& ps_global
->smime
->catype
== Container
182 && ps_global
->smime
->cacontent
){
184 if(!mem_add_extra_cacerts(ps_global
->smime
->cacontent
, lookup
)){
185 X509_STORE_free(store
);
189 else if(ps_global
->smime
&& ps_global
->smime
->catype
== Directory
190 && ps_global
->smime
->capath
){
191 if(add_certs_in_dir(lookup
, ps_global
->smime
->capath
, ".crt", &ps_global
->smime
->cacertlist
) < 0){
192 X509_STORE_free(store
);
197 if(!(lookup
=X509_STORE_add_lookup(store
, X509_LOOKUP_hash_dir()))){
198 X509_STORE_free(store
);
202 #ifdef SMIME_SSLCERTS
203 dprint((9, "get_ca_store(): adding cacerts from %s", SMIME_SSLCERTS
));
204 X509_LOOKUP_add_dir(lookup
, SMIME_SSLCERTS
, X509_FILETYPE_PEM
);
212 load_key(PERSONAL_CERT
*pc
, char *pass
)
215 EVP_PKEY
*key
= NULL
;
216 char buf
[MAXPATH
], file
[MAXPATH
];
218 if(!(ps_global
->smime
&& pc
&& pc
->name
))
221 if(ps_global
->smime
->privatetype
== Container
){
224 if(pc
->keytext
&& (q
= strstr(pc
->keytext
, "-----END")) != NULL
){
225 while(*q
&& *q
!= '\n')
231 if((in
= BIO_new_mem_buf(pc
->keytext
, q
-pc
->keytext
)) != NULL
){
232 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
237 else if(ps_global
->smime
->privatetype
== Directory
){
238 /* filename is path/name.key */
239 strncpy(buf
, pc
->name
, sizeof(buf
)-5);
240 buf
[sizeof(buf
)-5] = '\0';
241 strncat(buf
, ".key", 5);
242 build_path(file
, ps_global
->smime
->privatepath
, buf
, sizeof(file
));
244 if(!(in
= BIO_new_file(file
, "r")))
247 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
257 get_x509_name_entry(const char *key
, X509_NAME
*name
)
266 c
= X509_NAME_entry_count(name
);
271 e
= X509_NAME_get_entry(name
, i
);
278 n
= OBJ_obj2nid(e
->object
);
279 if((n
== NID_undef
) || ((id
=(char*) OBJ_nid2sn(n
)) == NULL
)){
280 i2t_ASN1_OBJECT(buf
, sizeof(buf
), e
->object
);
284 if((strucmp(id
, "email")==0) || (strucmp(id
, "emailAddress")==0)){
285 X509_NAME_get_text_by_OBJ(name
, e
->object
, buf
, sizeof(buf
)-1);
295 get_x509_subject_email(X509
*x
)
298 result
= get_x509_name_entry("email", X509_get_subject_name(x
));
300 result
= get_x509_name_entry("emailAddress", X509_get_subject_name(x
));
307 #include <openssl/x509v3.h>
309 * This newer version is from Adrian Vogel. It looks for the email
310 * address not only in the email address field, but also in an
311 * X509v3 extension field, Subject Altenative Name.
314 get_x509_subject_email(X509
*x
)
316 char **result
= NULL
;
318 STACK_OF(OPENSSL_STRING
) *emails
= X509_get1_email(x
);
319 if ((n
= sk_OPENSSL_STRING_num(emails
)) > 0) {
320 result
= fs_get((n
+1)*sizeof(char *));
321 for(i
= 0; i
< n
; i
++)
322 result
[i
] = cpystr(sk_OPENSSL_STRING_value(emails
, i
));
325 X509_email_free(emails
);
331 * Save the certificate for the given email address in
332 * ~/.alpine-smime/public.
334 * Should consider the security hazards in making a file with
335 * the email address that has come from the certificate.
337 * The argument email is destroyed.
339 * args: ctype says where the user wants to save the certificate
342 save_cert_for(char *email
, X509
*cert
, WhichCerts ctype
)
344 if(!ps_global
->smime
|| ctype
== Private
)
347 dprint((9, "save_cert_for(%s, %s)", email
? email
: "?", ctype
== Public
? _("Public") : ctype
== Private
? _("Private") : "CACert"));
348 emailstrclean(email
);
350 if(ps_global
->smime
->publictype
== Keychain
){
354 SecCertificateRef secCertificateRef
;
357 memset((void *) &certData
, 0, sizeof(certData
));
358 memset((void *) &secCertificateRef
, 0, sizeof(secCertificateRef
));
360 /* convert OpenSSL X509 cert data to MacOS certData */
361 if((certData
.Length
= i2d_X509(cert
, &(certData
.Data
))) > 0){
364 * Put that certData into a SecCertificateRef.
365 * Version 3 should work for versions 1-3.
367 if(!(rc
=SecCertificateCreateFromData(&certData
,
369 CSSM_CERT_ENCODING_DER
,
370 &secCertificateRef
))){
372 /* add it to the default keychain */
373 if(!(rc
=SecCertificateAddToKeychain(secCertificateRef
, NULL
))){
376 else if(rc
== errSecDuplicateItem
){
377 dprint((9, "save_cert_for: certificate for %s already in keychain", email
));
380 dprint((9, "SecCertificateAddToKeychain failed"));
384 dprint((9, "SecCertificateCreateFromData failed"));
388 dprint((9, "i2d_X509 failed"));
391 #endif /* APPLEKEYCHAIN */
393 else if(SMHOLDERTYPE(ctype
) == Container
){
394 REMDATA_S
*rd
= NULL
;
396 char *upath
= PATHCERTDIR(ctype
);
397 char *tempfile
= NULL
;
399 CertList
*clist
= DATACERT(ctype
);
401 add_to_end_of_certlist(&clist
, email
, X509_dup(cert
));
404 case Private
: ps_global
->smime
->privatecertlist
= clist
; break;
405 case Public
: ps_global
->smime
->publiccertlist
= clist
; break;
406 case CACert
: ps_global
->smime
->cacertlist
= clist
; break;
413 if(IS_REMOTE(upath
)){
414 rd
= rd_create_remote(RemImap
, upath
, REMOTE_SMIME_SUBTYPE
,
416 _("Can't access remote smime configuration."));
421 (void) rd_read_metadata(rd
);
423 if(rd
->access
== MaybeRorW
){
424 if(rd
->read_status
== 'R')
425 rd
->access
= ReadOnly
;
427 rd
->access
= ReadWrite
;
430 if(rd
->access
!= NoExists
){
432 rd_check_remvalid(rd
, 1L);
435 * If the cached info says it is readonly but
436 * it looks like it's been fixed now, change it to readwrite.
438 if(rd
->read_status
== 'R'){
439 rd_check_readonly_access(rd
);
440 if(rd
->read_status
== 'W'){
441 rd
->access
= ReadWrite
;
442 rd
->flags
|= REM_OUTOFDATE
;
445 rd
->access
= ReadOnly
;
449 if(rd
->flags
& REM_OUTOFDATE
){
450 if(rd_update_local(rd
) != 0){
452 dprint((1, "save_cert_for: rd_update_local failed\n"));
453 rd_close_remdata(&rd
);
460 if(rd
->access
!= ReadWrite
|| rd_remote_is_readonly(rd
)){
461 rd_close_remdata(&rd
);
465 rd
->flags
|= DO_REMTRIM
;
467 strncpy(path
, rd
->lf
, sizeof(path
)-1);
468 path
[sizeof(path
)-1] = '\0';
471 strncpy(path
, upath
, sizeof(path
)-1);
472 path
[sizeof(path
)-1] = '\0';
475 tempfile
= tempfile_in_same_dir(path
, "az", NULL
);
477 if(certlist_to_file(tempfile
, DATACERT(ctype
)))
481 if(rename_file(tempfile
, path
) < 0){
482 q_status_message2(SM_ORDER
, 3, 3,
483 _("Can't rename %s to %s"), tempfile
, path
);
488 if(!err
&& IS_REMOTE(upath
)){
494 we_cancel
= busy_cue(_("Copying to remote smime container"), NULL
, 1);
495 if((e
= rd_update_remote(rd
, datebuf
)) != 0){
497 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
498 _("Error opening temporary smime file %s: %s"),
499 rd
->lf
, error_description(errno
));
501 "write_remote_smime: error opening temp file %s\n",
502 rd
->lf
? rd
->lf
: "?"));
505 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
506 _("Error copying to %s: %s"),
507 rd
->rn
, error_description(errno
));
509 "write_remote_smime: error copying from %s to %s\n",
510 rd
->lf
? rd
->lf
: "?", rd
->rn
? rd
->rn
: "?"));
513 q_status_message(SM_ORDER
| SM_DING
, 5, 5,
514 _("Copy of smime cert to remote folder failed, changes NOT saved remotely"));
517 rd_update_metadata(rd
, datebuf
);
518 rd
->read_status
= 'W';
521 rd_close_remdata(&rd
);
527 fs_give((void **) &tempfile
);
530 else if(SMHOLDERTYPE(ctype
) == Directory
){
531 char *path
= PATHCERTDIR(ctype
);
532 char certfilename
[MAXPATH
];
535 build_path(certfilename
, path
, email
, sizeof(certfilename
));
536 strncat(certfilename
, ".crt", sizeof(certfilename
)-1-strlen(certfilename
));
537 certfilename
[sizeof(certfilename
)-1] = 0;
539 bio_out
= BIO_new_file(certfilename
, "w");
541 PEM_write_bio_X509(bio_out
, cert
);
543 q_status_message1(SM_ORDER
, 1, 1, _("Saved certificate for <%s>"), email
);
546 q_status_message1(SM_ORDER
, 1, 1, _("Couldn't save certificate for <%s>"), email
);
553 * Try to retrieve the certificate for the given email address.
554 * The caller should free the cert.
557 get_cert_for(char *email
, WhichCerts ctype
)
559 char certfilename
[MAXPATH
];
560 char emailaddr
[MAXPATH
];
564 if(!ps_global
->smime
)
567 dprint((9, "get_cert_for(%s, %s)", email
? email
: "?", "none yet"));
569 if(ctype
== Private
) /* there is no private certificate info */
570 ctype
= Public
; /* return public information instead */
571 strncpy(emailaddr
, email
, sizeof(emailaddr
)-1);
572 emailaddr
[sizeof(emailaddr
)-1] = 0;
574 /* clean it up (lowercase, space removal) */
575 emailstrclean(emailaddr
);
577 if(ps_global
->smime
->publictype
== Keychain
){
581 SecKeychainItemRef itemRef
= nil
;
582 SecKeychainAttributeList attrList
;
583 SecKeychainAttribute attrib
;
584 SecKeychainSearchRef searchRef
= nil
;
587 /* low-level form of MacOS data */
588 memset((void *) &certData
, 0, sizeof(certData
));
591 attrList
.attr
= &attrib
;
593 /* kSecAlias means email address for a certificate */
594 attrib
.tag
= kSecAlias
;
595 attrib
.data
= emailaddr
;
596 attrib
.length
= strlen(attrib
.data
);
598 /* Find the certificate in the default keychain */
599 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
600 kSecCertificateItemClass
,
604 if(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
))){
606 /* extract the data portion of the certificate */
607 if(!(rc
=SecCertificateGetData((SecCertificateRef
) itemRef
, &certData
))){
610 * Convert it from MacOS form to OpenSSL form.
611 * The input is certData from above and the output
614 if(!d2i_X509(&cert
, &(certData
.Data
), certData
.Length
)){
615 dprint((9, "d2i_X509 failed"));
619 dprint((9, "SecCertificateGetData failed"));
622 else if(rc
== errSecItemNotFound
){
623 dprint((9, "get_cert_for: Public cert for %s not found", emailaddr
));
626 dprint((9, "SecKeychainSearchCopyNext failed"));
630 dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
634 CFRelease(searchRef
);
636 #endif /* APPLEKEYCHAIN */
638 else if(SMHOLDERTYPE(ctype
) == Container
){
641 for(cl
= DATACERT(ctype
); cl
; cl
= cl
->next
){
642 if(cl
->name
&& !strucmp(emailaddr
, cl
->name
))
647 cert
= X509_dup((X509
*) cl
->x509_cert
);
649 else if(SMHOLDERTYPE(ctype
) == Directory
){
650 build_path(certfilename
, PATHCERTDIR(ctype
), emailaddr
, sizeof(certfilename
));
651 strncat(certfilename
, EXTCERT(ctype
), sizeof(certfilename
)-1-strlen(certfilename
));
652 certfilename
[sizeof(certfilename
)-1] = 0;
654 if((in
= BIO_new_file(certfilename
, "r"))!=0){
656 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
659 /* could check email addr in cert matches */
670 * load_cert_for_key finds a certificate in pathdir that matches a private key
671 * pkey. It returns its name in certfile, and the certificate in *pcert.
672 * return value: success: different from zero, failure 0. If both certfile
673 * and pcert are NULL, this function returns if there is certificate that
674 * matches the given key.
677 load_cert_for_key(char *pathdir
, EVP_PKEY
*pkey
, char **certfile
, X509
**pcert
)
684 char buf
[MAXPATH
+1], pathcert
[MAXPATH
+1];
686 if(pathdir
== NULL
|| pkey
== NULL
)
689 if(certfile
) *certfile
= NULL
;
690 if(pcert
) *pcert
= NULL
;
692 if((dirp
= opendir(pathdir
)) != NULL
){
693 while(rv
== 0 && (d
=readdir(dirp
)) != NULL
){
696 if((ll
=strlen(d
->d_name
)) && ll
> 4){
697 if(!strcmp(d
->d_name
+ll
-4, ".crt")){
698 strncpy(buf
, d
->d_name
, sizeof(buf
));
699 buf
[sizeof(buf
)-1] = '\0';
700 build_path(pathcert
, pathdir
, buf
, sizeof(pathcert
));
701 if((in
= BIO_new_file(pathcert
, "r")) != NULL
){
702 if((x
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
)) != NULL
){
703 if(X509_check_private_key(x
, pkey
) > 0){
705 if(certfile
) *certfile
= cpystr(buf
);
706 if(pcert
) *pcert
= x
;
723 mem_to_personal_certs(char *contents
)
725 PERSONAL_CERT
*result
= NULL
;
726 char *p
, *q
, *line
, *name
, *keytext
, *save_p
;
729 if(contents
&& *contents
){
730 for(p
= contents
; *p
!= '\0';){
733 while(*p
&& *p
!= '\n')
742 if(strncmp(EMAILADDRLEADER
, line
, strlen(EMAILADDRLEADER
)) == 0){
743 name
= line
+ strlen(EMAILADDRLEADER
);
744 cert
= get_cert_for(name
, Public
);
747 /* advance p past this record */
748 if((q
= strstr(keytext
, "-----END")) != NULL
){
749 while(*q
&& *q
!= '\n')
759 q_status_message(SM_ORDER
| SM_DING
, 3, 3, _("Error in privatekey container, missing END"));
765 pc
= (PERSONAL_CERT
*) fs_get(sizeof(*pc
));
767 pc
->name
= cpystr(name
);
768 pc
->keytext
= keytext
; /* a pointer into contents */
770 pc
->key
= load_key(pc
, "");
787 mem_to_certlist(char *contents
, WhichCerts ctype
)
789 CertList
*ret
= NULL
;
790 char *p
, *q
, *line
, *name
, *certtext
, *save_p
;
793 char *sep
= (ctype
== Public
|| ctype
== Private
)
794 ? EMAILADDRLEADER
: CACERTSTORELEADER
;
796 if(contents
&& *contents
){
797 for(p
= contents
; *p
!= '\0';){
800 while(*p
&& *p
!= '\n')
809 if(strncmp(sep
, line
, strlen(sep
)) == 0){
810 name
= line
+ strlen(sep
);
812 certtext
= strstr(p
, "-----BEGIN");
813 if(certtext
!= NULL
){
814 if((q
= strstr(certtext
, sep
)) != NULL
)
817 p
= q
= certtext
+strlen(certtext
);
819 if((in
= BIO_new_mem_buf(certtext
, q
-certtext
)) != 0){
820 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
825 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, _("Error in %scert container, missing BEGIN, certtext=%s"), ctype
== Public
? _("public") : _("ca"), p
);
830 add_to_end_of_certlist(&ret
, name
, cert
);
843 * Add the CACert Container contents into the CACert store.
845 * Returns > 0 for success, 0 for failure
848 mem_add_extra_cacerts(char *contents
, X509_LOOKUP
*lookup
)
850 char *p
, *q
, *line
, *certtext
, *save_p
;
857 * The most straight-forward way to do this is to write
858 * the container contents to a temp file and then load the
859 * contents of the file with X509_LOOKUP_load_file(), like
860 * is done in add_certs_in_dir(). What we don't know is if
861 * each file should consist of one cacert or if they can all
862 * just be jammed together into one file. To be safe, we'll use
863 * one file per and do each in a separate operation.
866 if(contents
&& *contents
){
867 for(p
= contents
; *p
!= '\0';){
870 while(*p
&& *p
!= '\n')
879 /* look for separator line */
880 if(strncmp(CACERTSTORELEADER
, line
, strlen(CACERTSTORELEADER
)) == 0){
881 /* certtext is the content that should go in a file */
882 certtext
= strstr(p
, "-----BEGIN");
883 if(certtext
!= NULL
){
884 if((q
= strstr(certtext
, CACERTSTORELEADER
)) != NULL
){
887 else{ /* end of file */
888 q
= certtext
+ strlen(certtext
);
892 in
= BIO_new_mem_buf(certtext
, q
-certtext
);
894 tempfile
= temp_nam(NULL
, "az");
897 out
= BIO_new_file(tempfile
, "w");
900 while((len
= BIO_read(in
, iobuf
, sizeof(iobuf
))) > 0)
901 BIO_write(out
, iobuf
, len
);
904 if(!X509_LOOKUP_load_file(lookup
, tempfile
, X509_FILETYPE_PEM
))
907 fs_give((void **) &tempfile
);
915 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext
);
920 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing separator, line=%s"), line
);
933 certlist_to_file(char *filename
, CertList
*certlist
)
939 if(filename
&& (bio_out
=BIO_new_file(filename
, "w")) != NULL
){
941 for(cl
= certlist
; cl
; cl
= cl
->next
){
942 if(cl
->name
&& cl
->name
[0] && cl
->x509_cert
){
943 if(!((BIO_puts(bio_out
, EMAILADDRLEADER
) > 0)
944 && (BIO_puts(bio_out
, cl
->name
) > 0)
945 && (BIO_puts(bio_out
, "\n") > 0)))
948 if(!PEM_write_bio_X509(bio_out
, (X509
*) cl
->x509_cert
))
961 add_to_end_of_certlist(CertList
**cl
, char *name
, X509
*cert
)
968 new = (CertList
*) fs_get(sizeof(*new));
969 memset((void *) new, 0, sizeof(*new));
970 new->x509_cert
= cert
;
971 new->name
= name
? cpystr(name
) : NULL
;
977 for(clp
= (*cl
); clp
->next
; clp
= clp
->next
)
986 free_certlist(CertList
**cl
)
989 free_certlist(&(*cl
)->next
);
991 fs_give((void **) &(*cl
)->name
);
994 X509_free((X509
*) (*cl
)->x509_cert
);
996 fs_give((void **) cl
);
1002 free_personal_certs(PERSONAL_CERT
**pc
)
1005 free_personal_certs(&(*pc
)->next
);
1007 fs_give((void **) &(*pc
)->name
);
1010 fs_give((void **) &(*pc
)->name
);
1013 X509_free((*pc
)->cert
);
1016 EVP_PKEY_free((*pc
)->key
);
1018 fs_give((void **) pc
);