From 6f31bb76d035bfc1a863d1b2e80ca4350eb8bbb6 Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Mon, 9 Sep 2024 06:54:39 +0900 Subject: [PATCH] state: add test for format stability This should have been added before the matrix sdk upgrade, but it is extra work to update the test for old serializing format too. That part was tested manually --- src/state.rs | 82 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/src/state.rs b/src/state.rs index e2a1f30..5edfd97 100644 --- a/src/state.rs +++ b/src/state.rs @@ -17,7 +17,7 @@ base64_serde_type!(Base64, base64::engine::general_purpose::STANDARD); use crate::args::args; /// data we want to keep around -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Session { pub homeserver: String, pub matrix_session: SerializedMatrixSession, @@ -25,7 +25,7 @@ pub struct Session { /// matrix-rust-sdk's "Session" struct as we used to serialize it /// as of matrix-rust-sdk commit 0b9c082e11955f49f99acd21542f62b40f11c418 -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct SerializedMatrixSession { /// The access token used for this session. pub access_token: String, @@ -54,11 +54,13 @@ struct Blob { /// try to decrypt session and return it fn check_pass(session_file: PathBuf, pass: &str) -> Result { - let blob = { - let content = fs::read(session_file).context("Could not read user session file")?; - serde_json::from_slice::(&content) - .context("Could not deserialize session file content.")? - }; + let blob_text = fs::read(session_file).context("Could not read user session file")?; + decrypt_blob(pass, &blob_text) +} + +fn decrypt_blob(pass: &str, blob_text: &[u8]) -> Result { + let blob = serde_json::from_slice::(blob_text) + .context("Could not deserialize session file content.")?; if blob.version != "argon2+chacha20poly1305" { return Err(Error::msg( "This version only supports argon2+chacha20poly1305", @@ -79,13 +81,7 @@ fn check_pass(session_file: PathBuf, pass: &str) -> Result { Ok(session) } -/// encrypt session and store it -pub fn create_user( - nick: &str, - pass: &str, - homeserver: &str, - auth_session: AuthSession, -) -> Result<()> { +fn encrypt_blob(pass: &str, homeserver: &str, auth_session: AuthSession) -> Result> { let session_meta = auth_session.meta(); let session = Session { homeserver: homeserver.into(), @@ -118,6 +114,17 @@ pub fn create_user( salt, nonce, }; + serde_json::to_vec(&blob).context("could not serialize blob") +} + +/// encrypt session and store it +pub fn create_user( + nick: &str, + pass: &str, + homeserver: &str, + auth_session: AuthSession, +) -> Result<()> { + let blob_text = encrypt_blob(pass, homeserver, auth_session)?; let user_dir = Path::new(&args().state_dir).join(nick); if !user_dir.is_dir() { @@ -133,7 +140,7 @@ pub fn create_user( .create_new(true) .open(user_dir.join("session")) .context("creating user session file failed")?; - file.write_all(&serde_json::to_vec(&blob).context("could not serialize blob")?) + file.write_all(&blob_text) .context("Writing to user session file failed")?; Ok(()) } @@ -150,3 +157,48 @@ pub fn login(nick: &str, pass: &str) -> Result> { Err(Error::msg(format!("unknown user {}", nick))) } } + +#[cfg(test)] +mod tests { + use super::*; + use matrix_sdk::{ + matrix_auth::{MatrixSession, MatrixSessionTokens}, + SessionMeta, + }; + + /// ensure on disk format is stable + #[test] + fn check_state_storage() -> Result<()> { + //{"homeserver":"https://matrix.codewreck.org","matrix_session":{"access_token":"syt_dGVzdDI_MsvRmWOsfnSDZMCycFUK_3UNGcT","user_id":"@test2:codewreck.org","device_id":"MSPYQMJBVG"}} + let session = AuthSession::Matrix(MatrixSession { + meta: SessionMeta { + user_id: "@test:domain.tld".try_into()?, + device_id: "ABCDEFGHIJ".try_into()?, + }, + tokens: MatrixSessionTokens { + access_token: "abc_abcdefg_abcdefgh_abcdef".into(), + refresh_token: None, + }, + }); + // can serialize/encrypt + let blob_string = &encrypt_blob("pass", "domain.tld", session)?; + + // can decrypt what we just encrypted + let session = decrypt_blob("pass", &blob_string)?; + assert_eq!(session.homeserver, "domain.tld"); + assert_eq!(session.matrix_session.user_id, "@test:domain.tld"); + assert_eq!(session.matrix_session.device_id, "ABCDEFGHIJ"); + assert_eq!( + session.matrix_session.access_token, + "abc_abcdefg_abcdefgh_abcdef" + ); + assert!(session.matrix_session.refresh_token.is_none()); + + // can decrypt something we encrypted ages ago (format stability check) + let old_blob = r#"{"version":"argon2+chacha20poly1305","ciphertext":"jTMm0N+nAl9jTD6sdppn+9w5B93QpGzng7YNyR+oDcFdHs3EEAUYKKBPTQlkJovthypQ+eDSrS9Vd9WJAdsa9NqGgyx+XoijMPL4LG+K88CnlKE/0GbNbGLH4r1QqGif5aimVJOmgI5rTgRAb+ZhfEGx5nmk1CNmCW5nCzLmWfdvjHJssMJt4JJFN82hJoVn2RHNwFY3q+MQ08E0zTvG1CA=","salt":"c9fUuFFl0Q1bzaBKAyvOcy+x1alIJ2mr/eZow4ut+58=","nonce":"QgY2eb3OGc7VCzw76t4b9kSPWx4pmZCG"}"#; + let old_session = decrypt_blob("pass", old_blob.as_bytes())?; + assert_eq!(session, old_session); + + Ok(()) + } +}