mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-24 16:49:26 +01:00
generate keypair on registration
This commit is contained in:
parent
269b59210c
commit
3454d93fef
@ -170,6 +170,15 @@ describe('NodeCrypto Function Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('rsaGenerateKeyPair', () => {
|
||||
testRsaGenerateKeyPair(1024);
|
||||
testRsaGenerateKeyPair(2048);
|
||||
|
||||
// Generating 4096 bit keys is really slow with Forge lib.
|
||||
// Maybe move to something else if we ever want to generate keys of this size.
|
||||
// testRsaGenerateKeyPair(4096);
|
||||
});
|
||||
|
||||
describe('randomBytes', () => {
|
||||
it('should make a value of the correct length', async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
@ -302,6 +311,16 @@ function testCompare(fast = false) {
|
||||
});
|
||||
}
|
||||
|
||||
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
it('should successfully generate a ' + length + ' bit key pair', async () => {
|
||||
const cryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
|
||||
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
|
||||
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
function makeStaticByteArray(length: number) {
|
||||
const arr = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
@ -256,6 +256,12 @@ describe('WebCrypto Function Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('rsaGenerateKeyPair', () => {
|
||||
testRsaGenerateKeyPair(1024);
|
||||
testRsaGenerateKeyPair(2048);
|
||||
testRsaGenerateKeyPair(4096);
|
||||
});
|
||||
|
||||
describe('randomBytes', () => {
|
||||
it('should make a value of the correct length', async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
@ -357,6 +363,16 @@ function testHmacFast(algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
it('should successfully generate a ' + length + ' bit key pair', async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
|
||||
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
|
||||
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
|
||||
});
|
||||
}
|
||||
|
||||
function getWebCryptoFunctionService() {
|
||||
const platformUtilsMock = TypeMoq.Mock.ofType<PlatformUtilsService>(PlatformUtilsServiceMock);
|
||||
platformUtilsMock.setup((x) => x.isEdge()).returns(() => navigator.userAgent.indexOf(' Edge/') !== -1);
|
||||
|
@ -20,14 +20,15 @@ export abstract class CryptoService {
|
||||
clearKey: () => Promise<any>;
|
||||
clearKeyHash: () => Promise<any>;
|
||||
clearEncKey: (memoryOnly?: boolean) => Promise<any>;
|
||||
clearPrivateKey: (memoryOnly?: boolean) => Promise<any>;
|
||||
clearKeyPair: (memoryOnly?: boolean) => Promise<any>;
|
||||
clearOrgKeys: (memoryOnly?: boolean) => Promise<any>;
|
||||
clearKeys: () => Promise<any>;
|
||||
toggleKey: () => Promise<any>;
|
||||
makeKey: (password: string, salt: string) => Promise<SymmetricCryptoKey>;
|
||||
makeShareKey: () => Promise<[CipherString, SymmetricCryptoKey]>;
|
||||
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, CipherString]>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey) => Promise<string>;
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<CipherString>;
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
|
||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<CipherString>;
|
||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
decryptToUtf8: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<string>;
|
||||
|
@ -18,5 +18,6 @@ export abstract class CryptoFunctionService {
|
||||
rsaEncrypt: (data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
||||
rsaDecrypt: (data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
||||
rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>;
|
||||
randomBytes: (length: number) => Promise<ArrayBuffer>;
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ import { Router } from '@angular/router';
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { RegisterRequest } from '../../models/request/registerRequest';
|
||||
import {
|
||||
RegisterKeysRequest,
|
||||
RegisterRequest,
|
||||
} from '../../models/request/registerRequest';
|
||||
|
||||
import { ApiService } from '../../abstractions/api.service';
|
||||
import { AuthService } from '../../abstractions/auth.service';
|
||||
@ -11,6 +14,7 @@ import { CryptoService } from '../../abstractions/crypto.service';
|
||||
import { I18nService } from '../../abstractions/i18n.service';
|
||||
|
||||
export class RegisterComponent {
|
||||
name: string = '';
|
||||
email: string = '';
|
||||
masterPassword: string = '';
|
||||
confirmMasterPassword: string = '';
|
||||
@ -52,8 +56,18 @@ export class RegisterComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.name = this.name === '' ? null : this.name;
|
||||
this.email = this.email.toLowerCase();
|
||||
const key = await this.cryptoService.makeKey(this.masterPassword, this.email);
|
||||
const encKey = await this.cryptoService.makeEncKey(key);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
|
||||
const request = new RegisterRequest(this.email, this.name, hashedPassword,
|
||||
this.hint, encKey[1].encryptedString);
|
||||
request.keys = new RegisterKeysRequest(keys[0], keys[1].encryptedString);
|
||||
|
||||
try {
|
||||
this.formPromise = this.register();
|
||||
this.formPromise = this.apiService.postRegister(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Registered' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('newAccountCreated'));
|
||||
@ -66,13 +80,4 @@ export class RegisterComponent {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus();
|
||||
}
|
||||
|
||||
private async register() {
|
||||
this.email = this.email.toLowerCase();
|
||||
const key = await this.cryptoService.makeKey(this.masterPassword, this.email);
|
||||
const encKey = await this.cryptoService.makeEncKey(key);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const request = new RegisterRequest(this.email, hashedPassword, this.hint, encKey.encryptedString);
|
||||
await this.apiService.postRegister(request);
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,25 @@ export class RegisterRequest {
|
||||
masterPasswordHash: string;
|
||||
masterPasswordHint: string;
|
||||
key: string;
|
||||
keys: RegisterKeysRequest;
|
||||
token: string;
|
||||
organizationUserId: string;
|
||||
|
||||
constructor(email: string, masterPasswordHash: string, masterPasswordHint: string, key: string) {
|
||||
this.name = null;
|
||||
constructor(email: string, name: string, masterPasswordHash: string, masterPasswordHint: string, key: string) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.masterPasswordHash = masterPasswordHash;
|
||||
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
export class RegisterKeysRequest {
|
||||
publicKey: string;
|
||||
encryptedPrivateKey: string;
|
||||
|
||||
constructor(publicKey: string, encryptedPrivateKey: string) {
|
||||
this.publicKey = publicKey;
|
||||
this.encryptedPrivateKey = encryptedPrivateKey;
|
||||
}
|
||||
}
|
||||
|
@ -229,8 +229,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.storageService.remove(Keys.encKey);
|
||||
}
|
||||
|
||||
clearPrivateKey(memoryOnly?: boolean): Promise<any> {
|
||||
clearKeyPair(memoryOnly?: boolean): Promise<any> {
|
||||
this.privateKey = null;
|
||||
this.publicKey = null;
|
||||
if (memoryOnly) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -251,7 +252,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
this.clearKeyHash(),
|
||||
this.clearOrgKeys(),
|
||||
this.clearEncKey(),
|
||||
this.clearPrivateKey(),
|
||||
this.clearKeyPair(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -281,6 +282,13 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return [encShareKey, new SymmetricCryptoKey(shareKey)];
|
||||
}
|
||||
|
||||
async makeKeyPair(key?: SymmetricCryptoKey): Promise<[string, CipherString]> {
|
||||
const keyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
||||
const publicB64 = Utils.fromBufferToB64(keyPair[0]);
|
||||
const privateEnc = await this.encrypt(keyPair[1], key);
|
||||
return [publicB64, privateEnc];
|
||||
}
|
||||
|
||||
async hashPassword(password: string, key: SymmetricCryptoKey): Promise<string> {
|
||||
if (key == null) {
|
||||
key = await this.getKey();
|
||||
@ -293,20 +301,22 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return Utils.fromBufferToB64(hash);
|
||||
}
|
||||
|
||||
async makeEncKey(key: SymmetricCryptoKey): Promise<CipherString> {
|
||||
async makeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> {
|
||||
const encKey = await this.cryptoFunctionService.randomBytes(64);
|
||||
let encKeyEnc: CipherString = null;
|
||||
// TODO: Uncomment when we're ready to enable key stretching
|
||||
return this.encrypt(encKey, key);
|
||||
encKeyEnc = await this.encrypt(encKey, key);
|
||||
/*
|
||||
if (key.key.byteLength === 32) {
|
||||
const newKey = await this.stretchKey(key);
|
||||
return this.encrypt(encKey, newKey);
|
||||
encKeyEnc = await this.encrypt(encKey, newKey);
|
||||
} else if (key.key.byteLength === 64) {
|
||||
return this.encrypt(encKey, key);
|
||||
encKeyEnc = await this.encrypt(encKey, key);
|
||||
} else {
|
||||
throw new Error('Invalid key size.');
|
||||
}
|
||||
*/
|
||||
return [new SymmetricCryptoKey(encKey), encKeyEnc];
|
||||
}
|
||||
|
||||
async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise<CipherString> {
|
||||
|
@ -67,7 +67,7 @@ export class LockService implements LockServiceAbstraction {
|
||||
await Promise.all([
|
||||
this.cryptoService.clearKey(),
|
||||
this.cryptoService.clearOrgKeys(true),
|
||||
this.cryptoService.clearPrivateKey(true),
|
||||
this.cryptoService.clearKeyPair(true),
|
||||
this.cryptoService.clearEncKey(true),
|
||||
]);
|
||||
|
||||
|
@ -144,6 +144,32 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
return Promise.resolve(publicKeyArray.buffer);
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
||||
return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => {
|
||||
forge.pki.rsa.generateKeyPair({
|
||||
bits: length,
|
||||
workers: -1,
|
||||
e: 0x10001, // 65537
|
||||
}, (error, keyPair) => {
|
||||
if (error != null) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(keyPair.publicKey);
|
||||
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes();
|
||||
const publicKey = Utils.fromByteStringToArray(publicKeyByteString);
|
||||
|
||||
const privateKeyAsn1 = (forge.pki as any).privateKeyToAsn1(keyPair.privateKey);
|
||||
const privateKeyPkcs8 = (forge.pki as any).wrapRsaPrivateKey(privateKeyAsn1);
|
||||
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
|
||||
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
|
||||
|
||||
resolve([publicKey.buffer, privateKey.buffer]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
randomBytes(length: number): Promise<ArrayBuffer> {
|
||||
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
crypto.randomBytes(length, (error, bytes) => {
|
||||
|
@ -223,6 +223,20 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return await this.subtle.exportKey('spki', impPublicKey);
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
||||
const rsaParams = {
|
||||
name: 'RSA-OAEP',
|
||||
modulusLength: length,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
|
||||
// Have to specify some algorithm
|
||||
hash: { name: this.toWebCryptoAlgorithm('sha1') },
|
||||
};
|
||||
const keyPair = await this.subtle.generateKey(rsaParams, true, ['encrypt', 'decrypt']);
|
||||
const publicKey = await this.subtle.exportKey('spki', keyPair.publicKey);
|
||||
const privateKey = await this.subtle.exportKey('pkcs8', keyPair.privateKey);
|
||||
return [publicKey, privateKey];
|
||||
}
|
||||
|
||||
randomBytes(length: number): Promise<ArrayBuffer> {
|
||||
const arr = new Uint8Array(length);
|
||||
this.crypto.getRandomValues(arr);
|
||||
|
Loading…
Reference in New Issue
Block a user