From ef415578d1b1019db585718968a303eeca4b8979 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 10 Nov 2022 18:26:48 -0800 Subject: [PATCH] Take advantage of platform features in Microsoft Authentication extension (#166066) --- .../extension-browser.webpack.config.js | 6 +- .../microsoft-authentication/package.json | 5 -- .../microsoft-authentication/src/AADHelper.ts | 36 +++++----- .../src/{env => }/browser/authServer.ts | 0 .../browser/sha256.ts => browser/buffer.ts} | 10 +-- .../{env/node/sha256.ts => browser/crypto.ts} | 6 +- .../src/browser/fetch.ts | 6 ++ .../src/cryptoUtils.ts | 45 +++++++++++++ .../src/{ => node}/authServer.ts | 0 .../src/node/buffer.ts | 12 ++++ .../src/node/crypto.ts | 7 ++ .../src/node/fetch.ts | 7 ++ .../microsoft-authentication/src/utils.ts | 4 -- extensions/microsoft-authentication/yarn.lock | 65 ------------------- 14 files changed, 105 insertions(+), 104 deletions(-) rename extensions/microsoft-authentication/src/{env => }/browser/authServer.ts (100%) rename extensions/microsoft-authentication/src/{env/browser/sha256.ts => browser/buffer.ts} (66%) rename extensions/microsoft-authentication/src/{env/node/sha256.ts => browser/crypto.ts} (68%) create mode 100644 extensions/microsoft-authentication/src/browser/fetch.ts create mode 100644 extensions/microsoft-authentication/src/cryptoUtils.ts rename extensions/microsoft-authentication/src/{ => node}/authServer.ts (100%) create mode 100644 extensions/microsoft-authentication/src/node/buffer.ts create mode 100644 extensions/microsoft-authentication/src/node/crypto.ts create mode 100644 extensions/microsoft-authentication/src/node/fetch.ts diff --git a/extensions/microsoft-authentication/extension-browser.webpack.config.js b/extensions/microsoft-authentication/extension-browser.webpack.config.js index 9530f6050a39f..4d0866c793f53 100644 --- a/extensions/microsoft-authentication/extension-browser.webpack.config.js +++ b/extensions/microsoft-authentication/extension-browser.webpack.config.js @@ -25,8 +25,10 @@ module.exports = withBrowserDefaults({ }, resolve: { alias: { - './env/node': path.resolve(__dirname, 'src/env/browser'), - './authServer': path.resolve(__dirname, 'src/env/browser/authServer'), + './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), + './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), + './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), + './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), } } }); diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 86caf8a385ae8..0b56f7a07a3c3 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -55,12 +55,7 @@ "@types/uuid": "8.0.0" }, "dependencies": { - "buffer": "^5.6.0", "node-fetch": "2.6.7", - "randombytes": "~2.1.0", - "sha.js": "2.4.11", - "stream": "0.0.2", - "uuid": "^8.2.0", "@vscode/extension-telemetry": "0.7.0-preview" }, "repository": { diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 3a7307a47c033..3609ca4bf4782 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -3,18 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as randomBytes from 'randombytes'; -import * as querystring from 'querystring'; -import { Buffer } from 'buffer'; import * as vscode from 'vscode'; -import { v4 as uuid } from 'uuid'; -import fetch, { Response } from 'node-fetch'; +import * as querystring from 'querystring'; +import path = require('path'); import Logger from './logger'; -import { isSupportedEnvironment, toBase64UrlEncoding } from './utils'; -import { sha256 } from './env/node/sha256'; +import { isSupportedEnvironment } from './utils'; +import { generateCodeChallenge, generateCodeVerifier, randomUUID } from './cryptoUtils'; import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; -import { LoopbackAuthServer } from './authServer'; -import path = require('path'); +import { LoopbackAuthServer } from './node/authServer'; +import { base64Decode } from './node/buffer'; +import { fetching } from './node/fetch'; const redirectUrl = 'https://vscode.dev/redirect'; const loginEndpointUrl = 'https://login.microsoftonline.com/'; @@ -295,8 +293,8 @@ export class AzureActiveDirectoryService { } private async createSessionWithLocalServer(scopeData: IScopeData) { - const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier)); + const codeVerifier = generateCodeVerifier(); + const codeChallenge = await generateCodeChallenge(codeVerifier); const qs = new URLSearchParams({ response_type: 'code', response_mode: 'query', @@ -328,15 +326,15 @@ export class AzureActiveDirectoryService { private async createSessionWithoutLocalServer(scopeData: IScopeData): Promise { let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`)); - const nonce = randomBytes(16).toString('base64'); + const nonce = generateCodeVerifier(); const callbackQuery = new URLSearchParams(callbackUri.query); callbackQuery.set('nonce', encodeURIComponent(nonce)); callbackUri = callbackUri.with({ query: callbackQuery.toString() }); const state = encodeURIComponent(callbackUri.toString(true)); - const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier)); + const codeVerifier = generateCodeVerifier(); + const codeChallenge = await generateCodeChallenge(codeVerifier); const signInUrl = `${loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize`; const oauthStartQuery = new URLSearchParams({ response_type: 'code', @@ -467,10 +465,10 @@ export class AzureActiveDirectoryService { try { if (json.id_token) { - claims = JSON.parse(Buffer.from(json.id_token.split('.')[1], 'base64').toString()); + claims = JSON.parse(base64Decode(json.id_token.split('.')[1])); } else { Logger.info('Attempting to parse access_token instead since no id_token was included in the response.'); - claims = JSON.parse(Buffer.from(json.access_token.split('.')[1], 'base64').toString()); + claims = JSON.parse(base64Decode(json.access_token.split('.')[1])); } } catch (e) { throw e; @@ -491,7 +489,7 @@ export class AzureActiveDirectoryService { idToken: json.id_token, refreshToken: json.refresh_token, scope: scopeData.scopeStr, - sessionId: existingId || `${id}/${uuid()}`, + sessionId: existingId || `${id}/${randomUUID()}`, account: { label, id @@ -739,10 +737,10 @@ export class AzureActiveDirectoryService { let attempts = 0; while (attempts <= 3) { attempts++; - let result: Response | undefined; + let result; let errorMessage: string | undefined; try { - result = await fetch(endpoint, { + result = await fetching(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/extensions/microsoft-authentication/src/env/browser/authServer.ts b/extensions/microsoft-authentication/src/browser/authServer.ts similarity index 100% rename from extensions/microsoft-authentication/src/env/browser/authServer.ts rename to extensions/microsoft-authentication/src/browser/authServer.ts diff --git a/extensions/microsoft-authentication/src/env/browser/sha256.ts b/extensions/microsoft-authentication/src/browser/buffer.ts similarity index 66% rename from extensions/microsoft-authentication/src/env/browser/sha256.ts rename to extensions/microsoft-authentication/src/browser/buffer.ts index 369a1533cf95d..4753622f7425f 100644 --- a/extensions/microsoft-authentication/src/env/browser/sha256.ts +++ b/extensions/microsoft-authentication/src/browser/buffer.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; +export function base64Encode(text: string): string { + return btoa(text); +} -export async function sha256(s: string | Uint8Array): Promise { - const createHash = require('sha.js'); - return createHash('sha256').update(s).digest('base64'); +export function base64Decode(text: string): string { + const data = atob(text); + return data; } diff --git a/extensions/microsoft-authentication/src/env/node/sha256.ts b/extensions/microsoft-authentication/src/browser/crypto.ts similarity index 68% rename from extensions/microsoft-authentication/src/env/node/sha256.ts rename to extensions/microsoft-authentication/src/browser/crypto.ts index 691f7eedaf939..37a1b43361f45 100644 --- a/extensions/microsoft-authentication/src/env/node/sha256.ts +++ b/extensions/microsoft-authentication/src/browser/crypto.ts @@ -3,8 +3,4 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - -export async function sha256(s: string | Uint8Array): Promise { - return (require('crypto')).createHash('sha256').update(s).digest('base64'); -} +export const crypto = globalThis.crypto; diff --git a/extensions/microsoft-authentication/src/browser/fetch.ts b/extensions/microsoft-authentication/src/browser/fetch.ts new file mode 100644 index 0000000000000..f7f69f1a1a1b5 --- /dev/null +++ b/extensions/microsoft-authentication/src/browser/fetch.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const fetching = fetch; diff --git a/extensions/microsoft-authentication/src/cryptoUtils.ts b/extensions/microsoft-authentication/src/cryptoUtils.ts new file mode 100644 index 0000000000000..582dae74c3c37 --- /dev/null +++ b/extensions/microsoft-authentication/src/cryptoUtils.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { base64Encode } from './node/buffer'; +import { crypto } from './node/crypto'; + +export function randomUUID() { + return crypto.randomUUID(); +} + +function dec2hex(dec: number): string { + return ('0' + dec.toString(16)).slice(-2); +} + +export function generateCodeVerifier(): string { + const array = new Uint32Array(56 / 2); + crypto.getRandomValues(array); + return Array.from(array, dec2hex).join(''); +} + +function sha256(plain: string | undefined) { + const encoder = new TextEncoder(); + const data = encoder.encode(plain); + return crypto.subtle.digest('SHA-256', data); +} + +function base64urlencode(a: ArrayBuffer) { + let str = ''; + const bytes = new Uint8Array(a); + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + str += String.fromCharCode(bytes[i]); + } + return base64Encode(str) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} + +export async function generateCodeChallenge(v: string) { + const hashed = await sha256(v); + const base64encoded = base64urlencode(hashed); + return base64encoded; +} diff --git a/extensions/microsoft-authentication/src/authServer.ts b/extensions/microsoft-authentication/src/node/authServer.ts similarity index 100% rename from extensions/microsoft-authentication/src/authServer.ts rename to extensions/microsoft-authentication/src/node/authServer.ts diff --git a/extensions/microsoft-authentication/src/node/buffer.ts b/extensions/microsoft-authentication/src/node/buffer.ts new file mode 100644 index 0000000000000..1ed028d4faa80 --- /dev/null +++ b/extensions/microsoft-authentication/src/node/buffer.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function base64Encode(text: string): string { + return Buffer.from(text, 'binary').toString('base64'); +} + +export function base64Decode(text: string): string { + return Buffer.from(text, 'base64').toString('utf8'); +} diff --git a/extensions/microsoft-authentication/src/node/crypto.ts b/extensions/microsoft-authentication/src/node/crypto.ts new file mode 100644 index 0000000000000..1b45ad993c335 --- /dev/null +++ b/extensions/microsoft-authentication/src/node/crypto.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as nodeCrypto from 'crypto'; + +export const crypto: Crypto = nodeCrypto.webcrypto as any; diff --git a/extensions/microsoft-authentication/src/node/fetch.ts b/extensions/microsoft-authentication/src/node/fetch.ts new file mode 100644 index 0000000000000..58718078e6995 --- /dev/null +++ b/extensions/microsoft-authentication/src/node/fetch.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import fetch from 'node-fetch'; + +export const fetching = fetch; diff --git a/extensions/microsoft-authentication/src/utils.ts b/extensions/microsoft-authentication/src/utils.ts index 443bb2dc0480c..7382cc2f4f269 100644 --- a/extensions/microsoft-authentication/src/utils.ts +++ b/extensions/microsoft-authentication/src/utils.ts @@ -4,10 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { env, UIKind, Uri } from 'vscode'; -export function toBase64UrlEncoding(base64string: string) { - return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding -} - const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; function isLocalhost(uri: Uri): boolean { if (!/^https?$/i.test(uri.scheme)) { diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index bfbd5e7e3735b..869896c8ebfa7 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -297,19 +297,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -buffer@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - cls-hooked@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" @@ -351,11 +338,6 @@ diagnostic-channel@1.1.0: dependencies: semver "^5.3.0" -emitter-component@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6" - integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY= - emitter-listener@^1.0.1, emitter-listener@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" @@ -381,16 +363,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -inherits@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" @@ -430,28 +402,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== -randombytes@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -safe-buffer@^5.0.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -462,14 +417,6 @@ semver@^5.3.0, semver@^5.4.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -sha.js@2.4.11: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - shimmer@^1.1.0, shimmer@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" @@ -480,13 +427,6 @@ stack-chain@^1.3.7: resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== -stream@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef" - integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8= - dependencies: - emitter-component "^1.1.1" - tough-cookie@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" @@ -525,11 +465,6 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -uuid@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" - integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== - uuid@^8.3.0: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"