diff --git a/deltachat-rpc-client/tests/test_securejoin.py b/deltachat-rpc-client/tests/test_securejoin.py index 2263cf558..782a35b34 100644 --- a/deltachat-rpc-client/tests/test_securejoin.py +++ b/deltachat-rpc-client/tests/test_securejoin.py @@ -30,7 +30,7 @@ def test_qr_setup_contact(acfactory, tmp_path) -> None: bob2.export_self_keys(tmp_path) logging.info("Bob imports a key") - bob.import_self_keys(tmp_path / "private-key-default.asc") + bob.import_self_keys(tmp_path) assert bob.get_config("key_id") == "2" bob_contact_alice_snapshot = bob_contact_alice.get_snapshot() diff --git a/src/imex.rs b/src/imex.rs index 0abd979ac..b8ec8a996 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -46,12 +46,13 @@ pub(crate) const BLOBS_BACKUP_NAME: &str = "blobs_backup"; #[repr(u32)] pub enum ImexMode { /// Export all private keys and all public keys of the user to the - /// directory given as `path`. The default key is written to the files `public-key-default.asc` - /// and `private-key-default.asc`, if there are more keys, they are written to files as - /// `public-key-.asc` and `private-key-.asc` + /// directory given as `path`. The default key is written to the files + /// `{public,private}-key--default-.asc`, if there are more keys, they are + /// written to files as `{public,private}-key---.asc`. ExportSelfKeys = 1, - /// Import private keys found in the directory given as `path`. + /// Import private keys found in `path` if it is a directory, otherwise import a private key + /// from `path`. /// The last imported key is made the default keys unless its name contains the string `legacy`. /// Public keys are not imported. ImportSelfKeys = 2, @@ -694,12 +695,12 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> { }, ) .await?; - + let self_addr = context.get_primary_self_addr().await?; for (id, public_key, private_key, is_default) in keys { let id = Some(id).filter(|_| is_default == 0); if let Ok(key) = public_key { - if let Err(err) = export_key_to_asc_file(context, dir, id, &key).await { + if let Err(err) = export_key_to_asc_file(context, dir, &self_addr, id, &key).await { error!(context, "Failed to export public key: {:#}.", err); export_errors += 1; } @@ -707,7 +708,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> { export_errors += 1; } if let Ok(key) = private_key { - if let Err(err) = export_key_to_asc_file(context, dir, id, &key).await { + if let Err(err) = export_key_to_asc_file(context, dir, &self_addr, id, &key).await { error!(context, "Failed to export private key: {:#}.", err); export_errors += 1; } @@ -720,15 +721,14 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> { Ok(()) } -/******************************************************************************* - * Classic key export - ******************************************************************************/ +/// Returns the exported key file name inside `dir`. async fn export_key_to_asc_file( context: &Context, dir: &Path, + addr: &str, id: Option, key: &T, -) -> Result<()> +) -> Result where T: DcKey, { @@ -738,24 +738,26 @@ where true => "private", }; let id = id.map_or("default".into(), |i| i.to_string()); - dir.join(format!("{}-key-{}.asc", kind, &id)) + let fp = DcKey::fingerprint(key).hex(); + format!("{kind}-key-{addr}-{id}-{fp}.asc") }; + let path = dir.join(&file_name); info!( context, - "Exporting key {:?} to {}", + "Exporting key {:?} to {}.", key.key_id(), - file_name.display() + path.display() ); // Delete the file if it already exists. - delete_file(context, &file_name).await.ok(); + delete_file(context, &path).await.ok(); let content = key.to_asc(None).into_bytes(); - write_file(context, &file_name, &content) + write_file(context, &path, &content) .await - .with_context(|| format!("cannot write key to {}", file_name.display()))?; - context.emit_event(EventType::ImexFileWritten(file_name)); - Ok(()) + .with_context(|| format!("cannot write key to {}", path.display()))?; + context.emit_event(EventType::ImexFileWritten(path)); + Ok(file_name) } /// Exports the database to *dest*, encrypted using *passphrase*. @@ -873,33 +875,46 @@ mod tests { let context = TestContext::new().await; let key = alice_keypair().public; let blobdir = Path::new("$BLOBDIR"); - assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key) + let filename = export_key_to_asc_file(&context.ctx, blobdir, "a@b", None, &key) .await - .is_ok()); + .unwrap(); + assert!(filename.starts_with("public-key-a@b-default-")); + assert!(filename.ends_with(".asc")); let blobdir = context.ctx.get_blobdir().to_str().unwrap(); - let filename = format!("{blobdir}/public-key-default.asc"); + let filename = format!("{blobdir}/{filename}"); let bytes = tokio::fs::read(&filename).await.unwrap(); assert_eq!(bytes, key.to_asc(None).into_bytes()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_export_private_key_to_asc_file() { + async fn test_import_private_key_exported_to_asc_file() { let context = TestContext::new().await; let key = alice_keypair().secret; let blobdir = Path::new("$BLOBDIR"); - assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key) + let filename = export_key_to_asc_file(&context.ctx, blobdir, "a@b", None, &key) .await - .is_ok()); + .unwrap(); + let fingerprint = filename + .strip_prefix("private-key-a@b-default-") + .unwrap() + .strip_suffix(".asc") + .unwrap(); + assert_eq!(fingerprint, DcKey::fingerprint(&key).hex()); let blobdir = context.ctx.get_blobdir().to_str().unwrap(); - let filename = format!("{blobdir}/private-key-default.asc"); + let filename = format!("{blobdir}/{filename}"); let bytes = tokio::fs::read(&filename).await.unwrap(); assert_eq!(bytes, key.to_asc(None).into_bytes()); + + let alice = &TestContext::new_alice().await; + if let Err(err) = imex(alice, ImexMode::ImportSelfKeys, Path::new(&filename), None).await { + panic!("got error on import: {err:#}"); + } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_export_and_import_key() { + async fn test_export_and_import_key_from_dir() { let export_dir = tempfile::tempdir().unwrap(); let context = TestContext::new_alice().await; @@ -925,12 +940,6 @@ mod tests { { 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:#}"); - } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)]