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:
@@ -12,6 +12,8 @@ use arbiter_server::{
|
||||
use diesel::{QueryDsl, SelectableHelper};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
const TEST_AAD: &[u8] = b"test-aad";
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn bootstrap() {
|
||||
@@ -57,7 +59,7 @@ async fn create_new_before_bootstrap_fails() {
|
||||
.unwrap();
|
||||
|
||||
let err = actor
|
||||
.create_new(SafeCell::new(b"data".to_vec()))
|
||||
.create_new(SafeCell::new(b"data".to_vec()), TEST_AAD.to_vec())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, Error::NotBootstrapped));
|
||||
@@ -71,7 +73,7 @@ async fn decrypt_before_bootstrap_fails() {
|
||||
.await
|
||||
.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));
|
||||
}
|
||||
|
||||
@@ -85,7 +87,7 @@ async fn new_restores_sealed_state() {
|
||||
let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus())
|
||||
.await
|
||||
.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));
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ async fn unseal_correct_password() {
|
||||
|
||||
let plaintext = b"survive a restart";
|
||||
let aead_id = actor
|
||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||
.await
|
||||
.unwrap();
|
||||
drop(actor);
|
||||
@@ -108,7 +110,7 @@ async fn unseal_correct_password() {
|
||||
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ async fn unseal_wrong_then_correct_password() {
|
||||
|
||||
let plaintext = b"important data";
|
||||
let aead_id = actor
|
||||
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||
.create_new(SafeCell::new(plaintext.to_vec()), TEST_AAD.to_vec())
|
||||
.await
|
||||
.unwrap();
|
||||
drop(actor);
|
||||
@@ -136,6 +138,6 @@ async fn unseal_wrong_then_correct_password() {
|
||||
let good_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user