Skip to content

Commit 86494c3

Browse files
committed
Change randomString et al to be secure
...and renames them, removing the special lowercase and uppercase versions and exporting the underlying function instead. Any apps that use these will either need to take the speed hit from secure random functions and use the new ones, or write their own insecure versions. The lowercase and uppercasde verisons were used exactly once each in element-web and never in js-sdk itself. The underlying function is very simple and exporting just this gives more flexibility with fewer exports.
1 parent bdd4d82 commit 86494c3

File tree

14 files changed

+80
-68
lines changed

14 files changed

+80
-68
lines changed

spec/unit/interactive-auth.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { logger } from "../../src/logger";
2020
import { InteractiveAuth, AuthType } from "../../src/interactive-auth";
2121
import { HTTPError, MatrixError } from "../../src/http-api";
2222
import { sleep } from "../../src/utils";
23-
import { randomString } from "../../src/randomstring";
23+
import { secureRandomString } from "../../src/randomstring";
2424

2525
// Trivial client object to test interactive auth
2626
// (we do not need TestClient here)
@@ -502,7 +502,7 @@ describe("InteractiveAuth", () => {
502502
const doRequest = jest.fn();
503503
const stateUpdated = jest.fn();
504504
const requestEmailToken = jest.fn();
505-
const sid = randomString(24);
505+
const sid = secureRandomString(24);
506506
requestEmailToken.mockImplementation(() => sleep(500, { sid }));
507507

508508
const ia = new InteractiveAuth({

spec/unit/matrixrtc/MatrixRTCSession.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { KnownMembership } from "../../../src/@types/membership";
1919
import { DEFAULT_EXPIRE_DURATION, SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
2020
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
2121
import { EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
22-
import { randomString } from "../../../src/randomstring";
22+
import { secureRandomString } from "../../../src/randomstring";
2323
import { flushPromises } from "../../test-utils/flushPromises";
2424
import { makeMockRoom, makeMockRoomState, membershipTemplate } from "./mocks";
2525

@@ -98,7 +98,7 @@ describe("MatrixRTCSession", () => {
9898
});
9999

100100
it("safely ignores events with no memberships section", () => {
101-
const roomId = randomString(8);
101+
const roomId = secureRandomString(8);
102102
const event = {
103103
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
104104
getContent: jest.fn().mockReturnValue({}),
@@ -133,7 +133,7 @@ describe("MatrixRTCSession", () => {
133133
});
134134

135135
it("safely ignores events with junk memberships section", () => {
136-
const roomId = randomString(8);
136+
const roomId = secureRandomString(8);
137137
const event = {
138138
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
139139
getContent: jest.fn().mockReturnValue({ memberships: ["i am a fish"] }),

spec/unit/matrixrtc/mocks.ts

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

1717
import { EventType, MatrixEvent, Room } from "../../../src";
1818
import { SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
19-
import { randomString } from "../../../src/randomstring";
19+
import { secureRandomString } from "../../../src/randomstring";
2020

2121
type MembershipData = SessionMembershipData[] | SessionMembershipData | {};
2222

@@ -41,7 +41,7 @@ export const membershipTemplate: SessionMembershipData = {
4141
};
4242

4343
export function makeMockRoom(membershipData: MembershipData): Room {
44-
const roomId = randomString(8);
44+
const roomId = secureRandomString(8);
4545
// Caching roomState here so it does not get recreated when calling `getLiveTimeline.getState()`
4646
const roomState = makeMockRoomState(membershipData, roomId);
4747
const room = {

spec/unit/randomstring.spec.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ limitations under the License.
1616

1717
import { decodeBase64 } from "../../src/base64";
1818
import {
19-
randomLowercaseString,
20-
randomString,
21-
randomUppercaseString,
19+
secureRandomString,
2220
secureRandomBase64Url,
21+
secureRandomStringFrom,
22+
LOWERCASE,
23+
UPPERCASE,
2324
} from "../../src/randomstring";
2425

2526
describe("Random strings", () => {
@@ -33,34 +34,40 @@ describe("Random strings", () => {
3334
expect(decoded).toHaveLength(n);
3435
});
3536

36-
it.each([8, 16, 32])("randomString generates string of %i characters", (n: number) => {
37-
const rand1 = randomString(n);
38-
const rand2 = randomString(n);
37+
it.each([8, 16, 32])("secureRandomString generates string of %i characters", (n: number) => {
38+
const rand1 = secureRandomString(n);
39+
const rand2 = secureRandomString(n);
3940

4041
expect(rand1).not.toEqual(rand2);
4142

4243
expect(rand1).toHaveLength(n);
4344
});
4445

45-
it.each([8, 16, 32])("randomLowercaseString generates lowercase string of %i characters", (n: number) => {
46-
const rand1 = randomLowercaseString(n);
47-
const rand2 = randomLowercaseString(n);
46+
it.each([8, 16, 32])(
47+
"secureRandomStringFrom generates lowercase string of %i characters when given lowercase",
48+
(n: number) => {
49+
const rand1 = secureRandomStringFrom(n, LOWERCASE);
50+
const rand2 = secureRandomStringFrom(n, LOWERCASE);
4851

49-
expect(rand1).not.toEqual(rand2);
52+
expect(rand1).not.toEqual(rand2);
5053

51-
expect(rand1).toHaveLength(n);
54+
expect(rand1).toHaveLength(n);
5255

53-
expect(rand1.toLowerCase()).toEqual(rand1);
54-
});
56+
expect(rand1.toLowerCase()).toEqual(rand1);
57+
},
58+
);
5559

56-
it.each([8, 16, 32])("randomUppercaseString generates lowercase string of %i characters", (n: number) => {
57-
const rand1 = randomUppercaseString(n);
58-
const rand2 = randomUppercaseString(n);
60+
it.each([8, 16, 32])(
61+
"secureRandomStringFrom generates uppercase string of %i characters when given uppercase",
62+
(n: number) => {
63+
const rand1 = secureRandomStringFrom(n, UPPERCASE);
64+
const rand2 = secureRandomStringFrom(n, UPPERCASE);
5965

60-
expect(rand1).not.toEqual(rand2);
66+
expect(rand1).not.toEqual(rand2);
6167

62-
expect(rand1).toHaveLength(n);
68+
expect(rand1).toHaveLength(n);
6369

64-
expect(rand1.toUpperCase()).toEqual(rand1);
65-
});
70+
expect(rand1.toUpperCase()).toEqual(rand1);
71+
},
72+
);
6673
});

spec/unit/secret-storage.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
ServerSideSecretStorageImpl,
2828
trimTrailingEquals,
2929
} from "../../src/secret-storage";
30-
import { randomString } from "../../src/randomstring";
30+
import { secureRandomString } from "../../src/randomstring";
3131
import { SecretInfo } from "../../src/secret-storage.ts";
3232
import { AccountDataEvents, ClientEvent, MatrixEvent, TypedEventEmitter } from "../../src";
3333
import { defer, IDeferred } from "../../src/utils";
@@ -179,7 +179,7 @@ describe("ServerSideSecretStorageImpl", function () {
179179
it("should return true for a correct key check", async function () {
180180
const secretStorage = new ServerSideSecretStorageImpl({} as AccountDataClient, {});
181181

182-
const myKey = new TextEncoder().encode(randomString(32));
182+
const myKey = new TextEncoder().encode(secureRandomString(32));
183183
const { iv, mac } = await calculateKeyCheck(myKey);
184184

185185
const keyInfo: SecretStorageKeyDescriptionAesV1 = {

spec/unit/thread-utils.spec.ts

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

1717
import { IEvent } from "../../src";
18-
import { randomString } from "../../src/randomstring";
18+
import { secureRandomString } from "../../src/randomstring";
1919
import { getRelationsThreadFilter } from "../../src/thread-utils";
2020

2121
function makeEvent(relatesToEvent: string, relType: string): Partial<IEvent> {
2222
return {
23-
event_id: randomString(10),
23+
event_id: secureRandomString(10),
2424
type: "m.room.message",
2525
content: {
2626
"msgtype": "m.text",

src/client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ import {
160160
Visibility,
161161
} from "./@types/partials.ts";
162162
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper.ts";
163-
import { randomString } from "./randomstring.ts";
163+
import { secureRandomString } from "./randomstring.ts";
164164
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup.ts";
165165
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace.ts";
166166
import { ISignatures } from "./@types/signed.ts";
@@ -1346,7 +1346,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
13461346
this.usingExternalCrypto = opts.usingExternalCrypto ?? false;
13471347
this.store = opts.store || new StubStore();
13481348
this.deviceId = opts.deviceId || null;
1349-
this.sessionId = randomString(10);
1349+
this.sessionId = secureRandomString(10);
13501350

13511351
const userId = opts.userId || null;
13521352
this.credentials = { userId };
@@ -7998,7 +7998,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
79987998
* @returns A new client secret
79997999
*/
80008000
public generateClientSecret(): string {
8001-
return randomString(32);
8001+
return secureRandomString(32);
80028002
}
80038003

80048004
/**

src/crypto/key_passphrase.ts

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

17-
import { randomString } from "../randomstring.ts";
17+
import { secureRandomString } from "../randomstring.ts";
1818
import { deriveRecoveryKeyFromPassphrase } from "../crypto-api/index.ts";
1919

2020
const DEFAULT_ITERATIONS = 500000;
@@ -30,7 +30,7 @@ interface IKey {
3030
* @param passphrase - The passphrase to generate the key from
3131
*/
3232
export async function keyFromPassphrase(passphrase: string): Promise<IKey> {
33-
const salt = randomString(32);
33+
const salt = secureRandomString(32);
3434

3535
const key = await deriveRecoveryKeyFromPassphrase(passphrase, salt, DEFAULT_ITERATIONS);
3636

src/crypto/verification/request/ToDeviceChannel.ts

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

18-
import { randomString } from "../../../randomstring.ts";
18+
import { secureRandomString } from "../../../randomstring.ts";
1919
import { logger } from "../../../logger.ts";
2020
import {
2121
CANCEL_TYPE,
@@ -283,7 +283,7 @@ export class ToDeviceChannel implements IVerificationChannel {
283283
* @returns the transaction id
284284
*/
285285
public static makeTransactionId(): string {
286-
return randomString(32);
286+
return secureRandomString(32);
287287
}
288288
}
289289

src/oidc/authorize.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
import { IdTokenClaims, Log, OidcClient, SigninResponse, SigninState, WebStorageStateStore } from "oidc-client-ts";
1818

1919
import { logger } from "../logger.ts";
20-
import { randomString } from "../randomstring.ts";
20+
import { secureRandomString } from "../randomstring.ts";
2121
import { OidcError } from "./error.ts";
2222
import {
2323
BearerTokenResponse,
@@ -52,7 +52,7 @@ export type AuthorizationParams = {
5252
* @returns scope
5353
*/
5454
export const generateScope = (deviceId?: string): string => {
55-
const safeDeviceId = deviceId ?? randomString(10);
55+
const safeDeviceId = deviceId ?? secureRandomString(10);
5656
return `openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:${safeDeviceId}`;
5757
};
5858

@@ -79,9 +79,9 @@ const generateCodeChallenge = async (codeVerifier: string): Promise<string> => {
7979
export const generateAuthorizationParams = ({ redirectUri }: { redirectUri: string }): AuthorizationParams => ({
8080
scope: generateScope(),
8181
redirectUri,
82-
state: randomString(8),
83-
nonce: randomString(8),
84-
codeVerifier: randomString(64), // https://tools.ietf.org/html/rfc7636#section-4.1 length needs to be 43-128 characters
82+
state: secureRandomString(8),
83+
nonce: secureRandomString(8),
84+
codeVerifier: secureRandomString(64), // https://tools.ietf.org/html/rfc7636#section-4.1 length needs to be 43-128 characters
8585
});
8686

8787
/**

0 commit comments

Comments
 (0)