From 2018024810269eeaa5ce868e555d5476da2a626d Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 7 Nov 2019 15:43:10 -0600 Subject: [PATCH] Hash password Fixes issues with unexpected characters breaking things when setting the cookie (like semicolons). This change as-is does not affect the security of code-server itself (we've just replaced the static password with a static hash) but if we were to add a salt in the future it would let us invalidate keys by rehashing with a new salt which could be handy. --- src/node/server.ts | 35 +++++++++++++++++++++-------------- src/node/util.ts | 4 ++++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/node/server.ts b/src/node/server.ts index c95ad1fb..a2d477da 100644 --- a/src/node/server.ts +++ b/src/node/server.ts @@ -63,7 +63,7 @@ import { TelemetryClient } from "vs/server/src/node/insights"; import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls"; import { Protocol } from "vs/server/src/node/protocol"; import { UpdateService } from "vs/server/src/node/update"; -import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from "vs/server/src/node/util"; +import { AuthType, getMediaMime, getUriTransformer, hash, localRequire, tmpdir } from "vs/server/src/node/util"; import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService"; import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api"; @@ -98,7 +98,11 @@ export interface Response { } export interface LoginPayload { - password?: string[] | string; + password?: string; +} + +export interface AuthPayload { + key?: string[]; } export class HttpError extends Error { @@ -137,6 +141,7 @@ export abstract class Server { host: options.auth === "password" && options.cert ? "0.0.0.0" : "localhost", ...options, basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "", + password: options.password ? hash(options.password) : undefined, }; this.protocol = this.options.cert ? "https" : "http"; if (this.protocol === "https") { @@ -357,11 +362,11 @@ export abstract class Server { } private async tryLogin(request: http.IncomingMessage): Promise { - const redirect = (password?: string | string[] | true) => { + const redirect = (password: string | true) => { return { redirect: "/", headers: typeof password === "string" - ? { "Set-Cookie": `password=${password}; Path=${this.options.basePath || "/"}; HttpOnly; SameSite=strict` } + ? { "Set-Cookie": `key=${password}; Path=${this.options.basePath || "/"}; HttpOnly; SameSite=strict` } : {}, }; }; @@ -371,8 +376,11 @@ export abstract class Server { } if (request.method === "POST") { const data = await this.getData(request); - if (this.authenticate(request, data)) { - return redirect(data.password); + const password = this.authenticate(request, { + key: typeof data.password === "string" ? [hash(data.password)] : undefined, + }); + if (password) { + return redirect(password); } console.error("Failed login attempt", JSON.stringify({ xForwardedFor: request.headers["x-forwarded-for"], @@ -432,19 +440,18 @@ export abstract class Server { : Promise.resolve({} as T); } - private authenticate(request: http.IncomingMessage, payload?: LoginPayload): string | boolean { - if (this.options.auth !== "password") { + private authenticate(request: http.IncomingMessage, payload?: AuthPayload): string | boolean { + if (this.options.auth === "none") { return true; } const safeCompare = localRequire("safe-compare/index"); if (typeof payload === "undefined") { - payload = this.parseCookies(request); + payload = this.parseCookies(request); } - if (this.options.password && payload.password) { - const toTest = Array.isArray(payload.password) ? payload.password : [payload.password]; - for (let i = 0; i < toTest.length; ++i) { - if (safeCompare(toTest[i], this.options.password)) { - return toTest[i]; + if (this.options.password && payload.key) { + for (let i = 0; i < payload.key.length; ++i) { + if (safeCompare(payload.key[i], this.options.password)) { + return payload.key[i]; } } } diff --git a/src/node/util.ts b/src/node/util.ts index 078fe0ef..f5dae6bd 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -67,6 +67,10 @@ export const generatePassword = async (length: number = 24): Promise => return buffer.toString("hex").substring(0, length); }; +export const hash = (str: string): string => { + return crypto.createHash("sha256").update(str).digest("hex"); +}; + export const getMediaMime = (filePath?: string): string => { return filePath && (vsGetMediaMime(filePath) || (<{[index: string]: string}>{ ".css": "text/css",