use crate::binary::{BinaryRead, BinaryWrite, read_u8, read_u16_be, write_u8, write_u16_be};
use crate::nanotdf::resource_locator::ResourceLocator;
use std::io::{self, Read, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PolicyType {
Remote = 0x00,
EmbeddedPlaintext = 0x01,
EmbeddedEncrypted = 0x02,
EmbeddedEncryptedPolicyKeyAccess = 0x03,
}
impl PolicyType {
pub fn from_byte(byte: u8) -> io::Result<Self> {
match byte {
0x00 => Ok(PolicyType::Remote),
0x01 => Ok(PolicyType::EmbeddedPlaintext),
0x02 => Ok(PolicyType::EmbeddedEncrypted),
0x03 => Ok(PolicyType::EmbeddedEncryptedPolicyKeyAccess),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid policy type: 0x{:02X}", byte),
)),
}
}
pub fn to_byte(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PolicyBody {
Remote(ResourceLocator),
EmbeddedPlaintext(Vec<u8>),
EmbeddedEncrypted(Vec<u8>),
EmbeddedEncryptedWithKeyAccess {
content: Vec<u8>,
key_access: ResourceLocator,
ephemeral_key: Vec<u8>,
},
}
impl PolicyBody {
pub fn policy_type(&self) -> PolicyType {
match self {
PolicyBody::Remote(_) => PolicyType::Remote,
PolicyBody::EmbeddedPlaintext(_) => PolicyType::EmbeddedPlaintext,
PolicyBody::EmbeddedEncrypted(_) => PolicyType::EmbeddedEncrypted,
PolicyBody::EmbeddedEncryptedWithKeyAccess { .. } => {
PolicyType::EmbeddedEncryptedPolicyKeyAccess
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Policy {
pub body: PolicyBody,
pub binding: Vec<u8>,
}
impl Policy {
pub fn new(body: PolicyBody, binding: Vec<u8>) -> Self {
Self { body, binding }
}
pub fn remote(locator: ResourceLocator, binding: Vec<u8>) -> Self {
Self::new(PolicyBody::Remote(locator), binding)
}
pub fn embedded_plaintext(content: Vec<u8>, binding: Vec<u8>) -> Self {
Self::new(PolicyBody::EmbeddedPlaintext(content), binding)
}
pub fn embedded_encrypted(content: Vec<u8>, binding: Vec<u8>) -> Self {
Self::new(PolicyBody::EmbeddedEncrypted(content), binding)
}
}
impl BinaryRead for Policy {
fn read_from<R: Read>(reader: &mut R) -> io::Result<Self> {
let policy_type = PolicyType::from_byte(read_u8(reader)?)?;
let body = match policy_type {
PolicyType::Remote => {
let locator = ResourceLocator::read_from(reader)?;
PolicyBody::Remote(locator)
}
PolicyType::EmbeddedPlaintext | PolicyType::EmbeddedEncrypted => {
let content_len = read_u16_be(reader)? as usize;
let mut content = vec![0u8; content_len];
reader.read_exact(&mut content)?;
if policy_type == PolicyType::EmbeddedPlaintext {
PolicyBody::EmbeddedPlaintext(content)
} else {
PolicyBody::EmbeddedEncrypted(content)
}
}
PolicyType::EmbeddedEncryptedPolicyKeyAccess => {
let content_len = read_u16_be(reader)? as usize;
let mut content = vec![0u8; content_len];
reader.read_exact(&mut content)?;
let key_access = ResourceLocator::read_from(reader)?;
let mut ephemeral_key = vec![0u8; 33];
reader.read_exact(&mut ephemeral_key)?;
PolicyBody::EmbeddedEncryptedWithKeyAccess {
content,
key_access,
ephemeral_key,
}
}
};
let mut binding = vec![0u8; 8];
reader.read_exact(&mut binding)?;
Ok(Self { body, binding })
}
}
impl BinaryWrite for Policy {
fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write_u8(writer, self.body.policy_type().to_byte())?;
match &self.body {
PolicyBody::Remote(locator) => {
locator.write_to(writer)?;
}
PolicyBody::EmbeddedPlaintext(content) | PolicyBody::EmbeddedEncrypted(content) => {
if content.len() > 0xFFFF {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"Policy content too large: {} bytes (max 65535)",
content.len()
),
));
}
write_u16_be(writer, content.len() as u16)?;
writer.write_all(content)?;
}
PolicyBody::EmbeddedEncryptedWithKeyAccess {
content,
key_access,
ephemeral_key,
} => {
if content.len() > 0xFFFF {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"Policy content too large: {} bytes (max 65535)",
content.len()
),
));
}
write_u16_be(writer, content.len() as u16)?;
writer.write_all(content)?;
key_access.write_to(writer)?;
writer.write_all(ephemeral_key)?;
}
}
writer.write_all(&self.binding)?;
Ok(())
}
fn serialized_size(&self) -> usize {
let body_size = match &self.body {
PolicyBody::Remote(locator) => locator.serialized_size(),
PolicyBody::EmbeddedPlaintext(content) | PolicyBody::EmbeddedEncrypted(content) => {
2 + content.len() }
PolicyBody::EmbeddedEncryptedWithKeyAccess {
content,
key_access,
ephemeral_key,
} => 2 + content.len() + key_access.serialized_size() + ephemeral_key.len(),
};
1 + body_size +
self.binding.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nanotdf::resource_locator::Protocol;
#[test]
fn test_remote_policy() {
let locator = ResourceLocator::new(Protocol::Https, b"kas.example.com/policy/123");
let binding = vec![0u8; 8]; let policy = Policy::remote(locator, binding);
assert!(matches!(policy.body, PolicyBody::Remote(_)));
assert_eq!(policy.body.policy_type(), PolicyType::Remote);
}
#[test]
fn test_embedded_plaintext_policy() {
let content = b"policy content".to_vec();
let binding = vec![0u8; 8];
let policy = Policy::embedded_plaintext(content.clone(), binding);
match &policy.body {
PolicyBody::EmbeddedPlaintext(c) => assert_eq!(c, &content),
_ => panic!("Expected EmbeddedPlaintext"),
}
}
#[test]
fn test_policy_roundtrip() {
let content = b"test policy".to_vec();
let binding = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let original = Policy::embedded_plaintext(content, binding);
let mut buf = Vec::new();
original.write_to(&mut buf).unwrap();
assert!(!buf.is_empty());
}
}