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 2008 University of Washington
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * ========================================================================
19 * This is based on a contribution from Jonathan Paisley, see smime.c
23 #include "../pith/headers.h"
27 #include "../pith/status.h"
28 #include "../pith/conf.h"
29 #include "../pith/remote.h"
30 #include "../pith/tempfile.h"
31 #include "../pith/busy.h"
32 #include "../pith/osdep/lstcmpnt.h"
36 #include <Security/SecKeychain.h>
37 #include <Security/SecKeychainItem.h>
38 #include <Security/SecKeychainSearch.h>
39 #include <Security/SecCertificate.h>
40 #endif /* APPLEKEYCHAIN */
43 /* internal prototypes */
44 static char *emailstrclean(char *string
);
45 static int add_certs_in_dir(X509_LOOKUP
*lookup
, char *path
);
46 static int certlist_to_file(char *filename
, CertList
*certlist
);
47 static int mem_add_extra_cacerts(char *contents
, X509_LOOKUP
*lookup
);
51 * Remove leading whitespace, trailing whitespace and convert
52 * to lowercase. Also remove slash characters
54 * Args: s, -- The string to clean
56 * Result: the cleaned string
59 emailstrclean(char *string
)
61 char *s
= string
, *sc
= NULL
, *p
= NULL
;
63 for(; *s
; s
++){ /* single pass */
64 if(!isspace((unsigned char) (*s
))){
65 p
= NULL
; /* not start of blanks */
66 if(!sc
) /* first non-blank? */
67 sc
= string
; /* start copying */
69 else if(!p
) /* it's OK if sc == NULL */
70 p
= sc
; /* start of blanks? */
72 if(sc
&& *s
!='/' && *s
!='\\') /* if copying, copy */
73 *sc
++ = isupper((unsigned char) (*s
))
74 ? (unsigned char) tolower((unsigned char) (*s
))
75 : (unsigned char) (*s
);
78 if(p
) /* if ending blanks */
79 *p
= '\0'; /* tie off beginning */
80 else if(!sc
) /* never saw a non-blank */
81 *string
= '\0'; /* so tie whole thing off */
88 * Add a lookup for each "*.crt" file in the given directory.
91 add_certs_in_dir(X509_LOOKUP
*lookup
, char *path
)
101 while(!ret
&& (d
=readdir(dirp
)) != NULL
){
102 if(srchrstr(d
->d_name
, ".crt")){
103 build_path(buf
, path
, d
->d_name
, sizeof(buf
));
105 if(!X509_LOOKUP_load_file(lookup
, buf
, X509_FILETYPE_PEM
)){
106 q_status_message1(SM_ORDER
, 3, 3, _("Error loading file %s"), buf
);
121 * Get an X509_STORE. This consists of the system
122 * certs directory and any certificates in the user's
123 * ~/.alpine-smime/ca directory.
129 X509_STORE
*store
= NULL
;
131 dprint((9, "get_ca_store()"));
133 if(!(store
=X509_STORE_new())){
134 dprint((9, "X509_STORE_new() failed"));
138 if(!(lookup
=X509_STORE_add_lookup(store
, X509_LOOKUP_file()))){
139 dprint((9, "X509_STORE_add_lookup() failed"));
140 X509_STORE_free(store
);
144 if(ps_global
->smime
&& ps_global
->smime
->catype
== Container
145 && ps_global
->smime
->cacontent
){
147 if(!mem_add_extra_cacerts(ps_global
->smime
->cacontent
, lookup
)){
148 X509_STORE_free(store
);
152 else if(ps_global
->smime
&& ps_global
->smime
->catype
== Directory
153 && ps_global
->smime
->capath
){
154 if(add_certs_in_dir(lookup
, ps_global
->smime
->capath
) < 0){
155 X509_STORE_free(store
);
160 if(!(lookup
=X509_STORE_add_lookup(store
, X509_LOOKUP_hash_dir()))){
161 X509_STORE_free(store
);
165 #ifdef SMIME_SSLCERTS
166 dprint((9, "get_ca_store(): adding cacerts from %s", SMIME_SSLCERTS
));
167 X509_LOOKUP_add_dir(lookup
, SMIME_SSLCERTS
, X509_FILETYPE_PEM
);
175 load_key(PERSONAL_CERT
*pc
, char *pass
)
178 EVP_PKEY
*key
= NULL
;
179 char buf
[MAXPATH
], file
[MAXPATH
];
181 if(!(ps_global
->smime
&& pc
&& pc
->name
))
184 if(ps_global
->smime
->privatetype
== Container
){
187 if(pc
->keytext
&& (q
= strstr(pc
->keytext
, "-----END")) != NULL
){
188 while(*q
&& *q
!= '\n')
194 if((in
= BIO_new_mem_buf(pc
->keytext
, q
-pc
->keytext
)) != NULL
){
195 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
200 else if(ps_global
->smime
->privatetype
== Directory
){
201 /* filename is path/name.key */
202 strncpy(buf
, pc
->name
, sizeof(buf
)-5);
203 buf
[sizeof(buf
)-5] = '\0';
204 strncat(buf
, ".key", 5);
205 build_path(file
, ps_global
->smime
->privatepath
, buf
, sizeof(file
));
207 if(!(in
= BIO_new_file(file
, "r")))
210 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
220 get_x509_name_entry(const char *key
, X509_NAME
*name
)
229 c
= X509_NAME_entry_count(name
);
234 e
= X509_NAME_get_entry(name
, i
);
241 n
= OBJ_obj2nid(e
->object
);
242 if((n
== NID_undef
) || ((id
=(char*) OBJ_nid2sn(n
)) == NULL
)){
243 i2t_ASN1_OBJECT(buf
, sizeof(buf
), e
->object
);
247 if((strucmp(id
, "email")==0) || (strucmp(id
, "emailAddress")==0)){
248 X509_NAME_get_text_by_OBJ(name
, e
->object
, buf
, sizeof(buf
)-1);
258 get_x509_subject_email(X509
*x
)
261 result
= get_x509_name_entry("email", X509_get_subject_name(x
));
263 result
= get_x509_name_entry("emailAddress", X509_get_subject_name(x
));
270 #include <openssl/x509v3.h>
272 * This newer version is from Adrian Vogel. It looks for the email
273 * address not only in the email address field, but also in an
274 * X509v3 extension field, Subject Altenative Name.
277 get_x509_subject_email(X509
*x
)
279 char **result
= NULL
;
281 STACK_OF(OPENSSL_STRING
) *emails
= X509_get1_email(x
);
282 if ((n
= sk_OPENSSL_STRING_num(emails
)) > 0) {
283 result
= fs_get((n
+1)*sizeof(char *));
284 for(i
= 0; i
< n
; i
++)
285 result
[i
] = cpystr(sk_OPENSSL_STRING_value(emails
, i
));
288 X509_email_free(emails
);
294 * Save the certificate for the given email address in
295 * ~/.alpine-smime/public.
297 * Should consider the security hazards in making a file with
298 * the email address that has come from the certificate.
300 * The argument email is destroyed.
303 save_cert_for(char *email
, X509
*cert
)
305 if(!ps_global
->smime
)
308 dprint((9, "save_cert_for(%s)", email
? email
: "?"));
309 emailstrclean(email
);
311 if(ps_global
->smime
->publictype
== Keychain
){
315 SecCertificateRef secCertificateRef
;
318 memset((void *) &certData
, 0, sizeof(certData
));
319 memset((void *) &secCertificateRef
, 0, sizeof(secCertificateRef
));
321 /* convert OpenSSL X509 cert data to MacOS certData */
322 if((certData
.Length
= i2d_X509(cert
, &(certData
.Data
))) > 0){
325 * Put that certData into a SecCertificateRef.
326 * Version 3 should work for versions 1-3.
328 if(!(rc
=SecCertificateCreateFromData(&certData
,
330 CSSM_CERT_ENCODING_DER
,
331 &secCertificateRef
))){
333 /* add it to the default keychain */
334 if(!(rc
=SecCertificateAddToKeychain(secCertificateRef
, NULL
))){
337 else if(rc
== errSecDuplicateItem
){
338 dprint((9, "save_cert_for: certificate for %s already in keychain", email
));
341 dprint((9, "SecCertificateAddToKeychain failed"));
345 dprint((9, "SecCertificateCreateFromData failed"));
349 dprint((9, "i2d_X509 failed"));
352 #endif /* APPLEKEYCHAIN */
354 else if(ps_global
->smime
->publictype
== Container
){
355 REMDATA_S
*rd
= NULL
;
357 char *tempfile
= NULL
;
360 add_to_end_of_certlist(&ps_global
->smime
->publiccertlist
, email
, X509_dup(cert
));
362 if(!ps_global
->smime
->publicpath
)
365 if(IS_REMOTE(ps_global
->smime
->publicpath
)){
366 rd
= rd_create_remote(RemImap
, ps_global
->smime
->publicpath
, REMOTE_SMIME_SUBTYPE
,
368 _("Can't access remote smime configuration."));
372 (void) rd_read_metadata(rd
);
374 if(rd
->access
== MaybeRorW
){
375 if(rd
->read_status
== 'R')
376 rd
->access
= ReadOnly
;
378 rd
->access
= ReadWrite
;
381 if(rd
->access
!= NoExists
){
383 rd_check_remvalid(rd
, 1L);
386 * If the cached info says it is readonly but
387 * it looks like it's been fixed now, change it to readwrite.
389 if(rd
->read_status
== 'R'){
390 rd_check_readonly_access(rd
);
391 if(rd
->read_status
== 'W'){
392 rd
->access
= ReadWrite
;
393 rd
->flags
|= REM_OUTOFDATE
;
396 rd
->access
= ReadOnly
;
400 if(rd
->flags
& REM_OUTOFDATE
){
401 if(rd_update_local(rd
) != 0){
403 dprint((1, "save_cert_for: rd_update_local failed\n"));
404 rd_close_remdata(&rd
);
411 if(rd
->access
!= ReadWrite
|| rd_remote_is_readonly(rd
)){
412 rd_close_remdata(&rd
);
416 rd
->flags
|= DO_REMTRIM
;
418 strncpy(path
, rd
->lf
, sizeof(path
)-1);
419 path
[sizeof(path
)-1] = '\0';
422 strncpy(path
, ps_global
->smime
->publicpath
, sizeof(path
)-1);
423 path
[sizeof(path
)-1] = '\0';
426 tempfile
= tempfile_in_same_dir(path
, "az", NULL
);
428 if(certlist_to_file(tempfile
, ps_global
->smime
->publiccertlist
))
432 if(rename_file(tempfile
, path
) < 0){
433 q_status_message2(SM_ORDER
, 3, 3,
434 _("Can't rename %s to %s"), tempfile
, path
);
439 if(!err
&& IS_REMOTE(ps_global
->smime
->publicpath
)){
445 we_cancel
= busy_cue(_("Copying to remote smime container"), NULL
, 1);
446 if((e
= rd_update_remote(rd
, datebuf
)) != 0){
448 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
449 _("Error opening temporary smime file %s: %s"),
450 rd
->lf
, error_description(errno
));
452 "write_remote_smime: error opening temp file %s\n",
453 rd
->lf
? rd
->lf
: "?"));
456 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
457 _("Error copying to %s: %s"),
458 rd
->rn
, error_description(errno
));
460 "write_remote_smime: error copying from %s to %s\n",
461 rd
->lf
? rd
->lf
: "?", rd
->rn
? rd
->rn
: "?"));
464 q_status_message(SM_ORDER
| SM_DING
, 5, 5,
465 _("Copy of smime cert to remote folder failed, changes NOT saved remotely"));
468 rd_update_metadata(rd
, datebuf
);
469 rd
->read_status
= 'W';
472 rd_close_remdata(&rd
);
478 fs_give((void **) &tempfile
);
481 else if(ps_global
->smime
->publictype
== Directory
){
482 char certfilename
[MAXPATH
];
485 build_path(certfilename
, ps_global
->smime
->publicpath
,
486 email
, sizeof(certfilename
));
487 strncat(certfilename
, ".crt", sizeof(certfilename
)-1-strlen(certfilename
));
488 certfilename
[sizeof(certfilename
)-1] = 0;
490 bio_out
= BIO_new_file(certfilename
, "w");
492 PEM_write_bio_X509(bio_out
, cert
);
494 q_status_message1(SM_ORDER
, 1, 1, _("Saved certificate for <%s>"), email
);
497 q_status_message1(SM_ORDER
, 1, 1, _("Couldn't save certificate for <%s>"), email
);
504 * Try to retrieve the certificate for the given email address.
505 * The caller should free the cert.
508 get_cert_for(char *email
)
511 char certfilename
[MAXPATH
];
512 char emailaddr
[MAXPATH
];
516 if(!ps_global
->smime
)
519 dprint((9, "get_cert_for(%s)", email
? email
: "?"));
521 strncpy(emailaddr
, email
, sizeof(emailaddr
)-1);
522 emailaddr
[sizeof(emailaddr
)-1] = 0;
524 /* clean it up (lowercase, space removal) */
525 emailstrclean(emailaddr
);
527 if(ps_global
->smime
->publictype
== Keychain
){
531 SecKeychainItemRef itemRef
= nil
;
532 SecKeychainAttributeList attrList
;
533 SecKeychainAttribute attrib
;
534 SecKeychainSearchRef searchRef
= nil
;
537 /* low-level form of MacOS data */
538 memset((void *) &certData
, 0, sizeof(certData
));
541 attrList
.attr
= &attrib
;
543 /* kSecAlias means email address for a certificate */
544 attrib
.tag
= kSecAlias
;
545 attrib
.data
= emailaddr
;
546 attrib
.length
= strlen(attrib
.data
);
548 /* Find the certificate in the default keychain */
549 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
550 kSecCertificateItemClass
,
554 if(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
))){
556 /* extract the data portion of the certificate */
557 if(!(rc
=SecCertificateGetData((SecCertificateRef
) itemRef
, &certData
))){
560 * Convert it from MacOS form to OpenSSL form.
561 * The input is certData from above and the output
564 if(!d2i_X509(&cert
, &(certData
.Data
), certData
.Length
)){
565 dprint((9, "d2i_X509 failed"));
569 dprint((9, "SecCertificateGetData failed"));
572 else if(rc
== errSecItemNotFound
){
573 dprint((9, "get_cert_for: Public cert for %s not found", emailaddr
));
576 dprint((9, "SecKeychainSearchCopyNext failed"));
580 dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
584 CFRelease(searchRef
);
586 #endif /* APPLEKEYCHAIN */
588 else if(ps_global
->smime
->publictype
== Container
){
589 if(ps_global
->smime
->publiccertlist
){
592 for(cl
= ps_global
->smime
->publiccertlist
; cl
; cl
= cl
->next
){
593 if(cl
->name
&& !strucmp(emailaddr
, cl
->name
))
598 cert
= X509_dup((X509
*) cl
->x509_cert
);
601 else if(ps_global
->smime
->publictype
== Directory
){
602 path
= ps_global
->smime
->publicpath
;
603 build_path(certfilename
, path
, emailaddr
, sizeof(certfilename
));
604 strncat(certfilename
, ".crt", sizeof(certfilename
)-1-strlen(certfilename
));
605 certfilename
[sizeof(certfilename
)-1] = 0;
607 if((in
= BIO_new_file(certfilename
, "r"))!=0){
609 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
612 /* could check email addr in cert matches */
624 mem_to_personal_certs(char *contents
)
626 PERSONAL_CERT
*result
= NULL
;
627 char *p
, *q
, *line
, *name
, *keytext
, *save_p
;
630 if(contents
&& *contents
){
631 for(p
= contents
; *p
!= '\0';){
634 while(*p
&& *p
!= '\n')
643 if(strncmp(EMAILADDRLEADER
, line
, strlen(EMAILADDRLEADER
)) == 0){
644 name
= line
+ strlen(EMAILADDRLEADER
);
645 cert
= get_cert_for(name
);
648 /* advance p past this record */
649 if((q
= strstr(keytext
, "-----END")) != NULL
){
650 while(*q
&& *q
!= '\n')
660 q_status_message(SM_ORDER
| SM_DING
, 3, 3, _("Error in privatekey container, missing END"));
666 pc
= (PERSONAL_CERT
*) fs_get(sizeof(*pc
));
668 pc
->name
= cpystr(name
);
669 pc
->keytext
= keytext
; /* a pointer into contents */
671 pc
->key
= load_key(pc
, "");
688 mem_to_certlist(char *contents
)
690 CertList
*ret
= NULL
;
691 char *p
, *q
, *line
, *name
, *certtext
, *save_p
;
695 if(contents
&& *contents
){
696 for(p
= contents
; *p
!= '\0';){
699 while(*p
&& *p
!= '\n')
708 if(strncmp(EMAILADDRLEADER
, line
, strlen(EMAILADDRLEADER
)) == 0){
709 name
= line
+ strlen(EMAILADDRLEADER
);
712 if(strncmp("-----BEGIN", certtext
, strlen("-----BEGIN")) == 0){
713 if((q
= strstr(certtext
, "-----END")) != NULL
){
714 while(*q
&& *q
!= '\n')
722 if((in
= BIO_new_mem_buf(certtext
, q
-certtext
)) != 0){
723 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
731 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in publiccert container, missing BEGIN, certtext=%s"), certtext
);
735 add_to_end_of_certlist(&ret
, name
, cert
);
749 * Add the CACert Container contents into the CACert store.
751 * Returns > 0 for success, 0 for failure
754 mem_add_extra_cacerts(char *contents
, X509_LOOKUP
*lookup
)
756 char *p
, *q
, *line
, *certtext
, *save_p
;
763 * The most straight-forward way to do this is to write
764 * the container contents to a temp file and then load the
765 * contents of the file with X509_LOOKUP_load_file(), like
766 * is done in add_certs_in_dir(). What we don't know is if
767 * each file should consist of one cacert or if they can all
768 * just be jammed together into one file. To be safe, we'll use
769 * one file per and do each in a separate operation.
772 if(contents
&& *contents
){
773 for(p
= contents
; *p
!= '\0';){
776 while(*p
&& *p
!= '\n')
785 /* look for separator line */
786 if(strncmp(CACERTSTORELEADER
, line
, strlen(CACERTSTORELEADER
)) == 0){
787 /* certtext is the content that should go in a file */
789 if(strncmp("-----BEGIN", certtext
, strlen("-----BEGIN")) == 0){
790 if((q
= strstr(certtext
, CACERTSTORELEADER
)) != NULL
){
793 else{ /* end of file */
794 q
= certtext
+ strlen(certtext
);
798 in
= BIO_new_mem_buf(certtext
, q
-certtext
);
800 tempfile
= temp_nam(NULL
, "az");
803 out
= BIO_new_file(tempfile
, "w");
806 while((len
= BIO_read(in
, iobuf
, sizeof(iobuf
))) > 0)
807 BIO_write(out
, iobuf
, len
);
810 if(!X509_LOOKUP_load_file(lookup
, tempfile
, X509_FILETYPE_PEM
))
813 fs_give((void **) &tempfile
);
821 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext
);
826 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing separator, line=%s"), line
);
839 certlist_to_file(char *filename
, CertList
*certlist
)
845 if(filename
&& (bio_out
=BIO_new_file(filename
, "w")) != NULL
){
847 for(cl
= certlist
; cl
; cl
= cl
->next
){
848 if(cl
->name
&& cl
->name
[0] && cl
->x509_cert
){
849 if(!((BIO_puts(bio_out
, EMAILADDRLEADER
) > 0)
850 && (BIO_puts(bio_out
, cl
->name
) > 0)
851 && (BIO_puts(bio_out
, "\n") > 0)))
854 if(!PEM_write_bio_X509(bio_out
, (X509
*) cl
->x509_cert
))
867 add_to_end_of_certlist(CertList
**cl
, char *name
, X509
*cert
)
874 new = (CertList
*) fs_get(sizeof(*new));
875 memset((void *) new, 0, sizeof(*new));
876 new->x509_cert
= cert
;
877 new->name
= name
? cpystr(name
) : NULL
;
883 for(clp
= (*cl
); clp
->next
; clp
= clp
->next
)
892 free_certlist(CertList
**cl
)
895 free_certlist(&(*cl
)->next
);
897 fs_give((void **) &(*cl
)->name
);
900 X509_free((X509
*) (*cl
)->x509_cert
);
902 fs_give((void **) cl
);
908 free_personal_certs(PERSONAL_CERT
**pc
)
911 free_personal_certs(&(*pc
)->next
);
913 fs_give((void **) &(*pc
)->name
);
916 fs_give((void **) &(*pc
)->name
);
919 X509_free((*pc
)->cert
);
922 EVP_PKEY_free((*pc
)->key
);
924 fs_give((void **) pc
);