diff --git a/rust/pvimg/Cargo.toml b/rust/pvimg/Cargo.toml index baccae44..e9d24aff 100644 --- a/rust/pvimg/Cargo.toml +++ b/rust/pvimg/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] anyhow = { version = "1.0.95", features = ["std"] } +base64 = "0.22.1" clap = { version ="4.5", features = ["derive", "wrap_help"]} deku = "0.18" deku_derive = "0.18" diff --git a/rust/pvimg/src/cmd/info.rs b/rust/pvimg/src/cmd/info.rs index 4452f7f4..29cfacd7 100644 --- a/rust/pvimg/src/cmd/info.rs +++ b/rust/pvimg/src/cmd/info.rs @@ -31,10 +31,16 @@ pub fn info(opt: &InfoArgs) -> Result { let key = SymKey::try_from_data(hdr.key_type(), read_file(key_path, "Reading key")?.into())?; let decrypted_hdr = hdr.decrypt(&key)?; - SeH::Decrypted(decrypted_hdr) + SeH::DecryptedSeHdr { + se_hdr: decrypted_hdr, + verified: true, + } } else { warn!("WARNING: The Secure Execution header integrity and authenticity was not verified. Specify '--hdr-key' to authenticate it. Do not trust the data without verification."); - SeH::Encrypted(hdr) + SeH::SeHdr { + se_hdr: hdr, + verified: false, + } }; match opt.format { diff --git a/rust/pvimg/src/pv_utils/psw.rs b/rust/pvimg/src/pv_utils/psw.rs index 60d6c139..4b77edd3 100644 --- a/rust/pvimg/src/pv_utils/psw.rs +++ b/rust/pvimg/src/pv_utils/psw.rs @@ -4,9 +4,9 @@ use deku::{ctx::Endian, DekuRead, DekuWrite}; use pv::request::Zeroize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -use super::serializing::ser_lower_hex; +use super::serializing::serde_hex_left_padded_u64; use crate::pv_utils::error::Error; pub const PSW32_ADDR_MASK: u64 = 0x000000007fffffff; @@ -14,12 +14,12 @@ pub const PSW_MASK_BA: u64 = 0x0000000080000000; pub const PSW_MASK_EA: u64 = 0x0000000100000000; pub const PSW_MASK_BIT_12: u64 = 0x08000000000000; -#[derive(Default, Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct PSW { - #[serde(serialize_with = "ser_lower_hex")] + #[serde(with = "serde_hex_left_padded_u64", rename = "mask_hex")] pub mask: u64, - #[serde(serialize_with = "ser_lower_hex")] + #[serde(with = "serde_hex_left_padded_u64", rename = "addr_hex")] pub addr: u64, } @@ -30,7 +30,7 @@ impl Zeroize for PSW { } } -#[derive(Debug, Default, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct ShortPsw(u64); @@ -135,4 +135,19 @@ mod tests { let new_psw: PSW = short_psw.into(); assert_eq!(new_psw, psw); } + + #[test] + fn psw_json_roundtrip() { + let psw = PSW { + addr: 0x1234567890abcdef, + mask: 0xfedcba0987654321, + }; + let json = serde_json::to_string(&psw).expect("should serialize"); + assert_eq!( + json, + "{\"mask_hex\":\"fedcba0987654321\",\"addr_hex\":\"1234567890abcdef\"}" + ); + let deserialized: PSW = serde_json::from_str(&json).expect("should deserialize"); + assert_eq!(psw, deserialized); + } } diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs index ade71e95..198a560c 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs @@ -15,7 +15,7 @@ use pv::{ }, static_assert, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub use super::hdr_v1::{SeHdrBinV1, SeHdrDataV1}; use super::{PlaintextControlFlagsV1, SecretControlFlagsV1}; @@ -23,7 +23,7 @@ use crate::{ misc::PAGESIZE, pv_utils::{ error::{Error, Result}, - serializing::{ser_hex, serialize_to_bytes}, + serializing::{serde_hex_array, serialize_to_bytes}, uvdata::{ AeadCipherTrait, AeadDataTrait, AeadPlainDataTrait, KeyExchangeTrait, UvDataPlainTrait, UvDataTrait, @@ -35,7 +35,7 @@ use crate::{ #[repr(u32)] #[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku( endian = "endian", id_type = "u32", @@ -47,26 +47,37 @@ pub enum SeHdrVersion { V1 = 0x100, } -#[derive(Clone, PartialEq, Eq, Debug, Serialize)] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[serde(tag = "kind")] +#[serde(rename_all = "snake_case")] pub enum SeH { - Decrypted(SeHdrPlain), - Encrypted(SeHdr), + DecryptedSeHdr { + verified: bool, + #[serde(flatten)] + se_hdr: SeHdrPlain, + }, + SeHdr { + verified: bool, + #[serde(flatten)] + se_hdr: SeHdr, + }, } #[repr(C)] -#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct SeHdrCommon { - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "magic_hex")] pub magic: [u8; 8], pub version: SeHdrVersion, } static_assert!(::std::mem::size_of::() == 12); #[repr(C)] -#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct SeHdrCommonWithSize { + #[serde(with = "serde_hex_array", rename = "magic_hex")] pub magic: [u8; 8], pub version: SeHdrVersion, pub sehs: u32, @@ -85,7 +96,7 @@ impl SeHdrCommon { } } -#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "Endian::Big")] /// Secure Execution header structure pub struct SeHdr { @@ -97,7 +108,7 @@ pub struct SeHdr { pub data: SeHdrVersioned, } -#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "Endian::Big")] /// Plain data Secure Execution header structure pub struct SeHdrPlain { @@ -109,7 +120,7 @@ pub struct SeHdrPlain { } #[enum_dispatch(AeadCipherTrait, AeadDataTrait, KeyExchangeTrait)] -#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[serde(untagged)] #[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")] pub enum SeHdrVersioned { @@ -123,7 +134,7 @@ pub enum SeHdrVersioned { KeyExchangeTrait, KeyExchangeBuilderTrait )] -#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize)] +#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[serde(untagged)] #[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")] pub enum SeHdrData { @@ -382,7 +393,7 @@ impl AeadCipherTrait for SeHdrPlain { mod tests { use std::io::Cursor; - use super::SeHdr; + use super::*; use crate::error::Error; #[test] diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs index 09877252..cbdcc5bf 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs @@ -14,7 +14,7 @@ use pv::request::{ random_array, Aes256XtsKey, Confidential, EcPubKeyCoord, Encrypt, Keyslot, SymKey, SymKeyType, Zeroize, SHA_512_HASH_LEN, }; -use serde::{Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use super::keys::phkh_v1; use crate::{ @@ -31,7 +31,8 @@ use crate::{ }, serializing::{ bytesize, bytesize_confidential, confidential_read_slice, confidential_write_slice, - ser_hex, ser_hex_confidential, ser_lower_hex, serialize_to_bytes, + serde_base64, serde_hex_array, serde_hex_confidential_array, serde_hex_left_padded_u64, + serialize_to_bytes, }, try_copy_slice_to_array, uvdata::{ @@ -49,27 +50,28 @@ struct HdrSizesV1 { pub sea: u64, } -#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct SeHdrAadV1 { #[deku(assert = "*sehs <= SeHdrDataV1::MAX_SIZE.try_into().unwrap()")] pub sehs: u32, - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "iv_hex")] pub iv: [u8; SymKeyType::AES_256_GCM_IV_LEN], + #[serde(skip)] res1: u32, #[deku(assert = "*nks <= (*sehs).into()", update = "self.keyslots.len()")] pub nks: u64, #[deku(assert = "*sea <= (*sehs).into()")] pub sea: u64, pub nep: u64, - #[serde(serialize_with = "ser_lower_hex")] + #[serde(with = "serde_hex_left_padded_u64", rename = "pcf_hex")] pub pcf: u64, pub cust_pub_key: EcPubKeyCoordV1, - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "pld_hex")] pub pld: [u8; SHA_512_HASH_LEN], - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "ald_hex")] pub ald: [u8; SHA_512_HASH_LEN], - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "tld_hex")] pub tld: [u8; SHA_512_HASH_LEN], #[deku(count = "nks")] pub keyslots: Vec, @@ -104,27 +106,30 @@ impl KeyExchangeTrait for SeHdrAadV1 { } } -#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize)] +#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct SeHdrConfV1 { - #[serde(serialize_with = "ser_hex_confidential")] + #[serde(with = "serde_hex_confidential_array", rename = "cck_hex")] #[deku( reader = "confidential_read_slice(deku::reader, endian)", writer = "confidential_write_slice(cck, deku::writer, endian)" )] cck: Confidential<[u8; 32]>, - #[serde(serialize_with = "ser_hex_confidential")] + #[serde(with = "serde_hex_confidential_array", rename = "xts_hex")] #[deku( reader = "confidential_read_slice(deku::reader, endian)", writer = "confidential_write_slice(xts, deku::writer, endian)" )] xts: Aes256XtsKey, psw: PSW, - #[serde(serialize_with = "ser_lower_hex")] + #[serde(with = "serde_hex_left_padded_u64", rename = "scf_hex")] pub scf: u64, + #[serde(skip)] #[deku(assert_eq = "0")] noi: u32, + #[serde(skip)] res2: u32, + #[serde(skip)] #[deku(count = "noi")] opt_items: Vec, } @@ -141,27 +146,41 @@ impl Zeroize for SeHdrConfV1 { } } -#[derive(Default, PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize)] +#[derive(Default, PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct SeHdrTagV1 { - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "tag_hex")] tag: [u8; SymKeyType::AES_256_GCM_TAG_LEN], } -fn ser_confidential_confv1( - encrypted: &Confidential, - ser: S, -) -> std::result::Result { - encrypted.value().serialize(ser) +mod ser_confidential_confv1 { + use pv::request::Confidential; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::SeHdrConfV1; + + pub fn serialize( + encrypted: &Confidential, + ser: S, + ) -> Result { + encrypted.value().serialize(ser) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + let conf = SeHdrConfV1::deserialize(deserializer)?; + Ok(Confidential::new(conf)) + } } /// Secure Execution Header definition -#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "big")] pub struct SeHdrDataV1 { #[serde(flatten)] pub aad: SeHdrAadV1, - #[serde(flatten, serialize_with = "ser_confidential_confv1")] + #[serde(flatten, with = "ser_confidential_confv1")] #[deku( reader = "confidential_read_sehdrconf_v1(deku::reader)", writer = "confidential_write_sehdrconf_v1(data, deku::writer)" @@ -439,14 +458,14 @@ impl SeHdrPubBuilderTrait for SeHdrDataV1 { } } -#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "big")] pub struct SeHdrBinV1 { #[serde(flatten)] pub aad: SeHdrAadV1, - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_base64", rename = "cipher_data_b64")] #[deku(bytes_read = "aad.sea")] - pub data: Vec, + pub cipher_data: Vec, #[serde(flatten)] pub tag: SeHdrTagV1, } @@ -512,7 +531,7 @@ impl AeadDataTrait for SeHdrBinV1 { } fn data(&self) -> Vec { - self.data.to_owned() + self.cipher_data.to_owned() } fn tag(&self) -> Vec { @@ -667,4 +686,93 @@ mod tests { .expect("should not fail"); assert!(matches!(builder.build(), Err(Error::InvalidSeHdr))); } + + #[test] + fn roundtrip_se_hdr_tag_v1_json() { + let tag = SeHdrTagV1 { + tag: [0x42; SymKeyType::AES_256_GCM_TAG_LEN], + }; + + let json = serde_json::to_string(&tag).expect("should serialize"); + assert_eq!(json, "{\"tag_hex\":\"42424242424242424242424242424242\"}"); + let deserialized: SeHdrTagV1 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(tag, deserialized); + } + + #[test] + fn roundtrip_se_hdr_conf_v1_json() { + let conf = SeHdrConfV1 { + cck: Confidential::new([0x11; 32]), + xts: Confidential::new([0x22; SymKeyType::AES_256_XTS_KEY_LEN]), + psw: PSW { + addr: 0x1000, + mask: 0x2000, + }, + scf: 0x42, + noi: 0, + res2: 0, + opt_items: vec![], + }; + + let json = serde_json::to_string(&conf).expect("should serialize"); + assert_eq!(json, "{\"cck_hex\":\"1111111111111111111111111111111111111111111111111111111111111111\",\"xts_hex\":\"22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\",\"psw\":{\"mask_hex\":\"0000000000002000\",\"addr_hex\":\"0000000000001000\"},\"scf_hex\":\"0000000000000042\"}"); + let deserialized: SeHdrConfV1 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(conf, deserialized); + } + + #[test] + fn roundtrip_se_hdr_aad_v1_json() { + let aad = SeHdrAadV1 { + sehs: 1024, + iv: [0x33; SymKeyType::AES_256_GCM_IV_LEN], + res1: 0, + nks: 2, + sea: 512, + nep: 10, + pcf: 0x100, + cust_pub_key: EcPubKeyCoordV1 { coord: [0x44; 160] }, + pld: [0x55; SHA_512_HASH_LEN], + ald: [0x66; SHA_512_HASH_LEN], + tld: [0x77; SHA_512_HASH_LEN], + keyslots: vec![], + }; + + let json = serde_json::to_string(&aad).expect("should serialize"); + assert_eq!(json, "{\"sehs\":1024,\"iv_hex\":\"333333333333333333333333\",\"nks\":2,\"sea\":512,\"nep\":10,\"pcf_hex\":\"0000000000000100\",\"cust_pub_key\":{\"coord_hex\":\"44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444\"},\"pld_hex\":\"55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555\",\"ald_hex\":\"66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666\",\"tld_hex\":\"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777\",\"keyslots\":[]}"); + let deserialized: SeHdrAadV1 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(aad, deserialized); + } + + #[test] + fn roundtrip_se_hdr_bin_v1_json() { + let bin = SeHdrBinV1 { + aad: SeHdrAadV1 { + sehs: 1024, + iv: [0x33; SymKeyType::AES_256_GCM_IV_LEN], + res1: 0, + nks: 0, + sea: 64, + nep: 10, + pcf: 0x100, + cust_pub_key: EcPubKeyCoordV1 { coord: [0x44; 160] }, + pld: [0x55; SHA_512_HASH_LEN], + ald: [0x66; SHA_512_HASH_LEN], + tld: [0x77; SHA_512_HASH_LEN], + keyslots: vec![], + }, + cipher_data: vec![0x88; 64], + tag: SeHdrTagV1 { + tag: [0x99; SymKeyType::AES_256_GCM_TAG_LEN], + }, + }; + + let json = serde_json::to_string(&bin).expect("should serialize"); + assert_eq!(json, "{\"sehs\":1024,\"iv_hex\":\"333333333333333333333333\",\"nks\":0,\"sea\":64,\"nep\":10,\"pcf_hex\":\"0000000000000100\",\"cust_pub_key\":{\"coord_hex\":\"44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444\"},\"pld_hex\":\"55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555\",\"ald_hex\":\"66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666\",\"tld_hex\":\"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777\",\"keyslots\":[],\"cipher_data_b64\":\"iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiA==\",\"tag_hex\":\"99999999999999999999999999999999\"}"); + let deserialized: SeHdrBinV1 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(bin, deserialized); + } } diff --git a/rust/pvimg/src/pv_utils/se_hdr/keys.rs b/rust/pvimg/src/pv_utils/se_hdr/keys.rs index 61b404d5..67ad4041 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/keys.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/keys.rs @@ -10,11 +10,11 @@ use openssl::{ pkey::{PKey, PKeyRef, Public}, }; use pv::{request::EcPubKeyCoord, static_assert}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use crate::{ error::{Error, Result}, - pv_utils::{serializing::ser_hex, try_copy_slice_to_array}, + pv_utils::{serializing::serde_hex_array, try_copy_slice_to_array}, }; /// Try to hash the public EC key. @@ -28,10 +28,10 @@ pub fn phkh_v1>>(key: T) -> Result<[u8; 32]> { try_copy_slice_to_array(&binding) } -#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct EcPubKeyCoordV1 { - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "coord_hex")] pub coord: [u8; 160], } @@ -68,18 +68,18 @@ impl TryFrom for PKey { } #[repr(C)] -#[derive(Default, Debug, PartialEq, Eq, Clone, DekuRead, DekuWrite, Serialize)] +#[derive(Default, Debug, PartialEq, Eq, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] /// Binary key slot v1 pub struct BinaryKeySlotV1 { - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "phkh_hex")] /// Public host key hash pub phkh: [u8; 32], /// Wrapper key - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "wrpk_hex")] pub wrpk: [u8; 32], /// Tag - #[serde(serialize_with = "ser_hex")] + #[serde(with = "serde_hex_array", rename = "kst_hex")] pub kst: [u8; 16], } static_assert!(size_of::() == 80); @@ -97,3 +97,44 @@ impl TryFrom> for BinaryKeySlotV1 { Ok(bin) } } + +#[cfg(test)] +mod serde_tests { + use super::*; + + #[test] + fn roundtrip_ecpubkey_json() { + let key = EcPubKeyCoordV1 { coord: [0x42; 160] }; + + let json = serde_json::to_string(&key).expect("should serialize"); + assert_eq!(json, + "{\"coord_hex\":\"42424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242\"}"); + let deserialized: EcPubKeyCoordV1 = + serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(key, deserialized); + } + + #[test] + fn roundtrip_keyslots_json() { + let keyslots = vec![ + BinaryKeySlotV1 { + phkh: [0x01; 32], + wrpk: [0x02; 32], + kst: [0x03; 16], + }, + BinaryKeySlotV1 { + phkh: [0x03; 32], + wrpk: [0x04; 32], + kst: [0x05; 16], + }, + ]; + + let json = serde_json::to_string(&keyslots).expect("should serialize"); + assert_eq!(json, "[{\"phkh_hex\":\"0101010101010101010101010101010101010101010101010101010101010101\",\"wrpk_hex\":\"0202020202020202020202020202020202020202020202020202020202020202\",\"kst_hex\":\"03030303030303030303030303030303\"},{\"phkh_hex\":\"0303030303030303030303030303030303030303030303030303030303030303\",\"wrpk_hex\":\"0404040404040404040404040404040404040404040404040404040404040404\",\"kst_hex\":\"05050505050505050505050505050505\"}]"); + let deserialized: Vec = + serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(keyslots, deserialized); + } +} diff --git a/rust/pvimg/src/pv_utils/serializing.rs b/rust/pvimg/src/pv_utils/serializing.rs index 63c61b40..12ff3008 100644 --- a/rust/pvimg/src/pv_utils/serializing.rs +++ b/rust/pvimg/src/pv_utils/serializing.rs @@ -2,37 +2,132 @@ // // Copyright IBM Corp. 2024 -use std::fmt::LowerHex; - use deku::{ reader::Reader, writer::Writer, DekuContainerRead, DekuContainerWrite, DekuError, DekuReader, DekuWriter, }; use pv::request::{Confidential, Zeroize}; -use serde::{Serialize, Serializer}; -use utils::HexSlice; use crate::pv_utils::error::Result; -pub fn ser_hex, S: Serializer>( - data: A, - ser: S, -) -> std::result::Result { - HexSlice::from(data.as_ref()).serialize(ser) +pub mod serde_hex_left_padded_u64 { + use std::fmt::LowerHex; + + use serde::Deserializer; + use serde::{Deserialize, Serialize, Serializer}; + + pub fn serialize( + data: &B, + serializer: S, + ) -> Result { + format!("{data:016x}").serialize(serializer) + } + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + u64::from_str_radix(&s, 16).map_err(serde::de::Error::custom) + } } -pub fn ser_lower_hex( - data: &B, - ser: S, -) -> std::result::Result { - format!("{data:#018x}").serialize(ser) +pub mod serde_hex_array { + use std::result::Result; + + use pv::misc::{decode_hex, encode_hex}; + use serde::{Deserialize, Deserializer, Serializer}; + + use crate::pv_utils::try_copy_slice_to_array; + + pub fn serialize( + data: &[u8; COUNT], + serializer: S, + ) -> Result { + serializer.serialize_str(&encode_hex(data)) + } + + #[allow(dead_code)] + pub fn deserialize<'de, D: Deserializer<'de>, const COUNT: usize>( + deserializer: D, + ) -> Result<[u8; COUNT], D::Error> { + let s = String::deserialize(deserializer)?; + let decoded = decode_hex(s).map_err(serde::de::Error::custom)?; + + try_copy_slice_to_array(&decoded).map_err(serde::de::Error::custom) + } } -pub fn ser_hex_confidential( - data: &Confidential<[u8; COUNT]>, - ser: S, -) -> std::result::Result { - ser_hex(data.value(), ser) +pub mod serde_hex_confidential_array { + use std::result::Result; + + use pv::request::Confidential; + use serde::{Deserializer, Serializer}; + + use super::serde_hex_array; + + pub fn serialize( + data: &Confidential<[u8; COUNT]>, + serializer: S, + ) -> Result { + serde_hex_array::serialize(data.value(), serializer) + } + + #[allow(dead_code)] + pub fn deserialize<'de, D: Deserializer<'de>, const COUNT: usize>( + deserializer: D, + ) -> Result, D::Error> { + Ok(Confidential::new(serde_hex_array::deserialize( + deserializer, + )?)) + } +} + +pub mod serde_base64 { + use base64::prelude::*; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(bytes: B, serializer: S) -> Result + where + S: Serializer, + B: AsRef<[u8]>, + { + let b64 = BASE64_STANDARD.encode(bytes.as_ref()); + serializer.serialize_str(&b64) + } + + #[allow(dead_code)] + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + BASE64_STANDARD.decode(&s).map_err(serde::de::Error::custom) + } +} + +pub mod serde_base64_array { + use base64::prelude::*; + use serde::{Deserialize, Deserializer, Serializer}; + + use crate::pv_utils::try_copy_slice_to_array; + + #[allow(dead_code)] + pub fn serialize(bytes: &[u8; N], serializer: S) -> Result + where + S: Serializer, + { + let b64 = BASE64_STANDARD.encode(bytes); + serializer.serialize_str(&b64) + } + + #[allow(dead_code)] + pub fn deserialize<'de, D, const COUNT: usize>(deserializer: D) -> Result<[u8; COUNT], D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let decoded = BASE64_STANDARD + .decode(&s) + .map_err(serde::de::Error::custom)?; + try_copy_slice_to_array(&decoded).map_err(serde::de::Error::custom) + } } /// Read a slice into a confidential array of type [`T`] and length [`N`]. @@ -146,4 +241,102 @@ mod tests { assert_eq!(test.test.value()[0], 0x0f010203); assert_eq!(test.to_bytes().unwrap().as_slice(), &DATA); } + + #[test] + fn test_serde_base64() { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { + #[serde(with = "super::serde_base64")] + data: Vec, + } + + let original = TestStruct { + data: vec![1, 2, 3, 4, 5], + }; + + let json = serde_json::to_string(&original).unwrap(); + assert!(json.contains("AQIDBAU=")); + + let deserialized: TestStruct = serde_json::from_str(&json).unwrap(); + assert_eq!(original, deserialized); + } + + #[test] + fn test_serde_base64_with_array() { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { + #[serde(with = "super::serde_base64_array")] + data: [u8; 5], + } + + let original = TestStruct { + data: [1, 2, 3, 4, 5], + }; + + let json = serde_json::to_string(&original).unwrap(); + assert!(json.contains("AQIDBAU=")); + + let deserialized: TestStruct = serde_json::from_str(&json).unwrap(); + assert_eq!(original, deserialized); + } + + #[test] + fn test_serde_hex_array() { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { + #[serde(with = "super::serde_hex_array")] + data: [u8; 5], + } + + let original = TestStruct { + data: [1, 2, 3, 4, 5], + }; + + let json = serde_json::to_string(&original).unwrap(); + assert!(json.contains("0102030405")); + assert_eq!(&json, r#"{"data":"0102030405"}"#); + + let deserialized: TestStruct = serde_json::from_str(&json).unwrap(); + assert_eq!(original, deserialized); + } + + #[test] + fn test_serde_hex_array_invalid_length() { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { + #[serde(with = "super::serde_hex_array")] + data: [u8; 5], + } + + // Try to deserialize with wrong length (3 bytes instead of 5) + let json = r#"{"data":"010203"}"#; + let result: Result = serde_json::from_str(json); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Expected size 5")); + } + + #[test] + fn test_serde_base64_array_invalid_length() { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { + #[serde(with = "super::serde_base64_array")] + data: [u8; 5], + } + + // Try to deserialize with wrong length (3 bytes instead of 5) + let json = r#"{"data":"AQID"}"#; // Base64 for [1, 2, 3] + let result: Result = serde_json::from_str(json); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Expected size 5")); + } }