From: Kevin McCarthy Date: Tue, 9 Jul 2019 03:58:32 +0000 (-0700) Subject: Add initial autocrypt account setup. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f92049e9172755e0ee9369e16c885958dee89db9;p=mutt Add initial autocrypt account setup. Generate gpg key and add account record to the database. --- 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 + +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 + +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 9f4c7d2b..3b423c2a 100644 --- 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) {