Skip to content

Commit 6281cd1

Browse files
piyalbasuCopilot
andauthored
release/5.37.3 (#2601)
* Bugfix/improve login (#2600) * bump iterations * generate unique iv * bump version numbers * Update extension/src/background/messageListener/__tests__/createAccount.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 58bd6a1 commit 6281cd1

File tree

6 files changed

+67
-39
lines changed

6 files changed

+67
-39
lines changed

extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "extension",
3-
"version": "5.37.2",
3+
"version": "5.37.3",
44
"license": "Apache-2.0",
55
"prettier": "../.prettierrc.yaml",
66
"scripts": {

extension/public/static/manifest/v3.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "Freighter",
3-
"version": "5.37.2",
4-
"version_name": "5.37.2",
3+
"version": "5.37.3",
4+
"version_name": "5.37.3",
55
"description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.",
66
"browser_specific_settings": {
77
"gecko": {

extension/src/background/ducks/session.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export interface SessionState {
5454
const initialState: InitialState = {
5555
publicKey: "",
5656
hashKey: {
57-
iv: "",
5857
key: "",
5958
},
6059
allAccounts: [] as Account[],
@@ -69,7 +68,7 @@ interface UiData {
6968

7069
interface AppData {
7170
privateKey?: string;
72-
hashKey?: { key: string; iv: string };
71+
hashKey?: { key: string };
7372
password?: string;
7473
}
7574

@@ -82,7 +81,6 @@ export const sessionSlice = createSlice({
8281
setActiveHashKey: (state, action: { payload: AppData }) => {
8382
const {
8483
hashKey = {
85-
iv: "",
8684
key: "",
8785
},
8886
} = action.payload;
@@ -106,7 +104,6 @@ export const sessionSlice = createSlice({
106104
timeoutAccountAccess: (state) => ({
107105
...state,
108106
hashKey: {
109-
iv: "",
110107
key: "",
111108
},
112109
password: "",

extension/src/background/helpers/__tests__/session.test.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ describe("session", () => {
99
const password = "password";
1010
const privateKey = "privateKey";
1111

12-
const { key, iv } = await deriveKeyFromString(password);
12+
const { key } = await deriveKeyFromString(password);
1313

1414
const encryptedPrivateKey = await encryptHashString({
1515
str: privateKey,
16-
keyObject: { key, iv },
16+
keyObject: { key },
1717
});
1818

1919
const decryptedPrivateKey = await decryptHashString({
2020
hash: encryptedPrivateKey,
21-
keyObject: { key, iv },
21+
keyObject: { key },
2222
});
2323

2424
expect(decryptedPrivateKey).toEqual(privateKey);
@@ -28,37 +28,60 @@ describe("session", () => {
2828
"passwordpasswordpasswordpasswordpasswordpassworw21w1w1@@@@dpasswordpasswordpasswordpcxassad@@@@asswordpasswordpasswordpasswordpassword";
2929
const privateKey = "privateKeyprivateKeyprivateKeyprivateKey";
3030

31-
const { key, iv } = await deriveKeyFromString(password);
31+
const { key } = await deriveKeyFromString(password);
3232

3333
const encryptedPrivateKey = await encryptHashString({
3434
str: privateKey,
35-
keyObject: { key, iv },
35+
keyObject: { key },
3636
});
3737

3838
const decryptedPrivateKey = await decryptHashString({
3939
hash: encryptedPrivateKey,
40-
keyObject: { key, iv },
40+
keyObject: { key },
4141
});
4242

4343
expect(decryptedPrivateKey).toEqual(privateKey);
4444
});
4545
it("should be able to encrypt and decrypt an empty string", async () => {
46-
// this is an edge case and should never happen, but want to make sure this does not throw an error
4746
const password = "";
4847
const privateKey = "";
4948

50-
const { key, iv } = await deriveKeyFromString(password);
49+
const { key } = await deriveKeyFromString(password);
5150

5251
const encryptedPrivateKey = await encryptHashString({
5352
str: privateKey,
54-
keyObject: { key, iv },
53+
keyObject: { key },
5554
});
5655

5756
const decryptedPrivateKey = await decryptHashString({
5857
hash: encryptedPrivateKey,
59-
keyObject: { key, iv },
58+
keyObject: { key },
6059
});
6160

6261
expect(decryptedPrivateKey).toEqual(privateKey);
6362
});
63+
it("should produce different ciphertexts for the same plaintext", async () => {
64+
const password = "password";
65+
const privateKey = "privateKey";
66+
67+
const { key } = await deriveKeyFromString(password);
68+
69+
const encrypted1 = await encryptHashString({
70+
str: privateKey,
71+
keyObject: { key },
72+
});
73+
74+
const encrypted2 = await encryptHashString({
75+
str: privateKey,
76+
keyObject: { key },
77+
});
78+
79+
const bytes1 = new Uint8Array(encrypted1);
80+
const bytes2 = new Uint8Array(encrypted2);
81+
const areEqual =
82+
bytes1.length === bytes2.length &&
83+
bytes1.every((val, i) => val === bytes2[i]);
84+
85+
expect(areEqual).toBe(false);
86+
});
6487
});

extension/src/background/helpers/session.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,44 +29,62 @@ export class SessionTimer {
2929
}
3030
}
3131

32+
const IV_LENGTH = 16; // AES-CBC uses 128-bit (16 byte) IV
33+
3234
interface HashString {
3335
str: string;
34-
keyObject: { iv: ArrayBuffer; key: CryptoKey };
36+
keyObject: { key: CryptoKey };
3537
}
3638

3739
const TEMPORARY_STORE_ENCRYPTION_NAME = "AES-CBC";
3840
const HASH_KEY_ENCRYPTION_PARAMS = { name: "PBKDF2", hash: "SHA-256" };
3941

40-
export const encryptHashString = ({ str, keyObject }: HashString) => {
42+
export const encryptHashString = async ({
43+
str,
44+
keyObject,
45+
}: HashString): Promise<ArrayBuffer> => {
46+
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
4147
const encoder = new TextEncoder();
4248
const encodedStr = encoder.encode(str);
4349

44-
return crypto.subtle.encrypt(
50+
const ciphertext = await crypto.subtle.encrypt(
4551
{
4652
name: TEMPORARY_STORE_ENCRYPTION_NAME,
47-
iv: keyObject.iv,
53+
iv,
4854
},
4955
keyObject.key,
5056
encodedStr,
5157
);
58+
59+
// Prepend IV to ciphertext so each encryption is self-contained
60+
const result = new Uint8Array(iv.byteLength + ciphertext.byteLength);
61+
result.set(iv, 0);
62+
result.set(new Uint8Array(ciphertext), iv.byteLength);
63+
64+
return result.buffer;
5265
};
5366

5467
interface DecodeHashString {
5568
hash: ArrayBuffer;
56-
keyObject: { iv: ArrayBuffer; key: CryptoKey };
69+
keyObject: { key: CryptoKey };
5770
}
5871

5972
export const decryptHashString = async ({
6073
hash,
6174
keyObject,
6275
}: DecodeHashString) => {
76+
const data = new Uint8Array(hash);
77+
// Extract the IV from the first 16 bytes
78+
const iv = data.slice(0, IV_LENGTH);
79+
const ciphertext = data.slice(IV_LENGTH);
80+
6381
const decrypted = await crypto.subtle.decrypt(
6482
{
6583
name: TEMPORARY_STORE_ENCRYPTION_NAME,
66-
iv: keyObject.iv,
84+
iv,
6785
},
6886
keyObject.key,
69-
hash,
87+
ciphertext,
7088
);
7189

7290
const textDecoder = new TextDecoder();
@@ -75,9 +93,8 @@ export const decryptHashString = async ({
7593
};
7694

7795
export const deriveKeyFromString = async (str: string) => {
78-
const iterations = 1000;
96+
const iterations = 600000;
7997
const keylen = 32;
80-
const keyLength = 48;
8198
// randomized salt will make sure the hashed password is different on every login
8299
const salt = crypto.getRandomValues(new Uint8Array(16)).toString();
83100

@@ -101,31 +118,26 @@ export const deriveKeyFromString = async (str: string) => {
101118
const derivation = await crypto.subtle.deriveBits(
102119
params,
103120
importedKey,
104-
keyLength * 8,
121+
keylen * 8,
105122
);
106123

107-
const derivedKey = derivation.slice(0, keylen);
108-
const iv = derivation.slice(keylen);
109-
110124
const importedEncryptionKey = await crypto.subtle.importKey(
111125
"raw",
112-
derivedKey,
126+
derivation,
113127
{ name: TEMPORARY_STORE_ENCRYPTION_NAME },
114128
true,
115129
["encrypt", "decrypt"],
116130
);
117131

118132
return {
119133
key: importedEncryptionKey,
120-
iv,
121134
};
122135
};
123136

124137
interface StoreActiveHashKey {
125138
sessionStore: Store;
126139
hashKey: {
127140
key: CryptoKey;
128-
iv: ArrayBuffer;
129141
};
130142
}
131143

@@ -141,8 +153,6 @@ export const storeActiveHashKey = async ({
141153
sessionStore.dispatch(
142154
setActiveHashKey({
143155
hashKey: {
144-
// properly encode ArrayBuffer into serializable format
145-
iv: encode(hashKey.iv),
146156
// JSON Web Key is able to be stringified without encoding
147157
key: JSON.stringify(exportedKey),
148158
},
@@ -159,7 +169,7 @@ export const getActiveHashKeyCryptoKey = async ({
159169
}: GetActiveHashKey) => {
160170
const hashKey = hashKeySelector(sessionStore.getState() as SessionState);
161171

162-
if (hashKey?.key && hashKey?.iv) {
172+
if (hashKey?.key) {
163173
try {
164174
const format = "jwk";
165175
// JSON Web Key can be parsed with decoding
@@ -175,7 +185,6 @@ export const getActiveHashKeyCryptoKey = async ({
175185
);
176186

177187
return {
178-
iv: decode(hashKey.iv),
179188
key,
180189
};
181190
} catch (e) {
@@ -191,7 +200,6 @@ interface StoreEncryptedTemporaryData {
191200
keyName: string;
192201
temporaryData: string;
193202
hashKey: {
194-
iv: ArrayBuffer;
195203
key: CryptoKey;
196204
};
197205
}

extension/src/background/messageListener/__tests__/createAccount.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe("Create account message listener", () => {
5757
const tempStoreId = await mockDataStorage.getItem(TEMPORARY_STORE_ID);
5858

5959
expect(response.hasPrivateKey).toBeTruthy();
60-
expect(session.hashKey?.iv).not.toBeUndefined();
60+
expect(session.hashKey?.key).toBeTruthy();
6161
expect(keyIdList.length).toBe(1);
6262
expect(keyId).toBeDefined();
6363
expect(applicationState).toBe(APPLICATION_STATE.PASSWORD_CREATED);

0 commit comments

Comments
 (0)