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

Signed content should include resource names #5

Closed
jyasskin opened this issue Aug 29, 2017 · 15 comments
Closed

Signed content should include resource names #5

jyasskin opened this issue Aug 29, 2017 · 15 comments

Comments

@jyasskin
Copy link
Member

I think that signing-bodies-without-signing-their-names is likely to turn into security vulnerabilities unless everyone tightly controls which keys they use to sign which content.

The simplest example might be:

needsAuth.js:

function needsAuth() { return true; }

noAuth.js:

function needsAuth() { return false; }

If an origin server is "foolish" enough to sign both of those with the same key, a malicious CDN could swap them in for each other, removing authentication from pages that should insist on it.

I'm pretty sure clever security researchers will be able to construct other attacks by replacing other more benign-looking Javascript files with each other. However, if the origin server signs the name (URL and maybe request headers) of each resource with its content, then the malicious CDN loses that axis of freedom and can't run this kind of attack.

@mikewest
Copy link
Member

I agree with you that this is a risk of signature-based mechanisms that doesn't exist with content-based mechanisms like hashes. It sounds like you'd suggest including more data in the signature in order to ensure that we're loading the thing we think we're loading: what do you think is necessary?

That is, let's extend your example a bit, say https://example.com/js/needsAuth.js and https://example.com/js/noAuth.js. Would you want to perform the signature over something like https://example.com/js/needsAuth.jsfunction needsAuth() { return true; }? Would /js/needsAuth.jsfunction needsAuth() { return true; } be enough? I worry about encoding the origin, given that a CDN is going to be distinct from a local testing environment, etc, which means you'd need to re-sign the content after testing but before uploading, which seems suboptimal.

Should we reconsider including other headers, like Content-Type? If so, I guess we would need to add more structure to the thing we're signing to make sure we can do it consistently... And if we do that, then implementing it in terms of the signature framework you're proposing might be reasonable.

/cc @otherdaniel, who I'd like to get more involved in the design discussions.

@otherdaniel
Copy link

I like the given example. I imagine it could be quite realistic, e.g. with a development and a release version, which - for some types of setups - differ in their authentication requirements. But I don't see how to assert the URL, without making life hard for CDNs.

I guess since signatures are effectively a relaxation of the integrity constraints, one puts an awful lot of trust into the key holder to relax to exactly the right set of files, where "exactly the right" would likely depend an awful lot on the specific use case.

One "solution" might be a "best practices" doc, that spells out quite clearly that with signatures all same-key-signed resource are effectively interchangeable, and if that's not desired, authors need to either change their signing practices (use more keys) or solve the problem inside the source files (e.g. declare a version number inside each signed script, and then have the main resource check the version numbers.)

Alternative: Maybe we could divorce the CSP header from the integrity attribute.

That is: If the whole point of signature-based integrity is to strengthen the existing integrity checks by allowing for an out-of-band info in the CSP header, then maybe we should allow the resource to specify a specific subresource (with a hash), and the CSP header to specify a key. That way, the page would still reference a specific byte sequence (making rollback attacks or jyasskin's example impossible). The CSP header would then have the weaker-but-more-encompassing guarantee that all resources need to (additionally) be signed by a specific key. That way, the page author can switch resources any time without running into trouble with the CSP policy, and the CSP policy would provide a meaningful guarantee about provenance without weakening the page's intended guarantees.

Or, in other words, add the signatures to the hashes, rather than replace the hashes.

@mikewest
Copy link
Member

We talked about this a little bit at lunch, and I like the idea of allowing signatures and digests to be used side-by-side. That would be a mechanism for addressing the underlying issue Jeffrey is pointing to here, but also rollback attacks that we're discussing elsewhere. My understanding of the suggestion is something like the following:

  1. We allow requests to have multiple sets of integrity metadata associated with them, and ensure that all sets of metadata pass the checks in 19.2 of https://fetch.spec.whatwg.org/#main-fetch. If any set fails, we fail the request (similar in nature to the process of dealing with multiple CSPs for a page).

  2. We extend the integrity syntax to support multiple sets of integrity metadata. That is, something like <script src="whatever.js" integrity="sha256-abc, ed25519-xyz"></script> would generate a request with two sets of integrity metadata: one for the hash, one for the signature.

  3. We change the CSP integration in 2.3 of https://w3c.github.io/webappsec-csp/#script-src-pre-request to deal with multiple integrity metadata sets by verifying that the CSP's integrity check matches at least one of them (relying on the one-fails-all-fail behavior described above).

Practically, I'm not sure many folks would use this mechanism, but it might be valuable enough for specific services to do the extra work.

@jyasskin
Copy link
Member Author

jyasskin commented Aug 31, 2017

I'm nervous about signing just the path without its origin, although I think it is comprehensible to tell developers that their private key needs to be scoped to a set of "equivalent" origins. This sort of thing is probably why certificates were invented: once you're attaching metadata to a public keypair, it's better for that to be explicit than implicit.

I could also imagine signing that a given body can be served as any of a list of names, but that does make things more complicated.

Are signatures intended to strengthen hash-based checks (by allowing the signing key to live offline, for example, which would need to be mentioned in the explainer), or are they intended to weaken them a bit so that resources can be upgraded without having to update the whole tree of dependers at the same time? Requiring both a hash and a signature only helps the first, and to really solve that problem, we need a way to communicate the public key that's not vulnerable to online keys leaking.

@otherdaniel
Copy link

Are signatures intended to strengthen hash-based checks (by allowing the signing key to live offline, for example, which would need to be mentioned in the explainer), or are they intended to weaken them a bit so that resources can be upgraded without having to update the whole tree of dependers at the same time?

Both, I take it. The intention is to strengthen checks by forcing a check in a CSP, or maybe in an Origin Policy (in a future where that exists). But if you do that, you have a deployment issue, because then you would need to update your CSP/OP on every change of every sub-resource, which likely makes your deployment difficult. Having all of this based on signatures (rather than hashes) would hopefully make that more viable. So, signatures would relax things a bit, in order to allow strengthening via CSP & friends.

The explainer mentions this, but doesn't call much attention to it, in the paragraph beginning with "Signatures can be layered on top of ..."

@jyasskin
Copy link
Member Author

jyasskin commented Sep 5, 2017

Right, so if hashes are difficult to deploy, then "add the signatures to the hashes, rather than replace the hashes." is also difficult to deploy and doesn't solve the problem signatures were originally meant to solve.

@otherdaniel
Copy link

Naaah, disagree.

The thing is, the signatures and the hashes would be in different places, where they fulfill different roles, and have different impact on deployability.

Strawman:

  1. Website owner has various security concerns, adds SRI w/ hashes to all subresources.
  2. Website owner is concerned that a script injection attack - which is bound to happen sooner or later -
    would allow the attacker to just inject a new, non-SRI protected script, and adds a CSP header. (... and if this were The Future which Origin Policies and all, they'd have added an origin policy instead.) This effectively adds a 'second factor' through a different channel, and one that script injection can't undo. In the case of OP, the 2nd factor has the additional advantage of being very long-lived.
  3. Now they have the problem that any change to any subresource requires them to change their server config (for CSP), or in Origin-Policy-Future-World to wait until a new OP has made it to all clients.
  4. With signatures+hashes, the CSP (or OP) would instead be based on signatures, while the page continues to uses hashes. That way, the page itself retains its guarantee of the exact bytes sent; and CSP/OP add a "second factor" guarantee that asserts the provenance of the resource. This is arguably weaker than hashes, but as an added layer in a header or long-lived Origin Policy adds something that the hashes can't.

This scenario does assume that main resource + subresource are tightly coupled and will be released together; while CSP/OP are more loosely coupled with different life-times (and, in large organizations, different teams maintaining them).

Having said that... I'm not yet sure if hashes-plus-signature solves all our problems; but I think the criticism above is a bit too simple.

@otherdaniel
Copy link

To cycle this back to the original issues:

Signatures - being a relaxation vs hashes - would potentially allow an attacker to substitute a resource that was validly signed in a different context. Examples that have been mentioned or that I can think of:

  • substituting an old version of a resource (e.g. old version doesn't have a critical bug fix)
  • substituting a development version for a release version (maybe dev-version doesn't contain certain checks)
  • denial-of-service, by substituting pretty much any script (e.g., if a page includes a framework + app logic, just substitute the framework in both places, so the page no longer does anything useful)

Do we have more scenarios, so that we could evaluate proposals against them?

@adrianhopebailie
Copy link

Have you considered using https://tools.ietf.org/html/draft-cavage-http-signatures-08 in which case you the response can specify one or more headers that must be included in the data that is signed?

This seems like it solves the immediate problem and provides flexibility for hosts to use headers such as Etag to ensure the correct version is also returned.

@mikewest
Copy link
Member

mikewest commented Nov 17, 2017

@adrianhopebailie: Thanks for the pointer! Skimming quickly though that document, it seems to be addressing a larger set of problems than we actually need for this use case. That said, I'd be thrilled for the signing portion of this proposal to be Someone Else's Problem™, and I know there's prior art in this area. If there's a good fit, I'm happy to use it.

@mnot pointed to http://httpwg.org/specs/rfc8188.html and https://tools.ietf.org/html/draft-thomson-http-mice-02 as other documents to look into. Mark, any opinions on the HTTP Signatures doc linked above?

@devd
Copy link

devd commented Nov 29, 2017

Would being able to change keys help (see #8)? That was my original thinking when I first read the spec: you want to revoke old code due to a vuln, rotate the public key (and in general, good practice would be to do automatic rotation).

Do other areas in code signing do any better? For example, what prevents an older version of the signed DLL from being loaded?

(on a separate note, I feel like it is too early to worry about this. It feels to me under the category of "problems we would love to have".)

@jyasskin
Copy link
Member Author

@devd This issue isn't really about expiration due to a vulnerability, it's about code that's expected to be served for requests to foo.js being unsafe if it's used to fulfil requests for bar.js.

@mnot
Copy link
Contributor

mnot commented Dec 5, 2017

@mikewest I'd say that there's been long-term interest in having some sort of signature mechanism for HTTP, but it's never been quite enough to get us a standard. It looks like that may have changed now.

WRT the different proposals -- I'm not too concerned yet about how the actual bits on the wire look; we'll get there (and for that, we could start with pretty much any proposal). Personally, I'd hope that if it's needed for SRI and for what Jeffrey wants (as well as other potential use cases), we can do it just once.

Use cases and requirements would be good to have more / broader discussion about; Jeffrey has given it a good start in his document, but it would be great to hear from others.

We should also discuss about how comfortable people are in leveraging WebPKI for signatures; AIUI there may be some landmines there (but I'm happy to be wrong about that).

Those discussions probably need a bit more visibility than on an issue in a repo that's specific to one use case. Jeffrey has activated some discussion in the IETF; let me chat with a few people and see where that's at, so we can figure out if that should live somewhere there (e.g., HTTP WG, DISPATCH), or WICG, or... (experiencing ETOOMANYHATS).

@aidantwoods
Copy link

I opened #11 to take a crack at solving this (separate issue to divert the discussion of a particular implementation from the problem in general). Posting here to get some feedback from those involved in this discussion :)

@mikewest
Copy link
Member

By shifting the mechanism onto RFC9421's message signatures, we're providing developers with substantially more flexibility about the set of metadata they sign. In particular, signing the "@path";req component should provide exactly the ability requested here, allowing developers to ensure that bar.js's signature is invalid for foo.js, and vice-versa. https://wicg.github.io/signature-based-sri/#profile has more detail on the integration with that document.

I'm going to close this out, as I think it's addressed. If there are still missing pieces, please do let me know.

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

No branches or pull requests

7 participants