summaryrefslogtreecommitdiffstats
path: root/autocrypt
diff options
context:
space:
mode:
authorKevin McCarthy <kevin@8t8.us>2019-07-08 20:58:32 -0700
committerKevin McCarthy <kevin@8t8.us>2019-08-03 14:08:09 -0700
commitf92049e9172755e0ee9369e16c885958dee89db9 (patch)
treeae70beaebb6c1b1626b958972f4d98f4b3d5f62b /autocrypt
parent4586444c93cc3634d2ab1f960c780c5a6dd2f42a (diff)
Add initial autocrypt account setup.
Generate gpg key and add account record to the database.
Diffstat (limited to 'autocrypt')
-rw-r--r--autocrypt/autocrypt.c73
-rw-r--r--autocrypt/autocrypt.h9
-rw-r--r--autocrypt/autocrypt_db.c185
-rw-r--r--autocrypt/autocrypt_gpgme.c146
-rw-r--r--autocrypt/autocrypt_private.h15
5 files changed, 421 insertions, 7 deletions
diff --git a/autocrypt/autocrypt.c b/autocrypt/autocrypt.c
index 1b7c06dc..c9269bcf 100644
--- a/autocrypt/autocrypt.c
+++ b/autocrypt/autocrypt.c
@@ -21,6 +21,7 @@
#endif
#include "mutt.h"
+#include "mutt_curses.h"
#include "autocrypt.h"
#include "autocrypt_private.h"
@@ -83,3 +84,75 @@ void mutt_autocrypt_cleanup (void)
{
mutt_autocrypt_db_close ();
}
+
+/* Creates a brand new account the first time autocrypt is initialized */
+int mutt_autocrypt_account_init (void)
+{
+ ADDRESS *addr = NULL;
+ BUFFER *keyid = NULL, *keydata = NULL;
+ AUTOCRYPT_ACCOUNT *account = NULL;
+ int done = 0, rv = -1;
+
+ dprint (1, (debugfile, "In mutt_autocrypt_account_init\n"));
+ if (mutt_yesorno (_("Create an initial autocrypt account?"),
+ MUTT_YES) != MUTT_YES)
+ return 0;
+
+ keyid = mutt_buffer_pool_get ();
+ keydata = mutt_buffer_pool_get ();
+
+ if (From)
+ {
+ addr = rfc822_cpy_adr_real (From);
+ if (!addr->personal && Realname)
+ addr->personal = safe_strdup (Realname);
+ }
+
+ do
+ {
+ if (mutt_edit_address (&addr, _("Autocrypt account address: "), 0))
+ goto cleanup;
+ if (!addr || !addr->mailbox || addr->next)
+ {
+ /* L10N:
+ Autocrypt prompts for an account email address, and requires
+ a single address.
+ */
+ mutt_error (_("Please enter a single email address"));
+ mutt_sleep (2);
+ done = 0;
+ }
+ else
+ done = 1;
+ } while (!done);
+
+ if (mutt_autocrypt_db_account_get (addr, &account) < 0)
+ goto cleanup;
+ if (account)
+ {
+ mutt_error _("That email address already has an autocrypt account");
+ goto cleanup;
+ }
+
+ if (mutt_autocrypt_gpgme_create_key (addr, keyid, keydata))
+ goto cleanup;
+
+ /* TODO: prompt for prefer_encrypt value? */
+ if (mutt_autocrypt_db_account_insert (addr, mutt_b2s (keyid), mutt_b2s (keydata), 0))
+ goto cleanup;
+
+ rv = 0;
+
+cleanup:
+ if (rv)
+ mutt_error _("Autocrypt account creation aborted.");
+ else
+ mutt_message _("Autocrypt account creation succeeded");
+ mutt_sleep (1);
+
+ mutt_autocrypt_db_account_free (&account);
+ rfc822_free_address (&addr);
+ mutt_buffer_pool_release (&keyid);
+ mutt_buffer_pool_release (&keydata);
+ return rv;
+}
diff --git a/autocrypt/autocrypt.h b/autocrypt/autocrypt.h
index 2429539e..c690fae2 100644
--- a/autocrypt/autocrypt.h
+++ b/autocrypt/autocrypt.h
@@ -23,6 +23,15 @@
WHERE sqlite3 *AutocryptDB;
+typedef struct
+{
+ char *email_addr;
+ char *keyid;
+ char *keydata;
+ int prefer_encrypt; /* 0 = nopref, 1 = mutual */
+ int enabled;
+} AUTOCRYPT_ACCOUNT;
+
int mutt_autocrypt_init (int);
void mutt_autocrypt_cleanup (void);
diff --git a/autocrypt/autocrypt_db.c b/autocrypt/autocrypt_db.c
index e7baa603..a053c433 100644
--- a/autocrypt/autocrypt_db.c
+++ b/autocrypt/autocrypt_db.c
@@ -21,6 +21,7 @@
#endif
#include "mutt.h"
+#include "mutt_idna.h"
#include "autocrypt.h"
#include "autocrypt_private.h"
@@ -52,10 +53,15 @@ int mutt_autocrypt_db_init (int can_create)
db_path = mutt_buffer_pool_get ();
mutt_buffer_concat_path (db_path, AutocryptDir, "autocrypt.db");
+
if (stat (mutt_b2s (db_path), &sb))
{
- if (!can_create || autocrypt_db_create (mutt_b2s (db_path)))
+ if (!can_create)
+ goto cleanup;
+ if (autocrypt_db_create (mutt_b2s (db_path)))
goto cleanup;
+ /* Don't abort the whole init process because account creation failed */
+ mutt_autocrypt_account_init ();
}
else
{
@@ -68,10 +74,10 @@ int mutt_autocrypt_db_init (int can_create)
mutt_sleep (0);
goto cleanup;
}
- }
- if (mutt_autocrypt_schema_update ())
- goto cleanup;
+ if (mutt_autocrypt_schema_update ())
+ goto cleanup;
+ }
rv = 0;
@@ -85,10 +91,175 @@ void mutt_autocrypt_db_close (void)
if (!AutocryptDB)
return;
- /* TODO:
- * call sqlite3_finalize () for each prepared statement
- */
+ sqlite3_finalize (AccountGetStmt);
+ AccountGetStmt = NULL;
+ sqlite3_finalize (AccountInsertStmt);
+ AccountInsertStmt = NULL;
sqlite3_close_v2 (AutocryptDB);
AutocryptDB = NULL;
}
+
+/* The autocrypt spec says email addresses should be
+ * normalized to lower case and stored in idna form.
+ *
+ * In order to avoid visible changes to addresses in the index,
+ * we make a copy of the address before lowercasing it.
+ *
+ * The return value must be freed.
+ */
+static char *normalize_email_addr (ADDRESS *addr)
+{
+ ADDRESS norm_addr = {0};
+
+ norm_addr.mailbox = safe_strdup (addr->mailbox);
+ norm_addr.is_intl = addr->is_intl;
+ norm_addr.intl_checked = addr->intl_checked;
+
+ mutt_addrlist_to_local (&norm_addr);
+ ascii_strlower (norm_addr.mailbox);
+ mutt_addrlist_to_intl (&norm_addr, NULL);
+
+ return norm_addr.mailbox;
+}
+
+/* Helper that converts to char * and safe_strdups the result */
+static char *strdup_column_text (sqlite3_stmt *stmt, int index)
+{
+ const char *val = (const char *)sqlite3_column_text (stmt, index);
+ return safe_strdup (val);
+}
+
+AUTOCRYPT_ACCOUNT *mutt_autocrypt_db_account_new (void)
+{
+ return safe_calloc (1, sizeof(AUTOCRYPT_ACCOUNT));
+}
+
+void mutt_autocrypt_db_account_free (AUTOCRYPT_ACCOUNT **account)
+{
+ if (!account || !*account)
+ return;
+ FREE (&(*account)->email_addr);
+ FREE (&(*account)->keyid);
+ FREE (&(*account)->keydata);
+ FREE (account); /* __FREE_CHECKED__ */
+}
+
+int mutt_autocrypt_db_account_get (ADDRESS *addr, AUTOCRYPT_ACCOUNT **account)
+{
+ int rv = -1, result;
+ char *email = NULL;
+
+ email = normalize_email_addr (addr);
+ *account = NULL;
+
+ if (!AccountGetStmt)
+ {
+ if (sqlite3_prepare_v2 (
+ AutocryptDB,
+ "SELECT "
+ "email_addr, "
+ "keyid, "
+ "keydata, "
+ "prefer_encrypt, "
+ "enabled "
+ "FROM account "
+ "WHERE email_addr = ?",
+ -1,
+ &AccountGetStmt,
+ NULL) != SQLITE_OK)
+ goto cleanup;
+ }
+
+ if (sqlite3_bind_text (AccountGetStmt,
+ 1,
+ email,
+ -1,
+ SQLITE_STATIC) != SQLITE_OK)
+ goto cleanup;
+
+ result = sqlite3_step (AccountGetStmt);
+ if (result != SQLITE_ROW)
+ {
+ if (result == SQLITE_DONE)
+ rv = 0;
+ goto cleanup;
+ }
+
+ *account = mutt_autocrypt_db_account_new ();
+ (*account)->email_addr = strdup_column_text (AccountGetStmt, 0);
+ (*account)->keyid = strdup_column_text (AccountGetStmt, 1);
+ (*account)->keydata = strdup_column_text (AccountGetStmt, 2);
+ (*account)->prefer_encrypt = sqlite3_column_int (AccountGetStmt, 3);
+ (*account)->enabled = sqlite3_column_int (AccountGetStmt, 4);
+
+ rv = 1;
+
+cleanup:
+ FREE (&email);
+ sqlite3_reset (AccountGetStmt);
+ return rv;
+}
+
+int mutt_autocrypt_db_account_insert (ADDRESS *addr, const char *keyid,
+ const char *keydata, int prefer_encrypt)
+{
+ int rv = -1;
+ char *email = NULL;
+
+ email = normalize_email_addr (addr);
+
+ if (!AccountInsertStmt)
+ {
+ if (sqlite3_prepare_v2 (
+ AutocryptDB,
+ "INSERT INTO account "
+ "(email_addr, "
+ "keyid, "
+ "keydata, "
+ "prefer_encrypt, "
+ "enabled) "
+ "VALUES (?, ?, ?, ?, ?);",
+ -1,
+ &AccountInsertStmt,
+ NULL) != SQLITE_OK)
+ goto cleanup;
+ }
+
+ if (sqlite3_bind_text (AccountInsertStmt,
+ 1,
+ email,
+ -1,
+ SQLITE_STATIC) != SQLITE_OK)
+ goto cleanup;
+ if (sqlite3_bind_text (AccountInsertStmt,
+ 2,
+ keyid,
+ -1,
+ SQLITE_STATIC) != SQLITE_OK)
+ goto cleanup;
+ if (sqlite3_bind_text (AccountInsertStmt,
+ 3,
+ keydata,
+ -1,
+ SQLITE_STATIC) != SQLITE_OK)
+ goto cleanup;
+ if (sqlite3_bind_int (AccountInsertStmt,
+ 4,
+ prefer_encrypt) != SQLITE_OK)
+ goto cleanup;
+ if (sqlite3_bind_int (AccountInsertStmt,
+ 5,
+ 1) != SQLITE_OK)
+ goto cleanup;
+
+ if (sqlite3_step (AccountInsertStmt) != SQLITE_DONE)
+ goto cleanup;
+
+ rv = 0;
+
+cleanup:
+ FREE (&email);
+ sqlite3_reset (AccountInsertStmt);
+ return rv;
+}
diff --git a/autocrypt/autocrypt_gpgme.c b/autocrypt/autocrypt_gpgme.c
index b12fdc58..f37fca7f 100644
--- a/autocrypt/autocrypt_gpgme.c
+++ b/autocrypt/autocrypt_gpgme.c
@@ -22,11 +22,157 @@
#include "mutt.h"
#include "crypt-gpgme.h"
+#include "mutt_idna.h"
#include "autocrypt.h"
#include "autocrypt_private.h"
+#include <gpgme.h>
+
+static int create_gpgme_context (gpgme_ctx_t *ctx)
+{
+ gpgme_error_t err;
+
+ err = gpgme_new (ctx);
+ if (!err)
+ err = gpgme_ctx_set_engine_info (*ctx, GPGME_PROTOCOL_OpenPGP, NULL,
+ AutocryptDir);
+ if (err)
+ {
+ mutt_error (_("error creating gpgme context: %s\n"), gpgme_strerror (err));
+ sleep (2);
+ return -1;
+ }
+
+ return 0;
+}
+
int mutt_autocrypt_gpgme_init (void)
{
pgp_gpgme_init ();
return 0;
}
+
+static int export_keydata (gpgme_ctx_t ctx, gpgme_key_t key, BUFFER *keydata)
+{
+ int rv = -1;
+ gpgme_data_t dh = NULL;
+ gpgme_key_t export_keys[2];
+ unsigned char *export_data = NULL;
+ size_t export_data_len;
+
+ if (gpgme_data_new (&dh))
+ goto cleanup;
+
+ /* This doesn't seem to work */
+#if 0
+ if (gpgme_data_set_encoding (dh, GPGME_DATA_ENCODING_BASE64))
+ goto cleanup;
+#endif
+
+ export_keys[0] = key;
+ export_keys[1] = NULL;
+ if (gpgme_op_export_keys (ctx, export_keys,
+ GPGME_EXPORT_MODE_MINIMAL,
+ dh))
+ goto cleanup;
+
+ export_data = (unsigned char *)gpgme_data_release_and_get_mem (dh, &export_data_len);
+ dh = NULL;
+
+ mutt_buffer_to_base64 (keydata, export_data, export_data_len);
+ gpgme_free (export_data);
+ export_data = NULL;
+
+ rv = 0;
+
+cleanup:
+ gpgme_data_release (dh);
+ return rv;
+}
+
+/* TODO: not sure if this function will be useful in the future. */
+int mutt_autocrypt_gpgme_export_key (const char *keyid, BUFFER *keydata)
+{
+ int rv = -1;
+ gpgme_ctx_t ctx = NULL;
+ gpgme_key_t key = NULL;
+
+ if (create_gpgme_context (&ctx))
+ goto cleanup;
+
+ if (gpgme_get_key (ctx, keyid, &key, 0))
+ goto cleanup;
+
+ if (export_keydata (ctx, key, keydata))
+ goto cleanup;
+
+ rv = 0;
+cleanup:
+ gpgme_key_unref (key);
+ gpgme_release (ctx);
+ return rv;
+}
+
+int mutt_autocrypt_gpgme_create_key (ADDRESS *addr, BUFFER *keyid, BUFFER *keydata)
+{
+ int rv = -1;
+ gpgme_ctx_t ctx = NULL;
+ gpgme_error_t err;
+ gpgme_genkey_result_t keyresult;
+ gpgme_key_t primary_key = NULL;
+ char buf[LONG_STRING] = {0};
+
+ /* gpgme says addresses should not be in idna form */
+ mutt_addrlist_to_local (addr);
+ rfc822_write_address (buf, sizeof(buf), addr, 0);
+
+ if (create_gpgme_context (&ctx))
+ goto cleanup;
+
+ mutt_message _("Generating autocrypt key...");
+
+ /* Primary key */
+ err = gpgme_op_createkey (ctx, buf, "ed25519",
+ 0, 0, NULL,
+ GPGME_CREATE_NOPASSWD | GPGME_CREATE_FORCE | GPGME_CREATE_NOEXPIRE);
+ if (err)
+ {
+ mutt_error (_("Error creating autocrypt key: %s\n"), gpgme_strerror (err));
+ sleep (2);
+ goto cleanup;
+ }
+ keyresult = gpgme_op_genkey_result (ctx);
+ if (!keyresult->fpr)
+ goto cleanup;
+ mutt_buffer_strcpy (keyid, keyresult->fpr);
+ dprint (1, (debugfile, "Generated key with id %s\n", mutt_b2s (keyid)));
+
+ /* Get gpgme_key_t to create the secondary key and export keydata */
+ err = gpgme_get_key (ctx, mutt_b2s (keyid), &primary_key, 0);
+ if (err)
+ goto cleanup;
+
+ /* Secondary key */
+ err = gpgme_op_createsubkey (ctx, primary_key, "cv25519",
+ 0, 0,
+ GPGME_CREATE_NOPASSWD | GPGME_CREATE_NOEXPIRE);
+ if (err)
+ {
+ mutt_error (_("Error creating autocrypt key: %s\n"), gpgme_strerror (err));
+ sleep (2);
+ goto cleanup;
+ }
+
+ /* get keydata */
+ if (export_keydata (ctx, primary_key, keydata))
+ goto cleanup;
+ dprint (1, (debugfile, "key has keydata *%s*\n", mutt_b2s (keydata)));
+
+ rv = 0;
+
+cleanup:
+ mutt_addrlist_to_intl (addr, NULL);
+ gpgme_key_unref (primary_key);
+ gpgme_release (ctx);
+ return rv;
+}
diff --git a/autocrypt/autocrypt_private.h b/autocrypt/autocrypt_private.h
index 923d11e4..8bb757a3 100644
--- a/autocrypt/autocrypt_private.h
+++ b/autocrypt/autocrypt_private.h
@@ -19,12 +19,27 @@
#ifndef _AUTOCRYPT_PRIVATE_H
#define _AUTOCRYPT_PRIVATE_H 1
+#include <sqlite3.h>
+
+int mutt_autocrypt_account_init (void);
+
int mutt_autocrypt_db_init (int can_create);
void mutt_autocrypt_db_close (void);
+AUTOCRYPT_ACCOUNT *mutt_autocrypt_db_account_new (void);
+void mutt_autocrypt_db_account_free (AUTOCRYPT_ACCOUNT **account);
+int mutt_autocrypt_db_account_get (ADDRESS *addr, AUTOCRYPT_ACCOUNT **account);
+int mutt_autocrypt_db_account_insert (ADDRESS *addr, const char *keyid,
+ const char *keydata, int prefer_encrypt);
+
int mutt_autocrypt_schema_init (void);
int mutt_autocrypt_schema_update (void);
int mutt_autocrypt_gpgme_init (void);
+int mutt_autocrypt_gpgme_create_key (ADDRESS *addr, BUFFER *keyid, BUFFER *keydata);
+
+/* Prepared statements */
+sqlite3_stmt *AccountGetStmt;
+sqlite3_stmt *AccountInsertStmt;
#endif