-
Notifications
You must be signed in to change notification settings - Fork 111
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
Use AttestationVerifier in the Rust Client #4655
Use AttestationVerifier in the Rust Client #4655
Conversation
cb6514a
to
77d282e
Compare
24464bc
to
d6bb63b
Compare
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.
Thanks
|
||
impl AttestationVerifier for InsecureAttestationVerifier { | ||
fn verify(&self, evidence: &Evidence, _: &Endorsements) -> anyhow::Result<DiceChainResult> { | ||
verify_dice_chain(evidence).context("couldn't verify the DICE chain") |
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.
Why do we need AttestationVerifier, InsecureAttestationVerifier - couldn't we just expose the function, i.e. a variant of verify which doesn't have the reference_values parameter? My idea so far was to use only static functions in the interface.
Also, I have no idea where a SecureAttestationVerifier would fit into the pattern.
How about this:
pub fn verify_minimal(
now_utc_millis: i64,
evidence: &Evidence,
endorsements: &Endorsements,
) -> anyhow::Result < DiceChainResult > {
verify_dice_chain(evidence)
}
This is a shortcut to verify which behaves identically to an actual verification, but with assumed (but not existent) ReferenceValues skipping over every single dispensable step. This would be much clearer IMO.
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.
Currently our client library accepts an implementation of the AttestationVerifier
And InsecureAttestationVerifier
is just one of those implementations. If we provide a verify_minimal
we would have to hardcode it into the client code.
The SecureAttestationVerifier
will be just another implementation of the AttestationVerifier
which may look like this:
struct SecureAttestationVerifier {
reference_values: ReferenceValues,
}
impl SecureAttestationVerifier {
fn create(reference_values: ReferenceValues) -> Self {...}
}
impl AttestationVerifier for SecureAttestationVerifier {
fn verify(evidence, endorsements) -> {
// Use self.reference_values
}
}
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.
This interface also allows us to create specific verifiers (i.e. OakFunctionsAttestationVerifier
) which will create reference values during the create
method. So that our prod clients won't need to construct reference values themselves (if they want to rely of the default ones).
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.
// Use only for testing, not in production.
pub fn verify_minimal(
evidence: &Evidence,
) -> anyhow::Result < DiceChainResult > {
verify_dice_chain(evidence)
}
(Alternative to the above)
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.
couldn't we just expose the function, i.e. a variant of verify which doesn't have the reference_values parameter? My idea so far was to use only static functions in the interface.
This function will never be seen by our prod teams - they will only see the OakClient
class that we provide to them. So it's OakClient
that will need to either use the minimal verification for testing or use actual verification.
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.
Yes, this interface is just there for prod teams' convenience - i.e. it a part of the client SDK and doesn't have to the the core logic of the attestation library.
I can move it into the oak_client
crate. And make InsecureAttestationVerifier
call the verify_minimal
function.
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.
Though we would still need to implement other verifiers (i.e. CustomAttestationVerifier
that accepts any reference values, or application specific verifiers like OakFunctionsAttestationVerifier
).
Where should we put those?
The same question about C++ and Java
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.
Ideally they would belong to that module containing the present AttestationVerifier Rust structs, with all of its impl. However, the "custom" part seems to be the addition of a ReferenceValues proto instance which will always be hard-wired. That should be contained in the "custom" place, i.e. OakFunctionsAttestationVerifier would not exist (or it would exist in private somewhere in oak_functions_*.) OakFunctionsAttestationVerifier (if it needs to be named) would just be the CustomAttestationVerifier and add its static instance of ReferenceValues.
Assuming that CustomAttestationVerifier and SecureAttestationVerifier are the same thing.
For C++ and Java: the JNI interface would just connect/implement the static public verify functions (all variants), so the wrappers would be reimplemented in the respective language.
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.
Moved InsecureAttestationVerifier
to oak_client
crate. But the AttestationVerifier
is currently still in the oak_attestation_verification
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.
Update: moved the trait to oak_client
as well
|
||
impl AttestationVerifier for InsecureAttestationVerifier { | ||
fn verify(&self, evidence: &Evidence, _: &Endorsements) -> anyhow::Result<DiceChainResult> { | ||
verify_dice_chain(evidence).context("couldn't verify the DICE chain") |
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.
// Use only for testing, not in production.
pub fn verify_minimal(
evidence: &Evidence,
) -> anyhow::Result < DiceChainResult > {
verify_dice_chain(evidence)
}
(Alternative to the above)
&self, | ||
evidence: &Evidence, | ||
endorsements: &Endorsements, | ||
) -> anyhow::Result<DiceChainResult>; |
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 thought we'd return AttestationResults?
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 had this at first, after some discussion we ended up addressing that by trait, so we keep an idiomatic Rust version on the inside.
Example:
let result = verify(NOW_UTC_MILLIS, &evidence, &endorsements, &reference_values);
let att_res = AttestationResults::from(&result);
Would this work with the JNI bindings?
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 think current JNI bindings are relying on just the AttestationResults
. But there is a wrapper layer between the JNI bindings and the actual verification code.
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.
Yeah, it's definitely possible to do a conversion in the wrapper layer. I'm rather wondering about what the right interface for AttestationVerifier should be - do we want to expose DiceChainResult at all?
It's technically possible to make Java interface return AttestationResults and to do the conversion in the wrapper but it feels a bit weird in the Java interface and Rust trait diverge too much in terms of signature... WDYT?
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 will rename to DiceChainResult, or address this in some other way, in a different PR.
oak_functions_client/src/lib.rs
Outdated
} | ||
|
||
impl OakFunctionsClient { | ||
pub async fn new(uri: &str) -> anyhow::Result<Self> { | ||
pub async fn new<V: AttestationVerifier>(uri: &str, verifier: &V) -> anyhow::Result<Self> { |
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 don't think we need a generic here, a Box dyn would probably be a bit simpler (also in OakClient::create), but up to you.
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.
Will it require always creating it as a Box
?
I.e. Box::new(InsecureAttestationVerifier{})
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 think so -- unless verifier: & dyn AttestationVerifier
works?
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.
Done
Made it verifier: &dyn AttestationVerifier
let channel = Channel::from_shared(uri.to_string()) | ||
.context("couldn't create gRPC channel")? | ||
.connect() | ||
.await | ||
.context("couldn't connect via gRPC channel")?; | ||
let transport = GrpcStreamingTransport::new(StreamingSessionClient::new(channel)); | ||
let oak_client = OakClient::create(transport) | ||
let oak_client = OakClient::create(transport, verifier) |
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.
BTW Why was this method called create instead of new?
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'm generally trying to call constructors that can fail as create
to differentiate them from new
that always succeeds
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.
Thanks!
&self, | ||
evidence: &Evidence, | ||
endorsements: &Endorsements, | ||
) -> anyhow::Result<DiceChainResult>; |
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 will rename to DiceChainResult, or address this in some other way, in a different PR.
7bc636e
to
5a38faf
Compare
7ba18fb
to
7ee7f1b
Compare
This PR:
AttestationVerifier
trait to theoak_attestation_verification
InsecureAttestationVerifier
implementation that doesn't use reference values and is needed for testingRef #3641
Ref #4074
Ref #4627