Skip to content

Commit

Permalink
Support generating and importing ECDSA keys
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cpetrov committed Nov 23, 2023
1 parent 55c0c0d commit a24934c
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 9 deletions.
2 changes: 1 addition & 1 deletion doc/api/CryptoKey.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"type": {
"map": {
"name": {
"type": "'ECDH'"
"type": { "union": ["'ECDH'", "'ECDSA'"] }
},
"namedCurve": {
"type": "'P-256'"
Expand Down
4 changes: 3 additions & 1 deletion doc/api/SubtleCrypto.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@
{
"map": {
"name": {
"type": "'ECDH'"
"type": {
"union": ["'ECDH'", "'ECDSA'"]
}
},
"namedCurve": {
"type": "'P-256'"
Expand Down
8 changes: 4 additions & 4 deletions src/tabris/Crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,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 {
Expand Down Expand Up @@ -218,7 +218,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'});
Expand Down
7 changes: 6 additions & 1 deletion src/tabris/CryptoKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'};

Expand All @@ -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) {
Expand Down
12 changes: 10 additions & 2 deletions test/tabris/Crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand All @@ -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())
Expand Down Expand Up @@ -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);
});

Expand Down

0 comments on commit a24934c

Please sign in to comment.