Skip to content

Commit

Permalink
feat(key-manager): add method to compute a shared secret (#555)
Browse files Browse the repository at this point in the history
* feat(key-manager): add method to compute a shared secret

fixes #541
  • Loading branch information
mirceanis authored Jun 10, 2021
1 parent 1fe7f01 commit 393c316
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 15 deletions.
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

0 comments on commit 393c316

Please sign in to comment.