keybase / keybase/keybase-issues

CORS issue with keybase.io API prevents preflight OPTIONS requests via XHR in JavaScript

Open
#1,959 5 comments 0 reactions 0 assignees View on GitHub
Dominant language
No language data
Stars
899
Forks
40
PR merge metrics
No merged PRs in 30d

Description

Cross-Origin Resource Sharing (CORS) Background, W3C recommendation and historical issues:

https://www.w3.org/TR/cors/#access-control-allow-origin-response-header
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
http://www.html5rocks.com/en/tutorials/cors/
https://bugs.jquery.com/ticket/8423

note: Fetch is the Living Standard meant to replace CORS
https://fetch.spec.whatwg.org/
https://github.com/whatwg/fetch

Summary of Problem:

CORS is awesome. The keybase.io key/fetch API doesn't allow it, presently. The Access-Control-Allow-Origin HTTP header indicates whether a resource can be shared across domains. For example if one wanted to host keybase.io functionality embedded on one's own website, like this:

https://jasoncoombs.com/verify.php

Then the Allow-Control-Allow-Origin: \* HTTP header must be provided by keybase.io in order to permit CORS/Fetch requests which are automated through JavaScript originating from a cross-origin domain such as through the XMLHTTPRequest interface for AJAX scripting. For testing purposes I installed this Chrome extension, which forces the insertion of the HTTP header:

https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi

This partly-resolved the problem, because Chrome stopped refusing to invoke XHR to access the keybase.io API after it started receiving the required HTTP header via the Chrome extension. This made it possible to use the keybase.io fetch API as a CORS request from within sitewide-js:

https://keybase.io/docs/api/1.0/call/key/fetch

However, the keybase.io server refuses to verify preflight OPTIONS requests. It was necessary to modify sitewide-js.js to force XHR to stop preflight. The changes were minor, and anyone can see how this works by using the Chrome extension, above, with the URL of my custom verify.php hosted at jasoncoombs.com but the changes made to hack this together might not be desired in general for other uses of XHR in the keybase web API.

Here are my revised script and the original, plus the diff results after both have been made pretty:

https://keybase.io/_/brew/_/9aa70b7b/sitewide-js.js
https://jasoncoombs.com/sitewide-js.js

```
$ diff sitewide-js-pretty.js 'sitewide-js original-pretty.js'
13c13
< var baseUrl = "https://keybase.io/";

---
> //# sourceMappingURL=
5500c5500
< contentType: "text/plain",

---
> contentType: "application/x-www-form-urlencoded; charset=UTF-8",
5636c5636
< jqXHR.setRequestHeader("Content-Type", "text/plain")

---
> jqXHR.setRequestHeader("Content-Type", s.contentType)
5939a5940,5942
> }
> if (!options.crossDomain && !headers["X-Requested-With"]) {
> headers["X-Requested-With"] = "XMLHttpRequest"
```

As you can see, to work around the automated preflight OPTIONS request, which the keybase.io API rejects for some reason, required removing X-Requested-With and forcing Content-Type to "text/plain" in two places.

For testing purposes, my wife sent me a signed love note. Her key is:

https://keybase.io/_/api/1.0/key/fetch.json?pgp_key_ids=4e3d8619f70b333a&ops=4

With my revised script it is possible to paste the following into the Verify page on my server and your browser's CORS request succeeds when fetching my wife's key from the keybase.io API (remember that the Chrome extension to force CORS to work must be active, until the keybase.io server itself sends the Allow-Control-Allow-Origin header):

```
-----BEGIN PGP MESSAGE-----
Comment: https://keybase.io/download
Version: Keybase Go 1.0.7 (windows)

xA0DAAoBTj2GGfcLMzoBy+F0AOIAAAAA5EkgbG92ZSB5b3UsIGhvbmXheSEAwsFc
BAABCgAQBQJWkFwRCRBOPYYZ9wszOgAA0tcQAItV6r329owQTvUngYS561fTay6R
kUzbzvq2GKWcTFZ/sp0LoQH0i8HerfDYzWZ3rUCXdWvwggVqupGJEUPFTz2W4910
Iov6xTEs2/isvOHIenfmjx8HT25AdsXSnfpINFkRIJR7imHDfqNbcRQTOR8wRfLU
joh3g+j1VGhSBpFQMVyWhyv2fA6cKm+rAZ0eJu+GcpOVF1R3wJWBh3eisQG9ZU2X
U5xmjzRbPJxbzo66u7KNOJFRlpxGwIt2dqg4d1RIVq2CFIcGZoEJ0w8xtDml7WmX
CVEz5Wy6ffP0n+Rh4MYJ/qN7mERq4mw4j2Qdhotf4/2zS7zT7bFw76IxKcI92Rzd
A5RK/TrZNgC5ClEIPgHDw8rNlrh6cB+AYFyjCMtaqIKgy1aFoL3a5+F9wmmlD5tn
JHPBiIJ7kPpx/GJ2W5gR8WV+iLfkWWUxQ9MGh/QUQueSd6QoXEbvRDtNEN4nY+Ln
dnTC9Zq4AUL4f27Q0TNvM8nTUxPsDSYKG5CV5bAeTVjc7kBKvy8eADn6BsNUjAEk
/SZGGC4CxMRGcu+xaVA1D3BwqvvUxI/zVLRuaDJ1akfsWK0lKSgUamvdwFsoOqYG
V/DtqvxChx+4eQq4sow+jbqK93yRYuHM2i983WgdYzSxRdngwIIWKtp2otfeHSMT
fc6ppa7rAmFsjnST
=Y+Sh
-----END PGP MESSAGE-----
```

The PGP MESSAGE block, above, can also be pasted and verified at keybase.io/verify which obviously does not use my server or my revised code at all, since this is already the intended functionality of the verify feature at keybase.io for end-users who receive PGP MESSAGE blocks and would like to verify and read them with a web browser (even anonymously, without being signed in to a keybase.io account).

Finally, because this was what I attempted to do in the first place which I found could not be done yet using keybase.io/verify one more line of JavaScript was inserted in my version of sitewide-js to pre-fill the message input from a URL-encoded search string if one is supplied.

The line I've inserted to do this is:

```
$("#input-msg").val(decodeURI(window.location.search).substr(1));
```

This makes it possible to turn the PGP MESSAGE block, above, into a clickable hyperlink. There is already a _run_if_prefilled() function defined in sitewide-js.js at keybase.io/verify so presumably it is a feature already on the drawing board. I hope this report helps to show that it is a feature that already works, with the normal caveat that cross-site scripting vulnerabilities need to be properly mitigated in how the keybase.io API receives and responds to user input. In this case the user input is being inserted into the HTML input tag which should make it easier to prevent XSS attacks.

I like this idea because without this functionality the end-user must be clever enough to copy and paste the message in order to verify and read it. Many users are only clever enough to click on links, and on mobile devices NOBODY is clever enough to copy-and-paste.

Try this URL (if you have the Chrome extension installed) and you will instantly see my wife's love note, with verification that she actually wrote it and signed it with her PGP key:

https://jasoncoombs.com/verify.php?-----BEGIN%20PGP%20MESSAGE-----%0D%0AComment:%20https://keybase.io/download%0D%0AVersion:%20Keybase%20Go%201.0.7%20(windows)%0D%0A%0D%0AxA0DAAoBTj2GGfcLMzoBy+F0AOIAAAAA5EkgbG92ZSB5b3UsIGhvbmXheSEAwsFc%0D%0ABAABCgAQBQJWkFwRCRBOPYYZ9wszOgAA0tcQAItV6r329owQTvUngYS561fTay6R%0D%0AkUzbzvq2GKWcTFZ/sp0LoQH0i8HerfDYzWZ3rUCXdWvwggVqupGJEUPFTz2W4910%0D%0AIov6xTEs2/isvOHIenfmjx8HT25AdsXSnfpINFkRIJR7imHDfqNbcRQTOR8wRfLU%0D%0Ajoh3g+j1VGhSBpFQMVyWhyv2fA6cKm+rAZ0eJu+GcpOVF1R3wJWBh3eisQG9ZU2X%0D%0AU5xmjzRbPJxbzo66u7KNOJFRlpxGwIt2dqg4d1RIVq2CFIcGZoEJ0w8xtDml7WmX%0D%0ACVEz5Wy6ffP0n+Rh4MYJ/qN7mERq4mw4j2Qdhotf4/2zS7zT7bFw76IxKcI92Rzd%0D%0AA5RK/TrZNgC5ClEIPgHDw8rNlrh6cB+AYFyjCMtaqIKgy1aFoL3a5+F9wmmlD5tn%0D%0AJHPBiIJ7kPpx/GJ2W5gR8WV+iLfkWWUxQ9MGh/QUQueSd6QoXEbvRDtNEN4nY+Ln%0D%0AdnTC9Zq4AUL4f27Q0TNvM8nTUxPsDSYKG5CV5bAeTVjc7kBKvy8eADn6BsNUjAEk%0D%0A/SZGGC4CxMRGcu+xaVA1D3BwqvvUxI/zVLRuaDJ1akfsWK0lKSgUamvdwFsoOqYG%0D%0AV/DtqvxChx+4eQq4sow+jbqK93yRYuHM2i983WgdYzSxRdngwIIWKtp2otfeHSMT%0D%0Afc6ppa7rAmFsjnST%0D%0A=Y+Sh%0D%0A-----END%20PGP%20MESSAGE-----

```
signed by emilycoombsceo

The Signed Text

I love you, honey!
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reproducing the rejected preflight OPTIONS request against the keybase.io key/fetch API, then inspect the referenced sitewide-js.js behavior and the verify flow. Done means the API supports the reported cross-origin XHR preflight without the workaround; the separate URL-prefill idea should be treated as additional scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
api, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.