Skip to content

Commit

Permalink
feat: add support for NIST Secp256r1 keys and ES256 signatures (#1039)
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp authored Oct 24, 2022
1 parent 6fa13ab commit 61eb369
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 5 deletions.
5 changes: 4 additions & 1 deletion packages/core/plugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@
"enum": [
"Ed25519",
"Secp256k1",
"Secp256r1",
"X25519",
"Bls12381G1",
"Bls12381G2"
Expand Down Expand Up @@ -1034,6 +1035,7 @@
"enum": [
"Ed25519",
"Secp256k1",
"Secp256r1",
"X25519",
"Bls12381G1",
"Bls12381G2"
Expand Down Expand Up @@ -2285,6 +2287,7 @@
"enum": [
"Ed25519",
"Secp256k1",
"Secp256r1",
"X25519",
"Bls12381G1",
"Bls12381G2"
Expand Down Expand Up @@ -4904,4 +4907,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion packages/core/src/types/IIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export type MinimalImportableIdentifier = {
*
* @public
*/
export type TKeyType = 'Ed25519' | 'Secp256k1' | 'X25519' | 'Bls12381G1' | 'Bls12381G2'
export type TKeyType = 'Ed25519' | 'Secp256k1' | 'Secp256r1' | 'X25519' | 'Bls12381G1' | 'Bls12381G2'

/**
* Cryptographic key
Expand Down
1 change: 1 addition & 0 deletions packages/kms-local/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@stablelib/nacl": "^1.0.2",
"@veramo/core": "^4.0.0",
"@veramo/key-manager": "^4.0.0",
"elliptic": "^6.5.4",
"base-58": "^0.0.1",
"debug": "^4.3.3",
"did-jwt": "^6.6.0",
Expand Down
44 changes: 42 additions & 2 deletions packages/kms-local/src/__tests__/kms-local.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { KeyManagementSystem } from '../key-management-system'
import { TKeyType } from '@veramo/core'
import { MemoryPrivateKeyStore } from '@veramo/key-manager/src'
import { TKeyType } from '../../../core/src'
import { MemoryPrivateKeyStore } from '../../../key-manager/src'
import * as u8a from 'uint8arrays'

describe('@veramo/kms-local', () => {
it('should compute a shared secret Ed+Ed', async () => {
Expand Down Expand Up @@ -99,3 +100,42 @@ describe('@veramo/kms-local', () => {
expect(kms.sharedSecret({ myKeyRef, theirKey })).rejects.toThrow('not_supported')
})
})

describe('@veramo/kms-local Secp256r1 support', () => {
it('should generate a managed key', async () => {
const kms = new KeyManagementSystem(new MemoryPrivateKeyStore())
const key = await kms.createKey({type: 'Secp256r1'})
expect(key.type).toEqual('Secp256r1')
expect(key.publicKeyHex).toHaveLength(64)
expect(key.kid).toBeDefined()
expect(key.meta).toEqual({
"algorithms": [
"ES256"
]
})
})

it('should import a private key', async () => {
const kms = new KeyManagementSystem(new MemoryPrivateKeyStore())
const privateKeyHex = '96fe4d2b4a5d3abc4679fe39aa5d4b76990ff416e6ff403a58bd722cf8352f94'
const key = await kms.importKey({kid: 'test', privateKeyHex, type: 'Secp256r1'})
expect(key.type).toEqual('Secp256r1')
expect(key.publicKeyHex).toEqual('930fc234a12c939ccb1591a7c394088a30a32e81ac832ed8a0136e32bd73f792')
expect(key.kid).toEqual('test')
expect(key.meta).toEqual({
"algorithms": [
"ES256"
]
})
})

it('should sign input data', async () => {
const kms = new KeyManagementSystem(new MemoryPrivateKeyStore())
const privateKeyHex = '96fe4d2b4a5d3abc4679fe39aa5d4b76990ff416e6ff403a58bd722cf8352f94'
const data = u8a.fromString("test", 'utf-8');

const key = await kms.importKey({kid: 'test', privateKeyHex, type: 'Secp256r1'})
const signature = await kms.sign({keyRef: key, data, algorithm: 'ES256'})
expect(signature).toEqual('tTHhkwVSNk-C84zHS_ObzpyMNVoFopwUkR_pKxSC4kPyEIZrB5L36AFWHQQhp827D9aUSMKi38yiCrSfI4h7VA')
})
})
38 changes: 37 additions & 1 deletion packages/kms-local/src/key-management-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TKeyType, IKey, ManagedKeyInfo, MinimalImportableKey, RequireOnly } fro
import { AbstractKeyManagementSystem, AbstractPrivateKeyStore, Eip712Payload } from '@veramo/key-manager'
import { ManagedPrivateKey } from '@veramo/key-manager'

import { EdDSASigner, ES256KSigner } from 'did-jwt'
import { EdDSASigner, ES256KSigner, ES256Signer } from 'did-jwt'
import {
generateKeyPair as generateSigningKeyPair,
convertPublicKeyToX25519,
Expand All @@ -23,6 +23,7 @@ import { randomBytes } from '@ethersproject/random'
import { arrayify, hexlify } from '@ethersproject/bytes'
import * as u8a from 'uint8arrays'
import Debug from 'debug'
import elliptic from 'elliptic'

const debug = Debug('veramo:kms:local')

Expand Down Expand Up @@ -71,6 +72,7 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem {
})
break
}
case 'Secp256r1': // Generation uses exactly the same input mechanism for both Secp256k1 and Secp256r1
case 'Secp256k1': {
const privateBytes = randomBytes(32)
key = await this.importKey({
Expand Down Expand Up @@ -133,7 +135,12 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem {
} else if (['eth_rawSign'].includes(algorithm)) {
return this.eth_rawSign(managedKey.privateKeyHex, data);
}
} else if (managedKey.type === 'Secp256r1' &&
(typeof algorithm === 'undefined' || algorithm === 'ES256')
) {
return await this.signES256(managedKey.privateKeyHex, algorithm, data)
}

throw Error(`not_supported: Cannot sign ${algorithm} using key of type ${managedKey.type}`)
}

Expand Down Expand Up @@ -268,6 +275,20 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem {
return signature as string
}

/**
* @returns a base64url encoded signature for the `ES256` alg
*/
private async signES256(
privateKeyHex: string,
alg: string | undefined,
data: Uint8Array,
): Promise<string> {
const signer = ES256Signer(arrayify(privateKeyHex, { allowMissingPrefix: true }), false)
const signature = await signer(data)
// base64url encoded string
return signature as string
}

/**
* Converts a {@link @veramo/key-manager#ManagedPrivateKey | ManagedPrivateKey} to {@link @veramo/core#ManagedKeyInfo}
*/
Expand Down Expand Up @@ -301,6 +322,21 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem {
}
break
}
case 'Secp256r1': {
const privateBytes = u8a.fromString(args.privateKeyHex.toLowerCase(), 'base16')
const secp256r1 = new elliptic.ec('p256')
const keyPair: elliptic.ec.KeyPair = secp256r1.keyFromPrivate(privateBytes)
const publicKeyHex = keyPair.getPublic(true, 'hex').substring(2) // We remove the 'compressed' type 03 prefix
key = {
type: args.type,
kid: args.alias || publicKeyHex,
publicKeyHex,
meta: {
algorithms: ['ES256'],
},
}
break
}
case 'X25519': {
const secretKeyBytes = u8a.fromString(args.privateKeyHex.toLowerCase(), 'base16')
const keyPairX25519 = generateEncryptionKeyPairFromSeed(secretKeyBytes)
Expand Down
1 change: 1 addition & 0 deletions packages/remote-server/src/web-did-doc-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const didDocEndpoint = '/.well-known/did.json'

const keyMapping: Record<TKeyType, string> = {
Secp256k1: 'EcdsaSecp256k1VerificationKey2019',
Secp256r1: 'EcdsaSecp256r1VerificationKey2019',
Ed25519: 'Ed25519VerificationKey2018',
X25519: 'X25519KeyAgreementKey2019',
Bls12381G1: 'Bls12381G1Key2020',
Expand Down

0 comments on commit 61eb369

Please sign in to comment.