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

Feat: TrezorHostProtocol #13542

Draft
wants to merge 15 commits into
base: develop
Choose a base branch
from
Draft

Feat: TrezorHostProtocol #13542

wants to merge 15 commits into from

Conversation

szymonlesisz
Copy link
Contributor

heavy draft

const iv1 = getIvFromNonce(recvNonce); // should be 1

const hostStaticKeys = getCurve25519KeyPair(Buffer.alloc(32).fill(9));
const hostEphemeralKeys = getCurve25519KeyPair(Buffer.alloc(32).fill(1));
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks suspicious. The host ephemeral key pair should be generated randomly before the HandshakeInitiationReq is sent, since its public part is contained in the message.

const iv0 = getIvFromNonce(sendNonce); // should be 0
const iv1 = getIvFromNonce(recvNonce); // should be 1

const hostStaticKeys = getCurve25519KeyPair(Buffer.alloc(32).fill(9));
Copy link
Contributor

Choose a reason for hiding this comment

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

The host static key should be generated randomly once and kept constant for all invocations of the protocol.

// const credentials = knownCredentials.find(c => {
// const isPaired = findKnownPairingCredentials(knownCredentials[0], trezorEphemeralPubkey);
// })
const hostTempKeys = hostStaticKeys || getCurve25519KeyPair(Buffer.alloc(32).fill(9));
Copy link
Contributor

Choose a reason for hiding this comment

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

The bytes inside getcurve25519KeyPair should be 0900000000000000000000000000000000000000000000000000000000000000 instead of 0909090909090909090909090909090909090909090909090909090909090909.

Comment on lines +83 to +121
function conditionalSwap(a: bigint, b: bigint, condition: boolean): [bigint, bigint] {
const mask = condition ? -1n : 0n;
const newA = (a & ~mask) | (b & mask);
const newB = (a & mask) | (b & ~mask);

return [newA, newB];
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if it makes sense to try to make this function run in constant time in TypeScript.

If so, then the line const mask = condition ? -1n : 0n; is likely not constant time since it probably uses a conditional branch.
if not so, then the function can be defined as return condition ? [b, a] : [a, b];.

Comment on lines +156 to +197
function conditionalMove(first: bigint, second: bigint, condition: boolean): bigint {
const trueMask = condition ? -1n : 0n;
const falseMask = ~trueMask;

return (first & falseMask) | (second & trueMask);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if it makes sense to try to make this function run in constant time in TypeScript.

If so, then the line const trueMask = condition ? -1n : 0n; is likely not constant time since it probably uses a conditional branch.
if not so, then the function can be defined as return condition ? second : first;.

if (!isPaired) {
if (PAIRING_METHODS.includes(2)) {
const createCodeEntryChallenge = await call('ThpCodeEntryChallenge', {
challenge: Buffer.alloc(32).fill(1),
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be generated randomly.

const { secret } = codeEntry.payload.message;
const sha = sha256(Buffer.from(secret, 'hex'));

console.warn('secret compare with commitment', sha.compare(handshakeCommitment));
Copy link
Contributor

Choose a reason for hiding this comment

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

You should return an error if the hashes don't match.

await new Promise(resolve => setTimeout(resolve, 1000));

const debugState = await call('DebugLinkGetState', {});
const { thp_pairing_code_entry_code, thp_pairing_secret } =
Copy link
Contributor

Choose a reason for hiding this comment

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

code_qr_code and code_nfc_unidirectional should be transmitted from Trezor to Host instead of thp_pairing_secret.

Comment on lines 334 to 310
if (pairingReq.payload.message.commitment) {
handshakeCommitment = Buffer.from(pairingReq.payload.message.commitment, 'hex');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be done only if code entry was a pairing method advertised in protocol parameters.

const sha = sha256(Buffer.from(secret, 'hex'));

console.warn('secret compare with commitment', sha.compare(handshakeCommitment));

Copy link
Contributor

Choose a reason for hiding this comment

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

You should check that code = SHA-256(h || secret || challenge || PairingMethod_CodeEntry) % 1000000.

Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly for nfcPairing and qrCodePairing.

@szymonlesisz szymonlesisz force-pushed the feat/thp branch 4 times, most recently from b621c58 to 8bd0e9b Compare August 19, 2024 18:54
@szymonlesisz szymonlesisz force-pushed the feat/thp branch 2 times, most recently from 3a87b56 to db8ab69 Compare August 26, 2024 09:35
@szymonlesisz szymonlesisz force-pushed the feat/thp branch 5 times, most recently from b2da7e9 to 5ef1e08 Compare September 16, 2024 15:56
@szymonlesisz szymonlesisz force-pushed the feat/thp branch 3 times, most recently from 6c0b109 to bb1c4da Compare September 25, 2024 15:20

it('AESGCM encode/decode', () => {
const key = Buffer.from(
'ccbf529fc8dd4662d4d1d1fa66368b8758c0b6673a1bb9d532d95ca607cbf729',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "ccbf529fc8dd4662d4d1d1fa66368b8758c0b6673a1bb9d532d95ca607cbf729" is used as
key
.
import { aesgcm } from '../../src/protocol-thp/crypto/aesgcm';
import { curve25519, getCurve25519KeyPair } from '../../src/protocol-thp/crypto/curve25519';

const PROTOCOL_NAME = Buffer.from('Noise_XX_25519_AESGCM_SHA256', 'ascii');

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical test

The hard-coded value "Noise_XX_25519_AESGCM_SHA256" is used as
key
.
@szymonlesisz szymonlesisz force-pushed the feat/thp branch 6 times, most recently from 26daf9d to a0a7f06 Compare October 24, 2024 12:20
Comment on lines 45 to 61
// const cmd = this.device.getCommands();
// const response = await cmd.typedCall('ResetDevice', 'Success', this.params);
console.log('----> running reset function!');
const response = await this.device.transport.call({
session: this.device.transportSession!,
name: 'LoadDevice',
data: {
pin: '',
label: 'THP device',
passphrase_protection: true,
mnemonics: ['all all all all all all all all all all all all'],
skip_checksum: true,
},
protocol: this.device.protocol,
protocolState: this.device.protocolState,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

this can be now dropped. TrezorConnect.loadDevice should be available

Copy link

🚀 Expo preview is ready!

  • Project → trezor-suite-preview
  • Platforms → android, ios
  • Scheme → trezorsuitelite
  • Runtime Version → 20
  • More info

Learn more about 𝝠 Expo Github Action

let newSessionId = 0;
// TODO: what if known.length >= 255?
while (!newSessionId || knownSessions.includes(newSessionId.toString())) {
newSessionId = getWeakRandomInt(1, 256);

Check failure

Code scanning / CodeQL

Insecure randomness High

This uses a cryptographically insecure random number generated at
Math.random()
in a security context.

Copilot Autofix AI about 18 hours ago

To fix the problem, we need to replace the use of getWeakRandomInt with a function that generates cryptographically secure random integers. We can use the crypto module available in Node.js to achieve this. Specifically, we can use crypto.randomInt to generate a secure random integer within a specified range.

  • Replace the call to getWeakRandomInt with a new function that uses crypto.randomInt to generate a secure random integer.
  • Ensure that the new function is imported and used correctly in the relevant parts of the code.
Suggested changeset 1
packages/utils/src/getWeakRandomInt.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/utils/src/getWeakRandomInt.ts b/packages/utils/src/getWeakRandomInt.ts
--- a/packages/utils/src/getWeakRandomInt.ts
+++ b/packages/utils/src/getWeakRandomInt.ts
@@ -1 +1,3 @@
+import { randomInt } from 'crypto';
+
 /**
@@ -11,3 +13,3 @@
 
-    return Math.floor(Math.random() * (max - min) + min);
+    return randomInt(min, max);
 };
EOF
@@ -1 +1,3 @@
import { randomInt } from 'crypto';

/**
@@ -11,3 +13,3 @@

return Math.floor(Math.random() * (max - min) + min);
return randomInt(min, max);
};
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
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

Successfully merging this pull request may close these issues.

3 participants