-
-
Notifications
You must be signed in to change notification settings - Fork 770
AppID: Restore AppIdPolicy
implementations' ability to use the accepted credential, and allow credential checkers to pass data with accepted credentials
#4028
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
Conversation
"The system must be very careful to not trust what is in the credential (ie in the TBF footer). For example, an attacker could use one company's signature for an app but change the metadata about which company signed it. " A proper credential has integrity, for the reasons you note. The signature binds to a signing identity; you should not be able to change which company signed it. Can you walk through a more specific example? |
I can try. In this scenario there are two trusted signers, CompanyA and CompanyB. The kernel runs apps from both of them, but assigns a different range of shortids to each company. If we (just for ease of illustration) assume a credential is a JSON blob that looks like: {
"signer": "CompanyA",
"signature": "ae890b34..."
} Let's say that is a valid credential (ie, company A did in fact sign and that signature is correct). I can then create a new credential that looks like: {
"signer": "CompanyB",
"signature": "ae890b34..."
} and replace the valid credential with this one in the TBF footer. Now, a credential verifier in the kernel, if it only checks the signature, will see that the signature is valid. The credential checker will also see that CompanyB is a valid signer. However, it also must explicitly check that the signature was generated by CompanyB. It wasn't so that check will fail. It's probably reasonable for the checker to reject this credential (even though the signature is valid and from a valid signer). A proper system with a proper implementation shouldn't have any issue. But, this is somewhat subtle, and leaves room for mistakes. Also, providing the credential to the AppIdAssigner somewhat invites this kind of implementation because it is really easy to think something along the lines of "oh, I can just put the shortid in the credential". Or, "I can look at the credential for the signing company". That second one is possible to do correctly, but the credential checker has to be implemented in a specific way. This is also based on the realities of the Tock use case. Meaning, the AppIdAssigner is almost certainly not going to duplicate the effort of being able to verify anything. So it will have to trust the checker policy implementation. |
It isn't just which company. A single company could have multiple certificates. I feel like we really need the entire certificate chain
The signature isn't valid though, it isn't signed by the cert chain in the footer. I agree Crypto is really hard and easy to make mistakes, but these checks will have to be done otherwise the signatures are useless.
I assume AppIdAssigner is the |
I'm using "company" as short hand. You can replace that with "signing entity" if you want.
I would encourage you to read through the AppID TRD. The mechanism is flexible by design. We can't unilaterally say that a particular signature is not valid. Also, there is no defined or required cert chain.
This is exactly what this PR is about. The checker runs, and the |
If an app is signed by a different
I agree with part 1. That's an easy one I'm not convinced on the second part. The justification in the PR doesn't seem clear |
Allowing the checker to explicitly communicate with the appid assigner helps with implementing complex appid assignment policies. Today, {
"signing_pub_key": "8109afe45...",
"signature": "090b4ea..."
} The appid assignment policy might want to do quite a bit. For example, assign certain shortids based on which of the company's keys was used for the signature, use a shortid TBF header or not (or for only some apps), use a fixed list for some apps. Modifying the TBF header may not be possible (particularly if the app is already signed). The checker must ensure the signature is valid, and in the process of that operation, may want to save some state about why the credential it accepted was accepted, as that might be useful for the appid assigner. This is a mechanism to support that. This would help avoid re-doing work and/or help simplify the in-kernel implementation of the appid assigner. |
I understand it's useful to share some of the state, but weren't they separated in the first place for a reason? Also, why a Also, won't this break if someone swaps the |
I'm not sure what else to use.
Yes. But potentially so would any security guarantees your kernel has (had). |
7df8b50
to
13b1317
Compare
If a process binary passes the credential check, include the credential that the checker accepted (if it exists).
If a process binary passes the credential checker, we need to store the accepted credential somewhere to use when assigning ids. We already have the ProcessBinary object, so this uses that.
For symmetry with `ProcessStandard`
If a credential checker accepts a credential, it may now return a nonzero usize with information about the accepted credential.
13b1317
to
b0416a0
Compare
doc/reference/trd-appid.md
Outdated
```rust | ||
pub enum CheckResult { | ||
Accept, | ||
Accept(Option<core::num::NonZeroUsize>), |
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.
Can we try to have this be a generic type T
, perhaps defaulting to a unit type (T = ()
)?
That would avoid imposing overheads on credential checkers that don't need to pass addl. data, and makes this data unambiguous -- it can only be created by the checker and never be duplicated.
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 assume this is possible, but it is fairly invasive. CheckResult<T>
the propagates to all of the checker machinery, which fine, but then because the T
is stored in a ProcessBinary
that has to be templated on T
, and at that point it is kind of everywhere, including code which isn't even using process checking.
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, I guess a NonZeroUsize
is fine here... It's a bummer that it does increase the CheckResult
size by another word.
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.
Resolution from today's call: maybe wrap the NonZeroUsize
into a wrapper struct that it's clearer that this is something produced by the process checker machine.
Rather than just us a `usize` directly, this wraps the usize in a struct so we can give it a name and documentation. This also changes the nonzero usize to a normal usize. Using a nonzero usize is a nice trick to make the option<usize> smaller, but not being able to use zero is a bit of usability hit. I don't think saving a couple bytes is worth it.
I added the struct. Also, how big of a deal is using the nonzerousize? I switched back to a usize because not being able to use 0 seems like a fairly bit usability hit (for example to identify public key 0). |
Without actually checking it, it might not matter in this enum, as Rust may be able to niche-fill the |
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.
Given the tradeoffs discussed on the call, I think the wrapper is a good solution (until someone comes up with something magical and smarter).
An additional maybe-word of memory per-process doesn't seem like a huge overhead, and I think it lends a bit more flexibility to checkers to have full ownership over their 'opaque' type; I think the plain usize
makes sense here.
…d of `NonZeroUsize` Co-authored-by: Pat Pannuto <[email protected]>
Pull Request Overview
This PR enables two features for AppID:
Allows an AppID assignment policy to use the accepted credential when assigning IDs. This is important if the AppId is the public key used to verify a signature, for example. (This functionality used to exist and I removed it previously because of how process loading changed. Turns out we still need it.)
Helps developers use the accepted credential correctly when assigning an AppID. There are two issues/pitfalls that can arise when trying to use an accepted credential to assign an AppID.
To help with these issues, this PR also includes a
usize
that the checker can store with the accepted credential to provide information about the accepted credential that the AppID assignment policy can use.For issue 1: this would allow the process checker to keep track of some metadata about why it accepted the credential. For example it might store the index of the public key that verified the signature. Or it might store the upper 16 bits of the ShortId for the organization that signed the app. The value is opaque, and different policies/implementations can use it differently. The
Compress
trait (ie assigning ShortId) is not asynchronous. So from a software engineering viewpoint, whatever state the AppId assignment policy needs must be fetched in advance, and the policy can not re-run any verification steps. Therefore, the checker needs some way to communicate with the AppId assigner if the assigner is going to use any outcome of the verification process in its AppId assignment.For issue 2: this gives developers some way to attach state to an accepted credential without including that state in the credential itself. This isn't providing a correctness guarantee, but it is providing an alternative to what seems intuitively valid: just include it in the credential. Otherwise, developers that want to use different credentials to assign different AppIds can't (or have to build some sort of workaround).
PART 1
First 4 commits.
As part of the refactor in #3849, I removed the ability to retrieve the accepted credential when comparing AppIds and setting ShortIds. This was a byproduct of not actually creating a full-fledged
ProcessStandard
object before performing those operations. We no longer needed to mark the process as having valid credentials.Unfortunately, that change made it impossible to 1) assign ShortIds based on the accepted credential, 2) define an AppId based on an accepted credential, and 3) assign fixed ShortIds to credentialed processes and locally unique ShortIds to non-credentialed processes.
This PR restores that functionality.
PART 2
While thinking through if supporting
get_credential()
is even a good idea, I'm concerned it has a potential issue. The issue is: credentials are stored outside of the integrity region, so using them to make any decisions about an application must be done very carefully. While I think ultimately we do want the API to expose credentials to the appid assigner (and other modules), some care is required.For example, say I want to assign ShortIds based on which company signed an app. The credential checker can validate the signature and decide whether to accept the signature or not. But then it is up to the appid assigner to actually create a ShortId. How does the AppId assigner know which company created the approved signature, and therefore which ShortId to assign? Right now I think there are two ways:
I don't think 2 is realistic. That would be possible, but would be a lot of overhead for a conceptually simple operation.
Option 1 does work, but has two drawbacks. First is the potential issue I mentioned about. The system must be very careful to not trust what is in the credential (ie in the TBF footer). For example, an attacker could use one company's signature for an app but change the metadata about which company signed it. The credential checker must check for this and reject the credential, but this would be easy to miss. The second issue is it requires some sort of registry at signing time that all signers agree to use to identify themselves.
Given these two options and their drawbacks, the second half of this PR allows a credential checker to return an opaque usize with any accepted credential. This usize can then convey information between the credential checker and the app id assigner on an application-by-application basis. This would simplify allowing the kernel to maintain some sort of key library where the accepted signature is matched to a pub/pri key pair and identified by some value. Or even the usize could be used to communication the intended ShortId directly.
Testing Strategy
Only compile tested, but the code is pretty straightforward.
TODO or Help Wanted
I'm not in love with this implementation for part 1. It turns out to be very easy to implement this by adding an
OptionalCell
toProcessBinary
which holds the credential if one was accepted. In practice, everywhere the credential is needed (ie creating a shortid, creating a ProcessStandard, etc.) we are already passing aProcessBinary
. But, does aProcessBinary
logically have an optional approved credential? Maybe?ProcessBinary
was really intended to represent static state stored in flash.Separating the two is certainly possible, but it adds a bunch of boilerplate code (ie creating a new type that holds both a
ProcessBinary
and anOption<Credential>
, and passing that around instead) to get to virtually the same exact place. Is that worth it? I'm not convinced. I don't think the current implementation is wrong, and it saves a bit of extra overhead in terms of code maintenance.Status: this implementation might be ok.
Change the optionalcell in process binary to contain a struct.
Documentation Updated
/docs
, or no updates are required.Formatting
make prepush
.