New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UCAN 1.0-RC.1 #247
UCAN 1.0-RC.1 #247
Conversation
// 1. account -*-> server | ||
// 2. server -a-> device | ||
// 3. dnslink -d-> account | ||
// 4. [dnslink -d-> account -*-> server -a-> device] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From looking at the existing code, I think its built around the idea that there would be a /ucan/proxy
type?
We... may have a problem with powerlines where they help with the simple case but nothing more complex.
If I'm reading the scenario that you put together correctly, it looks something like this:
1. account -*-> server
2. account -*-------------> device
3. dnslink -d-> account
4. [dnslink -d-> account -*-------------> device]
Which powerlines handle perfectly 👍
// 2.
delegation::Payload {
subject: None,
issuer: account_did,
audience: device_did,
command: "/",
policy: vec![],
// ...
// 3.
delegation::Payload {
subject: Some(dnslink_did),
issuer: dnslink_did,
audience: account_did,
command: "/",
policy: vec![],
// ...
}}
// 4.
invocation::Payload {
subject: dnslink_did,
issuer: device_did,
prf: ["bafy...2", "bafy...3"]
// ...
}}
...but that doesn't help if the device gets revoked/lost. We need to either put a server in the middle, or reissue UCANs by keeping a signing key around (which would be nice to avoid). The problem with using a subject = None
from the server is that you're granting everything delegated to the server, which we obviously don't want.
1. account -*-> server
2. server -a-> device
3. dnslink -d-> account
4. [dnslink -d-> account -*-> server -a-> device]
This is much closer to the original design with a Proxy
ability...
pub struct Proxy<DID: Did, T> {
pub subject: DID,
pub proxy: T,
pub prf: Vec<Cid>
}
...which totally works iff you understand the special proxy type.
Anyhow, just something to ponder. I don't think we can wedge this into the core type, but worth putting some cycles into.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to either put a server in the middle, or reissue UCANs by keeping a signing key around (which would be nice to avoid). The problem with using a subject = None from the server is that you're granting everything delegated to the server, which we obviously don't want.
Yeah, the idea was to put the server in the middle, and yes, that would grant everything delegated to the server.
I was thinking of two ways forward:
- In the future, implement a mode where the user 'owns' their key, by means of BLS multisig + server cosigner n' stuff.
- In the future, implement multiple server keys in a hierarchy, where the 'root' key is the one that gets full control and is a BLS key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implement multiple server keys in a hierarchy
Just because it's unclear in the above text: is this the version in the design docs, or a new design that relies on BLS features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version from the design docs :)
let server_sk = ed25519_dalek::SigningKey::generate(&mut thread_rng()); | ||
let server_signer = | ||
ucan::did::preset::Signer::Key(ucan::did::key::Signer::EdDsa(server_sk)); | ||
let server_ed_did_key = EdDidKey::new(server_sk); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have incompatible wrappers 😅 Luckily they're all the same key types under the hood. Let's chat about what we like from each approach, and make One Key Wrapper to Rule them All
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Happy to just adopt your key wrapper. The EdDidKey
is still an artifact from times where rs_ucan
didn't have its own wrapper that worked for me.
server, | ||
"/".into(), | ||
ucan::time::Timestamp::five_years_from_now(), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
☝️ I threw this helper together quickly:
pub fn powerbox(issuer: DID, audience: DID, command: String, expiration: Timestamp) -> Self {
let mut seed = vec![];
let nonce = Nonce::generate_12(seed.as_mut());
Payload {
issuer,
subject: None,
audience,
command,
policy: vec![],
metadata: BTreeMap::new(),
nonce,
expiration,
not_before: None,
}
}
I expect that we'll have a bunch of these kind of utilities
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I keep thinking about switching command: String
to something more struct
ured, but ultimately this just does string comparison at runtime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an interesting idea to add helpers for these :)
IMO it would still make sense to expose a builder, so e.g. you'd have a nonce set by default, don't need to set metadata
or not_before
everytime, etc.
Helpers like powerbox
could then return the builder, too, so you could do "a powerbox, but with a slight twist" lol
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea; I'll add that with the builder macro now 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sweet. It uses &mut Self
style. My personal fav
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might actually extend this to the Delegation
envelope, too, so that you don't have to manually wrap: on build
(maybe a different name?) it will run signing etc. This also avoids having to lift multiple layers of failure (inner builder / outer builder)
// 2. server -a-> device | ||
let account_device_ucan = ucan::delegation::Payload { | ||
// subject: None, // Some(account_did), // FIXME | ||
subject: Some(account_did), // FIXME |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Note that this is adapted to the more complex powerline scenario mentioned in the comment at the top of the module.)
Why the FIXME
? This grants access to do anything ("/"
) with the account, not a powerline.
nonce: Nonce::generate_12(seed.as_mut()), | ||
expiration: ucan::time::Timestamp::five_years_from_now(), | ||
not_before: None | ||
} // FIXME sign |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these are signed at the moment. It's not difficult, I just haven't done it yet:
let varsig_header = ucan::crypto::varsig::header::EdDsaHeader {
codec: libipld::cbor::DagCborCodec,
};
let delegation = Delegation::try_sign(
&server_signer,
varsig_header,
delegation_payload
);
// or just
let delegation = Delegation::try_sign(
&server_signer,
varsig_header,
delegation::Payload {
issuer: server,
audience: device,
command: "/".into(),
// ...
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this should get further cleaned up so that devs don't have to explicitly build a varsig header
let delegation = Delegation::try_sign(
&server_signer,
codec, // Can build a varsig header from the signer + codec internally
delegation::Payload {
issuer: server,
audience: device,
command: "/".into(),
// ...
});
// FissionAbility::AccountInfo, | ||
// now(), | ||
// &did_verifier_map, | ||
// &store, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember how I said that I don't promise that anything actually works?
I need to fix my MemoryStore
because it depends on the DID
having Ord
, which they don't currently (going to use string/byte comparison).
Should we close this in favor of the test being moved to |
@matheus23 good point! |
No description provided.