From: Kevin McCarthy Date: Tue, 9 Jul 2019 03:58:32 +0000 (-0700) Subject: Add initial autocrypt account setup X-Git-Tag: 2019-10-25~97^2~47 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f2b41a4785f91996771a7690b8a84b64f9399e13;p=neomutt Add initial autocrypt account setup Generate gpg key and add account record to the database. Co-authored-by: Richard Russon --- diff --git a/autocrypt/autocrypt.c b/autocrypt/autocrypt.c index d9f3b99be..7591b7847 100644 --- a/autocrypt/autocrypt.c +++ b/autocrypt/autocrypt.c @@ -27,10 +27,13 @@ #include #include #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; +} diff --git a/autocrypt/autocrypt.h b/autocrypt/autocrypt.h index 8cb6d543b..33f7b9139 100644 --- a/autocrypt/autocrypt.h +++ b/autocrypt/autocrypt.h @@ -28,6 +28,15 @@ 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); diff --git a/autocrypt/autocrypt_db.c b/autocrypt/autocrypt_db.c index e0cc46ea0..c0aafa84a 100644 --- a/autocrypt/autocrypt_db.c +++ b/autocrypt/autocrypt_db.c @@ -25,6 +25,7 @@ #include #include #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; +} diff --git a/autocrypt/autocrypt_gpgme.c b/autocrypt/autocrypt_gpgme.c index e9381da94..0d2ac3598 100644 --- a/autocrypt/autocrypt_gpgme.c +++ b/autocrypt/autocrypt_gpgme.c @@ -21,13 +21,152 @@ */ #include "config.h" +#include #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(©); + + 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; +} diff --git a/autocrypt/autocrypt_private.h b/autocrypt/autocrypt_private.h index f43a996a5..bced9947c 100644 --- a/autocrypt/autocrypt_private.h +++ b/autocrypt/autocrypt_private.h @@ -23,12 +23,30 @@ #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 -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 9bf30e242..728a35f25 100644 --- 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) {