summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2016-10-05 01:10:52 -0600
committerEduardo Chappa <chappa@washington.edu>2016-10-05 01:10:52 -0600
commit4f2c1e32cfe0ebcb628c5a55a52eef283aa39446 (patch)
tree31327e907a51c422e05f91f827bd1b166ce988d5
parent174c8ccf0d4aae97fc5858d082c58fd5b23402a0 (diff)
downloadalpine-4f2c1e32cfe0ebcb628c5a55a52eef283aa39446.tar.xz
* When Alpine is compiled with password file and SMIME support
the password file is encrypted using a private key/public certificate pair. If one such pair cannot be found, one will be created.
-rw-r--r--alpine/imap.c64
-rw-r--r--alpine/keymenu.c2
-rw-r--r--alpine/smime.c40
-rw-r--r--pith/pine.hlp97
-rw-r--r--pith/readfile.c10
-rw-r--r--pith/smime.c124
-rw-r--r--pith/smime.h6
-rw-r--r--pith/smkeys.c211
-rw-r--r--pith/smkeys.h16
9 files changed, 479 insertions, 91 deletions
diff --git a/alpine/imap.c b/alpine/imap.c
index d5c97eee..45cc860a 100644
--- a/alpine/imap.c
+++ b/alpine/imap.c
@@ -2302,7 +2302,7 @@ read_passfile(pinerc, l)
#else /* PASSFILE */
char tmp[MAILTMPLEN], *ui[5];
- int i, j, n;
+ int i, j, n, rv = 0;
#ifdef SMIME
char tmp2[MAILTMPLEN];
char *text = NULL, *text2 = NULL;
@@ -2333,18 +2333,48 @@ read_passfile(pinerc, l)
* do not init smime here, because the .pinerc might not have been
* read and we do not really know where the keys and certificates really
* are.
+ * Remark: setupwdcert will produce a null ps_global->pwdcert only when
+ * it is called for the first time and there are certificates at all,
+ * or when it is called after the first time and the user refuses to
+ * create a self-signed certificate. In this situation we will just
+ * let the user live in an insecure world, but no more passwords will
+ * be saved in the password file, and only those found there will be used.
*/
- if(ps_global->pwdcert == NULL)
- setup_pwdcert(&ps_global->pwdcert);
tmp2[0] = '\0';
fgets(tmp2, sizeof(tmp2), fp);
fclose(fp);
if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
- if(encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
- encrypted++;
+ /* there is an already existing password file, that is not encrypted
+ * and there is no key to encrypt it yet, go again through setup_pwdcert
+ * and encrypt it now.
+ */
+ if(tmp2[0]){ /* not empty, UNencrypted password file */
+ if(ps_global->pwdcert == NULL)
+ rv = setup_pwdcert(&ps_global->pwdcert);
+ if(rv == 0 && ps_global->pwdcert == NULL)
+ ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
+ if(ps_global->pwdcert == NULL){
+ q_status_message(SM_ORDER, 3, 3,
+ " Failed to create private key. Using UNencrypted Password file. ");
+ save_password = 0;
+ }
+ else{
+ if(rv == 1){
+ q_status_message(SM_ORDER, 3, 3,
+ " Failed to unlock private key. Using UNencrypted Password file. ");
+ save_password = 0; /* do not save more passwords */
+ }
+ }
+ if(ps_global->pwdcert != NULL
+ && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
+ encrypted++;
+ }
}
- else
+ else {
+ if(ps_global->pwdcert == NULL)
+ rv = setup_pwdcert(&ps_global->pwdcert);
encrypted++;
+ }
/*
* if password file is encrypted we attemtp to decrypt. We ask the
@@ -2568,7 +2598,7 @@ write_passfile(pinerc, l)
}
#ifdef SMIME
- strncpy(tmp2, tmp, sizeof(tmp2)-1);
+ strncpy(tmp2, tmp, sizeof(tmp2));
tmp2[sizeof(tmp2)-1] = '\0';
#endif /* SMIME */
@@ -2603,13 +2633,21 @@ write_passfile(pinerc, l)
fclose(fp);
#ifdef SMIME
if(text != NULL){
- if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0){
- if((fp = our_fopen(tmp2, "wb")) != NULL){
- fputs(text, fp);
- fclose(fp);
- }
+ if(ps_global->pwdcert == NULL){
+ q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
+ i = setup_pwdcert(&ps_global->pwdcert);
+ if(i == 0 && ps_global->pwdcert == NULL)
+ ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
+ }
+ if(ps_global->pwdcert == NULL){ /* we tried but failed */
+ if(i == -1)
+ q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
+ else
+ q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
}
- fs_give((void **)&text);
+ else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
+ q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
+ fs_give((void **)&text); /* do not save this text */
}
#endif /* SMIME */
#endif /* PASSFILE */
diff --git a/alpine/keymenu.c b/alpine/keymenu.c
index 7c8d167e..1d5c7f64 100644
--- a/alpine/keymenu.c
+++ b/alpine/keymenu.c
@@ -2670,7 +2670,7 @@ struct key config_smime_add_new_key[] =
NULL_MENU,
EXIT_SETUP_MENU,
{"I", N_("Import Key"), {MC_IMPORT,3,{'i', ctrl('M'), ctrl('J')}}, KS_NONE},
- NULL_MENU,
+ {"C", N_("Create Key"), {MC_ADD,1,{'c'}}, KS_NONE},
NULL_MENU,
NULL_MENU,
NULL_MENU,
diff --git a/alpine/smime.c b/alpine/smime.c
index 2eb58588..06693aa7 100644
--- a/alpine/smime.c
+++ b/alpine/smime.c
@@ -1269,6 +1269,41 @@ manage_certs_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags)
WhichCerts ctype = (*cl)->d.s.ctype;
switch(cmd){
+ case MC_ADD: /* create a self signed certificate and import it */
+ if(ctype == Password){
+ PERSONAL_CERT *pc;
+ char pathdir[MAXPATH+1], filename[MAXPATH+1];
+ struct stat sbuf;
+ int st;
+ smime_path(DF_SMIMETMPDIR, pathdir, sizeof(pathdir));
+ if(((st = our_stat(pathdir, &sbuf)) == 0
+ && (sbuf.st_mode & S_IFMT) == S_IFDIR)
+ || (st != 0
+ && can_access(pathdir, ACCESS_EXISTS) != 0
+ && our_mkpath(pathdir, 0700) == 0)){
+ pc = ALPINE_self_signed_certificate(NULL, 0, pathdir, MASTERNAME);
+ snprintf(filename, sizeof(filename), "%s/%s.key",
+ pathdir, MASTERNAME);
+ filename[sizeof(filename)-1] = '\0';
+ rv = import_certificate(ctype, pc, filename);
+ if(our_stat(pathdir, &sbuf) == 0){
+ if(unlink(filename) < 0)
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Could not remove private key %s.key"), MASTERNAME);
+ filename[strlen(filename)-4] = '\0';
+ strcat(filename, ".crt");
+ if(unlink(filename) < 0)
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Could not remove public certifica %s.crt"), MASTERNAME);
+ if(rmdir(pathdir) < 0)
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Could not remove temporary directory %s"), pathdir);
+ }
+ }
+ rv = 10; /* forces redraw */
+ }
+ break;
+
case MC_CHOICE:
if(PATHCERTDIR(ctype) == NULL)
return 0;
@@ -1320,7 +1355,7 @@ manage_certs_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags)
break;
}
case MC_IMPORT:
- rv = import_certificate(ctype);
+ rv = import_certificate(ctype, NULL, NULL);
if(rv < 0){
switch(rv){
default:
@@ -1384,6 +1419,7 @@ void manage_password_file_certificates(struct pine *ps)
if(ctmp == NULL){
ps->mangled_screen = 1;
// smime_reinit();
+ q_status_message(SM_ORDER, 1, 3, _("Failed to initialize password management screen (no key)"));
return;
}
@@ -1774,7 +1810,7 @@ smime_helper_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags)
break;
case MC_IMPORT:
- rv = import_certificate((*cl)->d.s.ctype);
+ rv = import_certificate((*cl)->d.s.ctype, NULL, NULL);
break;
default:
diff --git a/pith/pine.hlp b/pith/pine.hlp
index ea684a73..0a8e2ef7 100644
--- a/pith/pine.hlp
+++ b/pith/pine.hlp
@@ -140,7 +140,7 @@ with help text for the config screen and the composer that didn't have any
reasonable place to be called from.
Dummy change to get revision in pine.hlp
============= h_revision =================
-Alpine Commit 172 2016-09-29 09:24:36
+Alpine Commit 173 2016-10-05 01:10:48
============= h_news =================
<HTML>
<HEAD>
@@ -188,6 +188,11 @@ Additions include:
<LI> Unix-Alpine: Connect securely to a LDAP server on a secure port.
Based on a contribution by Wang Kang.
+ <LI> When Alpine is compiled with password file and SMIME support
+ the password file is encrypted using a private key/public
+ certificate pair. If one such pair cannot be found, one will be
+ created. <A HREF="h_password_file_support">Learn more</A>.
+
<LI> Alpine builds with any version bigger or equal to 1.0.0c, including
version 1.1.0, as well as LibreSSL.
@@ -1100,7 +1105,8 @@ or instead you can find the Apache License, version 2.0 at the web URL:
Index<BR>
<OL>
<LI><A HREF="#content">Explanation</A>
-<LI><A HREF="#example">Example</A>
+<LI><A HREF="#example_existing_key">Example of Use of Existing Key and Certificate</A>
+<LI><A HREF="#example_self_signed">Example of Creating Master Password</A>
</OL>
<P><A NAME="content">Unix Alpine Only.</A>
@@ -1109,28 +1115,38 @@ Index<BR>
then you can use a special file to save your passwords, and avoid typing
them every time you open a connection to a remote server.
-<P> If your version of Alpine was built with SMIME support, and you have a
-public certificate/private key pair, then Alpine will use such pair to
-encrypt your password file. If you have more than one key/certificate
-pair, Alpine will pick the first pair that it finds that works. You can also
-select a pair, and the way to do this is explained below.
+<P> If, in addition, your version of Alpine was built with SMIME support, then your
+password file will be encrypted with a strong key. There are two ways in
+which this can happen: Alpine will either use a matching private key and
+public certificate pair that you already own, or it will create one for
+you, just for purposes of encrypting this file. We describe both processes
+below.
+
+<P> Initially, Alpine will scan your public and private directories for a
+certificate/private key pair that works. Alpine will pick the first pair
+that it finds that matches.
<P> Once a pair has been chosen, it will be copied to the directory
~/.alpine-smime/.pwd, and from then on, Alpine will use the pair found in
that directory. The first time this process is done, this directory will
-be created, a key/certificate pair will be copied to it, and this pair
-will be used in the future to encrypt and decrypt your password file. You
-can create this directory and copy any key/certificate pair there. You
-can add a self-signed certificate there, if you like, and you can let
-this certificate expire. This will not affect the encryption and decryption
+be created, a key/certificate pair will be copied to it, from then on
+this pair will be used to encrypt and decrypt your password file.
+
+<P> If you want to use a specific key and certificate pair to encrypt
+your password file, you can create the directory ~/.alpine-smime/.pwd
+manually, and then create your preferred key/certificate pair there.
+Alpine will use this key regardless of if it has expired, or if it is
+self-signed. These issues do not affect the encryption or decryption
of the password file.
<P> If you prefer not to use the directory ~/.alpine-smime/.pwd to save
your key/certificate pair, you can specify a different one with the
-pwdcertdir command line option in Alpine. If the directory specified by
-this option is not found or there is no valid key/certificate pair there,
-Alpine will fail to encrypt and decrypt your password file. In other words,
-Alpine will not initialize this directory for you.
+this option is not found Alpine will fail to encrypt and decrypt your
+password file. However if it exists, Alpine will search for a
+key/certificate pair in that
+directory, and if it does not find one, it will create one and save it
+in that directory.
<P> Alpine does not care about the names of the key and certificates in
this directory, but the private key must have &quot;.key&quot; extension
@@ -1138,7 +1154,15 @@ and your public certificate must have the &quot;.crt&quot; extension. The
name of the private key will be used in the prompt when you are asked
to unlock your key to decrypt your password.
-<P><A NAME="example">An example follows</A>
+<P> If Alpine cannot find a suitable private key and public certificate
+pair to encrypt your password, it will create one. You will be asked to
+create a &quot;Master Password&quot; to protect such key. At this moment
+there are no restrictions on passwords, other than they have to be at
+least 8 characters long, but future versions of Alpine will include
+functionality to restrict master passwords, as set up by the administrator
+of the system in the pine.conf.fixed file.
+
+<P><A NAME="example_existing_key"><B>Example of Use of Existing Key and Certificate</B></A>
<P>Assume you have a private key called peter@address.com.key in your,
~/.alpine-smime/private directory, and a public certificate called
@@ -1178,10 +1202,43 @@ Enter password of key &lt;private_key&gt; to unlock password file:
<P>Observe that you do not need to use an existing key/certificate pair,
and that you can create a new private key/public certificate pair to
-encrypt and decrypt your password. However, once one is used, Alpine does
-not provide a mechanism to switch the encryption and decryption files to
-another key/certificate pair. This will be implemented in a future
-release of Alpine.
+encrypt and decrypt your password file. Alpine provides a mechanism to
+change the encryption key for this file in the S/MIME configuration
+screen.
+
+<P><A NAME="example_self_signed"><B>Example of Creating Master Password</B></A>
+
+<P> If Alpine cannot find a suitable private key and public certificate pair
+to encrypt your password file, it will create one. When doing so, it will
+start the process with the following warning:
+
+<PRE>
+Creating a Master Password for your Password file.
+</PRE>
+
+<P> Then Alpine will ask you to enter your Master Password:
+
+<PRE>
+Create master password (attempt 1 of 3):
+</PRE>
+
+<P> Once you enter this password, and it validates according to system policy,
+you will be asked to confirm this password.
+
+<PRE>
+Confirm master password (attempt 1 of 3):
+</PRE>
+
+<P> If you input the same password, then Alpine will set that as your
+Master Password, and you will use this password to unlock your key in the
+future.
+
+<P> If you would like to switch your Master Password in the future, you can
+do so by creating a new public key and public certificate pair. You can do
+so in the S/MIME configuration screen, in the &quot;Manage Key and
+Certificate for Password File&quot; section, simply enter your current
+password to unlock your current key and then press &quot;C&quot; to create
+a new key.
<P>
&lt;End of help&gt;
diff --git a/pith/readfile.c b/pith/readfile.c
index cae03fc1..e918ec99 100644
--- a/pith/readfile.c
+++ b/pith/readfile.c
@@ -78,6 +78,7 @@ our_copy(char *to_file, char *from_file)
{
STORE_S *in_cert, *out_cert;
unsigned char c;
+ long int size = 0;
in_cert = so_get(FileStar, from_file, READ_ACCESS | READ_FROM_LOCALE);
if (in_cert == NULL)
@@ -89,9 +90,14 @@ our_copy(char *to_file, char *from_file)
return -1;
}
- while(so_readc(&c, in_cert) > 0)
- so_writec(c, out_cert);
+ so_seek(out_cert, 0L, 0);
+ so_truncate(out_cert, 0);
+ while(so_readc(&c, in_cert) > 0){
+ so_writec(c, out_cert);
+// size++;
+ }
+// so_truncate(out_cert, size);
so_give(&in_cert);
so_give(&out_cert);
diff --git a/pith/smime.c b/pith/smime.c
index ba98506f..9ee83340 100644
--- a/pith/smime.c
+++ b/pith/smime.c
@@ -48,6 +48,7 @@ static char rcsid[] = "$Id: smime.c 1176 2008-09-29 21:16:42Z hubert@u.washingto
#include <openssl/buffer.h>
#include <openssl/x509v3.h>
+#include <openssl/evp.h>
/* internal prototypes */
static void forget_private_keys(void);
@@ -85,8 +86,8 @@ int smime_validate_extra_test(char *mimetext, unsigned long mimelen, char
int (*pith_opt_smime_get_passphrase)(void);
int (*pith_smime_import_certificate)(char *, char *, char *, size_t);
-int (*pith_smime_enter_password)(char *prompt, char *, size_t);
-int (*pith_smime_confirm_save)(char *email);
+int (*pith_smime_enter_password)(char *, char *, size_t);
+int (*pith_smime_confirm_save)(char *);
static X509_STORE *s_cert_store;
@@ -200,10 +201,16 @@ load_key_and_cert(char *pathkeydir, char *pathcertdir, char **keyfile,
* If setup is successful, setup ps_global->pwdcert.
* If any of this fails, ps_global->pwdcert will be null.
* Ok, that should do it.
+ *
+ * return values: 0 - everything is normal
+ * 1 - User could not unlock key
+ * 2 - User cancelled to create self signed certificate
+ * -1 - a not normal value.
*/
-void
+int
setup_pwdcert(void **pwdcert)
{
+ int rv;
int we_inited = 0;
int setup_dir = 0; /* make it non zero if we know which dir to use */
struct stat sbuf;
@@ -213,10 +220,10 @@ setup_pwdcert(void **pwdcert)
EVP_PKEY *pkey = NULL;
X509 *pcert = NULL;
PERSONAL_CERT *pc, *pc2 = NULL;
- static int was_here = 0;
+ static int was_here = 0, setup_certdir = 0;
if(pwdcert == NULL || was_here == 1)
- return;
+ return -1;
was_here++;
if(ps_global->pwdcertdir){
@@ -238,17 +245,18 @@ setup_pwdcert(void **pwdcert)
if(setup_dir == 0){
was_here = 0;
- return;
+ return -1;
}
if(load_key_and_cert(pathdir, pathdir, &keyfile, &certfile, &pkey, &pcert) < 0){
was_here = 0;
- return;
+ return 1;
}
-
- if(ps_global->pwdcertdir == NULL) /* save the result of pwdcertdir */
+ if(ps_global->pwdcertdir == NULL){ /* save the result of pwdcertdir */
+ setup_certdir = 1;
ps_global->pwdcertdir = cpystr(pathdir);
+ }
if(certfile && keyfile){
pc = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT));
@@ -259,16 +267,16 @@ setup_pwdcert(void **pwdcert)
pc->cname = certfile;
*pwdcert = (void *) pc;
was_here = 0;
- return;
+ return 0;
}
/* if the user gave a pwdcertdir and there is nothing there, do not
* continue. Let the user initialize on their own this directory.
*/
- if(ps_global->pwdcertdir != NULL){
+ if(setup_certdir){ /* if we are here, pwdcertdir failed */
was_here = 0;
- return;
- }
+ return -1;
+ }
/* look to see if there are any certificates lying around, first
* we try to load ps_global->smime to see if that has information
@@ -379,7 +387,7 @@ setup_pwdcert(void **pwdcert)
if(setup_dir){
*pwdcert = (void *) pc2;
was_here = 0;
- return;
+ return 0;
}
else if(pc2 != NULL)
free_personal_certs(&pc2);
@@ -432,16 +440,13 @@ setup_pwdcert(void **pwdcert)
*pwdcert = (void *) pc;
fs_give((void **)&certfile);
was_here = 0;
- return;
+ return 0;
}
-/* TODO: create self signed certificate
- q_status_message(SM_ORDER, 2, 2,
- _("No key/certificate pair found for password file encryption support"));
-*/
was_here = 0;
if(we_inited)
smime_deinit();
+ return 0;
}
#endif /* PASSFILE */
@@ -603,26 +608,38 @@ load_pkey_with_prompt(char *fpath, char *text, char *prompt, int *ret)
/* This is a tool for conf_screen, The return value must be zero when
* nothing changed, so if there is a failure in the import return 0
- * and return 1 when we succeeded
+ * and return 1 when we succeeded.\
+ * We call this function in two ways:
+ * either fname is null or not. If they fname is null, so is p_cert.
+ * if p_cert is not null, it is the PERSONAL_CERT structure of fname if this
+ * is available, otherwise we will fill it up here.
*/
int
-import_certificate(WhichCerts ctype)
+import_certificate(WhichCerts ctype, PERSONAL_CERT *p_cert, char *fname)
{
int r = 1, rc;
char filename[MAXPATH+1], full_filename[MAXPATH+1], buf[MAXPATH+1];
char *what;
- if(pith_smime_import_certificate == NULL){
+ if(pith_smime_import_certificate == NULL
+ || pith_smime_enter_password == NULL){
q_status_message(SM_ORDER, 0, 2,
_("import of certificates not implemented yet!"));
return 0;
}
- what = ctype == Public || ctype == CACert ? "certificate" : "key";
- r = (*pith_smime_import_certificate)(filename, full_filename, what, sizeof(filename) - 20);
-
- if(r < 0)
- return 0;
+ if(fname == NULL){
+ what = ctype == Public || ctype == CACert ? "certificate" : "key";
+ r = (*pith_smime_import_certificate)(filename, full_filename, what, sizeof(filename) - 20);
+
+ if(r < 0)
+ return 0;
+ } else {
+ char *s;
+ strncpy(full_filename, fname, sizeof(full_filename));
+ if((s = strrchr(full_filename, '/')) != '\0')
+ strncpy(filename, s+1, sizeof(filename));
+ }
/* we are trying to import a new key for the password file. First we ask for the
* private key. Once this is loaded, we make a reasonable attempt to find the
@@ -637,7 +654,7 @@ import_certificate(WhichCerts ctype)
char full_name_key[MAXPATH+1], full_name_cert[MAXPATH+1];
char *use_this_file;
char prompt[500];
- EVP_PKEY *key = NULL;
+ EVP_PKEY *key = p_cert ? p_cert->key : NULL;
rc = 1; /* assume success :) */
if(strlen(filename) > 4){
@@ -654,11 +671,12 @@ import_certificate(WhichCerts ctype)
return 0;
}
- snprintf(prompt, sizeof(prompt), _("Enter passphrase for <%s>: "), filename);
+ snprintf(prompt, sizeof(prompt), _("Enter passphrase to unlock new key <%s>: "), filename);
prompt[sizeof(prompt)-1] = '\0';
- if((key = load_pkey_with_prompt(full_filename, NULL, prompt, NULL)) != NULL){
+ if(key != NULL
+ || (key = load_pkey_with_prompt(full_filename, NULL, prompt, NULL)) != NULL){
BIO *ins = NULL;
- X509 *cert = NULL;
+ X509 *cert = p_cert ? p_cert->cert : NULL, *cert2;
strncpy(full_name_key, full_filename, sizeof(full_filename));
full_name_key[sizeof(full_name_key)-1] = '\0';
@@ -687,15 +705,20 @@ import_certificate(WhichCerts ctype)
strncat(PublicCertPath, EXTCERT(Public), 4);
PublicCertPath[sizeof(PublicCertPath)-1] = '\0';
}
-
- /* attempt #1 to guess public cert name, use .crt extension */
- if((ins = BIO_new_file(full_name_cert, "r")) != NULL){
- if((cert = PEM_read_bio_X509(ins, NULL, NULL, NULL)) != NULL){
+ /* attempt #1, use provided certificate,
+ * assumption is that full_name_cert is the file that this
+ * certificate derives from (which is obtained by substitution
+ * of .key extension in key by .crt extension)
+ */
+ if(cert != NULL) /* attempt #1 */
use_this_file = &full_name_cert[0];
- }
+ else if((ins = BIO_new_file(full_name_cert, "r")) != NULL){
+ /* attempt #2 to guess public cert name, use .crt extension */
+ if((cert = PEM_read_bio_X509(ins, NULL, NULL, NULL)) != NULL){
+ use_this_file = &full_name_cert[0];
+ }
}
- else{
- /* attempt #2 to guess public cert name: user the original key */
+ else{ /* attempt #3 to guess public cert name: use the original key */
if((ins = BIO_new_file(full_name_key, "r")) != NULL){
if((cert = PEM_read_bio_X509(ins, NULL, NULL, NULL)) != NULL){
use_this_file = &full_name_key[0];
@@ -703,7 +726,7 @@ import_certificate(WhichCerts ctype)
}
else {
int done = 0;
- /* attempt #3, ask the user */
+ /* attempt #4, ask the user */
do {
r = (*pith_smime_import_certificate)(filename, use_this_file, "certificate", sizeof(filename) - 20);
if(r < 0){
@@ -751,7 +774,7 @@ import_certificate(WhichCerts ctype)
char tmp2[MAILTMPLEN];
int encrypted = 0;
char *text;
- PERSONAL_CERT *pwdcert, *pc;
+ PERSONAL_CERT *pwdcert, *pc = p_cert;
pwdcert = (PERSONAL_CERT *) ps_global->pwdcert;
if(pwdcert == NULL)
@@ -770,15 +793,17 @@ import_certificate(WhichCerts ctype)
if(encrypted){
text = decrypt_file((char *)tmp, NULL, pwdcert);
if(text != NULL){
- pc = fs_get(sizeof(PERSONAL_CERT));
- memset((void *)pc, 0, sizeof(PERSONAL_CERT));
- filename[strlen(filename)-strlen(EXTCERT(Private))] = '\0';
- pc->name = cpystr(filename);
- snprintf(buf, sizeof(buf), "%s%s", filename, EXTCERT(Public));
- buf[sizeof(buf)-1] = '\0';
- pc->cname = cpystr(buf);
- pc->key = key;
- pc->cert = cert;
+ if(pc == NULL){
+ pc = fs_get(sizeof(PERSONAL_CERT));
+ memset((void *)pc, 0, sizeof(PERSONAL_CERT));
+ filename[strlen(filename)-strlen(EXTCERT(Private))] = '\0';
+ pc->name = cpystr(filename);
+ snprintf(buf, sizeof(buf), "%s%s", filename, EXTCERT(Public));
+ buf[sizeof(buf)-1] = '\0';
+ pc->cname = cpystr(buf);
+ pc->key = key;
+ pc->cert = cert;
+ }
if(encrypt_file((char *)tmp, text, pc)){ /* we did it! */
build_path(buf, PATHCERTDIR(ctype), pwdcert->name, sizeof(buf));
@@ -2449,6 +2474,7 @@ bio_from_store(STORE_S *store)
* 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.
+ * return value: 0 - failed to encrypt; 1 - success!
*/
int
encrypt_file(char *fp, char *text, PERSONAL_CERT *pc)
diff --git a/pith/smime.h b/pith/smime.h
index 01e4cdc0..ca92fbca 100644
--- a/pith/smime.h
+++ b/pith/smime.h
@@ -24,12 +24,14 @@
#include "../pith/filttype.h"
#include "../pith/smkeys.h"
+#include <openssl/x509.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#ifdef PASSFILE
#define DF_PASSWORD_DIR ".alpine-smime/.pwd"
#endif
+#define DF_SMIMETMPDIR ".alpine-smime/smtmp"
#define OUR_PKCS7_ENCLOSURE_SUBTYPE "x-pkcs7-enclosure"
@@ -68,14 +70,14 @@ int copy_privatecert_dir_to_container(void);
int copy_privatecert_container_to_dir(void);
int copy_cacert_dir_to_container(void);
int copy_cacert_container_to_dir(void);
-int import_certificate(WhichCerts);
+int import_certificate(WhichCerts, PERSONAL_CERT *, char *);
int copy_dir_to_container(WhichCerts which, char *contents);
#ifdef APPLEKEYCHAIN
int copy_publiccert_container_to_keychain(void);
int copy_publiccert_keychain_to_container(void);
#endif /* APPLEKEYCHAIN */
#ifdef PASSFILE
-void setup_pwdcert(void **pwdcert);
+int setup_pwdcert(void **pwdcert);
#endif /* PASSFILE */
void mark_cert_deleted(WhichCerts ctype, int num, unsigned state);
unsigned get_cert_deleted(WhichCerts ctype, int num);
diff --git a/pith/smkeys.c b/pith/smkeys.c
index 8666d53b..09b2a0e5 100644
--- a/pith/smkeys.c
+++ b/pith/smkeys.c
@@ -48,7 +48,218 @@ static char rcsid[] = "$Id: smkeys.c 1266 2009-07-14 18:39:12Z hubert@u.washingt
static char *emailstrclean(char *string);
static int mem_add_extra_cacerts(char *contents, X509_LOOKUP *lookup);
int compare_certs_by_name(const void *data1, const void *data2);
+int password_policy_check(char *);
+int (*pith_smime_enter_password)(char *, char *, size_t);
+
+/* test if password passes a predetermined policy.
+ * return value: 0 - does not pass; 1 - it passes
+ */
+int
+password_policy_check(char *password)
+{
+ int rv = 1;
+ char *error;
+ char tmp[1024];
+
+ if(password == NULL || password[0] == '\0'){
+ error = _("Password cannot be blank");
+ rv = 0;
+ } else if(strlen(password) < 8){
+ error = _("Password is too short");
+ rv = 0;
+ }
+ if(rv == 0){
+ snprintf(tmp, sizeof(tmp), "%s%s", error, _(". Enter password again"));
+ tmp[sizeof(tmp) - 1] = '\0';
+ q_status_message(SM_ORDER, 3, 3, tmp);
+ }
+ return rv;
+}
+
+
+int
+create_master_password(char *pass, size_t passlen, int first_time)
+{
+#define MAXTRIAL 3
+ int rv, trial;
+ char prompt[MAILTMPLEN];
+ char passbackup[MAILTMPLEN];
+
+ if(first_time)
+ q_status_message(SM_ORDER, 3, 3,
+ _(" Creating a Master Password for your Password file "));
+ else
+ q_status_message(SM_ORDER, 3, 3,
+ _(" Retrying to create a Master Password for your Password file "));
+
+ for(trial = 0; trial < MAXTRIAL; trial++){
+ snprintf(prompt, sizeof(prompt),
+ _("Create master password \(attempt %d of %d): "), trial+1, MAXTRIAL);
+ prompt[sizeof(prompt)- 1] = '\0';
+ pass[0] = '\0';
+ do {
+ rv = (pith_smime_enter_password)(prompt, pass, passlen);
+ if(password_policy_check(pass) == 0)
+ pass[0] = '\0';
+ } while ((rv !=0 && rv !=1 && rv > 0) || pass[0] == '\0');
+
+ snprintf(prompt, sizeof(prompt),
+ _("Confirm master password \(attempt %d of %d): "), trial+1, MAXTRIAL);
+ prompt[sizeof(prompt)- 1] = '\0';
+ passbackup[0] = '\0';
+ do {
+ rv = (pith_smime_enter_password)(prompt, passbackup, sizeof(passbackup));
+ } while ((rv !=0 && rv !=1 && rv > 0) || passbackup[0] == '\0');
+ if(!strcmp(pass, passbackup))
+ break;
+ if(trial + 1 < MAXTRIAL)
+ q_status_message(SM_ORDER, 2, 2, _("Passwords do not match, try again."));
+ else{
+ q_status_message(SM_ORDER, 2, 2, _("Passwords do not match, too many failures."));
+ pass[0] = '\0';
+ }
+ }
+ return (trial < MAXTRIAL) ? 1 : 0;
+}
+
+/*
+ * Create a self signed certificate with root name _fname_, in directory
+ * _pathdir_. If _version_ is 3, we use the _template_ file as configuration
+ * file for openssl. At this moment, we only call this function with template = NULL
+ * and version = 0, but a sensible call is
+ * ALPINE_self_signed_certificate("/etc/ssl/openssl.cnf", 2, pathdir, fname, first_time);
+ * or so.
+ * _pathdir_ is the directory to save the file,
+ * _fname_ is the root of the name to use. Append ".key" and ".crt" to this name
+ * _first_time_ is an indicator to tell us if this is the first time we call this function
+ */
+PERSONAL_CERT *
+ALPINE_self_signed_certificate(char *template, int version, char *pathdir, char *fname)
+{
+ BIGNUM *b = NULL;
+ X509_NAME *name = NULL;
+ X509_REQ *req = NULL;
+ EVP_PKEY_CTX *pkctx;
+ BIO *out = NULL;
+ char tmp[MAXPATH+1], password[1024];
+ char *keyfile = NULL, *certfile = NULL;
+ char *extensions = NULL;
+ FILE *fp;
+ long errline = -1L;
+ PERSONAL_CERT *pc = NULL;
+ EVP_PKEY *pkey = NULL;
+ X509 *pcert = NULL;
+ CONF *req_conf = NULL;
+ static int first_time = 1;
+
+ if(pathdir == NULL)
+ return NULL;
+
+ if(template){
+ if((out = BIO_new_file(template, "r")) == NULL){
+ q_status_message(SM_ORDER, 2, 2, _("Problem reading configuration file"));
+ return pc;
+ }
+
+ if((req_conf = NCONF_new(NULL)) != NULL
+ && NCONF_load_bio(req_conf, out, &errline) > 0){
+ if((extensions = NCONF_get_string(req_conf, "req", "x509_extensions")) != NULL){
+ X509V3_CTX ctx;
+ X509V3_set_ctx_test(&ctx);
+ X509V3_set_nconf(&ctx, req_conf);
+ if (!X509V3_EXT_add_nconf(req_conf, &ctx, extensions, NULL)) {
+ q_status_message(SM_ORDER, 2, 2, _("Problem loading openssl configuration"));
+ NCONF_free(req_conf);
+ return pc;
+ }
+ }
+ }
+ BIO_free(out);
+ out = NULL;
+ }
+
+ if(create_master_password(password, sizeof(password), first_time)
+ && (pkctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) != NULL
+ && EVP_PKEY_keygen_init(pkctx) > 0
+ && EVP_PKEY_CTX_set_rsa_keygen_bits(pkctx, 2048) > 0 /* RSA:2048 */
+ && EVP_PKEY_keygen(pkctx, &pkey) > 0){
+ snprintf(tmp, sizeof(tmp), "%s.key", fname);
+ tmp[sizeof(tmp)-1] = '\0';
+ keyfile = cpystr(tmp);
+ build_path(tmp, pathdir, keyfile, sizeof(tmp));
+ keyfile[strlen(keyfile)-4] = '\0'; /* keyfile does not have .key extension */
+ if((fp = fopen(tmp, "w")) != NULL
+ && (out = BIO_new_fp(fp, BIO_CLOSE | BIO_FP_TEXT)) != NULL
+ && PEM_write_bio_PrivateKey(out, pkey, EVP_des_ede3_cbc(),
+ NULL, 0, NULL, password)){
+ BIO_free(out);
+ out = NULL;
+ }
+ memset((void *)password, 0, sizeof(password));
+ if((req = X509_REQ_new()) != NULL
+ && X509_REQ_set_version(req, 0L)){
+ name = X509_REQ_get_subject_name(req);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, "Password File Certificate and Key Pair", -1, -1, 0);
+ if(X509_REQ_set_pubkey(req, pkey)
+ && (pcert = X509_new()) != NULL){
+ if(X509_set_version(pcert, version)
+ && (b = BN_new()) != NULL
+ && BN_set_word(b, 65537)
+ && BN_pseudo_rand(b, 64, 0, 0)
+ && X509_get_serialNumber(pcert)
+ && BN_to_ASN1_INTEGER(b, X509_get_serialNumber(pcert)) /* set serial */
+ && X509_set_issuer_name(pcert, X509_REQ_get_subject_name(req))
+ && X509_set_subject_name(pcert, X509_REQ_get_subject_name(req))){
+ X509V3_CTX ext_ctx;
+ EVP_PKEY *tmppkey;
+
+ X509_gmtime_adj(X509_getm_notBefore(pcert), 0);
+ X509_time_adj_ex(X509_getm_notAfter(pcert), 1095, 0, NULL);
+
+ if((tmppkey = X509_REQ_get0_pubkey(req)) != NULL
+ && X509_set_pubkey(pcert, tmppkey)){
+ if(extensions != NULL && version == 2){
+ X509V3_set_ctx(&ext_ctx, pcert, pcert, NULL, NULL, 0);
+ if(req_conf){ /* only if template is not null */
+ X509V3_set_nconf(&ext_ctx, req_conf);
+ X509V3_EXT_add_nconf(req_conf, &ext_ctx, extensions, pcert);
+ }
+ }
+ EVP_PKEY_free(tmppkey);
+ X509_sign(pcert, pkey, NULL);
+ }
+ BN_free(b);
+ }
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s.crt", fname);
+ tmp[sizeof(tmp)-1] = '\0';
+ certfile = cpystr(tmp);
+ build_path(tmp, pathdir, certfile, sizeof(tmp));
+ if((fp = fopen(tmp, "w")) != NULL
+ &&(out = BIO_new_fp(fp, BIO_FP_TEXT)) != NULL){
+ EVP_PKEY *tpubkey = X509_REQ_get0_pubkey(req);
+ PEM_write_bio_X509(out, pcert);
+ BIO_flush(out);
+ BIO_free(out);
+ out = NULL;
+ }
+ if(req_conf)
+ NCONF_free(req_conf);
+ }
+ if(keyfile && certfile && pkey && pcert){
+ pc = (PERSONAL_CERT *) fs_get(sizeof(PERSONAL_CERT));
+ memset((void *)pc, 0, sizeof(PERSONAL_CERT));
+ pc->name = keyfile;
+ pc->key = pkey;
+ pc->cert = pcert;
+ pc->cname = certfile;
+ }
+ first_time = 0;
+ return pc;
+}
CertList *
smime_X509_to_cert_info(X509 *x, char *name)
diff --git a/pith/smkeys.h b/pith/smkeys.h
index 0d3570bc..d4e4c582 100644
--- a/pith/smkeys.h
+++ b/pith/smkeys.h
@@ -30,6 +30,8 @@
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/safestack.h>
+#include <openssl/conf.h>
+#include <openssl/x509v3.h>
#ifndef OPENSSL_1_1_0
#define X509_get0_notBefore(x) ((x) && (x)->cert_info \
@@ -38,11 +40,21 @@
#define X509_get0_notAfter(x) ((x) && (x)->cert_info \
? (x)->cert_info->validity->notAfter \
: NULL)
+#define X509_getm_notBefore(x) ((x) && (x)->cert_info \
+ ? (x)->cert_info->validity->notBefore \
+ : NULL)
+#define X509_getm_notAfter(x) ((x) && (x)->cert_info \
+ ? (x)->cert_info->validity->notAfter \
+ : NULL)
+#define X509_REQ_get0_pubkey(x) (X509_REQ_get_pubkey((x)))
+#else
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
#endif /* OPENSSL_1_1_0 */
#define EMAILADDRLEADER "emailAddress="
#define CACERTSTORELEADER "cacert="
-
+#define MASTERNAME "MasterPassword"
typedef struct personal_cert {
X509 *cert;
@@ -78,7 +90,7 @@ void resort_certificates(CertList **data, WhichCerts ctype);
int setup_certs_backup_by_type(WhichCerts ctype);
char *smime_get_cn(X509 *);
CertList *smime_X509_to_cert_info(X509 *, char *);
-
+PERSONAL_CERT *ALPINE_self_signed_certificate(char *, int, char *, char *);
#endif /* PITH_SMKEYS_INCLUDED */
#endif /* SMIME */