From e72d527d88ce3363e2c87f1afd079bfad648338d Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 12 Sep 2023 17:12:45 +0000 Subject: [PATCH] api: make it possible to import secret key from a file Previously it was required that a directory path is provided to the import API. Now it is possible to point directly to the .asc file containing a secret key. This allows UI to present a file selection dialog to the user and let select the file directly. Selecting a directory is still supported for backwards compatibility. --- deltachat-ffi/deltachat.h | 1 + src/imex.rs | 116 ++++++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index e4d268262..4ef7b5b22 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2284,6 +2284,7 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co * * - **DC_IMEX_IMPORT_SELF_KEYS** (2) - Import private keys found in the directory given as `param1`. * The last imported key is made the default keys unless its name contains the string `legacy`. Public keys are not imported. + * If `param1` is a filename, import the private key from the file and make it the default. * * While dc_imex() returns immediately, the started job may take a while, * you can stop it using dc_stop_ongoing_process(). During execution of the job, diff --git a/src/imex.rs b/src/imex.rs index 2a1382d45..8698a9f0f 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -586,63 +586,74 @@ async fn export_backup_inner( Ok(()) } -/******************************************************************************* - * Classic key import - ******************************************************************************/ -async fn import_self_keys(context: &Context, dir: &Path) -> Result<()> { - /* hint: even if we switch to import Autocrypt Setup Files, we should leave the possibility to import - plain ASC keys, at least keys without a password, if we do not want to implement a password entry function. - Importing ASC keys is useful to use keys in Delta Chat used by any other non-Autocrypt-PGP implementation. +/// Imports secret key from a file. +async fn import_secret_key(context: &Context, path: &Path, set_default: bool) -> Result<()> { + let buf = read_file(context, &path).await?; + let armored = std::string::String::from_utf8_lossy(&buf); + set_self_key(context, &armored, set_default, false).await?; + Ok(()) +} + +/// Imports secret keys from the provided file or directory. +/// +/// If provided path is a file, ASCII-armored secret key is read from the file +/// and set as the default key. +/// +/// If provided path is a directory, all files with .asc extension +/// containing secret keys are imported and the last successfully +/// imported which does not contain "legacy" in its filename +/// is set as the default. +async fn import_self_keys(context: &Context, path: &Path) -> Result<()> { + let attr = tokio::fs::metadata(path).await?; + + if attr.is_file() { + info!( + context, + "Importing secret key from {} as the default key.", + path.display() + ); + let set_default = true; + import_secret_key(context, path, set_default).await?; + return Ok(()); + } - Maybe we should make the "default" key handlong also a little bit smarter - (currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */ - let mut set_default: bool; let mut imported_cnt = 0; - let dir_name = dir.to_string_lossy(); - let mut dir_handle = tokio::fs::read_dir(&dir).await?; + let mut dir_handle = tokio::fs::read_dir(&path).await?; while let Ok(Some(entry)) = dir_handle.next_entry().await { let entry_fn = entry.file_name(); let name_f = entry_fn.to_string_lossy(); - let path_plus_name = dir.join(&entry_fn); - match get_filesuffix_lc(&name_f) { - Some(suffix) => { - if suffix != "asc" { - continue; - } - set_default = if name_f.contains("legacy") { - info!(context, "found legacy key '{}'", path_plus_name.display()); - false - } else { - true - } - } - None => { + let path_plus_name = path.join(&entry_fn); + if let Some(suffix) = get_filesuffix_lc(&name_f) { + if suffix != "asc" { continue; } - } + } else { + continue; + }; + let set_default = !name_f.contains("legacy"); info!( context, - "considering key file: {}", + "Considering key file: {}.", path_plus_name.display() ); - match read_file(context, &path_plus_name).await { - Ok(buf) => { - let armored = std::string::String::from_utf8_lossy(&buf); - if let Err(err) = set_self_key(context, &armored, set_default, false).await { - info!(context, "set_self_key: {}", err); - continue; - } - } - Err(_) => continue, + if let Err(err) = import_secret_key(context, &path_plus_name, set_default).await { + warn!( + context, + "Failed to import secret key from {}: {:#}.", + path_plus_name.display(), + err + ); + continue; } + imported_cnt += 1; } ensure!( imported_cnt > 0, - "No private keys found in \"{}\".", - dir_name + "No private keys found in {}.", + path.display() ); Ok(()) } @@ -865,14 +876,35 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_export_and_import_key() { + let export_dir = tempfile::tempdir().unwrap(); + let context = TestContext::new_alice().await; - let blobdir = context.ctx.get_blobdir(); - if let Err(err) = imex(&context.ctx, ImexMode::ExportSelfKeys, blobdir, None).await { + if let Err(err) = imex( + &context.ctx, + ImexMode::ExportSelfKeys, + export_dir.path(), + None, + ) + .await + { panic!("got error on export: {err:#}"); } let context2 = TestContext::new_alice().await; - if let Err(err) = imex(&context2.ctx, ImexMode::ImportSelfKeys, blobdir, None).await { + if let Err(err) = imex( + &context2.ctx, + ImexMode::ImportSelfKeys, + export_dir.path(), + None, + ) + .await + { + panic!("got error on import: {err:#}"); + } + + let keyfile = export_dir.path().join("private-key-default.asc"); + let context3 = TestContext::new_alice().await; + if let Err(err) = imex(&context3.ctx, ImexMode::ImportSelfKeys, &keyfile, None).await { panic!("got error on import: {err:#}"); } }