diff --git a/src/api/Crypto.ts b/src/api/Crypto.ts index 69cb444..685f25c 100644 --- a/src/api/Crypto.ts +++ b/src/api/Crypto.ts @@ -9,6 +9,16 @@ sjcl.random.startCollectors(); export const HMAC_SIZE_BYTES = 32; +export class AsymmetricKeyPair { + publicKey: byte[]; + privateKey: byte[]; + + constructor(publicKey: byte[], privateKey: byte[]) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } +} + export function deriveKey(salt: string, password: string): string { const keySize = 190 * 8; diff --git a/src/api/EteSync.test.ts b/src/api/EteSync.test.ts index b31bf87..3e3106f 100644 --- a/src/api/EteSync.test.ts +++ b/src/api/EteSync.test.ts @@ -129,27 +129,28 @@ it('Journal Entry sync', async () => { }); it('User info sync', async () => { + const cryptoManager = new EteSync.CryptoManager(keyBase64, 'userInfo'); const userInfoManager = new EteSync.UserInfoManager(credentials, testApiBase); // Get when there's nothing await expect(userInfoManager.fetch(USER)).rejects.toBeInstanceOf(EteSync.HTTPError); // Create - let userInfo = new EteSync.UserInfo(); - userInfo.deserialize({pubkey: 'dGVzdAo=', content: 'dGVzdAo=', owner: USER}); + let userInfo = new EteSync.UserInfo(USER); + userInfo.setKeyPair(cryptoManager, new EteSync.AsymmetricKeyPair([0, 1, 2, 3], [4, 5, 6, 6])); await expect(userInfoManager.create(userInfo)).resolves.not.toBeNull(); // Get let userInfo2 = await userInfoManager.fetch(USER); expect(userInfo2).not.toBeNull(); - expect(userInfo.serialize().content).toEqual(userInfo2!.serialize().content); + expect(userInfo.getKeyPair(cryptoManager)).toEqual(userInfo2!.getKeyPair(cryptoManager)); // Update - userInfo.deserialize({pubkey: 'dGVzdDIK', content: 'dGVzdDIK', owner: USER}); + userInfo.setKeyPair(cryptoManager, new EteSync.AsymmetricKeyPair([1, 94, 45], [4, 34, 45, 45])); await userInfoManager.update(userInfo); userInfo2 = await userInfoManager.fetch(USER); expect(userInfo2).not.toBeNull(); - expect(userInfo.serialize().content).toEqual(userInfo2!.serialize().content); + expect(userInfo.getKeyPair(cryptoManager)).toEqual(userInfo2!.getKeyPair(cryptoManager)); // Delete await userInfoManager.delete(userInfo); diff --git a/src/api/EteSync.ts b/src/api/EteSync.ts index 3bc08c3..cc6530b 100644 --- a/src/api/EteSync.ts +++ b/src/api/EteSync.ts @@ -6,8 +6,8 @@ import * as Constants from './Constants'; import * as fetch from 'isomorphic-fetch'; import { byte, base64, stringToByteArray } from './Helpers'; -import { CryptoManager, HMAC_SIZE_BYTES } from './Crypto'; -export { CryptoManager, deriveKey } from './Crypto'; +import { CryptoManager, AsymmetricKeyPair, HMAC_SIZE_BYTES } from './Crypto'; +export { CryptoManager, AsymmetricKeyPair, deriveKey } from './Crypto'; class ExtendableError extends Error { constructor(message: any) { @@ -226,22 +226,53 @@ export class Entry extends BaseJournal { export interface UserInfoJson extends BaseItemJson { version?: number; - owner: string; + owner?: string; pubkey: base64; } export class UserInfo extends BaseItem { - constructor(version: number = Constants.CURRENT_VERSION) { + _owner: string; + + constructor(owner: string, version: number = Constants.CURRENT_VERSION) { super(); this._json.version = version; + this._owner = owner; } get version(): number { return this._json.version!; } - get owner() { - return this._json.owner; + get owner(): string { + return this._owner; + } + + get publicKey() { + return this._json.pubkey; + } + + serialize(): UserInfoJson { + let ret = super.serialize(); + ret.owner = this._owner; + return ret; + } + + setKeyPair(cryptoManager: CryptoManager, keyPair: AsymmetricKeyPair) { + this._json.pubkey = sjcl.codec.base64.fromBits(sjcl.codec.bytes.toBits(keyPair.publicKey)); + this._content = keyPair.privateKey; + const encrypted = cryptoManager.encryptBytes(keyPair.privateKey); + this._encrypted = this.calculateHmac(cryptoManager, encrypted).concat(encrypted); + } + + getKeyPair(cryptoManager: CryptoManager): AsymmetricKeyPair { + this.verify(cryptoManager); + + if (this._content === undefined) { + this._content = cryptoManager.decryptBytes(this.encryptedContent()); + } + + const pubkey = sjcl.codec.bytes.fromBits(sjcl.codec.base64.toBits(this._json.pubkey)); + return new AsymmetricKeyPair(pubkey, this._content as byte[]); } calculateHmac(cryptoManager: CryptoManager, encrypted: byte[]): byte[] { @@ -472,7 +503,7 @@ export class UserInfoManager extends BaseManager { fetch(owner: string): Promise { return new Promise((resolve, reject) => { this.newCall([owner, '']).then((json: UserInfoJson) => { - let userInfo = new UserInfo(json.version); + let userInfo = new UserInfo(owner, json.version); userInfo.deserialize(json); resolve(userInfo); }).catch((error: Error) => {