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

[Attestation Service] Change the API of CoCo-AS #240

Merged
merged 6 commits into from
Dec 15, 2023

Conversation

Xynnn007
Copy link
Member

@Xynnn007 Xynnn007 commented Nov 24, 2023

This PR will refactor the CoCo-AS furthermore, which will help to let CoCo-AS run as a seperate attestation-service without any coupling with KBS.

The API of CoCo AS will be definitely not related to KBS protocol. In this PR, CoCo-AS gRPC service will have the following API

message AttestationRequest {
    // TEE enum. Specify the evidence type
    Tee tee = 1;

    // Base64 encoded evidence. The alphabet is URL_SAFE_NO_PAD.
    // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5
    string evidence = 2;

    // Runtime Data used to check the binding relationship with report data in
    // Evidence
    oneof runtime_data {
        // Base64 encoded runtime data slice. The whole string will be base64
        // decoded. The result one will then be accumulated into a digest which
        // is used as the expected runtime data to check against the one inside
        // evidence.
        //
        // The alphabet is URL_SAFE_NO_PAD.
        // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5
        string raw_runtime_data = 3;

        // Runtime data in a JSON map. CoCoAS will rearrange each layer of the
        // data JSON object in dictionary order by key, then serialize and output
        // it into a compact string, and perform hash calculation on the whole
        // to check against the one inside evidence.
        //
        // After the verification, the structured runtime data field will be included
        // inside the token claims.
        string structured_runtime_data = 4;
    }

    // Init Data used to check the binding relationship with init data in
    // Evidence
    oneof init_data {
        // Base64 encoded init data slice. The whole string will be base64
        // decoded. The result one will then be accumulated into a digest which
        // is used as the expected init data to check against the one inside
        // evidence.
        //
        // The alphabet is URL_SAFE_NO_PAD.
        // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5
        string raw_init_data = 5;

        // Init data in a JSON map. CoCoAS will rearrange each layer of the
        // data JSON object in dictionary order by key, then serialize and output
        // it into a compact string, and perform hash calculation on the whole
        // to check against the one inside evidence.
        // 
        // After the verification, the structured init data field will be included
        // inside the token claims.
        string structured_init_data = 6;
    }

    // Hash algorithm used to calculate runtime data. Currently can be "sha256",
    // "sha384" or "sha512". If not specified, "sha384" will be selected.
    string runtime_data_hash_algorithm = 7;

    // Hash algorithm used to calculate init data. Currently can be "sha256",
    // "sha384" or "sha512". If not specified, "sha384" will be selected.
    string init_data_hash_algorithm = 8;

    // List of IDs of the policy used to check evidence. If not provided,
    // a "default" one will be used.
    repeated string policy_ids = 9;
}

Close #177
Related to #217

cc @jialez0

@Xynnn007 Xynnn007 force-pushed the refactor-as-api branch 3 times, most recently from ff25252 to adadb18 Compare November 24, 2023 11:58
@Xynnn007 Xynnn007 marked this pull request as ready for review November 24, 2023 12:20
@Xynnn007 Xynnn007 requested a review from sameo as a code owner November 24, 2023 12:20
tee: to_grpc_tee(tee).into(),
evidence: STANDARD.encode(attestation.tee_evidence),
runtime_data,
init_data: vec![],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @mkulke You might be interested in this -- This is the KBS side code that does not leverage the ability of the initdata verafication in this PR.

Copy link
Member

@jialez0 jialez0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks! @Xynnn007

@mythi
Copy link
Contributor

mythi commented Dec 1, 2023

message AttestationRequest {

we may also need nonce?

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 1, 2023

message AttestationRequest {

we may also need nonce?

Nonce is something that will be bound to the report data. This API now gives a general parameter runtime_data to check the binding between any report data and the evidence.

@mythi
Copy link
Contributor

mythi commented Dec 1, 2023

This API now gives a general parameter runtime_data to check the binding between any report data and the evidence.

AFAIUI, the AS cannot generate "expected report data" based on runtime_data (Tee pubkey) only if it does not know the nonce the attester used to generate the quote/report data.

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 1, 2023

This API now gives a general parameter runtime_data to check the binding between any report data and the evidence.\n\nAFAIUI, the AS cannot generate "expected report data" based on runtime_data (Tee pubkey) only if it does not know the nonce the attester used to generate the quote/report data.

You are right.

This api is now for user who knows the materials to calculate the report data.

We can add an extra API, which will give a nonce to the caller to get evidence. Then the user can call this attestation API.

@jepio
Copy link
Member

jepio commented Dec 1, 2023

I'm still reviewing, but the attestation requester should not be passing policy_id to evaluate against, that is insecure. The policy in RATS is supplied by the verifier owner, which is different then the attester.

Copy link
Member

@jepio jepio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a step in the right direction but needs some more thought.

It's not secure to mix claims attested through runtime data (like tee-pubkey, nonce) with "custom claims" which are unattested and user controlled. So we really need to impose some structure on runtime data (like a json dictionary) that can be included in the attested token separately from any user supplied data.

Custom claims should instead be a "nonce" supplied by the user to be included in the attestation token.

attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
Comment on lines 103 to 121
let runtime_data = request
.runtime_data
.iter()
.map(|data_item| {
STANDARD
.decode(data_item)
.map_err(|e| Status::aborted(format!("base64 decode runtime data: {e}")))
})
.collect::<std::result::Result<Vec<Vec<u8>>, Status>>()?;

let init_data: Vec<Vec<u8>> = request
.init_data
.iter()
.map(|data_item| {
STANDARD
.decode(data_item)
.map_err(|e| Status::aborted(format!("base64 decode init data: {e}")))
})
.collect::<std::result::Result<Vec<Vec<u8>>, Status>>()?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why array of base64 strings? I think these should just be base64 strings. We also need to document the fact that parameters are base64 encoded and document which base64 encoding is used in README.md. In remote APIs it is most commonly URL_SAFE_NO_PAD, not STANDARD.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is embarassing. If we does not use array, it will be difficult to distinguish an "empty runtime data" and "no runtime data" by only one parameter. It would be better to keep as-is and let me add more document about this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there optional parameters in grpc? i care more about how this works for a JSON http API, where we can have can have:

"runtime_data": {}

or

"runtime_data": { "data": "" }

or skip passing runtime_data.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In gRPC, optional key word is replaced by repeated. In on-going rest CoCo AS we will:

  • If no "runtime_data" field is in the request body. We do not check
  • If "runtime_data": {} field is given. we do the check, by hash(b"")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we stick to an array it would be more like:

  • no "runtime_data" or "runtime_data": [] - we don't check
  • "runtime_data": [""] we check hash(b"").

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 1, 2023

@jepio policy_id in the api would help the caller to apply different use cases.

The key point is that the returned attestation token will have information about the policy ids that are used to check against the evidence. Current AS only has a fixed policy and cannot choose a specific one from API.

Doea this make sense?

@jepio
Copy link
Member

jepio commented Dec 1, 2023

@jepio policy_id in the api would help the caller to apply different use cases.

The key point is that the returned attestation token will have information about the policy ids that are used to check against the evidence. Current AS only has a fixed policy and cannot choose a specific one from API.

Doea this make sense?

I see. I now see that ITA also allows the user the option to pass the policy in a similar way. Why don't we do it same way as well:

  • policy id parameter is optional and then default policy is applied
  • policy id is hash/uuid (i think hashes are a better option as they are unique and unmodifiable), not name

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 4, 2023

@jepio policy_id in the api would help the caller to apply different use cases.
The key point is that the returned attestation token will have information about the policy ids that are used to check against the evidence. Current AS only has a fixed policy and cannot choose a specific one from API.
Doea this make sense?

I see. I now see that ITA also allows the user the option to pass the policy in a similar way. Why don't we do it same way as well:

  • policy id parameter is optional and then default policy is applied
  • policy id is hash/uuid (i think hashes are a better option as they are unique and unmodifiable), not name

This is a great point. I think this is related to how policies are indexed. Currently we only have OPA as policy engine and the regos are stored in filesystem so we directly retrieve them with the file name.

In this PR, I added the policy digest into the result JWT as you suggested to ensure the integrity of policy recorded in the JWT. However how to specify which policy should be used might be another topic, as it is related to how it is stored. We can promote this in another PR. wdyt?

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 4, 2023

It's not secure to mix claims attested through runtime data (like tee-pubkey, nonce) with "custom claims" which are unattested and user controlled. So we really need to impose some structure on runtime data (like a json dictionary) that can be included in the attested token separately from any user supplied data.

It is a good idea that we should have a runtime data format/spec like undergoing initdata. This could help us to leverage report_data field in a more standard way. A json map would help both protect the integrity and embed into extra claims.

But for custormized claims, my idea is that this would give ability to the user to embed some extra context information without integrity protection. Whether to use such claims is determined by the consumer of the JWT. In CoCo-AS/KBS model, the attestation requester and JWT consumer both are KBS, so it is secure for KBS itself to leverage the pub-key fields inside the claims.

I'm not sure whether both should be supported (with/without integrity protection), but seems that we should.

Custom claims should instead be a "nonce" supplied by the user to be included in the attestation token.

From the perspective of opposing server replay attack, this is worth doing.

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 4, 2023

Update the PR due to @jepio 's suggestions. This is still under reviewing. I added some more documents and the examples for CoCoAS gRPC.

},
"evaluation-reports": [
{
"evaluation-result": "{\"allow\":true}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the evaluation result have to be a string? can't it be:

"evaluation-result": {
  "allow": true
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the raw string output by OPA when using the default_policy.rego. I am afraid that we cannot assume what the result will look like. It is decided by the rego.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can specify what it needs to look like to be valid. and besides: the code already parses the OPA output and checks that it matches this structure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me parse the output directly as a JSON object. I think the document of policies can be added in another PR that also can promote the policy storage.

@jepio
Copy link
Member

jepio commented Dec 6, 2023

@jepio policy_id in the api would help the caller to apply different use cases.
The key point is that the returned attestation token will have information about the policy ids that are used to check against the evidence. Current AS only has a fixed policy and cannot choose a specific one from API.
Doea this make sense?

I see. I now see that ITA also allows the user the option to pass the policy in a similar way. Why don't we do it same way as well:

  • policy id parameter is optional and then default policy is applied
  • policy id is hash/uuid (i think hashes are a better option as they are unique and unmodifiable), not name

This is a great point. I think this is related to how policies are indexed. Currently we only have OPA as policy engine and the regos are stored in filesystem so we directly retrieve them with the file name.

In this PR, I added the policy digest into the result JWT as you suggested to ensure the integrity of policy recorded in the JWT. However how to specify which policy should be used might be another topic, as it is related to how it is stored. We can promote this in another PR. wdyt?

I think we need to address this before. We can't change the token format every week because without stability we won't see adoption. We should not be leaking the fact that policies are stored in filesystem into our token API - in a cloud deployment of CoCo AS the backend storage will be blob storage. So I think we need policy management APIs like ITA (https://docs.trustauthority.intel.com/main/restapi/restapi-policy-management.html). When a user registers a policy they need to either pass a UUID or the API returns a UUID. And then this UUID would be passed with the attestation request. WDYT?

@jepio
Copy link
Member

jepio commented Dec 6, 2023

It's not secure to mix claims attested through runtime data (like tee-pubkey, nonce) with "custom claims" which are unattested and user controlled. So we really need to impose some structure on runtime data (like a json dictionary) that can be included in the attested token separately from any user supplied data.

It is a good idea that we should have a runtime data format/spec like undergoing initdata. This could help us to leverage report_data field in a more standard way. A json map would help both protect the integrity and embed into extra claims.

But for custormized claims, my idea is that this would give ability to the user to embed some extra context information without integrity protection. Whether to use such claims is determined by the consumer of the JWT. In CoCo-AS/KBS model, the attestation requester and JWT consumer both are KBS, so it is secure for KBS itself to leverage the pub-key fields inside the claims.

No, it's not secure. Anyone can request a CoCo-AS token passing an arbitrary public key in "custom claims", also one not stored in a TEE and then pass that token to CoCo KBS. So "runtime-claims" (attested through a REPORTDATA mechanism) need to be separated from arbitrary "custom claims" in the token. Anything else can't be trusted by consumers.

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 8, 2023

@jepio Updated the PR. Added a separate commit where I use oneof key word to support both raw init/runtime data match and structured init/runtime data match. PTAL if it is good. If it is good, I will update the KBS part to align with the CoCoAS part.

@Xynnn007 Xynnn007 requested a review from jepio December 8, 2023 06:14
Copy link
Member

@fitzthum fitzthum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good.

A few questions:

Do you think it would be useful if we had some kind of default policy? This could be set per-AS and evaluated on every request. It might help simplify the process of provisioning policies for simple use cases.

How does reference data fit into this? It looks like reference values aren't set via the attestation request or reported in the attestation results. The same policy could have very different results with different reference values.

attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
attestation-service/attestation-service/src/lib.rs Outdated Show resolved Hide resolved
if !allow {
bail!("Untrusted TEE evidence")
bail!("TEE evidence does not pass policy {policy_id}, reason: {policy_res}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the best way to handle this. It might be better to return a token that says that a policy has not passed evaluation. It's a bit hard to know. Maybe that would increase the complexity of the KBS.

If we stick with this approach we should make sure to document it well for clients who are trying to write policies.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the initial implementation I embed the policy validation results into the result token. Suggested by @jepio #240 (comment) I think it is right that if any policy fails to check, the verifiecation should fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Xynnn007 I think the meaning of specifying multiple policy IDs is to introduce more flexibility in the attestation result report. Therefore, it may not be a good idea to force all policies to pass on the AS side.

The requirement logic for proving a request may be varied. For example, some users may write different policies to verify different field contents, so that they want all specified policies to pass. However, other users may write different policies to correspond to different TEE software versions, so they hope that at least one policy can be passed.

The requirement logic like the above should be decided by the user himself, and the attestation result report should truthfully reflect the verification content of each policy, rather than forcing each specified policy to pass.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are two options for now

  1. Provision token only if all policies pass. less flexible for policy but easy to use token.
  2. Provision token regardless of any policy enforcement result. More flexible for policy but needs extra logic to use token.

right?

Now I am ok with both.

wdyt? @fitzthum @jepio

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think either one is fine. The first option sort of implies that the AS policies are going to return binary values, which isn't necessarily the case. The second option will likely increase the complexity of the KBS policies. I guess we can pick one, merge it, and see how we feel about it down the road.

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 11, 2023

Do you think it would be useful if we had some kind of default policy? This could be set per-AS and evaluated on every request. It might help simplify the process of provisioning policies for simple use cases.

Yes. I have already added this here. That means if no policy id is set, gRPC CoCo-AS will use the "default" one. This was not implementation inside the library as I think it is a specific behavior of the service rather than generic lib.

On the KBS side, we can add an optional parameter inside the launch configuration to specify the policy. If not specified, a hardcode default one will be used.

How does reference data fit into this? It looks like reference values aren't set via the attestation request or reported in the attestation results. The same policy could have very different results with different reference values.

Reference data seems a design challlenge. Reasons

  1. We currently tightly depend on the OPA policy to check the reference data. That is to say, if the policy does not do the check, the reference data actually do not work.
  2. @thomas-fossati once raised a good point that compound reference values are needed but still not supported by the corrent RVPS.

But overall, I agree with you that attestation token should have the reference data information inside. So let just temporarily ignore how 1 & 2 are handled, and just try to fix the token format, s.t. another field containing reference data information that was used when doing verification. wdyt?

Xynnn007 added a commit to Xynnn007/kbs that referenced this pull request Dec 11, 2023
Due to confidential-containers#240 (comment)
any claims inside the attestation token should be ensured integrity by
binding the digest into the HW evidence. This commit supports both
structured and raw init/runtime data to check against the binding of
that in evidence.

Now two kinds of binding check are supported:
1. Raw data. CoCo-AS can help to check if the runtime/init data field is
the same as the given raw data.
2. Structured data. CoCo-AS can help to check if the digest of the given
JSON object (with keys in every layer reordered in alphabet) is the same
as the runtime/init data. If matched, the result attestation token will
contain the JSON object as customized claims.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 12, 2023

Rebased and update the PR with the new API.

@jepio @fitzthum @jialez0 a decision left: whether the token can be provisioned without passing all OPA policies

@mythi Added the KBS side check for confidential-containers/guest-components#366 here I will put a PR on guest-components side then. PR: confidential-containers/guest-components#406

@Xynnn007
Copy link
Member Author

Xynnn007 commented Dec 12, 2023

Container build CI is not related to this PR. Will be resolved in a separate PR.
Fixed

Before this commit, the API of Attestation-Service lib is coupled with
KBS protocol.

Also, not all functionalities of verifier driver are used, including
checking the binding of initdata hash and runtime data hash.

This commit will help to only have attestation related parameters in
this API.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
After update the API of attestation-service lib, gRPC service also needs
to be updated. Before this commit, the API of gRPC AS is coupled with
KBS protocol. It is needed for a user to just use CoCo-AS to do some
verification work without coperating with KBS.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
New CoCo-AS API will decouple KBS protocol semantics. Also, this commit
changes the hashed materials when connecting to CoCo-AS.

Before this commit, only k_mod and k_exp of tee-pubkey field are
protected integrity when doing remote attestation. The `kty` and `alg`
field of the tee-pubkey are ignored.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
Fixed the wrong guides to build a gRPC CoCo-AS. Also update the new
format of attestation-token.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
Copy link
Contributor

@mythi mythi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@jepio jepio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. I'm ok with either of the approaches (always release token or only release token when all policies pass) until we find some problem.

@Xynnn007 Xynnn007 merged commit 0825413 into confidential-containers:main Dec 15, 2023
9 checks passed
@Xynnn007 Xynnn007 deleted the refactor-as-api branch December 15, 2023 08:04
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.

Refactoring the Attestation Service
5 participants