summaryrefslogtreecommitdiff
path: root/pith/smime.c
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2014-04-08 23:57:49 -0600
committerEduardo Chappa <chappa@washington.edu>2014-04-08 23:57:49 -0600
commit7d34d6b88a46a8cf950dc6305fa3c781edd9d4f7 (patch)
treeabcec7bc059644ca5ed9322e6bb1dda09743eccc /pith/smime.c
parent6a18937898292e97c15289e5ecd5d8f1c2205110 (diff)
downloadalpine-7d34d6b88a46a8cf950dc6305fa3c781edd9d4f7.tar.xz
* Fixes bug in 2.19.8 that would make Alpine fail to build in Windows.
* S/MIME configuration screen would deinitialize smime, not allowing it to send encrypted or signed messages. * Add documentation for /loser option in definition of external servers. * crashing bug in certificate management screen due to a BIO_free() call of memory that had not been allocated. * When the password file is decrypted, smime is inited. If smime is inited before the .pinerc is read, some values might not be correctly set. * When a password file exists, and S/MIME is enabled, encrypt it by either using an existing key/certificate pair. The key is saved separately in ~/.alpine-smime/.pwd, or in the directory specified by the -pwdcertdir command line option.
Diffstat (limited to 'pith/smime.c')
-rw-r--r--pith/smime.c430
1 files changed, 326 insertions, 104 deletions
diff --git a/pith/smime.c b/pith/smime.c
index e691ae68..05d41e7a 100644
--- a/pith/smime.c
+++ b/pith/smime.c
@@ -72,10 +72,14 @@ int smime_path(char *rpath, char *fpath, size_t len);
int smime_extract_and_save_cert(PKCS7 *p7);
int same_cert(X509 *, X509 *);
CertList * certlist_from_personal_certs(PERSONAL_CERT *pc);
+#ifdef PASSFILE
+void load_key_and_cert(char *pathdir, char **keyfile, char **certfile, EVP_PKEY **pkey, X509 **pcert);
+#endif /* PASSFILE */
int (*pith_opt_smime_get_passphrase)(void);
int (*pith_smime_import_certificate)(char *, char *, size_t);
-char *(*pith_enter_password)(char *prompt, char *, size_t);
+int (*pith_smime_enter_password)(char *prompt, char *, size_t);
+int (*pith_opt_get_data_prompt)(char *prompt, char *, size_t);
static X509_STORE *s_cert_store;
@@ -84,33 +88,277 @@ static int seeded = 0;
static int egdsocket = 0;
+#ifdef PASSFILE
+void
+load_key_and_cert(char *pathdir, char **keyfile,
+ char **certfile, EVP_PKEY **pkey, X509 **pcert)
+{
+ BIO *in;
+ char buf[MAXPATH+1], pathkey[MAXPATH+1], prompt[MAILTMPLEN], pass[MAILTMPLEN];
+ DIR *dirp;
+ struct dirent *d;
+ int rc;
+
+ if(keyfile) *keyfile = NULL;
+ if(certfile) *certfile = NULL;
+ if(pkey) *pkey = NULL;
+ if(pcert) *pcert = NULL;
+
+ if(pathdir == NULL) return;
+
+ if((dirp = opendir(pathdir)) != NULL){
+ while((d=readdir(dirp)) != NULL){
+ size_t ll;
+
+ if((ll=strlen(d->d_name)) && ll > 4){
+ if(keyfile && !strcmp(d->d_name+ll-4, ".key") && pkey == NULL){
+ strncpy(buf, d->d_name, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ build_path(pathkey, pathdir, buf, sizeof(pathkey));
+ buf[strlen(buf)-4] = '\0';
+ if((in = BIO_new_file(pathkey, "r")) != NULL){
+
+ snprintf(prompt, sizeof(prompt),
+ _("Enter password of key <%s> to unlock password file: "), buf);
+
+ do {
+ rc = 0;
+ pass[0] = '\0';
+ if(pith_smime_enter_password)
+ rc = (*pith_smime_enter_password)((char *)prompt, (char *)pass, sizeof(pass));
+ } while (rc!=0 && rc!=1 && rc>0);
+
+ if((*pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, pass)) != NULL){
+ *keyfile = cpystr(buf);
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 2,
+ _("loading private key <%s> failed. Continuing..."), buf);
+ BIO_free(in);
+ }
+ }
+
+ if(certfile && !strcmp(d->d_name+ll-4, ".crt") && pcert == NULL){
+ strncpy(buf, d->d_name, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ build_path(pathkey, pathdir, buf, sizeof(pathkey));
+ if((in = BIO_new_file(pathkey, "r")) != NULL){
+ if((*pcert = PEM_read_bio_X509(in, NULL, NULL, NULL)) != NULL)
+ *certfile = cpystr(buf);
+ else{
+ q_status_message1(SM_ORDER, 0, 2,
+ _("loading public certificate %s failed. Continuing..."), buf);
+ }
+ BIO_free(in);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* setup a key and certificate to encrypt and decrypt a password file.
+ * These files will be saved in the .alpine-smime/.pwd directory, but its
+ * location can be setup in the command line with the -pwdcertdir option.
+ * Here are the rules:
+ *
+ * Check if the .alpine-smime/.pwd (or -pwdcertdir directory) exists,
+ * if not create it. If we are successful, move to the next step
+ *
+ * - If the user has a key/cert pair, setup is successful;
+ * - if the used does not have a key/cert pair, or one cannot be found
+ * create one, setup is successful; (TODO: implement this)
+ * - in any other case, setup is not successful.
+ *
+ * If setup is successful, setup ps_global->smime->pwdcert.
+ * If any of this fails, ps_global->smime->pwdcert will be null.
+ * Ok, that should do it.
+ */
+void
+setup_pwdcert(void)
+{
+ int setup_dir = 0; /* make it non zero if we know which dir to use */
+ int rc,i,j,k;
+ struct stat sbuf;
+ char pathdir[MAXPATH+1], pathkey[MAXPATH+1], fpath[MAXPATH+1], pass[200];
+ char prompt[MAILTMPLEN];
+ EVP_PKEY *pkey = NULL;
+ X509 *pcert = NULL;
+ BIO *in;
+ PERSONAL_CERT *pc, *pc2 = NULL;
+
+ smime_init();
+
+ if(ps_global->pwdcertdir){
+ if(our_stat(ps_global->pwdcertdir, &sbuf) == 0
+ && ((sbuf.st_mode & S_IFMT) == S_IFDIR)){
+ setup_dir++;
+ strncpy(pathdir, ps_global->pwdcertdir, sizeof(pathdir));
+ pathdir[sizeof(pathdir)-1] = '\0';
+ }
+ } else {
+ smime_path(DF_PASSWORD_DIR, pathdir, sizeof(pathdir));
+ if(our_stat(pathdir, &sbuf) == 0){
+ if((sbuf.st_mode & S_IFMT) == S_IFDIR)
+ setup_dir++;
+ } else {
+ if(can_access(pathdir, ACCESS_EXISTS) != 0
+ && our_mkpath(pathdir, 0700) == 0)
+ setup_dir++;
+ }
+ }
+
+ if(setup_dir == 0)
+ return;
+
+ /* reuse setup dir to mean setup_key */
+ setup_dir = 0;
+
+ if(ps_global->smime->personal_certs){
+ pc = (PERSONAL_CERT *) ps_global->smime->personal_certs;
+ if(ps_global->smime->privatetype == Directory){
+ build_path(pathkey, ps_global->smime->privatepath, pc->name, sizeof(pathkey));
+ strncat(pathkey, ".key", 4);
+ pathkey[sizeof(pathkey)-1] = '\0';
+ if((in = BIO_new_file(pathkey, "r")) != NULL){
+ snprintf(prompt, sizeof(prompt),
+ _("Enter password of key <%s> to unlock password file: "), pc->name);
+
+ do {
+ rc = 0;
+ pass[0] = '\0';
+ if(pith_smime_enter_password)
+ rc = (*pith_smime_enter_password)((char *)prompt, (char *)pass, sizeof(pass));
+ } while (rc!=0 && rc!=1 && rc>0);
+
+ if((pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, pass)) != NULL){
+ pc2 = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT));
+ memset((void *)pc2, 0, sizeof(PERSONAL_CERT));
+ pc2->name = cpystr(pc->name);
+ pc2->key = pkey;
+ pc2->cert = X509_dup(pc->cert);
+ build_path(fpath, pathdir, pc->name, sizeof(fpath));
+ strncat(fpath, ".key", 4);
+ fpath[sizeof(fpath)-1] = '\0';
+ if(our_stat(fpath, &sbuf) == 0){
+ if((sbuf.st_mode & S_IFMT) == S_IFREG)
+ setup_dir++;
+ }
+ else if(our_copy(fpath, pathkey) == 0)
+ setup_dir++;
+
+ if(setup_dir){
+ setup_dir = 0;
+
+ build_path(pathkey, ps_global->smime->publicpath, pc->name, sizeof(pathkey));
+ strncat(pathkey, ".crt", 4);
+ pathkey[sizeof(pathkey)-1] = '\0';
+
+ build_path(fpath, pathdir, pc->name, sizeof(fpath));
+ strncat(fpath, ".crt", 4);
+ fpath[sizeof(fpath)-1] = '\0';
+
+ if(our_stat(fpath, &sbuf) == 0){
+ if((sbuf.st_mode & S_IFMT) == S_IFREG)
+ setup_dir++;
+ }
+ else if(our_copy(fpath, pathkey) == 0)
+ setup_dir++;
+ }
+
+ if(setup_dir)
+ ps_global->smime->pwdcert = (void *) pc2;
+ else{
+ free_personal_certs(&pc2);
+ return;
+ }
+ }
+ }
+ }
+ } /* end of if(ps_global->personal_certs) */
+ else {
+ /* if we are here it might be because we have not read the .pinerc file
+ * or because there are no certificates to copy. Which one is it?
+ */
+ /* look for a private key and public cert in pathdir
+ look for a public cert in pathdir
+ if not successful find one in DF_PRIVATE_DIR, etc.
+ if not successful, create a self-signed certificate and save it in pathdir.
+ if successful, load the keys.
+ if successful, set these keys as the keys to encrypt/decrypt.
+ */
+ char buf[MAXPATH+1];
+ char *keyfile;
+ char *certfile;
+ DIR *dirp;
+ struct dirent *d;
+
+ load_key_and_cert(pathdir, &keyfile, &certfile, &pkey, &pcert);
+ if(certfile && keyfile)
+ setup_dir++;
+
+ if(setup_dir == 0){
+ if(keyfile == NULL){
+ /* PATHCERTDIR(Private) must be null, so create a path */
+ set_current_val(&ps_global->vars[V_PRIVATEKEY_DIR], TRUE, TRUE);
+ smime_path(ps_global->VAR_PRIVATEKEY_DIR, pathkey, MAXPATH);
+ load_key_and_cert(pathkey, &keyfile, NULL, &pkey, NULL);
+ }
+ if(certfile == NULL){
+ /* PATHCERTDIR(Public) must be null, so create a path */
+ set_current_val(&ps_global->vars[V_PUBLICCERT_DIR], TRUE, TRUE);
+ smime_path(ps_global->VAR_PUBLICCERT_DIR, pathkey, MAXPATH);
+ load_key_and_cert(pathkey, NULL, &certfile, NULL, &pcert);
+ }
+
+ smime_reinit();
+ if(certfile && keyfile)
+ setup_dir++;
+ }
+
+ /* TODO: create self signed certificate */
+ if(setup_dir == 0)
+ q_status_message(SM_ORDER, 0, 2,
+ _("No key/certificate pair found. Password file not encrypted."));
+
+ if(keyfile && certfile){
+ pc2 = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT));
+ memset((void *)pc, 0, sizeof(PERSONAL_CERT));
+ pc2->name = keyfile;
+ pc2->key = pkey;
+ pc2->cert = pcert;
+ ps_global->smime->pwdcert = (void *) pc2;
+ }
+
+ if(certfile)
+ fs_give((void **)&certfile);
+ }
+}
+#endif /* PASSFILE */
+
+
int
import_certificate(WhichCerts ctype)
{
int r = 1;
char filename[MAXPATH+1], full_filename[MAXPATH+1], buf[MAXPATH+1];
- if(pith_smime_import_certificate == NULL)
+ if(pith_smime_import_certificate == NULL){
q_status_message(SM_ORDER, 0, 2,
_("import of certificates not implemented yet!"));
+ return -1;
+ }
smime_init();
r = (*pith_smime_import_certificate)(filename, full_filename, sizeof(filename) - 20);
- if(r < 0){
- switch(r){
- default:
- case -1:
- cmd_cancelled("Import certificate");
- break;
- case -2:
- q_status_message1(SM_ORDER, 0, 2,
- _("Can't import certificate outside of %s"),
- ps_global->VAR_OPER_DIR);
- break;
- }
- } else if (ctype == Private){
+ ps_global->mangled_screen = 1;
+
+ if(r < 0)
+ return r;
+ else if (ctype == Private){
char prompt[500], *s, *t;
char pass[MAILTMPLEN+1];
BIO *in;
@@ -132,8 +380,8 @@ import_certificate(WhichCerts ctype)
_("Enter passphrase for <%s>: "), s ? s : filename);
pass[0] = '\0';
- if(pith_enter_password)
- (*pith_enter_password)(prompt, (char *)pass, sizeof(pass));
+ if(pith_smime_enter_password)
+ (*pith_smime_enter_password)(prompt, (char *)pass, sizeof(pass));
if((key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass)) != NULL){
if(SMHOLDERTYPE(ctype) == Directory){
@@ -223,7 +471,6 @@ import_certificate(WhichCerts ctype)
}
}
if(DATACERT(ctype)) RENEWCERT(DATACERT(ctype)) = 1;
- ps_global->mangled_screen = 1;
return 0;
}
@@ -302,14 +549,12 @@ forget_private_keys(void)
* directory where the .pinerc is located, since according to documentation,
* the .alpine-smime directories are subdirectories of the home directory
*/
-
int smime_path(char *rpath, char *fpath, size_t len)
{
*fpath = '\0';
if(rpath && *rpath){
size_t spl = strlen(rpath);
- *fpath = '\0';
if(IS_REMOTE(rpath)){
if(spl < len - 1)
strncpy(fpath, rpath, len-1);
@@ -492,7 +737,12 @@ renew_cert_data(CertList **data, WhichCerts ctype)
}
}
-
+void
+smime_reinit(void)
+{
+ smime_deinit();
+ smime_init();
+}
/* Installed as an atexit() handler to save the random data */
void
@@ -531,7 +781,6 @@ smime_init(void)
ERR_load_crypto_strings();
app_RAND_load_file(NULL);
-
openssl_extra_randomness();
ps_global->smime->inited = 1;
}
@@ -1631,57 +1880,56 @@ bio_from_store(STORE_S *store)
* Encrypt file; given a path (char *) fp, replace the file
* by an encrypted version of it. If (char *) text is not null, then
* replace the text of (char *) fp by the encrypted version of (char *) text.
+ * certpath is the FULL path to the file containing the certificate used for
+ * encryption.
*/
int
-encrypt_file(char *fp, char *text)
+encrypt_file(char *fp, char *text, PERSONAL_CERT *pc)
{
const EVP_CIPHER *cipher = NULL;
STACK_OF(X509) *encerts = NULL;
X509 *cert;
- PERSONAL_CERT *pcert;
- BIO *in;
+ BIO *out;
PKCS7 *p7 = NULL;
FILE *fpp;
- int rv = 0;
+ int i, rv = 0;
+ char *fname;
+ char emailaddress[MAILTMPLEN];
smime_init();
- if((pcert = ps_global->smime->personal_certs) == NULL)
- return 0;
-
cipher = EVP_aes_256_cbc();
encerts = sk_X509_new_null();
-
- if((cert = get_cert_for(pcert->name, Public)) != NULL)
- sk_X509_push(encerts, cert);
- else
- goto end;
+
+ if(pc == NULL)
+ goto end;
+
+ sk_X509_push(encerts, pc->cert);
if(text){
- in = BIO_new(BIO_s_mem());
- if(in == NULL)
+ if((out = BIO_new(BIO_s_mem())) == NULL)
goto end;
- (void) BIO_reset(in);
- BIO_puts(in, text);
+ (void) BIO_reset(out);
+ BIO_puts(out, text);
}
else{
- if(!(in = BIO_new_file(fp, "rb")))
+ if(!(out = BIO_new_file(fp, "rb")))
goto end;
- BIO_read_filename(in, fp);
+ BIO_read_filename(out, fp);
}
- if((p7 = PKCS7_encrypt(encerts, in, cipher, 0)) == NULL)
+ if((p7 = PKCS7_encrypt(encerts, out, cipher, 0)) == NULL)
goto end;
- BIO_set_close(in, BIO_CLOSE);
- BIO_free(in);
- if(!(in = BIO_new_file(fp, "w")))
+ BIO_set_close(out, BIO_CLOSE);
+ BIO_free(out);
+ if(!(out = BIO_new_file(fp, "w")))
goto end;
- BIO_reset(in);
- rv = PEM_write_bio_PKCS7(in, p7);
- BIO_flush(in);
+ BIO_reset(out);
+ rv = PEM_write_bio_PKCS7(out, p7);
+ BIO_flush(out);
end:
- BIO_free(in);
+ BIO_free(out);
PKCS7_free(p7);
sk_X509_pop_free(encerts, X509_free);
@@ -2171,94 +2419,60 @@ find_certificate_matching_pkcs7(PKCS7 *p7)
/* decrypt an encrypted file.
Args: fp - the path to the encrypted file.
- rv - a code that thells the caller what happened inside the function
+ rv - a code that tells the caller what happened inside the function
+ pcert - a personal certificate that was used to encrypt this file
Returns the decoded text allocated in a char *, whose memory must be
freed by caller
*/
char *
-decrypt_file(char *fp, int *rv)
+decrypt_file(char *fp, int *rv, PERSONAL_CERT *pc)
{
PKCS7 *p7 = NULL;
- char *text, *tmp;
BIO *in = NULL, *out = NULL;
- EVP_PKEY *pkey = NULL, *key = NULL;
- PERSONAL_CERT *pcert = NULL;
- X509 *recip, *cert;
- STORE_S *outs = NULL, *store, *ins;
- int i, j;
+ int i;
long unsigned int len;
+ char *text, *s;
void *ret;
smime_init();
- if((text = read_file(fp, 0)) == NULL)
+ if(rv) *rv = -1; /* assume error initially */
+
+ if((text = read_file(fp, 0)) == NULL
+ || pc == NULL
+ || pc->key == NULL)
return NULL;
- tmp = fs_get(strlen(text) + (strlen(text) << 6) + 1);
- for(j = 0, i = strlen("-----BEGIN PKCS7-----") + 1; text[i] != '\0'
- && text[i] != '-'; j++, i++)
- tmp[j] = text[i];
- tmp[j] = '\0';
+ i = strlen("-----BEGIN PKCS7-----") + 1;
+ if((s = strchr(text+i, '-')) != NULL)
+ *s = '\0';
- ret = rfc822_base64(tmp, strlen(tmp), &len);
+ ret = rfc822_base64(text+i, strlen(text+i), &len);
if((in = BIO_new_mem_buf((char *)ret, len)) != NULL){
p7 = d2i_PKCS7_bio(in, NULL);
BIO_free(in);
}
- if(text) fs_give((void **)&text);
- if(ret) fs_give((void **)&ret);
-
- if((pcert = ps_global->smime->personal_certs) == NULL)
- goto end;
-
- if((i = load_private_key(pcert)) == 0
- && ps_global->smime
- && ps_global->smime->need_passphrase
- && !ps_global->smime->already_auto_asked)
- for(; i == 0;){
- ps_global->smime->already_auto_asked = 1;
- if(pith_opt_smime_get_passphrase){
- switch((*pith_opt_smime_get_passphrase)()){
- case 0 : i = load_private_key(pcert);
- break;
-
- case 1 : i = -1;
- break;
-
- default: break; /* repeat until we cancel */
- }
- }
- else
- i = -2;
- }
-
- if(rv) *rv = i;
+ if (ret) fs_give((void **)&ret);
- if((key = pcert->key) == NULL)
- goto end;
-
- recip = get_cert_for(pcert->name, Public);
out = BIO_new(BIO_s_mem());
(void) BIO_reset(out);
- i = PKCS7_decrypt(p7, key, recip, out, 0);
-
- if(F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global))
- forget_private_keys();
-
- if(i == 0){
+ if(PKCS7_decrypt(p7, pc->key, pc->cert, out, 0) == 0){
q_status_message1(SM_ORDER, 1, 1, _("Error decrypting: %s"),
(char*) openssl_error_string());
goto end;
}
- BIO_get_mem_data(out, &tmp);
-
- text = cpystr(tmp);
+ BIO_get_mem_data(out, &s);
+ i = strlen(s);
+ fs_resize((void **)&text, i+1);
+ strncpy(text, s, i+1);
+ text[i] = '\0';
BIO_free(out);
+ if (rv) *rv = 1;
end:
PKCS7_free(p7);
@@ -2766,6 +2980,14 @@ free_smime_struct(SMIME_STUFF_S **smime)
(*smime)->personal_certs = NULL;
}
+ if((*smime)->pwdcert){
+ PERSONAL_CERT *pc;
+
+ pc = (PERSONAL_CERT *) (*smime)->pwdcert;
+ free_personal_certs(&pc);
+ (*smime)->pwdcert = NULL;
+ }
+
if((*smime)->privatecontent)
fs_give((void **) &(*smime)->privatecontent);