mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +01:00
[PM-3732] Use subtle to make aes keys (#6162)
* Provide `aesGenerateKey` to make aes keys * Use aesGenerateKey when generating a key data * Fix device test
This commit is contained in:
parent
615248e04f
commit
0448910806
@ -53,7 +53,7 @@ export class AccessService {
|
|||||||
serviceAccountId: string,
|
serviceAccountId: string,
|
||||||
accessTokenView: AccessTokenView
|
accessTokenView: AccessTokenView
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const keyMaterial = await this.cryptoFunctionService.randomBytes(16);
|
const keyMaterial = await this.cryptoFunctionService.aesGenerateKey(128);
|
||||||
const key = await this.cryptoFunctionService.hkdf(
|
const key = await this.cryptoFunctionService.hkdf(
|
||||||
keyMaterial,
|
keyMaterial,
|
||||||
"bitwarden-accesstoken",
|
"bitwarden-accesstoken",
|
||||||
|
@ -167,7 +167,7 @@ export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstrac
|
|||||||
|
|
||||||
private async makeDeviceKey(): Promise<DeviceKey> {
|
private async makeDeviceKey(): Promise<DeviceKey> {
|
||||||
// Create 512-bit device key
|
// Create 512-bit device key
|
||||||
const randomBytes: CsprngArray = await this.cryptoFunctionService.randomBytes(64);
|
const randomBytes: CsprngArray = await this.cryptoFunctionService.aesGenerateKey(512);
|
||||||
const deviceKey = new SymmetricCryptoKey(randomBytes) as DeviceKey;
|
const deviceKey = new SymmetricCryptoKey(randomBytes) as DeviceKey;
|
||||||
|
|
||||||
return deviceKey;
|
return deviceKey;
|
||||||
|
@ -168,16 +168,16 @@ describe("deviceTrustCryptoService", () => {
|
|||||||
it("creates a new non-null 64 byte device key, securely stores it, and returns it", async () => {
|
it("creates a new non-null 64 byte device key, securely stores it, and returns it", async () => {
|
||||||
const mockRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
|
const mockRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
|
||||||
|
|
||||||
const cryptoFuncSvcRandomBytesSpy = jest
|
const cryptoFuncSvcGenerateKeySpy = jest
|
||||||
.spyOn(cryptoFunctionService, "randomBytes")
|
.spyOn(cryptoFunctionService, "aesGenerateKey")
|
||||||
.mockResolvedValue(mockRandomBytes);
|
.mockResolvedValue(mockRandomBytes);
|
||||||
|
|
||||||
// TypeScript will allow calling private methods if the object is of type 'any'
|
// TypeScript will allow calling private methods if the object is of type 'any'
|
||||||
// This is a hacky workaround, but it allows for cleaner tests
|
// This is a hacky workaround, but it allows for cleaner tests
|
||||||
const deviceKey = await (deviceTrustCryptoService as any).makeDeviceKey();
|
const deviceKey = await (deviceTrustCryptoService as any).makeDeviceKey();
|
||||||
|
|
||||||
expect(cryptoFuncSvcRandomBytesSpy).toHaveBeenCalledTimes(1);
|
expect(cryptoFuncSvcGenerateKeySpy).toHaveBeenCalledTimes(1);
|
||||||
expect(cryptoFuncSvcRandomBytesSpy).toHaveBeenCalledWith(deviceKeyBytesLength);
|
expect(cryptoFuncSvcGenerateKeySpy).toHaveBeenCalledWith(deviceKeyBytesLength * 8);
|
||||||
|
|
||||||
expect(deviceKey).not.toBeNull();
|
expect(deviceKey).not.toBeNull();
|
||||||
expect(deviceKey).toBeInstanceOf(SymmetricCryptoKey);
|
expect(deviceKey).toBeInstanceOf(SymmetricCryptoKey);
|
||||||
|
@ -93,7 +93,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
|||||||
keyConnectorUrl: legacyKeyConnectorUrl,
|
keyConnectorUrl: legacyKeyConnectorUrl,
|
||||||
userDecryptionOptions,
|
userDecryptionOptions,
|
||||||
} = tokenResponse;
|
} = tokenResponse;
|
||||||
const password = await this.cryptoFunctionService.randomBytes(64);
|
const password = await this.cryptoFunctionService.aesGenerateKey(512);
|
||||||
const kdfConfig = new KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
const kdfConfig = new KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
||||||
|
|
||||||
const masterKey = await this.cryptoService.makeMasterKey(
|
const masterKey = await this.cryptoService.makeMasterKey(
|
||||||
|
@ -66,5 +66,14 @@ export abstract class CryptoFunctionService {
|
|||||||
) => Promise<Uint8Array>;
|
) => Promise<Uint8Array>;
|
||||||
rsaExtractPublicKey: (privateKey: Uint8Array) => Promise<Uint8Array>;
|
rsaExtractPublicKey: (privateKey: Uint8Array) => Promise<Uint8Array>;
|
||||||
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[Uint8Array, Uint8Array]>;
|
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[Uint8Array, Uint8Array]>;
|
||||||
|
/**
|
||||||
|
* Generates a key of the given length suitable for use in AES encryption
|
||||||
|
*/
|
||||||
|
aesGenerateKey: (bitLength: 128 | 192 | 256 | 512) => Promise<CsprngArray>;
|
||||||
|
/**
|
||||||
|
* Generates a random array of bytes of the given length. Uses a cryptographically secure random number generator.
|
||||||
|
*
|
||||||
|
* Do not use this for generating encryption keys. Use aesGenerateKey or rsaGenerateKeyPair instead.
|
||||||
|
*/
|
||||||
randomBytes: (length: number) => Promise<CsprngArray>;
|
randomBytes: (length: number) => Promise<CsprngArray>;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
throw new Error("No Master Key found.");
|
throw new Error("No Master Key found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUserKey = await this.cryptoFunctionService.randomBytes(64);
|
const newUserKey = await this.cryptoFunctionService.aesGenerateKey(512);
|
||||||
return this.buildProtectedSymmetricKey(masterKey, newUserKey);
|
return this.buildProtectedSymmetricKey(masterKey, newUserKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +367,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
throw new Error("No key provided");
|
throw new Error("No key provided");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSymKey = await this.cryptoFunctionService.randomBytes(64);
|
const newSymKey = await this.cryptoFunctionService.aesGenerateKey(512);
|
||||||
return this.buildProtectedSymmetricKey(key, newSymKey);
|
return this.buildProtectedSymmetricKey(key, newSymKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +458,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async makeOrgKey<T extends OrgKey | ProviderKey>(): Promise<[EncString, T]> {
|
async makeOrgKey<T extends OrgKey | ProviderKey>(): Promise<[EncString, T]> {
|
||||||
const shareKey = await this.cryptoFunctionService.randomBytes(64);
|
const shareKey = await this.cryptoFunctionService.aesGenerateKey(512);
|
||||||
const publicKey = await this.getPublicKey();
|
const publicKey = await this.getPublicKey();
|
||||||
const encShareKey = await this.rsaEncrypt(shareKey, publicKey);
|
const encShareKey = await this.rsaEncrypt(shareKey, publicKey);
|
||||||
return [encShareKey, new SymmetricCryptoKey(shareKey) as T];
|
return [encShareKey, new SymmetricCryptoKey(shareKey) as T];
|
||||||
@ -731,8 +731,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
publicKey: string;
|
publicKey: string;
|
||||||
privateKey: EncString;
|
privateKey: EncString;
|
||||||
}> {
|
}> {
|
||||||
const randomBytes = await this.cryptoFunctionService.randomBytes(64);
|
const rawKey = await this.cryptoFunctionService.aesGenerateKey(512);
|
||||||
const userKey = new SymmetricCryptoKey(randomBytes) as UserKey;
|
const userKey = new SymmetricCryptoKey(rawKey) as UserKey;
|
||||||
const [publicKey, privateKey] = await this.makeKeyPair(userKey);
|
const [publicKey, privateKey] = await this.makeKeyPair(userKey);
|
||||||
await this.setUserKey(userKey);
|
await this.setUserKey(userKey);
|
||||||
await this.stateService.setEncryptedPrivateKey(privateKey.encryptedString);
|
await this.stateService.setEncryptedPrivateKey(privateKey.encryptedString);
|
||||||
|
@ -354,6 +354,20 @@ describe("WebCrypto Function Service", () => {
|
|||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("aesGenerateKey", () => {
|
||||||
|
it.each([128, 192, 256, 512])("Should make a key of %s bits long", async (length) => {
|
||||||
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
|
const key = await cryptoFunctionService.aesGenerateKey(length);
|
||||||
|
expect(key.byteLength * 8).toBe(length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not repeat itself for 512 length special case", async () => {
|
||||||
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
|
const key = await cryptoFunctionService.aesGenerateKey(512);
|
||||||
|
expect(key.slice(0, 32)).not.toEqual(key.slice(32, 64));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function testPbkdf2(
|
function testPbkdf2(
|
||||||
|
@ -347,6 +347,23 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
|||||||
return new Uint8Array(buffer);
|
return new Uint8Array(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async aesGenerateKey(bitLength = 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
||||||
|
if (bitLength === 512) {
|
||||||
|
// 512 bit keys are not supported in WebCrypto, so we concat two 256 bit keys
|
||||||
|
const key1 = await this.aesGenerateKey(256);
|
||||||
|
const key2 = await this.aesGenerateKey(256);
|
||||||
|
return new Uint8Array([...key1, ...key2]) as CsprngArray;
|
||||||
|
}
|
||||||
|
const aesParams = {
|
||||||
|
name: "AES-CBC",
|
||||||
|
length: bitLength,
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = await this.subtle.generateKey(aesParams, true, ["encrypt", "decrypt"]);
|
||||||
|
const rawKey = await this.subtle.exportKey("raw", key);
|
||||||
|
return new Uint8Array(rawKey) as CsprngArray;
|
||||||
|
}
|
||||||
|
|
||||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]> {
|
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]> {
|
||||||
const rsaParams = {
|
const rsaParams = {
|
||||||
name: "RSA-OAEP",
|
name: "RSA-OAEP",
|
||||||
@ -355,10 +372,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
|||||||
// Have to specify some algorithm
|
// Have to specify some algorithm
|
||||||
hash: { name: this.toWebCryptoAlgorithm("sha1") },
|
hash: { name: this.toWebCryptoAlgorithm("sha1") },
|
||||||
};
|
};
|
||||||
const keyPair = (await this.subtle.generateKey(rsaParams, true, [
|
const keyPair = await this.subtle.generateKey(rsaParams, true, ["encrypt", "decrypt"]);
|
||||||
"encrypt",
|
|
||||||
"decrypt",
|
|
||||||
])) as CryptoKeyPair;
|
|
||||||
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
|
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
|
||||||
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
|
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
|
||||||
return [new Uint8Array(publicKey), new Uint8Array(privateKey)];
|
return [new Uint8Array(publicKey), new Uint8Array(privateKey)];
|
||||||
|
@ -70,7 +70,7 @@ export class SendService implements InternalSendServiceAbstraction {
|
|||||||
send.hideEmail = model.hideEmail;
|
send.hideEmail = model.hideEmail;
|
||||||
send.maxAccessCount = model.maxAccessCount;
|
send.maxAccessCount = model.maxAccessCount;
|
||||||
if (model.key == null) {
|
if (model.key == null) {
|
||||||
model.key = await this.cryptoFunctionService.randomBytes(16);
|
model.key = await this.cryptoFunctionService.aesGenerateKey(128);
|
||||||
model.cryptoKey = await this.cryptoService.makeSendKey(model.key);
|
model.cryptoKey = await this.cryptoService.makeSendKey(model.key);
|
||||||
}
|
}
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
|
@ -271,6 +271,15 @@ describe("NodeCrypto Function Service", () => {
|
|||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("aesGenerateKey", () => {
|
||||||
|
it("should delegate to randomBytes", async () => {
|
||||||
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
|
const spy = jest.spyOn(nodeCryptoFunctionService, "randomBytes");
|
||||||
|
await nodeCryptoFunctionService.aesGenerateKey(256);
|
||||||
|
expect(spy).toHaveBeenCalledWith(32);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function testPbkdf2(
|
function testPbkdf2(
|
||||||
|
@ -271,6 +271,10 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
||||||
|
return this.randomBytes(bitLength / 8);
|
||||||
|
}
|
||||||
|
|
||||||
randomBytes(length: number): Promise<CsprngArray> {
|
randomBytes(length: number): Promise<CsprngArray> {
|
||||||
return new Promise<CsprngArray>((resolve, reject) => {
|
return new Promise<CsprngArray>((resolve, reject) => {
|
||||||
crypto.randomBytes(length, (error, bytes) => {
|
crypto.randomBytes(length, (error, bytes) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user