Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
Comply to new data privacy rules as described by #93. (#95)
Browse files Browse the repository at this point in the history
Additional changes beyond those described in #93 had to be implemented.
  • Loading branch information
fnando authored Aug 20, 2019
1 parent 4a66717 commit 0a4e603
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 291 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
"@types/jest": "^24.0.11",
"@types/stellar-base": "^0.10.2",
"bignumber.js": "^8.1.1",
"change-case": "^3.1.0",
"js-sha1": "^0.6.0",
"lodash": "^4.17.14",
"query-string": "^6.4.2",
"scrypt-async": "^2.0.1",
Expand Down
20 changes: 5 additions & 15 deletions src/KeyManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import sinon from "sinon";
import StellarBase from "stellar-base";

import { KeyType } from "./constants/keys";

import { KeyManager } from "./KeyManager";

import { IdentityEncrypter } from "./plugins/IdentityEncrypter";
import { MemoryKeyStore } from "./plugins/MemoryKeyStore";

Expand Down Expand Up @@ -39,24 +37,16 @@ describe("KeyManager", function() {
});

expect(metadata).toEqual({
type: KeyType.plaintextKey,
encrypterName: "IdentityEncrypter",
publicKey: "AVACYN",
creationTime: 666,
modifiedTime: 666,
id: "2d1707a654a4edbf3a64689e4ca493a85afa2a4f",
});

expect(await testKeyManager.loadAllKeyMetadata()).toEqual([
{
type: KeyType.plaintextKey,
encrypterName: "IdentityEncrypter",
publicKey: "AVACYN",
creationTime: 666,
modifiedTime: 666,
id: metadata.id,
},
]);

await testKeyManager.removeKey("AVACYN");
await testKeyManager.removeKey(metadata.id);

expect(await testKeyManager.loadAllKeyMetadata()).toEqual([]);
});
Expand All @@ -75,7 +65,7 @@ describe("KeyManager", function() {
const keypair = StellarBase.Keypair.master();

// save this key
await testKeyManager.storeKey({
const keyMetadata = await testKeyManager.storeKey({
key: {
type: KeyType.plaintextKey,
publicKey: keypair.publicKey(),
Expand All @@ -97,7 +87,7 @@ describe("KeyManager", function() {

const signedTransaction = await testKeyManager.signTransaction({
transaction,
publicKey: keypair.publicKey(),
id: keyMetadata.id,
password: "test",
});

Expand Down
87 changes: 51 additions & 36 deletions src/KeyManager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sha1 from "js-sha1";
import { Network, Networks, Transaction } from "stellar-sdk";

import { ledgerHandler } from "./keyTypeHandlers/ledger";
Expand All @@ -14,6 +15,7 @@ import {
KeyMetadata,
KeyStore,
KeyTypeHandler,
UnstoredKey,
} from "./types";

export interface KeyManagerParams {
Expand All @@ -22,14 +24,14 @@ export interface KeyManagerParams {
}

export interface StoreKeyParams {
key: Key;
key: Key | UnstoredKey;
encrypterName: string;
password?: string;
}

export interface SignTransactionParams {
transaction: Transaction;
publicKey: string;
id: string;
password?: string;
}

Expand Down Expand Up @@ -75,7 +77,7 @@ export class KeyManager {
private encrypterMap: { [key: string]: Encrypter };
private keyStore: KeyStore;
private keyHandlerMap: { [key: string]: KeyTypeHandler };
private keyCache: { [publicKey: string]: Key };
private keyCache: { [id: string]: Key };
private shouldCache: boolean;

constructor(params: KeyManagerParams) {
Expand Down Expand Up @@ -115,12 +117,21 @@ export class KeyManager {
password,
encrypterName,
}: StoreKeyParams): Promise<KeyMetadata> {
// happy path-only code to demonstrate idea
const encrypterObj = this.encrypterMap[encrypterName];
const encryptedKey = await encrypterObj.encryptKey({ key, password });
const id = key.id || sha1(`${key.privateKey}${key.publicKey}`);

const newKey: Key = {
...key,
id,
};

const encrypter = this.encrypterMap[encrypterName];
const encryptedKey = await encrypter.encryptKey({
key: newKey,
password,
});
const keyMetadata = await this.keyStore.storeKeys([encryptedKey]);

this._writeIndexCache(key.publicKey, key);
this._writeIndexCache(newKey.id, newKey);

return keyMetadata[0];
}
Expand All @@ -135,15 +146,16 @@ export class KeyManager {
}

/**
* remove the key specified by this publicKey.
* Remove the key specified by this publicKey.
*
* @async
* @param publicKey specifies which key to remove
* @param id Specifies which key to remove.
* The id is computed as `sha1(private key + public key)`.
* @returns Metadata of the removed key
*/
public async removeKey(publicKey: string): Promise<KeyMetadata | undefined> {
const res = await this.keyStore.removeKey(publicKey);
this._writeIndexCache(publicKey, undefined);
public async removeKey(id: string): Promise<KeyMetadata | undefined> {
const res = await this.keyStore.removeKey(id);
this._writeIndexCache(id, undefined);
return res;
}

Expand All @@ -153,26 +165,27 @@ export class KeyManager {
*
* @async
* @param {Transaction} transaction Transaction object to sign
* @param {string} publicKey key to sign with
* @returns signed transaction
* @param {string} id Key to sign with. The id is computed as
* `sha1(private key + public key)`.
* @returns Signed transaction
*/
public async signTransaction({
transaction,
publicKey,
id,
password,
}: SignTransactionParams): Promise<Transaction> {
let key = this._readFromCache(publicKey);
let key = this._readFromCache(id);

if (!key) {
const encryptedKey = await this.keyStore.loadKey(publicKey);
const encryptedKey = await this.keyStore.loadKey(id);

if (!encryptedKey) {
throw new Error("No key found");
}

const encrypterObj = this.encrypterMap[encryptedKey.encrypterName];
key = await encrypterObj.decryptKey({ encryptedKey, password });
this._writeIndexCache(publicKey, key);
const encrypter = this.encrypterMap[encryptedKey.encrypterName];
key = await encrypter.decryptKey({ encryptedKey, password });
this._writeIndexCache(id, key);
}

const keyHandler = this.keyHandlerMap[key.type];
Expand All @@ -195,34 +208,36 @@ export class KeyManager {
*
* @async
* @param {object} params Params object
* @param {string} params.publicKey The user's key to authenticate
* @param {string} params.id The user's key to authenticate. The id is
* computed as `sha1(private key + public key)`.
* @param {string} params.password The password that will decrypt that secret
* @param {string} params.authServer The URL of the authentication server
* @returns {Promise<string>} authToken JWT
*/
// tslint:enable max-line-length
public async getAuthToken({
publicKey,
id,
password,
authServer,
}: GetAuthTokenParams): Promise<AuthToken> {
let key = this._readFromCache(publicKey);
let key = this._readFromCache(id);

if (!key) {
const encryptedKey = await this.keyStore.loadKey(publicKey);
const encryptedKey = await this.keyStore.loadKey(id);

if (!encryptedKey) {
throw new Error("No key found");
}

const encrypterObj = this.encrypterMap[encryptedKey.encrypterName];
key = await encrypterObj.decryptKey({ encryptedKey, password });
this._writeIndexCache(publicKey, key);
const encrypter = this.encrypterMap[encryptedKey.encrypterName];
key = await encrypter.decryptKey({ encryptedKey, password });
this._writeIndexCache(id, key);
}

const challengeRes = await fetch(
`${authServer}?account=${encodeURIComponent(publicKey)}`,
`${authServer}?account=${encodeURIComponent(key.publicKey)}`,
);

const {
transaction,
network_passphrase,
Expand Down Expand Up @@ -277,15 +292,15 @@ export class KeyManager {
const oldKeys = await this.keyStore.loadAllKeys();
const newKeys = await Promise.all(
oldKeys.map(async (encryptedKey: EncryptedKey) => {
const encrypterObj = this.encrypterMap[encryptedKey.encrypterName];
const decryptedKey = await encrypterObj.decryptKey({
const encrypter = this.encrypterMap[encryptedKey.encrypterName];
const decryptedKey = await encrypter.decryptKey({
encryptedKey,
password: oldPassword,
});

this._writeIndexCache(encryptedKey.publicKey, decryptedKey);
this._writeIndexCache(decryptedKey.id, decryptedKey);

return encrypterObj.encryptKey({
return encrypter.encryptKey({
key: decryptedKey,
password: newPassword,
});
Expand All @@ -295,17 +310,17 @@ export class KeyManager {
return this.keyStore.updateKeys(newKeys);
}

private _readFromCache(publicKey: string): Key | undefined {
private _readFromCache(id: string): Key | undefined {
if (!this.shouldCache) {
return undefined;
}

return this.keyCache[publicKey];
return this.keyCache[id];
}

private _writeIndexCache(publicKey: string, key: Key | undefined) {
private _writeIndexCache(id: string, key: Key | undefined) {
if (this.shouldCache && key) {
this.keyCache[publicKey] = key;
this.keyCache[id] = key;
}
}
}
38 changes: 19 additions & 19 deletions src/PluginTesting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ describe("testEncrypter", () => {
function encryptKeyGood({ key, password }: any) {
return Promise.resolve({
...key,
type: undefined,
publicKey: undefined,
privateKey: undefined,
encryptedPrivateKey: `${key.privateKey}${password}`,
encryptedBlob: `${key.privateKey}${password}`,
});
}

function decryptKeyGood({ encryptedKey, password }: any) {
return Promise.resolve({
...encryptedKey,
encryptedPrivateKey: undefined,
encryptedBlob: undefined,
encrypterName: undefined,
salt: undefined,
privateKey: encryptedKey.encryptedPrivateKey.split(password)[0],
privateKey: encryptedKey.encryptedBlob.split(password)[0],
});
}

Expand All @@ -31,13 +33,12 @@ describe("testEncrypter", () => {
return Promise.resolve({ encryptedKey, password });
}

function decryptKeyIncorrect({ encryptedKey }: any) {
function decryptKeyIncorrect() {
return Promise.resolve({
...encryptedKey,
encryptedPrivateKey: undefined,
privateKey: "INCORRECT",
encryptedBlob: undefined,
encrypterName: undefined,
salt: undefined,
privateKey: encryptedKey.encryptedPrivateKey,
});
}

Expand All @@ -58,6 +59,7 @@ describe("testEncrypter", () => {
encryptKey: encryptKeyGood,
decryptKey: decryptKeyGood,
};

testEncrypter(goodEncrypter)
.then(() => done())
.catch(done);
Expand Down Expand Up @@ -135,9 +137,7 @@ describe("testEncrypter", () => {
describe("testKeyStore", () => {
function makeKeyMetadata(encryptedKey: any) {
return getKeyMetadata({
encryptedKey,
creationTime: Math.floor(Date.now() / 1000),
modifiedTime: Math.floor(Date.now() / 1000),
...encryptedKey,
});
}

Expand All @@ -153,14 +153,14 @@ describe("testKeyStore", () => {
storeKeys(keys: EncryptedKey[]) {
// kill anything already in storage
if (!skipStorageChecks) {
const areStored = keys.filter((key) => storage[key.publicKey]);
const areStored = keys.filter((key) => storage[key.id]);
if (areStored.length) {
throw new Error("stored already");
}
}

keys.forEach((key) => {
storage[key.publicKey] = key;
storage[key.id] = key;
});

return Promise.resolve(
Expand All @@ -170,31 +170,31 @@ describe("testKeyStore", () => {
updateKeys(keys: EncryptedKey[]) {
// kill anything already in storage
if (!skipUpdateChecks) {
const areNotStored = keys.filter((key) => !storage[key.publicKey]);
const areNotStored = keys.filter((key) => !storage[key.id]);
if (areNotStored.length) {
throw new Error("stored already");
}
}

keys.forEach((key) => {
storage[key.publicKey] = key;
storage[key.id] = key;
});

return Promise.resolve(
keys.map((encryptedKey: EncryptedKey) => makeKeyMetadata(encryptedKey)),
);
},
loadKey(publicKey: string) {
return Promise.resolve(storage[publicKey]);
loadKey(id: string) {
return Promise.resolve(storage[id]);
},
removeKey(publicKey: string) {
const key = storage[publicKey];
removeKey(id: string) {
const key = storage[id];

if (!key) {
return Promise.resolve(undefined);
}

delete storage[publicKey];
delete storage[id];

return Promise.resolve(makeKeyMetadata(key));
},
Expand Down
Loading

0 comments on commit 0a4e603

Please sign in to comment.