Skip to content

Commit

Permalink
Apply RFC7516 Standard to Response
Browse files Browse the repository at this point in the history
This patch is a fix up to previous commits to update the Response struct
to follow RFC7516.

The work included in the commit
1. Make both serialization and deserialization of `ProtectedHeader`
use base64-url-nopadding encoding. This means that the whole
ProtectedHeader will be serialized into a base64 string, rather than a
struct.
2. Make `encrypted_key`, `aad`, `iv`, `ciphertext`, `tag` to
base64-url-nopadding encoded string when serializing.
3. Automatical deseralization logic when parsing Response from a
flattened JSON JWE Serialization.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
  • Loading branch information
Xynnn007 committed Dec 2, 2024
1 parent 8aa469a commit 86be6cf
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 45 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tee-sev = [ "sev" ]
tee-snp = [ "sev" ]

[dependencies]
base64 = "0.22.1"
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
sev = { version = "3.2.0", features = ["openssl"], optional = true }
Expand Down
213 changes: 168 additions & 45 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate alloc;

#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::string::String;
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
use serde_json::Value;
use std::collections::BTreeMap;
#[cfg(feature = "std")]
Expand Down Expand Up @@ -98,15 +99,115 @@ pub struct ProtectedHeader {
pub other_fields: BTreeMap<String, String>,
}

fn serialize_base64_protected_header<S>(
sub: &ProtectedHeader,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let protected_header_json = serde_json::to_string(sub).map_err(serde::ser::Error::custom)?;
let encoded = BASE64_URL_SAFE_NO_PAD.encode(&protected_header_json);
serializer.serialize_str(&encoded)
}

fn deserialize_base64_protected_header<'de, D>(deserializer: D) -> Result<ProtectedHeader, D::Error>
where
D: serde::Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
let decoded = BASE64_URL_SAFE_NO_PAD
.decode(&encoded)
.map_err(serde::de::Error::custom)?;
let protected_header = serde_json::from_slice(&decoded).map_err(serde::de::Error::custom)?;

Ok(protected_header)
}

fn serialize_base64<S>(sub: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let encoded = BASE64_URL_SAFE_NO_PAD.encode(sub);
serializer.serialize_str(&encoded)
}

fn deserialize_base64<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
let decoded = BASE64_URL_SAFE_NO_PAD
.decode(&encoded)
.map_err(serde::de::Error::custom)?;

Ok(decoded)
}

fn serialize_base64_option<S>(sub: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match sub {
Some(value) => {
let encoded = BASE64_URL_SAFE_NO_PAD.encode(value);
serializer.serialize_str(&encoded)
}
None => serializer.serialize_none(),
}
}

fn deserialize_base64_option<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
let decoded = BASE64_URL_SAFE_NO_PAD
.decode(&encoded)
.map_err(serde::de::Error::custom)?;

Ok(Some(decoded))
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Response {
#[serde(
serialize_with = "serialize_base64_protected_header",
deserialize_with = "deserialize_base64_protected_header"
)]
pub protected: ProtectedHeader,
pub encrypted_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub aad: Option<String>,
pub iv: String,
pub ciphertext: String,
pub tag: String,

#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
pub encrypted_key: Vec<u8>,

#[serde(
deserialize_with = "deserialize_base64_option",
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_base64_option",
default = "Option::default"
)]
pub aad: Option<Vec<u8>>,

#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
pub iv: Vec<u8>,

#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
pub ciphertext: Vec<u8>,

#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
pub tag: Vec<u8>,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
Expand All @@ -118,6 +219,8 @@ pub struct ErrorInformation {

#[cfg(test)]
mod tests {
use serde_json::json;

use crate::*;

#[test]
Expand Down Expand Up @@ -154,83 +257,103 @@ mod tests {
fn parse_response() {
let data = r#"
{
"protected": {
"alg": "fakealg",
"enc": "fakeenc"
},
"encrypted_key": "fakekey",
"iv": "randomdata",
"ciphertext": "fakeencoutput",
"tag": "faketag"
"protected": "eyJhbGciOiJmYWtlYWxnIiwiZW5jIjoiZmFrZWVuYyJ9",
"encrypted_key": "ZmFrZWtleQ",
"iv": "cmFuZG9tZGF0YQ",
"ciphertext": "ZmFrZWVuY291dHB1dA",
"tag": "ZmFrZXRhZw"
}"#;

let response: Response = serde_json::from_str(data).unwrap();

assert_eq!(response.protected.alg, "fakealg");
assert_eq!(response.protected.enc, "fakeenc");
assert!(response.protected.other_fields.is_empty());
assert_eq!(response.encrypted_key, "fakekey");
assert_eq!(response.iv, "randomdata");
assert_eq!(response.ciphertext, "fakeencoutput");
assert_eq!(response.tag, "faketag");
assert_eq!(response.encrypted_key, "fakekey".as_bytes());
assert_eq!(response.iv, "randomdata".as_bytes());
assert_eq!(response.ciphertext, "fakeencoutput".as_bytes());
assert_eq!(response.tag, "faketag".as_bytes());
assert_eq!(response.aad, None);
}

#[test]
fn parse_response_with_aad() {
let data = r#"
{
"protected": {
"alg": "fakealg",
"enc": "fakeenc"
},
"encrypted_key": "fakekey",
"iv": "randomdata",
"aad": "fakeaad",
"ciphertext": "fakeencoutput",
"tag": "faketag"
"protected": "eyJhbGciOiJmYWtlYWxnIiwiZW5jIjoiZmFrZWVuYyJ9Cg",
"encrypted_key": "ZmFrZWtleQ",
"iv": "cmFuZG9tZGF0YQ",
"aad": "ZmFrZWFhZA",
"ciphertext": "ZmFrZWVuY291dHB1dA",
"tag": "ZmFrZXRhZw"
}"#;

let response: Response = serde_json::from_str(data).unwrap();

assert_eq!(response.protected.alg, "fakealg");
assert_eq!(response.protected.enc, "fakeenc");
assert!(response.protected.other_fields.is_empty());
assert_eq!(response.encrypted_key, "fakekey");
assert_eq!(response.iv, "randomdata");
assert_eq!(response.ciphertext, "fakeencoutput");
assert_eq!(response.tag, "faketag");
assert_eq!(response.encrypted_key, "fakekey".as_bytes());
assert_eq!(response.iv, "randomdata".as_bytes());
assert_eq!(response.ciphertext, "fakeencoutput".as_bytes());
assert_eq!(response.tag, "faketag".as_bytes());
assert_eq!(response.aad, Some("fakeaad".into()));
}

#[test]
fn parse_response_with_protectedheader() {
let data = r#"
{
"protected": {
"alg": "fakealg",
"enc": "fakeenc",
"fakefield": "fakevalue"
},
"encrypted_key": "fakekey",
"iv": "randomdata",
"aad": "fakeaad",
"ciphertext": "fakeencoutput",
"tag": "faketag"
"protected": "eyJhbGciOiJmYWtlYWxnIiwiZW5jIjoiZmFrZWVuYyIsImZha2VmaWVsZCI6ImZha2V2YWx1ZSJ9",
"encrypted_key": "ZmFrZWtleQ",
"iv": "cmFuZG9tZGF0YQ",
"aad": "ZmFrZWFhZA",
"ciphertext": "ZmFrZWVuY291dHB1dA",
"tag": "ZmFrZXRhZw"
}"#;

let response: Response = serde_json::from_str(data).unwrap();

assert_eq!(response.protected.alg, "fakealg");
assert_eq!(response.protected.enc, "fakeenc");
assert_eq!(response.protected.other_fields["fakefield"], "fakevalue");
assert_eq!(response.encrypted_key, "fakekey");
assert_eq!(response.iv, "randomdata");
assert_eq!(response.ciphertext, "fakeencoutput");
assert_eq!(response.tag, "faketag");
assert_eq!(response.encrypted_key, "fakekey".as_bytes());
assert_eq!(response.iv, "randomdata".as_bytes());
assert_eq!(response.ciphertext, "fakeencoutput".as_bytes());
assert_eq!(response.tag, "faketag".as_bytes());
assert_eq!(response.aad, Some("fakeaad".into()));
}

#[test]
fn serialize_response() {
let response = Response {
protected: ProtectedHeader {
alg: "fakealg".into(),
enc: "fakeenc".into(),
other_fields: [("fakefield".into(), "fakevalue".into())]
.into_iter()
.collect(),
},
encrypted_key: "fakekey".as_bytes().to_vec(),
iv: "randomdata".as_bytes().to_vec(),
aad: Some("fakeaad".into()),
tag: "faketag".as_bytes().to_vec(),
ciphertext: "fakeencoutput".as_bytes().to_vec(),
};

let expected = json!({
"protected": "eyJhbGciOiJmYWtlYWxnIiwiZW5jIjoiZmFrZWVuYyIsImZha2VmaWVsZCI6ImZha2V2YWx1ZSJ9",
"encrypted_key": "ZmFrZWtleQ",
"iv": "cmFuZG9tZGF0YQ",
"aad": "ZmFrZWFhZA",
"ciphertext": "ZmFrZWVuY291dHB1dA",
"tag": "ZmFrZXRhZw"
});

let serialized = serde_json::to_value(&response).unwrap();
assert_eq!(serialized, expected);
}

#[test]
fn parse_attestation_ec() {
let data = r#"
Expand Down

0 comments on commit 86be6cf

Please sign in to comment.