Compare commits
2 Commits
088fa6fe72
...
c56184d30b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c56184d30b | ||
|
|
9017ea4017 |
@@ -1,3 +1,12 @@
|
|||||||
|
[[tools.ast-grep]]
|
||||||
|
version = "0.42.0"
|
||||||
|
backend = "aqua:ast-grep/ast-grep"
|
||||||
|
"platforms.linux-arm64" = { checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"}
|
||||||
|
"platforms.linux-x64" = { checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"}
|
||||||
|
"platforms.macos-arm64" = { checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"}
|
||||||
|
"platforms.macos-x64" = { checksum = "sha256:979ffe611327056f4730a1ae71b0209b3b830f58b22c6ed194cda34f55400db2", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-apple-darwin.zip"}
|
||||||
|
"platforms.windows-x64" = { checksum = "sha256:55836fa1b2c65dc7d61615a4d9368622a0d2371a76d28b9a165e5a3ab6ae32a4", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-pc-windows-msvc.zip"}
|
||||||
|
|
||||||
[[tools."cargo:cargo-audit"]]
|
[[tools."cargo:cargo-audit"]]
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
backend = "cargo:cargo-audit"
|
backend = "cargo:cargo-audit"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ protoc = "29.6"
|
|||||||
"cargo:cargo-shear" = "latest"
|
"cargo:cargo-shear" = "latest"
|
||||||
"cargo:cargo-insta" = "1.46.3"
|
"cargo:cargo-insta" = "1.46.3"
|
||||||
python = "3.14.3"
|
python = "3.14.3"
|
||||||
|
ast-grep = "0.42.0"
|
||||||
|
|
||||||
[tasks.codegen]
|
[tasks.codegen]
|
||||||
sources = ['protobufs/*.proto']
|
sources = ['protobufs/*.proto']
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ use diesel::{
|
|||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use kameo::{Actor, actor::ActorRef, messages};
|
use kameo::{Actor, actor::ActorRef, messages};
|
||||||
use memsafe::MemSafe;
|
|
||||||
use rand::{SeedableRng, rng, rngs::StdRng};
|
use rand::{SeedableRng, rng, rngs::StdRng};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::keyholder::{CreateNew, Decrypt, KeyHolder},
|
actors::keyholder::{CreateNew, Decrypt, KeyHolder},
|
||||||
db::{
|
db::{
|
||||||
self, DatabasePool,
|
self, DatabasePool,
|
||||||
models::{self, EvmBasicGrant, SqliteTimestamp},
|
models::{self, SqliteTimestamp},
|
||||||
schema,
|
schema,
|
||||||
},
|
},
|
||||||
evm::{
|
evm::{
|
||||||
@@ -21,6 +20,7 @@ use crate::{
|
|||||||
ether_transfer::EtherTransfer, token_transfers::TokenTransfer,
|
ether_transfer::EtherTransfer, token_transfers::TokenTransfer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::evm::safe_signer;
|
pub use crate::evm::safe_signer;
|
||||||
@@ -108,11 +108,7 @@ impl EvmActor {
|
|||||||
pub async fn generate(&mut self) -> Result<Address, Error> {
|
pub async fn generate(&mut self) -> Result<Address, Error> {
|
||||||
let (mut key_cell, address) = safe_signer::generate(&mut self.rng);
|
let (mut key_cell, address) = safe_signer::generate(&mut self.rng);
|
||||||
|
|
||||||
// Move raw key bytes into a Vec<u8> MemSafe for KeyHolder
|
let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec()));
|
||||||
let plaintext = {
|
|
||||||
let reader = key_cell.read().expect("MemSafe read");
|
|
||||||
MemSafe::new(reader.to_vec()).expect("MemSafe allocation")
|
|
||||||
};
|
|
||||||
|
|
||||||
let aead_id: i32 = self
|
let aead_id: i32 = self
|
||||||
.keyholder
|
.keyholder
|
||||||
@@ -249,7 +245,7 @@ impl EvmActor {
|
|||||||
.ok_or(SignTransactionError::WalletNotFound)?;
|
.ok_or(SignTransactionError::WalletNotFound)?;
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let raw_key: MemSafe<Vec<u8>> = self
|
let raw_key: SafeCell<Vec<u8>> = self
|
||||||
.keyholder
|
.keyholder
|
||||||
.ask(Decrypt {
|
.ask(Decrypt {
|
||||||
aead_id: wallet.aead_encrypted_id,
|
aead_id: wallet.aead_encrypted_id,
|
||||||
@@ -257,7 +253,7 @@ impl EvmActor {
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| SignTransactionError::KeyholderSend)?;
|
.map_err(|_| SignTransactionError::KeyholderSend)?;
|
||||||
|
|
||||||
let signer = safe_signer::SafeSigner::from_memsafe(raw_key)?;
|
let signer = safe_signer::SafeSigner::from_cell(raw_key)?;
|
||||||
|
|
||||||
self.engine
|
self.engine
|
||||||
.evaluate_transaction(
|
.evaluate_transaction(
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use chacha20poly1305::{
|
|||||||
AeadInPlace, Key, KeyInit as _, XChaCha20Poly1305, XNonce,
|
AeadInPlace, Key, KeyInit as _, XChaCha20Poly1305, XNonce,
|
||||||
aead::{AeadMut, Error, Payload},
|
aead::{AeadMut, Error, Payload},
|
||||||
};
|
};
|
||||||
use memsafe::MemSafe;
|
|
||||||
use rand::{
|
use rand::{
|
||||||
Rng as _, SeedableRng,
|
Rng as _, SeedableRng,
|
||||||
rngs::{StdRng, SysRng},
|
rngs::{StdRng, SysRng},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::safe_cell::{SafeCell, SafeCellHandle as _};
|
||||||
|
|
||||||
pub const ROOT_KEY_TAG: &[u8] = "arbiter/seal/v1".as_bytes();
|
pub const ROOT_KEY_TAG: &[u8] = "arbiter/seal/v1".as_bytes();
|
||||||
pub const TAG: &[u8] = "arbiter/private-key/v1".as_bytes();
|
pub const TAG: &[u8] = "arbiter/private-key/v1".as_bytes();
|
||||||
|
|
||||||
@@ -47,40 +48,33 @@ impl<'a> TryFrom<&'a [u8]> for Nonce {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeyCell(pub MemSafe<Key>);
|
pub struct KeyCell(pub SafeCell<Key>);
|
||||||
impl From<MemSafe<Key>> for KeyCell {
|
impl From<SafeCell<Key>> for KeyCell {
|
||||||
fn from(value: MemSafe<Key>) -> Self {
|
fn from(value: SafeCell<Key>) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TryFrom<MemSafe<Vec<u8>>> for KeyCell {
|
impl TryFrom<SafeCell<Vec<u8>>> for KeyCell {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(mut value: MemSafe<Vec<u8>>) -> Result<Self, Self::Error> {
|
fn try_from(mut value: SafeCell<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||||
let value = value.read().unwrap();
|
let value = value.read();
|
||||||
if value.len() != size_of::<Key>() {
|
if value.len() != size_of::<Key>() {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
let mut cell = MemSafe::new(Key::default()).unwrap();
|
let cell = SafeCell::new_inline(|cell_write: &mut Key| {
|
||||||
{
|
cell_write.copy_from_slice(&value);
|
||||||
let mut cell_write = cell.write().unwrap();
|
});
|
||||||
let cell_slice: &mut [u8] = cell_write.as_mut();
|
|
||||||
cell_slice.copy_from_slice(&value);
|
|
||||||
}
|
|
||||||
Ok(Self(cell))
|
Ok(Self(cell))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyCell {
|
impl KeyCell {
|
||||||
pub fn new_secure_random() -> Self {
|
pub fn new_secure_random() -> Self {
|
||||||
let mut key = MemSafe::new(Key::default()).unwrap();
|
let key = SafeCell::new_inline(|key_buffer: &mut Key| {
|
||||||
{
|
|
||||||
let mut key_buffer = key.write().unwrap();
|
|
||||||
let key_buffer: &mut [u8] = key_buffer.as_mut();
|
|
||||||
|
|
||||||
let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap();
|
let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap();
|
||||||
rng.fill_bytes(key_buffer);
|
rng.fill_bytes(key_buffer);
|
||||||
}
|
});
|
||||||
|
|
||||||
key.into()
|
key.into()
|
||||||
}
|
}
|
||||||
@@ -91,7 +85,7 @@ impl KeyCell {
|
|||||||
associated_data: &[u8],
|
associated_data: &[u8],
|
||||||
mut buffer: impl AsMut<Vec<u8>>,
|
mut buffer: impl AsMut<Vec<u8>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let key_reader = self.0.read().unwrap();
|
let key_reader = self.0.read();
|
||||||
let key_ref = key_reader.deref();
|
let key_ref = key_reader.deref();
|
||||||
let cipher = XChaCha20Poly1305::new(key_ref);
|
let cipher = XChaCha20Poly1305::new(key_ref);
|
||||||
let nonce = XNonce::from_slice(nonce.0.as_ref());
|
let nonce = XNonce::from_slice(nonce.0.as_ref());
|
||||||
@@ -102,13 +96,13 @@ impl KeyCell {
|
|||||||
&mut self,
|
&mut self,
|
||||||
nonce: &Nonce,
|
nonce: &Nonce,
|
||||||
associated_data: &[u8],
|
associated_data: &[u8],
|
||||||
buffer: &mut MemSafe<Vec<u8>>,
|
buffer: &mut SafeCell<Vec<u8>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let key_reader = self.0.read().unwrap();
|
let key_reader = self.0.read();
|
||||||
let key_ref = key_reader.deref();
|
let key_ref = key_reader.deref();
|
||||||
let cipher = XChaCha20Poly1305::new(key_ref);
|
let cipher = XChaCha20Poly1305::new(key_ref);
|
||||||
let nonce = XNonce::from_slice(nonce.0.as_ref());
|
let nonce = XNonce::from_slice(nonce.0.as_ref());
|
||||||
let mut buffer = buffer.write().unwrap();
|
let mut buffer = buffer.write();
|
||||||
let buffer: &mut Vec<u8> = buffer.as_mut();
|
let buffer: &mut Vec<u8> = buffer.as_mut();
|
||||||
cipher.decrypt_in_place(nonce, associated_data, buffer)
|
cipher.decrypt_in_place(nonce, associated_data, buffer)
|
||||||
}
|
}
|
||||||
@@ -119,7 +113,7 @@ impl KeyCell {
|
|||||||
associated_data: &[u8],
|
associated_data: &[u8],
|
||||||
plaintext: impl AsRef<[u8]>,
|
plaintext: impl AsRef<[u8]>,
|
||||||
) -> Result<Vec<u8>, Error> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
let key_reader = self.0.read().unwrap();
|
let key_reader = self.0.read();
|
||||||
let key_ref = key_reader.deref();
|
let key_ref = key_reader.deref();
|
||||||
let mut cipher = XChaCha20Poly1305::new(key_ref);
|
let mut cipher = XChaCha20Poly1305::new(key_ref);
|
||||||
let nonce = XNonce::from_slice(nonce.0.as_ref());
|
let nonce = XNonce::from_slice(nonce.0.as_ref());
|
||||||
@@ -146,19 +140,18 @@ pub fn generate_salt() -> Salt {
|
|||||||
|
|
||||||
/// User password might be of different length, have not enough entropy, etc...
|
/// User password might be of different length, have not enough entropy, etc...
|
||||||
/// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation.
|
/// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation.
|
||||||
pub fn derive_seal_key(mut password: MemSafe<Vec<u8>>, salt: &Salt) -> KeyCell {
|
pub fn derive_seal_key(mut password: SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
||||||
let params = argon2::Params::new(262_144, 3, 4, None).unwrap();
|
let params = argon2::Params::new(262_144, 3, 4, None).unwrap();
|
||||||
let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params);
|
let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params);
|
||||||
let mut key = MemSafe::new(Key::default()).unwrap();
|
let mut key = SafeCell::new(Key::default());
|
||||||
{
|
password.read_inline(|password_source| {
|
||||||
let password_source = password.read().unwrap();
|
let mut key_buffer = key.write();
|
||||||
let mut key_buffer = key.write().unwrap();
|
|
||||||
let key_buffer: &mut [u8] = key_buffer.as_mut();
|
let key_buffer: &mut [u8] = key_buffer.as_mut();
|
||||||
|
|
||||||
hasher
|
hasher
|
||||||
.hash_password_into(password_source.deref(), salt, key_buffer)
|
.hash_password_into(password_source.deref(), salt, key_buffer)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
});
|
||||||
|
|
||||||
key.into()
|
key.into()
|
||||||
}
|
}
|
||||||
@@ -166,20 +159,20 @@ pub fn derive_seal_key(mut password: MemSafe<Vec<u8>>, salt: &Salt) -> KeyCell {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use memsafe::MemSafe;
|
use crate::safe_cell::SafeCell;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn derive_seal_key_deterministic() {
|
pub fn derive_seal_key_deterministic() {
|
||||||
static PASSWORD: &[u8] = b"password";
|
static PASSWORD: &[u8] = b"password";
|
||||||
let password = MemSafe::new(PASSWORD.to_vec()).unwrap();
|
let password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let password2 = MemSafe::new(PASSWORD.to_vec()).unwrap();
|
let password2 = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key1 = derive_seal_key(password, &salt);
|
let mut key1 = derive_seal_key(password, &salt);
|
||||||
let mut key2 = derive_seal_key(password2, &salt);
|
let mut key2 = derive_seal_key(password2, &salt);
|
||||||
|
|
||||||
let key1_reader = key1.0.read().unwrap();
|
let key1_reader = key1.0.read();
|
||||||
let key2_reader = key2.0.read().unwrap();
|
let key2_reader = key2.0.read();
|
||||||
|
|
||||||
assert_eq!(key1_reader.deref(), key2_reader.deref());
|
assert_eq!(key1_reader.deref(), key2_reader.deref());
|
||||||
}
|
}
|
||||||
@@ -187,11 +180,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn successful_derive() {
|
pub fn successful_derive() {
|
||||||
static PASSWORD: &[u8] = b"password";
|
static PASSWORD: &[u8] = b"password";
|
||||||
let password = MemSafe::new(PASSWORD.to_vec()).unwrap();
|
let password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key = derive_seal_key(password, &salt);
|
let mut key = derive_seal_key(password, &salt);
|
||||||
let key_reader = key.0.read().unwrap();
|
let key_reader = key.0.read();
|
||||||
let key_ref = key_reader.deref();
|
let key_ref = key_reader.deref();
|
||||||
|
|
||||||
assert_ne!(key_ref.as_slice(), &[0u8; 32][..]);
|
assert_ne!(key_ref.as_slice(), &[0u8; 32][..]);
|
||||||
@@ -200,7 +193,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn encrypt_decrypt() {
|
pub fn encrypt_decrypt() {
|
||||||
static PASSWORD: &[u8] = b"password";
|
static PASSWORD: &[u8] = b"password";
|
||||||
let password = MemSafe::new(PASSWORD.to_vec()).unwrap();
|
let password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key = derive_seal_key(password, &salt);
|
let mut key = derive_seal_key(password, &salt);
|
||||||
@@ -212,12 +205,12 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_ne!(buffer, b"secret data");
|
assert_ne!(buffer, b"secret data");
|
||||||
|
|
||||||
let mut buffer = MemSafe::new(buffer).unwrap();
|
let mut buffer = SafeCell::new(buffer);
|
||||||
|
|
||||||
key.decrypt_in_place(&nonce, associated_data, &mut buffer)
|
key.decrypt_in_place(&nonce, associated_data, &mut buffer)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let buffer = buffer.read().unwrap();
|
let buffer = buffer.read();
|
||||||
assert_eq!(*buffer, b"secret data");
|
assert_eq!(*buffer, b"secret data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ use diesel::{
|
|||||||
};
|
};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
use kameo::{Actor, Reply, messages};
|
use kameo::{Actor, Reply, messages};
|
||||||
use memsafe::MemSafe;
|
|
||||||
use strum::{EnumDiscriminants, IntoDiscriminant};
|
use strum::{EnumDiscriminants, IntoDiscriminant};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::db::{
|
use crate::safe_cell::SafeCell;
|
||||||
self,
|
use crate::{
|
||||||
models::{self, RootKeyHistory},
|
db::{
|
||||||
schema::{self},
|
self,
|
||||||
|
models::{self, RootKeyHistory},
|
||||||
|
schema::{self},
|
||||||
|
},
|
||||||
|
safe_cell::SafeCellHandle as _,
|
||||||
};
|
};
|
||||||
use encryption::v1::{self, KeyCell, Nonce};
|
use encryption::v1::{self, KeyCell, Nonce};
|
||||||
|
|
||||||
@@ -136,7 +139,7 @@ impl KeyHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn bootstrap(&mut self, seal_key_raw: MemSafe<Vec<u8>>) -> Result<(), Error> {
|
pub async fn bootstrap(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
|
||||||
if !matches!(self.state, State::Unbootstrapped) {
|
if !matches!(self.state, State::Unbootstrapped) {
|
||||||
return Err(Error::AlreadyBootstrapped);
|
return Err(Error::AlreadyBootstrapped);
|
||||||
}
|
}
|
||||||
@@ -148,16 +151,15 @@ impl KeyHolder {
|
|||||||
let root_key_nonce = v1::Nonce::default();
|
let root_key_nonce = v1::Nonce::default();
|
||||||
let data_encryption_nonce = v1::Nonce::default();
|
let data_encryption_nonce = v1::Nonce::default();
|
||||||
|
|
||||||
let root_key_ciphertext: Vec<u8> = {
|
let root_key_ciphertext: Vec<u8> = root_key.0.read_inline(|reader| {
|
||||||
let root_key_reader = root_key.0.read().unwrap();
|
let root_key_reader = reader.as_slice();
|
||||||
let root_key_reader = root_key_reader.as_slice();
|
|
||||||
seal_key
|
seal_key
|
||||||
.encrypt(&root_key_nonce, v1::ROOT_KEY_TAG, root_key_reader)
|
.encrypt(&root_key_nonce, v1::ROOT_KEY_TAG, root_key_reader)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
error!(?err, "Fatal bootstrap error");
|
error!(?err, "Fatal bootstrap error");
|
||||||
Error::Encryption(err)
|
Error::Encryption(err)
|
||||||
})?
|
})
|
||||||
};
|
})?;
|
||||||
|
|
||||||
let mut conn = self.db.get().await?;
|
let mut conn = self.db.get().await?;
|
||||||
|
|
||||||
@@ -199,7 +201,7 @@ impl KeyHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn try_unseal(&mut self, seal_key_raw: MemSafe<Vec<u8>>) -> Result<(), Error> {
|
pub async fn try_unseal(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
|
||||||
let State::Sealed {
|
let State::Sealed {
|
||||||
root_key_history_id,
|
root_key_history_id,
|
||||||
} = &self.state
|
} = &self.state
|
||||||
@@ -225,7 +227,7 @@ impl KeyHolder {
|
|||||||
})?;
|
})?;
|
||||||
let mut seal_key = v1::derive_seal_key(seal_key_raw, &salt);
|
let mut seal_key = v1::derive_seal_key(seal_key_raw, &salt);
|
||||||
|
|
||||||
let mut root_key = MemSafe::new(current_key.ciphertext.clone()).unwrap();
|
let mut root_key = SafeCell::new(current_key.ciphertext.clone());
|
||||||
|
|
||||||
let nonce = v1::Nonce::try_from(current_key.root_key_encryption_nonce.as_slice()).map_err(
|
let nonce = v1::Nonce::try_from(current_key.root_key_encryption_nonce.as_slice()).map_err(
|
||||||
|_| {
|
|_| {
|
||||||
@@ -256,7 +258,7 @@ impl KeyHolder {
|
|||||||
|
|
||||||
// Decrypts the `aead_encrypted` entry with the given ID and returns the plaintext
|
// Decrypts the `aead_encrypted` entry with the given ID and returns the plaintext
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn decrypt(&mut self, aead_id: i32) -> Result<MemSafe<Vec<u8>>, Error> {
|
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
|
||||||
let State::Unsealed { root_key, .. } = &mut self.state else {
|
let State::Unsealed { root_key, .. } = &mut self.state else {
|
||||||
return Err(Error::NotBootstrapped);
|
return Err(Error::NotBootstrapped);
|
||||||
};
|
};
|
||||||
@@ -279,14 +281,14 @@ impl KeyHolder {
|
|||||||
);
|
);
|
||||||
Error::BrokenDatabase
|
Error::BrokenDatabase
|
||||||
})?;
|
})?;
|
||||||
let mut output = MemSafe::new(row.ciphertext).unwrap();
|
let mut output = SafeCell::new(row.ciphertext);
|
||||||
root_key.decrypt_in_place(&nonce, v1::TAG, &mut output)?;
|
root_key.decrypt_in_place(&nonce, v1::TAG, &mut output)?;
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: MemSafe<Vec<u8>>) -> Result<i32, Error> {
|
pub async fn create_new(&mut self, mut plaintext: SafeCell<Vec<u8>>) -> Result<i32, Error> {
|
||||||
let State::Unsealed {
|
let State::Unsealed {
|
||||||
root_key,
|
root_key,
|
||||||
root_key_history_id,
|
root_key_history_id,
|
||||||
@@ -299,7 +301,7 @@ impl KeyHolder {
|
|||||||
// Borrow checker note: &mut borrow a few lines above is disjoint from this field
|
// Borrow checker note: &mut borrow a few lines above is disjoint from this field
|
||||||
let nonce = Self::get_new_nonce(&self.db, *root_key_history_id).await?;
|
let nonce = Self::get_new_nonce(&self.db, *root_key_history_id).await?;
|
||||||
|
|
||||||
let mut ciphertext_buffer = plaintext.write().unwrap();
|
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, v1::TAG, &mut *ciphertext_buffer)?;
|
||||||
|
|
||||||
@@ -348,15 +350,17 @@ mod tests {
|
|||||||
use diesel::SelectableHelper;
|
use diesel::SelectableHelper;
|
||||||
|
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use memsafe::MemSafe;
|
|
||||||
|
|
||||||
use crate::db::{self};
|
use crate::{
|
||||||
|
db::{self},
|
||||||
|
safe_cell::SafeCell,
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
async fn bootstrapped_actor(db: &db::DatabasePool) -> KeyHolder {
|
async fn bootstrapped_actor(db: &db::DatabasePool) -> KeyHolder {
|
||||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||||
let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||||
actor.bootstrap(seal_key).await.unwrap();
|
actor.bootstrap(seal_key).await.unwrap();
|
||||||
actor
|
actor
|
||||||
}
|
}
|
||||||
@@ -391,7 +395,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(MemSafe::new(b"post-interleave".to_vec()).unwrap())
|
.create_new(SafeCell::new(b"post-interleave".to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let row: models::AeadEncrypted = schema::aead_encrypted::table
|
let row: models::AeadEncrypted = schema::aead_encrypted::table
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
use chacha20poly1305::aead::KeyInit;
|
|
||||||
use ed25519_dalek::VerifyingKey;
|
use ed25519_dalek::VerifyingKey;
|
||||||
use kameo::{Actor, messages, prelude::Context};
|
use kameo::{Actor, messages, prelude::Context};
|
||||||
use tokio::{select, sync::watch};
|
use tokio::{select, sync::watch};
|
||||||
|
|||||||
@@ -2,21 +2,25 @@ use std::{ops::DerefMut, sync::Mutex};
|
|||||||
|
|
||||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||||
use kameo::error::SendError;
|
use kameo::error::SendError;
|
||||||
use memsafe::MemSafe;
|
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
use crate::actors::{
|
use crate::safe_cell::SafeCell;
|
||||||
evm::{Generate, ListWallets, UseragentListGrants},
|
use crate::{
|
||||||
evm::{UseragentCreateGrant, UseragentDeleteGrant},
|
actors::{
|
||||||
keyholder::{self, Bootstrap, TryUnseal},
|
evm::{
|
||||||
user_agent::{
|
Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants,
|
||||||
BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState,
|
},
|
||||||
session::{
|
keyholder::{self, Bootstrap, TryUnseal},
|
||||||
UserAgentSession,
|
user_agent::{
|
||||||
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState,
|
||||||
|
session::{
|
||||||
|
UserAgentSession,
|
||||||
|
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
safe_cell::SafeCellHandle as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
@@ -93,19 +97,17 @@ impl UserAgentSession {
|
|||||||
nonce: &[u8],
|
nonce: &[u8],
|
||||||
ciphertext: &[u8],
|
ciphertext: &[u8],
|
||||||
associated_data: &[u8],
|
associated_data: &[u8],
|
||||||
) -> Result<MemSafe<Vec<u8>>, ()> {
|
) -> Result<SafeCell<Vec<u8>>, ()> {
|
||||||
let nonce = XNonce::from_slice(nonce);
|
let nonce = XNonce::from_slice(nonce);
|
||||||
|
|
||||||
let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key);
|
let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key);
|
||||||
let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into());
|
let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into());
|
||||||
|
|
||||||
let mut key_buffer = MemSafe::new(ciphertext.to_vec()).unwrap();
|
let mut key_buffer = SafeCell::new(ciphertext.to_vec());
|
||||||
|
|
||||||
let decryption_result = {
|
let decryption_result = key_buffer.write_inline(|write_handle| {
|
||||||
let mut write_handle = key_buffer.write().unwrap();
|
|
||||||
let write_handle = write_handle.deref_mut();
|
|
||||||
cipher.decrypt_in_place(nonce, associated_data, write_handle)
|
cipher.decrypt_in_place(nonce, associated_data, write_handle)
|
||||||
};
|
});
|
||||||
|
|
||||||
match decryption_result {
|
match decryption_result {
|
||||||
Ok(_) => Ok(key_buffer),
|
Ok(_) => Ok(key_buffer),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::safe_cell::{SafeCell, SafeCellHandle as _};
|
||||||
use alloy::{
|
use alloy::{
|
||||||
consensus::SignableTransaction,
|
consensus::SignableTransaction,
|
||||||
network::{TxSigner, TxSignerSync},
|
network::{TxSigner, TxSignerSync},
|
||||||
@@ -8,7 +9,6 @@ use alloy::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use k256::ecdsa::{self, RecoveryId, SigningKey, signature::hazmat::PrehashSigner};
|
use k256::ecdsa::{self, RecoveryId, SigningKey, signature::hazmat::PrehashSigner};
|
||||||
use memsafe::MemSafe;
|
|
||||||
|
|
||||||
/// An Ethereum signer that stores its secp256k1 secret key inside a
|
/// An Ethereum signer that stores its secp256k1 secret key inside a
|
||||||
/// hardware-protected [`MemSafe`] cell.
|
/// hardware-protected [`MemSafe`] cell.
|
||||||
@@ -20,7 +20,7 @@ use memsafe::MemSafe;
|
|||||||
/// Because [`MemSafe::read`] requires `&mut self` while the [`Signer`] trait
|
/// Because [`MemSafe::read`] requires `&mut self` while the [`Signer`] trait
|
||||||
/// requires `&self`, the cell is wrapped in a [`Mutex`].
|
/// requires `&self`, the cell is wrapped in a [`Mutex`].
|
||||||
pub struct SafeSigner {
|
pub struct SafeSigner {
|
||||||
key: Mutex<MemSafe<SigningKey>>,
|
key: Mutex<SafeCell<SigningKey>>,
|
||||||
address: Address,
|
address: Address,
|
||||||
chain_id: Option<ChainId>,
|
chain_id: Option<ChainId>,
|
||||||
}
|
}
|
||||||
@@ -42,14 +42,13 @@ impl std::fmt::Debug for SafeSigner {
|
|||||||
/// rejection, but we retry to be correct).
|
/// rejection, but we retry to be correct).
|
||||||
///
|
///
|
||||||
/// Returns the protected key bytes and the derived Ethereum address.
|
/// Returns the protected key bytes and the derived Ethereum address.
|
||||||
pub fn generate(rng: &mut impl rand::Rng) -> (MemSafe<[u8; 32]>, Address) {
|
pub fn generate(rng: &mut impl rand::Rng) -> (SafeCell<[u8; 32]>, Address) {
|
||||||
loop {
|
loop {
|
||||||
let mut cell = MemSafe::new([0u8; 32]).expect("MemSafe allocation");
|
let mut cell = SafeCell::new_inline(|w: &mut [u8; 32]| {
|
||||||
{
|
rng.fill_bytes(w);
|
||||||
let mut w = cell.write().expect("MemSafe write");
|
});
|
||||||
rng.fill_bytes(w.as_mut());
|
|
||||||
}
|
let reader = cell.read();
|
||||||
let reader = cell.read().expect("MemSafe read");
|
|
||||||
if let Ok(sk) = SigningKey::from_slice(reader.as_ref()) {
|
if let Ok(sk) = SigningKey::from_slice(reader.as_ref()) {
|
||||||
let address = secret_key_to_address(&sk);
|
let address = secret_key_to_address(&sk);
|
||||||
drop(reader);
|
drop(reader);
|
||||||
@@ -64,8 +63,8 @@ impl SafeSigner {
|
|||||||
/// The key bytes are read from protected memory, parsed as a secp256k1
|
/// The key bytes are read from protected memory, parsed as a secp256k1
|
||||||
/// scalar, and immediately moved into a new [`MemSafe`] cell. The raw
|
/// scalar, and immediately moved into a new [`MemSafe`] cell. The raw
|
||||||
/// bytes are never exposed outside this function.
|
/// bytes are never exposed outside this function.
|
||||||
pub fn from_memsafe(mut cell: MemSafe<Vec<u8>>) -> Result<Self> {
|
pub fn from_cell(mut cell: SafeCell<Vec<u8>>) -> Result<Self> {
|
||||||
let reader = cell.read().map_err(Error::other)?;
|
let reader = cell.read();
|
||||||
let sk = SigningKey::from_slice(reader.as_slice()).map_err(Error::other)?;
|
let sk = SigningKey::from_slice(reader.as_slice()).map_err(Error::other)?;
|
||||||
drop(reader);
|
drop(reader);
|
||||||
Self::new(sk)
|
Self::new(sk)
|
||||||
@@ -75,7 +74,7 @@ impl SafeSigner {
|
|||||||
/// memory region.
|
/// memory region.
|
||||||
pub fn new(key: SigningKey) -> Result<Self> {
|
pub fn new(key: SigningKey) -> Result<Self> {
|
||||||
let address = secret_key_to_address(&key);
|
let address = secret_key_to_address(&key);
|
||||||
let cell = MemSafe::new(key).map_err(Error::other)?;
|
let cell = SafeCell::new(key);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
key: Mutex::new(cell),
|
key: Mutex::new(cell),
|
||||||
address,
|
address,
|
||||||
@@ -85,7 +84,7 @@ impl SafeSigner {
|
|||||||
|
|
||||||
fn sign_hash_inner(&self, hash: &B256) -> Result<Signature> {
|
fn sign_hash_inner(&self, hash: &B256) -> Result<Signature> {
|
||||||
let mut cell = self.key.lock().expect("SafeSigner mutex poisoned");
|
let mut cell = self.key.lock().expect("SafeSigner mutex poisoned");
|
||||||
let reader = cell.read().map_err(Error::other)?;
|
let reader = cell.read();
|
||||||
let sig: (ecdsa::Signature, RecoveryId) = reader.sign_prehash(hash.as_ref())?;
|
let sig: (ecdsa::Signature, RecoveryId) = reader.sign_prehash(hash.as_ref())?;
|
||||||
Ok(sig.into())
|
Ok(sig.into())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use arbiter_proto::{
|
|||||||
EvmGrantCreateRequest, EvmGrantCreateResponse, EvmGrantDeleteRequest,
|
EvmGrantCreateRequest, EvmGrantCreateResponse, EvmGrantDeleteRequest,
|
||||||
EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse, GrantEntry,
|
EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse, GrantEntry,
|
||||||
SharedSettings as ProtoSharedSettings, SpecificGrant as ProtoSpecificGrant,
|
SharedSettings as ProtoSharedSettings, SpecificGrant as ProtoSpecificGrant,
|
||||||
SpecificGrant as ProtoGrantSpecificGrant,
|
|
||||||
TokenTransferSettings as ProtoTokenTransferSettings,
|
TokenTransferSettings as ProtoTokenTransferSettings,
|
||||||
VolumeRateLimit as ProtoVolumeRateLimit, WalletCreateResponse, WalletEntry, WalletList,
|
VolumeRateLimit as ProtoVolumeRateLimit, WalletCreateResponse, WalletEntry, WalletList,
|
||||||
WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult,
|
WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult,
|
||||||
@@ -42,7 +41,6 @@ use crate::{
|
|||||||
TransportResponseError, UnsealError, VaultState,
|
TransportResponseError, UnsealError, VaultState,
|
||||||
},
|
},
|
||||||
evm::{
|
evm::{
|
||||||
self,
|
|
||||||
policies::{Grant, SpecificGrant},
|
policies::{Grant, SpecificGrant},
|
||||||
policies::{
|
policies::{
|
||||||
SharedGrantSettings, TransactionRateLimit, VolumeRateLimit, ether_transfer,
|
SharedGrantSettings, TransactionRateLimit, VolumeRateLimit, ether_transfer,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub mod context;
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod evm;
|
pub mod evm;
|
||||||
pub mod grpc;
|
pub mod grpc;
|
||||||
|
pub mod safe_cell;
|
||||||
|
|
||||||
const DEFAULT_CHANNEL_SIZE: usize = 1000;
|
const DEFAULT_CHANNEL_SIZE: usize = 1000;
|
||||||
|
|
||||||
@@ -25,4 +26,3 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
111
server/crates/arbiter-server/src/safe_cell.rs
Normal file
111
server/crates/arbiter-server/src/safe_cell.rs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::{any::type_name, fmt};
|
||||||
|
|
||||||
|
use memsafe::MemSafe;
|
||||||
|
|
||||||
|
pub trait SafeCellHandle<T> {
|
||||||
|
type CellRead<'a>: Deref<Target = T>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
T: 'a;
|
||||||
|
type CellWrite<'a>: Deref<Target = T> + DerefMut<Target = T>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
T: 'a;
|
||||||
|
|
||||||
|
fn new(value: T) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
fn read(&mut self) -> Self::CellRead<'_>;
|
||||||
|
fn write(&mut self) -> Self::CellWrite<'_>;
|
||||||
|
|
||||||
|
fn new_inline<F>(f: F) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
T: Default,
|
||||||
|
F: for<'a> FnOnce(&'a mut T),
|
||||||
|
{
|
||||||
|
let mut cell = Self::new(T::default());
|
||||||
|
{
|
||||||
|
let mut handle = cell.write();
|
||||||
|
f(handle.deref_mut());
|
||||||
|
}
|
||||||
|
cell
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_inline<F, R>(&mut self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&T) -> R,
|
||||||
|
{
|
||||||
|
f(&*self.read())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_inline<F, R>(&mut self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> R,
|
||||||
|
{
|
||||||
|
f(&mut *self.write())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemSafeCell<T>(MemSafe<T>);
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for MemSafeCell<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("MemSafeCell")
|
||||||
|
.field("inner", &format_args!("<protected {}>", type_name::<T>()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SafeCellHandle<T> for MemSafeCell<T> {
|
||||||
|
type CellRead<'a>
|
||||||
|
= memsafe::MemSafeRead<'a, T>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
T: 'a;
|
||||||
|
type CellWrite<'a>
|
||||||
|
= memsafe::MemSafeWrite<'a, T>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
T: 'a;
|
||||||
|
|
||||||
|
fn new(value: T) -> Self {
|
||||||
|
match MemSafe::new(value) {
|
||||||
|
Ok(inner) => Self(inner),
|
||||||
|
Err(err) => {
|
||||||
|
// If protected memory cannot be allocated, process integrity is compromised.
|
||||||
|
abort_memory_breach("safe cell allocation", &err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read(&mut self) -> Self::CellRead<'_> {
|
||||||
|
match self.0.read() {
|
||||||
|
Ok(inner) => inner,
|
||||||
|
Err(err) => abort_memory_breach("safe cell read", &err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn write(&mut self) -> Self::CellWrite<'_> {
|
||||||
|
match self.0.write() {
|
||||||
|
Ok(inner) => inner,
|
||||||
|
Err(err) => {
|
||||||
|
// If protected memory becomes unwritable here, treat it as a fatal memory breach.
|
||||||
|
abort_memory_breach("safe cell write", &err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abort_memory_breach(action: &str, err: &memsafe::error::MemoryError) -> ! {
|
||||||
|
eprintln!("fatal {action}: {err}");
|
||||||
|
std::process::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SafeCell<T> = MemSafeCell<T>;
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
use arbiter_proto::transport::{Bi, Error};
|
use arbiter_proto::transport::{Bi, Error};
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::KeyHolder,
|
actors::keyholder::KeyHolder,
|
||||||
db::{self, schema},
|
db::{self, schema}, safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use diesel::QueryDsl;
|
use diesel::QueryDsl;
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use memsafe::MemSafe;
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn bootstrapped_keyholder(db: &db::DatabasePool) -> KeyHolder {
|
pub async fn bootstrapped_keyholder(db: &db::DatabasePool) -> KeyHolder {
|
||||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||||
actor
|
actor
|
||||||
.bootstrap(MemSafe::new(b"test-seal-key".to_vec()).unwrap())
|
.bootstrap(SafeCell::new(b"test-seal-key".to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
actor
|
actor
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::{CreateNew, Error, KeyHolder},
|
actors::keyholder::{CreateNew, Error, KeyHolder},
|
||||||
db::{self, models, schema},
|
db::{self, models, schema},
|
||||||
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::sql_query};
|
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::sql_query};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use kameo::actor::{ActorRef, Spawn as _};
|
use kameo::actor::{ActorRef, Spawn as _};
|
||||||
use memsafe::MemSafe;
|
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
use crate::common;
|
use crate::common;
|
||||||
@@ -24,7 +24,7 @@ async fn write_concurrently(
|
|||||||
let plaintext = format!("{prefix}-{i}").into_bytes();
|
let plaintext = format!("{prefix}-{i}").into_bytes();
|
||||||
let id = actor
|
let id = actor
|
||||||
.ask(CreateNew {
|
.ask(CreateNew {
|
||||||
plaintext: MemSafe::new(plaintext.clone()).unwrap(),
|
plaintext: SafeCell::new(plaintext.clone()),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -118,7 +118,7 @@ async fn insert_failure_does_not_create_partial_row() {
|
|||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let err = actor
|
let err = actor
|
||||||
.create_new(MemSafe::new(b"should fail".to_vec()).unwrap())
|
.create_new(SafeCell::new(b"should fail".to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, Error::DatabaseTransaction(_)));
|
assert!(matches!(err, Error::DatabaseTransaction(_)));
|
||||||
@@ -162,12 +162,12 @@ async fn decrypt_roundtrip_after_high_concurrency() {
|
|||||||
|
|
||||||
let mut decryptor = KeyHolder::new(db.clone()).await.unwrap();
|
let mut decryptor = KeyHolder::new(db.clone()).await.unwrap();
|
||||||
decryptor
|
decryptor
|
||||||
.try_unseal(MemSafe::new(b"test-seal-key".to_vec()).unwrap())
|
.try_unseal(SafeCell::new(b"test-seal-key".to_vec()))
|
||||||
.await
|
.await
|
||||||
.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).await.unwrap();
|
||||||
assert_eq!(*decrypted.read().unwrap(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::{Error, KeyHolder},
|
actors::keyholder::{Error, KeyHolder},
|
||||||
db::{self, models, schema},
|
db::{self, models, schema},
|
||||||
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use diesel::{QueryDsl, SelectableHelper};
|
use diesel::{QueryDsl, SelectableHelper};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use memsafe::MemSafe;
|
|
||||||
|
|
||||||
use crate::common;
|
use crate::common;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ async fn test_bootstrap() {
|
|||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||||
|
|
||||||
let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||||
actor.bootstrap(seal_key).await.unwrap();
|
actor.bootstrap(seal_key).await.unwrap();
|
||||||
|
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
@@ -43,7 +43,7 @@ async fn test_bootstrap_rejects_double() {
|
|||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let mut actor = common::bootstrapped_keyholder(&db).await;
|
let mut actor = common::bootstrapped_keyholder(&db).await;
|
||||||
|
|
||||||
let seal_key2 = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
let seal_key2 = SafeCell::new(b"test-seal-key".to_vec());
|
||||||
let err = actor.bootstrap(seal_key2).await.unwrap_err();
|
let err = actor.bootstrap(seal_key2).await.unwrap_err();
|
||||||
assert!(matches!(err, Error::AlreadyBootstrapped));
|
assert!(matches!(err, Error::AlreadyBootstrapped));
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ async fn test_create_new_before_bootstrap_fails() {
|
|||||||
let mut actor = KeyHolder::new(db).await.unwrap();
|
let mut actor = KeyHolder::new(db).await.unwrap();
|
||||||
|
|
||||||
let err = actor
|
let err = actor
|
||||||
.create_new(MemSafe::new(b"data".to_vec()).unwrap())
|
.create_new(SafeCell::new(b"data".to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, Error::NotBootstrapped));
|
assert!(matches!(err, Error::NotBootstrapped));
|
||||||
@@ -91,17 +91,17 @@ async fn test_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(MemSafe::new(plaintext.to_vec()).unwrap())
|
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
drop(actor);
|
drop(actor);
|
||||||
|
|
||||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||||
let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
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).await.unwrap();
|
||||||
assert_eq!(*decrypted.read().unwrap(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -112,20 +112,20 @@ async fn test_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(MemSafe::new(plaintext.to_vec()).unwrap())
|
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
drop(actor);
|
drop(actor);
|
||||||
|
|
||||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||||
|
|
||||||
let bad_key = MemSafe::new(b"wrong-password".to_vec()).unwrap();
|
let bad_key = SafeCell::new(b"wrong-password".to_vec());
|
||||||
let err = actor.try_unseal(bad_key).await.unwrap_err();
|
let err = actor.try_unseal(bad_key).await.unwrap_err();
|
||||||
assert!(matches!(err, Error::InvalidKey));
|
assert!(matches!(err, Error::InvalidKey));
|
||||||
|
|
||||||
let good_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
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).await.unwrap();
|
||||||
assert_eq!(*decrypted.read().unwrap(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use std::collections::HashSet;
|
|||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::{Error, encryption::v1},
|
actors::keyholder::{Error, encryption::v1},
|
||||||
db::{self, models, schema},
|
db::{self, models, schema},
|
||||||
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::update};
|
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::update};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use memsafe::MemSafe;
|
|
||||||
|
|
||||||
use crate::common;
|
use crate::common;
|
||||||
|
|
||||||
@@ -18,12 +18,12 @@ async fn test_create_decrypt_roundtrip() {
|
|||||||
|
|
||||||
let plaintext = b"hello arbiter";
|
let plaintext = b"hello arbiter";
|
||||||
let aead_id = actor
|
let aead_id = actor
|
||||||
.create_new(MemSafe::new(plaintext.to_vec()).unwrap())
|
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut decrypted = actor.decrypt(aead_id).await.unwrap();
|
let mut decrypted = actor.decrypt(aead_id).await.unwrap();
|
||||||
assert_eq!(*decrypted.read().unwrap(), plaintext);
|
assert_eq!(*decrypted.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -44,11 +44,11 @@ async fn test_ciphertext_differs_across_entries() {
|
|||||||
|
|
||||||
let plaintext = b"same content";
|
let plaintext = b"same content";
|
||||||
let id1 = actor
|
let id1 = actor
|
||||||
.create_new(MemSafe::new(plaintext.to_vec()).unwrap())
|
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let id2 = actor
|
let id2 = actor
|
||||||
.create_new(MemSafe::new(plaintext.to_vec()).unwrap())
|
.create_new(SafeCell::new(plaintext.to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -70,8 +70,8 @@ async fn test_ciphertext_differs_across_entries() {
|
|||||||
|
|
||||||
let mut d1 = actor.decrypt(id1).await.unwrap();
|
let mut d1 = actor.decrypt(id1).await.unwrap();
|
||||||
let mut d2 = actor.decrypt(id2).await.unwrap();
|
let mut d2 = actor.decrypt(id2).await.unwrap();
|
||||||
assert_eq!(*d1.read().unwrap(), plaintext);
|
assert_eq!(*d1.read(), plaintext);
|
||||||
assert_eq!(*d2.read().unwrap(), plaintext);
|
assert_eq!(*d2.read(), plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -83,7 +83,7 @@ async fn test_nonce_never_reused() {
|
|||||||
let n = 5;
|
let n = 5;
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
actor
|
actor
|
||||||
.create_new(MemSafe::new(format!("secret {i}").into_bytes()).unwrap())
|
.create_new(SafeCell::new(format!("secret {i}").into_bytes()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ async fn broken_db_nonce_format_fails_closed() {
|
|||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let err = actor
|
let err = actor
|
||||||
.create_new(MemSafe::new(b"must fail".to_vec()).unwrap())
|
.create_new(SafeCell::new(b"must fail".to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, Error::BrokenDatabase));
|
assert!(matches!(err, Error::BrokenDatabase));
|
||||||
@@ -145,7 +145,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_keyholder(&db).await;
|
let mut actor = common::bootstrapped_keyholder(&db).await;
|
||||||
let id = actor
|
let id = actor
|
||||||
.create_new(MemSafe::new(b"decrypt target".to_vec()).unwrap())
|
.create_new(SafeCell::new(b"decrypt target".to_vec()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use arbiter_server::{
|
|||||||
user_agent::{Request, Response, UnsealError, session::UserAgentSession},
|
user_agent::{Request, Response, UnsealError, session::UserAgentSession},
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||||
use memsafe::MemSafe;
|
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgentSession) {
|
async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgentSession) {
|
||||||
@@ -17,7 +17,7 @@ async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgen
|
|||||||
actors
|
actors
|
||||||
.key_holder
|
.key_holder
|
||||||
.ask(Bootstrap {
|
.ask(Bootstrap {
|
||||||
seal_key_raw: MemSafe::new(seal_key.to_vec()).unwrap(),
|
seal_key_raw: SafeCell::new(seal_key.to_vec()),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
0
server/rules/.gitkeep
Normal file
0
server/rules/.gitkeep
Normal file
10
server/rules/safecell/new-inline.yaml
Normal file
10
server/rules/safecell/new-inline.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
id: safecell-new-inline
|
||||||
|
language: Rust
|
||||||
|
rule:
|
||||||
|
pattern: $CELL.write_inline(|$W| $BODY);
|
||||||
|
follows:
|
||||||
|
pattern: let mut $CELL = SafeCell::new($INIT);
|
||||||
|
fix:
|
||||||
|
template: let mut $CELL = SafeCell::new_inline(|$W| $BODY);
|
||||||
|
expandStart:
|
||||||
|
pattern: let mut $CELL = SafeCell::new($INIT)
|
||||||
17
server/rules/safecell/read-inline.yaml
Normal file
17
server/rules/safecell/read-inline.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
id: safecell-read-inline
|
||||||
|
language: Rust
|
||||||
|
rule:
|
||||||
|
pattern:
|
||||||
|
context: |
|
||||||
|
{
|
||||||
|
let $READ = $CELL.read();
|
||||||
|
$$$BODY
|
||||||
|
}
|
||||||
|
selector: block
|
||||||
|
inside:
|
||||||
|
kind: block
|
||||||
|
fix:
|
||||||
|
template: |
|
||||||
|
$CELL.read_inline(|$READ| {
|
||||||
|
$$$BODY
|
||||||
|
});
|
||||||
13
server/rules/safecell/write-inline.yaml
Normal file
13
server/rules/safecell/write-inline.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
id: safecell-write-inline
|
||||||
|
language: Rust
|
||||||
|
rule:
|
||||||
|
pattern: |
|
||||||
|
{
|
||||||
|
let mut $WRITE = $CELL.write();
|
||||||
|
$$$BODY
|
||||||
|
}
|
||||||
|
fix:
|
||||||
|
template: |
|
||||||
|
$CELL.write_inline(|$WRITE| {
|
||||||
|
$$$BODY
|
||||||
|
});
|
||||||
2
server/sgconfig.yml
Normal file
2
server/sgconfig.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ruleDirs:
|
||||||
|
- ./rules
|
||||||
Reference in New Issue
Block a user