mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 09:26:29 +03:00
Add API to change database passphrase
New API is: - dc_context_is_encrypted() - dc_context_change_passphrase()
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### API Changes
|
||||||
|
- added APIs to check if database is encrypted and to change the passphrase:
|
||||||
|
`dc_context_is_encrypted()` and `dc_context_change_passphrase()` #3029.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- don't watch Sent folder by default #3025
|
- don't watch Sent folder by default #3025
|
||||||
- use webxdc app name in chatlist/quotes/replies etc. #3027
|
- use webxdc app name in chatlist/quotes/replies etc. #3027
|
||||||
|
|||||||
@@ -241,6 +241,32 @@ int dc_context_open (dc_context_t *context, const char*
|
|||||||
int dc_context_is_open (dc_context_t *context);
|
int dc_context_is_open (dc_context_t *context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return 1 if database is encrypted. Can only be checked on open database.
|
||||||
|
* Use this method to decide whether to present an option to change passphrase
|
||||||
|
* to the user.
|
||||||
|
*
|
||||||
|
* @member dc_context_t
|
||||||
|
* @param context The context object.
|
||||||
|
* @return 1 if database is encrypted, 0 if database is not encrypted or on
|
||||||
|
* error.
|
||||||
|
*/
|
||||||
|
int dc_context_is_encrypted (dc_context_t *context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes passphrase for the open database. The database must be encrypted
|
||||||
|
* already, i.e. have a non-empty password. Unencrypted databases can only be
|
||||||
|
* encrypted during import/export.
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object.
|
||||||
|
* @param passpharse New passphrase.
|
||||||
|
* @return 1 if database was reencrypted with the new passphrase, 0 on error
|
||||||
|
* (database is closed, database is not encrypted, other SQLCipher error).
|
||||||
|
*/
|
||||||
|
int dc_context_change_passphrase (dc_context_t *context, char *passphrase);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free a context object.
|
* Free a context object.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -144,6 +144,34 @@ pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc:
|
|||||||
block_on(ctx.is_open()) as libc::c_int
|
block_on(ctx.is_open()) as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_context_is_encrypted(context: *mut dc_context_t) -> libc::c_int {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_context_is_encrypted()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = &*context;
|
||||||
|
block_on(ctx.is_encrypted()) as libc::c_int
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_context_change_passphrase(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
passphrase: *const libc::c_char,
|
||||||
|
) -> libc::c_int {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_context_change_passphrase()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = &*context;
|
||||||
|
let passphrase = to_string_lossy(passphrase);
|
||||||
|
block_on(ctx.change_passphrase(passphrase))
|
||||||
|
.log_err(ctx, "change_passphrase failed")
|
||||||
|
.is_ok() as libc::c_int
|
||||||
|
}
|
||||||
|
|
||||||
/// Release the context structure.
|
/// Release the context structure.
|
||||||
///
|
///
|
||||||
/// This function releases the memory of the `dc_context_t` structure.
|
/// This function releases the memory of the `dc_context_t` structure.
|
||||||
|
|||||||
@@ -146,6 +146,12 @@ impl Context {
|
|||||||
self.sql.is_open().await
|
self.sql.is_open().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if database is encrypted. Returns false if database is not open yet or not
|
||||||
|
/// encrypted.
|
||||||
|
pub async fn is_encrypted(&self) -> bool {
|
||||||
|
self.sql.is_encrypted().await.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests the database passphrase.
|
/// Tests the database passphrase.
|
||||||
///
|
///
|
||||||
/// Returns true if passphrase is correct.
|
/// Returns true if passphrase is correct.
|
||||||
@@ -155,6 +161,14 @@ impl Context {
|
|||||||
self.sql.check_passphrase(passphrase).await
|
self.sql.check_passphrase(passphrase).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes the database passphrase.
|
||||||
|
///
|
||||||
|
/// Works only for encrypted databases. Encrypted database can only be converted to unencrypted
|
||||||
|
/// one and backwards via import/export.
|
||||||
|
pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
|
||||||
|
self.sql.change_passphrase(self, passphrase).await
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn with_blobdir(
|
pub(crate) async fn with_blobdir(
|
||||||
dbfile: PathBuf,
|
dbfile: PathBuf,
|
||||||
blobdir: PathBuf,
|
blobdir: PathBuf,
|
||||||
@@ -1069,4 +1083,34 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_change_passphrase() -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let dbfile = dir.path().join("db.sqlite");
|
||||||
|
|
||||||
|
let id = 1;
|
||||||
|
let context = Context::new_closed(dbfile.clone().into(), id)
|
||||||
|
.await
|
||||||
|
.context("failed to create context")?;
|
||||||
|
assert_eq!(context.open("foo".to_string()).await?, true);
|
||||||
|
assert_eq!(context.is_open().await, true);
|
||||||
|
assert_eq!(context.is_encrypted().await, true);
|
||||||
|
|
||||||
|
context.change_passphrase("bar".to_string()).await?;
|
||||||
|
drop(context);
|
||||||
|
|
||||||
|
let id = 2;
|
||||||
|
let context = Context::new(dbfile.into(), id)
|
||||||
|
.await
|
||||||
|
.context("failed to create context")?;
|
||||||
|
assert_eq!(context.is_open().await, false);
|
||||||
|
assert_eq!(context.check_passphrase("foo".to_string()).await?, false);
|
||||||
|
assert_eq!(context.check_passphrase("bar".to_string()).await?, true);
|
||||||
|
assert_eq!(context.open("foo".to_string()).await?, false);
|
||||||
|
assert_eq!(context.open("bar".to_string()).await?, true);
|
||||||
|
assert_eq!(context.is_encrypted().await, true);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/sql.rs
34
src/sql.rs
@@ -97,6 +97,40 @@ impl Sql {
|
|||||||
*self.is_encrypted.read().await
|
*self.is_encrypted.read().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes the database passpharse.
|
||||||
|
///
|
||||||
|
/// The database must be open and encrypted already.
|
||||||
|
pub(crate) async fn change_passphrase(
|
||||||
|
&self,
|
||||||
|
context: &Context,
|
||||||
|
passphrase: String,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Take the whole pool so nobody opens another connection in parallel.
|
||||||
|
let pool = self
|
||||||
|
.pool
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.take()
|
||||||
|
.context("the database must be open before rekeying")?;
|
||||||
|
|
||||||
|
// Get one connection and rekey the database.
|
||||||
|
// All other connections will stop working after that.
|
||||||
|
let connection = pool
|
||||||
|
.get()
|
||||||
|
.context("failed to get connection from the pool")?;
|
||||||
|
connection
|
||||||
|
.pragma_update(None, "rekey", &passphrase)
|
||||||
|
.context("failed to set PRAGMA rekey")?;
|
||||||
|
drop(pool);
|
||||||
|
|
||||||
|
// Reopen the database with new passphrase.
|
||||||
|
self.open(context, passphrase)
|
||||||
|
.await
|
||||||
|
.context("failed to reopen the database after rekeying")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Closes all underlying Sqlite connections.
|
/// Closes all underlying Sqlite connections.
|
||||||
async fn close(&self) {
|
async fn close(&self) {
|
||||||
let _ = self.pool.write().await.take();
|
let _ = self.pool.write().await.take();
|
||||||
|
|||||||
Reference in New Issue
Block a user