1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

determine length based on alg. fix 512 wc pbkdf2

This commit is contained in:
Kyle Spearrin
2018-04-17 21:18:47 -04:00
parent 81f7bd7b76
commit 3ca8716fc6
5 changed files with 35 additions and 39 deletions

View File

@@ -1,6 +1,6 @@
export abstract class CryptoFunctionService { export abstract class CryptoFunctionService {
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
iterations: number, length: number) => Promise<ArrayBuffer>; iterations: number) => Promise<ArrayBuffer>;
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>; hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>; hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
} }

View File

@@ -1,11 +1,11 @@
import * as Abstractions from './abstractions'; import * as Abstractions from './abstractions';
import * as Enums from './enums'; import * as Enums from './enums';
import * as Misc from './misc';
import * as Data from './models/data'; import * as Data from './models/data';
import * as Domain from './models/domain'; import * as Domain from './models/domain';
import * as Misc from './misc';
import * as Request from './models/request'; import * as Request from './models/request';
import * as Response from './models/response'; import * as Response from './models/response';
import * as Services from './services';
import * as View from './models/view'; import * as View from './models/view';
import * as Services from './services';
export { Abstractions, Enums, Data, Domain, Misc, Request, Response, Services, View }; export { Abstractions, Enums, Data, Domain, Misc, Request, Response, Services, View };

View File

@@ -4,7 +4,8 @@ import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
export class NodeCryptoFunctionService implements CryptoFunctionService { export class NodeCryptoFunctionService implements CryptoFunctionService {
async pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', async pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
iterations: number, length: number): Promise<ArrayBuffer> { iterations: number): Promise<ArrayBuffer> {
const len = algorithm === 'sha256' ? 256 : 512;
const nodePassword = this.toNodeValue(password); const nodePassword = this.toNodeValue(password);
const nodeSalt = this.toNodeValue(salt); const nodeSalt = this.toNodeValue(salt);
return new Promise<ArrayBuffer>((resolve, reject) => { return new Promise<ArrayBuffer>((resolve, reject) => {

View File

@@ -12,15 +12,18 @@ describe('WebCrypto Function Service', () => {
const utf8256Key = 'yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I='; const utf8256Key = 'yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=';
const unicode256Key = 'ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w='; const unicode256Key = 'ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w=';
const regular512Key = 'liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/I='; const regular512Key = 'liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57' +
const utf8512Key = 'df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANs='; 'eyhhx5wfKo5Cg==';
const unicode512Key = 'FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3Xtw='; const utf8512Key = 'df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN' +
'zXANiVZpnw==';
const unicode512Key = 'FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD' +
'L3FiQDTROh1lg==';
testPbkdf2ValidKey(false, 'sha256', regular256Key, utf8256Key, unicode256Key); testPbkdf2(false, 'sha256', regular256Key, utf8256Key, unicode256Key);
testPbkdf2ValidKey(false, 'sha512', regular512Key, utf8512Key, unicode512Key); testPbkdf2(false, 'sha512', regular512Key, utf8512Key, unicode512Key);
testPbkdf2ValidKey(true, 'sha256', regular256Key, utf8256Key, unicode256Key); testPbkdf2(true, 'sha256', regular256Key, utf8256Key, unicode256Key);
testPbkdf2ValidKey(true, 'sha512', regular512Key, utf8512Key, unicode512Key); testPbkdf2(true, 'sha512', regular512Key, utf8512Key, unicode512Key);
}); });
describe('hash', () => { describe('hash', () => {
@@ -64,7 +67,7 @@ describe('WebCrypto Function Service', () => {
}); });
}); });
function testPbkdf2ValidKey(edge: boolean, algorithm: 'sha256' | 'sha512', regularKey: string, function testPbkdf2(edge: boolean, algorithm: 'sha256' | 'sha512', regularKey: string,
utf8Key: string, unicodeKey: string) { utf8Key: string, unicodeKey: string) {
const forEdge = edge ? ' for edge' : ''; const forEdge = edge ? ' for edge' : '';
const regularEmail = 'user@example.com'; const regularEmail = 'user@example.com';
@@ -76,26 +79,26 @@ function testPbkdf2ValidKey(edge: boolean, algorithm: 'sha256' | 'sha512', regul
it('should create valid ' + algorithm + ' key from regular input' + forEdge, async () => { it('should create valid ' + algorithm + ' key from regular input' + forEdge, async () => {
const webCryptoFunctionService = getWebCryptoFunctionService(edge); const webCryptoFunctionService = getWebCryptoFunctionService(edge);
const key = await webCryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000, 256); const key = await webCryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000);
expect(UtilsService.fromBufferToB64(key)).toBe(regularKey); expect(UtilsService.fromBufferToB64(key)).toBe(regularKey);
}); });
it('should create valid ' + algorithm + ' key from utf8 input' + forEdge, async () => { it('should create valid ' + algorithm + ' key from utf8 input' + forEdge, async () => {
const webCryptoFunctionService = getWebCryptoFunctionService(edge); const webCryptoFunctionService = getWebCryptoFunctionService(edge);
const key = await webCryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000, 256); const key = await webCryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000);
expect(UtilsService.fromBufferToB64(key)).toBe(utf8Key); expect(UtilsService.fromBufferToB64(key)).toBe(utf8Key);
}); });
it('should create valid ' + algorithm + ' key from unicode input' + forEdge, async () => { it('should create valid ' + algorithm + ' key from unicode input' + forEdge, async () => {
const webCryptoFunctionService = getWebCryptoFunctionService(edge); const webCryptoFunctionService = getWebCryptoFunctionService(edge);
const key = await webCryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000, 256); const key = await webCryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000);
expect(UtilsService.fromBufferToB64(key)).toBe(unicodeKey); expect(UtilsService.fromBufferToB64(key)).toBe(unicodeKey);
}); });
it('should create valid ' + algorithm + ' key from array buffer input' + forEdge, async () => { it('should create valid ' + algorithm + ' key from array buffer input' + forEdge, async () => {
const webCryptoFunctionService = getWebCryptoFunctionService(edge); const webCryptoFunctionService = getWebCryptoFunctionService(edge);
const key = await webCryptoFunctionService.pbkdf2(UtilsService.fromUtf8ToArray(regularPassword).buffer, const key = await webCryptoFunctionService.pbkdf2(UtilsService.fromUtf8ToArray(regularPassword).buffer,
UtilsService.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000, 256); UtilsService.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000);
expect(UtilsService.fromBufferToB64(key)).toBe(regularKey); expect(UtilsService.fromBufferToB64(key)).toBe(regularKey);
}); });
} }
@@ -147,12 +150,6 @@ function getWebCryptoFunctionService(edge = false) {
} }
class BrowserPlatformUtilsService implements PlatformUtilsService { class BrowserPlatformUtilsService implements PlatformUtilsService {
constructor(private edge: boolean) { }
isEdge() {
return this.edge;
}
identityClientId: string; identityClientId: string;
getDevice: () => DeviceType; getDevice: () => DeviceType;
getDeviceString: () => string; getDeviceString: () => string;
@@ -173,4 +170,10 @@ class BrowserPlatformUtilsService implements PlatformUtilsService {
type?: string) => Promise<boolean>; type?: string) => Promise<boolean>;
isDev: () => boolean; isDev: () => boolean;
copyToClipboard: (text: string, options?: any) => void; copyToClipboard: (text: string, options?: any) => void;
constructor(private edge: boolean) { }
isEdge() {
return this.edge;
}
} }

View File

@@ -17,20 +17,19 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
} }
async pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', async pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
iterations: number, length: number): Promise<ArrayBuffer> { iterations: number): Promise<ArrayBuffer> {
if (this.isEdge) { if (this.isEdge) {
const len = algorithm === 'sha256' ? 32 : 64;
const passwordBytes = this.toForgeBytes(password); const passwordBytes = this.toForgeBytes(password);
const saltBytes = this.toForgeBytes(salt); const saltBytes = this.toForgeBytes(salt);
const derivedKeyBytes = (forge as any).pbkdf2(passwordBytes, saltBytes, iterations, length / 8, algorithm); const derivedKeyBytes = (forge as any).pbkdf2(passwordBytes, saltBytes, iterations, len, algorithm);
return this.fromForgeBytesToBuf(derivedKeyBytes); return this.fromForgeBytesToBuf(derivedKeyBytes);
} }
const len = algorithm === 'sha256' ? 256 : 512;
const passwordBuf = this.toBuf(password); const passwordBuf = this.toBuf(password);
const saltBuf = this.toBuf(salt); const saltBuf = this.toBuf(salt);
const importedKey = await this.subtle.importKey('raw', passwordBuf, { name: 'PBKDF2' },
false, ['deriveKey', 'deriveBits']);
const alg: Pbkdf2Params = { const alg: Pbkdf2Params = {
name: 'PBKDF2', name: 'PBKDF2',
salt: saltBuf, salt: saltBuf,
@@ -38,13 +37,8 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
hash: { name: this.toWebCryptoAlgorithm(algorithm) }, hash: { name: this.toWebCryptoAlgorithm(algorithm) },
}; };
const keyType: AesDerivedKeyParams = { const impKey = await this.subtle.importKey('raw', passwordBuf, { name: 'PBKDF2' }, false, ['deriveBits']);
name: 'AES-CBC', return await window.crypto.subtle.deriveBits(alg, impKey, len);
length: length,
};
const derivedKey = await this.subtle.deriveKey(alg, importedKey, keyType, true, ['encrypt', 'decrypt']);
return await this.subtle.exportKey('raw', derivedKey);
} }
async hash(value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> { async hash(value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
@@ -64,9 +58,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
} }
const valueBuf = this.toBuf(value); const valueBuf = this.toBuf(value);
return await this.subtle.digest({ return await this.subtle.digest({ name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf);
name: this.toWebCryptoAlgorithm(algorithm)
}, valueBuf);
} }
async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> { async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
@@ -84,8 +76,8 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
hash: { name: this.toWebCryptoAlgorithm(algorithm) }, hash: { name: this.toWebCryptoAlgorithm(algorithm) },
}; };
const importedKey = await this.subtle.importKey('raw', key, signingAlgorithm, false, ['sign']); const impKey = await this.subtle.importKey('raw', key, signingAlgorithm, false, ['sign']);
return await this.subtle.sign(signingAlgorithm, importedKey, value); return await this.subtle.sign(signingAlgorithm, impKey, value);
} }
private toBuf(value: string | ArrayBuffer): ArrayBuffer { private toBuf(value: string | ArrayBuffer): ArrayBuffer {