diff --git a/flake.lock b/flake.lock index c13ba4a7..036ecc31 100644 --- a/flake.lock +++ b/flake.lock @@ -19,25 +19,6 @@ "type": "github" } }, - "devshell": { - "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1711099426, - "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", - "owner": "numtide", - "repo": "devshell", - "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, "flake-utils": { "inputs": { "systems": "systems" @@ -59,24 +40,6 @@ "inputs": { "systems": "systems_2" }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, "locked": { "lastModified": 1710146030, "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", @@ -93,11 +56,11 @@ }, "nixos-unstable": { "locked": { - "lastModified": 1711616573, - "narHash": "sha256-FvZiEl6D4iLXqSQ3oGjQ/qehhPZ5E7iTHr/YA1Rw8kY=", + "lastModified": 1722892243, + "narHash": "sha256-E0CKe8EJfdEA0Td/Z79AHpYuWg8H8U6RFUxaEaRVHdc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c99b66784962e8984444b4d9e72d00d3549afdb2", + "rev": "54a75f91a509dec6e474c9336830af230fce8d1a", "type": "github" }, "original": { @@ -123,60 +86,40 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1704161960, - "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "63143ac2c9186be6d9da6035fa22620018c85932", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1711460390, - "narHash": "sha256-akSgjDZL6pVHEfSE6sz1DNSXuYX6hq+P/1Z5IoYWs7E=", + "lastModified": 1722791413, + "narHash": "sha256-rCTrlCWvHzMCNcKxPE3Z/mMK2gDZ+BvvpEVyRM4tKmU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "44733514b72e732bd49f5511bd0203dea9b9a434", + "rev": "8b5b6723aca5a51edf075936439d9cd3947b7b2c", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "type": "indirect" } }, "root": { "inputs": { "command-utils": "command-utils", - "devshell": "devshell", - "flake-utils": "flake-utils_3", + "flake-utils": "flake-utils_2", "nixos-unstable": "nixos-unstable", - "nixpkgs": "nixpkgs_3", + "nixpkgs": "nixpkgs_2", "rust-overlay": "rust-overlay" } }, "rust-overlay": { "inputs": { - "flake-utils": [ - "flake-utils" - ], "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1711592024, - "narHash": "sha256-oD4OJ3TRmVrbAuKZWxElRCyCagNCDuhfw2exBmNOy48=", + "lastModified": 1722910815, + "narHash": "sha256-v6Vk/xlABhw2QzOa6xh3Jx/IvmlbKbOazFM+bDFQlWU=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "aa858717377db2ed8ffd2d44147d907baee656e5", + "rev": "7df2ac544c203d21b63aac23bfaec7f9b919a733", "type": "github" }, "original": { @@ -214,21 +157,6 @@ "repo": "default", "type": "github" } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 925581d7..74ae9831 100644 --- a/flake.nix +++ b/flake.nix @@ -2,13 +2,11 @@ description = "ucan"; inputs = { - nixpkgs.url = "nixpkgs/nixos-23.11"; + nixpkgs.url = "nixpkgs/nixos-24.05"; nixos-unstable.url = "nixpkgs/nixos-unstable-small"; command-utils.url = "github:expede/nix-command-utils"; - flake-utils.url = "github:numtide/flake-utils"; - devshell.url = "github:numtide/devshell"; rust-overlay = { url = "github:oxalica/rust-overlay"; @@ -19,7 +17,6 @@ outputs = { self, - devshell, flake-utils, nixos-unstable, nixpkgs, @@ -29,7 +26,6 @@ flake-utils.lib.eachDefaultSystem ( system: let overlays = [ - devshell.overlays.default (import rust-overlay) ]; @@ -102,7 +98,7 @@ "release:host" = cmd "Build release for ${system}" "${cargo} build --release"; - "release:wasm:web" = cmd "Build release for wasm32-unknown-unknown with web bindings" + "release:wasm:web" = cmd "Build release for wasm32-unknown-unknown with web bindings" "${wasm-pack} build --release --target=web"; "release:wasm:nodejs" = cmd "Build release for wasm32-unknown-unknown with Node.js bindgings" @@ -115,7 +111,7 @@ "build:wasm:web" = cmd "Build for wasm32-unknown-unknown with web bindings" "${wasm-pack} build --dev --target=web"; - + "build:wasm:nodejs" = cmd "Build for wasm32-unknown-unknown with Node.js bindgings" "${wasm-pack} build --dev --target=nodejs"; @@ -174,7 +170,7 @@ "test:host && test:docs && test:wasm"; "test:host" = cmd "Run Cargo tests for host target" - "${cargo} test --features=test_utils"; + "${cargo} test --features=mermaid_docs,test_utils"; "test:wasm" = cmd "Run wasm-pack tests on all targets" "test:wasm:node && test:wasm:chrome"; @@ -220,7 +216,6 @@ self.packages.${system}.irust (pkgs.hiPrio pkgs.rust-bin.nightly.latest.rustfmt) - pre-commit pkgs.wasm-pack chromedriver protobuf @@ -234,8 +229,6 @@ ++ lib.optionals stdenv.isDarwin darwin-installs; shellHook = '' - [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg - export RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache" unset SOURCE_DATE_EPOCH '' diff --git a/src/crypto/signature/envelope.rs b/src/crypto/signature/envelope.rs index 60165def..90011002 100644 --- a/src/crypto/signature/envelope.rs +++ b/src/crypto/signature/envelope.rs @@ -89,29 +89,6 @@ pub trait Envelope: Sized { Ok(w) } - /// Attempt to sign some payload with a given signer. - /// - /// # Arguments - /// - /// * `signer` - The signer to use to sign the payload. - /// * `payload` - The payload to sign. - /// - /// # Errors - /// - /// * [`SignError`] - the payload can't be encoded or the signature fails. - // FIXME ported - fn try_sign( - signer: &::Signer, - varsig_header: Self::VarsigHeader, - payload: Self::Payload, - ) -> Result - where - Ipld: Encode, - Named: From, - { - Self::try_sign_generic(signer, varsig_header, payload) - } - /// Attempt to sign some payload with a given signer and specific codec. /// /// # Arguments @@ -126,7 +103,7 @@ pub trait Envelope: Sized { /// /// # Example /// - fn try_sign_generic( + fn try_sign( signer: &::Signer, varsig_header: Self::VarsigHeader, payload: Self::Payload, diff --git a/src/crypto/varsig/header/traits.rs b/src/crypto/varsig/header/traits.rs index e6da044c..0132d689 100644 --- a/src/crypto/varsig/header/traits.rs +++ b/src/crypto/varsig/header/traits.rs @@ -2,13 +2,13 @@ use libipld_core::codec::{Codec, Encode}; use signature::Verifier; use thiserror::Error; -pub trait Header: for<'a> TryFrom<&'a [u8]> + Into> { +pub trait Header: for<'a> TryFrom<&'a [u8]> + Into> { type Signature: signature::SignatureEncoding; type Verifier: signature::Verifier; - fn codec(&self) -> &Enc; + fn codec(&self) -> &C; - fn encode_payload, Buf: std::io::Write>( + fn encode_payload, Buf: std::io::Write>( &self, payload: T, buffer: &mut Buf, @@ -16,7 +16,7 @@ pub trait Header: for<'a> TryFrom<&'a [u8]> + Into> { payload.encode(Self::codec(self).clone(), buffer) } - fn try_verify<'a, T: Encode>( + fn try_verify<'a, T: Encode>( &self, verifier: &'a Self::Verifier, signature: &'a Self::Signature, diff --git a/src/delegation.rs b/src/delegation.rs index a452f6b3..dae31f1d 100644 --- a/src/delegation.rs +++ b/src/delegation.rs @@ -28,10 +28,16 @@ use crate::{ did::{self, Did}, time::{TimeBoundError, Timestamp}, }; -use libipld_core::{codec::Codec, ipld::Ipld, link::Link}; +use core::str::FromStr; +use libipld_core::{ + codec::{Codec, Encode}, + ipld::Ipld, + link::Link, +}; use policy::Predicate; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use std::marker::PhantomData; use web_time::SystemTime; /// A [`Delegation`] is a signed delegation [`Payload`] @@ -46,7 +52,127 @@ pub struct Delegation< pub varsig_header: V, pub payload: Payload, pub signature: DID::Signature, - _marker: std::marker::PhantomData, + _marker: PhantomData, +} + +pub struct DelegationRequired< + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec = varsig::encoding::Preset, +> { + pub subject: Subject, + pub issuer: DID, + pub audience: DID, + pub command: String, + + pub codec: PhantomData, + pub varsig_header: V, + pub signer: DID::Signer, +} + +pub struct DelegationBuilder< + DID: Did = did::preset::Verifier, + V: varsig::Header = varsig::header::Preset, + C: Codec = varsig::encoding::Preset, +> { + subject: Subject, + issuer: DID, + audience: DID, + command: String, + + codec: PhantomData, + varsig_header: V, + signer: DID::Signer, + + via: Option, + policy: Vec, + metadata: BTreeMap, + nonce: Option, + expiration: Option, + not_before: Option, +} + +impl + Clone, C: Codec> DelegationRequired +where + Ipld: Encode, +{ + pub fn into_builder(self) -> DelegationBuilder { + DelegationBuilder { + subject: self.subject, + issuer: self.issuer, + audience: self.audience, + command: self.command, + + codec: self.codec, + varsig_header: self.varsig_header, + signer: self.signer, + + via: None, + policy: vec![], + metadata: Default::default(), + nonce: None, + expiration: None, + not_before: None, + } + } +} + +impl + Clone, C: Codec> DelegationBuilder +where + Ipld: Encode, +{ + pub fn try_sign(self) -> Result, crate::crypto::signature::SignError> + where + ::Err: std::fmt::Debug, + { + let payload = Payload { + subject: self.subject, + issuer: self.issuer, + audience: self.audience, + via: self.via, + + command: self.command, + policy: self.policy, + + nonce: self.nonce.unwrap_or(Nonce::generate_16()), + metadata: self.metadata, + + expiration: self.expiration, + not_before: self.not_before, + }; + + Delegation::try_sign(&self.signer, self.varsig_header, payload) + } + + pub fn via(mut self, via: DID) -> DelegationBuilder { + self.via = Some(via); + self + } + + pub fn policy(mut self, policy: Vec) -> DelegationBuilder { + self.policy = policy; + self + } + + pub fn nonce(mut self, nonce: Nonce) -> DelegationBuilder { + self.nonce = Some(nonce); + self + } + + pub fn metadata(mut self, metadata: BTreeMap) -> DelegationBuilder { + self.metadata = metadata; + self + } + + pub fn expiration(mut self, expiration: Timestamp) -> DelegationBuilder { + self.expiration = Some(expiration); + self + } + + pub fn not_before(mut self, not_before: Timestamp) -> DelegationBuilder { + self.not_before = Some(not_before); + self + } } #[derive(Clone, Debug, PartialEq)] @@ -72,7 +198,7 @@ impl, C: Codec> Delegation { varsig_header, payload, signature, - _marker: std::marker::PhantomData, + _marker: PhantomData, } } @@ -82,8 +208,8 @@ impl, C: Codec> Delegation { } /// Retrive the `subject` of a [`Delegation`] - pub fn subject(&self) -> Option<&DID> { - self.payload.subject.as_ref() + pub fn subject(&self) -> &Subject { + &self.payload.subject } /// Retrive the `audience` of a [`Delegation`] @@ -150,7 +276,7 @@ where varsig_header, payload, signature, - _marker: std::marker::PhantomData, + _marker: PhantomData, } } @@ -197,3 +323,140 @@ where Self::try_from_ipld_envelope(ipld).map_err(serde::de::Error::custom) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::varsig::{encoding, header}; + use assert_matches::assert_matches; + use rand::thread_rng; + use std::collections::BTreeMap; + use testresult::TestResult; + + fn gen_did() -> (crate::did::preset::Verifier, crate::did::preset::Signer) { + let sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let verifier = + crate::did::preset::Verifier::Key(crate::did::key::Verifier::EdDsa(sk.verifying_key())); + let signer = crate::did::preset::Signer::Key(crate::did::key::Signer::EdDsa(sk)); + + (verifier, signer) + } + + mod required { + use super::*; + + fn fixture() -> DelegationRequired { + let (alice, alice_signer) = gen_did(); + let (bob, _bob_signer) = gen_did(); + let (carol, _carol_signer) = gen_did(); + + DelegationRequired { + subject: Subject::Any, + issuer: alice, + audience: bob, + command: "/".to_string(), + + signer: alice_signer, + codec: PhantomData, + varsig_header: header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }), + } + } + + #[test_log::test] + fn test_sign() -> TestResult { + let delegation = fixture().into_builder().try_sign(); + assert_matches!(delegation, Ok(_)); + Ok(()) + } + + #[test_log::test] + fn test_sign_with_metadata() -> TestResult { + let meta = BTreeMap::from_iter([("foo".into(), 123.into())]); + let delegation = fixture().into_builder().metadata(meta.clone()).try_sign()?; + + assert_eq!(delegation.metadata(), &meta); + Ok(()) + } + + #[test_log::test] + fn test_sign_with_via() -> TestResult { + let (alice, _) = gen_did(); + let delegation = fixture().into_builder().via(alice.clone()).try_sign()?; + assert_eq!(delegation.via(), Some(alice).as_ref()); + Ok(()) + } + + #[test_log::test] + fn test_sign_with_policy() -> TestResult { + let pred = Predicate::Equal(FromStr::from_str(".foo")?, 123.into()); + let delegation = fixture() + .into_builder() + .policy(vec![pred.clone()]) + .try_sign()?; + + assert_eq!(delegation.policy(), &vec![pred]); + Ok(()) + } + + #[test_log::test] + fn test_sign_with_expiration() -> TestResult { + let exp = Timestamp::now(); + let delegation = fixture() + .into_builder() + .expiration(exp.clone()) + .try_sign()?; + + assert_eq!(delegation.expiration(), Some(&exp)); + Ok(()) + } + + #[test_log::test] + fn test_sign_with_not_before() -> TestResult { + let nbf = Timestamp::now(); + let delegation = fixture() + .into_builder() + .not_before(nbf.clone()) + .try_sign()?; + + assert_eq!(delegation.not_before(), Some(&nbf)); + Ok(()) + } + } + + mod builder { + use super::*; + + fn fixture() -> Result { + let (alice, alice_signer) = gen_did(); + let (bob, bob_signer) = gen_did(); + let (carol, carol_signer) = gen_did(); + + DelegationRequired { + subject: Subject::Any, + issuer: alice, + audience: bob, + command: "/".to_string(), + + signer: alice_signer, + codec: PhantomData, + varsig_header: header::Preset::EdDsa(header::EdDsaHeader { + codec: encoding::Preset::DagCbor, + }), + } + .into_builder() + .via(carol) + .policy(vec![]) + .metadata(BTreeMap::from_iter([("foo".into(), 123.into())])) + .try_sign() + } + + #[test_log::test] + fn test_full_builder() -> TestResult { + let delegation = fixture(); + assert_matches!(delegation, Ok(_)); + Ok(()) + } + } +} diff --git a/src/delegation/agent.rs b/src/delegation/agent.rs index 9b050878..f13ecba0 100644 --- a/src/delegation/agent.rs +++ b/src/delegation/agent.rs @@ -2,6 +2,7 @@ use super::{payload::Payload, policy::Predicate, store::Store, Delegation}; use crate::{ ability::arguments::Named, crypto::{signature::Envelope, varsig, Nonce}, + delegation::Subject, did, did::Did, time::Timestamp, @@ -58,7 +59,7 @@ where pub fn delegate( &self, audience: DID, - subject: Option<&DID>, + subject: Subject<&DID>, via: Option, command: String, new_policy: Vec, @@ -71,9 +72,11 @@ where let nonce = Nonce::generate_16(); let (subject, policy) = match subject { - Some(subject) if *subject == self.did => (Some(subject.clone()), new_policy), - None => (None, new_policy), - Some(subject) => { + Subject::Known(subject) if *subject == self.did => { + (Subject::Known(subject.clone()), new_policy) + } + Subject::Any => (Subject::Any, new_policy), + Subject::Known(subject) => { let proofs = &self .store .get_chain(&self.did, &subject, &command, vec![], now) @@ -83,7 +86,7 @@ where let mut policy = to_delegate.policy.clone(); policy.extend(new_policy); - (Some(subject.clone()), policy) + (Subject::Known(subject.clone()), policy) } }; @@ -101,6 +104,7 @@ where }; Ok(Delegation::try_sign(&self.signer, varsig_header, payload).expect("FIXME")) + // FIXME } pub fn receive( diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 4446623b..80226c12 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -8,7 +8,6 @@ use crate::{ time::{TimeBoundError, Timestamp}, }; use core::str::FromStr; -use derive_builder::Builder; use libipld_core::ipld::Ipld; use std::{collections::BTreeMap, fmt::Debug}; use thiserror::Error; @@ -24,7 +23,7 @@ use crate::ipld; /// /// This contains the semantic information about the delegation, including the /// issuer, subject, audience, the delegated ability, time bounds, and so on. -#[derive(Debug, Clone, PartialEq, Builder)] // FIXME Serialize, Deserialize, Builder)] +#[derive(Debug, Clone, PartialEq)] pub struct Payload { /// The subject of the [`Delegation`]. /// @@ -36,7 +35,7 @@ pub struct Payload { /// by the subject. /// /// [`Delegation`]: super::Delegation - pub subject: Option, + pub subject: Subject, /// The issuer of the [`Delegation`]. /// @@ -50,42 +49,102 @@ pub struct Payload { pub audience: DID, /// A [`Did`] that must be in the delegation chain at invocation time. - #[builder(default)] pub via: Option, /// The command being delegated. pub command: String, /// Any [`Predicate`] policies that constrain the `args` on an [`Invocation`][crate::invocation::Invocation]. - #[builder(default)] pub policy: Vec, /// Extensible, free-form fields. - #[builder(default)] pub metadata: BTreeMap, /// A [cryptographic nonce] to ensure that the UCAN's [`Cid`] is unique. /// /// [cryptograpgic nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce /// [`Cid`]: libipld_core::cid::Cid ; - #[builder(default = "Nonce::generate_16()")] pub nonce: Nonce, /// The latest wall-clock time that the UCAN is valid until, /// given as a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time - #[builder(default)] pub expiration: Option, /// An optional earliest wall-clock time that the UCAN is valid from, /// given as a [Unix timestamp]. /// /// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time - #[builder(default)] pub not_before: Option, } +pub struct Required { + pub subject: Subject, + pub issuer: DID, + pub audience: DID, + pub command: String, +} + +impl From> for Payload { + fn from(required: Required) -> Self { + Payload { + subject: required.subject, + issuer: required.issuer, + audience: required.audience, + command: required.command, + policy: vec![], + metadata: BTreeMap::new(), + nonce: Nonce::generate_16(), + expiration: None, + not_before: None, + via: None, + } + } +} + +impl Required { + pub fn build(self) -> Payload { + self.into() + } + + pub fn with_via(self, via: Option) -> Payload { + let mut payload: Payload = self.into(); + payload.via = via; + payload + } + + pub fn with_policy(self, policy: Vec) -> Payload { + let mut payload: Payload = self.into(); + payload.policy = policy; + payload + } + + pub fn with_metadata(self, metadata: BTreeMap) -> Payload { + let mut payload: Payload = self.into(); + payload.metadata = metadata; + payload + } + + pub fn with_nonce(self, nonce: Nonce) -> Payload { + let mut payload: Payload = self.into(); + payload.nonce = nonce; + payload + } + + pub fn with_expiration(self, expiration: Option) -> Payload { + let mut payload: Payload = self.into(); + payload.expiration = expiration; + payload + } + + pub fn with_not_before(self, not_before: Option) -> Payload { + let mut payload: Payload = self.into(); + payload.not_before = not_before; + payload + } +} + impl Payload { pub fn check_time(&self, now: SystemTime) -> Result<(), TimeBoundError> { let ts_now = &Timestamp::postel(now); @@ -104,6 +163,53 @@ impl Payload { Ok(()) } + + pub fn with_via(&mut self, via: Option) -> &mut Payload { + self.via = via; + self + } + + pub fn with_policy(&mut self, policy: Vec) -> &mut Payload { + self.policy = policy; + self + } + + pub fn with_metadata(&mut self, metadata: BTreeMap) -> &mut Payload { + self.metadata = metadata; + self + } + + pub fn with_nonce(&mut self, nonce: Nonce) -> &mut Payload { + self.nonce = nonce; + self + } + + pub fn with_expiration(&mut self, expiration: Option) -> &mut Payload { + self.expiration = expiration; + self + } + + pub fn with_not_before(&mut self, not_before: Option) -> &mut Payload { + self.not_before = not_before; + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] +pub enum Subject { + Known(DID), + Any, +} + +impl Ord for Subject { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match (self, other) { + (Subject::Any, Subject::Any) => core::cmp::Ordering::Equal, + (Subject::Any, Subject::Known(_)) => core::cmp::Ordering::Less, + (Subject::Known(_), Subject::Any) => core::cmp::Ordering::Greater, + (Subject::Known(a), Subject::Known(b)) => a.cmp(b), + } + } } impl Capsule for Payload { @@ -138,10 +244,10 @@ where match k.as_str() { "sub" => { subject = Some(match ipld { - Ipld::Null => None, - Ipld::String(s) => { - Some(DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?) - } + Ipld::Null => Subject::Any, + Ipld::String(s) => Subject::Known( + DID::from_str(s.as_str()).map_err(ParseError::DidParseError)?, + ), bad => return Err(ParseError::WrongTypeForField("sub".to_string(), bad)), }) } @@ -318,7 +424,7 @@ impl From> for Named { ), ]); - if let Some(subject) = payload.subject { + if let Subject::Known(subject) = payload.subject { args.insert("sub".to_string(), Ipld::String(subject.to_string())); } else { args.insert("sub".to_string(), Ipld::Null); @@ -367,7 +473,7 @@ where ) .prop_map( |( - subject, + maybe_subject, issuer, audience, command, @@ -380,7 +486,6 @@ where )| { Payload { issuer, - subject, audience, command, policy, @@ -389,6 +494,10 @@ where expiration, not_before, via, + subject: match maybe_subject { + None => Subject::Any, + Some(s) => Subject::Known(s), + }, } }, ) @@ -453,10 +562,10 @@ mod tests { // Optional Fields match (payload.subject, named.get("sub")) { - (Some(sub), Some(Ipld::String(s))) => { + (Subject::Known(sub), Some(Ipld::String(s))) => { prop_assert_eq!(&sub.to_string(), s); } - (None, Some(Ipld::Null)) => prop_assert!(true), + (Subject::Any, Some(Ipld::Null)) => prop_assert!(true), _ => prop_assert!(false) } diff --git a/src/delegation/store/memory.rs b/src/delegation/store/memory.rs index bf1c4cab..cccaf7c7 100644 --- a/src/delegation/store/memory.rs +++ b/src/delegation/store/memory.rs @@ -3,7 +3,7 @@ use crate::ability::arguments::Named; use crate::delegation; use crate::{ crypto::varsig, - delegation::{policy::Predicate, Delegation}, + delegation::{policy::Predicate, Delegation, Subject}, did::{self, Did}, }; use libipld_core::codec::Encode; @@ -90,7 +90,7 @@ struct MemoryStoreInner< C: Codec = varsig::encoding::Preset, > { ucans: BTreeMap>>, - index: BTreeMap, BTreeMap>>, + index: BTreeMap, BTreeMap>>, revocations: BTreeSet, } @@ -160,7 +160,7 @@ where let mut tx = self.lock(); tx.index - .entry(delegation.subject().cloned()) + .entry(delegation.subject().clone()) .or_default() .entry(delegation.audience().clone()) .or_default() @@ -188,8 +188,12 @@ where let blank_map = BTreeMap::new(); let tx = self.lock(); - let all_powerlines = tx.index.get(&None).unwrap_or(&blank_map); - let all_aud_for_subject = tx.index.get(&Some(subject.clone())).unwrap_or(&blank_map); + let all_powerlines = tx.index.get(&Subject::Any).unwrap_or(&blank_map); + let all_aud_for_subject = tx + .index + .get(&Subject::Known(subject.clone())) + .unwrap_or(&blank_map); + let powerline_candidates = all_powerlines.get(aud).unwrap_or(&blank_set); let sub_candidates = all_aud_for_subject.get(aud).unwrap_or(&blank_set); @@ -251,7 +255,7 @@ where let issuer = delegation.issuer().clone(); // Hit a root delegation, AKA base case - if Some(&issuer) == delegation.subject() { + if Subject::Known(issuer.clone()) == *delegation.subject() { break 'outer; } @@ -327,12 +331,13 @@ mod tests { let deleg = Delegation::try_sign( &signer, varsig_header, - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(did.clone()) - .audience(did.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: Subject::Any, + issuer: did.clone(), + audience: did.clone(), + command: "/".into(), + } + .into(), )?; store.insert(deleg.clone())?; @@ -355,12 +360,13 @@ mod tests { let deleg = Delegation::try_sign( &signer, varsig_header, - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(did.clone()) - .audience(did.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: Subject::Any, + issuer: did.clone(), + audience: did.clone(), + command: "/".into(), + } + .into(), )?; store.insert(deleg.clone())?; @@ -413,12 +419,13 @@ mod tests { let deleg = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: alice.clone(), + audience: bob.clone(), + command: "/".into(), + } + .into(), )?; store.insert(deleg.clone())?; @@ -444,12 +451,13 @@ mod tests { let noise = crate::Delegation::try_sign( &bob_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(bob.clone()) - .audience(carol.clone()) - .command("/example".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: bob.clone(), + audience: carol.clone(), + command: "/example".into(), + } + .into(), )?; store.insert(noise.clone())?; @@ -457,12 +465,13 @@ mod tests { let deleg = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: alice.clone(), + audience: bob.clone(), + command: "/".into(), + } + .into(), )?; store.insert(deleg.clone())?; @@ -470,12 +479,13 @@ mod tests { let more_noise = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(alice.clone()) - .audience(carol.clone()) - .command("/test".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: alice.clone(), + audience: carol.clone(), + command: "/test".into(), + } + .into(), )?; store.insert(more_noise.clone())?; @@ -501,12 +511,13 @@ mod tests { let deleg_1 = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: alice.clone(), + audience: bob.clone(), + command: "/".into(), + } + .into(), )?; store.insert(deleg_1.clone())?; @@ -514,12 +525,13 @@ mod tests { let deleg_2 = crate::Delegation::try_sign( &bob_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(alice.clone())) - .issuer(bob.clone()) - .audience(carol.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Known(alice.clone()), + issuer: bob.clone(), + audience: carol.clone(), + command: "/".into(), + } + .into(), )?; store.insert(deleg_2.clone())?; @@ -555,12 +567,13 @@ mod tests { let deleg_1 = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/test".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: alice.clone(), + audience: bob.clone(), + command: "/test".into(), + } + .into(), )?; store.insert(deleg_1.clone())?; @@ -568,12 +581,13 @@ mod tests { let deleg_2 = crate::Delegation::try_sign( &bob_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(alice.clone())) - .issuer(bob.clone()) - .audience(carol.clone()) - .command("/test/me".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Known(alice.clone()), + issuer: bob.clone(), + audience: carol.clone(), + command: "/test/me".into(), + } + .into(), )?; store.insert(deleg_2.clone())?; @@ -616,12 +630,13 @@ mod tests { let alice_to_bob = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/test".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: alice.clone(), + audience: bob.clone(), + command: "/test".into(), + } + .into(), )?; store.insert(alice_to_bob.clone())?; @@ -629,12 +644,13 @@ mod tests { let carol_to_dan = crate::Delegation::try_sign( &carol_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(alice.clone())) - .issuer(carol.clone()) - .audience(dan.clone()) - .command("/test/me".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Known(alice.clone()), + issuer: carol.clone(), + audience: dan.clone(), + command: "/test/me".into(), + } + .into(), )?; store.insert(carol_to_dan.clone())?; @@ -675,36 +691,39 @@ mod tests { let bob_to_carol = crate::Delegation::try_sign( &bob_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(bob.clone()) - .audience(carol.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: bob.clone(), + audience: carol.clone(), + command: "/".into(), + } + .into(), )?; // 2. carol -a-> dave let carol_to_dave = crate::Delegation::try_sign( &carol_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(carol.clone()) - .audience(dave.clone()) - .command("/".into()) - .build()?, // I don't love this is now failable + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: carol.clone(), + audience: dave.clone(), + command: "/".into(), + } + .into(), )?; // 3. alice -d-> bob let alice_to_bob = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(alice.clone())) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Known(alice.clone()), + issuer: alice.clone(), + audience: bob.clone(), + command: "/".into(), + } + .into(), )?; store.insert(bob_to_carol.clone())?; @@ -756,36 +775,39 @@ mod tests { let bob_to_carol = crate::Delegation::try_sign( &bob_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(bob.clone()) - .audience(carol.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: bob.clone(), + audience: carol.clone(), + command: "/".into(), + } + .into(), )?; // 2. carol -a-> dave let carol_to_dave = crate::Delegation::try_sign( &carol_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(carol.clone()) - .audience(dave.clone()) - .command("/".into()) - .build()?, // I don't love this is now failable + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: carol.clone(), + audience: dave.clone(), + command: "/".into(), + } + .into(), )?; // 3. alice -d-> bob let alice_to_bob = crate::Delegation::try_sign( &alice_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(alice.clone())) - .issuer(alice.clone()) - .audience(bob.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Known(alice.clone()), + issuer: alice.clone(), + audience: bob.clone(), + command: "/".into(), + } + .into(), )?; store.insert(bob_to_carol.clone())?; diff --git a/src/invocation/agent.rs b/src/invocation/agent.rs index ee040a21..afd68eb2 100644 --- a/src/invocation/agent.rs +++ b/src/invocation/agent.rs @@ -638,36 +638,39 @@ mod tests { let account_to_server = crate::Delegation::try_sign( &account_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(None) - .issuer(account.clone()) - .audience(server.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: account.clone(), + audience: server.clone(), + command: "/".into(), + } + .into(), )?; // 2. server -a-> device let server_to_device = crate::Delegation::try_sign( &server_signer, varsig_header.clone(), // FIXME can also put this on a builder - crate::delegation::PayloadBuilder::default() - .subject(None) // FIXME needs a sibject when we figure out powerbox - .issuer(server.clone()) - .audience(device.clone()) - .command("/".into()) - .build()?, // I don't love this is now failable + crate::delegation::Required { + subject: crate::delegation::Subject::Any, + issuer: server.clone(), + audience: device.clone(), + command: "/".into(), + } + .into(), )?; // 3. dnslink -d-> account let dnslink_to_account = crate::Delegation::try_sign( &dnslink_signer, varsig_header.clone(), - crate::delegation::PayloadBuilder::default() - .subject(Some(dnslink.clone())) - .issuer(dnslink.clone()) - .audience(account.clone()) - .command("/".into()) - .build()?, + crate::delegation::Required { + subject: crate::delegation::Subject::Known(dnslink.clone()), + issuer: dnslink.clone(), + audience: account.clone(), + command: "/".into(), + } + .into(), )?; del_store.insert(account_to_server.clone())?; diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 4543046f..e146387e 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -8,6 +8,7 @@ use crate::{ delegation::{ self, policy::{selector::SelectorError, Predicate}, + Subject, }, did::{Did, Verifiable}, time::{Expired, Timestamp}, @@ -179,7 +180,7 @@ impl Payload { return Err(ValidationError::MisalignedIssAud.into()); } - if let Some(proof_subject) = &proof.subject { + if let Subject::Known(proof_subject) = &proof.subject { if self.subject != *proof_subject { return Err(ValidationError::InvalidSubject.into()); }