From e3242331b62c4225a2693587d25aeee49a44ecd6 Mon Sep 17 00:00:00 2001 From: Christian Petrov Date: Thu, 23 Nov 2023 17:59:26 +0000 Subject: [PATCH] Support generating and importing ECDSA keys ECDSA and ECDH keys have the same internal representation on the native client but are used for different purposes - ECDSA for signing and ECDH for key exchange. This commit adds support for ECDSA keys to the `SubtleCrypto` API. This prepares the way for supporting `crypto.subtle.sign()` and `crypto.subtle.verify()`. It also removes `"ECDH"` from the allowed string values for the `algorithm` parameter of `crypto.subtle.importKey`, because for ECDH and ECDSA algorithms, always an object that includes `namedCurve` must be provided [1]. [1]: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey --- doc/api/CryptoKey.json | 2 +- doc/api/SubtleCrypto.json | 8 ++++++-- src/tabris/Crypto.ts | 19 +++++++++++++------ src/tabris/CryptoKey.ts | 9 +++++++-- test/tabris/Crypto.test.ts | 12 ++++++++++-- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/doc/api/CryptoKey.json b/doc/api/CryptoKey.json index 95bfccf5..0ad021c6 100644 --- a/doc/api/CryptoKey.json +++ b/doc/api/CryptoKey.json @@ -23,7 +23,7 @@ "type": { "map": { "name": { - "type": "'ECDH'" + "type": { "union": ["'ECDH'", "'ECDSA'"] } }, "namedCurve": { "type": "'P-256'" diff --git a/doc/api/SubtleCrypto.json b/doc/api/SubtleCrypto.json index 4f9aa380..7fb5b678 100644 --- a/doc/api/SubtleCrypto.json +++ b/doc/api/SubtleCrypto.json @@ -287,7 +287,9 @@ { "map": { "name": { - "type": "'ECDH'" + "type": { + "union": ["'ECDH'", "'ECDSA'"] + } }, "namedCurve": { "type": "'P-256'" @@ -330,7 +332,9 @@ "type": { "map": { "name": { - "type": "'ECDH'" + "type": { + "union": ["'ECDH'", "'ECDSA'"] + } }, "namedCurve": { "type": "'P-256'" diff --git a/src/tabris/Crypto.ts b/src/tabris/Crypto.ts index 3a4bf73d..873dd06e 100644 --- a/src/tabris/Crypto.ts +++ b/src/tabris/Crypto.ts @@ -1,6 +1,13 @@ import NativeObject from './NativeObject'; import {toValueString} from './Console'; -import CryptoKey, {Algorithm, AlgorithmECDH, AlgorithmHKDF, AlgorithmInternal, _CryptoKey} from './CryptoKey'; +import CryptoKey, { + Algorithm, + AlgorithmECDH, + AlgorithmECDSA, + AlgorithmHKDF, + AlgorithmInternal, + _CryptoKey +} from './CryptoKey'; import {allowOnlyKeys, allowOnlyValues, getBuffer, getCid, getNativeObject} from './util'; import checkType from './checkType'; @@ -75,11 +82,11 @@ class SubtleCrypto { allowOnlyValues(format, ['spki', 'pkcs8', 'raw'], 'format'); checkType(getBuffer(keyData), ArrayBuffer, {name: 'keyData'}); if (typeof algorithm === 'string') { - allowOnlyValues(algorithm, ['ECDH', 'AES-GCM', 'HKDF'], 'algorithm'); + allowOnlyValues(algorithm, ['AES-GCM', 'HKDF'], 'algorithm'); } else { checkType(algorithm, Object, {name: 'algorithm'}); - allowOnlyValues(algorithm.name, ['ECDH', 'AES-GCM'], 'algorithm.name'); - if (algorithm.name === 'ECDH') { + allowOnlyValues(algorithm.name, ['ECDH', 'ECDSA', 'AES-GCM'], 'algorithm.name'); + if (algorithm.name === 'ECDH' || algorithm.name === 'ECDSA') { allowOnlyKeys(algorithm, ['name', 'namedCurve']); allowOnlyValues(algorithm.namedCurve, ['P-256'], 'algorithm.namedCurve'); } else { @@ -210,7 +217,7 @@ class SubtleCrypto { } async generateKey( - algorithm: AlgorithmECDH, + algorithm: AlgorithmECDH | AlgorithmECDSA, extractable: boolean, keyUsages: string[] ): Promise<{privateKey: CryptoKey, publicKey: CryptoKey}> { @@ -218,7 +225,7 @@ class SubtleCrypto { throw new TypeError(`Expected 3 arguments, got ${arguments.length}`); } allowOnlyKeys(algorithm, ['name', 'namedCurve']); - allowOnlyValues(algorithm.name, ['ECDH'], 'algorithm.name'); + allowOnlyValues(algorithm.name, ['ECDH', 'ECDSA'], 'algorithm.name'); allowOnlyValues(algorithm.namedCurve, ['P-256'], 'algorithm.namedCurve'); checkType(extractable, Boolean, {name: 'extractable'}); checkType(keyUsages, Array, {name: 'keyUsages'}); diff --git a/src/tabris/CryptoKey.ts b/src/tabris/CryptoKey.ts index 0a76ac1a..ecfb730b 100644 --- a/src/tabris/CryptoKey.ts +++ b/src/tabris/CryptoKey.ts @@ -2,7 +2,7 @@ import {TypedArray} from './Crypto'; import NativeObject from './NativeObject'; import {getBuffer, getCid, setNativeObject} from './util'; -export type AlgorithmInternal = AlgorithmHKDF | AlgorithmECDH | 'HKDF' | 'AES-GCM'; +export type AlgorithmInternal = AlgorithmHKDF | AlgorithmECDH | AlgorithmECDSA | 'HKDF' | 'AES-GCM'; export type Algorithm = AlgorithmInternal | {name: 'AES-GCM'}; @@ -19,6 +19,11 @@ export type AlgorithmECDH = { public?: CryptoKey }; +export type AlgorithmECDSA = { + name: 'ECDSA', + namedCurve: 'P-256' +}; + export default class CryptoKey { constructor(nativeObject: _CryptoKey, data: CryptoKey) { @@ -107,7 +112,7 @@ export class _CryptoKey extends NativeObject { } async generate( - algorithm: AlgorithmECDH, + algorithm: AlgorithmECDH | AlgorithmECDSA, extractable: boolean, keyUsages: string[] ): Promise { diff --git a/test/tabris/Crypto.test.ts b/test/tabris/Crypto.test.ts index 1667e2fd..0b6572d1 100644 --- a/test/tabris/Crypto.test.ts +++ b/test/tabris/Crypto.test.ts @@ -647,7 +647,7 @@ describe('Crypto', function() { it('checks algorithm.name', async function() { (params[2] as any).name = 'foo'; await expect(importKey()) - .rejectedWith(TypeError, 'algorithm.name must be "ECDH" or "AES-GCM", got "foo"'); + .rejectedWith(TypeError, 'algorithm.name must be "ECDH", "ECDSA" or "AES-GCM", got "foo"'); expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0); }); @@ -665,6 +665,14 @@ describe('Crypto', function() { expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0); }); + it('checks algorithm keys for ECDSA', async function() { + (params[2] as any).name = 'ECDSA'; + (params[2] as any).foo = 'foo'; + await expect(importKey()) + .rejectedWith(TypeError, 'Object contains unexpected entry "foo"'); + expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0); + }); + it('checks algorithm keys for AES-GCM', async function() { (params[2] as any) = {name: 'AES-GCM', namedCurve: 'P-256'}; await expect(importKey()) @@ -1069,7 +1077,7 @@ describe('Crypto', function() { // @ts-ignore params[0].name = 'foo'; await expect(generateKey()) - .rejectedWith(TypeError, 'algorithm.name must be "ECDH", got "foo"'); + .rejectedWith(TypeError, 'algorithm.name must be "ECDH" or "ECDSA", got "foo"'); expect(client.calls({op: 'create', type: 'tabris.CryptoKey'}).length).to.equal(0); });