use aegis_vm::{
execute, VmError,
bytecode::{BytecodeHeader, BytecodePackage, BytecodeFlags, ProtectionLevel},
crypto::CryptoContext,
build_config::opcodes::{stack, arithmetic, control, exec, register, native},
};
fn execute_encrypted(
ctx: &CryptoContext,
package: &BytecodePackage,
input: &[u8],
) -> Result<u64, VmError> {
if !package.header.is_encrypted() {
return execute(&package.code, input);
}
let decrypted = ctx.decrypt(
&package.code,
&package.header.nonce,
&package.header.tag,
)?;
execute(&decrypted, input)
}
#[test]
fn test_encrypted_simple_addition() {
let bytecode = [
stack::PUSH_IMM8, 40,
stack::PUSH_IMM8, 2,
arithmetic::ADD,
exec::HALT,
];
let seed = [0x42u8; 32];
let mut ctx = CryptoContext::new(seed);
let (ciphertext, nonce, tag) = ctx.encrypt(&bytecode).unwrap();
let mut header = BytecodeHeader::new(
ctx.build_id,
1234567890,
BytecodeFlags::Encrypted as u16,
);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage {
header,
code: ciphertext,
};
let result = execute_encrypted(&ctx, &package, &[]).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_encrypted_complex_arithmetic() {
let bytecode = [
stack::PUSH_IMM8, 100,
stack::PUSH_IMM8, 3,
arithmetic::MUL, stack::PUSH_IMM8, 50,
arithmetic::ADD, stack::PUSH_IMM8, 8,
arithmetic::SUB, exec::HALT,
];
let seed = [0x42u8; 32];
let mut ctx = CryptoContext::new(seed);
let (ciphertext, nonce, tag) = ctx.encrypt(&bytecode).unwrap();
let mut header = BytecodeHeader::new(ctx.build_id, 0, BytecodeFlags::Encrypted as u16);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage { header, code: ciphertext };
let result = execute_encrypted(&ctx, &package, &[]).unwrap();
assert_eq!(result, 342);
}
#[test]
fn test_encrypted_with_input() {
let bytecode = [
native::NATIVE_READ, 0x00, 0x00, stack::PUSH_IMM8, 10,
arithmetic::ADD,
exec::HALT,
];
let seed = [0x42u8; 32];
let mut ctx = CryptoContext::new(seed);
let (ciphertext, nonce, tag) = ctx.encrypt(&bytecode).unwrap();
let mut header = BytecodeHeader::new(ctx.build_id, 0, BytecodeFlags::Encrypted as u16);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage { header, code: ciphertext };
let input = 32u64.to_le_bytes();
let result = execute_encrypted(&ctx, &package, &input).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_encrypted_loop() {
let bytecode = [
register::MOV_IMM, 0, 5, 0, 0, 0, 0, 0, 0, 0,
register::MOV_IMM, 1, 0, 0, 0, 0, 0, 0, 0, 0,
stack::PUSH_REG, 0, stack::PUSH_REG, 1, arithmetic::ADD, stack::POP_REG, 1, stack::PUSH_REG, 0, arithmetic::DEC, stack::DUP, stack::POP_REG, 0, stack::PUSH_IMM8, 0,
arithmetic::SUB,
control::JNZ, (256 - 19) as u8, 0xFF,
stack::PUSH_REG, 1,
exec::HALT,
];
let seed = [0x42u8; 32];
let mut ctx = CryptoContext::new(seed);
let (ciphertext, nonce, tag) = ctx.encrypt(&bytecode).unwrap();
let mut header = BytecodeHeader::new(ctx.build_id, 0, BytecodeFlags::Encrypted as u16);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage { header, code: ciphertext };
let result = execute_encrypted(&ctx, &package, &[]).unwrap();
assert_eq!(result, 15);
}
#[test]
fn test_tampered_encrypted_bytecode_fails() {
let bytecode = [
stack::PUSH_IMM8, 42,
exec::HALT,
];
let seed = [0x42u8; 32];
let mut ctx = CryptoContext::new(seed);
let (mut ciphertext, nonce, tag) = ctx.encrypt(&bytecode).unwrap();
if !ciphertext.is_empty() {
ciphertext[0] ^= 0xFF;
}
let mut header = BytecodeHeader::new(ctx.build_id, 0, BytecodeFlags::Encrypted as u16);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage { header, code: ciphertext };
let result = execute_encrypted(&ctx, &package, &[]);
assert!(result.is_err());
}
#[test]
#[cfg(not(feature = "whitebox"))]
fn test_wrong_key_fails() {
let bytecode = [
stack::PUSH_IMM8, 42,
exec::HALT,
];
let seed1 = [0x42u8; 32];
let mut ctx1 = CryptoContext::new(seed1);
let (ciphertext, nonce, tag) = ctx1.encrypt(&bytecode).unwrap();
let mut header = BytecodeHeader::new(ctx1.build_id, 0, BytecodeFlags::Encrypted as u16);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage { header, code: ciphertext };
let seed2 = [0x43u8; 32];
let ctx2 = CryptoContext::new(seed2);
let result = execute_encrypted(&ctx2, &package, &[]);
assert!(result.is_err());
}
#[test]
fn test_plaintext_mode() {
let bytecode = vec![
stack::PUSH_IMM8, 42,
exec::HALT,
];
let seed = [0x42u8; 32];
let ctx = CryptoContext::new(seed);
let mut header = BytecodeHeader::new(ctx.build_id, 0, 0); header.code_len = bytecode.len() as u32;
let package = BytecodePackage { header, code: bytecode };
let result = execute_encrypted(&ctx, &package, &[]).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_package_serialization() {
let bytecode = [
stack::PUSH_IMM8, 42,
exec::HALT,
];
let seed = [0x42u8; 32];
let mut ctx = CryptoContext::new(seed);
let (ciphertext, nonce, tag) = ctx.encrypt(&bytecode).unwrap();
let mut header = BytecodeHeader::new(ctx.build_id, 1234567890, BytecodeFlags::Encrypted as u16);
header.nonce = nonce;
header.tag = tag;
header.code_len = ciphertext.len() as u32;
let package = BytecodePackage { header, code: ciphertext };
let serialized = package.to_bytes();
let deserialized = BytecodePackage::from_bytes(&serialized).unwrap();
assert_eq!(deserialized.header.build_id, ctx.build_id);
assert_eq!(deserialized.header.timestamp, 1234567890);
assert!(deserialized.header.is_encrypted());
let result = execute_encrypted(&ctx, &deserialized, &[]).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_protection_level_flags() {
assert_eq!(ProtectionLevel::Debug.to_flags(), 0);
assert_eq!(ProtectionLevel::Low.to_flags(), BytecodeFlags::Encrypted as u16);
assert_eq!(
ProtectionLevel::Medium.to_flags(),
BytecodeFlags::Encrypted as u16 | BytecodeFlags::HasIntegrity as u16
);
assert_eq!(
ProtectionLevel::Paranoid.to_flags(),
BytecodeFlags::Encrypted as u16
| BytecodeFlags::HasIntegrity as u16
| BytecodeFlags::HasTimingChecks as u16
| BytecodeFlags::Paranoid as u16
);
}