diff --git a/Cargo.toml b/Cargo.toml index efac211..99fc670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ base64-url = { version = "2.0.2" } ipld-core = { version = "0.4" } serde_ipld_dagjson = { version = "0.2", default-features = false, optional = true } serde_ipld_dagcbor = "0.6" +serde_json = "1" serde = "1" serde_derive = "1" thiserror = "1" diff --git a/src/codec.rs b/src/codec.rs index 281585c..437422e 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -91,7 +91,18 @@ impl TryFrom for JsonWebSignature { type Error = Error; fn try_from(value: Encoded) -> Result { - let link = Cid::try_from(value.payload.as_ref().ok_or(Error::NotJws)?.as_slice())?; + let payload = value.payload.as_ref().ok_or(Error::NotJws)?; + + let (link, pld) = match serde_json::from_slice::(payload.as_ref()) { + Ok(json) => { + let res = match crate::JsonPld(json).try_into().map_err(|_| Error::NotJws)? { + Ipld::Map(map) => map, + _ => return Err(Error::NotJws), + }; + (None, Some(res)) + } + Err(_) => (Some(Cid::try_from(payload.as_ref())?), None), + }; Ok(Self { payload: value .payload @@ -104,6 +115,7 @@ impl TryFrom for JsonWebSignature { .map(Signature::from) .collect(), link, + pld, }) } } diff --git a/src/lib.rs b/src/lib.rs index 18e1b71..c27bb8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,6 @@ use std::collections::BTreeMap; use ipld_core::{ cid::Cid, codec::{Codec, Links}, - ipld, ipld::Ipld, }; use serde_derive::Serialize; @@ -152,22 +151,62 @@ impl Codec for DagJsonCodec { #[derive(Clone, Debug, PartialEq, Serialize)] pub struct JsonWebSignature { /// CID link from the payload. - pub link: Cid, - + #[serde(skip_serializing_if = "Option::is_none")] + pub link: Option, /// The payload base64 url encoded. pub payload: String, - + #[serde(skip_serializing_if = "Option::is_none")] + /// The json payload with ipfs:// prefixed strings replaced with CIDs. + pub pld: Option>, /// The set of signatures. pub signatures: Vec, } -impl<'a> From<&'a JsonWebSignature> for Ipld { - fn from(value: &'a JsonWebSignature) -> Self { - ipld!({ - "payload": value.payload.to_owned(), - "signatures": value.signatures.iter().map(Ipld::from).collect::>(), - "link": value.link, - }) +#[derive(Clone, Debug, PartialEq)] +/// Wrapper around a json value to convert ipfs:// prefixed strings to CIDs. +struct JsonPld(serde_json::Value); + +impl TryFrom for Ipld { + type Error = anyhow::Error; + + fn try_from(value: JsonPld) -> Result { + // can we use a serde_ipld_* crate or something to handle most of this for for us? + match value.0 { + serde_json::Value::Null => Ok(Ipld::Null), + serde_json::Value::Bool(b) => Ok(Ipld::Bool(b)), + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + Ok(Ipld::Integer(i as i128)) + } else if let Some(f) = n.as_f64() { + // should we reject NaN and infinities? + Ok(Ipld::Float(f)) + } else { + anyhow::bail!("Invalid number. Should be unreachable") + } + } + serde_json::Value::String(s) => { + if let Some(str) = s.strip_prefix("ipfs://") { + let cid = Cid::try_from(str)?; + Ok(Ipld::Link(cid)) + } else { + Ok(Ipld::String(s)) + } + } + serde_json::Value::Array(arr) => { + let list = arr + .into_iter() + .map(|v| JsonPld(v).try_into()) + .collect::, _>>()?; + Ok(Ipld::List(list)) + } + serde_json::Value::Object(obj) => { + let map = obj + .into_iter() + .map(|(k, v)| JsonPld(v).try_into().map(|v| (k, v))) + .collect::, _>>()?; + Ok(Ipld::Map(map)) + } + } } } @@ -356,6 +395,8 @@ impl<'a> From<&'a Recipient> for Ipld { mod tests { use std::collections::BTreeMap; + use ipld_core::ipld; + use super::*; struct JwsFixture { @@ -425,7 +466,7 @@ mod tests { } = fixture_jws(); let (payload_b64, protected_b64, signature_b64) = fixture_jws_base64(&payload, &protected, &signature); - let link = Cid::try_from(base64_url::decode(&payload_b64).unwrap()).unwrap(); + let link = Some(Cid::try_from(base64_url::decode(&payload_b64).unwrap()).unwrap()); assert_roundtrip( DagJoseCodec, &JsonWebSignature { @@ -439,6 +480,7 @@ mod tests { signature: signature_b64, }], link, + pld: None, }, &ipld!({ "payload": payload, diff --git a/tests/fixtures.rs b/tests/fixtures.rs index cbbbd29..f85dbd4 100644 --- a/tests/fixtures.rs +++ b/tests/fixtures.rs @@ -100,6 +100,7 @@ test_fixture!(jws, "jws"); test_fixture!(jws_signature_1, "jws-signature-1"); test_fixture!(jws_signature_2, "jws-signature-2"); test_fixture!(jws_signatures, "jws-signatures"); +test_fixture!(jws_signature_pld, "jws-signature-pld"); test_fixture!(jwe_symmetric, "jwe-symmetric"); test_fixture!(jwe_asymmetric, "jwe-asymmetric"); test_fixture!(jwe_no_recipients, "jwe-no-recipients"); diff --git a/tests/fixtures/dag-jose.md b/tests/fixtures/dag-jose.md index 744d2f1..c3a54d7 100644 --- a/tests/fixtures/dag-jose.md +++ b/tests/fixtures/dag-jose.md @@ -22,7 +22,7 @@ Fixtures ### JWS -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jws/serial.dag-jose.cid) ``` @@ -74,7 +74,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWS with one signature -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jws-signature-1/serial.dag-jose.cid) ``` @@ -126,7 +126,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWS with another signature -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jws-signature-2/serial.dag-jose.cid) ``` @@ -178,7 +178,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWS with multiple signatures -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jws-signatures/serial.dag-jose.cid) ``` @@ -237,9 +237,101 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) } ``` +### JWS with payload + +This is the base32-encoded JSON payload for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): + +[testmark]:# (jws-signature-pld/serial.dag-jose.cid) +``` +bagcqceras6vcqjafsrhwfsgxmzd6g5c2vm3mfolbbubg5rfuhhnobay4m2vq +``` + +This is a DAG-JOSE object, in hexadecimal: + +[testmark]:# (jws-signature-pld/serial.dag-jose.hex) +``` +a2677061796c6f61645901717b2274657374223a227061796c6f6164222c22614c696e6b223a2269 +7066733a2f2f6261667962656967367876356e777068666d76636e656b74706e6f6a747333336a71 +6375616d37626d7965327062353461646e727463636a6c7375222c22617272223a5b22697066733a +2f2f6261667962656967367876356e777068666d76636e656b74706e6f6a747333336a716375616d +37626d7965327062353461646e727463636a6c7375222c226974656d31222c226974656d32225d2c +226e6573746564223a7b22614c696e6b223a22697066733a2f2f6261667962656967367876356e77 +7068666d76636e656b74706e6f6a747333336a716375616d37626d7965327062353461646e727463 +636a6c7375222c22617272223a5b22697066733a2f2f6261667962656967367876356e777068666d +76636e656b74706e6f6a747333336a716375616d37626d7965327062353461646e727463636a6c73 +75222c226974656d31222c226974656d32225d7d7d6a7369676e61747572657381a26970726f7465 +63746564507b22616c67223a2245533235364b227d697369676e61747572655840218f001e1401d2 +7e50a4ed68a6c7bcddde87ad759fa1f3e35ba89fe6f541ec3932df08f6f9d693f08d711d7e6ce0f6 +ee0b7a30668dabafc0be9c642c7ed1c4fb +``` + +When it is parsed, we should see these paths within the data +when we walk over it at the [data model](/docs/data-model/) level: + +[testmark]:# (jws-signature-pld/paths) +```text +payload +pld +pld/aLink +pld/arr +pld/arr/0 +pld/arr/1 +pld/arr/2 +pld/nested/aLink +pld/nested/arr +pld/nested/arr/0 +pld/nested/arr/1 +pld/nested/arr/2 +signatures +signatures/0 +signatures/0/protected +signatures/0/signature +``` + +If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) +(and prettyprint it), we should get this result: + +[testmark]:# (jws-signature-pld/datamodel.dag-json.pretty) +```json +{ + "payload": "eyJ0ZXN0IjoicGF5bG9hZCIsImFMaW5rIjoiaXBmczovL2JhZnliZWlnNnh2NW53cGhmbXZjbmVrdHBub2p0czMzanFjdWFtN2JteWUycGI1NGFkbnJ0Y2NqbHN1IiwiYXJyIjpbImlwZnM6Ly9iYWZ5YmVpZzZ4djVud3BoZm12Y25la3Rwbm9qdHMzM2pxY3VhbTdibXllMnBiNTRhZG5ydGNjamxzdSIsIml0ZW0xIiwiaXRlbTIiXSwibmVzdGVkIjp7ImFMaW5rIjoiaXBmczovL2JhZnliZWlnNnh2NW53cGhmbXZjbmVrdHBub2p0czMzanFjdWFtN2JteWUycGI1NGFkbnJ0Y2NqbHN1IiwiYXJyIjpbImlwZnM6Ly9iYWZ5YmVpZzZ4djVud3BoZm12Y25la3Rwbm9qdHMzM2pxY3VhbTdibXllMnBiNTRhZG5ydGNjamxzdSIsIml0ZW0xIiwiaXRlbTIiXX19", + "pld": { + "test": "payload", + "aLink": { + "/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu" + }, + "arr": [ + { + "/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu" + }, + "item1", + "item2" + ], + "nested": { + "aLink": { + "/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu" + }, + "arr": [ + { + "/": "bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu" + }, + "item1", + "item2" + ] + } + }, + "signatures": [ + { + "protected": "eyJhbGciOiJFUzI1NksifQ", + "signature": "IY8AHhQB0n5QpO1opse83d6HrXWfofPjW6if5vVB7Dky3wj2-daT8I1xHX5s4PbuC3owZo2rr8C-nGQsftHE-w" + } + ] +} +``` + ### JWE symmetric -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jwe-symmetric/serial.dag-jose.cid) ``` @@ -281,7 +373,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWE asymmetric -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jwe-asymmetric/serial.dag-jose.cid) ``` @@ -339,7 +431,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWE with no recipients -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jwe-no-recipients/serial.dag-jose.cid) ``` @@ -382,7 +474,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWE with one recipient -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jwe-recipient/serial.dag-jose.cid) ``` @@ -456,7 +548,7 @@ If we re-encoded this data in [DAG-JSON](/docs/codecs/known/dag-json/) ### JWE with multiple recipients -This is the base64url-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): +This is the base32-encoded CID for a DAG-JOSE object, when using SHA2-256 (multihash code 0x12): [testmark]:# (jwe-recipients/serial.dag-jose.cid) ```