diff --git a/package-lock.json b/package-lock.json index 7d874d3..bb37559 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,8 @@ "go-ipfs": "^0.17.0", "ipfs": "^0.66.0", "ipfsd-ctl": "^13.0.0", + "key-did-provider-ed25519": "^2.0.1", + "key-did-resolver": "^2.3.0", "localstorage-down": "^0.6.7", "localstorage-level-migration": "^0.2.0", "markdown-toc": "^1.2.0", @@ -5733,6 +5735,15 @@ "platform": "^1.3.3" } }, + "node_modules/bigint-mod-arith": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", + "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", + "dev": true, + "engines": { + "node": ">=10.4.0" + } + }, "node_modules/bignumber.js": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", @@ -15582,6 +15593,66 @@ "node": ">= 6" } }, + "node_modules/key-did-provider-ed25519": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/key-did-provider-ed25519/-/key-did-provider-ed25519-2.0.1.tgz", + "integrity": "sha512-FaB2g7zUAeN/bLaFl2YSNQx9NsxgDd/3TR1YGvoXLhENsvZBdyA2V7hX8/MZhZ5jm82wL5lRw3O4UXuz/pJANQ==", + "dev": true, + "dependencies": { + "@stablelib/ed25519": "^1.0.2", + "did-jwt": "^6.0.0", + "fast-json-stable-stringify": "^2.1.0", + "rpc-utils": "^0.6.2", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/key-did-provider-ed25519/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, + "node_modules/key-did-provider-ed25519/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dev": true, + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/key-did-resolver": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/key-did-resolver/-/key-did-resolver-2.3.0.tgz", + "integrity": "sha512-q3ChQILPe+u8qkpWP196fEoxsygEyjM3K25qrGaMSolVaUdfgj7qwMz2DE/GRIlfNK2HgKW6KXA8RZMy8aL4MA==", + "dev": true, + "dependencies": { + "@stablelib/ed25519": "^1.0.2", + "bigint-mod-arith": "^3.1.0", + "multiformats": "^9.5.2", + "nist-weierstrauss": "^1.3.0", + "uint8arrays": "^3.0.0", + "varint": "^6.0.0" + } + }, + "node_modules/key-did-resolver/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, + "node_modules/key-did-resolver/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dev": true, + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/keyv": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", @@ -18157,6 +18228,31 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/nist-weierstrauss": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/nist-weierstrauss/-/nist-weierstrauss-1.6.1.tgz", + "integrity": "sha512-FpjCOnPV/s3ZVIkeldCVSml2K4lruabPbBgoEitpCK1JL0KTVoWb56CFTU6rZn5i6VqAjdwcOp0FDwJACPmeFA==", + "dev": true, + "dependencies": { + "multiformats": "^9.6.5", + "uint8arrays": "^2.1.4" + } + }, + "node_modules/nist-weierstrauss/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, + "node_modules/nist-weierstrauss/node_modules/uint8arrays": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", + "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", + "dev": true, + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -32199,6 +32295,12 @@ "platform": "^1.3.3" } }, + "bigint-mod-arith": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", + "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", + "dev": true + }, "bignumber.js": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", @@ -39821,6 +39923,67 @@ } } }, + "key-did-provider-ed25519": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/key-did-provider-ed25519/-/key-did-provider-ed25519-2.0.1.tgz", + "integrity": "sha512-FaB2g7zUAeN/bLaFl2YSNQx9NsxgDd/3TR1YGvoXLhENsvZBdyA2V7hX8/MZhZ5jm82wL5lRw3O4UXuz/pJANQ==", + "dev": true, + "requires": { + "@stablelib/ed25519": "^1.0.2", + "did-jwt": "^6.0.0", + "fast-json-stable-stringify": "^2.1.0", + "rpc-utils": "^0.6.2", + "uint8arrays": "^3.0.0" + }, + "dependencies": { + "multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, + "uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dev": true, + "requires": { + "multiformats": "^9.4.2" + } + } + } + }, + "key-did-resolver": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/key-did-resolver/-/key-did-resolver-2.3.0.tgz", + "integrity": "sha512-q3ChQILPe+u8qkpWP196fEoxsygEyjM3K25qrGaMSolVaUdfgj7qwMz2DE/GRIlfNK2HgKW6KXA8RZMy8aL4MA==", + "dev": true, + "requires": { + "@stablelib/ed25519": "^1.0.2", + "bigint-mod-arith": "^3.1.0", + "multiformats": "^9.5.2", + "nist-weierstrauss": "^1.3.0", + "uint8arrays": "^3.0.0", + "varint": "^6.0.0" + }, + "dependencies": { + "multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, + "uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dev": true, + "requires": { + "multiformats": "^9.4.2" + } + } + } + }, "keyv": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", @@ -41710,6 +41873,33 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nist-weierstrauss": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/nist-weierstrauss/-/nist-weierstrauss-1.6.1.tgz", + "integrity": "sha512-FpjCOnPV/s3ZVIkeldCVSml2K4lruabPbBgoEitpCK1JL0KTVoWb56CFTU6rZn5i6VqAjdwcOp0FDwJACPmeFA==", + "dev": true, + "requires": { + "multiformats": "^9.6.5", + "uint8arrays": "^2.1.4" + }, + "dependencies": { + "multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, + "uint8arrays": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", + "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", + "dev": true, + "requires": { + "multiformats": "^9.4.2" + } + } + } + }, "no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", diff --git a/package.json b/package.json index 10d9491..fa335df 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "go-ipfs": "^0.17.0", "ipfs": "^0.66.0", "ipfsd-ctl": "^13.0.0", - "localstorage-down": "^0.6.7", - "localstorage-level-migration": "^0.2.0", + "key-did-provider-ed25519": "^2.0.1", + "key-did-resolver": "^2.3.0", "markdown-toc": "^1.2.0", "mkdirp": "^2.1.1", "mocha": "^10.2.0", diff --git a/src/identities/identity-provider.js b/src/identities/identity-provider.js new file mode 100644 index 0000000..c46a2d6 --- /dev/null +++ b/src/identities/identity-provider.js @@ -0,0 +1,177 @@ +import Identity from './identity.js' +import IdentityProvider from './providers/interface.js' +import OrbitDBIdentityProvider from './providers/orbitdb.js' +// import DIDIdentityProvider from './identity-providers/did.js' +// import EthIdentityProvider from './identity-providers/ethereum.js' +import KeyStore from '../key-store.js' +import IdentityStorage from '../storage/identity.js' +import LRU from 'lru' +import path from 'path' + +const defaultType = 'orbitdb' +const identityKeysPath = path.join('./orbitdb', 'identity', 'identitykeys') + +const supportedTypes = { + orbitdb: OrbitDBIdentityProvider + // [DIDIdentityProvider.type]: DIDIdentityProvider, + // [EthIdentityProvider.type]: EthIdentityProvider +} + +const getHandlerFor = (type) => { + if (!Identities.isSupported(type)) { + throw new Error(`IdentityProvider type '${type}' is not supported`) + } + return supportedTypes[type] +} + +class Identities { + constructor (options) { + this._keystore = options.keystore + this._signingKeyStore = options.signingKeyStore || this._keystore + this._knownIdentities = options.cache || new LRU(options.cacheSize || 100) + this._storage = options.identityStore + } + + static get IdentityProvider () { return IdentityProvider } + + get keystore () { return this._keystore } + + get signingKeyStore () { return this._signingKeyStore } + + async sign (identity, data) { + const signingKey = await this.keystore.getKey(identity.id) + if (!signingKey) { + throw new Error('Private signing key not found from KeyStore') + } + const sig = await this.keystore.sign(signingKey, data) + return sig + } + + async verify (signature, publicKey, data, verifier = 'v1') { + return this.keystore.verify(signature, publicKey, data, verifier) + } + + async createIdentity (options = {}) { + const keystore = options.keystore || this.keystore + const type = options.type || defaultType + const identityProvider = type === defaultType ? new OrbitDBIdentityProvider(options.signingKeyStore || keystore) : new (getHandlerFor(type))(options) + const id = await identityProvider.getId(options) + + const { publicKey, idSignature } = await this.signId(id) + const pubKeyIdSignature = await identityProvider.signIdentity(publicKey + idSignature, options) + // return new Identity(id, publicKey, idSignature, pubKeyIdSignature, type, this) + const identity = new Identity(id, publicKey, idSignature, pubKeyIdSignature, type, this) + await identity.store() + // const hash = options.identityStore.put(identity.toJSON()) + return identity + } + + async get (hash) { + return this._storage.get(hash) + } + + async signId (id) { + const keystore = this.keystore + const key = await keystore.getKey(id) || await keystore.createKey(id) + const publicKey = keystore.getPublic(key) + const idSignature = await keystore.sign(key, id) + return { publicKey, idSignature } + } + + async verifyIdentity (identity) { + if (!Identity.isIdentity(identity)) { + return false + } + + const knownID = this._knownIdentities.get(identity.signatures.id) + if (knownID) { + return identity.id === knownID.id && + identity.publicKey === knownID.publicKey && + identity.signatures.id === knownID.signatures.id && + identity.signatures.publicKey === knownID.signatures.publicKey + } + + const verifyIdSig = await this.keystore.verify( + identity.signatures.id, + identity.publicKey, + identity.id + ) + if (!verifyIdSig) return false + + const IdentityProvider = getHandlerFor(identity.type) + const verified = await IdentityProvider.verifyIdentity(identity) + if (verified) { + this._knownIdentities.set(identity.signatures.id, Identity.toJSON(identity)) + } + + return verified + } + + static async verifyIdentity (identity) { + if (!Identity.isIdentity(identity)) { + return false + } + + const verifyIdSig = await KeyStore.verify( + identity.signatures.id, + identity.publicKey, + identity.id + ) + + if (!verifyIdSig) return false + + const IdentityProvider = getHandlerFor(identity.type) + return IdentityProvider.verifyIdentity(identity) + } + + static async createIdentity (options = {}) { + if (!options.keystore) { + options.keystore = new KeyStore(options.identityKeysPath || identityKeysPath) + } + if (!options.signingKeyStore) { + if (options.signingKeysPath) { + options.signingKeyStore = new KeyStore(options.signingKeysPath) + } else { + options.signingKeyStore = options.keystore + } + } + await options.keystore.open() + await options.signingKeyStore.open() + + let identityStore + if (options.storage) { + identityStore = await IdentityStorage({ storage: options.storage }) + } else if (options.ipfs) { + identityStore = await IdentityStorage({ ipfs: options.ipfs }) + } else { + identityStore = await IdentityStorage() + } + + options = Object.assign({}, { type: defaultType, identityStore }, options) + const identities = new Identities(options) + return identities.createIdentity(options) + } + + static isSupported (type) { + return Object.keys(supportedTypes).includes(type) + } + + static addIdentityProvider (IdentityProvider) { + if (!IdentityProvider) { + throw new Error('IdentityProvider class needs to be given as an option') + } + + if (!IdentityProvider.type || + typeof IdentityProvider.type !== 'string') { + throw new Error('Given IdentityProvider class needs to implement: static get type() { /* return a string */ }.') + } + + supportedTypes[IdentityProvider.type] = IdentityProvider + } + + static removeIdentityProvider (type) { + delete supportedTypes[type] + } +} + +export { Identities as default, Identity } diff --git a/src/identities/index.js b/src/identities/index.js index 3b1d792..66cb49e 100644 --- a/src/identities/index.js +++ b/src/identities/index.js @@ -1,180 +1,2 @@ -import Identity from './identity.js' -import IdentityProvider from './providers/interface.js' -import OrbitDBIdentityProvider from './providers/orbitdb.js' -// import DIDIdentityProvider from './identity-providers/did.js' -// import EthIdentityProvider from './identity-providers/ethereum.js' -import KeyStore from '../key-store.js' -import IdentityStorage from '../storage/identity.js' -import LRU from 'lru' -import path from 'path' - -const defaultType = 'orbitdb' -const identityKeysPath = path.join('./orbitdb', 'identity', 'identitykeys') - -const supportedTypes = { - orbitdb: OrbitDBIdentityProvider - // [DIDIdentityProvider.type]: DIDIdentityProvider, - // [EthIdentityProvider.type]: EthIdentityProvider -} - -const getHandlerFor = (type) => { - if (!Identities.isSupported(type)) { - throw new Error(`IdentityProvider type '${type}' is not supported`) - } - return supportedTypes[type] -} - -class Identities { - constructor (options) { - this._keystore = options.keystore - this._signingKeyStore = options.signingKeyStore || this._keystore - this._knownIdentities = options.cache || new LRU(options.cacheSize || 100) - this._storage = options.identityStore - } - - static get IdentityProvider () { return IdentityProvider } - - get keystore () { return this._keystore } - - get signingKeyStore () { return this._signingKeyStore } - - async sign (identity, data) { - const signingKey = await this.keystore.getKey(identity.id) - if (!signingKey) { - throw new Error('Private signing key not found from KeyStore') - } - const sig = await this.keystore.sign(signingKey, data) - return sig - } - - async verify (signature, publicKey, data, verifier = 'v1') { - return this.keystore.verify(signature, publicKey, data, verifier) - } - - async createIdentity (options = {}) { - const keystore = options.keystore || this.keystore - const type = options.type || defaultType - const identityProvider = type === defaultType ? new OrbitDBIdentityProvider(options.signingKeyStore || keystore) : new (getHandlerFor(type))(options) - const id = await identityProvider.getId(options) - - if (options.migrate) { - await options.migrate({ targetStore: keystore._store, targetId: id }) - } - const { publicKey, idSignature } = await this.signId(id) - const pubKeyIdSignature = await identityProvider.signIdentity(publicKey + idSignature, options) - // return new Identity(id, publicKey, idSignature, pubKeyIdSignature, type, this) - const identity = new Identity(id, publicKey, idSignature, pubKeyIdSignature, type, this) - await identity.store() - // const hash = options.identityStore.put(identity.toJSON()) - return identity - } - - async get (hash) { - return this._storage.get(hash) - } - - async signId (id) { - const keystore = this.keystore - const key = await keystore.getKey(id) || await keystore.createKey(id) - const publicKey = keystore.getPublic(key) - const idSignature = await keystore.sign(key, id) - return { publicKey, idSignature } - } - - async verifyIdentity (identity) { - if (!Identity.isIdentity(identity)) { - return false - } - - const knownID = this._knownIdentities.get(identity.signatures.id) - if (knownID) { - return identity.id === knownID.id && - identity.publicKey === knownID.publicKey && - identity.signatures.id === knownID.signatures.id && - identity.signatures.publicKey === knownID.signatures.publicKey - } - - const verifyIdSig = await this.keystore.verify( - identity.signatures.id, - identity.publicKey, - identity.id - ) - if (!verifyIdSig) return false - - const IdentityProvider = getHandlerFor(identity.type) - const verified = await IdentityProvider.verifyIdentity(identity) - if (verified) { - this._knownIdentities.set(identity.signatures.id, Identity.toJSON(identity)) - } - - return verified - } - - static async verifyIdentity (identity) { - if (!Identity.isIdentity(identity)) { - return false - } - - const verifyIdSig = await KeyStore.verify( - identity.signatures.id, - identity.publicKey, - identity.id - ) - - if (!verifyIdSig) return false - - const IdentityProvider = getHandlerFor(identity.type) - return IdentityProvider.verifyIdentity(identity) - } - - static async createIdentity (options = {}) { - if (!options.keystore) { - options.keystore = new KeyStore(options.identityKeysPath || identityKeysPath) - } - if (!options.signingKeyStore) { - if (options.signingKeysPath) { - options.signingKeyStore = new KeyStore(options.signingKeysPath) - } else { - options.signingKeyStore = options.keystore - } - } - await options.keystore.open() - await options.signingKeyStore.open() - - let identityStore - if (options.storage) { - identityStore = await IdentityStorage({ storage: options.storage }) - } else if (options.ipfs) { - identityStore = await IdentityStorage({ ipfs: options.ipfs }) - } else { - identityStore = await IdentityStorage() - } - - options = Object.assign({}, { type: defaultType, identityStore }, options) - const identities = new Identities(options) - return identities.createIdentity(options) - } - - static isSupported (type) { - return Object.keys(supportedTypes).includes(type) - } - - static addIdentityProvider (IdentityProvider) { - if (!IdentityProvider) { - throw new Error('IdentityProvider class needs to be given as an option') - } - - if (!IdentityProvider.type || - typeof IdentityProvider.type !== 'string') { - throw new Error('Given IdentityProvider class needs to implement: static get type() { /* return a string */ }.') - } - - supportedTypes[IdentityProvider.type] = IdentityProvider - } - - static removeIdentityProvider (type) { - delete supportedTypes[type] - } -} - -export { Identities as default, Identity } +export { default as IdentityProvider } from './identity-provider.js' +export { default as Identity } from './identity.js' diff --git a/src/index.js b/src/index.js index a5b2031..86f7371 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,3 @@ export { Log, DefaultAccessController, Entry } from './oplog/index.js' export { default as KeyStore } from './key-store.js' +export { IdentityProvider } from './identities/index.js' diff --git a/test/document-store.spec.js b/test/document-store.spec.js index f2a9ba5..698de3e 100644 --- a/test/document-store.spec.js +++ b/test/document-store.spec.js @@ -1,7 +1,7 @@ import { deepStrictEqual, strictEqual } from 'assert' import rimraf from 'rimraf' import { Log, Entry } from '../src/oplog/index.js' -import IdentityProvider from '../src/identities/index.js' +import { IdentityProvider } from '../src/identities/index.js' import KeyStore from '../src/key-store.js' import { DocumentStore, Database } from '../src/db/index.js' import { IPFSBlockStorage, LevelStorage } from '../src/storage/index.js' diff --git a/test/event-store.spec.js b/test/event-store.spec.js index e4884d2..9da5082 100644 --- a/test/event-store.spec.js +++ b/test/event-store.spec.js @@ -1,7 +1,7 @@ import { deepStrictEqual, strictEqual } from 'assert' import rimraf from 'rimraf' import { Log, Entry } from '../src/oplog/index.js' -import IdentityProvider from '../src/identities/index.js' +import { IdentityProvider } from '../src/identities/index.js' import KeyStore from '../src/key-store.js' import { EventStore, Database } from '../src/db/index.js' import { IPFSBlockStorage, LevelStorage } from '../src/storage/index.js' diff --git a/test/feed.spec.js b/test/feed.spec.js index 65138c9..90837e8 100644 --- a/test/feed.spec.js +++ b/test/feed.spec.js @@ -1,7 +1,7 @@ import { deepStrictEqual, strictEqual } from 'assert' import rimraf from 'rimraf' import { Log, Entry } from '../src/oplog/index.js' -import IdentityProvider from '../src/identities/index.js' +import { IdentityProvider } from '../src/identities/index.js' import KeyStore from '../src/key-store.js' import { Feed, Database } from '../src/db/index.js' import { IPFSBlockStorage, LevelStorage } from '../src/storage/index.js' diff --git a/test/fixtures/orbit-db-identity-keys.js b/test/fixtures/orbit-db-identity-keys.js index 0156386..05c6c95 100644 --- a/test/fixtures/orbit-db-identity-keys.js +++ b/test/fixtures/orbit-db-identity-keys.js @@ -1,5 +1,5 @@ import KeyStore from '../../src/key-store.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import rimraf from 'rimraf' const { createIdentity } = IdentityProvider diff --git a/test/identies/fixtures/keys/000005.ldb b/test/identies/fixtures/keys/000005.ldb new file mode 100644 index 0000000..4895d76 Binary files /dev/null and b/test/identies/fixtures/keys/000005.ldb differ diff --git a/test/identies/fixtures/keys/000032.log b/test/identies/fixtures/keys/000032.log new file mode 100644 index 0000000..e69de29 diff --git a/test/identies/fixtures/keys/CURRENT b/test/identies/fixtures/keys/CURRENT new file mode 100644 index 0000000..d95f027 --- /dev/null +++ b/test/identies/fixtures/keys/CURRENT @@ -0,0 +1 @@ +MANIFEST-000031 diff --git a/test/identies/fixtures/keys/LOCK b/test/identies/fixtures/keys/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/test/identies/fixtures/keys/LOG b/test/identies/fixtures/keys/LOG new file mode 100644 index 0000000..0d9cafb --- /dev/null +++ b/test/identies/fixtures/keys/LOG @@ -0,0 +1,3 @@ +2023/02/16-14:47:21.736625 171233000 Recovering log #30 +2023/02/16-14:47:21.737020 171233000 Delete type=3 #29 +2023/02/16-14:47:21.737069 171233000 Delete type=0 #30 diff --git a/test/identies/fixtures/keys/LOG.old b/test/identies/fixtures/keys/LOG.old new file mode 100644 index 0000000..c178010 --- /dev/null +++ b/test/identies/fixtures/keys/LOG.old @@ -0,0 +1,3 @@ +2023/02/16-14:46:59.857680 16e03b000 Recovering log #28 +2023/02/16-14:46:59.858826 16e03b000 Delete type=0 #28 +2023/02/16-14:46:59.858852 16e03b000 Delete type=3 #27 diff --git a/test/identies/fixtures/keys/MANIFEST-000031 b/test/identies/fixtures/keys/MANIFEST-000031 new file mode 100644 index 0000000..fd12011 Binary files /dev/null and b/test/identies/fixtures/keys/MANIFEST-000031 differ diff --git a/test/identities/browser.spec.js b/test/identities/browser.spec.js new file mode 100644 index 0000000..f347894 --- /dev/null +++ b/test/identities/browser.spec.js @@ -0,0 +1,28 @@ +// import path from 'path' +// import isNode from 'is-node' + +// // This file will be picked up by webpack into the +// // tests bundle and the code here gets run when imported +// // into the browser tests index through browser/run.js +// if (!isNode) { +// const existingKey = (await import('./fixtures/keys/existing.json')).default +// const testKey1 = (await import('./fixtures/keys/QmPhnEjVkYE1Ym7F5MkRUfkD6NtuSptE7ugu1Ggr149W2X.json')).default +// const testKey2 = (await import('./fixtures/keys/0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa.json')).default + +// // If in browser, put the fixture keys in local storage +// // so that Keystore can find them +// const levelup = (await import('levelup')).default +// const level = (await import('level-js')).default +// const storagePath = path.resolve('./test/fixtures/savedKeys') +// const signingStore = levelup(level(storagePath)) + +// const copyFixtures = [] +// copyFixtures.push(signingStore) + +// /* global localStorage */ +// copyFixtures.push(localStorage.setItem('existing.json', JSON.stringify(existingKey))) +// copyFixtures.push(signingStore.put('QmPhnEjVkYE1Ym7F5MkRUfkD6NtuSptE7ugu1Ggr149W2X', JSON.stringify(testKey1))) +// copyFixtures.push(signingStore.put('0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa', JSON.stringify(testKey2))) + +// Promise.all(copyFixtures) +// } diff --git a/test/identities/browser/index.html b/test/identities/browser/index.html new file mode 100644 index 0000000..bf1c0e0 --- /dev/null +++ b/test/identities/browser/index.html @@ -0,0 +1,21 @@ + + + + Mocha Tests + + + + +
+ + + + + + + diff --git a/test/identities/did-identity-provider.spec.js b/test/identities/did-identity-provider.spec.js new file mode 100644 index 0000000..a8f1859 --- /dev/null +++ b/test/identities/did-identity-provider.spec.js @@ -0,0 +1,138 @@ +import assert from 'assert' +import path from 'path' +import rmrf from 'rimraf' +import { KeyStore, IdentityProvider } from '../../src/index.js' +import { Identity } from '../../src/identities/index.js' +import { Ed25519Provider } from 'key-did-provider-ed25519' +import KeyDidResolver from 'key-did-resolver' +import DIDIdentityProvider from '../../src/identities/providers/did.js' +const keypath = path.resolve('./test/identies/fixtures/keys') +let keystore + +const seed = new Uint8Array([157, 94, 116, 198, 19, 248, 93, 239, 173, 82, 245, 222, 199, 7, 183, 177, 123, 238, 83, 240, 143, 188, 87, 191, 33, 95, 58, 136, 46, 218, 219, 245]) +const didStr = 'did:key:z6MkpnTJwrrVuphNh1uKb5DB7eRxvqniVaSDUHU6jtGVmn3r' + +const type = DIDIdentityProvider.type +describe('DID Identity Provider', function () { + before(async () => { + DIDIdentityProvider.setDIDResolver(KeyDidResolver.getResolver()) + IdentityProvider.addIdentityProvider(DIDIdentityProvider) + keystore = new KeyStore(keypath) + await keystore.open() + }) + + after(async () => { + await keystore.close() + }) + + describe('create an DID identity', () => { + let identity + + before(async () => { + const didProvider = new Ed25519Provider(seed) + identity = await IdentityProvider.createIdentity({ type, keystore, didProvider }) + }) + + it('has the correct id', async () => { + assert.strictEqual(identity.id, didStr) + }) + + it('created a key for id in keystore', async () => { + const key = await keystore.getKey(didStr) + assert.notStrictEqual(key, undefined) + }) + + it('has the correct public key', async () => { + const signingKey = await keystore.getKey(didStr) + assert.notStrictEqual(signingKey, undefined) + assert.strictEqual(identity.publicKey, keystore.getPublic(signingKey)) + }) + + it('has a signature for the id', async () => { + const signingKey = await keystore.getKey(didStr) + const idSignature = await keystore.sign(signingKey, didStr) + const verifies = await KeyStore.verify(idSignature, identity.publicKey, didStr) + assert.strictEqual(verifies, true) + assert.strictEqual(identity.signatures.id, idSignature) + }) + + it('has a signature for the publicKey', async () => { + const signingKey = await keystore.getKey(didStr) + const idSignature = await keystore.sign(signingKey, didStr) + assert.notStrictEqual(idSignature, undefined) + }) + }) + + describe('verify identity', () => { + let identity + + before(async () => { + const didProvider = new Ed25519Provider(seed) + identity = await IdentityProvider.createIdentity({ type, keystore, didProvider }) + }) + + it('DID identity verifies', async () => { + const verified = await IdentityProvider.verifyIdentity(identity) + assert.strictEqual(verified, true) + }) + + it('DID identity with incorrect id does not verify', async () => { + const identity2 = new Identity('NotAnId', identity.publicKey, identity.signatures.id, identity.signatures.publicKey, identity.type, identity.provider) + const verified = await IdentityProvider.verifyIdentity(identity2) + assert.strictEqual(verified, false) + }) + }) + + describe('sign data with an identity', () => { + let identity + const data = 'hello friend' + + before(async () => { + const didProvider = new Ed25519Provider(seed) + identity = await IdentityProvider.createIdentity({ type, keystore, didProvider }) + }) + + it('sign data', async () => { + const signingKey = await keystore.getKey(identity.id) + const expectedSignature = await keystore.sign(signingKey, data) + const signature = await identity.provider.sign(identity, data, keystore) + assert.strictEqual(signature, expectedSignature) + }) + + it('throws an error if private key is not found from keystore', async () => { + // Remove the key from the keystore (we're using a mock storage in these tests) + const modifiedIdentity = new Identity('this id does not exist', identity.publicKey, '', identity.signatures, identity.type, identity.provider) + let signature + let err + try { + signature = await identity.provider.sign(modifiedIdentity, data, keystore) + } catch (e) { + err = e.toString() + } + assert.strictEqual(signature, undefined) + assert.strictEqual(err, 'Error: Private signing key not found from KeyStore') + }) + + describe('verify data signed by an identity', () => { + const data = 'hello friend' + let identity + let signature + + before(async () => { + const didProvider = new Ed25519Provider(seed) + identity = await IdentityProvider.createIdentity({ type, keystore, didProvider }) + signature = await identity.provider.sign(identity, data, keystore) + }) + + it('verifies that the signature is valid', async () => { + const verified = await identity.provider.verify(signature, identity.publicKey, data) + assert.strictEqual(verified, true) + }) + + it('doesn\'t verify invalid signature', async () => { + const verified = await identity.provider.verify('invalid', identity.publicKey, data) + assert.strictEqual(verified, false) + }) + }) + }) +}) diff --git a/test/identities/ethereum-identity-provider.spec.js b/test/identities/ethereum-identity-provider.spec.js new file mode 100644 index 0000000..1223642 --- /dev/null +++ b/test/identities/ethereum-identity-provider.spec.js @@ -0,0 +1,133 @@ +import assert from 'assert' +import path from 'path' +import rmrf from 'rimraf' +import { KeyStore, IdentityProvider } from '../../src/index.js' +import { Identity } from '../../src/identities/index.js' +import EthIdentityProvider from '../../src/identities/providers/ethereum.js' + +const keypath = path.resolve('./test/identities/fixtures/keys') +let keystore + +const type = EthIdentityProvider.type +describe('Ethereum Identity Provider', function () { + before(async () => { + IdentityProvider.addIdentityProvider(EthIdentityProvider) + keystore = new KeyStore(keypath) + await keystore.open() + }) + + after(async () => { + await keystore.close() + }) + + describe('create an ethereum identity', () => { + let identity + let wallet + + before(async () => { + const ethIdentityProvider = new EthIdentityProvider() + wallet = await ethIdentityProvider._createWallet() + identity = await IdentityProvider.createIdentity({ type, keystore, wallet }) + }) + + it('has the correct id', async () => { + assert.strictEqual(identity.id, wallet.address) + }) + + it('created a key for id in keystore', async () => { + const key = await keystore.getKey(wallet.address) + assert.notStrictEqual(key, undefined) + }) + + it('has the correct public key', async () => { + const signingKey = await keystore.getKey(wallet.address) + assert.notStrictEqual(signingKey, undefined) + assert.strictEqual(identity.publicKey, keystore.getPublic(signingKey)) + }) + + it('has a signature for the id', async () => { + const signingKey = await keystore.getKey(wallet.address) + const idSignature = await keystore.sign(signingKey, wallet.address) + const verifies = await KeyStore.verify(idSignature, Buffer.from(signingKey.public.marshal()).toString('hex'), wallet.address) + assert.strictEqual(verifies, true) + assert.strictEqual(identity.signatures.id, idSignature) + }) + + it('has a signature for the publicKey', async () => { + const signingKey = await keystore.getKey(wallet.address) + const idSignature = await keystore.sign(signingKey, wallet.address) + const publicKeyAndIdSignature = await wallet.signMessage(identity.publicKey + idSignature) + assert.strictEqual(identity.signatures.publicKey, publicKeyAndIdSignature) + }) + }) + + describe('verify identity', () => { + let identity + + before(async () => { + identity = await IdentityProvider.createIdentity({ keystore, type }) + }) + + it('ethereum identity verifies', async () => { + const verified = await IdentityProvider.verifyIdentity(identity) + assert.strictEqual(verified, true) + }) + + it('ethereum identity with incorrect id does not verify', async () => { + const identity2 = new Identity('NotAnId', identity.publicKey, identity.signatures.id, identity.signatures.publicKey, identity.type, identity.provider) + const verified = await IdentityProvider.verifyIdentity(identity2) + assert.strictEqual(verified, false) + }) + }) + + describe('sign data with an identity', () => { + let identity + const data = 'hello friend' + + before(async () => { + identity = await IdentityProvider.createIdentity({ keystore, type }) + }) + + it('sign data', async () => { + const signingKey = await keystore.getKey(identity.id) + const expectedSignature = await keystore.sign(signingKey, data) + const signature = await identity.provider.sign(identity, data, keystore) + assert.strictEqual(signature, expectedSignature) + }) + + it('throws an error if private key is not found from keystore', async () => { + // Remove the key from the keystore (we're using a mock storage in these tests) + const modifiedIdentity = new Identity('this id does not exist', identity.publicKey, '', identity.signatures, identity.type, identity.provider) + let signature + let err + try { + signature = await identity.provider.sign(modifiedIdentity, data, keystore) + } catch (e) { + err = e.toString() + } + assert.strictEqual(signature, undefined) + assert.strictEqual(err, 'Error: Private signing key not found from KeyStore') + }) + + describe('verify data signed by an identity', () => { + const data = 'hello friend' + let identity + let signature + + before(async () => { + identity = await IdentityProvider.createIdentity({ type, keystore }) + signature = await identity.provider.sign(identity, data, keystore) + }) + + it('verifies that the signature is valid', async () => { + const verified = await identity.provider.verify(signature, identity.publicKey, data) + assert.strictEqual(verified, true) + }) + + it('doesn\'t verify invalid signature', async () => { + const verified = await identity.provider.verify('invalid', identity.publicKey, data) + assert.strictEqual(verified, false) + }) + }) + }) +}) diff --git a/test/identities/fixtures/keys/000046.ldb b/test/identities/fixtures/keys/000046.ldb new file mode 100644 index 0000000..d9d3f43 Binary files /dev/null and b/test/identities/fixtures/keys/000046.ldb differ diff --git a/test/identities/fixtures/keys/000048.ldb b/test/identities/fixtures/keys/000048.ldb new file mode 100644 index 0000000..73519b8 Binary files /dev/null and b/test/identities/fixtures/keys/000048.ldb differ diff --git a/test/identities/fixtures/keys/000051.ldb b/test/identities/fixtures/keys/000051.ldb new file mode 100644 index 0000000..e03d0b5 Binary files /dev/null and b/test/identities/fixtures/keys/000051.ldb differ diff --git a/test/identities/fixtures/keys/000054.ldb b/test/identities/fixtures/keys/000054.ldb new file mode 100644 index 0000000..a6d12fc Binary files /dev/null and b/test/identities/fixtures/keys/000054.ldb differ diff --git a/test/identities/fixtures/keys/000055.log b/test/identities/fixtures/keys/000055.log new file mode 100644 index 0000000..e644280 Binary files /dev/null and b/test/identities/fixtures/keys/000055.log differ diff --git a/test/identities/fixtures/keys/0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa.json b/test/identities/fixtures/keys/0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa.json new file mode 100644 index 0000000..ec1dc2f --- /dev/null +++ b/test/identities/fixtures/keys/0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa.json @@ -0,0 +1 @@ +{"publicKey":"030d78ff62afb656ac62db1aae3b1536a614991e28bb4d721498898b7d41943396","privateKey":"6657f74ad8223ed8a169b12d4afbf1d368175e3f08b295d511131ee93c6cceeb"} \ No newline at end of file diff --git a/test/identities/fixtures/keys/CURRENT b/test/identities/fixtures/keys/CURRENT new file mode 100644 index 0000000..774dcea --- /dev/null +++ b/test/identities/fixtures/keys/CURRENT @@ -0,0 +1 @@ +MANIFEST-000053 diff --git a/test/identities/fixtures/keys/LOCK b/test/identities/fixtures/keys/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/test/identities/fixtures/keys/LOG b/test/identities/fixtures/keys/LOG new file mode 100644 index 0000000..601ea4c --- /dev/null +++ b/test/identities/fixtures/keys/LOG @@ -0,0 +1,5 @@ +2023/02/16-14:47:21.828619 170a2b000 Recovering log #52 +2023/02/16-14:47:21.828667 170a2b000 Level-0 table #54: started +2023/02/16-14:47:21.828864 170a2b000 Level-0 table #54: 1279 bytes OK +2023/02/16-14:47:21.829422 170a2b000 Delete type=0 #52 +2023/02/16-14:47:21.829490 170a2b000 Delete type=3 #50 diff --git a/test/identities/fixtures/keys/LOG.old b/test/identities/fixtures/keys/LOG.old new file mode 100644 index 0000000..559432b --- /dev/null +++ b/test/identities/fixtures/keys/LOG.old @@ -0,0 +1,5 @@ +2023/02/16-14:46:59.958708 16e843000 Recovering log #49 +2023/02/16-14:46:59.958747 16e843000 Level-0 table #51: started +2023/02/16-14:46:59.958926 16e843000 Level-0 table #51: 1279 bytes OK +2023/02/16-14:46:59.959275 16e843000 Delete type=3 #47 +2023/02/16-14:46:59.959309 16e843000 Delete type=0 #49 diff --git a/test/identities/fixtures/keys/MANIFEST-000053 b/test/identities/fixtures/keys/MANIFEST-000053 new file mode 100644 index 0000000..3dac6b6 Binary files /dev/null and b/test/identities/fixtures/keys/MANIFEST-000053 differ diff --git a/test/identities/fixtures/keys/QmPhnEjVkYE1Ym7F5MkRUfkD6NtuSptE7ugu1Ggr149W2X.json b/test/identities/fixtures/keys/QmPhnEjVkYE1Ym7F5MkRUfkD6NtuSptE7ugu1Ggr149W2X.json new file mode 100644 index 0000000..9235b03 --- /dev/null +++ b/test/identities/fixtures/keys/QmPhnEjVkYE1Ym7F5MkRUfkD6NtuSptE7ugu1Ggr149W2X.json @@ -0,0 +1 @@ +{"publicKey":"0260baeaffa1de1e4135e5b395e0380563a622b9599d1b8e012a0f7603f516bdaa","privateKey":"198594a8de39fd97017d11996d619b3746211605a9d290964badf58bc79bdb33"} \ No newline at end of file diff --git a/test/identities/fixtures/keys/existing.json b/test/identities/fixtures/keys/existing.json new file mode 100644 index 0000000..37d22f2 --- /dev/null +++ b/test/identities/fixtures/keys/existing.json @@ -0,0 +1 @@ +{"publicKey":"045756c20f03ec494d07e8dd8456f67d6bd97ca175e6c4882435fe364392f131406db3a37eebe1d634b105a57b55e4f17247c1ec8ffe04d6a95d1e0ee8bed7cfbd","privateKey":"3928a45bc5642ddef4938d30b899563e7e142b1947c3bd02c313621e311dd07e"} \ No newline at end of file diff --git a/test/identities/identity-provider.spec.js b/test/identities/identity-provider.spec.js new file mode 100644 index 0000000..f22f45c --- /dev/null +++ b/test/identities/identity-provider.spec.js @@ -0,0 +1,311 @@ +import assert from 'assert' +import path from 'path' +import rmrf from 'rimraf' +import { KeyStore, IdentityProvider } from '../../src/index.js' +import { Identity } from '../../src/identities/index.js' +import fs from 'fs-extra' +const fixturesPath = path.resolve('./test/identities/fixtures/keys') +const savedKeysPath = path.resolve('./test/identities/fixtures/savedKeys') +const signingKeysPath = path.resolve('./test/identities/signingKeys') +const identityKeysPath = path.resolve('./test/identities/identityKeys') +const type = 'orbitdb' + +describe('Identity Provider', function () { + before(async () => { + rmrf.sync(signingKeysPath) + rmrf.sync(identityKeysPath) + }) + + after(async () => { + rmrf.sync(signingKeysPath) + rmrf.sync(identityKeysPath) + }) + + describe('Creating IdentityProvider', () => { + const id = 'A' + let identity + + it('identityKeysPath only - has the correct id', async () => { + identity = await IdentityProvider.createIdentity({ id, identityKeysPath }) + const key = await identity.provider.keystore.getKey(id) + const externalId = Buffer.from(key.public.marshal()).toString('hex') + assert.strictEqual(identity.id, externalId) + }) + + it('identityKeysPath and signingKeysPath - has a different id', async () => { + identity = await IdentityProvider.createIdentity({ id, identityKeysPath, signingKeysPath }) + const key = await identity.provider.keystore.getKey(id) + const externalId = Buffer.from(key.public.marshal()).toString('hex') + assert.notStrictEqual(identity.id, externalId) + }) + + afterEach(async () => { + await identity.provider.keystore.close() + await identity.provider.signingKeyStore.close() + }) + }) + + describe('Passing in custom keystore', async () => { + const id = 'B'; let identity; let keystore; let signingKeyStore + + before(async () => { + keystore = new KeyStore(identityKeysPath) + await keystore.open() + signingKeyStore = new KeyStore(signingKeysPath) + await signingKeyStore.open() + }) + + it('has the correct id', async () => { + identity = await IdentityProvider.createIdentity({ id, keystore }) + keystore = identity.provider._keystore + const key = await keystore.getKey(id) + const externalId = Buffer.from(key.public.marshal()).toString('hex') + assert.strictEqual(identity.id, externalId) + }) + + + it('created a key for id in identity-keystore', async () => { + const key = await keystore.getKey(id) + assert.notStrictEqual(key, undefined) + }) + + it('has the correct public key', async () => { + const key = await keystore.getKey(id) + const externalId = Buffer.from(key.public.marshal()).toString('hex') + const signingKey = await keystore.getKey(externalId) + assert.notStrictEqual(signingKey, undefined) + assert.strictEqual(identity.publicKey, keystore.getPublic(signingKey)) + }) + + it('has a signature for the id', async () => { + const key = await keystore.getKey(id) + const externalId = Buffer.from(key.public.marshal()).toString('hex') + const signingKey = await keystore.getKey(externalId) + const idSignature = await keystore.sign(signingKey, externalId) + const publicKey = Buffer.from(signingKey.public.marshal()).toString('hex') + const verifies = await KeyStore.verify(idSignature, publicKey, externalId) + assert.strictEqual(verifies, true) + assert.strictEqual(identity.signatures.id, idSignature) + }) + + it('has a signature for the publicKey', async () => { + const key = await keystore.getKey(id) + const externalId = Buffer.from(key.public.marshal()).toString('hex') + const signingKey = await keystore.getKey(externalId) + const idSignature = await keystore.sign(signingKey, externalId) + const externalKey = await keystore.getKey(id) + const publicKeyAndIdSignature = await keystore.sign(externalKey, identity.publicKey + idSignature) + assert.strictEqual(identity.signatures.publicKey, publicKeyAndIdSignature) + }) + + after(async () => { + await keystore.close() + await signingKeyStore.close() + }) + }) + + describe('create an identity with saved keys', () => { + let keystore, signingKeyStore + + let savedKeysKeyStore, identity + const id = 'QmPhnEjVkYE1Ym7F5MkRUfkD6NtuSptE7ugu1Ggr149W2X' + + const expectedPublicKey = '040d78ff62afb656ac62db1aae3b1536a614991e28bb4d721498898b7d4194339640cd18c37b259e2c77738de0d6f9a5d52e0b936611de6b6ba78891a8b2a38317' + const expectedIdSignature = '30450221009de7b91952d73f577e85962aa6301350865212e3956862f80f4ebb626ffc126b022027d57415fb145b7e06cf06320fbfa63ea98a958b065726fe86eaab809a6bf607' + const expectedPkIdSignature = '304402202806e7c2406ca1f35961d38adc3997c179e142d54e1ca838ace373fae27124fd02200d6ca3aea6e1341bf5e4e0b84b559bbeefecfade34115de266a69d04d924905e' + + before(async () => { + keystore = new KeyStore(identityKeysPath) + await keystore.open() + signingKeyStore = new KeyStore(signingKeysPath) + await signingKeyStore.open() + + await fs.copy(fixturesPath, savedKeysPath) + savedKeysKeyStore = new KeyStore(savedKeysPath) + await savedKeysKeyStore.open() + identity = await IdentityProvider.createIdentity({ id, keystore: savedKeysKeyStore }) + }) + + after(async () => { + rmrf.sync(savedKeysPath) + }) + + it('has the correct id', async () => { + const key = await savedKeysKeyStore.getKey(id) + assert.strictEqual(identity.id, Buffer.from(key.public.marshal()).toString('hex')) + }) + + it('has the correct public key', async () => { + assert.strictEqual(identity.publicKey, expectedPublicKey) + }) + + it('has the correct identity type', async () => { + assert.strictEqual(identity.type, type) + }) + + it('has the correct idSignature', async () => { + assert.strictEqual(identity.signatures.id, expectedIdSignature) + }) + + it('has a pubKeyIdSignature for the publicKey', async () => { + assert.strictEqual(identity.signatures.publicKey, expectedPkIdSignature) + }) + + it('has the correct signatures', async () => { + const internalSigningKey = await savedKeysKeyStore.getKey(identity.id) + const externalSigningKey = await savedKeysKeyStore.getKey(id) + const idSignature = await savedKeysKeyStore.sign(internalSigningKey, identity.id) + const pubKeyIdSignature = await savedKeysKeyStore.sign(externalSigningKey, identity.publicKey + idSignature) + const expectedSignature = { id: idSignature, publicKey: pubKeyIdSignature } + assert.deepStrictEqual(identity.signatures, expectedSignature) + }) + + after(async () => { + await keystore.close() + await signingKeyStore.close() + }) + }) + + describe('verify identity\'s signature', () => { + const id = 'QmFoo' + let identity, keystore, signingKeyStore + + before(async () => { + keystore = new KeyStore(identityKeysPath) + await keystore.open() + signingKeyStore = new KeyStore(signingKeysPath) + await signingKeyStore.open() + }) + + it('identity pkSignature verifies', async () => { + identity = await IdentityProvider.createIdentity({ id, type, keystore, signingKeyStore }) + const verified = await KeyStore.verify(identity.signatures.id, identity.publicKey, identity.id) + assert.strictEqual(verified, true) + }) + + it('identity signature verifies', async () => { + identity = await IdentityProvider.createIdentity({ id, type, keystore, signingKeyStore }) + const verified = await KeyStore.verify(identity.signatures.publicKey, identity.id, identity.publicKey + identity.signatures.id) + assert.strictEqual(verified, true) + }) + + it('false signature doesn\'t verify', async () => { + class IP { + async getId () { return 'pubKey' } + + async signIdentity (data) { return `false signature '${data}'` } + + static async verifyIdentity (data) { return false } + + static get type () { return 'fake' } + } + + IdentityProvider.addIdentityProvider(IP) + identity = await IdentityProvider.createIdentity({ type: IP.type, keystore, signingKeyStore }) + const verified = await IdentityProvider.verifyIdentity(identity) + assert.strictEqual(verified, false) + }) + + after(async () => { + await keystore.close() + await signingKeyStore.close() + }) + }) + + describe('verify identity', () => { + const id = 'QmFoo' + let identity, keystore, signingKeyStore + + before(async () => { + keystore = new KeyStore(identityKeysPath) + await keystore.open() + signingKeyStore = new KeyStore(signingKeysPath) + await signingKeyStore.open() + }) + + it('identity verifies', async () => { + identity = await IdentityProvider.createIdentity({ id, type, keystore, signingKeyStore }) + const verified = await identity.provider.verifyIdentity(identity) + assert.strictEqual(verified, true) + }) + + after(async () => { + await keystore.close() + await signingKeyStore.close() + }) + }) + + describe('sign data with an identity', () => { + const id = '0x01234567890abcdefghijklmnopqrstuvwxyz' + const data = 'hello friend' + let identity, keystore, signingKeyStore + + before(async () => { + keystore = new KeyStore(identityKeysPath) + await keystore.open() + signingKeyStore = new KeyStore(signingKeysPath) + await signingKeyStore.open() + identity = await IdentityProvider.createIdentity({ id, keystore, signingKeyStore }) + }) + + it('sign data', async () => { + const signingKey = await keystore.getKey(identity.id) + const expectedSignature = await keystore.sign(signingKey, data) + const signature = await identity.provider.sign(identity, data, keystore) + assert.strictEqual(signature, expectedSignature) + }) + + it('throws an error if private key is not found from keystore', async () => { + // Remove the key from the keystore (we're using a mock storage in these tests) + const modifiedIdentity = new Identity('this id does not exist', identity.publicKey, '', identity.signatures, identity.type, identity.provider) + let signature + let err + try { + signature = await identity.provider.sign(modifiedIdentity, data, keystore) + } catch (e) { + err = e.toString() + } + assert.strictEqual(signature, undefined) + assert.strictEqual(err, 'Error: Private signing key not found from KeyStore') + }) + + after(async () => { + await keystore.close() + await signingKeyStore.close() + }) + }) + + describe('verify data signed by an identity', () => { + const id = '03602a3da3eb35f1148e8028f141ec415ef7f6d4103443edbfec2a0711d716f53f' + const data = 'hello friend' + let identity, keystore, signingKeyStore + let signature + + before(async () => { + keystore = new KeyStore(identityKeysPath) + await keystore.open() + signingKeyStore = new KeyStore(signingKeysPath) + await signingKeyStore.open() + }) + + beforeEach(async () => { + identity = await IdentityProvider.createIdentity({ id, type, keystore, signingKeyStore }) + signature = await identity.provider.sign(identity, data, keystore) + }) + + it('verifies that the signature is valid', async () => { + const verified = await identity.provider.verify(signature, identity.publicKey, data) + assert.strictEqual(verified, true) + }) + + it('doesn\'t verify invalid signature', async () => { + const verified = await identity.provider.verify('invalid', identity.publicKey, data) + assert.strictEqual(verified, false) + }) + + after(async () => { + await keystore.close() + await signingKeyStore.close() + }) + }) +}) diff --git a/test/identities/identity.spec.js b/test/identities/identity.spec.js new file mode 100644 index 0000000..0d03e16 --- /dev/null +++ b/test/identities/identity.spec.js @@ -0,0 +1,109 @@ +import assert from 'assert' +import { Identity } from '../../src/identities/index.js' + +describe('Identity', function () { + const id = '0x01234567890abcdefghijklmnopqrstuvwxyz' + const publicKey = '' + const idSignature = 'signature for ' + const publicKeyAndIdSignature = 'signature for ' + const type = 'orbitdb' + const provider = 'IdentityProviderInstance' + + let identity + + before(async () => { + identity = new Identity(id, publicKey, idSignature, publicKeyAndIdSignature, type, provider) + }) + + it('has the correct id', async () => { + assert.strictEqual(identity.id, id) + }) + + it('has the correct publicKey', async () => { + assert.strictEqual(identity.publicKey, publicKey) + }) + + it('has the correct idSignature', async () => { + assert.strictEqual(identity.signatures.id, idSignature) + }) + + it('has the correct publicKeyAndIdSignature', async () => { + assert.strictEqual(identity.signatures.publicKey, publicKeyAndIdSignature) + }) + + it('has the correct provider', async () => { + assert.deepStrictEqual(identity.provider, provider) + }) + + it('converts identity to a JSON object', async () => { + const expected = { + id, + publicKey, + signatures: { id: idSignature, publicKey: publicKeyAndIdSignature }, + type + } + assert.deepStrictEqual(identity.toJSON(), expected) + }) + + describe('Constructor inputs', () => { + it('throws and error if id was not given in constructor', async () => { + let err + try { + identity = new Identity() + } catch (e) { + err = e.toString() + } + assert.strictEqual(err, 'Error: Identity id is required') + }) + + it('throws and error if publicKey was not given in constructor', async () => { + let err + try { + identity = new Identity('abc') + } catch (e) { + err = e.toString() + } + assert.strictEqual(err, 'Error: Invalid public key') + }) + + it('throws and error if identity signature was not given in constructor', async () => { + let err + try { + identity = new Identity('abc', publicKey) + } catch (e) { + err = e.toString() + } + assert.strictEqual(err, 'Error: Signature of the id (idSignature) is required') + }) + + it('throws and error if identity signature was not given in constructor', async () => { + let err + try { + identity = new Identity('abc', publicKey, idSignature) + } catch (e) { + err = e.toString() + } + assert.strictEqual(err, 'Error: Signature of (publicKey + idSignature) is required') + }) + + it('throws and error if identity provider was not given in constructor', async () => { + let err + try { + identity = new Identity('abc', publicKey, idSignature, publicKeyAndIdSignature, type) + } catch (e) { + err = e.toString() + } + assert.strictEqual(err, 'Error: Identity provider is required') + }) + + it('throws and error if identity type was not given in constructor', async () => { + let err + try { + identity = new Identity('abc', publicKey, idSignature, publicKeyAndIdSignature, null, provider) + } catch (e) { + err = e.toString() + } + assert.strictEqual(err, 'Error: Identity type is required') + }) + }) +}) diff --git a/test/keyvalue.spec.js b/test/keyvalue.spec.js index 9267f61..70e98a2 100644 --- a/test/keyvalue.spec.js +++ b/test/keyvalue.spec.js @@ -1,7 +1,7 @@ import { deepStrictEqual, strictEqual } from 'assert' import rimraf from 'rimraf' import { Log, Entry } from '../src/oplog/index.js' -import IdentityProvider from '../src/identities/index.js' +import { IdentityProvider } from '../src/identities/index.js' import KeyStore from '../src/key-store.js' import { KeyValue, KeyValuePersisted, Database } from '../src/db/index.js' import { IPFSBlockStorage, LevelStorage } from '../src/storage/index.js' diff --git a/test/oplog/append.test.js b/test/oplog/append.test.js index 4e3b767..7c59a69 100644 --- a/test/oplog/append.test.js +++ b/test/oplog/append.test.js @@ -3,7 +3,7 @@ import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log } from '../../src/oplog/index.js' import MemoryStorage from '../../src/storage/memory.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' // Test utils diff --git a/test/oplog/crdt.test.js b/test/oplog/crdt.test.js index 8bf6a41..6a9d1bb 100644 --- a/test/oplog/crdt.test.js +++ b/test/oplog/crdt.test.js @@ -2,7 +2,7 @@ import { strictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/entry.test.js b/test/oplog/entry.test.js index dd4e603..2356d87 100644 --- a/test/oplog/entry.test.js +++ b/test/oplog/entry.test.js @@ -2,7 +2,7 @@ import { strictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Entry } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import { config, testAPIs, startIpfs, stopIpfs } from 'orbit-db-test-utils' // import IdentityStorage from '../src/identity-storage.js' diff --git a/test/oplog/heads.test.js b/test/oplog/heads.test.js index 40830b4..179e643 100644 --- a/test/oplog/heads.test.js +++ b/test/oplog/heads.test.js @@ -2,7 +2,7 @@ import { strictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/iterator.test.js b/test/oplog/iterator.test.js index ce3951d..9b8ce11 100644 --- a/test/oplog/iterator.test.js +++ b/test/oplog/iterator.test.js @@ -1,7 +1,7 @@ import { strictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { Log } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import LogCreator from './utils/log-creator.js' import all from 'it-all' diff --git a/test/oplog/join-concurrent.test.js b/test/oplog/join-concurrent.test.js index 9436ce2..794707a 100644 --- a/test/oplog/join-concurrent.test.js +++ b/test/oplog/join-concurrent.test.js @@ -2,7 +2,7 @@ import { strictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/join.test.js b/test/oplog/join.test.js index 7870e52..43bfc15 100644 --- a/test/oplog/join.test.js +++ b/test/oplog/join.test.js @@ -1,7 +1,7 @@ import { strictEqual, notStrictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { Log, Clock } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/load.test.js b/test/oplog/load.test.js index e83effa..3d850a0 100644 --- a/test/oplog/load.test.js +++ b/test/oplog/load.test.js @@ -3,7 +3,7 @@ import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log, Entry, Sorting } from '../../src/oplog/index.js' import bigLogString from '../fixtures/big-log.fixture.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import LogCreator from './utils/log-creator.js' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/log.test.js b/test/oplog/log.test.js index 3a5915b..928b148 100644 --- a/test/oplog/log.test.js +++ b/test/oplog/log.test.js @@ -1,7 +1,7 @@ import { notStrictEqual, deepStrictEqual, strictEqual } from 'assert' import rimraf from 'rimraf' import { Log, Entry } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import { copy } from 'fs-extra' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/references.test.js b/test/oplog/references.test.js index ca19cd7..2851e3a 100644 --- a/test/oplog/references.test.js +++ b/test/oplog/references.test.js @@ -2,7 +2,7 @@ import { strictEqual } from 'assert' import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' import MemoryStorage from '../../src/storage/memory.js' diff --git a/test/oplog/replicate.test.js b/test/oplog/replicate.test.js index 1d4f352..d28ed5f 100644 --- a/test/oplog/replicate.test.js +++ b/test/oplog/replicate.test.js @@ -3,7 +3,7 @@ import rimraf from 'rimraf' import { copy } from 'fs-extra' import { Log, Entry } from '../../src/index.js' import { MemoryStorage, IPFSBlockStorage } from '../../src/storage/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' // Test utils diff --git a/test/oplog/signed-log.test.js b/test/oplog/signed-log.test.js index 516c591..a69654d 100644 --- a/test/oplog/signed-log.test.js +++ b/test/oplog/signed-log.test.js @@ -1,7 +1,7 @@ import { notStrictEqual, strictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import { Log } from '../../src/oplog/index.js' -import IdentityProvider from '../../src/identities/index.js' +import { IdentityProvider } from '../../src/identities/index.js' import KeyStore from '../../src/key-store.js' // Test utils diff --git a/test/storage.spec.js b/test/storage.spec.js index b06d6b3..bb163b4 100644 --- a/test/storage.spec.js +++ b/test/storage.spec.js @@ -2,7 +2,7 @@ import * as IPFS from 'ipfs' import { strictEqual, notStrictEqual } from 'assert' import rimraf from 'rimraf' import { Log } from '../src/oplog/index.js' -import IdentityProvider from '../src/identities/index.js' +import { IdentityProvider } from '../src/identities/index.js' import KeyStore from '../src/key-store.js' import { IPFSBlockStorage, MemoryStorage, LRUStorage, ComposedStorage } from '../src/storage/index.js' import { copy } from 'fs-extra'