-
Notifications
You must be signed in to change notification settings - Fork 10
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
Role struct constructors #99
Comments
Thanks for the feedback. See below.
Note that these are not the ephemeral keys. These are the public keys present in the corresponding CRED_I/CRED_R. For the moment, we do not parse the credentials for the sake of simplicity but rather use these values in the corresponding ECDH calculations.
This is indeed a current limitation of the API. We would be happy to accept a PR on this.
True. I went through Note that with the evolution of the hacspec framework, we can now use it on the vanilla Rust branch of the code. That means that we will be getting rid of the hacspec version of the code quite soon, so if you are going to work on the code base, I would suggest doing so on |
That's good to hear!
Right, I mixed those up. (So then, providing the g_peer will be up to whatever provides the cred_peer for the provided id_cred_peer).
Those two have large For testing of upcoming PRs, is there anything I should do locally, or can I trust that CI will complain if I leave the hacspec parts behind somehow? |
Note that only
The CI does all the necessary steps, but the hacspec checks will run only on the hacspec part of the code. So, for now, work on the Rust code and we can test later the hacspec aspects. (Please do not use early returns as these won't work when hacspec-checked). |
Looking at this with some evenings into the library, and looking at all the data passed in at the constructor, it seems to me that the end game of a
This can all be expressed as extra arguments and return values in the right functions (and implementing it is one of my next steps). However, I'm wondering whether given the strict sequence (out-of-sequence calls make error go WrongState anyway), and given that the So a mid-term proposal could look like let a: EdhocResponder = Default::default();
let (a, ead1) = a.process_message_1(); // now a is of type EdhocResponderPhase1
let (a, m2) = a.prepare_message_2(...); // now a is of type EdhocResponderPhase2 and any error in sequence would be spotted by the type checker, instead of returning WrongState at runtime. There'd probably still be checks for the state (because the inner State object would stay the same, and we can't easily express its invariant to Rust), but some good const propagation could still make them go away completely. |
Trying around, this is going to affect edhoc.rs more, because we'll have to split up
However, the alternative is to decide CRED_I / G_I in a callback from within r_process_message_3, which I can't imagine makes anything any better, especially because that callback may block / may require async. (For example if looking up the credential takes time b/c it happens over a network and we have a cache miss). Let's ignore 1 for the time being -- it may be a lot of data but it's bounded as long as we have limits on the EAD size we can process. I'd like to sketch a proposal first -- this would replace what was previously a let (state, mac_3, id_cred_i, ead_3_untrusted) = r_process_message_3a(state, message_3)?;
let (cred_i, g_i) = look_up_credentials(&id_cred_i); // may also use ead_3_untrusted
let (state, prk_out) = r_process_message_3b(state, mac_3, id_cred_i, ead_3_untrusted, cred_i, g_i)?;
// As that was successful, we may now also rename ead_3_untrusted to ead_3 Assembling the check from a mac_3 and the ead_3_untrusted is the alternative to passing in the message_3 once more -- none of which I'm really happy with, given that all things might go wrong if the user sends in the wrong mac_3 / ead_3_untrusted. The alternative is lugging them around with the state (which gets moved around anyway), but I'm not sure how well this would fly with hax: let state_and_more = r_process_message_3a(state, message_3)?; // different type from `state`
let (cred_i, g_i) = look_up_credentials(state_and_more.id_cred_i()); // may also use accessor for ead_3_untrusted
let (state, prk_out) = r_process_message_3b(state_and_more, cred_i, g_i)?;
// ead_3 is now lost, but had we cloned it out, we could now trust it
// (If we were willing to carry it around, the latest state could also keep it; the compiler should
// throw it away anyway if unused if LTO is used) I have a soft preference for approach 2 (which also moves the State even of the (One way of addressing the question is: Where does the verifier even know what |
Agreed.
Indeed, we organized the library in this manner for exactly the reason you outline. We can and should provide an idiomatic API in
This looks great!
Just to make sure we are on the same page here, when you refer to the "inner" State object, do you mean the value of state passed to the hacspec-verified functions? Don't we still need those checks to verify the correct progress of hte state machine? e.g. how will typeset make sure that we do not process the same message twice if such a message was received over the wire? |
They will, but may use results from the outside in const propagation. Ideally, we could inform the compiler that an invariant needs to be upheld -- say, |
Before we discard the callback option, let me understand the pitfalls a bit better. I understand that retrieving Other than that, on splitting the message 3 processing: I think that approach 1 is dangerous as it lets the application handle internal state of the protocol. I think it is out of the question. As per approach 2, IIUC, you would essentially be adding It would be great if @karthikbhargavan could chime in here. |
A lot of embedded Rust environments don't block, but run on async instead. While it's certainly possible to have versions of the r_process_message_3 function around for blocking and async evaluation, I doubt that'd fly well with the verification, and I also don't think it's pretty (which is really an odd term to say that I think it's not good practice but can't express the arguments well yet).
To understand better the impact of this I'd like to add to the open questions: Why is the state struct that's passed around between the phases of the protocol of the same type all the time? AFAICT, every function takes it only in one state and on exit it is in one state (unless there's an error), so why not typestate in the first place? (That'd make carrying mac_3 and ead_3 less hard). |
There is no good explanation for this. We discussed previously splitting it but have not implemented it yet. |
The original points have been addressed. Thanks, closing this. |
Attempting to use edhoc-rs in a demo application, I found a few properties of the role constructors (eg.
EdhocInitiator
) odd:The g_i / g_r is fed in explicitly -- from an application point of view I'd expect the library to either use the platform's random number generator (or, when that is not possible, to provide a random number callback that'll pull as many bytes as needed).[edit: see Role struct constructors #99 (comment), these are the long-term keys]All those binary arguments are passed in hex encoded strings -- a conversion I'd prefer never to do on an embedded system.[edit:fixed in API: Use u8 instead of hex str #101]I think I could provide patches for most of those, but these might be here for a reason. Could you explain why those are as they are, and whether those are boundary conditions set up by hacspec, or maybe just remnants of earlier development phases? If hacspec tightens things down here, do you have a roadmap for how to provide additional interfaces for constrained devices when hacspec limitations do not apply?
The text was updated successfully, but these errors were encountered: