Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/common/crypto.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { encodeHex, VSBuffer } from '../vs/base/common/buffer';
7
import * as strings from '../vs/base/common/strings';
8
9
export async function createRequestHMAC(hmacSecret: string | undefined): Promise<string | undefined> {
10
// If we don't have the right env variables this could happen
11
if (!hmacSecret) {
12
return undefined;
13
}
14
15
const key = await crypto.subtle.importKey(
16
'raw',
17
new TextEncoder().encode(hmacSecret),
18
{ name: 'HMAC', hash: 'SHA-256' },
19
false,
20
['sign']
21
);
22
23
const current = Math.floor(Date.now() / 1000).toString();
24
const textEncoder = new TextEncoder();
25
const data = textEncoder.encode(current);
26
27
const signature = await crypto.subtle.sign('HMAC', key, data);
28
const signatureArray = Array.from(new Uint8Array(signature));
29
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
30
31
return `${current}.${signatureHex}`;
32
}
33
34
export async function createSha256Hash(data: string | Uint8Array): Promise<string> {
35
const dataUint8 = typeof data === 'string' ? new TextEncoder().encode(data) : data;
36
const hashBuffer = await crypto.subtle.digest('SHA-256', dataUint8);
37
const hashArray = new Uint8Array(hashBuffer);
38
let hashHex = '';
39
for (const byte of hashArray) {
40
hashHex += byte.toString(16).padStart(2, '0');
41
}
42
43
return hashHex;
44
}
45
46
const _cachedSha256Hashes = new Map<string, string>();
47
export function getCachedSha256Hash(text: string): string {
48
if (_cachedSha256Hashes.has(text)) {
49
return _cachedSha256Hashes.get(text)!;
50
}
51
52
const hash = createSha256HashSyncInsecure(text);
53
_cachedSha256Hashes.set(text, hash);
54
return hash;
55
}
56
57
58
function createSha256HashSyncInsecure(data: string): string {
59
const sha256 = new StringSHA256Insecure();
60
sha256.update(data);
61
return sha256.digest();
62
}
63
64
const enum SHA256Constant {
65
BLOCK_SIZE = 64, // 512 / 8
66
UNICODE_REPLACEMENT = 0xFFFD,
67
}
68
69
function toHexString(buffer: ArrayBuffer): string;
70
function toHexString(value: number, bitsize?: number): string;
71
function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string {
72
if (bufferOrValue instanceof ArrayBuffer) {
73
return encodeHex(VSBuffer.wrap(new Uint8Array(bufferOrValue)));
74
}
75
76
return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0');
77
}
78
79
function rightRotate(value: number, bits: number): number {
80
return ((value >>> bits) | (value << (32 - bits))) >>> 0;
81
}
82
83
/**
84
* A simple, synchronous implementation of SHA-256 for strings.
85
* Only to be used in non-security-critical paths where synchronous operation is required.
86
*/
87
class StringSHA256Insecure {
88
private static _k = [
89
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
90
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
91
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
92
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
93
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
94
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
95
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
96
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
97
];
98
99
private static _bigBlock32 = new DataView(new ArrayBuffer(256)); // 64 * 4 = 256
100
101
private _h0 = 0x6a09e667;
102
private _h1 = 0xbb67ae85;
103
private _h2 = 0x3c6ef372;
104
private _h3 = 0xa54ff53a;
105
private _h4 = 0x510e527f;
106
private _h5 = 0x9b05688c;
107
private _h6 = 0x1f83d9ab;
108
private _h7 = 0x5be0cd19;
109
110
private readonly _buff: Uint8Array;
111
private readonly _buffDV: DataView;
112
private _buffLen: number;
113
private _totalLen: number;
114
private _leftoverHighSurrogate: number;
115
private _finished: boolean;
116
117
constructor() {
118
this._buff = new Uint8Array(SHA256Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */);
119
this._buffDV = new DataView(this._buff.buffer);
120
this._buffLen = 0;
121
this._totalLen = 0;
122
this._leftoverHighSurrogate = 0;
123
this._finished = false;
124
}
125
126
public update(str: string): void {
127
const strLen = str.length;
128
if (strLen === 0) {
129
return;
130
}
131
132
const buff = this._buff;
133
let buffLen = this._buffLen;
134
let leftoverHighSurrogate = this._leftoverHighSurrogate;
135
let charCode: number;
136
let offset: number;
137
138
if (leftoverHighSurrogate !== 0) {
139
charCode = leftoverHighSurrogate;
140
offset = -1;
141
leftoverHighSurrogate = 0;
142
} else {
143
charCode = str.charCodeAt(0);
144
offset = 0;
145
}
146
147
while (true) {
148
let codePoint = charCode;
149
if (strings.isHighSurrogate(charCode)) {
150
if (offset + 1 < strLen) {
151
const nextCharCode = str.charCodeAt(offset + 1);
152
if (strings.isLowSurrogate(nextCharCode)) {
153
offset++;
154
codePoint = strings.computeCodePoint(charCode, nextCharCode);
155
} else {
156
// illegal => unicode replacement character
157
codePoint = SHA256Constant.UNICODE_REPLACEMENT;
158
}
159
} else {
160
// last character is a surrogate pair
161
leftoverHighSurrogate = charCode;
162
break;
163
}
164
} else if (strings.isLowSurrogate(charCode)) {
165
// illegal => unicode replacement character
166
codePoint = SHA256Constant.UNICODE_REPLACEMENT;
167
}
168
169
buffLen = this._push(buff, buffLen, codePoint);
170
offset++;
171
if (offset < strLen) {
172
charCode = str.charCodeAt(offset);
173
} else {
174
break;
175
}
176
}
177
178
this._buffLen = buffLen;
179
this._leftoverHighSurrogate = leftoverHighSurrogate;
180
}
181
182
private _push(buff: Uint8Array, buffLen: number, codePoint: number): number {
183
if (codePoint < 0x0080) {
184
buff[buffLen++] = codePoint;
185
} else if (codePoint < 0x0800) {
186
buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
187
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
188
} else if (codePoint < 0x10000) {
189
buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
190
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
191
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
192
} else {
193
buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
194
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
195
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
196
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
197
}
198
199
if (buffLen >= SHA256Constant.BLOCK_SIZE) {
200
this._step();
201
buffLen -= SHA256Constant.BLOCK_SIZE;
202
this._totalLen += SHA256Constant.BLOCK_SIZE;
203
// take last 3 in case of UTF8 overflow
204
buff[0] = buff[SHA256Constant.BLOCK_SIZE + 0];
205
buff[1] = buff[SHA256Constant.BLOCK_SIZE + 1];
206
buff[2] = buff[SHA256Constant.BLOCK_SIZE + 2];
207
}
208
209
return buffLen;
210
}
211
212
public digest(): string {
213
if (!this._finished) {
214
this._finished = true;
215
if (this._leftoverHighSurrogate) {
216
// illegal => unicode replacement character
217
this._leftoverHighSurrogate = 0;
218
this._buffLen = this._push(this._buff, this._buffLen, SHA256Constant.UNICODE_REPLACEMENT);
219
}
220
this._totalLen += this._buffLen;
221
this._wrapUp();
222
}
223
224
return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4) + toHexString(this._h5) + toHexString(this._h6) + toHexString(this._h7);
225
}
226
227
private _wrapUp(): void {
228
this._buff[this._buffLen++] = 0x80;
229
this._buff.subarray(this._buffLen).fill(0);
230
231
if (this._buffLen > 56) {
232
this._step();
233
this._buff.fill(0);
234
}
235
236
// this will fit because the mantissa can cover up to 52 bits
237
const ml = 8 * this._totalLen;
238
239
this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false);
240
this._buffDV.setUint32(60, ml % 4294967296, false);
241
242
this._step();
243
}
244
245
private _step(): void {
246
const bigBlock32 = StringSHA256Insecure._bigBlock32;
247
const data = this._buffDV;
248
const k = StringSHA256Insecure._k;
249
250
// Copy chunk into first 16 words of message schedule
251
for (let j = 0; j < 64 /* 16*4 */; j += 4) {
252
bigBlock32.setUint32(j, data.getUint32(j, false), false);
253
}
254
255
// Extend the first 16 words into the remaining 48 words of the message schedule
256
for (let j = 16; j < 64; j++) {
257
const offset = j * 4;
258
const w15 = bigBlock32.getUint32((j - 15) * 4, false);
259
const w2 = bigBlock32.getUint32((j - 2) * 4, false);
260
const s0 = rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3);
261
const s1 = rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10);
262
const w16 = bigBlock32.getUint32((j - 16) * 4, false);
263
const w7 = bigBlock32.getUint32((j - 7) * 4, false);
264
bigBlock32.setUint32(offset, (w16 + s0 + w7 + s1) >>> 0, false);
265
}
266
267
// Initialize working variables
268
let a = this._h0;
269
let b = this._h1;
270
let c = this._h2;
271
let d = this._h3;
272
let e = this._h4;
273
let f = this._h5;
274
let g = this._h6;
275
let h = this._h7;
276
277
// Compression function main loop
278
for (let j = 0; j < 64; j++) {
279
const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
280
const ch = (e & f) ^ ((~e) & g);
281
const temp1 = (h + S1 + ch + k[j] + bigBlock32.getUint32(j * 4, false)) >>> 0;
282
const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
283
const maj = (a & b) ^ (a & c) ^ (b & c);
284
const temp2 = (S0 + maj) >>> 0;
285
286
h = g;
287
g = f;
288
f = e;
289
e = (d + temp1) >>> 0;
290
d = c;
291
c = b;
292
b = a;
293
a = (temp1 + temp2) >>> 0;
294
}
295
296
// Add the compressed chunk to the current hash value
297
this._h0 = (this._h0 + a) >>> 0;
298
this._h1 = (this._h1 + b) >>> 0;
299
this._h2 = (this._h2 + c) >>> 0;
300
this._h3 = (this._h3 + d) >>> 0;
301
this._h4 = (this._h4 + e) >>> 0;
302
this._h5 = (this._h5 + f) >>> 0;
303
this._h6 = (this._h6 + g) >>> 0;
304
this._h7 = (this._h7 + h) >>> 0;
305
}
306
}
307
308