Skip to content

Commit

Permalink
Add a new optional field to the SignatureResponse struct. This field …
Browse files Browse the repository at this point in the history
…contains the

binary representation of the epoch which was signed. This will allow client code
to cross-check the signature with the original data in an unambiguous way, without
requiring exect details of the serialization method used.
  • Loading branch information
Max Sang committed Dec 2, 2024
1 parent 72173b2 commit b2b4dd6
Showing 1 changed file with 90 additions and 23 deletions.
113 changes: 90 additions & 23 deletions plexi_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use anyhow::anyhow;
use bincode::{BorrowDecode, Decode, Encode};
use ed25519_dalek::SIGNATURE_LENGTH;
use prost::Message;
use serde::{de, Deserializer};
use serde::{de, Deserializer, Serializer};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "openapi")]
Expand Down Expand Up @@ -135,6 +135,10 @@ impl Epoch {
pub fn is_first(&self) -> bool {
self.0 == FIRST_EPOCH.0
}

pub fn as_root_epoch(&self, digest: &str) -> String {
format!("{}/{}", self.0, digest)
}
}

impl From<&Epoch> for u64 {
Expand Down Expand Up @@ -385,20 +389,18 @@ impl fmt::Debug for SignatureRequest {
}
}

#[derive(Clone, Serialize)]
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct SignatureResponse {
version: Ciphersuite,
ciphersuite: Ciphersuite,
namespace: String,
timestamp: u64,
epoch: Epoch,
#[serde(with = "hex::serde")]
digest: Vec<u8>,
#[serde(with = "hex::serde")]
signature: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
key_id: Option<u8>,
serialized_message: Option<Vec<u8>>,
}

impl fmt::Debug for SignatureResponse {
Expand All @@ -412,6 +414,7 @@ impl fmt::Debug for SignatureResponse {
.field("digest", &hex::encode(&self.digest))
.field("signature", &hex::encode(&self.signature))
.field("key_id", &self.key_id)
.field("serialized_message", &self.serialized_message)
.finish()
}
}
Expand All @@ -427,6 +430,7 @@ impl SignatureResponse {
digest: Vec<u8>,
signature: Vec<u8>,
key_id: Option<u8>,
serialized_message: Option<Vec<u8>>,
) -> Self {
Self {
version: *version,
Expand All @@ -437,6 +441,7 @@ impl SignatureResponse {
digest,
signature,
key_id,
serialized_message,
}
}

Expand Down Expand Up @@ -473,8 +478,12 @@ impl SignatureResponse {
self.key_id
}

pub fn serialized_message(&self) -> Option<Vec<u8>> {
self.serialized_message.clone()
}

pub fn verify(&self, verifying_key: &[u8]) -> anyhow::Result<()> {
// at the time of writting, all version use ed25519 keys. this simplify parsing of the verifying key
// at the time of writing, all versions use ed25519 keys. This simplifies parsing of the verifying key.
match self.version {
#[cfg(feature = "bincode")]
Ciphersuite::BincodeEd25519 => (),
Expand Down Expand Up @@ -527,6 +536,12 @@ impl From<Report> for HashMap<String, String> {
if let Some(key_id) = val.key_id {
map.insert("key_id".to_string(), key_id.to_string());
}
if let Some(serialized_message) = val.serialized_message {
map.insert(
"serialized_message".to_string(),
hex::encode(serialized_message),
);
}
map
}
}
Expand Down Expand Up @@ -576,10 +591,52 @@ impl TryFrom<HashMap<String, String>> for Report {
.map(|id| id.parse())
.transpose()
.map_err(|_| PlexiError::BadParameter("key_id".to_string()))?,
serialized_message: value
.get("serialized_message")
.map(|msg| hex::decode(msg))

Check failure on line 596 in plexi_core/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.81)

redundant closure

error: redundant closure --> plexi_core/src/lib.rs:596:22 | 596 | .map(|msg| hex::decode(msg)) | ^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `hex::decode` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `-D clippy::redundant-closure` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]`

Check failure on line 596 in plexi_core/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.81)

redundant closure

error: redundant closure --> plexi_core/src/lib.rs:596:22 | 596 | .map(|msg| hex::decode(msg)) | ^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `hex::decode` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `-D clippy::redundant-closure` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]`
.transpose()
.map_err(|_| PlexiError::BadParameter("serialized_message".to_string()))?,
})
}
}

#[derive(Deserialize, Serialize)]
struct TempSignatureResponse {
version: Option<Ciphersuite>,
ciphersuite: Option<Ciphersuite>,
namespace: String,
timestamp: u64,
epoch: Epoch,
#[serde(with = "hex::serde")]
digest: Vec<u8>,
#[serde(with = "hex::serde")]
signature: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
key_id: Option<u8>,
serialized_message: Option<String>,
}

impl Serialize for SignatureResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let sm = self.serialized_message.as_ref().map(|m| hex::encode(m));

Check failure on line 624 in plexi_core/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.81)

redundant closure

error: redundant closure --> plexi_core/src/lib.rs:624:55 | 624 | let sm = self.serialized_message.as_ref().map(|m| hex::encode(m)); | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `hex::encode` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

Check failure on line 624 in plexi_core/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.81)

redundant closure

error: redundant closure --> plexi_core/src/lib.rs:624:55 | 624 | let sm = self.serialized_message.as_ref().map(|m| hex::encode(m)); | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `hex::encode` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
let tsp = TempSignatureResponse {
ciphersuite: Some(self.ciphersuite),
version: Some(self.ciphersuite),
namespace: self.namespace.clone(),
timestamp: self.timestamp,
epoch: self.epoch,
digest: self.digest.clone(),
signature: self.signature.clone(),
key_id: self.key_id,
serialized_message: sm,
};
tsp.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for SignatureResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand All @@ -594,23 +651,7 @@ fn deserialize_signature_response<'de, D>(deserializer: D) -> Result<SignatureRe
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct TempSignatureResponse {
version: Option<Ciphersuite>,
ciphersuite: Option<Ciphersuite>,
namespace: String,
timestamp: u64,
epoch: Epoch,
#[serde(with = "hex::serde")]
digest: Vec<u8>,
#[serde(with = "hex::serde")]
signature: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
key_id: Option<u8>,
}

let temp = TempSignatureResponse::deserialize(deserializer)?;

let suite_value = match (temp.version, temp.ciphersuite) {
(Some(v), _) => v,
(_, Some(c)) => c,
Expand All @@ -620,7 +661,11 @@ where
))
}
};

let sm = temp
.serialized_message
.map(|m| hex::decode(m))

Check failure on line 666 in plexi_core/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.81)

redundant closure

error: redundant closure --> plexi_core/src/lib.rs:666:14 | 666 | .map(|m| hex::decode(m)) | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `hex::decode` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

Check failure on line 666 in plexi_core/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (1.81)

redundant closure

error: redundant closure --> plexi_core/src/lib.rs:666:14 | 666 | .map(|m| hex::decode(m)) | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `hex::decode` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
.transpose()
.map_err(|_| de::Error::custom("serialized_message should be hex encoded"))?;
Ok(SignatureResponse {
version: suite_value,
ciphersuite: suite_value,
Expand All @@ -630,6 +675,7 @@ where
digest: temp.digest,
signature: temp.signature,
key_id: temp.key_id,
serialized_message: sm,
})
}

Expand Down Expand Up @@ -769,4 +815,25 @@ mod tests {
.is_ok());
}
}

#[test]
fn test_signature_response_serialization() {
let test_response = SignatureResponse {
version: Ciphersuite::ProtobufEd25519,
ciphersuite: Ciphersuite::ProtobufEd25519,
namespace: "n".to_string(),
timestamp: 2,
epoch: Epoch(3),
digest: vec![4],
signature: vec![5],
key_id: Some(6),
serialized_message: Some(vec![7]),
};
let test_json = r#"{"version":1,"ciphersuite":1,"namespace":"n","timestamp":2,"epoch":3,"digest":"04","signature":"05","key_id":6,"serialized_message":"07"}"#;
let serialized = serde_json::to_string(&test_response).unwrap();
assert_eq!(serialized, test_json.to_string());
let deserialized: Result<SignatureResponse, _> = serde_json::from_str(test_json);
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), test_response);
}
}

0 comments on commit b2b4dd6

Please sign in to comment.