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-2020 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"
37 #include "../pith/options.h"
41 #include <Security/SecKeychain.h>
42 #include <Security/SecKeychainItem.h>
43 #include <Security/SecKeychainSearch.h>
44 #include <Security/SecCertificate.h>
45 #endif /* APPLEKEYCHAIN */
48 /* internal prototypes */
49 static char *emailstrclean(char *string
);
50 static int mem_add_extra_cacerts(char *contents
, X509_LOOKUP
*lookup
);
51 int compare_certs_by_name(const void *data1
, const void *data2
);
52 int password_policy_check(char *);
54 /* test if password passes a predetermined policy.
55 * return value: 0 - does not pass; 1 - it passes
58 password_policy_check(char *password
)
64 if(password
== NULL
|| password
[0] == '\0'){
65 error
= _("Password cannot be blank");
67 } else if(strlen(password
) < 8){
68 error
= _("Password is too short");
72 snprintf(tmp
, sizeof(tmp
), "%s%s", error
, _(". Enter password again"));
73 tmp
[sizeof(tmp
) - 1] = '\0';
74 q_status_message(SM_ORDER
, 3, 3, tmp
);
81 create_master_password(char *pass
, size_t passlen
, int first_time
)
85 char prompt
[MAILTMPLEN
];
86 char passbackup
[MAILTMPLEN
];
89 q_status_message(SM_ORDER
, 3, 3,
90 _(" Creating a Master Password for your Password file "));
92 q_status_message(SM_ORDER
, 3, 3,
93 _(" Retrying to create a Master Password for your Password file "));
95 for(trial
= 0; trial
< MAXTRIAL
; trial
++){
96 snprintf(prompt
, sizeof(prompt
),
97 _("Create master password (attempt %d of %d): "), trial
+1, MAXTRIAL
);
98 prompt
[sizeof(prompt
)- 1] = '\0';
101 /* rv == 1 means cancel */
102 rv
= (pith_smime_enter_password
)(prompt
, pass
, passlen
);
103 if(rv
== 1 || password_policy_check(pass
) == 0)
105 if(rv
== 1) return 0;
106 } while ((rv
!= 0 && rv
!= 1) || (rv
== 0 && pass
[0] == '\0'));
108 snprintf(prompt
, sizeof(prompt
),
109 _("Confirm master password (attempt %d of %d): "), trial
+1, MAXTRIAL
);
110 prompt
[sizeof(prompt
)- 1] = '\0';
111 passbackup
[0] = '\0';
113 rv
= (pith_smime_enter_password
)(prompt
, passbackup
, sizeof(passbackup
));
114 } while ((rv
!=0 && rv
!=1 && rv
> 0) || passbackup
[0] == '\0');
115 if(!strcmp(pass
, passbackup
))
117 if(trial
+ 1 < MAXTRIAL
)
118 q_status_message(SM_ORDER
, 2, 2, _("Passwords do not match, try again."));
120 q_status_message(SM_ORDER
, 2, 2, _("Passwords do not match, too many failures."));
124 return (trial
< MAXTRIAL
) ? 1 : 0;
128 * Create a self signed certificate with root name _fname_, in directory
129 * _pathdir_. If _version_ is 3, we use the _template_ file as configuration
130 * file for openssl. At this moment, we only call this function with template = NULL
131 * and version = 0, but a sensible call is
132 * ALPINE_self_signed_certificate("/etc/ssl/openssl.cnf", 2, pathdir, fname, first_time);
134 * _pathdir_ is the directory to save the file,
135 * _fname_ is the root of the name to use. Append ".key" and ".crt" to this name
136 * _first_time_ is an indicator to tell us if this is the first time we call this function
139 ALPINE_self_signed_certificate(char *template, int version
, char *pathdir
, char *fname
)
142 X509_NAME
*name
= NULL
;
143 X509_REQ
*req
= NULL
;
146 char tmp
[MAXPATH
+1], password
[1024];
147 char *keyfile
= NULL
, *certfile
= NULL
;
148 char *extensions
= NULL
;
151 PERSONAL_CERT
*pc
= NULL
;
152 EVP_PKEY
*pkey
= NULL
;
154 CONF
*req_conf
= NULL
;
155 static int first_time
= 1;
161 if((out
= BIO_new_file(template, "r")) == NULL
){
162 q_status_message(SM_ORDER
, 2, 2, _("Problem reading configuration file"));
166 if((req_conf
= NCONF_new(NULL
)) != NULL
167 && NCONF_load_bio(req_conf
, out
, &errline
) > 0){
168 if((extensions
= NCONF_get_string(req_conf
, "req", "x509_extensions")) != NULL
){
170 X509V3_set_ctx_test(&ctx
);
171 X509V3_set_nconf(&ctx
, req_conf
);
172 if (!X509V3_EXT_add_nconf(req_conf
, &ctx
, extensions
, NULL
)) {
173 q_status_message(SM_ORDER
, 2, 2, _("Problem loading openssl configuration"));
174 NCONF_free(req_conf
);
183 if(create_master_password(password
, sizeof(password
), first_time
)
184 && (pkctx
= EVP_PKEY_CTX_new_id(EVP_PKEY_RSA
, NULL
)) != NULL
185 && EVP_PKEY_keygen_init(pkctx
) > 0
186 && EVP_PKEY_CTX_set_rsa_keygen_bits(pkctx
, 2048) > 0 /* RSA:2048 */
187 && EVP_PKEY_keygen(pkctx
, &pkey
) > 0){
188 snprintf(tmp
, sizeof(tmp
), "%s.key", fname
);
189 tmp
[sizeof(tmp
)-1] = '\0';
190 keyfile
= cpystr(tmp
);
191 build_path(tmp
, pathdir
, keyfile
, sizeof(tmp
));
192 keyfile
[strlen(keyfile
)-4] = '\0'; /* keyfile does not have .key extension */
193 if((fp
= fopen(tmp
, "w")) != NULL
194 && (out
= BIO_new_fp(fp
, BIO_CLOSE
| BIO_FP_TEXT
)) != NULL
195 && PEM_write_bio_PrivateKey(out
, pkey
, EVP_des_ede3_cbc(),
196 NULL
, 0, NULL
, password
)){
200 memset((void *)password
, 0, sizeof(password
));
201 if((req
= X509_REQ_new()) != NULL
202 && X509_REQ_set_version(req
, 0L)){
203 name
= X509_REQ_get_subject_name(req
);
204 X509_NAME_add_entry_by_txt(name
, "CN", MBSTRING_ASC
, "Password File Certificate and Key Pair", -1, -1, 0);
205 if(X509_REQ_set_pubkey(req
, pkey
)
206 && (pcert
= X509_new()) != NULL
){
207 if(X509_set_version(pcert
, version
)
208 && (b
= BN_new()) != NULL
209 && BN_set_word(b
, 65537)
210 && BN_pseudo_rand(b
, 64, 0, 0)
211 && X509_get_serialNumber(pcert
)
212 && BN_to_ASN1_INTEGER(b
, X509_get_serialNumber(pcert
)) /* set serial */
213 && X509_set_issuer_name(pcert
, X509_REQ_get_subject_name(req
))
214 && X509_set_subject_name(pcert
, X509_REQ_get_subject_name(req
))){
218 X509_gmtime_adj(X509_getm_notBefore(pcert
), 0);
219 X509_time_adj_ex(X509_getm_notAfter(pcert
), 1095, 0, NULL
);
221 if((tmppkey
= X509_REQ_get0_pubkey(req
)) != NULL
222 && X509_set_pubkey(pcert
, tmppkey
)){
223 if(extensions
!= NULL
&& version
== 2){
224 X509V3_set_ctx(&ext_ctx
, pcert
, pcert
, NULL
, NULL
, 0);
225 if(req_conf
){ /* only if template is not null */
226 X509V3_set_nconf(&ext_ctx
, req_conf
);
227 X509V3_EXT_add_nconf(req_conf
, &ext_ctx
, extensions
, pcert
);
230 EVP_PKEY_free(tmppkey
);
231 X509_sign(pcert
, pkey
, NULL
);
238 snprintf(tmp
, sizeof(tmp
), "%s.crt", fname
);
239 tmp
[sizeof(tmp
)-1] = '\0';
240 certfile
= cpystr(tmp
);
241 build_path(tmp
, pathdir
, certfile
, sizeof(tmp
));
242 if((fp
= fopen(tmp
, "w")) != NULL
243 &&(out
= BIO_new_fp(fp
, BIO_FP_TEXT
)) != NULL
){
244 EVP_PKEY
*tpubkey
= X509_REQ_get0_pubkey(req
);
245 PEM_write_bio_X509(out
, pcert
);
251 NCONF_free(req_conf
);
253 if(keyfile
&& certfile
&& pkey
&& pcert
){
254 pc
= (PERSONAL_CERT
*) fs_get(sizeof(PERSONAL_CERT
));
255 memset((void *)pc
, 0, sizeof(PERSONAL_CERT
));
259 pc
->cname
= certfile
;
266 smime_X509_to_cert_info(X509
*x
, char *name
)
271 if(x
== NULL
) return NULL
;
273 cert
= fs_get(sizeof(CertList
));
274 memset((void *)cert
, 0, sizeof(CertList
));
276 cert
->name
= name
? cpystr(name
) : NULL
;
277 cert
->data
.date_from
= smime_get_date(X509_get0_notBefore(x
));
278 cert
->data
.date_to
= smime_get_date(X509_get0_notAfter(x
));
279 cert
->cn
= smime_get_cn(x
);
280 get_fingerprint(x
, EVP_md5(), buf
, sizeof(buf
), NULL
);
281 cert
->data
.md5
= cpystr(buf
);
286 #define SMIME_BACKUP_DIR ".backup"
287 #define MAX_TRY_BACKUP 100
289 /* return value: 0 - success, -1 error
290 * Call this function after setting up paths in ps_global->smime
291 * and reading certificates names in certlist.
294 setup_certs_backup_by_type(WhichCerts ctype
)
296 int rv
= 0; /* assume success */
300 char p
[MAXPATH
+1]; /* path to where the backup is */
301 char buf
[MAXPATH
+1], buf2
[MAXPATH
+1];
306 struct dirent
*df
; /* file in the directory */
308 struct _finddata_t dbuf
;
309 char bufn
[_MAX_PATH
+ 4];
311 #endif /* !_WINDOWS */
312 CertList
*cert
, *cl2
;
316 return rv
; /* remove when this function is complete */
318 if(SMHOLDERTYPE(ctype
) == Directory
){
319 d
= PATHCERTDIR(ctype
);
321 len
= strlen(d
) + strlen(S_FILESEP
) + strlen(SMIME_BACKUP_DIR
) + 1;
322 snprintf(p
, MAXPATH
, "%s%s%s", d
, S_FILESEP
, SMIME_BACKUP_DIR
);
324 if(our_stat(p
, &sbuf
) < 0){
325 if(our_mkpath(p
, 0700) != 0)
327 } else if((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
){
328 for(i
= 0, done
= 0; done
== 0 && i
< MAX_TRY_BACKUP
; i
++){
329 snprintf(buf2
, len
+2, "%s%d", p
, i
);
330 if(our_stat(buf2
, &sbuf
) < 0){
331 if(our_mkpath(buf2
, 0700) == 0)
334 else if((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
)
337 strncpy(p
, buf2
, MAXPATH
);
344 /* if we are here, we have a backup directory where to
345 * backup certificates/keys, so now we will go
346 * through the list of certificates and back them up
349 data
= BACKUPDATACERT(ctype
);
350 for(cl
= DATACERT(ctype
); cl
; cl
= cl
->next
){
351 char clname
[MAXPATH
+1];
353 snprintf(clname
, MAXPATH
, "%s%s", cl
->name
, ctype
== Private
? ".key" : "");
354 clname
[MAXPATH
] = '\0';
355 len
= strlen(d
) + strlen(clname
) + 2;
357 snprintf(buf
, len
, "%s%s%s", d
, S_FILESEP
, clname
);
358 buf
[sizeof(buf
)-1] = '\0';
359 len
= strlen(p
) + strlen(clname
) + strlen(cl
->data
.md5
) + 3;
361 snprintf(buf2
, len
, "%s%s%s.%s", p
, S_FILESEP
, clname
, cl
->data
.md5
);
362 buf2
[sizeof(buf2
)-1] = '\0';
363 done
= 0; /* recycle done: it means we have a file that may be a certificate*/
364 if(stat(buf2
, &sbuf
) < 0){
365 if (our_copy(buf2
, buf
) == 0)
367 } else if((sbuf
.st_mode
& S_IFMT
) == S_IFREG
)
374 if((in
= BIO_new_file(buf2
, "r"))!=0){
375 cert
= fs_get(sizeof(CertList
));
376 memset((void *)cert
, 0, sizeof(CertList
));
377 cert
->x509_cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
378 if(cl
->data
.date_from
!= NULL
)
379 cert
->data
.date_from
= cpystr(cl
->data
.date_from
);
380 if(cl
->data
.date_to
!= NULL
)
381 cert
->data
.date_to
= cpystr(cl
->data
.date_to
);
382 if(cl
->data
.md5
!= NULL
)
383 cert
->data
.md5
= cpystr(cl
->data
.md5
);
385 cert
->cn
= cpystr(cl
->cn
);
386 snprintf(buf2
, len
, "%s.%s", cl
->name
, cl
->data
.md5
);
387 buf2
[sizeof(buf2
)-1] = '\0';
388 cert
->name
= cpystr(buf2
);
392 for (cl2
= data
; cl2
&& cl2
->next
; cl2
= cl2
->next
);
400 default: alpine_panic("Bad ctype (0)");
406 /* if we are here, it means we just loaded the backup variable with
407 * a copy of the data that comes from the certlist not coming from
408 * backup. Now we are going to load the contents of the .backup
412 /* Here is the plan: read the backup directory (in the variable "p")
413 * and attempt to add it. If already there, skip it; otherwise continue
416 if ((dirp
= opendir(p
)) != NULL
) {
417 while ((df
= readdir(dirp
)) != NULL
) {
420 if (fname
&& *fname
== '.') /* no hidden files here */
423 snprintf(bufn
, sizeof(bufn
), "%s%s*.*", p
, (p
[strlen(p
) - 1] == '\\') ? "" : "\\");
424 bufn
[sizeof(bufn
) - 1] = '\0';
425 if ((findrv
= _findfirst(bufn
, &dbuf
)) >= 0) {
427 fname
= fname_to_utf8(dbuf
.name
);
428 #endif /* ! _WINDOWS */
429 /* make sure that we have a file */
430 snprintf(buf2
, sizeof(buf2
), "%s%s%s", p
, S_FILESEP
, fname
);
431 buf2
[sizeof(buf2
) - 1] = '\0';
432 if (our_stat(buf2
, &sbuf
) == 0
433 && (sbuf
.st_mode
& S_IFMT
) != S_IFREG
)
436 /* make sure it is not already in the list */
437 for (cl
= data
; cl
; cl
= cl
->next
)
438 if (strcmp(cl
->name
, fname
) == 0)
443 /* ok, if it is not in the list, and it is a certificate. Add it */
447 if ((in
= BIO_new_file(buf2
, "r")) != 0) {
448 x
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
449 if (x
) { /* for now copy this information */
450 cert
= smime_X509_to_cert_info(x
, fname
);
451 /* we will use the cert->data.md5 variable to find a backup
452 certificate, not the name */
461 /* here we must check it is a key of some cert....*/
464 default: alpine_panic("Bad ctype (1)");
470 } while (_findnext(findrv
, &dbuf
) == 0);
472 #endif /* ! _WINDOWS */
474 /* Now that we are here, we have all the information in the backup
479 case Public
: ps_global
->smime
->backuppubliccertlist
= data
; break;
480 case Private
: ps_global
->smime
->backupprivatecertlist
= data
; break;
481 case CACert
: ps_global
->smime
->backupcacertlist
= data
; break;
482 default: alpine_panic("Bad ctype (n)");
485 } else if(SMHOLDERTYPE(ctype
) == Container
){
487 } /* else APPLEKEYCHAIN */
492 smime_get_cn(X509
*x
)
499 subject
= X509_get_subject_name(x
);
500 if((e
= X509_NAME_get_entry(subject
, X509_NAME_entry_count(subject
)-1)) != NULL
){
501 X509_NAME_get_text_by_OBJ(subject
, X509_NAME_ENTRY_get_object(e
), buf
, sizeof(buf
));
509 compare_certs_by_name(const void *data1
, const void *data2
)
514 CertList
*cl1
= *(CertList
**) data1
;
515 CertList
*cl2
= *(CertList
**) data2
;
518 if((s
= strchr(cl1
->name
, '@')) != NULL
){
523 if((s
= strchr(cl2
->name
, '@')) != NULL
){
528 if((rv
= strucmp(cl1
->name
, cl2
->name
)) == 0)
529 rv
= strucmp(cl1
->name
+ i
+ 1, cl2
->name
+ j
+ 1);
530 if(i
>= 0) cl1
->name
[i
] = '@';
531 if(j
>= 0) cl2
->name
[j
] = '@';
536 resort_certificates(CertList
**data
, WhichCerts ctype
)
539 CertList
*cl
= *data
;
546 for(i
= 0; cl
; cl
= cl
->next
, i
++)
547 if(SMHOLDERTYPE(ctype
) == Directory
&& ctype
!= Private
){
548 for(t
= s
= cl
->name
; (t
= strstr(s
, ".crt")) != NULL
; s
= t
+1);
549 if (s
) *(s
-1) = '\0';
552 cll
= fs_get(i
*sizeof(CertList
*));
553 for(cl
= *data
, i
= 0; cl
; cl
= cl
->next
, i
++)
555 qsort((void *)cll
, j
, sizeof(CertList
*), compare_certs_by_name
);
556 for(i
= 0; i
< j
- 1; i
++){
557 cll
[i
]->next
= cll
[i
+1];
558 if(SMHOLDERTYPE(ctype
) == Directory
&& ctype
!= Private
)
559 cll
[i
]->name
[strlen(cll
[i
]->name
)]= '.'; /* restore ".crt" part */
561 if(SMHOLDERTYPE(ctype
) == Directory
&& ctype
!= Private
)
562 cll
[j
-1]->name
[strlen(cll
[j
-1]->name
)]= '.'; /* restore ".crt" part */
563 cll
[j
-1]->next
= NULL
;
569 get_fingerprint(X509
*cert
, const EVP_MD
*type
, char *buf
, size_t maxLen
, char *s
)
571 unsigned char md
[128];
577 X509_digest(cert
, type
, md
, &len
);
581 for(i
=0; i
<len
; i
++){
585 if(i
!= 0 && s
&& *s
)
588 snprintf(b
, maxLen
- (b
-buf
), "%02x", md
[i
]);
595 * Remove leading whitespace, trailing whitespace and convert
596 * to lowercase. Also remove slash characters
598 * Args: s, -- The string to clean
600 * Result: the cleaned string
603 emailstrclean(char *string
)
605 char *s
= string
, *sc
= NULL
, *p
= NULL
;
607 for(; *s
; s
++){ /* single pass */
608 if(!isspace((unsigned char) (*s
))){
609 p
= NULL
; /* not start of blanks */
610 if(!sc
) /* first non-blank? */
611 sc
= string
; /* start copying */
613 else if(!p
) /* it's OK if sc == NULL */
614 p
= sc
; /* start of blanks? */
616 if(sc
&& *s
!='/' && *s
!='\\') /* if copying, copy */
617 *sc
++ = isupper((unsigned char) (*s
))
618 ? (unsigned char) tolower((unsigned char) (*s
))
619 : (unsigned char) (*s
);
622 if(p
) /* if ending blanks */
623 *p
= '\0'; /* tie off beginning */
624 else if(!sc
) /* never saw a non-blank */
625 *string
= '\0'; /* so tie whole thing off */
632 smime_get_date(const ASN1_TIME
*tm
)
634 BIO
*mb
= BIO_new(BIO_s_mem());
636 char date
[MAILTMPLEN
];
637 char buf
[MAILTMPLEN
];
638 char *m
, *d
, *t
, *y
, *z
;
643 (void) BIO_reset(mb
);
644 if(ASN1_TIME_print(mb
, tm
) == 0)
645 return cpystr(_("Invalid"));
647 (void) BIO_flush(mb
);
648 len
= BIO_read(mb
, iobuf
, sizeof(iobuf
));
651 /* openssl returns the date in the format:
652 * "MONTH (as name) DAY (as number) TIME(hh:mm:ss) YEAR GMT"
655 d
= strchr(iobuf
, ' ');
657 while(*d
== ' ') d
++;
658 t
= strchr(d
+1, ' ');
660 while(*t
== ' ') t
++;
661 y
= strchr(t
+1, ' ');
663 while(*y
== ' ') y
++;
664 z
= strchr(y
+1, ' ');
666 while(*z
== ' ') z
++;
668 snprintf(date
, sizeof(date
), "%s %s %s %s (%s)", d
, m
, y
, t
, z
);
669 date
[sizeof(date
)-1] = '\0';
670 if(F_ON(F_DATES_TO_LOCAL
,ps_global
)){
671 parse_date(convert_date_to_local(date
), &smd
);
672 memset(&smtm
, 0, sizeof(smtm
));
673 smtm
.tm_year
= smd
.year
- 1900;
674 smtm
.tm_mon
= MIN(MAX(smd
.month
-1, 0), 11);
675 smtm
.tm_mday
= MIN(MAX(smd
.day
, 1), 31);
676 our_strftime(buf
, sizeof(buf
), "%x", &smtm
);
679 snprintf(buf
, sizeof(buf
), "%s/%s/%s", m
, d
, y
+ strlen(y
) - 2);
680 buf
[sizeof(buf
)-1] = '\0';
687 * Add a lookup for each "*.crt*" file in the given directory.
690 add_certs_in_dir(X509_LOOKUP
*lookup
, char *path
, char *ext
, CertList
**cdata
)
692 char buf
[MAXPATH
], *fname
;
697 struct _finddata_t dbuf
;
698 char bufn
[_MAX_PATH
+ 4];
700 #endif /* !_WINDOWS */
702 int ret
= 0, nfiles
= 0, nerr
= 0;
705 if((dirp
= opendir(path
)) != NULL
){
706 while(!ret
&& (d
=readdir(dirp
)) != NULL
){
709 snprintf(bufn
, sizeof(bufn
), "%s%s*.*", path
, (path
[strlen(path
)-1] == '\\') ? "" : "\\");
710 bufn
[sizeof(bufn
)-1] = '\0';
711 if((findrv
= _findfirst(bufn
, &dbuf
)) >= 0){
713 fname
= fname_to_utf8(dbuf
.name
);
714 #endif /* ! _WINDOWS */
715 if(srchrstr(fname
, ext
)){
717 build_path(buf
, path
, fname
, sizeof(buf
));
719 if(!X509_LOOKUP_load_file(lookup
, buf
, X509_FILETYPE_PEM
)){
720 q_status_message1(SM_ORDER
, 3, 3, _("Error loading file %s"), buf
);
727 cert
= fs_get(sizeof(CertList
));
728 memset((void *)cert
, 0, sizeof(CertList
));
729 cert
->name
= cpystr(fname
);
730 /* read buf into a bio and fill the CertData structure */
731 if((in
= BIO_new_file(buf
, "r"))!=0){
732 if((x
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
)) != NULL
){
733 cert
->data
.date_from
= smime_get_date(X509_get0_notBefore(x
));
734 cert
->data
.date_to
= smime_get_date(X509_get0_notAfter(x
));
735 get_fingerprint(x
, EVP_md5(), buf
, sizeof(buf
), NULL
);
736 cert
->data
.md5
= cpystr(buf
);
737 cert
->cn
= smime_get_cn(x
);
745 for (cl
= *cdata
; cl
&& cl
->next
; cl
= cl
->next
);
757 } while(_findnext(findrv
, &dbuf
) == 0);
759 #endif /* ! _WINDOWS */
762 /* if all certificates fail to load */
763 if(nerr
> 0 && nerr
== nfiles
) ret
= -1;
769 * Get an X509_STORE. This consists of the system
770 * certs directory and any certificates in the user's
771 * ~/.alpine-smime/ca directory.
777 X509_STORE
*store
= NULL
;
779 dprint((9, "get_ca_store()"));
781 if(!(store
=X509_STORE_new())){
782 dprint((9, "X509_STORE_new() failed"));
786 if(!(lookup
=X509_STORE_add_lookup(store
, X509_LOOKUP_file()))){
787 dprint((9, "X509_STORE_add_lookup() failed"));
788 free_x509_store(&store
);
792 if(ps_global
->smime
&& ps_global
->smime
->catype
== Container
793 && ps_global
->smime
->cacontent
){
795 if(!mem_add_extra_cacerts(ps_global
->smime
->cacontent
, lookup
)){
796 free_x509_store(&store
);
800 else if(ps_global
->smime
&& ps_global
->smime
->catype
== Directory
801 && ps_global
->smime
->capath
){
802 if(add_certs_in_dir(lookup
, ps_global
->smime
->capath
, ".crt", &ps_global
->smime
->cacertlist
) < 0){
803 free_x509_store(&store
);
806 resort_certificates(&ps_global
->smime
->cacertlist
, CACert
);
809 if(!(lookup
= X509_STORE_add_lookup(store
, X509_LOOKUP_hash_dir()))){
810 free_x509_store(&store
);
814 #ifdef SMIME_SSLCERTS
815 dprint((9, "get_ca_store(): adding cacerts from %s", SMIME_SSLCERTS
));
816 X509_LOOKUP_add_dir(lookup
, SMIME_SSLCERTS
, X509_FILETYPE_PEM
);
823 free_x509_store(X509_STORE
**xstore
)
825 if(xstore
== NULL
|| *xstore
== NULL
)
827 X509_STORE_free(*xstore
);
832 load_key(PERSONAL_CERT
*pc
, char *pass
, int flag
)
835 EVP_PKEY
*key
= NULL
;
836 char buf
[MAXPATH
], file
[MAXPATH
];
838 if(!(ps_global
->smime
&& pc
&& pc
->name
))
841 if(ps_global
->smime
->privatetype
== Container
){
844 if(pc
->keytext
&& (q
= strstr(pc
->keytext
, "-----END")) != NULL
){
845 while(*q
&& *q
!= '\n')
851 if((in
= BIO_new_mem_buf(pc
->keytext
, q
-pc
->keytext
)) != NULL
){
852 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
857 else if(ps_global
->smime
->privatetype
== Directory
){
858 /* filename is path/name.key */
859 strncpy(buf
, pc
->name
, sizeof(buf
)-5);
860 buf
[sizeof(buf
)-5] = '\0';
861 strncat(buf
, ".key", 5);
862 build_path(file
, ps_global
->smime
->privatepath
, buf
, sizeof(file
));
864 if(!(in
= BIO_new_file(file
, "r")))
867 key
= PEM_read_bio_PrivateKey(in
, NULL
, NULL
, pass
);
875 #include <openssl/x509v3.h>
877 * This newer version is from Adrian Vogel. It looks for the email
878 * address not only in the email address field, but also in an
879 * X509v3 extension field, Subject Alternative Name.
882 get_x509_subject_email(X509
*x
)
884 char **result
= NULL
;
886 STACK_OF(OPENSSL_STRING
) *emails
= X509_get1_email(x
);
887 if ((n
= sk_OPENSSL_STRING_num(emails
)) > 0) {
888 result
= fs_get((n
+1)*sizeof(char *));
889 for(i
= 0; i
< n
; i
++)
890 result
[i
] = cpystr(sk_OPENSSL_STRING_value(emails
, i
));
893 X509_email_free(emails
);
899 * Save the certificate for the given email address in
900 * ~/.alpine-smime/public.
902 * Should consider the security hazards in making a file with
903 * the email address that has come from the certificate.
905 * The argument email is destroyed.
907 * args: ctype says where the user wants to save the certificate
910 save_cert_for(char *email
, X509
*cert
, WhichCerts ctype
)
912 if(!ps_global
->smime
|| ctype
== Private
)
915 dprint((9, "save_cert_for(%s, %s)", email
? email
: "?", ctype
== Public
? _("Public") : ctype
== Private
? _("Private") : "CACert"));
916 emailstrclean(email
);
918 if(ps_global
->smime
->publictype
== Keychain
){
922 SecCertificateRef secCertificateRef
;
925 memset((void *) &certData
, 0, sizeof(certData
));
926 memset((void *) &secCertificateRef
, 0, sizeof(secCertificateRef
));
928 /* convert OpenSSL X509 cert data to MacOS certData */
929 if((certData
.Length
= i2d_X509(cert
, &(certData
.Data
))) > 0){
932 * Put that certData into a SecCertificateRef.
933 * Version 3 should work for versions 1-3.
935 if(!(rc
=SecCertificateCreateFromData(&certData
,
937 CSSM_CERT_ENCODING_DER
,
938 &secCertificateRef
))){
940 /* add it to the default keychain */
941 if(!(rc
=SecCertificateAddToKeychain(secCertificateRef
, NULL
))){
944 else if(rc
== errSecDuplicateItem
){
945 dprint((9, "save_cert_for: certificate for %s already in keychain", email
));
948 dprint((9, "SecCertificateAddToKeychain failed"));
952 dprint((9, "SecCertificateCreateFromData failed"));
956 dprint((9, "i2d_X509 failed"));
959 #endif /* APPLEKEYCHAIN */
961 else if(SMHOLDERTYPE(ctype
) == Container
){
962 REMDATA_S
*rd
= NULL
;
963 char *ret_dir
= NULL
;
966 char *upath
= PATHCERTDIR(ctype
);
967 char *tempfile
= NULL
;
969 CertList
*clist
= DATACERT(ctype
);
971 add_to_end_of_certlist(&clist
, email
, X509_dup(cert
));
974 case Private
: ps_global
->smime
->privatecertlist
= clist
; break;
975 case Public
: ps_global
->smime
->publiccertlist
= clist
; break;
976 case CACert
: ps_global
->smime
->cacertlist
= clist
; break;
983 if(IS_REMOTE(upath
)){
984 rd
= rd_create_remote(RemImap
, upath
, REMOTE_SMIME_SUBTYPE
,
986 _("Can't access remote smime configuration."));
991 (void) rd_read_metadata(rd
);
993 if(rd
->access
== MaybeRorW
){
994 if(rd
->read_status
== 'R')
995 rd
->access
= ReadOnly
;
997 rd
->access
= ReadWrite
;
1000 if(rd
->access
!= NoExists
){
1002 rd_check_remvalid(rd
, 1L);
1005 * If the cached info says it is readonly but
1006 * it looks like it's been fixed now, change it to readwrite.
1008 if(rd
->read_status
== 'R'){
1009 rd_check_readonly_access(rd
);
1010 if(rd
->read_status
== 'W'){
1011 rd
->access
= ReadWrite
;
1012 rd
->flags
|= REM_OUTOFDATE
;
1015 rd
->access
= ReadOnly
;
1019 if(rd
->flags
& REM_OUTOFDATE
){
1020 if(rd_update_local(rd
) != 0){
1022 dprint((1, "save_cert_for: rd_update_local failed\n"));
1023 rd_close_remdata(&rd
);
1030 if(rd
->access
!= ReadWrite
|| rd_remote_is_readonly(rd
)){
1031 rd_close_remdata(&rd
);
1035 rd
->flags
|= DO_REMTRIM
;
1037 strncpy(path
, rd
->lf
, sizeof(path
)-1);
1038 path
[sizeof(path
)-1] = '\0';
1041 strncpy(path
, upath
, sizeof(path
)-1);
1042 path
[sizeof(path
)-1] = '\0';
1045 tempfile
= tempfile_in_same_dir(path
, "az", &ret_dir
);
1047 if(certlist_to_file(tempfile
, DATACERT(ctype
)))
1050 if(!err
&& ret_dir
){
1051 if(IS_REMOTE(upath
)){
1052 strncpy(fpath
, rd
->lf
, sizeof(fpath
));
1053 fpath
[sizeof(fpath
)-1] = '\0';
1056 if(strlen(path
) + strlen(tempfile
) - strlen(ret_dir
) + 1 < sizeof(path
))
1057 snprintf(fpath
, sizeof(fpath
), "%s%c%s",
1058 path
, tempfile
[strlen(ret_dir
)], tempfile
+ strlen(ret_dir
) + 1);
1065 fs_give((void **)&ret_dir
);
1068 if(rename_file(tempfile
, fpath
) < 0){
1069 q_status_message2(SM_ORDER
, 3, 3,
1070 _("Can't rename %s to %s"), tempfile
, fpath
);
1075 if(!err
&& IS_REMOTE(upath
)){
1081 we_cancel
= busy_cue(_("Copying to remote smime container"), NULL
, 1);
1082 if((e
= rd_update_remote(rd
, datebuf
)) != 0){
1084 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
1085 _("Error opening temporary smime file %s: %s"),
1086 rd
->lf
, error_description(errno
));
1088 "write_remote_smime: error opening temp file %s\n",
1089 rd
->lf
? rd
->lf
: "?"));
1092 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
1093 _("Error copying to %s: %s"),
1094 rd
->rn
, error_description(errno
));
1096 "write_remote_smime: error copying from %s to %s\n",
1097 rd
->lf
? rd
->lf
: "?", rd
->rn
? rd
->rn
: "?"));
1100 q_status_message(SM_ORDER
| SM_DING
, 5, 5,
1101 _("Copy of smime cert to remote folder failed, changes NOT saved remotely"));
1104 rd_update_metadata(rd
, datebuf
);
1105 rd
->read_status
= 'W';
1108 rd_close_remdata(&rd
);
1111 cancel_busy_cue(-1);
1114 fs_give((void **) &tempfile
);
1117 else if(SMHOLDERTYPE(ctype
) == Directory
){
1118 char *path
= PATHCERTDIR(ctype
);
1119 char certfilename
[MAXPATH
];
1122 build_path(certfilename
, path
, email
, sizeof(certfilename
));
1123 strncat(certfilename
, ".crt", sizeof(certfilename
)-1-strlen(certfilename
));
1124 certfilename
[sizeof(certfilename
)-1] = 0;
1126 bio_out
= BIO_new_file(certfilename
, "w");
1128 PEM_write_bio_X509(bio_out
, cert
);
1130 q_status_message1(SM_ORDER
, 1, 1, _("Saved certificate for <%s>"), email
);
1133 q_status_message1(SM_ORDER
, 1, 1, _("Couldn't save certificate for <%s>"), email
);
1140 * Try to retrieve the certificate for the given email address.
1141 * The caller should free the cert.
1144 get_cert_for(char *email
, WhichCerts ctype
, int tolower
)
1146 char certfilename
[MAXPATH
];
1147 char emailaddr
[MAXPATH
];
1151 if(ctype
== Password
){
1152 build_path(certfilename
, PATHCERTDIR(ctype
), email
, sizeof(certfilename
));
1153 strncat(certfilename
, EXTCERT(Public
), sizeof(certfilename
)-1-strlen(certfilename
));
1154 certfilename
[sizeof(certfilename
)-1] = 0;
1156 if((in
= BIO_new_file(certfilename
, "r"))!=0){
1158 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
1161 /* could check email addr in cert matches */
1170 if(!ps_global
->smime
)
1173 dprint((9, "get_cert_for(%s, %s)", email
? email
: "?", "none yet"));
1175 if(ctype
== Private
) /* there is no private certificate info */
1176 ctype
= Public
; /* return public information instead */
1177 strncpy(emailaddr
, email
, sizeof(emailaddr
)-1);
1178 emailaddr
[sizeof(emailaddr
)-1] = 0;
1180 /* clean it up (lowercase, space removal) */
1182 emailstrclean(emailaddr
);
1184 if(ps_global
->smime
->publictype
== Keychain
){
1185 #ifdef APPLEKEYCHAIN
1188 SecKeychainItemRef itemRef
= nil
;
1189 SecKeychainAttributeList attrList
;
1190 SecKeychainAttribute attrib
;
1191 SecKeychainSearchRef searchRef
= nil
;
1194 /* low-level form of MacOS data */
1195 memset((void *) &certData
, 0, sizeof(certData
));
1198 attrList
.attr
= &attrib
;
1200 /* kSecAlias means email address for a certificate */
1201 attrib
.tag
= kSecAlias
;
1202 attrib
.data
= emailaddr
;
1203 attrib
.length
= strlen(attrib
.data
);
1205 /* Find the certificate in the default keychain */
1206 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
1207 kSecCertificateItemClass
,
1211 if(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
))){
1213 /* extract the data portion of the certificate */
1214 if(!(rc
=SecCertificateGetData((SecCertificateRef
) itemRef
, &certData
))){
1217 * Convert it from MacOS form to OpenSSL form.
1218 * The input is certData from above and the output
1219 * is the X509 *cert.
1221 if(!d2i_X509(&cert
, &(certData
.Data
), certData
.Length
)){
1222 dprint((9, "d2i_X509 failed"));
1226 dprint((9, "SecCertificateGetData failed"));
1229 else if(rc
== errSecItemNotFound
){
1230 dprint((9, "get_cert_for: Public cert for %s not found", emailaddr
));
1233 dprint((9, "SecKeychainSearchCopyNext failed"));
1237 dprint((9, "SecKeychainSearchCreateFromAttributes failed"));
1241 CFRelease(searchRef
);
1243 #endif /* APPLEKEYCHAIN */
1245 else if(SMHOLDERTYPE(ctype
) == Container
){
1248 for(cl
= DATACERT(ctype
); cl
; cl
= cl
->next
){
1249 if(cl
->name
&& !strucmp(emailaddr
, cl
->name
))
1254 cert
= X509_dup((X509
*) cl
->x509_cert
);
1256 else if(SMHOLDERTYPE(ctype
) == Directory
){
1257 build_path(certfilename
, PATHCERTDIR(ctype
), emailaddr
, sizeof(certfilename
));
1258 strncat(certfilename
, EXTCERT(ctype
), sizeof(certfilename
)-1-strlen(certfilename
));
1259 certfilename
[sizeof(certfilename
)-1] = 0;
1261 if((in
= BIO_new_file(certfilename
, "r"))!=0){
1263 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
1266 /* could check email addr in cert matches */
1278 * load_cert_for_key finds a certificate in pathdir that matches a private key
1279 * pkey. It returns its name in certfile, and the certificate in *pcert.
1280 * return value: success: different from zero, failure 0. If both certfile
1281 * and pcert are NULL, this function returns if there is certificate that
1282 * matches the given key.
1285 load_cert_for_key(char *pathdir
, EVP_PKEY
*pkey
, char **certfile
, X509
**pcert
)
1290 #else /* _WINDOWS */
1291 struct _finddata_t dbuf
;
1292 char bufn
[_MAX_PATH
+ 4];
1294 #endif /* ! _WINDOWS */
1299 char buf
[MAXPATH
+1], pathcert
[MAXPATH
+1], *fname
;
1301 if(pathdir
== NULL
|| pkey
== NULL
)
1304 if(certfile
) *certfile
= NULL
;
1305 if(pcert
) *pcert
= NULL
;
1308 if((dirp
= opendir(pathdir
)) != NULL
){
1309 while(rv
== 0 && (d
=readdir(dirp
)) != NULL
){
1312 snprintf(bufn
, sizeof(bufn
), "%s%s*.*", pathdir
, (pathdir
[strlen(pathdir
)-1] == '\\') ? "" : "\\");
1313 bufn
[sizeof(bufn
)-1] = '\0';
1314 if((findrv
= _findfirst(bufn
, &dbuf
)) >= 0){
1316 fname
= fname_to_utf8(dbuf
.name
);
1317 #endif /* ! _WINDOWS */
1318 if((ll
=strlen(fname
)) && ll
> 4){
1319 if(!strcmp(fname
+ll
-4, ".crt")){
1320 strncpy(buf
, fname
, sizeof(buf
));
1321 buf
[sizeof(buf
)-1] = '\0';
1322 build_path(pathcert
, pathdir
, buf
, sizeof(pathcert
));
1323 if((in
= BIO_new_file(pathcert
, "r")) != NULL
){
1324 if((x
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
)) != NULL
){
1325 if(X509_check_private_key(x
, pkey
) > 0){
1327 if(certfile
) *certfile
= cpystr(buf
);
1328 if(pcert
) *pcert
= x
;
1340 #else /* _WINDOWS */
1341 } while(_findnext(findrv
, &dbuf
) == 0);
1350 mem_to_personal_certs(char *contents
)
1352 PERSONAL_CERT
*result
= NULL
;
1353 char *p
, *q
, *line
, *name
, *keytext
, *save_p
;
1356 if(contents
&& *contents
){
1357 for(p
= contents
; *p
!= '\0';){
1360 while(*p
&& *p
!= '\n')
1369 if(strncmp(EMAILADDRLEADER
, line
, strlen(EMAILADDRLEADER
)) == 0){
1370 name
= line
+ strlen(EMAILADDRLEADER
);
1371 cert
= get_cert_for(name
, Public
, 1);
1374 /* advance p past this record */
1375 if((q
= strstr(keytext
, "-----END")) != NULL
){
1376 while(*q
&& *q
!= '\n')
1386 q_status_message(SM_ORDER
| SM_DING
, 3, 3, _("Error in privatekey container, missing END"));
1392 pc
= (PERSONAL_CERT
*) fs_get(sizeof(*pc
));
1394 pc
->name
= cpystr(name
);
1396 pc
->keytext
= keytext
; /* a pointer into contents */
1398 pc
->key
= load_key(pc
, "", SM_NORMALCERT
);
1415 mem_to_certlist(char *contents
, WhichCerts ctype
)
1417 CertList
*ret
= NULL
;
1418 char *p
, *q
, *line
, *name
, *certtext
, *save_p
;
1421 char *sep
= (ctype
== Public
|| ctype
== Private
)
1422 ? EMAILADDRLEADER
: CACERTSTORELEADER
;
1424 if(contents
&& *contents
){
1425 for(p
= contents
; *p
!= '\0';){
1428 while(*p
&& *p
!= '\n')
1437 if(strncmp(sep
, line
, strlen(sep
)) == 0){
1438 name
= line
+ strlen(sep
);
1440 certtext
= strstr(p
, "-----BEGIN");
1441 if(certtext
!= NULL
){
1442 if((q
= strstr(certtext
, sep
)) != NULL
)
1445 p
= q
= certtext
+strlen(certtext
);
1447 if((in
= BIO_new_mem_buf(certtext
, q
-certtext
)) != 0){
1448 cert
= PEM_read_bio_X509(in
, NULL
, NULL
, NULL
);
1453 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, _("Error in %scert container, missing BEGIN, certtext=%s"), ctype
== Public
? _("public") : _("ca"), p
);
1458 add_to_end_of_certlist(&ret
, name
, cert
);
1466 resort_certificates(&ret
, ctype
);
1473 * Add the CACert Container contents into the CACert store.
1475 * Returns > 0 for success, 0 for failure
1478 mem_add_extra_cacerts(char *contents
, X509_LOOKUP
*lookup
)
1480 char *p
, *q
, *line
, *certtext
, *save_p
;
1482 int len
, failed
= 0;
1487 * The most straight-forward way to do this is to write
1488 * the container contents to a temp file and then load the
1489 * contents of the file with X509_LOOKUP_load_file(), like
1490 * is done in add_certs_in_dir(). What we don't know is if
1491 * each file should consist of one cacert or if they can all
1492 * just be jammed together into one file. To be safe, we'll use
1493 * one file per and do each in a separate operation.
1496 if(contents
&& *contents
){
1497 for(p
= contents
; *p
!= '\0';){
1500 while(*p
&& *p
!= '\n')
1509 /* look for separator line */
1510 if(strncmp(CACERTSTORELEADER
, line
, strlen(CACERTSTORELEADER
)) == 0){
1511 /* certtext is the content that should go in a file */
1512 certtext
= strstr(p
, "-----BEGIN");
1513 if(certtext
!= NULL
){
1514 if((q
= strstr(certtext
, CACERTSTORELEADER
)) != NULL
){
1517 else{ /* end of file */
1518 q
= certtext
+ strlen(certtext
);
1522 in
= BIO_new_mem_buf(certtext
, q
-certtext
);
1524 tempfile
= temp_nam(NULL
, "az");
1525 out
= tempfile
!= NULL
? BIO_new_file(tempfile
, "w") : NULL
;
1527 while((len
= BIO_read(in
, iobuf
, sizeof(iobuf
))) > 0)
1528 BIO_write(out
, iobuf
, len
);
1531 if(!X509_LOOKUP_load_file(lookup
, tempfile
, X509_FILETYPE_PEM
))
1535 if(tempfile
!= NULL
){
1537 fs_give((void **) &tempfile
);
1545 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing BEGIN, certtext=%s"), certtext
);
1550 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, _("Error in cacert container, missing separator, line=%s"), line
);
1563 certlist_to_file(char *filename
, CertList
*certlist
)
1566 BIO
*bio_out
= NULL
;
1569 if(filename
&& (bio_out
=BIO_new_file(filename
, "w")) != NULL
){
1571 for(cl
= certlist
; cl
; cl
= cl
->next
){
1572 if(cl
->name
&& cl
->name
[0] && cl
->x509_cert
){
1573 if(!((BIO_puts(bio_out
, EMAILADDRLEADER
) > 0)
1574 && (BIO_puts(bio_out
, cl
->name
) > 0)
1575 && (BIO_puts(bio_out
, "\n") > 0)))
1578 if(!PEM_write_bio_X509(bio_out
, (X509
*) cl
->x509_cert
))
1591 add_to_end_of_certlist(CertList
**cl
, char *name
, X509
*cert
)
1598 new = smime_X509_to_cert_info(cert
, name
);
1605 free_certlist(CertList
**cl
)
1608 if((*cl
)->data
.date_from
)
1609 fs_give((void **) &(*cl
)->data
.date_from
);
1611 if((*cl
)->data
.date_to
)
1612 fs_give((void **) &(*cl
)->data
.date_to
);
1615 fs_give((void **) &(*cl
)->data
.md5
);
1618 fs_give((void **) &(*cl
)->name
);
1621 fs_give((void **) &(*cl
)->cn
);
1623 if((*cl
)->x509_cert
)
1624 X509_free((X509
*) (*cl
)->x509_cert
);
1626 free_certlist(&(*cl
)->next
);
1628 fs_give((void **) cl
);
1634 free_personal_certs(PERSONAL_CERT
**pc
)
1638 fs_give((void **) &(*pc
)->name
);
1641 fs_give((void **) &(*pc
)->cname
);
1644 X509_free((*pc
)->cert
);
1647 EVP_PKEY_free((*pc
)->key
);
1649 free_personal_certs(&(*pc
)->next
);
1651 fs_give((void **) pc
);