Skip to content

Commit 712ba61

Browse files
authored
Remove crypto shims (#4292)
* Inline subtlecrypto shim The presence of this thing just makes code more confusing. * Remove pre-node-20 webcrypto hack Until node 20.0, the webcrypto API lived at `crypto.webCrypto`. It's now available at the same place as in web -- `globalThis.crypto`. See: https://nodejs.org/docs/latest-v20.x/api/webcrypto.html#web-crypto-api * oidc auth test: Clean up mocking THe previous reset code wasn't really resetting the right thing. Let's just re-init `window.crypto` on each test. * Remove `crypto` shim This isn't very useful any more.
1 parent 957329b commit 712ba61

File tree

11 files changed

+51
-81
lines changed

11 files changed

+51
-81
lines changed

spec/unit/oidc/authorize.spec.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { getRandomValues } from "node:crypto";
2626
import { TextEncoder } from "node:util";
2727

2828
import { Method } from "../../../src";
29-
import * as crypto from "../../../src/crypto/crypto";
3029
import { logger } from "../../../src/logger";
3130
import {
3231
completeAuthorizationCodeGrant,
@@ -39,11 +38,6 @@ import { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "../../test-uti
3938

4039
jest.mock("jwt-decode");
4140

42-
const webCrypto = new Crypto();
43-
44-
// save for resetting mocks
45-
const realSubtleCrypto = crypto.subtleCrypto;
46-
4741
describe("oidc authorization", () => {
4842
const delegatedAuthConfig = makeDelegatedAuthConfig();
4943
const authorizationEndpoint = delegatedAuthConfig.authorizationEndpoint;
@@ -62,20 +56,18 @@ describe("oidc authorization", () => {
6256
delegatedAuthConfig.metadata.issuer + ".well-known/openid-configuration",
6357
mockOpenIdConfiguration(),
6458
);
59+
global.TextEncoder = TextEncoder;
60+
});
6561

62+
beforeEach(() => {
63+
const webCrypto = new Crypto();
6664
Object.defineProperty(window, "crypto", {
6765
value: {
6866
getRandomValues,
6967
randomUUID: jest.fn().mockReturnValue("not-random-uuid"),
7068
subtle: webCrypto.subtle,
7169
},
7270
});
73-
global.TextEncoder = TextEncoder;
74-
});
75-
76-
afterEach(() => {
77-
// @ts-ignore reset any ugly mocking we did
78-
crypto.subtleCrypto = realSubtleCrypto;
7971
});
8072

8173
it("should generate authorization params", () => {
@@ -99,7 +91,7 @@ describe("oidc authorization", () => {
9991
it("should generate url with correct parameters", async () => {
10092
// test the no crypto case here
10193
// @ts-ignore mocking
102-
crypto.subtleCrypto = undefined;
94+
globalThis.crypto.subtle = undefined;
10395

10496
const authorizationParams = generateAuthorizationParams({ redirectUri: baseUrl });
10597
const authUrl = new URL(

src/crypto/aes.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ limitations under the License.
1515
*/
1616

1717
import { decodeBase64, encodeBase64 } from "../base64";
18-
import { subtleCrypto, crypto } from "./crypto";
1918

2019
// salt for HKDF, with 8 bytes of zeros
2120
const zeroSalt = new Uint8Array(8);
@@ -49,7 +48,7 @@ export async function encryptAES(
4948
iv = decodeBase64(ivStr);
5049
} else {
5150
iv = new Uint8Array(16);
52-
crypto.getRandomValues(iv);
51+
globalThis.crypto.getRandomValues(iv);
5352

5453
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
5554
// (which would mean we wouldn't be able to decrypt on Android). The loss
@@ -60,7 +59,7 @@ export async function encryptAES(
6059
const [aesKey, hmacKey] = await deriveKeys(key, name);
6160
const encodedData = new TextEncoder().encode(data);
6261

63-
const ciphertext = await subtleCrypto.encrypt(
62+
const ciphertext = await globalThis.crypto.subtle.encrypt(
6463
{
6564
name: "AES-CTR",
6665
counter: iv,
@@ -70,7 +69,7 @@ export async function encryptAES(
7069
encodedData,
7170
);
7271

73-
const hmac = await subtleCrypto.sign({ name: "HMAC" }, hmacKey, ciphertext);
72+
const hmac = await globalThis.crypto.subtle.sign({ name: "HMAC" }, hmacKey, ciphertext);
7473

7574
return {
7675
iv: encodeBase64(iv),
@@ -91,11 +90,11 @@ export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name:
9190

9291
const ciphertext = decodeBase64(data.ciphertext);
9392

94-
if (!(await subtleCrypto.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
93+
if (!(await globalThis.crypto.subtle.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
9594
throw new Error(`Error decrypting secret ${name}: bad MAC`);
9695
}
9796

98-
const plaintext = await subtleCrypto.decrypt(
97+
const plaintext = await globalThis.crypto.subtle.decrypt(
9998
{
10099
name: "AES-CTR",
101100
counter: decodeBase64(data.iv),
@@ -109,8 +108,8 @@ export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name:
109108
}
110109

111110
async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
112-
const hkdfkey = await subtleCrypto.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
113-
const keybits = await subtleCrypto.deriveBits(
111+
const hkdfkey = await globalThis.crypto.subtle.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
112+
const keybits = await globalThis.crypto.subtle.deriveBits(
114113
{
115114
name: "HKDF",
116115
salt: zeroSalt,
@@ -126,9 +125,12 @@ async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, Cr
126125
const aesKey = keybits.slice(0, 32);
127126
const hmacKey = keybits.slice(32);
128127

129-
const aesProm = subtleCrypto.importKey("raw", aesKey, { name: "AES-CTR" }, false, ["encrypt", "decrypt"]);
128+
const aesProm = globalThis.crypto.subtle.importKey("raw", aesKey, { name: "AES-CTR" }, false, [
129+
"encrypt",
130+
"decrypt",
131+
]);
130132

131-
const hmacProm = subtleCrypto.importKey(
133+
const hmacProm = globalThis.crypto.subtle.importKey(
132134
"raw",
133135
hmacKey,
134136
{

src/crypto/backup.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import {
3838
} from "./keybackup";
3939
import { UnstableValue } from "../NamespacedValue";
4040
import { CryptoEvent } from "./index";
41-
import { crypto } from "./crypto";
4241
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api";
4342
import { BackupTrustInfo } from "../crypto-api/keybackup";
4443
import { BackupDecryptor } from "../common-crypto/CryptoBackend";
@@ -764,7 +763,7 @@ export class Curve25519 implements BackupAlgorithm {
764763

765764
function randomBytes(size: number): Uint8Array {
766765
const buf = new Uint8Array(size);
767-
crypto.getRandomValues(buf);
766+
globalThis.crypto.getRandomValues(buf);
768767
return buf;
769768
}
770769

src/crypto/crypto.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,5 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { logger } from "../logger";
18-
19-
export let crypto = globalThis.crypto;
20-
export let subtleCrypto = crypto?.subtle ?? crypto?.webkitSubtle; // TODO: Stop using webkitSubtle fallback
21-
22-
/* eslint-disable @typescript-eslint/no-var-requires */
23-
if (!crypto) {
24-
try {
25-
crypto = require("crypto").webcrypto;
26-
} catch (e) {
27-
logger.error("Failed to load webcrypto", e);
28-
}
29-
}
30-
if (!subtleCrypto) {
31-
subtleCrypto = crypto?.subtle;
32-
}
33-
/* eslint-enable @typescript-eslint/no-var-requires */
34-
35-
export function setCrypto(_crypto: Crypto): void {
36-
crypto = _crypto;
37-
subtleCrypto = _crypto.subtle ?? _crypto.webkitSubtle;
38-
}
17+
/** @deprecated this is a no-op and should no longer be called. */
18+
export function setCrypto(_crypto: Crypto): void {}

src/crypto/key_passphrase.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ limitations under the License.
1515
*/
1616

1717
import { randomString } from "../randomstring";
18-
import { subtleCrypto } from "./crypto";
1918

2019
const DEFAULT_ITERATIONS = 500000;
2120

@@ -62,15 +61,19 @@ export async function deriveKey(
6261
iterations: number,
6362
numBits = DEFAULT_BITSIZE,
6463
): Promise<Uint8Array> {
65-
if (!subtleCrypto || !TextEncoder) {
64+
if (!globalThis.crypto.subtle || !TextEncoder) {
6665
throw new Error("Password-based backup is not available on this platform");
6766
}
6867

69-
const key = await subtleCrypto.importKey("raw", new TextEncoder().encode(password), { name: "PBKDF2" }, false, [
70-
"deriveBits",
71-
]);
68+
const key = await globalThis.crypto.subtle.importKey(
69+
"raw",
70+
new TextEncoder().encode(password),
71+
{ name: "PBKDF2" },
72+
false,
73+
["deriveBits"],
74+
);
7275

73-
const keybits = await subtleCrypto.deriveBits(
76+
const keybits = await globalThis.crypto.subtle.deriveBits(
7477
{
7578
name: "PBKDF2",
7679
salt: new TextEncoder().encode(salt),

src/crypto/verification/QRCode.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ limitations under the License.
1818
* QR code key verification.
1919
*/
2020

21-
import { crypto } from "../crypto";
2221
import { VerificationBase as Base } from "./Base";
2322
import { newKeyMismatchError, newUserCancelledError } from "./Error";
2423
import { decodeBase64, encodeUnpaddedBase64 } from "../../base64";
@@ -202,7 +201,7 @@ export class QRCodeData {
202201

203202
private static generateSharedSecret(): string {
204203
const secretBytes = new Uint8Array(11);
205-
crypto.getRandomValues(secretBytes);
204+
globalThis.crypto.getRandomValues(secretBytes);
206205
return encodeUnpaddedBase64(secretBytes);
207206
}
208207

src/oidc/authorize.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ limitations under the License.
1616

1717
import { IdTokenClaims, Log, OidcClient, SigninResponse, SigninState, WebStorageStateStore } from "oidc-client-ts";
1818

19-
import { subtleCrypto } from "../crypto/crypto";
2019
import { logger } from "../logger";
2120
import { randomString } from "../randomstring";
2221
import { OidcError } from "./error";
2322
import {
24-
validateIdToken,
25-
ValidatedIssuerMetadata,
26-
validateStoredUserState,
27-
UserState,
2823
BearerTokenResponse,
24+
UserState,
2925
validateBearerTokenResponse,
26+
ValidatedIssuerMetadata,
27+
validateIdToken,
28+
validateStoredUserState,
3029
} from "./validate";
3130

3231
// reexport for backwards compatibility
@@ -57,14 +56,14 @@ export const generateScope = (deviceId?: string): string => {
5756

5857
// https://www.rfc-editor.org/rfc/rfc7636
5958
const generateCodeChallenge = async (codeVerifier: string): Promise<string> => {
60-
if (!subtleCrypto) {
59+
if (!globalThis.crypto.subtle) {
6160
// @TODO(kerrya) should this be allowed? configurable?
6261
logger.warn("A secure context is required to generate code challenge. Using plain text code challenge");
6362
return codeVerifier;
6463
}
6564
const utf8 = new TextEncoder().encode(codeVerifier);
6665

67-
const digest = await subtleCrypto.digest("SHA-256", utf8);
66+
const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8);
6867

6968
return btoa(String.fromCharCode(...new Uint8Array(digest)))
7069
.replace(/=/g, "")

src/randomstring.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ limitations under the License.
1616
*/
1717

1818
import { encodeUnpaddedBase64Url } from "./base64";
19-
import { crypto } from "./crypto/crypto";
2019

2120
const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
2221
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
2322
const DIGITS = "0123456789";
2423

2524
export function secureRandomBase64Url(len: number): string {
2625
const key = new Uint8Array(len);
27-
crypto.getRandomValues(key);
26+
globalThis.crypto.getRandomValues(key);
2827

2928
return encodeUnpaddedBase64Url(key);
3029
}

src/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@ limitations under the License.
1717
import { SAS } from "@matrix-org/olm";
1818

1919
import {
20-
RendezvousError,
20+
LegacyRendezvousFailureReason as RendezvousFailureReason,
21+
RendezvousChannel,
2122
RendezvousCode,
23+
RendezvousError,
2224
RendezvousIntent,
23-
RendezvousChannel,
24-
RendezvousTransportDetails,
2525
RendezvousTransport,
26-
LegacyRendezvousFailureReason as RendezvousFailureReason,
26+
RendezvousTransportDetails,
2727
} from "..";
28-
import { encodeUnpaddedBase64, decodeBase64 } from "../../base64";
29-
import { crypto, subtleCrypto } from "../../crypto/crypto";
28+
import { decodeBase64, encodeUnpaddedBase64 } from "../../base64";
3029
import { generateDecimalSas } from "../../crypto/verification/SASDecimal";
3130
import { UnstableValue } from "../../NamespacedValue";
3231

@@ -56,11 +55,11 @@ export interface EncryptedPayload {
5655
}
5756

5857
async function importKey(key: Uint8Array): Promise<CryptoKey> {
59-
if (!subtleCrypto) {
58+
if (!globalThis.crypto.subtle) {
6059
throw new Error("Web Crypto is not available");
6160
}
6261

63-
const imported = subtleCrypto.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
62+
const imported = globalThis.crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
6463

6564
return imported;
6665
}
@@ -164,16 +163,16 @@ export class MSC3903ECDHv2RendezvousChannel<T> implements RendezvousChannel<T> {
164163
}
165164

166165
private async encrypt(data: T): Promise<MSC3903ECDHPayload> {
167-
if (!subtleCrypto) {
166+
if (!globalThis.crypto.subtle) {
168167
throw new Error("Web Crypto is not available");
169168
}
170169

171170
const iv = new Uint8Array(32);
172-
crypto.getRandomValues(iv);
171+
globalThis.crypto.getRandomValues(iv);
173172

174173
const encodedData = new TextEncoder().encode(JSON.stringify(data));
175174

176-
const ciphertext = await subtleCrypto.encrypt(
175+
const ciphertext = await globalThis.crypto.subtle.encrypt(
177176
{
178177
name: "AES-GCM",
179178
iv,
@@ -208,11 +207,11 @@ export class MSC3903ECDHv2RendezvousChannel<T> implements RendezvousChannel<T> {
208207

209208
const ciphertextBytes = decodeBase64(ciphertext);
210209

211-
if (!subtleCrypto) {
210+
if (!globalThis.crypto.subtle) {
212211
throw new Error("Web Crypto is not available");
213212
}
214213

215-
const plaintext = await subtleCrypto.decrypt(
214+
const plaintext = await globalThis.crypto.subtle.decrypt(
216215
{
217216
name: "AES-GCM",
218217
iv: decodeBase64(iv),

src/rust-crypto/DehydratedDeviceManager.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { encodeUri } from "../utils";
2121
import { IHttpOpts, MatrixError, MatrixHttpApi, Method } from "../http-api";
2222
import { IToDeviceEvent } from "../sync-accumulator";
2323
import { ServerSideSecretStorage } from "../secret-storage";
24-
import { crypto } from "../crypto/crypto";
2524
import { decodeBase64, encodeUnpaddedBase64 } from "../base64";
2625
import { Logger } from "../logger";
2726

@@ -155,7 +154,7 @@ export class DehydratedDeviceManager {
155154
*/
156155
public async resetKey(): Promise<void> {
157156
const key = new Uint8Array(32);
158-
crypto.getRandomValues(key);
157+
globalThis.crypto.getRandomValues(key);
159158
await this.secretStorage.store(SECRET_STORAGE_NAME, encodeUnpaddedBase64(key));
160159
this.key = key;
161160
}

0 commit comments

Comments
 (0)