Skip to content

feat(mv3): Ledger MV3 support#22275

Merged
brad-decker merged 13 commits intodevelopfrom
feat/ledger-mv3-support
Dec 21, 2023
Merged

feat(mv3): Ledger MV3 support#22275
brad-decker merged 13 commits intodevelopfrom
feat/ledger-mv3-support

Conversation

@brad-decker
Copy link
Copy Markdown
Contributor

@brad-decker brad-decker commented Dec 13, 2023

Description

Adds the ledger-iframe to the offscreen document and establishes MV3 support for ledger. This is done by the following method:

  1. ledger-iframe.ts interfaces with the ledger keyring iframe, working as a middle layer between the two. Messages received by the offscreen document that target the ledger iframe will be forwarded to the ledger keyring iframe (the github pages generated content from @MetaMask/eth-ledger-bridge-keyring. A callback manager uses an incremental id to assign an id to each message which is returned in the response such that a callback can be declared only for a specific id.
  2. ledger-offscreen-bridge.ts is a bridge for the LedgerKeyring that interfaces with the offscreen document such that when the keyring calls out for specific methods, it calls into the offscreen bridge and then the communication is sent to the offscreen document and forwarded to the keyring iframe.

In addition some more cleanup of the #18794 deprecation of u2f and ledger live. We no longer support setting any other method than the browsers lowest level method for connecting. For chrome webhid is the gold standard, for firefox which doesn't support webhid we use u2f. There were still a number of places in the code that reference the transportType from preferences and migrations were done to change the value for many users when we only need to know what the support for those two protocols are.

So firstly it changes the setLedgerTransportPreference to not take an input, but rather just uses the browsers support of navigator.hid to determine whether to use webhid or u2f. When #18794 landed service workers did not have access to webhid, now they do, so this is a viable approach. I also added deprecation messages where appropriate. In a future PR we should go through and remove any usages of the preference. Ideally we'd also set the transport type for the ledger device closer to the bridge, however i'm not familiar with the unlock mechanism of the keyring and trying to do this was causing issues with the mv3 connectivity.

Related issues

Fixes #17724 - No longer relevant because background/service workers have access to window.navigator.webhid
Fixes #21624 - Connects ledger to offscreen doc

Screenshots/Recordings

Manual testing steps

  1. run yarn start:mv3
  2. reload the extension in your browser.
  3. try to add a ledger device. (Remember you must have your device unlocked and the ethereum app open on your ledger
  4. Try to use the test dapp to send tx, sign messages, etc.

Pre-merge author checklist

  • I’ve followed MetaMask Coding Standards.
  • I've clearly explained what problem this PR is solving and how it is solved.
  • I've linked related issues
  • I've included manual testing steps
  • I've included screenshots/recordings if applicable
  • I’ve included tests if applicable
  • I’ve documented my code using JSDoc format if applicable
  • I’ve applied the right labels on the PR (see labeling guidelines). Not required for external contributors.
  • I’ve properly set the pull request status:
    • In case it's not yet "ready for review", I've set it to "draft".
    • In case it's "ready for review", I've changed it from "draft" to "non-draft".

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@brad-decker brad-decker added the team-extension-platform Extension Platform team label Dec 13, 2023
@metamaskbot metamaskbot added the INVALID-PR-TEMPLATE PR's body doesn't match template label Dec 13, 2023
@brad-decker brad-decker marked this pull request as ready for review December 14, 2023 17:02
@brad-decker brad-decker requested review from a team and kumavis as code owners December 14, 2023 17:02
@brad-decker brad-decker removed the INVALID-PR-TEMPLATE PR's body doesn't match template label Dec 14, 2023
@metamaskbot metamaskbot added INVALID-PR-TEMPLATE PR's body doesn't match template and removed INVALID-PR-TEMPLATE PR's body doesn't match template labels Dec 14, 2023
@codecov
Copy link
Copy Markdown

codecov bot commented Dec 14, 2023

Codecov Report

Attention: 65 lines in your changes are missing coverage. Please review.

Comparison is base (c6d6946) 67.90% compared to head (359627f) 67.80%.

❗ Current head 359627f differs from pull request most recent head eed8495. Consider uploading reports for the commit eed8495 to get more accurate results

Files Patch % Lines
...ts/lib/offscreen-bridge/ledger-offscreen-bridge.ts 3.03% 64 Missing ⚠️
app/scripts/metamask-controller.js 90.91% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #22275      +/-   ##
===========================================
- Coverage    67.90%   67.80%   -0.10%     
===========================================
  Files         1071     1069       -2     
  Lines        41378    41334      -44     
  Branches     11112    11104       -8     
===========================================
- Hits         28096    28023      -73     
- Misses       13282    13311      +29     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [0e09033]
Page Load Metrics (1079 ± 127 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint822741617034
domContentLoaded9184687536
load77615391079265127
domInteractive9184687536
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 2.38 KiB (0.07%)
  • ui: -500 Bytes (-0.01%)
  • common: 187 Bytes (0.00%)

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [247ff78]
Page Load Metrics (1356 ± 203 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint894592058943
domContentLoaded11237837335
load78322911356423203
domInteractive11237837335
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 2.38 KiB (0.07%)
  • ui: -500 Bytes (-0.01%)
  • common: 187 Bytes (0.00%)

@pedronfigueiredo
Copy link
Copy Markdown
Contributor

pedronfigueiredo commented Dec 18, 2023

Testing with a ledger device on the latest Chrome, I see this message on the background (and offscreen) console. Can you help me understand what I may have done wrong?

Screenshot 2023-12-18 at 15 34 33

@metamaskbot metamaskbot added needs-dev-review PR needs reviews from other engineers (in order to receive required approvals) and removed team-extension-client labels Dec 18, 2023
@pedronfigueiredo
Copy link
Copy Markdown
Contributor

Testing with a ledger device on the latest Chrome, I see this message on the background (and offscreen) console. Can you help me understand what I may have done wrong?

Screenshot 2023-12-18 at 15 34 33

After another trial, I couldn't reproduce this error again. Tested manually and it works on both nano x and s plus. LGTM

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [359627f]
Page Load Metrics (1344 ± 139 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint984001908943
domContentLoaded12231727737
load88618891344289139
domInteractive12231727737
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 2.38 KiB (0.07%)
  • ui: -500 Bytes (-0.01%)
  • common: 187 Bytes (0.00%)

async getKeyringForDevice(deviceName, hdPath = null) {
const keyringOverrides = this.opts.overrides?.keyrings;
let keyringName = null;
console.log(deviceName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

console.log

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

action: LedgerAction.makeApp,
},
(response) => {
if (response.success) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I notice this if else block is different from what is currently on the main branch on the eth-ledger-bridge-keyring repo. Is that expected?

https://github.com/MetaMask/eth-ledger-bridge-keyring/blob/main/src/ledger-iframe-bridge.ts#L132-L138

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its mostly expected, I will add an else if error with a catch for if neither success or error is defined.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

replicated the else catch for a missing error

messageId,
};

// It has already been checked that they are not null above, so the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It was checked, but this code is still run since the if(!iframe?.contentWindow) block doesn't short circuit. Should it return false?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's a really good catch @davidmurdoch :D

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for that, it now returns early in this case

// It has already been checked that they are not null above, so the
// optional chaining here is for compiler typechecking only. This avoids
// overriding our non-null assertion rule.
iframe?.contentWindow?.postMessage(iframeMsg, '*');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
iframe?.contentWindow?.postMessage(iframeMsg, '*');
iframe?.contentWindow?.postMessage(iframeMsg, LEDGER_FRAME_ORIGIN_URL);

IMPORTANT ☝️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(otherwise, external potentially breached realms can redirect the location of this iframe to a malicious one and still receive the message)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

Comment on lines +21 to +22
window.addEventListener('message', ({ origin, data }) => {
if (origin !== LEDGER_FRAME_ORIGIN_URL) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
window.addEventListener('message', ({ origin, data }) => {
if (origin !== LEDGER_FRAME_ORIGIN_URL) {
window.addEventListener('message', ({ origin, data, source }) => {
if (origin !== LEDGER_FRAME_ORIGIN_URL || source !== iframe?.contentWindow) {

Would be a great touch (for that to work, const iframe = document.querySelector('iframe') should be moved to the upper scope, but that shouldn't cause too much trouble)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

Comment on lines 9 to +10
<iframe src="./trezor-iframe.html"></iframe>
<iframe src="./ledger-iframe.html"></iframe>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would this security hardening work?

Suggested change
<iframe src="./trezor-iframe.html"></iframe>
<iframe src="./ledger-iframe.html"></iframe>
<iframe sandbox="" src="./trezor-iframe.html"></iframe>
<iframe sandbox="" src="./ledger-iframe.html"></iframe>

Or does it clash with the rest of the logic introduced in this PR? Could be a nice touch. Weird, because the origin of the content of the iframe is at a similar trust level of the code that loads it, but if such isolation costs us nothing, it could only add security without giving up on anything.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding this property caused two things: Before the iframe would show up in the extension manifest in the extension manager, and it received communication from the offscreen document. After setting it both those things stopped working. For those reasons I did not add this hardening to the PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair enough, that was a long shot - thanks for trying!
I'm not too worried about that honestly, would have been nice but far from required

Copy link
Copy Markdown
Contributor

@danjm danjm left a comment

Choose a reason for hiding this comment

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

looks good to me overall

left one nit about a stray console log, and I left one question

and of course Gal's comments need to be addressed. Also, we should check whether any of Gal's comments apply to the previously merged trezor PR as well.

Copy link
Copy Markdown
Contributor

@weizman weizman left a comment

Choose a reason for hiding this comment

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

8c21b93 lgtm (iframe related security side of things)

Copy link
Copy Markdown
Contributor

@danjm danjm left a comment

Choose a reason for hiding this comment

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

Great work!

@brad-decker brad-decker merged commit 4fb8560 into develop Dec 21, 2023
@brad-decker brad-decker deleted the feat/ledger-mv3-support branch December 21, 2023 16:44
@github-actions github-actions bot locked and limited conversation to collaborators Dec 21, 2023
@github-actions github-actions bot removed the needs-dev-review PR needs reviews from other engineers (in order to receive required approvals) label Dec 21, 2023
@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [d73f19e]
Page Load Metrics (1348 ± 160 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint924021928641
domContentLoaded9201777335
load81620151348334160
domInteractive9201777335
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 2.37 KiB (0.07%)
  • ui: -500 Bytes (-0.01%)
  • common: 187 Bytes (0.00%)

@metamaskbot metamaskbot added the release-11.9.0 Issue or pull request that will be included in release 11.9.0 label Dec 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-11.9.0 Issue or pull request that will be included in release 11.9.0 team-extension-platform Extension Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connect Ledger to Offscreen Document API [Bug]: MV3 Incorrect Default Ledger Transport Type

6 participants