use crate::{
metadata::security::{
ArgumentType, ArgumentValue, NamedArgument, Permission, PermissionSetFormat,
},
utils::{to_u32, write_compressed_int, write_compressed_uint},
Result,
};
use std::{collections::HashMap, io::Write};
pub fn encode_permission_set(
permissions: &[Permission],
format: PermissionSetFormat,
) -> Result<Vec<u8>> {
let mut encoder = PermissionSetEncoder::new();
encoder.encode_permission_set(permissions, format)
}
pub struct PermissionSetEncoder {
buffer: Vec<u8>,
}
impl PermissionSetEncoder {
#[must_use]
pub fn new() -> Self {
PermissionSetEncoder { buffer: Vec::new() }
}
pub fn encode_permission_set(
&mut self,
permissions: &[Permission],
format: PermissionSetFormat,
) -> Result<Vec<u8>> {
self.buffer.clear();
match format {
PermissionSetFormat::BinaryLegacy => self.encode_binary_format(permissions)?,
PermissionSetFormat::BinaryCompressed => {
self.encode_binary_compressed_format(permissions)?;
}
PermissionSetFormat::Xml => self.encode_xml_format(permissions)?,
PermissionSetFormat::Unknown => {
return Err(malformed_error!(
"Cannot encode unknown permission set format"
));
}
}
Ok(self.buffer.clone())
}
fn encode_binary_format(&mut self, permissions: &[Permission]) -> Result<()> {
self.buffer.push(0x2E);
write_compressed_uint(to_u32(permissions.len())?, &mut self.buffer);
for permission in permissions {
self.encode_permission_binary(permission)?;
}
Ok(())
}
fn encode_binary_compressed_format(&mut self, permissions: &[Permission]) -> Result<()> {
self.buffer.push(0x2F);
let mut string_table = HashMap::new();
let mut string_list = Vec::new();
let mut next_index = 0u32;
for permission in permissions {
if !string_table.contains_key(&permission.class_name) {
string_table.insert(permission.class_name.clone(), next_index);
string_list.push(permission.class_name.clone());
next_index += 1;
}
if !string_table.contains_key(&permission.assembly_name) {
string_table.insert(permission.assembly_name.clone(), next_index);
string_list.push(permission.assembly_name.clone());
next_index += 1;
}
for arg in &permission.named_arguments {
if !string_table.contains_key(&arg.name) {
string_table.insert(arg.name.clone(), next_index);
string_list.push(arg.name.clone());
next_index += 1;
}
if let ArgumentValue::String(ref value) = arg.value {
if !string_table.contains_key(value) {
string_table.insert(value.clone(), next_index);
string_list.push(value.clone());
next_index += 1;
}
}
}
}
write_compressed_uint(to_u32(string_list.len())?, &mut self.buffer);
for string in &string_list {
let string_bytes = string.as_bytes();
write_compressed_uint(to_u32(string_bytes.len())?, &mut self.buffer);
self.buffer.extend_from_slice(string_bytes);
}
write_compressed_uint(to_u32(permissions.len())?, &mut self.buffer);
for permission in permissions {
let class_name_index = string_table[&permission.class_name];
let assembly_name_index = string_table[&permission.assembly_name];
write_compressed_uint(class_name_index, &mut self.buffer);
write_compressed_uint(assembly_name_index, &mut self.buffer);
write_compressed_uint(to_u32(permission.named_arguments.len())?, &mut self.buffer);
for arg in &permission.named_arguments {
let name_index = string_table[&arg.name];
write_compressed_uint(name_index, &mut self.buffer);
let type_byte = match &arg.arg_type {
ArgumentType::Boolean => 0x02,
ArgumentType::Char => 0x03,
ArgumentType::SByte => 0x04,
ArgumentType::Byte => 0x05,
ArgumentType::Int16 => 0x06,
ArgumentType::UInt16 => 0x07,
ArgumentType::Int32 => 0x08,
ArgumentType::UInt32 => 0x09,
ArgumentType::Int64 => 0x0A,
ArgumentType::UInt64 => 0x0B,
ArgumentType::Single => 0x0C,
ArgumentType::Double => 0x0D,
ArgumentType::String => 0x0E,
ArgumentType::Type => 0x50,
ArgumentType::Object => 0x51,
_ => {
return Err(malformed_error!(
"Unsupported argument type for compressed encoding: {:?}",
arg.arg_type
));
}
};
self.buffer.push(type_byte);
match &arg.value {
ArgumentValue::Boolean(value) => {
self.buffer.push(u8::from(*value));
}
ArgumentValue::Char(value) => {
self.buffer
.extend_from_slice(&(*value as u16).to_le_bytes());
}
ArgumentValue::SByte(value) => {
self.buffer.push(value.to_ne_bytes()[0]);
}
ArgumentValue::Byte(value) => {
self.buffer.push(*value);
}
ArgumentValue::Int16(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::UInt16(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::Int32(value) => {
write_compressed_int(*value, &mut self.buffer);
}
ArgumentValue::UInt32(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::Int64(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::UInt64(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::Single(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::Double(value) => {
self.buffer.extend_from_slice(&value.to_le_bytes());
}
ArgumentValue::String(value) | ArgumentValue::Type(value) => {
let value_index = string_table[value];
write_compressed_uint(value_index, &mut self.buffer);
}
_ => {
return Err(malformed_error!(
"Unsupported argument value for compressed encoding: {:?}",
arg.value
));
}
}
}
}
Ok(())
}
fn encode_permission_binary(&mut self, permission: &Permission) -> Result<()> {
let class_name_bytes = permission.class_name.as_bytes();
write_compressed_uint(to_u32(class_name_bytes.len())?, &mut self.buffer);
self.buffer.extend_from_slice(class_name_bytes);
let blob_data = Self::encode_permission_blob(permission)?;
write_compressed_uint(to_u32(blob_data.len())?, &mut self.buffer);
self.buffer.extend_from_slice(&blob_data);
Ok(())
}
fn encode_permission_blob(permission: &Permission) -> Result<Vec<u8>> {
let mut blob = Vec::new();
write_compressed_uint(to_u32(permission.named_arguments.len())?, &mut blob);
for arg in &permission.named_arguments {
Self::encode_named_argument(arg, &mut blob)?;
}
Ok(blob)
}
fn encode_named_argument(arg: &NamedArgument, blob: &mut Vec<u8>) -> Result<()> {
blob.push(0x54);
let type_byte = match &arg.arg_type {
ArgumentType::Boolean => 0x02,
ArgumentType::Char => 0x03,
ArgumentType::SByte => 0x04,
ArgumentType::Byte => 0x05,
ArgumentType::Int16 => 0x06,
ArgumentType::UInt16 => 0x07,
ArgumentType::Int32 => 0x08,
ArgumentType::UInt32 => 0x09,
ArgumentType::Int64 => 0x0A,
ArgumentType::UInt64 => 0x0B,
ArgumentType::Single => 0x0C,
ArgumentType::Double => 0x0D,
ArgumentType::String => 0x0E,
ArgumentType::Type => 0x50,
ArgumentType::Object => 0x51,
ArgumentType::Enum(ref type_name) => {
blob.push(0x55);
let type_name_bytes = type_name.as_bytes();
let type_name_len = u32::try_from(type_name_bytes.len()).map_err(|_| {
malformed_error!("Enum type name too long: {} bytes", type_name_bytes.len())
})?;
write_compressed_uint(type_name_len, blob);
blob.extend_from_slice(type_name_bytes);
return Self::encode_argument_value(&arg.value, blob);
}
ArgumentType::Array(ref _elem_type) => {
return Err(malformed_error!(
"Array argument types are not yet supported for encoding"
));
}
ArgumentType::Unknown(code) => {
return Err(malformed_error!(
"Cannot encode unknown argument type: 0x{:02X}",
code
));
}
};
blob.push(type_byte);
let name_bytes = arg.name.as_bytes();
let name_len = u32::try_from(name_bytes.len())
.map_err(|_| malformed_error!("Argument name too long: {} bytes", name_bytes.len()))?;
write_compressed_uint(name_len, blob);
blob.extend_from_slice(name_bytes);
Self::encode_argument_value(&arg.value, blob)
}
fn encode_argument_value(value: &ArgumentValue, blob: &mut Vec<u8>) -> Result<()> {
match value {
ArgumentValue::Boolean(v) => {
blob.push(u8::from(*v));
}
ArgumentValue::Char(v) => {
blob.extend_from_slice(&(*v as u16).to_le_bytes());
}
ArgumentValue::SByte(v) => {
blob.push(v.to_ne_bytes()[0]);
}
ArgumentValue::Byte(v) => {
blob.push(*v);
}
ArgumentValue::Int16(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::UInt16(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::Int32(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::UInt32(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::Int64(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::UInt64(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::Single(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::Double(v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::String(v) => {
let string_bytes = v.as_bytes();
let string_len = u32::try_from(string_bytes.len()).map_err(|_| {
malformed_error!(
"Argument string value too long: {} bytes",
string_bytes.len()
)
})?;
write_compressed_uint(string_len, blob);
blob.extend_from_slice(string_bytes);
}
ArgumentValue::Type(v) => {
let type_bytes = v.as_bytes();
let type_len = u32::try_from(type_bytes.len()).map_err(|_| {
malformed_error!("Type name too long: {} bytes", type_bytes.len())
})?;
write_compressed_uint(type_len, blob);
blob.extend_from_slice(type_bytes);
}
ArgumentValue::Object(inner) => {
let inner_type_byte = match inner.as_ref() {
ArgumentValue::Boolean(_) => 0x02,
ArgumentValue::Char(_) => 0x03,
ArgumentValue::SByte(_) => 0x04,
ArgumentValue::Byte(_) => 0x05,
ArgumentValue::Int16(_) => 0x06,
ArgumentValue::UInt16(_) => 0x07,
ArgumentValue::Int32(_) => 0x08,
ArgumentValue::UInt32(_) => 0x09,
ArgumentValue::Int64(_) => 0x0A,
ArgumentValue::UInt64(_) => 0x0B,
ArgumentValue::Single(_) => 0x0C,
ArgumentValue::Double(_) => 0x0D,
ArgumentValue::String(_) => 0x0E,
ArgumentValue::Type(_) => 0x50,
_ => {
return Err(malformed_error!(
"Cannot encode nested object value: {:?}",
inner
));
}
};
blob.push(inner_type_byte);
Self::encode_argument_value(inner, blob)?;
}
ArgumentValue::Enum(ref _type_name, v) => {
blob.extend_from_slice(&v.to_le_bytes());
}
ArgumentValue::Array(elements) => {
let count = u32::try_from(elements.len()).map_err(|_| {
malformed_error!("Array too large: {} elements", elements.len())
})?;
blob.extend_from_slice(&count.to_le_bytes());
for elem in elements {
Self::encode_argument_value(elem, blob)?;
}
}
ArgumentValue::Null => {
blob.push(0xFF);
}
}
Ok(())
}
fn encode_xml_format(&mut self, permissions: &[Permission]) -> Result<()> {
writeln!(
&mut self.buffer,
r#"<PermissionSet class="System.Security.PermissionSet" version="1">"#
)
.map_err(|e| malformed_error!("Failed to write XML header: {}", e))?;
for permission in permissions {
self.encode_permission_xml(permission)?;
}
writeln!(&mut self.buffer, "</PermissionSet>")
.map_err(|e| malformed_error!("Failed to write XML footer: {}", e))?;
Ok(())
}
fn encode_permission_xml(&mut self, permission: &Permission) -> Result<()> {
write!(
&mut self.buffer,
r#" <IPermission class="{}" version="1""#,
permission.class_name
)
.map_err(|e| malformed_error!("Failed to write XML permission start: {}", e))?;
for arg in &permission.named_arguments {
let value_str = match &arg.value {
ArgumentValue::Boolean(v) => v.to_string(),
ArgumentValue::Char(v) => v.to_string(),
ArgumentValue::SByte(v) => v.to_string(),
ArgumentValue::Byte(v) => v.to_string(),
ArgumentValue::Int16(v) => v.to_string(),
ArgumentValue::UInt16(v) => v.to_string(),
ArgumentValue::Int32(v) => v.to_string(),
ArgumentValue::UInt32(v) => v.to_string(),
ArgumentValue::Int64(v) => v.to_string(),
ArgumentValue::UInt64(v) => v.to_string(),
ArgumentValue::Single(v) => v.to_string(),
ArgumentValue::Double(v) => v.to_string(),
ArgumentValue::String(v) | ArgumentValue::Type(v) => v.clone(),
ArgumentValue::Enum(type_name, v) => format!("{type_name}.{v}"),
ArgumentValue::Object(inner) => inner.to_string(),
ArgumentValue::Array(elements) => {
elements
.iter()
.map(|e| format!("{e}"))
.collect::<Vec<_>>()
.join(",")
}
ArgumentValue::Null => "null".to_string(),
};
let escaped_value = Self::xml_escape(&value_str);
write!(&mut self.buffer, r#" {}="{}""#, arg.name, escaped_value)
.map_err(|e| malformed_error!("Failed to write XML attribute: {}", e))?;
}
writeln!(&mut self.buffer, "/>")
.map_err(|e| malformed_error!("Failed to write XML permission end: {}", e))?;
Ok(())
}
fn xml_escape(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
}
impl Default for PermissionSetEncoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::security::{ArgumentType, ArgumentValue, NamedArgument, Permission};
#[test]
fn test_encode_empty_permission_set_binary() {
let permissions = vec![];
let encoded =
encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy).unwrap();
assert_eq!(encoded, vec![0x2E, 0x00]);
}
#[test]
fn test_encode_simple_security_permission_binary() {
let permissions = vec![Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
}];
let encoded =
encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy).unwrap();
assert_eq!(encoded[0], 0x2E);
assert_eq!(encoded[1], 0x01);
let class_name = b"System.Security.Permissions.SecurityPermission";
assert_eq!(encoded[2], class_name.len() as u8);
let name_start = 3;
let name_end = name_start + class_name.len();
assert_eq!(&encoded[name_start..name_end], class_name);
}
#[test]
fn test_encode_permission_with_multiple_arguments() {
let permissions = vec![Permission {
class_name: "System.Security.Permissions.FileIOPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![
NamedArgument {
name: "Read".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("C:\\temp".to_string()),
},
NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(false),
},
],
}];
let encoded =
encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy).unwrap();
assert_eq!(encoded[0], 0x2E);
assert_eq!(encoded[1], 0x01);
assert!(encoded[2] > 0);
}
#[test]
fn test_encode_xml_format() {
let permissions = vec![Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
}];
let encoded = encode_permission_set(&permissions, PermissionSetFormat::Xml).unwrap();
let xml_str = String::from_utf8(encoded).unwrap();
assert!(xml_str.contains("<PermissionSet"));
assert!(xml_str.contains("System.Security.Permissions.SecurityPermission"));
assert!(xml_str.contains("Unrestricted=\"true\""));
assert!(xml_str.contains("</PermissionSet>"));
}
#[test]
fn test_xml_escaping() {
let _encoder = PermissionSetEncoder::new();
let input = r#"<test>"value"&more</test>"#;
let escaped = PermissionSetEncoder::xml_escape(input);
assert_eq!(
escaped,
"<test>"value"&more</test>"
);
}
#[test]
fn test_encode_unknown_format() {
let permissions = vec![];
let result = encode_permission_set(&permissions, PermissionSetFormat::Unknown);
assert!(result.is_err());
}
#[test]
fn test_encode_unsupported_argument_type() {
let permissions = vec![Permission {
class_name: "TestPermission".to_string(),
assembly_name: "TestAssembly".to_string(),
named_arguments: vec![NamedArgument {
name: "UnsupportedArg".to_string(),
arg_type: ArgumentType::Unknown(0xFF), value: ArgumentValue::Null,
}],
}];
let result = encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy);
assert!(result.is_err());
}
#[test]
fn test_encode_int64_argument() {
let permissions = vec![Permission {
class_name: "TestPermission".to_string(),
assembly_name: "TestAssembly".to_string(),
named_arguments: vec![NamedArgument {
name: "LongValue".to_string(),
arg_type: ArgumentType::Int64,
value: ArgumentValue::Int64(0x0102_0304_0506_0708),
}],
}];
let result = encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy);
assert!(result.is_ok());
let encoded = result.unwrap();
assert!(encoded.len() > 10); }
#[test]
fn test_encode_double_argument() {
let permissions = vec![Permission {
class_name: "TestPermission".to_string(),
assembly_name: "TestAssembly".to_string(),
named_arguments: vec![NamedArgument {
name: "DoubleValue".to_string(),
arg_type: ArgumentType::Double,
value: ArgumentValue::Double(std::f64::consts::PI),
}],
}];
let result = encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy);
assert!(result.is_ok());
}
#[test]
fn test_encode_binary_compressed_format() {
let permissions = vec![
Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
},
Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(), assembly_name: "mscorlib".to_string(), named_arguments: vec![NamedArgument {
name: "Flags".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("Execution".to_string()),
}],
},
];
let encoded =
encode_permission_set(&permissions, PermissionSetFormat::BinaryCompressed).unwrap();
assert_eq!(encoded[0], 0x2F);
let legacy_encoded =
encode_permission_set(&permissions, PermissionSetFormat::BinaryLegacy).unwrap();
assert!(encoded.len() < legacy_encoded.len());
}
#[test]
fn test_string_deduplication_in_compressed_format() {
let permissions = vec![
Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
},
Permission {
class_name: "System.Security.Permissions.SecurityPermission".to_string(), assembly_name: "mscorlib".to_string(), named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(), arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(false),
}],
},
];
let encoded =
encode_permission_set(&permissions, PermissionSetFormat::BinaryCompressed).unwrap();
assert_eq!(encoded[0], 0x2F);
assert_eq!(encoded[1], 0x03); }
}