1
0
Форкнуть 0

Refactor, InMemoryKeyStore, SidetreeRegistrar

This commit is contained in:
Brandon Murdoch 2019-03-24 17:44:19 -07:00
Родитель 81faa631b0
Коммит a0dafda83d
13 изменённых файлов: 2324 добавлений и 289 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -17,6 +17,9 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
# vs code settings
.vscode
# nyc test coverage
.nyc_output

1954
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -26,6 +26,10 @@
"devDependencies": {
"@types/jasmine": "3.3.8",
"@types/node": "11.9.3",
"@types/lodash": "4.14.123",
"@types/pouchdb": "6.3.3",
"@types/pouchdb-adapter-memory": "6.1.2",
"@types/memdown": "3.0.0",
"jasmine": "3.3.1",
"jasmine-reporters": "2.3.2",
"jasmine-spec-reporter": "4.2.1",
@ -37,13 +41,15 @@
"fetch-mock": "7.3.0"
},
"dependencies": {
"typescript": "3.3.3",
"node-fetch": "2.3.0",
"base64url": "3.0.1",
"es6-promise": "4.2.6",
"isomorphic-fetch": "2.2.1",
"lodash": "4.17.11",
"multihashes": "0.4.14",
"base64url": "3.0.1"
"pouchdb": "7.0.0",
"pouchdb-adapter-memory": "7.0.0",
"crypto-pouch": "3.1.3",
"typescript": "3.3.3"
},
"nyc": {
"extension": [

Просмотреть файл

@ -4,9 +4,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Resolver from './resolvers/Resolver';
import Registrar from './registrars/Registrar';
import KeyStore from './keystores/KeyStore';
import IResolver from './resolvers/IResolver';
import IRegistrar from './registrars/IRegistrar';
import IKeyStore from './keystores/IKeyStore';
/**
* Interface defining options for the
@ -17,19 +17,19 @@ export default class UserAgentOptions {
* Instance of Resolver than can be used
* to resolve identifiers.
*/
resolver?: Resolver;
resolver?: IResolver;
/**
* Instance of Registar than can be used
* to register identifiers.
*/
registrar?: Registrar;
registrar?: IRegistrar;
/**
* Instance of KeyStore than can be used
* to get and save keys.
*/
keyStore?: KeyStore;
keyStore?: IKeyStore;
/**
* The timeout when making requests to

Просмотреть файл

@ -1,25 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Interface defining methods and properties to
* be implemented by specific key stores.
*/
export default interface KeyStore {
/**
* Returns the key associated with the specified
* key identifier.
* @param keyIdentifier for which to return the key.
*/
get (keyIdentifier: string): Promise<Buffer>;
/**
* Saves the specified key to the key store using
* the key identifier.
* @param keyIdentifier for the key being saved.
* @param key being saved to the key store.
*/
save (keyIdentifier: string, key: Buffer): Promise<boolean>;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Interface defining methods and properties to
* be implemented by specific key stores.
*/
export default interface IKeyStore {
/**
* Returns the key associated with the specified
* key identifier.
* @param keyIdentifier for which to return the key.
*/
get (keyIdentifier: string): Promise<Buffer>;
/**
* Saves the specified key to the key store using
* the key identifier.
* @param keyIdentifier for the key being saved.
* @param key being saved to the key store.
*/
save (keyIdentifier: string, key: Buffer): Promise<void>;
}

Просмотреть файл

@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import IKeyStore from './IKeyStore';
import PouchDB from 'pouchdb';
import PouchDBAdapterMemory from 'pouchdb-adapter-memory';
import UserAgentError from '../UserAgentError';
PouchDB.plugin(PouchDBAdapterMemory);
const keyStore = new PouchDB('keyStore', { adapter: 'memory' });
/**
* An encrypted in memory implementation of IKeyStore using PouchDB
* and memdown. As soon as the process ends or
* the reference to the store is released all data is discarded.
*
* This implementation is intended as a batteries included approach
* to allow simple testing and experimentation with the UserAgent SDK.
*/
export default class InMemoryKeyStore implements IKeyStore {
/**
* Constructs an instance of the in memory key store
* optionally encrypting the contents of the store
* using the specified encryption key.
* @param [encryptionKey] a 32 byte buffer that will
* be used as the key or a string which will be used to
* generate one.
*/
constructor (encryptionKey?: Buffer | string) {
if (encryptionKey) {
let options: any = {};
const isBuffer = encryptionKey instanceof Buffer;
// If passed a buffer check that the
// size is 32 bytes, otherwise throw
if (isBuffer && encryptionKey.length !== 32) {
throw new UserAgentError('The encryption key buffer must be 32 bytes.');
}
if (isBuffer) {
options.key = encryptionKey;
} else {
options.password = encryptionKey;
}
PouchDB.plugin(require('crypto-pouch'));
// Set the encryption key for the store
(keyStore as any).crypto(options);
}
}
/**
* Gets the key from the store using the specified identifier.
* @param keyIdentifier for which to return the key.
*/
public async get (keyIdentifier: string): Promise<Buffer> {
try {
const keyDocument: any = await keyStore.get(keyIdentifier);
return Buffer.from(keyDocument.key);
} catch (error) {
throw new UserAgentError(`No key found for '${keyIdentifier}'.`);
}
}
/**
* Saves the specified key to the store using the key identifier.
* @param keyIdentifier to store the key against
* @param key the key to store.
*/
public async save (keyIdentifier: string, key: Buffer): Promise<void> {
// Format the document
const keyDocument = {
_id: keyIdentifier,
key: key
};
await keyStore.put(keyDocument);
}
}

Просмотреть файл

@ -10,7 +10,7 @@ import IdentifierDocument from '../IdentifierDocument';
* Interface defining methods and properties to
* be implemented by specific registration methods.
*/
export default interface Registrar {
export default interface IRegistrar {
/**
* Registers the identifier document on the ledger
* returning the identifier generated by the registrar.

Просмотреть файл

@ -10,15 +10,15 @@ import Identifier from '../Identifier';
import IdentifierDocument from '../IdentifierDocument';
import UserAgentError from '../UserAgentError';
import UserAgentOptions from '../UserAgentOptions';
import IRegistrar from './IRegistrar';
import Multihash from './Multihash';
import Registrar from './Registrar';
const cloneDeep = require('lodash/fp/cloneDeep');
declare var fetch: any;
/**
* Registrar implementation for the Sidetree (ION) network
*/
export default class SidetreeRegistrar implements Registrar {
export default class SidetreeRegistrar implements IRegistrar {
private timeoutInMilliseconds: number;
/**

Просмотреть файл

@ -1,80 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
require('es6-promise').polyfill();
import 'isomorphic-fetch';
import Identifier from '../Identifier';
import IdentifierDocument from '../IdentifierDocument';
import Resolver from './Resolver';
import UserAgentOptions from '../UserAgentOptions';
import UserAgentError from '../UserAgentError';
declare var fetch: any;
/**
* Fetches DID Documents from remote resolvers over http
* @class
* @implements Resolver
*/
export default class HttpResolver implements Resolver {
private timeoutInMilliseconds: number;
/**
* Constructs an instance of the HttpResolver class.
* @param url of the remote resolver.
* @param [options] for configuring the resolver.
*/
constructor (public url: string, public options?: UserAgentOptions) {
// Format the url
const slash = url.endsWith('/') ? '' : '/';
this.url = `${url}${slash}1.0/identifiers/`;
this.timeoutInMilliseconds =
1000 *
(!this.options || !this.options.timeoutInSeconds
? 30
: this.options.timeoutInSeconds);
}
/**
* Sends a fetch request to the resolver URL including the
* specified identifier.
* @param identifier to resolve.
*/
public async resolve (identifier: Identifier): Promise<IdentifierDocument> {
const query = `${this.url}${identifier.id}`;
return new Promise(async (resolve, reject) => {
let timer = setTimeout(
() => reject(new UserAgentError('Fetch timed out.')), this.timeoutInMilliseconds
);
// Now call the actual fetch with the updated options
const response = await fetch(query);
// Got a response so clear the timer
clearTimeout(timer);
// Check if the response was OK, and
// if not return the appropriate error
if (!response.ok) {
let error: Error;
switch (response.status) {
case 404:
error = new UserAgentError(`Identifier document not found for '${identifier.id}'`);
break;
default:
error = new UserAgentError(`Resolver at '${this.url}' returned an error with '${response.statusText}'`);
}
// Reject the promise
reject(error);
return;
}
const responseJson = await response.json();
const identifierDocument = new IdentifierDocument(responseJson.document || responseJson);
resolve(identifierDocument);
});
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
require('es6-promise').polyfill();
import 'isomorphic-fetch';
import Identifier from '../Identifier';
import IdentifierDocument from '../IdentifierDocument';
import IResolver from './IResolver';
import UserAgentOptions from '../UserAgentOptions';
import UserAgentError from '../UserAgentError';
declare var fetch: any;
/**
* Fetches DID Documents from remote resolvers over http
* @class
* @implements Resolver
*/
export default class HttpResolver implements IResolver {
private timeoutInMilliseconds: number;
/**
* Constructs an instance of the HttpResolver class.
* @param url of the remote resolver.
* @param [options] for configuring the resolver.
*/
constructor (public url: string, public options?: UserAgentOptions) {
// Format the url
const slash = url.endsWith('/') ? '' : '/';
this.url = `${url}${slash}1.0/identifiers/`;
this.timeoutInMilliseconds =
1000 *
(!this.options || !this.options.timeoutInSeconds
? 30
: this.options.timeoutInSeconds);
}
/**
* Sends a fetch request to the resolver URL including the
* specified identifier.
* @param identifier to resolve.
*/
public async resolve (identifier: Identifier): Promise<IdentifierDocument> {
const query = `${this.url}${identifier.id}`;
return new Promise(async (resolve, reject) => {
let timer = setTimeout(
() => reject(new UserAgentError('Fetch timed out.')), this.timeoutInMilliseconds
);
// Now call the actual fetch with the updated options
const response = await fetch(query);
// Got a response so clear the timer
clearTimeout(timer);
// Check if the response was OK, and
// if not return the appropriate error
if (!response.ok) {
let error: Error;
switch (response.status) {
case 404:
error = new UserAgentError(`Identifier document not found for '${identifier.id}'`);
break;
default:
error = new UserAgentError(`Resolver at '${this.url}' returned an error with '${response.statusText}'`);
}
// Reject the promise
reject(error);
return;
}
const responseJson = await response.json();
const identifierDocument = new IdentifierDocument(responseJson.document || responseJson);
resolve(identifierDocument);
});
}
}

Просмотреть файл

@ -1,20 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Identifier from '../Identifier';
import IdentifierDocument from '../IdentifierDocument';
/**
* Interface defining methods and properties to
* be implemented by specific resolver methods.
*/
export default interface Resolver {
/**
* Returns the identifier document for the specified
* identifier.
* @param identifier for which to return the identifier document.
*/
resolve (identifier: Identifier): Promise<IdentifierDocument>;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Identifier from '../Identifier';
import IdentifierDocument from '../IdentifierDocument';
/**
* Interface defining methods and properties to
* be implemented by specific resolver methods.
*/
export default interface IResolver {
/**
* Returns the identifier document for the specified
* identifier.
* @param identifier for which to return the identifier document.
*/
resolve (identifier: Identifier): Promise<IdentifierDocument>;
}

Просмотреть файл

@ -1,104 +1,109 @@
/**
* Type for defining a identifier document
* public key.
*/
export interface PublicKey {
/**
* The id of the public key in the format
* #{keyIdentifier}.
*/
id: string;
/**
* The type of the public key.
*/
type: string;
/**
* The onwer of the key.
*/
owner?: string;
/**
* The JWK public key.
*/
publicKeyJwk: any;
}
/**
* Type for defining a identifier document
* authentication reference.
*/
export interface AuthenticationReference {
/**
* The type of the authentication reference.
*/
type: string;
/**
* A public key reference in the format
* #{keyIdentifier}.
*/
publicKeyReference: string;
}
/**
* Type for defining a identifier document
* service reference.
*/
export interface ServiceReference {
/**
* The type of the service reference.
*/
type: string;
/**
* A public key reference in the format
* #{keyIdentifier}.
*/
publicKeyReference: string;
/**
* The service endpoint for the
* service reference
*/
serviceEndpoint: ServiceEndpoint;
}
/**
* Type for defining a identifier document
* service reference endpoint.
*/
export interface ServiceEndpoint {
/**
* The type of the service reference.
*/
context: string;
/**
* The type of the service reference.
*/
type: string;
}
/**
* Type for defining a identifier document
* service reference endpoint for a host.
*/
export interface HostServiceEndpoint extends ServiceEndpoint {
/**
* The type of the service reference.
*/
locations: Array<string>;
}
/**
* Type for defining a identifier document
* service reference endpoint for a user.
*/
export interface UserServiceEndpoint extends ServiceEndpoint {
/**
* The type of the service reference.
*/
instances: Array<string>;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Interface for defining a identifier document
* public key.
*/
export interface PublicKey {
/**
* The id of the public key in the format
* #{keyIdentifier}.
*/
id: string;
/**
* The type of the public key.
*/
type: string;
/**
* The onwer of the key.
*/
owner?: string;
/**
* The JWK public key.
*/
publicKeyJwk: any;
}
/**
* Interface for defining a identifier document
* authentication reference.
*/
export interface AuthenticationReference {
/**
* The type of the authentication reference.
*/
type: string;
/**
* A public key reference in the format
* #{keyIdentifier}.
*/
publicKeyReference: string;
}
/**
* Interface for defining a identifier document
* service reference.
*/
export interface ServiceReference {
/**
* The type of the service reference.
*/
type: string;
/**
* A public key reference in the format
* #{keyIdentifier}.
*/
publicKeyReference: string;
/**
* The service endpoint for the
* service reference
*/
serviceEndpoint: ServiceEndpoint;
}
/**
* Interface for defining a identifier document
* service reference endpoint.
*/
export interface ServiceEndpoint {
/**
* The type of the service reference.
*/
context: string;
/**
* The type of the service reference.
*/
type: string;
}
/**
* Interface for defining a identifier document
* service reference endpoint for a host.
*/
export interface HostServiceEndpoint extends ServiceEndpoint {
/**
* The type of the service reference.
*/
locations: Array<string>;
}
/**
* Interface for defining a identifier document
* service reference endpoint for a user.
*/
export interface UserServiceEndpoint extends ServiceEndpoint {
/**
* The type of the service reference.
*/
instances: Array<string>;
}

Просмотреть файл

@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import InMemoryKeyStore from '../../src/keystores/InMemoryKeyStore';
describe('InMemoryKeyStore', () => {
it('should return a new instance with no encryption', () => {
const keyStore = new InMemoryKeyStore();
expect(keyStore).toBeDefined();
});
it('should return a new instance with encryption enabled via string', () => {
const keyStore = new InMemoryKeyStore('some password string');
expect(keyStore).toBeDefined();
});
it('should return a new instance with encryption enabled via buffer', () => {
const encryptionKey = Buffer.from('7468697320697320612074c3a9737445');
const keyStore = new InMemoryKeyStore(encryptionKey);
expect(keyStore).toBeDefined();
});
it('should throw when encryption key buffer less than 32 bytes', () => {
const encryptionKey = Buffer.from('8697320697'); // 10
expect(() => new InMemoryKeyStore(encryptionKey)).toThrowError('The encryption key buffer must be 32 bytes.');
});
it('should throw when encryption key buffer greater than 32 bytes', () => {
const encryptionKey = Buffer.from('7468697320697320612074c3a973748697320697'); // 40
expect(() => new InMemoryKeyStore(encryptionKey)).toThrowError('The encryption key buffer must be 32 bytes.');
});
it('should throw when key not found', async (done) => {
try{
const keyStore = new InMemoryKeyStore();
await keyStore.get('does not exist');
} catch (error) {
expect(error.message).toEqual(`No key found for 'does not exist'.`);
}
done();
});
it('should save key to store and retrieve saved key', async (done) => {
try{
const keyBuffer: Buffer = Buffer.from('Some key material');
const keyStore = new InMemoryKeyStore();
await keyStore.save('did:test:123456789#master', keyBuffer);
// Now try get get the key back
const buffer: Buffer = await keyStore.get('did:test:123456789#master');
expect(buffer).toBeDefined();
expect(buffer.toString()).toEqual('Some key material');
} catch (error) {
fail(`Exception not expected, got: '${error}'`);
}
done();
});
it('should save key to store and retrieve saved key when using encrypted store', async (done) => {
try{
const keyBuffer: Buffer = Buffer.from('Some key material');
const keyStore = new InMemoryKeyStore('password');
await keyStore.save('did:test:987654321#master', keyBuffer);
// Now try get get the key back
const buffer: Buffer = await keyStore.get('did:test:987654321#master');
expect(buffer).toBeDefined();
expect(buffer.toString()).toEqual('Some key material');
} catch (error) {
fail(`Exception not expected, got: '${error}'`);
}
done();
});
});

Просмотреть файл

@ -10,13 +10,14 @@
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
"allowSyntheticDefaultImports": true,
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */