]> granicus.if.org Git - mutt/commitdiff
Add initial autocrypt account setup.
authorKevin McCarthy <kevin@8t8.us>
Tue, 9 Jul 2019 03:58:32 +0000 (20:58 -0700)
committerKevin McCarthy <kevin@8t8.us>
Sat, 3 Aug 2019 21:08:09 +0000 (14:08 -0700)
Generate gpg key and add account record to the database.

autocrypt/autocrypt.c
autocrypt/autocrypt.h
autocrypt/autocrypt_db.c
autocrypt/autocrypt_gpgme.c
autocrypt/autocrypt_private.h
main.c

index 1b7c06dc373f642ef5b4b39249fb6f359539ea24..c9269bcf4e6fc99b432b123aaa35ac12c47aa39a 100644 (file)
@@ -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;
+}
index 2429539efd352eb16ce1764e86688f9e715becfe..c690fae256552b68c022bce33ce1d08f015ff920 100644 (file)
 
 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);
 
index e7baa603f89d4acde755b6782250169f15be9333..a053c433c83d72746a7ece2c35d5084b7441de8a 100644 (file)
@@ -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;
+}
index b12fdc58d873816b4710404646f7e660e70e89f0..f37fca7faf415c2590ac0d5d687b067216b0add7 100644 (file)
 
 #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;
+}
index 923d11e45753d868d8063e4aa2ba88a2772173b3..8bb757a3a0972855e50881f1c79451283ddc25a3 100644 (file)
 #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
diff --git a/main.c b/main.c
index 9f4c7d2b5f32a922c9d3bb0755f6fc35ff674778..3b423c2a0cd03fa89024944be6b3d0de4eff9c41 100644 (file)
--- a/main.c
+++ b/main.c
@@ -867,10 +867,6 @@ int main (int argc, char **argv, char **environ)
 
   /* Initialize crypto backends.  */
   crypt_init ();
-#ifdef USE_AUTOCRYPT
-  if (option (OPTAUTOCRYPT))
-    mutt_autocrypt_init (!(sendflags & SENDBATCH));
-#endif
 
   if (newMagic)
     mx_set_magic (newMagic);
@@ -922,6 +918,13 @@ int main (int argc, char **argv, char **environ)
     mutt_message = mutt_curses_message;
   }
 
+  /* Initialize autocrypt after curses messages are working,
+   * because of the initial account setup screens. */
+#ifdef USE_AUTOCRYPT
+  if (option (OPTAUTOCRYPT))
+    mutt_autocrypt_init (!(sendflags & SENDBATCH));
+#endif
+
   /* Create the Maildir directory if it doesn't exist. */
   if (!option (OPTNOCURSES) && Maildir)
   {