generate keypair on registration

This commit is contained in:
Kyle Spearrin 2018-07-03 11:41:55 -04:00
parent 269b59210c
commit 3454d93fef
10 changed files with 127 additions and 22 deletions

View File

@ -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++) {

View File

@ -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);

View File

@ -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>;

View File

@ -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>;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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> {

View File

@ -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),
]);

View File

@ -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) => {

View File

@ -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);