-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mask secret value passed between extension scripts (#557)
* Mask secret value passed between extension scripts * Use Opaque type, mask privateKeys along the way * Mask imported wallets
- Loading branch information
1 parent
b5844c0
commit c775234
Showing
25 changed files
with
234 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
import type { ethers } from 'ethers'; | ||
import type { ExternallyOwnedAccount } from './AccountContainer'; | ||
import type { MaskedSignerObject, SignerObject } from './SignerObject'; | ||
|
||
// TODO: rename BareWallet to SignerWallet? | ||
export interface BareWallet extends ExternallyOwnedAccount { | ||
mnemonic: { phrase: string; path: string } | null; | ||
privateKey: ethers.Wallet['privateKey']; | ||
} | ||
export interface BareWallet extends ExternallyOwnedAccount, SignerObject {} | ||
|
||
export interface MaskedBareWallet | ||
extends ExternallyOwnedAccount, | ||
MaskedSignerObject {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import type { ethers } from 'ethers'; | ||
import type { LocallyEncoded } from 'src/shared/wallet/encode-locally'; | ||
|
||
export interface SignerObject { | ||
mnemonic: { phrase: string; path: string } | null; | ||
privateKey: ethers.Wallet['privateKey']; | ||
} | ||
|
||
export interface MaskedSignerObject { | ||
mnemonic: { phrase: LocallyEncoded; path: string } | null; | ||
privateKey: LocallyEncoded; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// eslint-disable-next-line @typescript-eslint/no-namespace | ||
namespace OpaqueSymbols { | ||
export declare const type: unique symbol; | ||
export declare const name: unique symbol; | ||
} | ||
|
||
export type Opaque<T, Name> = { | ||
readonly [OpaqueSymbols.type]: T; | ||
readonly [OpaqueSymbols.name]: Name; | ||
}; | ||
|
||
export function opaqueType<T extends Opaque<unknown, unknown>>( | ||
value: T[typeof OpaqueSymbols.type] | ||
) { | ||
return value as T; | ||
} | ||
|
||
export function unwrapOpaqueType<T extends Opaque<unknown, unknown>>(value: T) { | ||
return value as T[typeof OpaqueSymbols.type]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export type { BareWallet } from 'src/background/Wallet/model/BareWallet'; | ||
export type { MaskedBareWallet } from 'src/background/Wallet/model/BareWallet'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { unwrapOpaqueType } from '../type-utils/Opaque'; | ||
import { encodeForMasking, decodeMasked } from './encode-locally'; | ||
|
||
describe.only('encode-locally.ts', () => { | ||
test('encodeForMasking', () => { | ||
const value = 'hello'; | ||
expect(decodeMasked(encodeForMasking(value))).toBe(value); | ||
}); | ||
|
||
test('maskedValue is not equal to input value', () => { | ||
const value = 'something'; | ||
const encoded = encodeForMasking(value); | ||
expect(encoded).not.toBe(value); | ||
}); | ||
|
||
test('maskedValue and value are not substrings of each other', () => { | ||
const values = ['one two three', '000000', '#$%@#$==']; | ||
values.forEach((value) => { | ||
const encoded = encodeForMasking(value); | ||
const encodedString = unwrapOpaqueType(encoded); | ||
expect(value.includes(encodedString)).toBe(false); | ||
expect(encodedString.includes(value)).toBe(false); | ||
}); | ||
}); | ||
|
||
test('encodeForMasking longer text', () => { | ||
const value = | ||
"What is Lorem Ipsum?\nLorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s!"; | ||
expect(decodeMasked(encodeForMasking(value))).toBe(value); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { type Opaque } from '../type-utils/Opaque'; | ||
import { opaqueType, unwrapOpaqueType } from '../type-utils/Opaque'; | ||
|
||
export type LocallyEncoded = Opaque<string, 'LocallyEncoded'>; | ||
|
||
const NONSECRET_KEY_FOR_INTERNAL_USE = '2024-06-18'; | ||
|
||
/** | ||
* This function is intended to mask a secret value that is being | ||
* passed between extension scripts (ui, service-worker and web-worker) | ||
* and may live in memory for some time. | ||
* The purpose is to prevent passing clear text. | ||
* This "encoding" is simply one step less trivial than base64 | ||
* and should not be considered encryption. | ||
*/ | ||
export function encodeForMasking(value: string) { | ||
const key = NONSECRET_KEY_FOR_INTERNAL_USE; | ||
let encoded = ''; | ||
for (let i = 0; i < value.length; i++) { | ||
encoded += String.fromCharCode( | ||
value.charCodeAt(i) ^ key.charCodeAt(i % key.length) | ||
); | ||
} | ||
return opaqueType<LocallyEncoded>(btoa(encoded)); | ||
} | ||
|
||
export function decodeMasked(encoded: LocallyEncoded) { | ||
const key = NONSECRET_KEY_FOR_INTERNAL_USE; | ||
const decoded = atob(unwrapOpaqueType(encoded)); | ||
let data = ''; | ||
for (let i = 0; i < decoded.length; i++) { | ||
data += String.fromCharCode( | ||
decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length) | ||
); | ||
} | ||
return data; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { walletPort } from 'src/ui/shared/channels'; | ||
|
||
export function usePendingRecoveryPhrase() { | ||
return useQuery({ | ||
queryKey: ['getPendingRecoveryPhrase'], | ||
queryFn: () => { | ||
return walletPort.request('getPendingRecoveryPhrase'); | ||
}, | ||
cacheTime: 0 /** sensitive value, prevent from being cached */, | ||
suspense: false, | ||
retry: 0, | ||
refetchOnMount: false, | ||
refetchOnWindowFocus: true, | ||
useErrorBoundary: false, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.