-
Notifications
You must be signed in to change notification settings - Fork 51
use OpenPGP.js from web content script, simplify #5013
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 OpenPGP.js from web content script, simplify #5013
Conversation
…in-content-script-live-test
…in-content-script-live-test
…in-content-script-live-test
…in-content-script-live-test
…in-content-script-live-test
…in-content-script-live-test
…in-content-script-live-test
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.
There was really a lot of work done only to be able to call web-stream-tools readToEnd
function, with different approaches for unit test code, extension pages and content script. May it be possible to convince OpenPGP.js developers to simply re-export this helper function from their library, @tomholub ?
private static patch = (opgpKey: OpenPGP.Key) => { | ||
if (Catch.browser()?.name === 'firefox') { | ||
// need to re-create Uint8Array from globalThis.Uint8Array or window.Uint8Array for Firefox content script | ||
OpenPGPKey.fixUint8Arrays(opgpKey); |
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.
The main problem with Firefox was that Uint8Array's created by OpenPGP.js were of globalThis.Uint8Array
or window.Uint8Array
types
and this fix openpgpjs/web-stream-tools#23 didn't help because
calling a method of such an object (e.g. stripped = bin.subarray(i);
inside OpenPGP.js) caused an exception Error: Permission denied to access property "constructor"
However, this type of workaround works: new Uint8Array(bin).subarray(i)
So this patch iterates over the tree of OpenPGP.Key object to re-initialize all Uint8Array's with new Uint8Array
Opinions, @sosnovsky, @tomholub?
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 imagine this could be fixed purely in OpenPGP.js which would be cleaner? Either that, or monkey-patch the types on our end.
The main problem with Firefox was that Uint8Array's created by OpenPGP.js were of globalThis.Uint8Array or window.Uint8Array types
Does that mean they create sometimes one type and sometimes the other?
Option 1: Can the library be patched to always create the same "good" type?
Or are you saying that both globalThis.Uint8Array
and window.Uint8Array
are "bad" and we need just Uint8Array
? Where does that one come from if not global or window?
Option 2: Could the library be patched to accept a config.Uint8ArrayClass = Uint8Array
and then that would be used throughout the codebase? That would be a larger change, presumably.
Option 3: Alternatively, on the "broken" type, could we monkey patch it from outside the library like this? Right in requireOpenpgpjs
globalThis.Uint8Array.prototype.subarray = function (i) {
return new Uint8Array(bin).subarray(i);
}
window.Uint8Array.prototype.subarray = function (i) {
return new Uint8Array(bin).subarray(i);
}
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.
The main problem with Firefox was that Uint8Array's created by OpenPGP.js were of globalThis.Uint8Array or window.Uint8Array types
Does that mean they create sometimes one type and sometimes the other?
- With regard to this particular test
if (x instanceof window.Uint8Array)
seems to be good enough, whileif (x instanceof globalThis.Uint8Array)
causes the test to fail. - The
window.openpgp
(where we take the instance of OpenPGP.js) was undefined when loading the OpenPGP.js bundle in Firefox, so I changed it toglobalThis.openpgp
. This works in both Chrome and Firefox. SoglobalThis
in Firefox is a sandbox, while in Chrome it's a window. - And the issue (that I dealt with earlier) https://github.com/openpgpjs/web-stream-tools/pull/23/files is that OpenPGP.js doesn't recognize its "own" Uint8Array type when running in a Firefox sandbox.
I just had another look at OpenPGP.js code (and there is native Web Cryptography API
involved) and have this hypothesis (not tested yet):
As the "Permission denied" error was encountered in serializeParams
function, probably these "bad" window.Uint8Array
objects are returned from this Web Cryptography API, while globalThis.Uint8Array
(sandboxed) my be created by the OpenPGP.js code itself. And the "constructor" permission denied error actually means that subarray
is trying to create window.Uint8Array
(same as itself) but as it is called by a sandbox only globalThis.Uint8Array
are allowed to be created, hence the cul-de-sac. We can try and file this as a bug to Firefox.
Option 1: Can the library be patched to always create the same "good" type?
The code itself only has simple new Uint8Array
calls, so the fact that it creates globalThis.Uint8Array
is a consequence that the code is running in a sandbox. So this question should be rephrased as -- can and should we make the code run not in a sandbox in Firefox? If not, we can probably try and update the library to re-initialize all Uint8Array's coming from Web Cryptography API (that is, convert window.Uint8Array to sandboxed globalThis.Uint8Array), but how to unit-test this? We need to handle each place where Uint8Array arrives from the API and make sure we handle each new such place.
Or are you saying that both
globalThis.Uint8Array
andwindow.Uint8Array
are "bad" and we need justUint8Array
? Where does that one come from if not global or window?
Apparently this is due to the complex Firefox process model (that I don't fully understand yet) https://wiki.mozilla.org/Security/Sandbox/Process_model
Looks like globalThis.Uint8Array
(sandbox) is bearable in a sense that it can be dealt with patches like https://github.com/openpgpjs/web-stream-tools/pull/23/files but there may be problems in the code consuming these objects should they try to test their types.
Option 2: Could the library be patched to accept a
config.Uint8ArrayClass = Uint8Array
and then that would be used throughout the codebase? That would be a larger change, presumably.
It's not an issue of mis-configuration.
Option 3: Alternatively, on the "broken" type, could we monkey patch it from outside the library like this? Right in
requireOpenpgpjs
globalThis.Uint8Array.prototype.subarray = function (i) { return new Uint8Array(bin).subarray(i); } window.Uint8Array.prototype.subarray = function (i) { return new Uint8Array(bin).subarray(i); }
I can try this.
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.
This works in both Chrome and Firefox:
if (window !== globalThis) {
window.Uint8Array.prototype.subarray = function (i) {
return new globalThis.Uint8Array(this).subarray(i);
};
}
and this too:
if (window !== globalThis) {
window.Uint8Array.prototype.subarray = function (i) {
return new Uint8Array(this).subarray(i);
};
}
we can also add Catch.browser().name === 'firefox'
into the condition.
The following results in an infinite recursion in Chrome:
window.Uint8Array.prototype.subarray = function (i) {
return new Uint8Array(this).subarray(i);
};
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.
But may this patch possibly affect other scripts on the page? Is window
of the content script isolated from the window
of the page itself?
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.
we can also add Catch.browser().name === 'firefox' into the condition.
yes. This looks good. If that's the case, we don't have to test Firefox so thoroughly, since it's a global fix, and we also wouldn't have to set up Firefox automated tests at this time.
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.
we don't have to test Firefox so thoroughly
There potentially might be issues with other objects than Uint8Array
. Also we might need to patch other methods that return an array, e.g. slice
and filter
?
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 tested slice
and filter
-- slice
failed so I patched it too, filter
is working without modification.
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 also tested the "bad" Uint8Array as input to a function like readBinary()
or openpgp.readKey{ binaryKey: ... }
When unpatched, the permission denied exception happens.
When patched everything is ok.
My concern is whether our prototype patch applies to all potential sources of Uint8Array
s such as network, background page etc.?
Should we pre-emptively re-initialize all Uint8Array arguments of, say, KeyUtil
and MsgUtil
methods?
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.
Should we pre-emptively re-initialize all Uint8Array arguments of, say, KeyUtil and MsgUtil methods?
I wouldn't. I'd just go with the patches that appear to work, do some manual testing on Firefox, release it, wait for any reports and then fix those (which should be coming from less common workflows).
We only probably have 5k users on Firefox, as opposed to hundreds of thousands on Chrome. Plus I treat Firefox as our beta channel. So I'm not too worried, and wouldn't want to over-focus on this.
makeMockBuild(CHROME_ENTERPRISE); | ||
makeLocalFesBuild(CHROME_ENTERPRISE); | ||
makeContentScriptTestsBuild('chrome-consumer-mock'); | ||
// makeContentScriptTestsBuild('firefox-consumer'); // for manual testing of content script in Firefox |
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.
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.
Should we look into automating firefox tests?
makeMockBuild(CHROME_CONSUMER); | ||
makeMockBuild(CHROME_ENTERPRISE); | ||
makeLocalFesBuild(CHROME_ENTERPRISE); | ||
makeContentScriptTestsBuild('chrome-consumer-mock'); |
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.
The build chrome-consumer-content-script-tests-mock
is used in automated testing
await gmailPage.waitAny('@content-script-test-result'); | ||
const allValues = (await gmailPage.readAll('@content-script-test-result')).map(el => el.innerText); | ||
// multiple results appear in Firefox. The test is successful only if all divs have the word 'pass' | ||
expect(Value.arr.unique(allValues)).to.eql(['pass']); |
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.
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.
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.
Maybe they run 3 times as a retry because they are failing?
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.
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.
- Changes in this file are ignored on incremental builds for some reason.
- I think it makes sense to test all possible OpenPGP.js use cases from the content script in Firefox
? sourceBuildType.slice(0, -5) + '-content-script-tests-mock' | ||
: sourceBuildType + '-content-script-tests'; | ||
exec(`cp -r ${buildDir(sourceBuildType)} ${buildDir(testBuildType)}`); | ||
edit(`${buildDir(testBuildType)}/js/content_scripts/webmail_bundle.js`, code => |
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.
This basically wipes out the webMail code, only libraries are left and test/source/tests/content-script-test.js
immediately calls OpenPGP.js
and web-stream-tools
directly
BrowserMsg.bgAddListener('storeAcctSet', (r: Bm.StoreAcctSet) => AcctStore.set(r.acctEmail, r.values)); | ||
BrowserMsg.bgAddListener('processAndStoreKeysFromEkmLocally', processAndStoreKeysFromEkmLocally); | ||
BrowserMsg.bgAddListener('getLocalKeyExpiration', getLocalKeyExpiration); | ||
|
||
// todo - when https://github.com/FlowCrypt/flowcrypt-browser/issues/2560 | ||
// is fixed, this can be moved to the gmail content script, and some may be removed |
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.
Also BrowserMsg.addPgpListeners();
could be removed from below, and added to content script message listener instead.
Then pgp_block can be updated to call content script instead of background script.
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.
Should I do this in this PR?
I thought after #5022 we may no longer need some of these messages, e.g. pgpMsgDecrypt
or pgpMsgVerifyDetached
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 see. then it doesn't have to be in this pr.
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.
Looks good on a high level, will wait for @sosnovsky to check
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.
Looks good for me too 👍
This PR use OpenPGP.js from web content script, removing unnecessary calls to background
close #4906
close #2560
Tests (delete all except exactly one):
To be filled by reviewers
I have reviewed that this PR... (tick whichever items you personally focused on during this review):