]> granicus.if.org Git - neomutt/commitdiff
Add initial autocrypt account setup
authorKevin McCarthy <kevin@8t8.us>
Tue, 9 Jul 2019 03:58:32 +0000 (20:58 -0700)
committerRichard Russon <rich@flatcap.org>
Mon, 19 Aug 2019 23:14:27 +0000 (00:14 +0100)
Generate gpg key and add account record to the database.

Co-authored-by: Richard Russon <rich@flatcap.org>
autocrypt/autocrypt.c
autocrypt/autocrypt.h
autocrypt/autocrypt_db.c
autocrypt/autocrypt_gpgme.c
autocrypt/autocrypt_private.h
main.c

index d9f3b99be621e181cc177ff475732fd332904f0a..7591b78470d91e8d6d154fcceb155e19a7bcba4f 100644 (file)
 #include <sys/types.h>
 #include <unistd.h>
 #include "autocrypt_private.h"
+#include "address/lib.h"
 #include "mutt.h"
 #include "autocrypt.h"
 #include "curs_lib.h"
 #include "globals.h"
+#include "mutt_curses.h"
+#include "send.h"
 
 static int autocrypt_dir_init(int can_create)
 {
@@ -88,3 +91,78 @@ 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)
+{
+  struct Address *addr = NULL;
+  struct AutocryptAccount *account = NULL;
+  bool done = false;
+  int rc = -1;
+
+  mutt_debug(LL_DEBUG1, "In mutt_autocrypt_account_init\n");
+  if (mutt_yesorno(_("Create an initial autocrypt account?"), MUTT_YES) != MUTT_YES)
+    return 0;
+
+  struct Buffer *keyid = mutt_buffer_pool_get();
+  struct Buffer *keydata = mutt_buffer_pool_get();
+
+  if (C_From)
+  {
+    addr = mutt_addr_copy(C_From);
+    if (!addr->personal && C_Realname)
+      addr->personal = mutt_str_strdup(C_Realname);
+  }
+
+  struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
+  mutt_addrlist_append(&al, addr);
+
+  do
+  {
+    if (mutt_edit_address(&al, _("Autocrypt account address: "), 0) != 0)
+      goto cleanup;
+
+    addr = TAILQ_FIRST(&al);
+    if (!addr || !addr->mailbox || TAILQ_NEXT(addr, entries))
+    {
+      /* L10N:
+         Autocrypt prompts for an account email address, and requires
+         a single address.
+      */
+      mutt_error(_("Please enter a single email address"));
+      done = false;
+    }
+    else
+      done = true;
+  } while (!done);
+
+  addr = TAILQ_FIRST(&al);
+  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;
+
+  rc = 0;
+
+cleanup:
+  if (rc == 0)
+    mutt_message(_("Autocrypt account creation succeeded"));
+  else
+    mutt_error(_("Autocrypt account creation aborted"));
+
+  mutt_autocrypt_db_account_free(&account);
+  mutt_addrlist_clear(&al);
+  mutt_buffer_pool_release(&keyid);
+  mutt_buffer_pool_release(&keydata);
+  return rc;
+}
index 8cb6d543bd2f390157a3d5716724edd17e7c0d77..33f7b913973f2451fa7375b3673e0c52c0edb6a9 100644 (file)
 
 WHERE sqlite3 *AutocryptDB;
 
+struct AutocryptAccount
+{
+  char *email_addr;
+  char *keyid;
+  char *keydata;
+  int prefer_encrypt;    /* 0 = nopref, 1 = mutual */
+  int enabled;
+};
+
 int mutt_autocrypt_init (int);
 void mutt_autocrypt_cleanup (void);
 
index e0cc46ea08b642dafd8f12a56e73ab70eb01ffcc..c0aafa84a69eca9d0d32434d595cfa899dd04136 100644 (file)
@@ -25,6 +25,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include "autocrypt_private.h"
+#include "address/lib.h"
 #include "mutt.h"
 #include "autocrypt.h"
 #include "globals.h"
@@ -54,10 +55,15 @@ int mutt_autocrypt_db_init(int can_create)
 
   db_path = mutt_buffer_pool_get();
   mutt_buffer_concat_path(db_path, C_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
   {
@@ -66,10 +72,10 @@ int mutt_autocrypt_db_init(int can_create)
       mutt_error(_("Unable to open autocrypt database %s"), mutt_b2s(db_path));
       goto cleanup;
     }
-  }
 
-  if (mutt_autocrypt_schema_update())
-    goto cleanup;
+    if (mutt_autocrypt_schema_update())
+      goto cleanup;
+  }
 
   rv = 0;
 
@@ -83,10 +89,150 @@ 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(struct Address *addr)
+{
+  struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
+  struct Address *norm_addr = mutt_addr_copy(addr);
+  mutt_addrlist_append(&al, norm_addr);
+
+  mutt_addrlist_to_local(&al);
+  mutt_str_strlower(norm_addr->mailbox);
+  mutt_addrlist_to_intl(&al, NULL);
+
+  char *email = mutt_str_strdup(TAILQ_FIRST(&al)->mailbox);
+  mutt_addrlist_clear(&al);
+
+  return email;
+}
+
+/* Helper that converts to char * and mutt_str_strdups the result */
+static char *strdup_column_text(sqlite3_stmt *stmt, int index)
+{
+  const char *val = (const char *) sqlite3_column_text(stmt, index);
+  return mutt_str_strdup(val);
+}
+
+struct AutocryptAccount *mutt_autocrypt_db_account_new(void)
+{
+  return mutt_mem_calloc(1, sizeof(struct AutocryptAccount));
+}
+
+void mutt_autocrypt_db_account_free(struct AutocryptAccount **account)
+{
+  if (!account || !*account)
+    return;
+  FREE(&(*account)->email_addr);
+  FREE(&(*account)->keyid);
+  FREE(&(*account)->keydata);
+  FREE(account);
+}
+
+int mutt_autocrypt_db_account_get(struct Address *addr, struct AutocryptAccount **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(struct 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;
+}
index e9381da94f019ad8d11807faf220628dd461d01d..0d2ac35982b34ee2add06e94371e2c610cbdaae5 100644 (file)
  */
 
 #include "config.h"
+#include <gpgme.h>
 #include "autocrypt_private.h"
+#include "address/lib.h"
 #include "mutt.h"
 #include "autocrypt.h"
+#include "globals.h"
 #include "ncrypt/crypt_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, C_AutocryptDir);
+  if (err)
+  {
+    mutt_error(_("error creating gpgme context: %s\n"), gpgme_strerror(err));
+    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, struct Buffer *keydata)
+{
+  int rv = -1;
+  gpgme_data_t dh = NULL;
+  gpgme_key_t export_keys[2];
+  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;
+
+  char *export_data = gpgme_data_release_and_get_mem(dh, &export_data_len);
+  dh = NULL;
+
+  mutt_b64_buffer_encode(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, struct 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(struct Address *addr, struct Buffer *keyid,
+                                    struct 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[1024] = { 0 };
+
+  /* gpgme says addresses should not be in idna form */
+  struct Address *copy = mutt_addr_copy(addr);
+  mutt_addr_to_local(copy);
+  mutt_addr_write(buf, sizeof(buf), copy, false);
+  mutt_addr_free(&copy);
+
+  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));
+    goto cleanup;
+  }
+  keyresult = gpgme_op_genkey_result(ctx);
+  if (!keyresult->fpr)
+    goto cleanup;
+  mutt_buffer_strcpy(keyid, keyresult->fpr);
+  mutt_debug(LL_DEBUG1, "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));
+    goto cleanup;
+  }
+
+  /* get keydata */
+  if (export_keydata(ctx, primary_key, keydata))
+    goto cleanup;
+  mutt_debug(LL_DEBUG1, "key has keydata *%s*\n", mutt_b2s(keydata));
+
+  rv = 0;
+
+cleanup:
+  gpgme_key_unref(primary_key);
+  gpgme_release(ctx);
+  return rv;
+}
index f43a996a584aa4cf4fe99a38555615b4e53be0df..bced9947c04d3fac8a35ed2cb897384878e59429 100644 (file)
 #ifndef MUTT_AUTOCRYPT_AUTOCRYPT_PRIVATE_H
 #define MUTT_AUTOCRYPT_AUTOCRYPT_PRIVATE_H
 
-int mutt_autocrypt_db_init(int can_create);
-void mutt_autocrypt_db_close(void);
+#include <sqlite3.h>
 
-int mutt_autocrypt_schema_init(void);
-int mutt_autocrypt_schema_update(void);
+struct Address;
+struct Buffer;
 
-int mutt_autocrypt_gpgme_init(void);
+int mutt_autocrypt_account_init (void);
+
+int mutt_autocrypt_db_init (int can_create);
+void mutt_autocrypt_db_close (void);
+
+struct AutocryptAccount *mutt_autocrypt_db_account_new (void);
+void mutt_autocrypt_db_account_free (struct AutocryptAccount **account);
+int mutt_autocrypt_db_account_get (struct Address *addr, struct AutocryptAccount **account);
+int mutt_autocrypt_db_account_insert (struct 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 (struct Address *addr, struct Buffer *keyid, struct Buffer *keydata);
+
+/* Prepared statements */
+sqlite3_stmt *AccountGetStmt;
+sqlite3_stmt *AccountInsertStmt;
 
 #endif /* MUTT_AUTOCRYPT_AUTOCRYPT_PRIVATE_H */
diff --git a/main.c b/main.c
index 9bf30e242ec06e5c4405373e3eef46e70b06c7ef..728a35f25571f60d71c21a3293093c7076830e4f 100644 (file)
--- a/main.c
+++ b/main.c
@@ -736,10 +736,6 @@ int main(int argc, char *argv[], char *envp[])
 
   /* Initialize crypto backends.  */
   crypt_init();
-#ifdef USE_AUTOCRYPT
-  if (C_Autocrypt)
-    mutt_autocrypt_init(!(sendflags & SEND_BATCH));
-#endif
 
   if (new_magic)
   {
@@ -801,6 +797,13 @@ int main(int argc, char *argv[], char *envp[])
     log_queue_set_max_size(100);
   }
 
+  /* Initialize autocrypt after curses messages are working,
+   * because of the initial account setup screens. */
+#ifdef USE_AUTOCRYPT
+  if (C_Autocrypt)
+    mutt_autocrypt_init(!(sendflags & SEND_BATCH));
+#endif
+
   /* Create the C_Folder directory if it doesn't exist. */
   if (!OptNoCurses && C_Folder)
   {