Skip to content
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

Merged
merged 11 commits into from
Jan 19, 2024

Conversation

ipetr0v
Copy link
Contributor

@ipetr0v ipetr0v commented Jan 16, 2024

This PR:

  • Adds an AttestationVerifier trait to the oak_attestation_verification
  • Adds an InsecureAttestationVerifier implementation that doesn't use reference values and is needed for testing

Ref #3641
Ref #4074
Ref #4627

@ipetr0v ipetr0v changed the title Use InsecureAttestationVerifier in the Rust Client Use AttestationVerifier in the Rust Client Jan 16, 2024
@ipetr0v ipetr0v marked this pull request as ready for review January 17, 2024 10:42
Copy link
Collaborator

@conradgrobler conradgrobler left a 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")
Copy link
Collaborator

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.

Copy link
Contributor Author

@ipetr0v ipetr0v Jan 17, 2024

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
    }
}

Copy link
Contributor Author

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).

Copy link
Collaborator

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)

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@ipetr0v ipetr0v Jan 17, 2024

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

Copy link
Collaborator

@thmsbinder thmsbinder Jan 17, 2024

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

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")
Copy link
Collaborator

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>;
Copy link
Contributor

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?

Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Collaborator

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.

}

impl OakFunctionsClient {
pub async fn new(uri: &str) -> anyhow::Result<Self> {
pub async fn new<V: AttestationVerifier>(uri: &str, verifier: &V) -> anyhow::Result<Self> {
Copy link
Collaborator

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.

Copy link
Contributor Author

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{})

Copy link
Collaborator

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?

Copy link
Contributor Author

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)
Copy link
Collaborator

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?

Copy link
Contributor Author

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

Copy link
Collaborator

@thmsbinder thmsbinder left a 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>;
Copy link
Collaborator

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.

@ipetr0v ipetr0v merged commit b1e4abd into project-oak:main Jan 19, 2024
17 checks passed
@ipetr0v ipetr0v deleted the attestation_verifier_trait branch January 19, 2024 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants