Updated Crypto to use MTP-Crypto

This commit is contained in:
Alex Emmet 2026-07-03 06:28:09 +02:00
commit f0011a67cf
7 changed files with 358 additions and 371 deletions

View file

@ -8,24 +8,30 @@ use crate::notifications::tauri;
use crate::sql::sql::initialize_db;
use crate::sql::sql::print_users;
use crate::transport::omikron_connection;
use crate::util::crypto_helper::load_public_key;
use crate::util::crypto_helper::load_secret_key;
use crate::util::file_util::get_directory;
use crate::util::logger::PrintType;
use crate::util::logger::startup;
use base64::Engine as _;
use dotenv::from_path;
use mtp_crypto::{Keyring, PublicKeyBundle};
use once_cell::sync::Lazy;
use rustls::crypto::aws_lc_rs::default_provider;
use std::env;
use std::path::Path;
static PRIVATE_KEY: Lazy<String> = Lazy::new(|| env::var("PRIVATE_KEY").unwrap());
pub fn get_private_key() -> x448::Secret {
load_secret_key(&*PRIVATE_KEY).unwrap()
static KEYRING_ENV: Lazy<String> = Lazy::new(|| env::var("KEYRING").unwrap());
fn load_keyring() -> Keyring {
let bytes = base64::engine::general_purpose::STANDARD
.decode(&*KEYRING_ENV)
.expect("Invalid KEYRING env var: not valid base64");
Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize")
}
static PUBLIC_KEY: Lazy<String> = Lazy::new(|| env::var("PUBLIC_KEY").unwrap());
pub fn get_public_key() -> x448::PublicKey {
load_public_key(&*PUBLIC_KEY).unwrap()
pub fn get_keyring() -> &'static Keyring {
static KEYRING: Lazy<Keyring> = Lazy::new(load_keyring);
&KEYRING
}
pub fn get_public_key_bundle() -> PublicKeyBundle {
get_keyring().public_key_bundle()
}
#[tokio::main]

View file

@ -1,16 +1,16 @@
use crate::get_public_key;
use crate::get_public_key_bundle;
use crate::sql::sql;
use crate::sql::user_online_tracker::get_iota_primary_omikron_connection;
use crate::transport::omikron_manager::get_random_omikron;
use crate::util::file_util::get_directory;
use crate::{
sql::sql::{get_by_user_id, get_omikron_by_id},
util::crypto_helper::public_key_to_base64,
};
use actix_web::HttpResponse;
use actix_web::http::{StatusCode, header};
use base64::Engine as _;
use json::JsonValue;
use mtp_crypto::PublicKeyBundle;
pub async fn handle(path: &str, body_string: Option<String>) -> HttpResponse {
if path == "OPTIONS" {
@ -195,7 +195,8 @@ pub async fn handle(path: &str, body_string: Option<String>) -> HttpResponse {
["api", "get", "public_key"] => {
let mut res = JsonValue::new_object();
res["status"] = "success".into();
res["public_key"] = public_key_to_base64(&get_public_key()).into();
let bundle = get_public_key_bundle();
res["public_key"] = base64::engine::general_purpose::STANDARD.encode(bundle.to_bytes()).into();
(StatusCode::OK, res.dump())
}

View file

@ -1,137 +0,0 @@
use aes_gcm::{
Aes256Gcm, Nonce,
aead::{Aead, KeyInit, OsRng},
};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use rand_core::RngCore;
use sha2::{Digest, Sha256};
use x448::{PublicKey, Secret, SharedSecret};
/// Errors for crypto operations
#[derive(Debug)]
pub enum CryptoError {
Base64Decode,
AgreementError,
EncryptionError(aes_gcm::Error),
DecryptionError(aes_gcm::Error),
}
impl From<base64::DecodeError> for CryptoError {
fn from(_: base64::DecodeError) -> Self {
CryptoError::Base64Decode
}
}
pub fn generate_keypair() -> (Secret, PublicKey) {
let mut buf = [0u8; 56];
let mut rng = OsRng;
rng.fill_bytes(&mut buf);
let secret = Secret::from_bytes(&buf).unwrap();
let public = PublicKey::from(&secret);
(secret, public)
}
pub fn public_key_to_base64(pubkey: &PublicKey) -> String {
STANDARD.encode(pubkey.as_bytes().as_ref())
}
pub fn secret_key_to_base64(secret: &Secret) -> String {
STANDARD.encode(secret.as_bytes().as_ref())
}
pub fn load_public_key(base64_pub: &str) -> Option<PublicKey> {
let bytes = STANDARD.decode(base64_pub).unwrap();
PublicKey::from_bytes(&bytes)
}
pub fn load_secret_key(base64_secret: &str) -> Option<Secret> {
let bytes = STANDARD.decode(base64_secret).unwrap();
Secret::from_bytes(&bytes)
}
fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(shared.as_bytes());
let result = hasher.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(&result[..32]);
key
}
pub fn encrypt_b64(
base64_secret: &str,
base64_peer_pub: &str,
plaintext: &str,
) -> Result<String, CryptoError> {
let secret = load_secret_key(base64_secret).unwrap();
let peer_pub = load_public_key(base64_peer_pub).unwrap();
encrypt(secret, peer_pub, plaintext)
}
pub fn encrypt(
secret: Secret,
peer_pub: PublicKey,
plaintext: &str,
) -> Result<String, CryptoError> {
let shared = secret
.to_diffie_hellman(&peer_pub)
.ok_or(CryptoError::AgreementError)?;
let key_bytes = derive_aes_key(&shared);
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext.as_bytes())
.map_err(CryptoError::EncryptionError)?;
// prefix nonce to ciphertext
let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
out.extend_from_slice(&nonce_bytes);
out.extend_from_slice(&ciphertext);
Ok(STANDARD.encode(&out))
}
pub fn decrypt_b64(
base64_secret: &str,
base64_peer_pub: &str,
encrypted_base64: &str,
) -> Result<String, CryptoError> {
let secret = load_secret_key(base64_secret).unwrap();
let peer_pub = load_public_key(base64_peer_pub).unwrap();
decrypt(secret, peer_pub, encrypted_base64)
}
pub fn decrypt(
secret: Secret,
peer_pub: PublicKey,
encrypted_base64: &str,
) -> Result<String, CryptoError> {
let shared = secret
.to_diffie_hellman(&peer_pub)
.ok_or(CryptoError::AgreementError)?;
let key_bytes = derive_aes_key(&shared);
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
let encrypted = STANDARD.decode(encrypted_base64)?;
if encrypted.len() < 12 {
return Err(CryptoError::DecryptionError(aes_gcm::Error));
}
let nonce_bytes = &encrypted[..12];
let ciphertext = &encrypted[12..];
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext_bytes = cipher
.decrypt(nonce, ciphertext)
.map_err(CryptoError::DecryptionError)?;
let plaintext = String::from_utf8(plaintext_bytes)
.map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?;
Ok(plaintext)
}
pub fn hash_it(input: &str) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
hasher.finalize().to_vec()
}
pub fn hex_hash(input: &str) -> String {
let digest = hash_it(input);
digest.iter().map(|b| format!("{:02x}", b)).collect()
}

View file

@ -1,201 +0,0 @@
use aes_gcm::{
Aes256Gcm, Nonce,
aead::{Aead, KeyInit, Payload},
};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD};
use hkdf::Hkdf;
use sha2::{Digest, Sha256};
use x448::{PublicKey, Secret};
// --- Custom Errors ---
#[derive(Debug)]
pub enum SecurePayloadError {
InvalidBase64,
InvalidHex,
EncryptionError,
DecryptionError,
InvalidKeyLength,
}
// --- Data Format Enum ---
#[derive(Clone, Copy, Debug)]
pub enum DataFormat {
Raw,
Base64,
Hex,
}
// --- Main Class Structure ---
pub struct SecurePayload {
/// The internal canonical representation is always raw bytes.
inner_data: Vec<u8>,
/// The private key of the user associated with this payload instance.
private_key: Secret,
}
impl Clone for SecurePayload {
fn clone(&self) -> Self {
Self {
inner_data: self.inner_data.clone(),
private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(),
}
}
}
impl SecurePayload {
/// Clear Constructor: Takes data in any format and the user's private key.
pub fn new<S, T: AsRef<[u8]>>(
data: T,
format: DataFormat,
private_key: S,
) -> Result<Self, SecurePayloadError>
where
S: Into<Secret>,
{
let raw_data = match format {
DataFormat::Raw => data.as_ref().to_vec(),
DataFormat::Base64 => BASE64_STD
.decode(data.as_ref())
.map_err(|_| SecurePayloadError::InvalidBase64)?,
DataFormat::Hex => {
hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)?
}
};
Ok(Self {
inner_data: raw_data,
private_key: private_key.into(),
})
}
/// Helper to get the public key associated with this instance's private key.
pub fn get_public_key(&self) -> [u8; 56] {
*PublicKey::from(&self.private_key).as_bytes()
}
/// Exports the internal data to the requested format
pub fn export(&self, format: DataFormat) -> String {
match format.into() {
DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(),
DataFormat::Base64 => BASE64_STD.encode(&self.inner_data),
DataFormat::Hex => hex::encode(&self.inner_data),
}
}
/// Access raw bytes directly
pub fn get_bytes(&self) -> &[u8] {
&self.inner_data
}
/// Returns the SHA-256 Hash of the data in the requested format
pub fn get_hash(&self, format: DataFormat) -> String {
let mut hasher = Sha256::new();
hasher.update(&self.inner_data);
let result = hasher.finalize();
match format {
DataFormat::Raw => String::from_utf8_lossy(&result).to_string(),
DataFormat::Base64 => BASE64_STD.encode(result),
DataFormat::Hex => hex::encode(result),
}
}
/// Encrypts the held data for a specific recipient using AES-256-GCM.
/// The message will contain ONLY the ciphertext.
pub fn encrypt_x448<S>(&self, public_key: S) -> Result<SecurePayload, SecurePayloadError>
where
S: Into<PublicKey>,
{
let peer_pub = public_key.into();
let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap();
println!(
"Encryption Shared Secret (Hex): {}",
hex::encode(shared_secret.as_bytes())
);
// 3. Key & Nonce Derivation (HKDF)
// We derive 32 bytes for the key and 12 bytes for a deterministic nonce.
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce)
hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm)
.map_err(|_| SecurePayloadError::EncryptionError)?;
let key = &okm[..32];
let nonce_bytes = &okm[32..];
// 4. Encrypt with AES-256-GCM
let cipher = Aes256Gcm::new(key.into());
let nonce = Nonce::from_slice(nonce_bytes);
let ciphertext = cipher
.encrypt(
nonce,
Payload {
msg: &self.inner_data,
aad: &[],
},
)
.map_err(|_| SecurePayloadError::EncryptionError)?;
// 5. Result is ONLY the ciphertext. No key or nonce is packed.
Ok(SecurePayload {
inner_data: ciphertext,
private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(),
})
}
/// Decrypts the held data providing the sender's public key manually.
pub fn decrypt_to_format(
&self,
peer_public_key_bytes: &[u8; 56],
output_format: DataFormat,
) -> Result<String, SecurePayloadError> {
let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?;
Ok(decrypted_instance.export(output_format))
}
/// Decrypts the held data using the internal Private Key and the provided Peer Public Key.
pub fn decrypt_x448(
&self,
peer_public_key_bytes: &[u8; 56],
) -> Result<SecurePayload, SecurePayloadError> {
// 1. Perform Exchange
let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap();
let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap();
// LOGGING: Shared Secret
println!(
"Decryption Shared Secret (Hex): {}",
hex::encode(shared_secret.as_bytes())
);
// 2. Key & Nonce Derivation (Must match encryption exactly)
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut okm = [0u8; 44];
hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm)
.map_err(|_| SecurePayloadError::DecryptionError)?;
let key = &okm[..32];
let nonce_bytes = &okm[32..];
// 3. Decrypt with AES-256-GCM
let cipher = Aes256Gcm::new(key.into());
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext = cipher
.decrypt(
nonce,
Payload {
msg: &self.inner_data,
aad: &[],
},
)
.map_err(|_| SecurePayloadError::DecryptionError)?;
Ok(SecurePayload {
inner_data: plaintext,
private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(),
})
}
}

View file

@ -1,4 +1,2 @@
pub mod crypto_helper;
pub mod crypto_util;
pub mod file_util;
pub mod logger;