feat(server::crypto): add Shamir secret sharing utilities
Wraps vsss_rs Gf256::split_array / combine_array into thin split_key / combine_shares helpers. Also widens derive_key salt parameter from &[u8;16] to &[u8] to accommodate the 32-byte share salts.
This commit is contained in:
@@ -61,12 +61,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn derive_seal_key_deterministic() {
|
fn derive_seal_key_deterministic() {
|
||||||
static PASSWORD: &[u8] = b"password";
|
static PASSWORD: &[u8] = b"password";
|
||||||
let password = SafeCell::new(PASSWORD.to_vec());
|
let mut password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let password2 = SafeCell::new(PASSWORD.to_vec());
|
let mut password2 = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key1 = derive_key(password, &salt);
|
let mut key1 = derive_key(&mut password, &salt);
|
||||||
let mut key2 = derive_key(password2, &salt);
|
let mut key2 = derive_key(&mut password2, &salt);
|
||||||
|
|
||||||
let key1_reader = key1.0.read();
|
let key1_reader = key1.0.read();
|
||||||
let key2_reader = key2.0.read();
|
let key2_reader = key2.0.read();
|
||||||
@@ -77,10 +77,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn successful_derive() {
|
fn successful_derive() {
|
||||||
static PASSWORD: &[u8] = b"password";
|
static PASSWORD: &[u8] = b"password";
|
||||||
let password = SafeCell::new(PASSWORD.to_vec());
|
let mut password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key = derive_key(password, &salt);
|
let mut key = derive_key(&mut password, &salt);
|
||||||
let key_reader = key.0.read();
|
let key_reader = key.0.read();
|
||||||
|
|
||||||
assert_ne!(key_reader.as_slice(), &[0u8; 32][..]);
|
assert_ne!(key_reader.as_slice(), &[0u8; 32][..]);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
|
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
|
||||||
use encryption::v1::{Nonce, Salt};
|
use encryption::v1::Nonce;
|
||||||
|
|
||||||
use argon2::{Algorithm, Argon2};
|
use argon2::{Algorithm, Argon2};
|
||||||
use chacha20poly1305::{
|
use chacha20poly1305::{
|
||||||
@@ -13,6 +13,7 @@ use rand::{
|
|||||||
|
|
||||||
pub mod encryption;
|
pub mod encryption;
|
||||||
pub mod integrity;
|
pub mod integrity;
|
||||||
|
pub mod shamir;
|
||||||
|
|
||||||
pub struct KeyCell(pub SafeCell<Key>);
|
pub struct KeyCell(pub SafeCell<Key>);
|
||||||
impl From<SafeCell<Key>> for KeyCell {
|
impl From<SafeCell<Key>> for KeyCell {
|
||||||
@@ -94,7 +95,7 @@ impl KeyCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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_key(password: &mut SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
pub fn derive_key(password: &mut SafeCell<Vec<u8>>, salt: &[u8]) -> KeyCell {
|
||||||
let params = {
|
let params = {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
@@ -132,10 +133,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn encrypt_decrypt() {
|
fn encrypt_decrypt() {
|
||||||
static PASSWORD: &[u8] = b"password";
|
static PASSWORD: &[u8] = b"password";
|
||||||
let password = SafeCell::new(PASSWORD.to_vec());
|
let mut password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key = derive_key(password, &salt);
|
let mut key = derive_key(&mut password, &salt);
|
||||||
let nonce = Nonce(*b"unique nonce 123 1231233"); // 24 bytes for XChaCha20Poly1305
|
let nonce = Nonce(*b"unique nonce 123 1231233"); // 24 bytes for XChaCha20Poly1305
|
||||||
let associated_data = b"associated data";
|
let associated_data = b"associated data";
|
||||||
let mut buffer = b"secret data".to_vec();
|
let mut buffer = b"secret data".to_vec();
|
||||||
|
|||||||
27
server/crates/arbiter-server/src/crypto/shamir.rs
Normal file
27
server/crates/arbiter-server/src/crypto/shamir.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use vsss_rs::Gf256;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ShamirError {
|
||||||
|
#[error("Failed to split key: {0}")]
|
||||||
|
Split(String),
|
||||||
|
#[error("Failed to combine shares: {0}")]
|
||||||
|
Combine(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split `key` into `total` shares where any `threshold` shares can reconstruct it.
|
||||||
|
/// Each returned Vec<u8> is a share with format [`identifier_byte`, `value_bytes`...].
|
||||||
|
pub fn split_key(
|
||||||
|
threshold: usize,
|
||||||
|
total: usize,
|
||||||
|
key: &[u8],
|
||||||
|
rng: impl rand_core::RngCore + rand_core::CryptoRng,
|
||||||
|
) -> Result<Vec<Vec<u8>>, ShamirError> {
|
||||||
|
Gf256::split_array(threshold, total, key, rng)
|
||||||
|
.map_err(|e| ShamirError::Split(format!("{e:?}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reconstruct the secret from `threshold` or more shares.
|
||||||
|
pub fn combine_shares(shares: &[Vec<u8>]) -> Result<Vec<u8>, ShamirError> {
|
||||||
|
Gf256::combine_array(shares)
|
||||||
|
.map_err(|e| ShamirError::Combine(format!("{e:?}")))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user