diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index fe6ce5e88..9b9e232ee 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -1404,11 +1404,14 @@ impl CryptoStore for SqliteCryptoStore { mod tests { use std::path::Path; + use matrix_sdk_common::deserialized_responses::WithheldCode; use matrix_sdk_crypto::{ - cryptostore_integration_tests, cryptostore_integration_tests_time, store::CryptoStore, + cryptostore_integration_tests, cryptostore_integration_tests_time, olm::SenderDataType, + store::CryptoStore, }; use matrix_sdk_test::async_test; use once_cell::sync::Lazy; + use ruma::{device_id, room_id, user_id}; use similar_asserts::assert_eq; use tempfile::{tempdir, TempDir}; use tokio::fs; @@ -1424,11 +1427,11 @@ mod tests { database: SqliteCryptoStore, } - async fn get_test_db() -> TestDb { + async fn get_test_db(data_path: &str, passphrase: Option<&str>) -> TestDb { let db_name = "matrix-sdk-crypto.sqlite3"; let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); - let database_path = manifest_path.join("testing/data/storage").join(db_name); + let database_path = manifest_path.join(data_path).join(db_name); let tmpdir = tempdir().unwrap(); let destination = tmpdir.path().join(db_name); @@ -1436,8 +1439,9 @@ mod tests { // Copy the test database to the tempdir so our test runs are idempotent. std::fs::copy(&database_path, destination).unwrap(); - let database = - SqliteCryptoStore::open(tmpdir.path(), None).await.expect("Can't open the test store"); + let database = SqliteCryptoStore::open(tmpdir.path(), passphrase) + .await + .expect("Can't open the test store"); TestDb { _dir: tmpdir, database } } @@ -1446,7 +1450,7 @@ mod tests { /// pre-filled database, or in other words use a test vector for this. #[async_test] async fn test_open_test_vector_store() { - let TestDb { _dir: _, database } = get_test_db().await; + let TestDb { _dir: _, database } = get_test_db("testing/data/storage", None).await; let account = database .load_account() @@ -1508,6 +1512,277 @@ mod tests { assert_eq!(master_key.to_base64(), "iCUEtB1RwANeqRa5epDrblLk4mer/36sylwQ5hYY3oE"); } + /// Test that we didn't regress in our storage layer by loading data from a + /// pre-filled database, or in other words use a test vector for this. + #[async_test] + async fn test_open_test_vector_encrypted_store() { + let TestDb { _dir: _, database } = get_test_db( + "testing/data/storage/alice", + Some(concat!( + "/rCia2fYAJ+twCZ1Xm2mxFCYcmJdyzkdJjwtgXsziWpYS/UeNxnixuSieuwZXm+x1VsJHmWpl", + "H+QIQBZpEGZtC9/S/l8xK+WOCesmET0o6yJ/KP73ofDtjBlnNpPwuHLKFpyTbyicpCgQ4UT+5E", + "UBuJ08TY9Ujdf1D13k5kr5tSZUefDKKCuG1fCRqlU8ByRas1PMQsZxT2W8t7QgBrQiiGmhpo/O", + "Ti4hfx97GOxncKcxTzppiYQNoHs/f15+XXQD7/oiCcqRIuUlXNsU6hRpFGmbYx2Pi1eyQViQCt", + "B5dAEiSD0N8U81wXYnpynuTPtnL+hfnOJIn7Sy7mkERQeKg" + )), + ) + .await; + + let account = database + .load_account() + .await + .unwrap() + .expect("The test database is prefilled with data, we should find an account"); + + let user_id = account.user_id(); + let device_id = account.device_id(); + + assert_eq!( + user_id.as_str(), + "@alice:localhost", + "The user ID should match to the one we expect." + ); + + assert_eq!( + device_id.as_str(), + "JVVORTHFXY", + "The device ID should match to the one we expect." + ); + + let tracked_users = + database.load_tracked_users().await.expect("Should be tracking some users"); + + assert_eq!(tracked_users.len(), 6); + + let known_users = vec![ + user_id!("@alice:localhost"), + user_id!("@dehydration3:localhost"), + user_id!("@eve:localhost"), + user_id!("@bob:localhost"), + user_id!("@malo:localhost"), + user_id!("@carl:localhost"), + ]; + + // load the identities + for user_id in known_users { + database.get_user_identity(user_id).await.expect("Should load this identity").unwrap(); + } + + let carl_identity = + database.get_user_identity(user_id!("@carl:localhost")).await.unwrap().unwrap(); + + assert_eq!( + carl_identity.master_key().get_first_key().unwrap().to_base64(), + "CdhKYYDeBDQveOioXEGWhTPCyzc63Irpar3CNyfun2Q" + ); + assert!(!carl_identity.was_previously_verified()); + + let bob_identity = + database.get_user_identity(user_id!("@bob:localhost")).await.unwrap().unwrap(); + + assert_eq!( + bob_identity.master_key().get_first_key().unwrap().to_base64(), + "COh2GYOJWSjem5QPRCaGp9iWV83IELG1IzLKW2S3pFY" + ); + // Bob is verified so this flag should be set + assert!(bob_identity.was_previously_verified()); + + let known_devices = vec![ + (device_id!("OPXQHCZSKW"), user_id!("@alice:localhost")), + // a dehydrated one + ( + device_id!("EvW+9IrGR10KVgVeZP25/KaPfx4R86FofVMcaz7VOho"), + user_id!("@alice:localhost"), + ), + (device_id!("HEEFRFQENV"), user_id!("@alice:localhost")), + (device_id!("JVVORTHFXY"), user_id!("@alice:localhost")), + (device_id!("NQUWWSKKHS"), user_id!("@alice:localhost")), + (device_id!("ORBLPFYCPG"), user_id!("@alice:localhost")), + (device_id!("YXOWENSEGM"), user_id!("@dehydration3:localhost")), + (device_id!("VXLFMYCHXC"), user_id!("@bob:localhost")), + (device_id!("FDGDQAEWOW"), user_id!("@bob:localhost")), + (device_id!("VXLFMYCHXC"), user_id!("@bob:localhost")), + (device_id!("FDGDQAEWOW"), user_id!("@bob:localhost")), + (device_id!("QKUKWJTTQC"), user_id!("@malo:localhost")), + (device_id!("LOUXJECTFG"), user_id!("@malo:localhost")), + (device_id!("MKKMAEVLPB"), user_id!("@carl:localhost")), + ]; + + for (device_id, user_id) in known_devices { + database.get_device(user_id, device_id).await.expect("Should load the device").unwrap(); + } + + let known_sender_key_to_session_count = vec![ + ("FfYcYfDF4nWy+LHdK6CEpIMlFAQDORc30WUkghL06kM", 1), + ("EvW+9IrGR10KVgVeZP25/KaPfx4R86FofVMcaz7VOho", 1), + ("hAGsoA4a9M6wwEUX5Q1jux1i+tUngLi01n5AmhDoHTY", 1), + ("aKqtSJymLzuoglWFwPGk1r/Vm2LE2hFESzXxn4RNjRM", 0), + ("zHK1psCrgeMn0kaz8hcdvA3INyar9jg1yfrSp0p1pHo", 1), + ("1QmBA316Wj5jIFRwNOti6N6Xh/vW0bsYCcR4uPfy8VQ", 1), + ("g5ef2vZF3VXgSPyODIeXpyHIRkuthvLhGvd6uwYggWU", 1), + ("o7hfupPd1VsNkRIvdlH6ujrEJFSKjFCGbxhAd31XxjI", 1), + ("Z3RxKQLxY7xpP+ZdOGR2SiNE37SrvmRhW7GPu1UGdm8", 1), + ("GDomaav8NiY3J+dNEeApJm+O0FooJ3IpVaIyJzCN4w4", 1), + ("7m7fqkHyEr47V5s/KjaxtJMOr3pSHrrns2q2lWpAQi8", 0), + ("9psAkPUIF8vNbWbnviX3PlwRcaeO53EHJdNtKpTY1X0", 0), + ("mqanh+ztw5oRtpqYQgLGW864i6NY2zpoKMIlrcyC+Aw", 0), + ("fJU/TJdbsv7tVbbpHw1Ke73ziElnM32cNhP2WIg4T10", 0), + ("sUIeFeFcCZoa5IC6nJ6Vrbvztcyx09m8BBg57XKRClg", 1), + ]; + + for (id, count) in known_sender_key_to_session_count { + let olm_sessions = + database.get_sessions(id).await.expect("Should have some olm sessions"); + + println!("### Session id: {:?}", id); + assert_eq!(olm_sessions.map_or(0, |v| v.len()), count); + } + + let inbound_group_sessions = database.get_inbound_group_sessions().await.unwrap(); + assert_eq!(inbound_group_sessions.len(), 15); + let known_inbound_group_sessions = vec![ + ( + "5hNAxrLai3VI0LKBwfh3wLfksfBFWds0W1a5X5/vSXA", + room_id!("!SRstFdydzrGwJYtVfm:localhost"), + ), + ( + "M6d2eU3y54gaYTbvGSlqa/xc1Az35l56Cp9sxzHWO4g", + room_id!("!SRstFdydzrGwJYtVfm:localhost"), + ), + ( + "IrydwXkRk2N2AqUMIVmLL3oJgMq14R9KId0P/uSD100", + room_id!("!SRstFdydzrGwJYtVfm:localhost"), + ), + ( + "Y74+l9jTo7N5UF+GQwdpgJGe4sn1+QtWITq7BxulHIE", + room_id!("!SRstFdydzrGwJYtVfm:localhost"), + ), + ( + "HpJxQR57WbQGdY6w2Q+C16znVvbXGa+JvQdRoMpWbXg", + room_id!("!SRstFdydzrGwJYtVfm:localhost"), + ), + ( + "Xetvi+ydFkZt8dpONGFbEusQb/Chc2V0XlLByZhsbgE", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "wv/WN/39akyerIXczTaIpjAuLnwgXKRtbXFSEHiJqxo", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "nA4gQwL//Cm8OdlyjABl/jChbPT/cP5V4Sd8iuE6H0s", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "bAAgqFeRDTjfEqL6Qf/c9mk55zoNDCSlboAIRd6b0hw", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "exPbsMMdGfAG2qmDdFtpAn+koVprfzS0Zip/RA9QRCE", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "h+om7oSw/ZV94fcKaoe8FGXJwQXWOfKQfzbGgNWQILI", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "ul3VXonpgk4lO2L3fEWubP/nxsTmLHqu5v8ZM9vHEcw", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "JXY15UxC3az2mwg8uX4qwgxfvCM4aygiIWMcdNiVQoc", + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + ), + ( + "OGB9lObr9kWUvha9tB5sMfOF/Mztk24JwQz/nwg3iFQ", + room_id!("!OgRiTRMaUzLdpCeDBM:localhost"), + ), + ( + "SFkHcbxjUOYF7mUAYI/oEMDZFaXszQbCN6Jza7iemj0", + room_id!("!OgRiTRMaUzLdpCeDBM:localhost"), + ), + ]; + + // ensure we can load them all + for (session_id, room_id) in &known_inbound_group_sessions { + database + .get_inbound_group_session(room_id, session_id) + .await + .expect("Should be able to load inbound group session") + .unwrap(); + } + + let bob_sender_verified = database + .get_inbound_group_session( + room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"), + "exPbsMMdGfAG2qmDdFtpAn+koVprfzS0Zip/RA9QRCE", + ) + .await + .unwrap() + .unwrap(); + + assert_eq!(bob_sender_verified.sender_data.to_type(), SenderDataType::SenderVerified); + assert!(bob_sender_verified.backed_up()); + assert!(!bob_sender_verified.has_been_imported()); + + let alice_unknown_device = database + .get_inbound_group_session( + room_id!("!SRstFdydzrGwJYtVfm:localhost"), + "IrydwXkRk2N2AqUMIVmLL3oJgMq14R9KId0P/uSD100", + ) + .await + .unwrap() + .unwrap(); + + assert_eq!(alice_unknown_device.sender_data.to_type(), SenderDataType::UnknownDevice); + assert!(alice_unknown_device.backed_up()); + assert!(alice_unknown_device.has_been_imported()); + + let carl_tofu_session = database + .get_inbound_group_session( + room_id!("!OgRiTRMaUzLdpCeDBM:localhost"), + "OGB9lObr9kWUvha9tB5sMfOF/Mztk24JwQz/nwg3iFQ", + ) + .await + .unwrap() + .unwrap(); + + assert_eq!(carl_tofu_session.sender_data.to_type(), SenderDataType::SenderUnverified); + assert!(carl_tofu_session.backed_up()); + assert!(!carl_tofu_session.has_been_imported()); + + // Load outbound sessions + database + .get_outbound_group_session(room_id!("!OgRiTRMaUzLdpCeDBM:localhost")) + .await + .unwrap() + .unwrap(); + database + .get_outbound_group_session(room_id!("!ZIwZcFqZVAYLAqVjfV:localhost")) + .await + .unwrap() + .unwrap(); + database + .get_outbound_group_session(room_id!("!SRstFdydzrGwJYtVfm:localhost")) + .await + .unwrap() + .unwrap(); + + let withheld_info = database + .get_withheld_info( + room_id!("!OgRiTRMaUzLdpCeDBM:localhost"), + "SASgZ+EklvAF4QxJclMlDRlmL0fAMjAJJIKFMdb4Ht0", + ) + .await + .expect("This session should be withheld") + .unwrap(); + + assert_eq!(withheld_info.content.withheld_code(), WithheldCode::Unverified); + + let backup_keys = database.load_backup_keys().await.expect("backup key should be cached"); + assert_eq!(backup_keys.backup_version.unwrap(), "6"); + assert!(backup_keys.decryption_key.is_some()); + } async fn get_store( name: &str, passphrase: Option<&str>, diff --git a/testing/data/storage/alice/README.md b/testing/data/storage/alice/README.md new file mode 100644 index 000000000..315cddc21 --- /dev/null +++ b/testing/data/storage/alice/README.md @@ -0,0 +1,8 @@ +This database was built using a developer build of EXA using a localhost synapse. +Contains several olm sessions, megolm sessions, identities. +Used as a snapshot to test migrations to futures versions. +The database file is exported using the Android Studio `Device Explorer` tool (in `files/sessions/xxx`). +In order to get the passphrase a breakpoint must be added in the ClientBuilder to get it. +Passphrase: `/rCia2fYAJ+twCZ1Xm2mxFCYcmJdyzkdJjwtgXsziWpYS/UeNxnixuSieuwZXm+x1VsJHmWplH+QIQBZpEGZtC9/S/l8xK+WOCesmET0o6yJ/KP73ofDtjBlnNpPwuHLKFpyTbyicpCgQ4UT+5EUBuJ08TY9Ujdf1D13k5kr5tSZUefDKKCuG1fCRqlU8ByRas1PMQsZxT2W8t7QgBrQiiGmhpo/OTi4hfx97GOxncKcxTzppiYQNoHs/f15+XXQD7/oiCcqRIuUlXNsU6hRpFGmbYx2Pi1eyQViQCtB5dAEiSD0N8U81wXYnpynuTPtnL+hfnOJIn7Sy7mkERQeKg` + +Username is @alice:localhost \ No newline at end of file diff --git a/testing/data/storage/alice/matrix-sdk-crypto.sqlite3 b/testing/data/storage/alice/matrix-sdk-crypto.sqlite3 new file mode 100644 index 000000000..51a539126 Binary files /dev/null and b/testing/data/storage/alice/matrix-sdk-crypto.sqlite3 differ