Skip to content

Commit 3c22b7d

Browse files
committed
Remove argon2 password hashing
1 parent 5efb766 commit 3c22b7d

File tree

4 files changed

+53
-146
lines changed

4 files changed

+53
-146
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
},
6060
"dependencies": {
6161
"@vscode/vscode-languagedetection": "1.0.15",
62-
"argon2": "^0.28.2",
6362
"applicationinsights": "1.0.8",
6463
"chokidar": "3.5.1",
6564
"express": "^4.17.1",

src/vs/server/node/auth.ts

Lines changed: 18 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import * as http from 'http';
21
import * as crypto from 'crypto';
3-
import * as argon2 from 'argon2';
2+
import * as express from 'express';
43
import { ServerParsedArgs } from 'vs/server/node/args';
5-
import { serveError } from 'vs/server/node/http';
64

75
/** Ensures that the input is sanitized by checking
86
* - it's a string
@@ -15,92 +13,39 @@ export function sanitizeString(str: string): string {
1513
return typeof str === 'string' && str.trim().length > 0 ? str.trim() : '';
1614
}
1715

18-
export const ensureAuthenticated = async (args: ServerParsedArgs, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> => {
19-
const isAuthenticated = await authenticated(args, req);
20-
if (!isAuthenticated) {
21-
serveError(req, res, 401, 'Unauthorized');
22-
}
23-
return isAuthenticated;
24-
};
25-
2616
/**
2717
* Return true if authenticated via cookies.
2818
*/
29-
export const authenticated = async (args: ServerParsedArgs, req: http.IncomingMessage): Promise<boolean> => {
19+
export const authenticated = async (args: ServerParsedArgs, req: express.Request): Promise<boolean> => {
3020
if (!args.password && !args.hashedPassword) {
3121
return true;
3222
}
33-
const passwordMethod = getPasswordMethod(args.hashedPassword);
34-
const cookies = parseCookies(req);
3523
const isCookieValidArgs: IsCookieValidArgs = {
36-
passwordMethod,
37-
cookieKey: sanitizeString(cookies.key),
38-
passwordFromArgs: args.password || '',
39-
hashedPasswordFromArgs: args.hashedPassword,
24+
cookieKey: sanitizeString(req.cookies.key),
25+
passwordFromArgs: args.password || ''
4026
};
4127

4228
return await isCookieValid(isCookieValidArgs);
4329
};
4430

45-
function parseCookies(request: http.IncomingMessage): Record<string, string> {
46-
const cookies: Record<string, string> = {},
47-
rc = request.headers.cookie;
48-
49-
// eslint-disable-next-line code-no-unused-expressions
50-
rc && rc.split(';').forEach(cookie => {
51-
let parts = cookie.split('=');
52-
if (parts.length > 0) {
53-
const name = parts.shift()!.trim();
54-
let value = decodeURI(parts.join('='));
55-
value = value.substring(1, value.length - 1);
56-
cookies[name] = value;
57-
}
58-
});
59-
60-
return cookies;
61-
}
62-
63-
export type PasswordMethod = 'ARGON2' | 'PLAIN_TEXT';
64-
65-
/**
66-
* Used to determine the password method.
67-
*
68-
* There are three options for the return value:
69-
* 1. "SHA256" -> the legacy hashing algorithm
70-
* 2. "ARGON2" -> the newest hashing algorithm
71-
* 3. "PLAIN_TEXT" -> regular ol' password with no hashing
72-
*
73-
* @returns "ARGON2" | "PLAIN_TEXT"
74-
*/
75-
export function getPasswordMethod(hashedPassword: string | undefined): PasswordMethod {
76-
if (!hashedPassword) {
77-
return 'PLAIN_TEXT';
78-
}
79-
return 'ARGON2';
80-
}
81-
8231
type PasswordValidation = {
8332
isPasswordValid: boolean
8433
hashedPassword: string
8534
};
8635

8736
type HandlePasswordValidationArgs = {
88-
/** The PasswordMethod */
89-
passwordMethod: PasswordMethod
9037
/** The password provided by the user */
91-
passwordFromRequestBody: string
38+
passwordFromRequestBody: string | undefined
9239
/** The password set in PASSWORD or config */
9340
passwordFromArgs: string | undefined
94-
/** The hashed-password set in HASHED_PASSWORD or config */
95-
hashedPasswordFromArgs: string | undefined
9641
};
9742

9843
function safeCompare(a: string, b: string): boolean {
9944
if (b.length > a.length) {
100-
a = a.padEnd(b.length);
45+
a = a.padEnd(b.length, '0');
10146
}
10247
if (a.length > b.length) {
103-
b = b.padEnd(a.length);
48+
b = b.padEnd(a.length, '0');
10449
}
10550
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
10651
}
@@ -113,114 +58,44 @@ export const generatePassword = async (length = 24): Promise<string> => {
11358
return buffer.toString('hex').substring(0, length);
11459
};
11560

116-
/**
117-
* Used to hash the password.
118-
*/
119-
export const hash = async (password: string): Promise<string> => {
120-
try {
121-
return await argon2.hash(password);
122-
} catch (error) {
123-
console.error(error);
124-
return '';
125-
}
126-
};
127-
128-
/**
129-
* Used to verify if the password matches the hash
130-
*/
131-
export const isHashMatch = async (password: string, hash: string) => {
132-
if (password === '' || hash === '' || !hash.startsWith('$')) {
133-
return false;
134-
}
135-
try {
136-
return await argon2.verify(hash, password);
137-
} catch (error) {
138-
throw new Error(error);
139-
}
140-
};
141-
142-
/**
143-
* Used to hash the password using the sha256
144-
* algorithm. We only use this to for checking
145-
* the hashed-password set in the config.
146-
*
147-
* Kept for legacy reasons.
148-
*/
149-
export const hashLegacy = (str: string): string => {
61+
export const hash = (str: string): string => {
15062
return crypto.createHash('sha256').update(str).digest('hex');
15163
};
15264

153-
/**
154-
* Used to check if the password matches the hash using
155-
* the hashLegacy function
156-
*/
157-
export const isHashLegacyMatch = (password: string, hashPassword: string) => {
158-
const hashedWithLegacy = hashLegacy(password);
65+
export const isHashMatch = (password: string, hashPassword: string) => {
66+
const hashedWithLegacy = hash(password);
15967
return safeCompare(hashedWithLegacy, hashPassword);
16068
};
16169

162-
/**
163-
* Checks if a password is valid and also returns the hash
164-
* using the PasswordMethod
165-
*/
16670
export async function handlePasswordValidation({
167-
passwordMethod,
16871
passwordFromArgs,
169-
passwordFromRequestBody,
170-
hashedPasswordFromArgs,
72+
passwordFromRequestBody
17173
}: HandlePasswordValidationArgs): Promise<PasswordValidation> {
17274
const passwordValidation: PasswordValidation = {
17375
isPasswordValid: false,
17476
hashedPassword: '',
17577
};
17678

177-
switch (passwordMethod) {
178-
case 'PLAIN_TEXT': {
179-
const isValid = passwordFromArgs ? safeCompare(passwordFromRequestBody, passwordFromArgs) : false;
180-
passwordValidation.isPasswordValid = isValid;
79+
if (passwordFromRequestBody) {
80+
const isValid = passwordFromArgs ? safeCompare(passwordFromRequestBody, passwordFromArgs) : false;
81+
passwordValidation.isPasswordValid = isValid;
18182

182-
const hashedPassword = await hash(passwordFromRequestBody);
183-
passwordValidation.hashedPassword = hashedPassword;
184-
break;
185-
}
186-
case 'ARGON2': {
187-
const isValid = await isHashMatch(passwordFromRequestBody, hashedPasswordFromArgs || '');
188-
passwordValidation.isPasswordValid = isValid;
189-
190-
passwordValidation.hashedPassword = hashedPasswordFromArgs || '';
191-
break;
192-
}
193-
default:
194-
break;
83+
const hashedPassword = hash(passwordFromRequestBody);
84+
passwordValidation.hashedPassword = hashedPassword;
19585
}
19686

19787
return passwordValidation;
19888
}
19989

20090
export type IsCookieValidArgs = {
201-
passwordMethod: PasswordMethod
20291
cookieKey: string
203-
hashedPasswordFromArgs: string | undefined
20492
passwordFromArgs: string | undefined
20593
};
20694

20795
/** Checks if a req.cookies.key is valid using the PasswordMethod */
20896
export async function isCookieValid({
20997
passwordFromArgs = '',
210-
cookieKey,
211-
hashedPasswordFromArgs = '',
212-
passwordMethod,
98+
cookieKey
21399
}: IsCookieValidArgs): Promise<boolean> {
214-
let isValid = false;
215-
switch (passwordMethod) {
216-
case 'PLAIN_TEXT':
217-
isValid = await isHashMatch(passwordFromArgs, cookieKey);
218-
break;
219-
case 'ARGON2':
220-
isValid = safeCompare(cookieKey, hashedPasswordFromArgs);
221-
break;
222-
default:
223-
break;
224-
}
225-
return isValid;
100+
return isHashMatch(passwordFromArgs, cookieKey);
226101
}

src/vs/server/node/server.main.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ import { IGetEnvironmentDataArguments, IRemoteAgentEnvironmentDTO, IScanExtensio
5656
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
5757
import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService';
5858
import { args, devMode } from 'vs/server/node/args';
59-
import { handleHttp, IServerOptions, rawURITransformerFactory } from 'vs/server/node/server.http';
59+
import { handleHttp, rawURITransformerFactory } from 'vs/server/node/server.http';
60+
import { IServerOptions } from 'vs/server/node/server.opts';
6061

6162
export type IRawURITransformerFactory = (remoteAuthority: string) => IRawURITransformer;
6263
export const IRawURITransformerFactory = createDecorator<IRawURITransformerFactory>('rawURITransformerFactory');
@@ -426,6 +427,13 @@ export async function main(options: IServerOptions): Promise<void> {
426427
// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)
427428
bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, bufferLogService.getLevel());
428429

429-
handleHttp(options, instantiationService, logService, environmentService, onDidClientConnectEmitter, channelServer);
430+
handleHttp({
431+
serverOptions: options,
432+
instantiationService,
433+
logService,
434+
environmentService,
435+
onDidClientConnectEmitter,
436+
channelServer
437+
});
430438
});
431439
}

src/vs/server/node/server.opts.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as cp from 'child_process';
2+
import * as http from 'http';
3+
import { IDisposable } from 'vs/base/common/lifecycle';
4+
import { IPCServer } from 'vs/base/parts/ipc/common/ipc';
5+
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
6+
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
7+
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
8+
9+
export interface IStartServerResult {
10+
installingInitialExtensions?: Promise<void>
11+
}
12+
13+
export interface IServerOptions {
14+
port?: number;
15+
main?: string
16+
mainDev?: string
17+
skipExtensions?: Set<string>
18+
configure?(services: ServiceCollection, channelServer: IPCServer<RemoteAgentConnectionContext>): void
19+
start?(accessor: ServicesAccessor, channelServer: IPCServer<RemoteAgentConnectionContext>): IStartServerResult | void
20+
21+
configureExtensionHostForkOptions?(opts: cp.ForkOptions, accessor: ServicesAccessor, channelServer: IPCServer<RemoteAgentConnectionContext>): void;
22+
configureExtensionHostProcess?(extensionHost: cp.ChildProcess, accessor: ServicesAccessor, channelServer: IPCServer<RemoteAgentConnectionContext>): IDisposable;
23+
24+
handleRequest?(pathname: string | null, req: http.IncomingMessage, res: http.ServerResponse, accessor: ServicesAccessor, channelServer: IPCServer<RemoteAgentConnectionContext>): Promise<boolean>;
25+
}

0 commit comments

Comments
 (0)