Skip to content

Commit

Permalink
feat(key-manager): implement JWE functionality directly in `key-manag…
Browse files Browse the repository at this point in the history
…er` (#557)

The KMS is only used to compute shared secret, it doesn't need to deal with JWE directly.
This is now the responsibility of `@veramo/key-manager`
fixes #556
  • Loading branch information
mirceanis authored Jun 11, 2021
1 parent 393c316 commit a030f0a
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 61 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/types/IKeyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,11 @@ export interface IKeyManager extends IPluginMethodMap {

/**
* 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}
*
* @param args {@link IKeyManagerSharedKeyArgs}
* @returns a `Promise` that resolves to a hex encoded shared secret
*/
keyManagerSharedSecret(args: IKeyManagerSharedSecretArgs): Promise<string>
Expand Down
4 changes: 1 addition & 3 deletions packages/key-manager/src/abstract-key-management-system.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { IKey, TKeyType } from '@veramo/core'
import { arrayify } from '@ethersproject/bytes'
import { arrayify, hexlify } from '@ethersproject/bytes'
import { serialize, Transaction } from '@ethersproject/transactions'
import * as u8a from 'uint8arrays'

export abstract class AbstractKeyManagementSystem {
abstract createKey(args: { type: TKeyType; meta?: any }): Promise<Omit<IKey, 'kms'>>
abstract deleteKey(args: { kid: string }): Promise<boolean>
abstract encryptJWE(args: { key: IKey; to: Omit<IKey, 'kms'>; data: string }): Promise<string>
abstract decryptJWE(args: { key: IKey; data: string }): Promise<string>

/**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */
async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise<string> {
Expand Down
49 changes: 43 additions & 6 deletions packages/key-manager/src/key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ import {
schema,
IKeyManagerSignArgs,
IKeyManagerSharedSecretArgs,
TKeyType,
} from '@veramo/core'
import * as u8a from 'uint8arrays'
import { JWE, createAnonDecrypter, createAnonEncrypter, createJWE, decryptJWE, ECDH } from 'did-jwt'
import { arrayify, hexlify } from '@ethersproject/bytes'
import { toUtf8String, toUtf8Bytes } from '@ethersproject/strings'
import { convertPublicKeyToX25519 } from '@stablelib/ed25519'

/**
* Agent plugin that provides {@link @veramo/core#IKeyManager} methods
Expand Down Expand Up @@ -98,16 +103,37 @@ export class KeyManager implements IAgentPlugin {

/** {@inheritDoc @veramo/core#IKeyManager.keyManagerEncryptJWE} */
async keyManagerEncryptJWE({ kid, to, data }: IKeyManagerEncryptJWEArgs): Promise<string> {
const key = await this.store.get({ kid })
const kms = this.getKms(key.kms)
return kms.encryptJWE({ key, to, data })
// TODO: if a sender `key` is provided, then it should be used to create an authenticated encrypter
// const key = await this.store.get({ kid })

let recipientPublicKey: Uint8Array
if (to.type === 'Ed25519') {
recipientPublicKey = arrayify('0x' + to.publicKeyHex)
recipientPublicKey = convertPublicKeyToX25519(recipientPublicKey)
} else if (to.type === 'X25519') {
recipientPublicKey = arrayify('0x' + to.publicKeyHex)
} else {
throw new Error('not_supported: The recipient public key type is not supported')
}

const dataBytes = toUtf8Bytes(data)
const encrypter = createAnonEncrypter(recipientPublicKey)
const result: JWE = await createJWE(dataBytes, [encrypter])

return JSON.stringify(result)
}

/** {@inheritDoc @veramo/core#IKeyManager.keyManagerDecryptJWE} */
async keyManagerDecryptJWE({ kid, data }: IKeyManagerDecryptJWEArgs): Promise<string> {
const key = await this.store.get({ kid })
const kms = this.getKms(key.kms)
return kms.decryptJWE({ key, data })
const jwe: JWE = JSON.parse(data)
const ecdh = this.createX25519ECDH(kid)

// TODO: figure out if the JWE is anon or not to determine the type of decrypter to use
const decrypter = createAnonDecrypter(ecdh)

const decrypted = await decryptJWE(jwe, decrypter)
const result = toUtf8String(decrypted)
return result
}

/** {@inheritDoc @veramo/core#IKeyManager.keyManagerSignJWT} */
Expand Down Expand Up @@ -158,4 +184,15 @@ export class KeyManager implements IAgentPlugin {
const kms = this.getKms(myKey.kms)
return kms.sharedSecret({ myKey, theirKey })
}

createX25519ECDH(secretKeyRef: string): ECDH {
return async (theirPublicKey: Uint8Array): Promise<Uint8Array> => {
if (theirPublicKey.length !== 32) {
throw new Error('invalid_argument: incorrect publicKey key length for X25519')
}
const publicKey = { type: <TKeyType>'X25519', publicKeyHex: hexlify(theirPublicKey).substring(2) }
const shared = await this.keyManagerSharedSecret({ secretKeyRef, publicKey })
return arrayify('0x' + shared)
}
}
}
51 changes: 2 additions & 49 deletions packages/kms-local/src/key-management-system.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { TKeyType, IKey } from '@veramo/core'
import { AbstractKeyManagementSystem } from '@veramo/key-manager'
import {
createAnonDecrypter,
createAnonEncrypter,
createJWE,
decryptJWE,
EdDSASigner,
ES256KSigner,
JWE,
} from 'did-jwt'
import { EdDSASigner, ES256KSigner } from 'did-jwt'
import { generateKeyPair, convertPublicKeyToX25519, convertSecretKeyToX25519 } from '@stablelib/ed25519'
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 { toUtf8String } from '@ethersproject/strings'
import { parse } from '@ethersproject/transactions'
import { Wallet } from '@ethersproject/wallet'
import { SigningKey } from '@ethersproject/signing-key'
Expand Down Expand Up @@ -83,45 +75,6 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem {
return true
}

async encryptJWE({ key, to, data }: { key: IKey; to: IKey; data: string }): Promise<string> {
let recipientPublicKey: Uint8Array
if (to.type === 'Ed25519') {
recipientPublicKey = arrayify('0x' + to.publicKeyHex)
recipientPublicKey = convertPublicKeyToX25519(recipientPublicKey)
} else if (to.type === 'X25519') {
recipientPublicKey = arrayify('0x' + to.publicKeyHex)
} else {
throw new Error('not_supported: The recipient public key type is not supported')
}

const dataBytes = toUtf8Bytes(data)
const encrypter = createAnonEncrypter(recipientPublicKey)
const result: JWE = await createJWE(dataBytes, [encrypter])

return JSON.stringify(result)
}

async decryptJWE({ key, data }: { key: IKey; data: string }): Promise<string> {
if (!key.privateKeyHex) throw Error('No private key')

let secretKey: Uint8Array
if (key.type === 'Ed25519') {
secretKey = arrayify('0x' + key.privateKeyHex)
secretKey = convertSecretKeyToX25519(secretKey)
} else if (key.type === 'X25519') {
secretKey = arrayify('0x' + key.privateKeyHex)
} else {
throw new Error('not_supported: The recipient public key type is not supported')
}

const jwe: JWE = JSON.parse(data)
const decrypter = createAnonDecrypter(secretKey)

const decrypted = await decryptJWE(jwe, decrypter)
const result = toUtf8String(decrypted)
return result
}

async sign({ key, algorithm, data }: { key: IKey; algorithm?: string; data: Uint8Array }): Promise<string> {
//FIXME: KMS implementation should not rely on private keys being provided, but rather manage their own keys
if (!key.privateKeyHex) throw Error('No private key for kid: ' + key.kid)
Expand Down

0 comments on commit a030f0a

Please sign in to comment.