|
1 | 1 | //! Deserializer for version API response format
|
2 | 2 |
|
| 3 | +use anyhow::Context; |
3 | 4 | use serde::Deserialize;
|
4 | 5 |
|
5 | 6 | /// JSON response including signature and signed content
|
6 |
| -/// Note that signature verification isn't accomplished by deserializing |
7 |
| -#[derive(Deserialize)] |
| 7 | +/// This type does not implement [serde::Deserialize] to prevent accidental deserialization without |
| 8 | +/// signature verification. |
8 | 9 | pub struct SignedResponse {
|
9 | 10 | /// Signature of the canonicalized JSON of `signed`
|
10 | 11 | pub signature: ResponseSignature,
|
11 | 12 | /// Content signed by `signature`
|
12 | 13 | pub signed: Response,
|
13 | 14 | }
|
14 | 15 |
|
| 16 | +/// Helper class that leaves the signed data untouched |
| 17 | +/// Note that deserializing doesn't verify anything |
| 18 | +#[derive(serde::Deserialize)] |
| 19 | +struct PartialSignedResponse { |
| 20 | + /// Signature of the canonicalized JSON of `signed` |
| 21 | + pub signature: ResponseSignature, |
| 22 | + /// Content signed by `signature` |
| 23 | + pub signed: serde_json::Value, |
| 24 | +} |
| 25 | + |
| 26 | +impl SignedResponse { |
| 27 | + /// Deserialize some bytes to JSON, and verify them, including signature and expiry. |
| 28 | + /// If successful, the deserialized data is returned. |
| 29 | + pub fn deserialize_and_verify(key: VerifyingKey, bytes: &[u8]) -> Result<Self, anyhow::Error> { |
| 30 | + let partial_data: PartialSignedResponse = |
| 31 | + serde_json::from_slice(bytes).context("Invalid version JSON")?; |
| 32 | + |
| 33 | + // Check if the key matches |
| 34 | + if partial_data.signature.keyid.0 != key.0 { |
| 35 | + anyhow::bail!("Unrecognized key"); |
| 36 | + } |
| 37 | + |
| 38 | + // Serialize to canonical json format |
| 39 | + let canon_data = json_canon::to_vec(&partial_data.signed) |
| 40 | + .context("Failed to serialize to canonical JSON")?; |
| 41 | + |
| 42 | + // Check if the data is signed by our key |
| 43 | + partial_data |
| 44 | + .signature |
| 45 | + .keyid |
| 46 | + .0 |
| 47 | + .verify_strict(&canon_data, &partial_data.signature.sig.0) |
| 48 | + .context("Signature verification failed")?; |
| 49 | + |
| 50 | + // Deserialize the canonical JSON to structured representation |
| 51 | + let signed_response: Response = |
| 52 | + serde_json::from_slice(&canon_data).context("Failed to deserialize response")?; |
| 53 | + |
| 54 | + // FIXME: Check expiry |
| 55 | + |
| 56 | + Ok(SignedResponse { |
| 57 | + signature: partial_data.signature, |
| 58 | + signed: signed_response, |
| 59 | + }) |
| 60 | + } |
| 61 | +} |
| 62 | + |
15 | 63 | /// JSON response signature
|
16 | 64 | #[derive(Deserialize)]
|
17 | 65 | pub struct ResponseSignature {
|
@@ -137,10 +185,19 @@ pub struct SpecificVersionArchitectureResponse {
|
137 | 185 | mod test {
|
138 | 186 | use super::*;
|
139 | 187 |
|
140 |
| - /// Test that a valid version response is successfully deserialized |
| 188 | + /// Test that a valid signed version response is successfully deserialized and verified |
141 | 189 | #[test]
|
142 |
| - fn test_response_deserialization() { |
143 |
| - let _: SignedResponse = |
144 |
| - serde_json::from_str(include_str!("../test-version-response.json")).unwrap(); |
| 190 | + fn test_response_deserialization_and_verification() { |
| 191 | + const TEST_PUBKEY: &str = |
| 192 | + "AEC24A08466F3D6A1EDCDB2AD3C234428AB9D991B6BEA7F53CB9F172E6CB40D8"; |
| 193 | + let pubkey = hex::decode(TEST_PUBKEY).unwrap(); |
| 194 | + let verifying_key = |
| 195 | + ed25519_dalek::VerifyingKey::from_bytes(&pubkey.try_into().unwrap()).unwrap(); |
| 196 | + |
| 197 | + SignedResponse::deserialize_and_verify( |
| 198 | + VerifyingKey(verifying_key), |
| 199 | + include_bytes!("../test-version-response.json"), |
| 200 | + ) |
| 201 | + .expect("expected valid signed version metadata"); |
145 | 202 | }
|
146 | 203 | }
|
0 commit comments