fix!: protect evm_wallet integrity and bind key ciphertext to wallet address
Three independent failures allowed an offline attacker with DB write access to sign transactions using a different wallet's private key: 1. evm_wallet had no HMAC envelope — aead_encrypted_id could be swapped silently. 2. AEAD used a static tag as AAD — any valid ciphertext decrypted as any wallet key. 3. No post-decryption check that the derived address matched the requested wallet. Fix: sign_entity covers (address, aead_encrypted_id) in a single transaction; CreateNew/Decrypt take caller-provided AAD (wallet address bytes); after decryption signer.address() is verified against the requested wallet address.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::vault::{CreateNew, Decrypt, Vault},
|
actors::vault::{CreateNew, Decrypt, Vault},
|
||||||
crypto::integrity,
|
crypto::integrity::{self, Integrable},
|
||||||
db::{
|
db::{
|
||||||
DatabaseError, DatabasePool,
|
DatabaseError, DatabasePool,
|
||||||
models::{self},
|
models::{self},
|
||||||
@@ -25,14 +25,35 @@ use diesel::{
|
|||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use kameo::{Actor, actor::ActorRef, messages};
|
use kameo::{Actor, actor::ActorRef, messages};
|
||||||
use rand::{SeedableRng, rng, rngs::StdRng};
|
use rand::{SeedableRng, rng, rngs::StdRng};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
pub use crate::evm::safe_signer;
|
pub use crate::evm::safe_signer;
|
||||||
|
|
||||||
|
/// Integrity guard that binds a wallet's encrypted key ID to its Ethereum address.
|
||||||
|
/// Both fields are included in the HMAC — swapping `aead_encrypted_id` in the DB
|
||||||
|
/// invalidates the envelope MAC, and the AEAD ciphertext is also bound to `address`
|
||||||
|
/// as AAD, so decryption fails too.
|
||||||
|
#[derive(arbiter_macros::Hashable)]
|
||||||
|
struct EvmWalletIntegrity {
|
||||||
|
aead_encrypted_id: i32,
|
||||||
|
address: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Integrable for EvmWalletIntegrity {
|
||||||
|
const KIND: &'static str = "evm_wallet";
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum SignTransactionError {
|
pub enum SignTransactionError {
|
||||||
#[error("Wallet not found")]
|
#[error("Wallet not found")]
|
||||||
WalletNotFound,
|
WalletNotFound,
|
||||||
|
|
||||||
|
#[error("Decrypted key does not match requested wallet address")]
|
||||||
|
KeyAddressMismatch,
|
||||||
|
|
||||||
|
#[error("Internal signing error")]
|
||||||
|
Internal,
|
||||||
|
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
Database(#[from] DatabaseError),
|
Database(#[from] DatabaseError),
|
||||||
|
|
||||||
@@ -64,6 +85,12 @@ pub enum Error {
|
|||||||
Integrity(#[from] integrity::Error),
|
Integrity(#[from] integrity::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<diesel::result::Error> for Error {
|
||||||
|
fn from(e: diesel::result::Error) -> Self {
|
||||||
|
Self::Database(DatabaseError::from(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Actor)]
|
#[derive(Actor)]
|
||||||
pub struct EvmActor {
|
pub struct EvmActor {
|
||||||
pub vault: ActorRef<Vault>,
|
pub vault: ActorRef<Vault>,
|
||||||
@@ -97,20 +124,39 @@ impl EvmActor {
|
|||||||
|
|
||||||
let aead_id: i32 = self
|
let aead_id: i32 = self
|
||||||
.vault
|
.vault
|
||||||
.ask(CreateNew { plaintext })
|
.ask(CreateNew {
|
||||||
|
plaintext,
|
||||||
|
aad: address.as_slice().to_vec(),
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::VaultSend)?;
|
.map_err(|_| Error::VaultSend)?;
|
||||||
|
|
||||||
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
||||||
let wallet_id = insert_into(schema::evm_wallet::table)
|
let wallet_id = conn
|
||||||
|
.exclusive_transaction(async |conn| {
|
||||||
|
let wallet_id: i32 = insert_into(schema::evm_wallet::table)
|
||||||
.values(&models::NewEvmWallet {
|
.values(&models::NewEvmWallet {
|
||||||
address: address.as_slice().to_vec(),
|
address: address.as_slice().to_vec(),
|
||||||
aead_encrypted_id: aead_id,
|
aead_encrypted_id: aead_id,
|
||||||
})
|
})
|
||||||
.returning(schema::evm_wallet::id)
|
.returning(schema::evm_wallet::id)
|
||||||
.get_result(&mut conn)
|
.get_result(conn)
|
||||||
.await
|
.await
|
||||||
.map_err(DatabaseError::from)?;
|
.map_err(DatabaseError::from)
|
||||||
|
.map_err(Error::Database)?;
|
||||||
|
|
||||||
|
integrity::sign_entity(
|
||||||
|
conn,
|
||||||
|
&self.vault,
|
||||||
|
&EvmWalletIntegrity { address, aead_encrypted_id: aead_id },
|
||||||
|
wallet_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Error::Integrity)?;
|
||||||
|
|
||||||
|
Ok::<i32, Error>(wallet_id)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok((wallet_id, address))
|
Ok((wallet_id, address))
|
||||||
}
|
}
|
||||||
@@ -241,16 +287,51 @@ impl EvmActor {
|
|||||||
.ok_or(SignTransactionError::WalletNotFound)?;
|
.ok_or(SignTransactionError::WalletNotFound)?;
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
|
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
||||||
|
let attestation = integrity::verify_entity(
|
||||||
|
&mut conn,
|
||||||
|
&self.vault,
|
||||||
|
&EvmWalletIntegrity {
|
||||||
|
address: wallet_address,
|
||||||
|
aead_encrypted_id: wallet.aead_encrypted_id,
|
||||||
|
},
|
||||||
|
wallet.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(?e, wallet_id = wallet.id, "EVM wallet integrity check failed");
|
||||||
|
SignTransactionError::Internal
|
||||||
|
})?;
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
|
if attestation != integrity::AttestationStatus::Attested {
|
||||||
|
error!(
|
||||||
|
wallet_id = wallet.id,
|
||||||
|
"EVM wallet integrity unavailable; refusing to sign"
|
||||||
|
);
|
||||||
|
return Err(SignTransactionError::Internal);
|
||||||
|
}
|
||||||
|
|
||||||
let raw_key: SafeCell<Vec<u8>> = self
|
let raw_key: SafeCell<Vec<u8>> = self
|
||||||
.vault
|
.vault
|
||||||
.ask(Decrypt {
|
.ask(Decrypt {
|
||||||
aead_id: wallet.aead_encrypted_id,
|
aead_id: wallet.aead_encrypted_id,
|
||||||
|
aad: wallet.address.clone(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| SignTransactionError::VaultSend)?;
|
.map_err(|_| SignTransactionError::VaultSend)?;
|
||||||
|
|
||||||
let signer = safe_signer::SafeSigner::from_cell(raw_key)?;
|
let signer = safe_signer::SafeSigner::from_cell(raw_key)?;
|
||||||
|
|
||||||
|
if signer.address() != wallet_address {
|
||||||
|
error!(
|
||||||
|
expected = %wallet_address,
|
||||||
|
actual = %signer.address(),
|
||||||
|
"Decrypted private key address does not match requested wallet"
|
||||||
|
);
|
||||||
|
return Err(SignTransactionError::KeyAddressMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
self.engine
|
self.engine
|
||||||
.evaluate_transaction(wallet_access, transaction.clone(), RunKind::Execution)
|
.evaluate_transaction(wallet_access, transaction.clone(), RunKind::Execution)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -298,8 +298,10 @@ impl Vault {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decrypts an AEAD entry. The `aad` must match the value used at encryption time;
|
||||||
|
/// a mismatch causes authentication failure, preventing cross-wallet key swaps.
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
|
pub async fn decrypt(&mut self, aead_id: i32, aad: Vec<u8>) -> Result<SafeCell<Vec<u8>>, Error> {
|
||||||
let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?;
|
let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?;
|
||||||
|
|
||||||
let row: models::AeadEncrypted = {
|
let row: models::AeadEncrypted = {
|
||||||
@@ -321,13 +323,15 @@ impl Vault {
|
|||||||
Error::BrokenDatabase
|
Error::BrokenDatabase
|
||||||
})?;
|
})?;
|
||||||
let mut output = SafeCell::new(row.ciphertext);
|
let mut output = SafeCell::new(row.ciphertext);
|
||||||
root_key.decrypt_in_place(&nonce, v1::TAG, &mut output)?;
|
root_key.decrypt_in_place(&nonce, &aad, &mut output)?;
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `aead_encrypted` entry and returns its ID.
|
||||||
|
/// The `aad` is bound into the ciphertext and must be reproduced exactly at decryption time.
|
||||||
// Creates new `aead_encrypted` entry in the database and returns it's ID
|
// Creates new `aead_encrypted` entry in the database and returns it's ID
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn create_new(&mut self, mut plaintext: SafeCell<Vec<u8>>) -> Result<i32, Error> {
|
pub async fn create_new(&mut self, mut plaintext: SafeCell<Vec<u8>>, aad: Vec<u8>) -> Result<i32, Error> {
|
||||||
let Unsealed {
|
let Unsealed {
|
||||||
root_key,
|
root_key,
|
||||||
root_key_history_id,
|
root_key_history_id,
|
||||||
@@ -339,7 +343,7 @@ impl Vault {
|
|||||||
|
|
||||||
let mut ciphertext_buffer = plaintext.write();
|
let mut ciphertext_buffer = plaintext.write();
|
||||||
let ciphertext_buffer: &mut Vec<u8> = ciphertext_buffer.as_mut();
|
let ciphertext_buffer: &mut Vec<u8> = ciphertext_buffer.as_mut();
|
||||||
root_key.encrypt_in_place(&nonce, v1::TAG, &mut *ciphertext_buffer)?;
|
root_key.encrypt_in_place(&nonce, &aad, &mut *ciphertext_buffer)?;
|
||||||
|
|
||||||
let ciphertext = std::mem::take(ciphertext_buffer);
|
let ciphertext = std::mem::take(ciphertext_buffer);
|
||||||
|
|
||||||
@@ -474,7 +478,7 @@ mod tests {
|
|||||||
assert_eq!(root_row.data_encryption_nonce, n2.to_vec());
|
assert_eq!(root_row.data_encryption_nonce, n2.to_vec());
|
||||||
|
|
||||||
let id = actor
|
let id = actor
|
||||||
.create_new(SafeCell::new(b"post-interleave".to_vec()))
|
.create_new(SafeCell::new(b"post-interleave".to_vec()), b"test-aad".to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let row: models::AeadEncrypted = schema::aead_encrypted::table
|
let row: models::AeadEncrypted = schema::aead_encrypted::table
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ use kameo::actor::{ActorRef, Spawn as _};
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
const TEST_AAD: &[u8] = b"test-aad";
|
||||||
|
|
||||||
async fn write_concurrently(
|
async fn write_concurrently(
|
||||||
actor: ActorRef<Vault>,
|
actor: ActorRef<Vault>,
|
||||||
prefix: &'static str,
|
prefix: &'static str,
|
||||||
@@ -27,6 +29,7 @@ async fn write_concurrently(
|
|||||||
let id = actor
|
let id = actor
|
||||||
.ask(CreateNew {
|
.ask(CreateNew {
|
||||||
plaintext: SafeCell::new(plaintext.clone()),
|
plaintext: SafeCell::new(plaintext.clone()),
|
||||||
|
aad: TEST_AAD.to_vec(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -120,7 +123,7 @@ async fn insert_failure_does_not_create_partial_row() {
|
|||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let err = actor
|
let err = actor
|
||||||
.create_new(SafeCell::new(b"should fail".to_vec()))
|
.create_new(SafeCell::new(b"should fail".to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, Error::DatabaseTransaction(_)));
|
assert!(matches!(err, Error::DatabaseTransaction(_)));
|
||||||
@@ -171,7 +174,7 @@ async fn decrypt_roundtrip_after_high_concurrency() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for (id, plaintext) in expected {
|
for (id, plaintext) in expected {
|
||||||
let mut decrypted = decryptor.decrypt(id).await.unwrap();
|
let mut decrypted = decryptor.decrypt(id, TEST_AAD.to_vec()).await.unwrap();
|
||||||
assert_eq!(*decrypted.read(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ use arbiter_server::{
|
|||||||
use diesel::{QueryDsl, SelectableHelper};
|
use diesel::{QueryDsl, SelectableHelper};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
const TEST_AAD: &[u8] = b"test-aad";
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
async fn bootstrap() {
|
async fn bootstrap() {
|
||||||
@@ -57,7 +59,7 @@ async fn create_new_before_bootstrap_fails() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let err = actor
|
let err = actor
|
||||||
.create_new(SafeCell::new(b"data".to_vec()))
|
.create_new(SafeCell::new(b"data".to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, Error::NotBootstrapped));
|
assert!(matches!(err, Error::NotBootstrapped));
|
||||||
@@ -71,7 +73,7 @@ async fn decrypt_before_bootstrap_fails() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let err = actor.decrypt(1).await.unwrap_err();
|
let err = actor.decrypt(1, TEST_AAD.to_vec()).await.unwrap_err();
|
||||||
assert!(matches!(err, Error::NotBootstrapped));
|
assert!(matches!(err, Error::NotBootstrapped));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ async fn new_restores_sealed_state() {
|
|||||||
let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus())
|
let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let err = actor2.decrypt(1).await.unwrap_err();
|
let err = actor2.decrypt(1, TEST_AAD.to_vec()).await.unwrap_err();
|
||||||
assert!(matches!(err, Error::Sealed));
|
assert!(matches!(err, Error::Sealed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ async fn unseal_correct_password() {
|
|||||||
|
|
||||||
let plaintext = b"survive a restart";
|
let plaintext = b"survive a restart";
|
||||||
let aead_id = actor
|
let aead_id = actor
|
||||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
drop(actor);
|
drop(actor);
|
||||||
@@ -108,7 +110,7 @@ async fn unseal_correct_password() {
|
|||||||
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||||
actor.try_unseal(seal_key).await.unwrap();
|
actor.try_unseal(seal_key).await.unwrap();
|
||||||
|
|
||||||
let mut decrypted = actor.decrypt(aead_id).await.unwrap();
|
let mut decrypted = actor.decrypt(aead_id, TEST_AAD.to_vec()).await.unwrap();
|
||||||
assert_eq!(*decrypted.read(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ async fn unseal_wrong_then_correct_password() {
|
|||||||
|
|
||||||
let plaintext = b"important data";
|
let plaintext = b"important data";
|
||||||
let aead_id = actor
|
let aead_id = actor
|
||||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
drop(actor);
|
drop(actor);
|
||||||
@@ -136,6 +138,6 @@ async fn unseal_wrong_then_correct_password() {
|
|||||||
let good_key = SafeCell::new(b"test-seal-key".to_vec());
|
let good_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||||
actor.try_unseal(good_key).await.unwrap();
|
actor.try_unseal(good_key).await.unwrap();
|
||||||
|
|
||||||
let mut decrypted = actor.decrypt(aead_id).await.unwrap();
|
let mut decrypted = actor.decrypt(aead_id, TEST_AAD.to_vec()).await.unwrap();
|
||||||
assert_eq!(*decrypted.read(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::update};
|
|||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
const TEST_AAD: &[u8] = b"test-aad";
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
async fn create_decrypt_roundtrip() {
|
async fn create_decrypt_roundtrip() {
|
||||||
@@ -18,11 +20,11 @@ async fn create_decrypt_roundtrip() {
|
|||||||
|
|
||||||
let plaintext = b"hello arbiter";
|
let plaintext = b"hello arbiter";
|
||||||
let aead_id = actor
|
let aead_id = actor
|
||||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut decrypted = actor.decrypt(aead_id).await.unwrap();
|
let mut decrypted = actor.decrypt(aead_id, TEST_AAD.to_vec()).await.unwrap();
|
||||||
assert_eq!(*decrypted.read(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ async fn decrypt_nonexistent_returns_not_found() {
|
|||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let mut actor = common::bootstrapped_vault(&db).await;
|
let mut actor = common::bootstrapped_vault(&db).await;
|
||||||
|
|
||||||
let err = actor.decrypt(9999).await.unwrap_err();
|
let err = actor.decrypt(9999, TEST_AAD.to_vec()).await.unwrap_err();
|
||||||
assert!(matches!(err, Error::NotFound));
|
assert!(matches!(err, Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +46,11 @@ async fn ciphertext_differs_across_entries() {
|
|||||||
|
|
||||||
let plaintext = b"same content";
|
let plaintext = b"same content";
|
||||||
let id1 = actor
|
let id1 = actor
|
||||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let id2 = actor
|
let id2 = actor
|
||||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -68,8 +70,8 @@ async fn ciphertext_differs_across_entries() {
|
|||||||
|
|
||||||
assert_ne!(row1.ciphertext, row2.ciphertext);
|
assert_ne!(row1.ciphertext, row2.ciphertext);
|
||||||
|
|
||||||
let mut d1 = actor.decrypt(id1).await.unwrap();
|
let mut d1 = actor.decrypt(id1, TEST_AAD.to_vec()).await.unwrap();
|
||||||
let mut d2 = actor.decrypt(id2).await.unwrap();
|
let mut d2 = actor.decrypt(id2, TEST_AAD.to_vec()).await.unwrap();
|
||||||
assert_eq!(*d1.read(), plaintext);
|
assert_eq!(*d1.read(), plaintext);
|
||||||
assert_eq!(*d2.read(), plaintext);
|
assert_eq!(*d2.read(), plaintext);
|
||||||
}
|
}
|
||||||
@@ -83,7 +85,7 @@ async fn nonce_never_reused() {
|
|||||||
let n = 5;
|
let n = 5;
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
actor
|
actor
|
||||||
.create_new(SafeCell::new(format!("secret {i}").into_bytes()))
|
.create_new(SafeCell::new(format!("secret {i}").into_bytes()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -137,7 +139,7 @@ async fn broken_db_nonce_format_fails_closed() {
|
|||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let err = actor
|
let err = actor
|
||||||
.create_new(SafeCell::new(b"must fail".to_vec()))
|
.create_new(SafeCell::new(b"must fail".to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, Error::BrokenDatabase));
|
assert!(matches!(err, Error::BrokenDatabase));
|
||||||
@@ -145,7 +147,7 @@ async fn broken_db_nonce_format_fails_closed() {
|
|||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let mut actor = common::bootstrapped_vault(&db).await;
|
let mut actor = common::bootstrapped_vault(&db).await;
|
||||||
let id = actor
|
let id = actor
|
||||||
.create_new(SafeCell::new(b"decrypt target".to_vec()))
|
.create_new(SafeCell::new(b"decrypt target".to_vec()), TEST_AAD.to_vec())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
@@ -156,6 +158,6 @@ async fn broken_db_nonce_format_fails_closed() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let err = actor.decrypt(id).await.unwrap_err();
|
let err = actor.decrypt(id, TEST_AAD.to_vec()).await.unwrap_err();
|
||||||
assert!(matches!(err, Error::BrokenDatabase));
|
assert!(matches!(err, Error::BrokenDatabase));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user