Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(key-manager): add method to compute a shared secret #555

Merged
merged 2 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions __tests__/shared/keyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,37 +200,48 @@ export default (testContext: {
expect(typeof rawTx).toEqual('string')
})

// it.todo('Should Encrypt/Decrypt')
it('Should Encrypt/Decrypt', async () => {
const message = 'foo bar'

const senderKey = await agent.keyManagerCreate({
kms: 'local',
type: 'Ed25519',
})

const recipientKey = await agent.keyManagerCreate({
kms: 'local',
type: 'Ed25519',
})

const encrypted = await agent.keyManagerEncryptJWE({
kid: senderKey.kid,
to: recipientKey,
data: message,
})

expect(typeof encrypted).toEqual('string')

const decrypted = await agent.keyManagerDecryptJWE({
kid: recipientKey.kid,
data: encrypted,
})

expect(decrypted).toEqual(message)
})

describe('using Secp256k1 testvectors', () => {
it('Should compute sharedSecret', async () => {
const kmsArray = await agent.keyManagerGetKeyManagementSystems()
const kms = kmsArray[0] || 'local'
await agent.keyManagerImport({
type: 'X25519',
kid: 'senderKey1',
publicKeyHex: 'c4f35b52cc5309e70a1954b00e757d2b134f3cde9beb8179312b5ce4198a1379',
privateKeyHex: 'c796444e0ee9ec8e1c57ae1334a5900c287426fa5177aa093ed9199573e34aca',
kms,
})
const receiverKey = {
type: <TKeyType>'X25519',
publicKeyHex: 'c1d9ca35bd2c86ad0d61f682c30b24c73045a96773d82ff3b21ebadf85c39244',
}
const secret = await agent.keyManagerSharedSecret({ secretKeyRef: 'senderKey1', publicKey: receiverKey })
expect(secret).toEqual('ee94c7fcf5298291029a3c3d59a8a05367a1806f36668a1f67f5ea8149097476')
})

describe('using Secp256k1 test vectors', () => {
const importedKey = {
kid: '04155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c7037e2bd897812170c92a4c978d6a10481491a37299d74c4bd412a111a4ac875',
kms: 'local',
Expand Down Expand Up @@ -304,7 +315,7 @@ export default (testContext: {
})
})

describe('using Ed25519 testvectors', () => {
describe('using Ed25519 test vectors', () => {
const importedKey = {
kid: 'ea75250531f6834328ac210618253288e4c54632962a9708ca82e4a399f79000',
kms: 'local',
Expand Down
41 changes: 41 additions & 0 deletions packages/core/plugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,38 @@
],
"description": "Input arguments for {@link IKeyManager.keyManagerGet | keyManagerGet }"
},
"IKeyManagerSharedSecretArgs": {
"type": "object",
"properties": {
"secretKeyRef": {
"type": "string",
"description": "The secret key handle (`kid`) as returned by {@link IKeyManager.keyManagerCreate | keyManagerCreate }"
},
"publicKey": {
"type": "object",
"properties": {
"publicKeyHex": {
"type": "string",
"description": "Public key"
},
"type": {
"$ref": "#/components/schemas/TKeyType",
"description": "Key type"
}
},
"required": [
"publicKeyHex",
"type"
],
"description": "The public key of the other party. The `type` of key MUST be compatible with the type referenced by `secretKeyRef`"
}
},
"required": [
"secretKeyRef",
"publicKey"
],
"description": "Input arguments for {@link IKeyManager.keyManagerSharedSecret | keyManagerSharedSecret }"
},
"IKeyManagerSignArgs": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -706,6 +738,15 @@
"type": "boolean"
}
},
"keyManagerSharedSecret": {
"description": "Compute a shared secret with the public key of another party.",
"arguments": {
"$ref": "#/components/schemas/IKeyManagerSharedSecretArgs"
},
"returnType": {
"type": "string"
}
},
"keyManagerSign": {
"description": "Generates a signature according to the algorithm specified.",
"arguments": {
Expand Down
31 changes: 30 additions & 1 deletion packages/core/src/types/IKeyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export interface IKeyManagerSignArgs {
/**
* The algorithm to use for signing.
* This must be one of the algorithms supported by the KMS for this key type.
*
*
* The algorithm used here should match one of the names listed in `IKey.meta.algorithms`
*/
algorithm?: string
Expand All @@ -112,6 +112,24 @@ export interface IKeyManagerSignArgs {
[x: string]: any
}

/**
* Input arguments for {@link IKeyManager.keyManagerSharedSecret | keyManagerSharedSecret}
* @public
*/
export interface IKeyManagerSharedSecretArgs {
/**
* The secret key handle (`kid`)
* as returned by {@link IKeyManager.keyManagerCreate | keyManagerCreate}
*/
secretKeyRef: string,

/**
* The public key of the other party.
* The `type` of key MUST be compatible with the type referenced by `secretKeyRef`
*/
publicKey: Pick<IKey, 'publicKeyHex' | 'type'>
}

/**
* Input arguments for {@link IKeyManager.keyManagerSignJWT | keyManagerSignJWT}
* @public
Expand Down Expand Up @@ -181,6 +199,17 @@ export interface IKeyManager extends IPluginMethodMap {
*/
keyManagerSign(args: IKeyManagerSignArgs): Promise<string>

/**
* Compute a shared secret with the public key of another party.
*
* This computes the raw shared secret (the result of a Diffie-Hellman computation)
* To use this for symmetric encryption you MUST apply a KDF on the result.
*
* @param args {@link IKeyManagerSharedKeyArgs}
* @returns a `Promise` that resolves to a hex encoded shared secret
*/
keyManagerSharedSecret(args: IKeyManagerSharedSecretArgs): Promise<string>

/**
* Encrypts data
* @beta
Expand Down
2 changes: 1 addition & 1 deletion packages/key-manager/src/__tests__/default.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
describe('did-manager', () => {
describe('key-manager', () => {
const a = 100
it('should run a dummy test', () => {
expect(a).toEqual(100)
Expand Down
2 changes: 2 additions & 0 deletions packages/key-manager/src/abstract-key-management-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ export abstract class AbstractKeyManagementSystem {
}

abstract sign(args: { key: IKey; algorithm?: string; data: Uint8Array; [x: string]: any }): Promise<string>

abstract sharedSecret(args: { myKey: IKey; theirKey: Pick<IKey, 'publicKeyHex' | 'type'> }): Promise<string>
}
20 changes: 19 additions & 1 deletion packages/key-manager/src/key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IKeyManagerSignEthTXArgs,
schema,
IKeyManagerSignArgs,
IKeyManagerSharedSecretArgs,
} from '@veramo/core'
import * as u8a from 'uint8arrays'

Expand Down Expand Up @@ -46,6 +47,7 @@ export class KeyManager implements IAgentPlugin {
keyManagerSignJWT: this.keyManagerSignJWT.bind(this),
keyManagerSignEthTX: this.keyManagerSignEthTX.bind(this),
keyManagerSign: this.keyManagerSign.bind(this),
keyManagerSharedSecret: this.keyManagerSharedSecret.bind(this),
}
}

Expand All @@ -66,7 +68,7 @@ export class KeyManager implements IAgentPlugin {
const partialKey = await kms.createKey({ type: args.type, meta: args.meta })
const key: IKey = { ...partialKey, kms: args.kms }
if (args.meta || key.meta) {
key.meta = {...args.meta, ...key.meta}
key.meta = { ...args.meta, ...key.meta }
}
await this.store.import(key)
if (key.privateKeyHex) {
Expand Down Expand Up @@ -140,4 +142,20 @@ export class KeyManager implements IAgentPlugin {
const kms = this.getKms(key.kms)
return kms.signEthTX({ key, transaction })
}

/** {@inheritDoc @veramo/core#IKeyManager.keyManagerSharedKey} */
async keyManagerSharedSecret(args: IKeyManagerSharedSecretArgs): Promise<string> {
const { secretKeyRef, publicKey } = args
const myKey = await this.store.get({ kid: secretKeyRef })
const theirKey = publicKey
if (
myKey.type === theirKey.type ||
(['Ed25519', 'X25519'].includes(myKey.type) && ['Ed25519', 'X25519'].includes(theirKey.type))
) {
} else {
throw new Error('invalid_argument: the key types have to match to be able to compute a shared secret')
}
const kms = this.getKms(myKey.kms)
return kms.sharedSecret({ myKey, theirKey })
}
}
100 changes: 100 additions & 0 deletions packages/kms-local/src/__tests__/kms-local.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { KeyManagementSystem } from '../key-management-system'
import { IKey, TKeyType } from '@veramo/core'

describe('@veramo/kms-local', () => {
it('should compute a shared secret Ed+Ed', async () => {
const kms = new KeyManagementSystem()
const myKey = {
type: 'Ed25519',
privateKeyHex:
'ed3991fa33d4df22c88b78249e4d73c509c640a873a66808ad5dce774334ce94ee5072bc20355b4cd5499e04ee70853591bffa1874b1b5467dedd648d5b89ecb',
} as IKey
const theirKey = {
type: <TKeyType>'Ed25519',
publicKeyHex: 'e1d1dc2afe59bb054c44ba23ba07561d15ba83f9d1c42568ac11351fbdfd87c6',
}

const secret = await kms.sharedSecret({ myKey, theirKey })
expect(secret).toEqual('2f1d171ad32fdbd10d1b06600d70223f7298809d4a3690fa03d6b4688c7b116a')
})

it('should compute a shared secret Ed+X', async () => {
const kms = new KeyManagementSystem()
const myKey = {
type: 'Ed25519',
privateKeyHex:
'ed3991fa33d4df22c88b78249e4d73c509c640a873a66808ad5dce774334ce94ee5072bc20355b4cd5499e04ee70853591bffa1874b1b5467dedd648d5b89ecb',
} as IKey
const theirKey = {
type: <TKeyType>'X25519',
publicKeyHex: '09c99ad2fdb13247d97f4343d05cc20930db0808697e89f8f3d111a40cb6ee35',
}

const secret = await kms.sharedSecret({ myKey, theirKey })
expect(secret).toEqual('2f1d171ad32fdbd10d1b06600d70223f7298809d4a3690fa03d6b4688c7b116a')
})

it('should compute a shared secret X+Ed', async () => {
const kms = new KeyManagementSystem()
const myKey = {
type: 'X25519',
privateKeyHex: '704380837434dde8a41bebcb75494578bf243fa19cd59e120a1de84e0815c84d',
} as IKey

const theirKey = {
type: <TKeyType>'Ed25519',
publicKeyHex: 'e1d1dc2afe59bb054c44ba23ba07561d15ba83f9d1c42568ac11351fbdfd87c6',
}

const secret = await kms.sharedSecret({ myKey, theirKey })
expect(secret).toEqual('2f1d171ad32fdbd10d1b06600d70223f7298809d4a3690fa03d6b4688c7b116a')
})

it('should compute a shared secret X+X', async () => {
const kms = new KeyManagementSystem()
const myKey = {
type: 'X25519',
privateKeyHex: '704380837434dde8a41bebcb75494578bf243fa19cd59e120a1de84e0815c84d',
} as IKey

const theirKey = {
type: <TKeyType>'X25519',
publicKeyHex: '09c99ad2fdb13247d97f4343d05cc20930db0808697e89f8f3d111a40cb6ee35',
}

const secret = await kms.sharedSecret({ myKey, theirKey })
expect(secret).toEqual('2f1d171ad32fdbd10d1b06600d70223f7298809d4a3690fa03d6b4688c7b116a')
})

it('should throw on invalid myKey type', async () => {
expect.assertions(1)
const kms = new KeyManagementSystem()
const myKey = {
type: 'Secp256k1',
privateKeyHex: '704380837434dde8a41bebcb75494578bf243fa19cd59e120a1de84e0815c84d',
} as IKey

const theirKey = {
type: <TKeyType>'X25519',
publicKeyHex: '09c99ad2fdb13247d97f4343d05cc20930db0808697e89f8f3d111a40cb6ee35',
}

expect(kms.sharedSecret({ myKey, theirKey })).rejects.toThrow('not_supported')
})

it('should throw on invalid theirKey type', async () => {
expect.assertions(1)
const kms = new KeyManagementSystem()
const myKey = {
type: 'X25519',
privateKeyHex: '704380837434dde8a41bebcb75494578bf243fa19cd59e120a1de84e0815c84d',
} as IKey

const theirKey = {
type: <TKeyType>'Secp256k1',
publicKeyHex: '09c99ad2fdb13247d97f4343d05cc20930db0808697e89f8f3d111a40cb6ee35',
}

expect(kms.sharedSecret({ myKey, theirKey })).rejects.toThrow('not_supported')
})
})
24 changes: 21 additions & 3 deletions packages/kms-local/src/key-management-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import {
JWE,
} from 'did-jwt'
import { generateKeyPair, convertPublicKeyToX25519, convertSecretKeyToX25519 } from '@stablelib/ed25519'
import { generateKeyPair as generateEncryptionKeypair } from '@stablelib/x25519'
import { generateKeyPair as generateEncryptionKeypair, sharedKey } from '@stablelib/x25519'
import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer'
import { TransactionRequest } from '@ethersproject/abstract-provider'
import { toUtf8Bytes, toUtf8String } from '@ethersproject/strings'
import { parse } from '@ethersproject/transactions'
import { Wallet } from '@ethersproject/wallet'
import { SigningKey } from '@ethersproject/signing-key'
import { randomBytes } from "@ethersproject/random";
import { randomBytes } from '@ethersproject/random'
import Debug from 'debug'
import { arrayify } from '@ethersproject/bytes'
import { arrayify, hexlify } from '@ethersproject/bytes'
const debug = Debug('veramo:kms:local')

export class KeyManagementSystem extends AbstractKeyManagementSystem {
Expand Down Expand Up @@ -145,6 +145,24 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem {
throw Error(`not_supported: Cannot sign ${algorithm} using key of type ${key.type}`)
}

async sharedSecret(args: { myKey: IKey; theirKey: Pick<IKey, 'type' | 'publicKeyHex'> }): Promise<string> {
const { myKey, theirKey } = args
let myKeyBytes = arrayify('0x' + myKey.privateKeyHex)
if (myKey.type === 'Ed25519') {
myKeyBytes = convertSecretKeyToX25519(myKeyBytes)
} else if (myKey.type !== 'X25519') {
throw new Error(`not_supported: can't compute shared secret for type=${myKey.type}`)
}
let theirKeyBytes = arrayify('0x' + theirKey.publicKeyHex)
if (theirKey.type === 'Ed25519') {
theirKeyBytes = convertPublicKeyToX25519(theirKeyBytes)
} else if (theirKey.type !== 'X25519') {
throw new Error(`not_supported: can't compute shared secret for type=${theirKey.type}`)
}
const shared = sharedKey(myKeyBytes, theirKeyBytes)
return hexlify(shared).substring(2)
}

/**
* @returns a `0x` prefixed hex string representing the signed EIP712 data
*/
Expand Down