From da4c31aa28fb928b7098afe2d0d678c5986644fa Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Mon, 4 Jan 2021 14:13:23 +0000 Subject: [PATCH 01/16] Add CryptoHelper tests --- libs/keyStore/tests/KeyStoreInMemory.spec.ts | 6 +- libs/plugin/lib/CryptoHelpers.ts | 10 +- libs/plugin/tests/CryptoFactory.spec.ts | 7 +- libs/plugin/tests/CryptoHelpers.spec.ts | 98 ++++++++++++++++++++ 4 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 libs/plugin/tests/CryptoHelpers.spec.ts diff --git a/libs/keyStore/tests/KeyStoreInMemory.spec.ts b/libs/keyStore/tests/KeyStoreInMemory.spec.ts index 11d2282..fd927a8 100644 --- a/libs/keyStore/tests/KeyStoreInMemory.spec.ts +++ b/libs/keyStore/tests/KeyStoreInMemory.spec.ts @@ -40,11 +40,15 @@ describe('KeyStoreInMemory', () => { y: 'AAEE', alg: 'ECDSA' }; + + let list = await keyStore.list(); + expect(list).toEqual({}); + await keyStore.save(new KeyReference('1'), key1); await keyStore.save(new KeyReference('1'), key2); await keyStore.save(new KeyReference('2'), key3); await keyStore.save(new KeyReference('3'), key4); - let list = await keyStore.list(); + list = await keyStore.list(); // tslint:disable-next-line: no-backbone-get-set-outside-model expect(list['1'].kids.length).toEqual(2); diff --git a/libs/plugin/lib/CryptoHelpers.ts b/libs/plugin/lib/CryptoHelpers.ts index fa7c3a2..ae9a2f8 100644 --- a/libs/plugin/lib/CryptoHelpers.ts +++ b/libs/plugin/lib/CryptoHelpers.ts @@ -36,9 +36,9 @@ export default class CryptoHelpers { case 'SHA-384': case 'SHA-512': return cryptoFactory.getMessageDigest(jwa, scope, keyReference); - } - - throw new Error(`Algorithm '${JSON.stringify(algorithm)}' is not supported`); + default: + throw new Error(`Algorithm '${JSON.stringify(algorithm)}' is not supported. Should be unreachable`); + } } /** @@ -82,7 +82,7 @@ export default class CryptoHelpers { return { name: 'HMAC', hash: { name: `SHA-${jwa.toUpperCase().replace('HS', '')}` } }; } - throw new Error(`Algorithm ${JSON.stringify(jwa)} is not supported`); + throw new Error(`Algorithm '${jwa}' is not supported`); } /** @@ -105,7 +105,7 @@ export default class CryptoHelpers { return `RSA-OAEP-256`; case 'AES-GCM': const length = algorithm.length || 128; - return `A${length}GCMKW`; + return `A${length}GCM`; case 'HMAC': return `HS256`; diff --git a/libs/plugin/tests/CryptoFactory.spec.ts b/libs/plugin/tests/CryptoFactory.spec.ts index 3e8c2a5..ed7680c 100644 --- a/libs/plugin/tests/CryptoFactory.spec.ts +++ b/libs/plugin/tests/CryptoFactory.spec.ts @@ -31,7 +31,7 @@ describe('CryptoFactory', () => { it('should change a crypto suite item',() => { const keyStore = new KeyStoreInMemory(); - const factory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()); + let factory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()); const algorithm = 'ES256K-tobeinvented'; const subtleCrypto = new SubtleCryptoMock(); let keyReference = new KeyReference('', 'secret'); @@ -39,6 +39,11 @@ describe('CryptoFactory', () => { let keyEncrypters: any = factory.getKeyEncrypter(algorithm, CryptoFactoryScope.All, keyReference); expect(keyEncrypters.ID).toBeUndefined(); + factory.addKeyEncrypter(algorithm, {subtleCrypto: subtleCrypto, scope: CryptoFactoryScope.All, keyStoreType: ['secret']}); + keyEncrypters = factory.getKeyEncrypter(algorithm, CryptoFactoryScope.Private, new KeyReference('', 'test')); + expect(keyEncrypters.ID).toBeUndefined(); + + factory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()); factory.addKeyEncrypter(algorithm, {subtleCrypto: subtleCrypto, scope: CryptoFactoryScope.Private, keyStoreType: ['secret']}); keyEncrypters = factory.getKeyEncrypter(algorithm, CryptoFactoryScope.Private, keyReference); expect(keyEncrypters.ID).toEqual('SubtleCryptoMock'); diff --git a/libs/plugin/tests/CryptoHelpers.spec.ts b/libs/plugin/tests/CryptoHelpers.spec.ts new file mode 100644 index 0000000..289035c --- /dev/null +++ b/libs/plugin/tests/CryptoHelpers.spec.ts @@ -0,0 +1,98 @@ +import { SubtleCryptoNode, CryptoFactory, CryptoFactoryScope, CryptoHelpers } from '../lib/index'; +import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; + +describe('CryptoHelpers', () => { + it('should return getSubtleCryptoForAlgorithm', () => { + const keyStore = new KeyStoreInMemory(); + let factory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()); + let keyReference = new KeyReference('', 'secret'); + let subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'RSASSA-PKCS1-V1_5'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'ECDSA'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'EdDSA'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'RSA-OAEP'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'RSA-OAEP-256'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'AES-GCM'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'HMAC'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'SHA-256'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'SHA-384'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + subtle = CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'SHA-512'}, CryptoFactoryScope.All, keyReference); + expect(subtle.constructor.name).toEqual('Subtle'); + + // Negative cases + expect(() => CryptoHelpers.getSubtleCryptoForAlgorithm(factory, {name: 'SHA1'}, CryptoFactoryScope.All, keyReference)).toThrowError(`Algorithm '{"name":"SHA1"}' is not supported`); + + const testSpy = { name: 'SHA-1' }; + const webCryptoToJwaSpy: jasmine.Spy = spyOn(CryptoHelpers, 'webCryptoToJwa').and.callFake(() => 'SHA-1'); + expect(() => CryptoHelpers.getSubtleCryptoForAlgorithm(factory, testSpy, CryptoFactoryScope.All, keyReference)).toThrowError(`Algorithm '{"name":"SHA-1"}' is not supported. Should be unreachable`); + }); + it('should return jwaToWebCrypto', () => { + expect(CryptoHelpers.jwaToWebCrypto('Rs256')).toEqual({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256'} }); + expect(CryptoHelpers.jwaToWebCrypto('Rs384')).toEqual({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-384'} }); + expect(CryptoHelpers.jwaToWebCrypto('Rs512')).toEqual({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-512'} }); + expect(CryptoHelpers.jwaToWebCrypto('RSA-OAEP-256')).toEqual({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' }); + expect(CryptoHelpers.jwaToWebCrypto('RSA-OAEP')).toEqual({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' }); + expect(CryptoHelpers.jwaToWebCrypto('A128GCM', [0,1,2], [3,4,5])).toEqual({ name: 'AES-GCM', iv: [0, 1, 2], additionalData: [3,4,5], tagLength: 128, length: 128}); + expect(CryptoHelpers.jwaToWebCrypto('A192GCM', [0,1,2], [3,4,5])).toEqual({ name: 'AES-GCM', iv: [0, 1, 2], additionalData: [3,4,5], tagLength: 128, length: 192}); + expect(CryptoHelpers.jwaToWebCrypto('A256GCM', [0,1,2], [3,4,5])).toEqual({ name: 'AES-GCM', iv: [0, 1, 2], additionalData: [3,4,5], tagLength: 128, length: 256}); + expect(CryptoHelpers.jwaToWebCrypto('Es256k')).toEqual({ name: 'ECDSA', namedCurve: 'secp256k1', crv: 'secp256k1', hash: { name: 'SHA-256' } }); + expect(CryptoHelpers.jwaToWebCrypto('EdDSA')).toEqual({ name: 'EdDSA', namedCurve: 'ed25519', crv: 'ed25519', hash: { name: 'SHA-256' } }); + expect(CryptoHelpers.jwaToWebCrypto('SHA-256')).toEqual({ hash: { name: 'SHA-256'} }); + expect(CryptoHelpers.jwaToWebCrypto('SHA-384')).toEqual({ hash: { name: 'SHA-384'} }); + expect(CryptoHelpers.jwaToWebCrypto('SHA-512')).toEqual({ hash: { name: 'SHA-512'} }); + expect(CryptoHelpers.jwaToWebCrypto('HS256')).toEqual({ name: 'HMAC', hash: { name: 'SHA-256'} }); + expect(CryptoHelpers.jwaToWebCrypto('HS384')).toEqual({ name: 'HMAC', hash: { name: 'SHA-384'} }); + expect(CryptoHelpers.jwaToWebCrypto('HS512')).toEqual({ name: 'HMAC', hash: { name: 'SHA-512'} }); + + // Negative cases + expect(() => CryptoHelpers.jwaToWebCrypto('SHA1')).toThrowError(`Algorithm 'SHA1' is not supported`); + }); + + it('should return webCryptoToJwa', () => { + expect(CryptoHelpers.webCryptoToJwa({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256'} })).toEqual('RS256'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-384'} })).toEqual('RS384'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-512'} })).toEqual('RS512'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' })).toEqual('RSA-OAEP-256'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' })).toEqual('RSA-OAEP-256'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'AES-GCM', iv: [0, 1, 2], additionalData: [3,4,5], tagLength: 128, length: 128})).toEqual('A128GCM'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'AES-GCM', iv: [0, 1, 2], additionalData: [3,4,5], tagLength: 128, length: 192})).toEqual('A192GCM'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'AES-GCM', iv: [0, 1, 2], additionalData: [3,4,5], tagLength: 128, length: 256})).toEqual('A256GCM'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'ECDSA', namedCurve: 'secp256k1', crv: 'secp256k1', hash: { name: 'SHA-256' } })).toEqual('ES256K'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'EdDSA', namedCurve: 'ed25519', crv: 'ed25519', hash: { name: 'SHA-256' } })).toEqual('EdDSA'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'SHA-256' })).toEqual('SHA-256'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'SHA-384' })).toEqual('SHA-384'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'SHA-512' })).toEqual('SHA-512'); + expect(CryptoHelpers.webCryptoToJwa({ name: 'HMAC', hash: { name: 'SHA-256'} })).toEqual('HS256'); + + // Negative cases + expect(() => CryptoHelpers.webCryptoToJwa({ name: 'SHA1' })).toThrowError(`Algorithm '{"name":"SHA1"}' is not supported`); + }); + + it('should return getKeyImportAlgorithm', () => { + let jwk: any = {crv: 'secp256k1'}; + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256'} }, jwk)).toEqual({ name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256'} }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-384'} }, jwk)).toEqual({ name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-384'} }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-512'} }, jwk)).toEqual({ name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-512'} }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' }, jwk)).toEqual({ name: 'RSA-OAEP', hash: 'SHA-256' }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' }, jwk)).toEqual({ name: 'RSA-OAEP', hash: 'SHA-256' }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'ECDSA', namedCurve: 'secp256k1', crv: 'secp256k1', hash: { name: 'SHA-256' } }, jwk)).toEqual({ name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }); + jwk = {crv: 'ed25519'}; + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'EdDSA', namedCurve: 'ed25519', crv: 'ed25519', hash: { name: 'SHA-256' } }, jwk)).toEqual({ name: 'EdDSA', namedCurve: 'ed25519', hash: { name: 'SHA-256' } }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'SHA-256' }, jwk)).toEqual({ name: 'SHA-256', hash: 'SHA-256' }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'SHA-384' }, jwk)).toEqual({ name: 'SHA-384', hash: 'SHA-384' }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'SHA-512' }, jwk)).toEqual({ name: 'SHA-512', hash: 'SHA-512' }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'HMAC', hash: { name: 'SHA-256'} }, jwk)).toEqual({ name: 'HMAC', hash: { name: 'SHA-256'} }); + expect(CryptoHelpers.getKeyImportAlgorithm({ name: 'AES-GCM'}, jwk)).toEqual({ name: 'AES-GCM' }); + + // Negative cases + expect(() => CryptoHelpers.getKeyImportAlgorithm({ name: 'SHA-1'}, jwk)).toThrowError(`Algorithm '{"name":"SHA-1"}' is not supported`); + }); +}); \ No newline at end of file From f624fef2ed6ab9ea13b1e0eeddd1105bbdc01a66 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 11:22:08 +0000 Subject: [PATCH 02/16] Add unit tests for SubtleCryptoExtension --- libs/plugin/lib/SubtleCryptoExtension.ts | 2 +- .../tests/SubtleCryptoExtension.spec.ts | 151 +++++++++++++++++- 2 files changed, 144 insertions(+), 9 deletions(-) diff --git a/libs/plugin/lib/SubtleCryptoExtension.ts b/libs/plugin/lib/SubtleCryptoExtension.ts index 5865b75..89629fa 100644 --- a/libs/plugin/lib/SubtleCryptoExtension.ts +++ b/libs/plugin/lib/SubtleCryptoExtension.ts @@ -68,7 +68,7 @@ export default class SubtleCryptoExtension extends Subtle implements ISubtleCryp const format: string = (algorithm).format; if (isElliptic && signature.byteLength <= 64 && format) { if (format.toUpperCase() !== 'DER') { - throw new CryptoError(algorithm, 'Only DER format supported for signature'); + return Promise.reject(new CryptoError(algorithm, 'Only DER format supported for signature')); } // DER format needed for signature, specied in algorithm diff --git a/libs/plugin/tests/SubtleCryptoExtension.spec.ts b/libs/plugin/tests/SubtleCryptoExtension.spec.ts index eca6d82..559d12d 100644 --- a/libs/plugin/tests/SubtleCryptoExtension.spec.ts +++ b/libs/plugin/tests/SubtleCryptoExtension.spec.ts @@ -5,8 +5,9 @@ import { SubtleCryptoNode, CryptoFactory, CryptoFactoryScope, CryptoHelpers, SubtleCryptoExtension } from '../lib'; import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; import EcPrivateKey from 'verifiablecredentials-crypto-sdk-typescript-keys/dist/lib/ec/EcPrivateKey'; -import { PublicKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; - +import { PublicKey, JsonWebKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; +import base64url from 'base64url'; +const clone = require('clone'); describe('SubtleCryptoExtension', () => { const keyStore = new KeyStoreInMemory(); @@ -15,12 +16,26 @@ describe('SubtleCryptoExtension', () => { it('should generate an ECDSA key', async () => { const alg = CryptoHelpers.jwaToWebCrypto('Es256K'); - const key: any = await generator.generateKey( + let key: any = await generator.generateKey( alg, true, ['sign', 'verify'] ); - const jwk = await cryptoFactory.defaultCrypto.exportKey('jwk', key.privateKey); + let jwk = await cryptoFactory.defaultCrypto.exportKey('jwk', key.privateKey); + expect(jwk.d).toBeDefined(); + expect(jwk.x).toBeDefined(); + expect(jwk.y).toBeDefined(); + expect(jwk.kty).toEqual('EC'); + + // pairwise + const keyReference = new KeyReference('seed'); + await keyStore.save(keyReference, base64url(Buffer.from('ABCDEFG'))); + jwk = await generator.generatePairwiseKey( + alg, + 'seed', + 'persona', + 'peer' + ); expect(jwk.d).toBeDefined(); expect(jwk.x).toBeDefined(); expect(jwk.y).toBeDefined(); @@ -52,9 +67,11 @@ describe('SubtleCryptoExtension', () => { expect(jwk.k).toBeDefined(); expect(jwk.kty).toEqual('oct'); }); - it('should sign a message', async () => { + + it('should sign a message with ECDSA', async () => { const keyStore = new KeyStoreInMemory(); - const factory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()); + const crypto = SubtleCryptoNode.getSubtleCrypto(); + const factory = new CryptoFactory(keyStore, crypto); const subtle = new SubtleCryptoExtension(factory); const alg = { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' }, format: 'DER' }; @@ -63,17 +80,126 @@ describe('SubtleCryptoExtension', () => { const payload = Buffer.from('test'); let signature = await subtle.signByKeyStore(alg, new KeyReference('key'), payload); expect(signature.byteLength).toBeGreaterThan(65); - const publicKey = (await keyStore.get(new KeyReference('key'), {publicKeyOnly: true})).getKey(); + const publicKey = (await keyStore.get(new KeyReference('key'), { publicKeyOnly: true })).getKey(); let result = await subtle.verifyByJwk(alg, publicKey, signature, payload); expect(result).toBeTruthy(); + // verify by passing in crypto key + const cryptoKey = await subtle.importKey('jwk', jwk, alg, true, ['sign', 'verify']); + let keyReference = new KeyReference('non-saved', 'signing', '', cryptoKey) + signature = await subtle.signByKeyStore(alg, keyReference, payload); + expect(signature.byteLength).toBeGreaterThan(65); + result = await subtle.verifyByJwk(alg, publicKey, signature, payload); + expect(result).toBeTruthy(); + // without DER delete (alg).format; signature = await subtle.signByKeyStore(alg, new KeyReference('key'), payload); expect(signature.byteLength).toBeLessThanOrEqual(64); result = await subtle.verifyByJwk(alg, publicKey, signature, payload); expect(result).toBeTruthy(); + + // without DER if the underlying algorithm provides DER + const signSpy: jasmine.Spy = spyOn(crypto, 'sign').and.callFake((alg: any) => { + (alg).format = 'DER'; + const r = new Uint8Array(32); + r.fill(0xff); + const s = new Uint8Array(32); + s.fill(0xff); + + return Promise.resolve(SubtleCryptoExtension.toDer([r, s])); + }); + signature = await subtle.signByKeyStore(alg, keyReference, payload); + expect(signature.byteLength).toBeLessThanOrEqual(64); + + // negative cases + // Only DER format supported for signature + const modifiedAlg = clone(alg); + (modifiedAlg).format = 'DER'; + (modifiedAlg).name = 'EdDSA'; + signSpy.and.callFake((alg: any) => { + (alg).format = 'XXX'; + return Promise.resolve(new ArrayBuffer(64)); + }); + try { + await subtle.signByKeyStore(modifiedAlg, keyReference, payload); + fail('Should throw signature error'); + } catch (exception) { + console.log('test'); + expect(exception.message).toEqual('Only DER format supported for signature'); + } + + // Verify failure + const verifySpy: jasmine.Spy = spyOn(crypto, 'verify').and.callFake(() => { + return Promise.resolve(false); + }); + result = await subtle.verifyByJwk(alg, publicKey, signature, payload); + expect(result).toBeFalsy(); + + const getKeyImportAlgorithmSpy: jasmine.Spy = spyOn(CryptoHelpers, 'getKeyImportAlgorithm').and.callFake((alg: any) => { + // change the algorithm will break the signature + const keyImportAlgorithm = clone(alg); + alg.name = 'EDDSA'; + return keyImportAlgorithm; + }); + const importSpy: jasmine.Spy = spyOn(crypto, 'importKey').and.callFake(() => { + return Promise.resolve(cryptoKey); + }); + result = await subtle.verifyByJwk(alg, publicKey, signature, payload); + expect(result).toBeFalsy(); }); + + it('should sign a message with RSA', async () => { + const keyStore = new KeyStoreInMemory(); + const crypto = SubtleCryptoNode.getSubtleCrypto(); + const factory = new CryptoFactory(keyStore, crypto); + const subtle = new SubtleCryptoExtension(factory); + const alg = { name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256' }}; + let keyReference = new KeyReference('key'); + + const keyPair = (await subtle.generateKey(alg, true, ['sign', 'verify'])); + const publicJwk = await subtle.exportKey('jwk', keyPair.publicKey); + const privateJwk = await subtle.exportKey('jwk', keyPair.privateKey); + await keyStore.save(keyReference, privateJwk); + + const payload = Buffer.from('test'); + let signature = await subtle.signByKeyStore(alg, keyReference, payload); + expect(signature.byteLength).toEqual(256); + const publicKey = (await keyStore.get(keyReference, { publicKeyOnly: true })).getKey(); + let result = await subtle.verifyByJwk(alg, publicKey, signature, payload); + expect(result).toBeTruthy(); + + // Verify failure + const verifySpy: jasmine.Spy = spyOn(crypto, 'verify').and.callFake(() => { + return Promise.resolve(false); + }); + result = await subtle.verifyByJwk(alg, publicKey, signature, payload); + expect(result).toBeFalsy(); + }); + + it('should encrypt a message with RSA', async () => { + const keyStore = new KeyStoreInMemory(); + const crypto = SubtleCryptoNode.getSubtleCrypto(); + const factory = new CryptoFactory(keyStore, crypto); + const subtle = new SubtleCryptoExtension(factory); + const alg = { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: 'SHA-256' }; + let keyReference = new KeyReference('key'); + + const keyPair = (await subtle.generateKey(alg, true, ['encrypt', 'decrypt'])); + const publicJwk = await subtle.exportKey('jwk', keyPair.publicKey); + const privateJwk = await subtle.exportKey('jwk', keyPair.privateKey); + await keyStore.save(keyReference, privateJwk); + + const payload = Buffer.from('test'); + let cipher = await subtle.encryptByJwk(alg, publicJwk, payload); + expect(cipher).toBeDefined(); + + let decrypted = await subtle.decryptByKeyStore(alg, keyReference, cipher); + expect(new Uint8Array(decrypted)).toEqual(new Uint8Array(payload)); + decrypted = await subtle.decryptByJwk(alg, privateJwk, cipher); + expect(new Uint8Array(decrypted)).toEqual(new Uint8Array(payload)); + }); + it('should sign a message with key reference options', async () => { const keyStore = new KeyStoreInMemory(); const factory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()); @@ -85,7 +211,7 @@ describe('SubtleCryptoExtension', () => { const payload = Buffer.from('test'); let signature = await subtle.signByKeyStore(alg, new KeyReference('key'), payload); expect(signature.byteLength).toBeGreaterThan(65); - const publicKey = (await keyStore.get(new KeyReference('key'), {publicKeyOnly: true})).getKey(); + const publicKey = (await keyStore.get(new KeyReference('key'), { publicKeyOnly: true })).getKey(); let result = await subtle.verifyByJwk(alg, publicKey, signature, payload); expect(result).toBeTruthy(); }); @@ -102,6 +228,15 @@ describe('SubtleCryptoExtension', () => { expect(Buffer.from(der).toString('hex').toUpperCase()).toEqual(expected); }); + it('should fail to decode DER', () => { + const signature = new Uint8Array(64); + signature.fill(0xff); + expect(() => SubtleCryptoExtension.fromDer(signature)).toThrowError('No DER format to decode'); + + signature[0] = 0x30; + expect(() => SubtleCryptoExtension.fromDer(signature)).toThrowError('Marker on index 2 must be 0x02'); + }); + it('should correctly roundtrip from R||S signature to DER signature', () => { const scenarios = [ From c9e92b0b2ee53d818eaa7b69c2a1f61942b70344 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 11:34:40 +0000 Subject: [PATCH 03/16] Add SubtleCryptoNode unit tests --- libs/plugin/tests/SubtleCryptoNode.spec.ts | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 libs/plugin/tests/SubtleCryptoNode.spec.ts diff --git a/libs/plugin/tests/SubtleCryptoNode.spec.ts b/libs/plugin/tests/SubtleCryptoNode.spec.ts new file mode 100644 index 0000000..78cdb6c --- /dev/null +++ b/libs/plugin/tests/SubtleCryptoNode.spec.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { SubtleCryptoNode, CryptoFactory, CryptoFactoryScope, CryptoHelpers, SubtleCryptoExtension } from '../lib'; +import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; +import EcPrivateKey from 'verifiablecredentials-crypto-sdk-typescript-keys/dist/lib/ec/EcPrivateKey'; +import { PublicKey, JsonWebKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; +import base64url from 'base64url'; + +describe('SubtleCryptoNode', () => { + it('should create instance', () => { + let subtle: any = new SubtleCryptoNode(); + expect(subtle.crypto.constructor.name).toEqual('Subtle'); + expect(subtle.getSubtleCrypto().constructor.name).toEqual('Subtle'); + expect(SubtleCryptoNode.getSubtleCrypto().constructor.name).toEqual('Subtle'); + }); + it('should test algorithmTransform', () => { + let subtle: any = new SubtleCryptoNode(); + let alg: any = {test: 'name'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + alg = {foo: 'fighters'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + }); + it('should test keyImportTransform', () => { + let subtle: any = new SubtleCryptoNode(); + let jwk: any = {test: 'name'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + }); + it('should test keyExportTransform', () => { + let subtle: any = new SubtleCryptoNode(); + let jwk: any = {test: 'name'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + }); +}); \ No newline at end of file From bf1d50b2f7ed9880730ae632fc5c95871f61c03c Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 12:28:03 +0000 Subject: [PATCH 04/16] Add Subtle unit tests --- libs/plugin/tests/Subtle.spec.ts | 54 ++++++++++++++++++++++ libs/plugin/tests/SubtleCryptoNode.spec.ts | 3 +- 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 libs/plugin/tests/Subtle.spec.ts diff --git a/libs/plugin/tests/Subtle.spec.ts b/libs/plugin/tests/Subtle.spec.ts new file mode 100644 index 0000000..1b8f00d --- /dev/null +++ b/libs/plugin/tests/Subtle.spec.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Subtle, CryptoFactory, CryptoFactoryScope, CryptoHelpers, SubtleCryptoExtension } from '../lib'; +import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; +import EcPrivateKey from 'verifiablecredentials-crypto-sdk-typescript-keys/dist/lib/ec/EcPrivateKey'; +import { PublicKey, JsonWebKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; +import base64url from 'base64url'; + +fdescribe('Subtle', () => { + it('should create instance', () => { + let subtle: any = new Subtle(); + expect(subtle.getSubtleCrypto().constructor.name).toEqual('Subtle'); + }); + it('should test algorithmTransform', () => { + let subtle = new Subtle(); + let alg: any = {test: 'name'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + alg = {namedCurve: 'P-256K'}; + expect(subtle.algorithmTransform(alg)).toEqual({namedCurve: 'K-256'}); + alg = {namedCurve: 'SECP256K1'}; + expect(subtle.algorithmTransform(alg)).toEqual({namedCurve: 'K-256'}); + alg = {crv: 'P-256K'}; + expect(subtle.algorithmTransform(alg)).toEqual({crv: 'K-256'}); + alg = {crv: 'SECP256K1'}; + expect(subtle.algorithmTransform(alg)).toEqual({crv: 'K-256'}); + }); + it('should test keyImportTransform', () => { + let subtle: any = new Subtle(); + let jwk: any = {test: 'name'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + jwk = {crv: 'P-256K'}; + expect(subtle.keyImportTransform(jwk)).toEqual({crv: 'K-256'}); + jwk = {crv: 'SECP256K1'}; + expect(subtle.keyImportTransform(jwk)).toEqual({crv: 'K-256'}); + + }); + it('should test keyExportTransform', () => { + let subtle: any = new Subtle(); + let jwk: any = {test: 'name'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + jwk = {crv: 'P-256K'}; + expect(subtle.keyExportTransform(jwk)).toEqual({crv: 'SECP256K1'}); + jwk = {crv: 'K-256'}; + expect(subtle.keyExportTransform(jwk)).toEqual({crv: 'SECP256K1'}); + jwk = {crv: 'SECP256K1'}; + expect(subtle.keyExportTransform(jwk)).toEqual({crv: 'SECP256K1'}); + }); +}); \ No newline at end of file diff --git a/libs/plugin/tests/SubtleCryptoNode.spec.ts b/libs/plugin/tests/SubtleCryptoNode.spec.ts index 78cdb6c..5030fd6 100644 --- a/libs/plugin/tests/SubtleCryptoNode.spec.ts +++ b/libs/plugin/tests/SubtleCryptoNode.spec.ts @@ -11,12 +11,11 @@ import base64url from 'base64url'; describe('SubtleCryptoNode', () => { it('should create instance', () => { let subtle: any = new SubtleCryptoNode(); - expect(subtle.crypto.constructor.name).toEqual('Subtle'); expect(subtle.getSubtleCrypto().constructor.name).toEqual('Subtle'); expect(SubtleCryptoNode.getSubtleCrypto().constructor.name).toEqual('Subtle'); }); it('should test algorithmTransform', () => { - let subtle: any = new SubtleCryptoNode(); + let subtle = new SubtleCryptoNode(); let alg: any = {test: 'name'}; expect(subtle.algorithmTransform(alg)).toEqual(alg); alg = {foo: 'fighters'}; From 03f317fd49e92718680ec22b48cae59382c7e4c5 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 14:42:14 +0000 Subject: [PATCH 05/16] More Sublte and browser tests --- libs/plugin/lib/CryptoHelpers.ts | 4 +- libs/plugin/lib/Pairwise/RsaPairwiseKey.ts | 2 +- libs/plugin/lib/SubtleCryptoBrowser.ts | 2 +- libs/plugin/tests/PairwiseKey.spec.ts | 10 +++ libs/plugin/tests/Subtle.spec.ts | 77 +++++++++++-------- libs/plugin/tests/SubtleCryptoBrowser.spec.ts | 31 ++++++++ .../tests/SubtleCryptoBrowser.spec.ts.ts | 9 --- .../tests/SubtleCryptoExtension.spec.ts | 4 +- 8 files changed, 94 insertions(+), 45 deletions(-) create mode 100644 libs/plugin/tests/SubtleCryptoBrowser.spec.ts delete mode 100644 libs/plugin/tests/SubtleCryptoBrowser.spec.ts.ts diff --git a/libs/plugin/lib/CryptoHelpers.ts b/libs/plugin/lib/CryptoHelpers.ts index ae9a2f8..a247c28 100644 --- a/libs/plugin/lib/CryptoHelpers.ts +++ b/libs/plugin/lib/CryptoHelpers.ts @@ -91,7 +91,7 @@ export default class CryptoHelpers { * @param hash Optional hash for the algorithm */ public static webCryptoToJwa(algorithm: any): string { - const hash: string = algorithm.hash || algorithm.name || 'SHA-256'; + const hash: string = algorithm.hash || algorithm.name; switch (algorithm.name.toUpperCase()) { case 'RSASSA-PKCS1-V1_5': return `RS${CryptoHelpers.getHash(hash)}`; @@ -124,7 +124,7 @@ export default class CryptoHelpers { * @param algorithm used for signature */ public static getKeyImportAlgorithm(algorithm: CryptoAlgorithm, jwk: PublicKey | JsonWebKey): string | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | DhImportKeyParams { - const hash = (algorithm).hash || (algorithm).name || 'SHA-256'; + const hash = (algorithm).hash || (algorithm).name; const name = algorithm.name; switch (algorithm.name.toUpperCase()) { case 'RSASSA-PKCS1-V1_5': diff --git a/libs/plugin/lib/Pairwise/RsaPairwiseKey.ts b/libs/plugin/lib/Pairwise/RsaPairwiseKey.ts index 60e769d..e06e4a0 100644 --- a/libs/plugin/lib/Pairwise/RsaPairwiseKey.ts +++ b/libs/plugin/lib/Pairwise/RsaPairwiseKey.ts @@ -35,7 +35,7 @@ type PrimeDelegate = Array<(cryptoFactory: CryptoFactory, inx: number, key: Buff // This method is currently breaking the subtle crypto pattern and needs to be fixed to be platform independent // Set the key size - const keySize = algorithm.modulusLength || 1024; + const keySize = algorithm.modulusLength || 2048; // Get deterministic base number for p const peerIdBuffer = Buffer.from(peerId); diff --git a/libs/plugin/lib/SubtleCryptoBrowser.ts b/libs/plugin/lib/SubtleCryptoBrowser.ts index e880a79..bcbc293 100644 --- a/libs/plugin/lib/SubtleCryptoBrowser.ts +++ b/libs/plugin/lib/SubtleCryptoBrowser.ts @@ -24,7 +24,7 @@ import { ISubtleCrypto } from './index'; // tslint:disable-next-line:no-typeof-undefined if (typeof window !== 'undefined') { // return browser api - return window.crypto.subtle; + return (window.crypto?.subtle); } throw new CryptoError({}, 'window is not defined. Must be defined in browser.') diff --git a/libs/plugin/tests/PairwiseKey.spec.ts b/libs/plugin/tests/PairwiseKey.spec.ts index 7662d11..f1c21f5 100644 --- a/libs/plugin/tests/PairwiseKey.spec.ts +++ b/libs/plugin/tests/PairwiseKey.spec.ts @@ -8,6 +8,7 @@ import {CryptoFactory, SubtleCryptoExtension, SubtleCryptoNode, PairwiseKey } fr import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; import { PrivateKey, EcPrivateKey, OctKey, KeyContainer } from 'verifiablecredentials-crypto-sdk-typescript-keys'; import base64url from 'base64url'; +import RsaPairwiseKey from '../lib/Pairwise/RsaPairwiseKey'; class Helpers { // Make sure we generate the same pairwise key @@ -235,4 +236,13 @@ describe('PairwiseKey', () => { expect(1).toBe(results.filter(element => element === pairwiseKey.d).length); } }); + + it('should generate RSA pairwise', async () =>{ + const keyStore = new KeyStoreInMemory(); + const cryptoFactory = new CryptoFactory(keyStore, new SubtleCryptoNode().getSubtleCrypto()); + + const alg = { name: 'RSASSA-PKCS1-v1_5', publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256' }}; + const rsaPairwiseKey: any = await RsaPairwiseKey.generate(cryptoFactory, Buffer.from('seed'), alg, 'peer'); + expect(base64url.toBuffer(rsaPairwiseKey.n).byteLength).toEqual(256); + }); }); diff --git a/libs/plugin/tests/Subtle.spec.ts b/libs/plugin/tests/Subtle.spec.ts index 1b8f00d..de2b33c 100644 --- a/libs/plugin/tests/Subtle.spec.ts +++ b/libs/plugin/tests/Subtle.spec.ts @@ -2,53 +2,70 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Subtle, CryptoFactory, CryptoFactoryScope, CryptoHelpers, SubtleCryptoExtension } from '../lib'; -import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; -import EcPrivateKey from 'verifiablecredentials-crypto-sdk-typescript-keys/dist/lib/ec/EcPrivateKey'; -import { PublicKey, JsonWebKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; -import base64url from 'base64url'; +import { Subtle } from '../lib'; -fdescribe('Subtle', () => { +describe('Subtle', () => { it('should create instance', () => { let subtle: any = new Subtle(); expect(subtle.getSubtleCrypto().constructor.name).toEqual('Subtle'); }); it('should test algorithmTransform', () => { let subtle = new Subtle(); - let alg: any = {test: 'name'}; + let alg: any = { test: 'name' }; expect(subtle.algorithmTransform(alg)).toEqual(alg); - alg = {namedCurve: 'P-256K'}; - expect(subtle.algorithmTransform(alg)).toEqual({namedCurve: 'K-256'}); - alg = {namedCurve: 'SECP256K1'}; - expect(subtle.algorithmTransform(alg)).toEqual({namedCurve: 'K-256'}); - alg = {crv: 'P-256K'}; - expect(subtle.algorithmTransform(alg)).toEqual({crv: 'K-256'}); - alg = {crv: 'SECP256K1'}; - expect(subtle.algorithmTransform(alg)).toEqual({crv: 'K-256'}); + alg = { namedCurve: 'P-256K' }; + expect(subtle.algorithmTransform(alg)).toEqual({ namedCurve: 'K-256' }); + alg = { namedCurve: 'SECP256K1' }; + expect(subtle.algorithmTransform(alg)).toEqual({ namedCurve: 'K-256' }); + alg = { crv: 'P-256K' }; + expect(subtle.algorithmTransform(alg)).toEqual({ crv: 'K-256' }); + alg = { crv: 'SECP256K1' }; + expect(subtle.algorithmTransform(alg)).toEqual({ crv: 'K-256' }); }); it('should test keyImportTransform', () => { let subtle: any = new Subtle(); - let jwk: any = {test: 'name'}; + let jwk: any = { test: 'name' }; expect(subtle.keyImportTransform(jwk)).toEqual(jwk); - jwk = {foo: 'fighters'}; + jwk = { foo: 'fighters' }; expect(subtle.keyImportTransform(jwk)).toEqual(jwk); - jwk = {crv: 'P-256K'}; - expect(subtle.keyImportTransform(jwk)).toEqual({crv: 'K-256'}); - jwk = {crv: 'SECP256K1'}; - expect(subtle.keyImportTransform(jwk)).toEqual({crv: 'K-256'}); + jwk = { crv: 'P-256K' }; + expect(subtle.keyImportTransform(jwk)).toEqual({ crv: 'K-256' }); + jwk = { crv: 'SECP256K1' }; + expect(subtle.keyImportTransform(jwk)).toEqual({ crv: 'K-256' }); + jwk = { crv: 'XXX' }; + expect(subtle.keyImportTransform(jwk)).toEqual({ crv: 'XXX' }); }); it('should test keyExportTransform', () => { let subtle: any = new Subtle(); - let jwk: any = {test: 'name'}; + let jwk: any = { test: 'name' }; expect(subtle.keyExportTransform(jwk)).toEqual(jwk); - jwk = {foo: 'fighters'}; + jwk = { foo: 'fighters' }; expect(subtle.keyExportTransform(jwk)).toEqual(jwk); - jwk = {crv: 'P-256K'}; - expect(subtle.keyExportTransform(jwk)).toEqual({crv: 'SECP256K1'}); - jwk = {crv: 'K-256'}; - expect(subtle.keyExportTransform(jwk)).toEqual({crv: 'SECP256K1'}); - jwk = {crv: 'SECP256K1'}; - expect(subtle.keyExportTransform(jwk)).toEqual({crv: 'SECP256K1'}); - }); + jwk = { crv: 'P-256K' }; + expect(subtle.keyExportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + jwk = { crv: 'K-256' }; + expect(subtle.keyExportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + jwk = { crv: 'SECP256K1' }; + expect(subtle.keyExportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + }); + it('should test digest', async() => { + let subtle: any = new Subtle(); + let alg: any = { name: 'SHA-256' }; + expect(await subtle.digest(alg, new Uint8Array([1,2,3,4]))).toBeDefined(); + }); + it('should test generate/export', async() => { + let subtle = new Subtle(); + let alg: any = { name: "HMAC", hash: {name: "SHA-256"} }; + const key = await subtle.generateKey(alg, true, ['sign']); + expect(key).toBeDefined(); + let jwk: any = await subtle.exportKey('raw', key); + expect(new Uint8Array(jwk)[0]).toBeDefined(); + let cryptoKey = await subtle.importKey('raw', jwk, alg, true, ['sign']); + expect(cryptoKey).toBeDefined(); + jwk = await subtle.exportKey('jwk', key); + expect(jwk.k).toBeDefined(); + jwk = await subtle.importKey('jwk', jwk, alg, true, ['sign']); + expect(jwk.type).toEqual('secret'); + }); }); \ No newline at end of file diff --git a/libs/plugin/tests/SubtleCryptoBrowser.spec.ts b/libs/plugin/tests/SubtleCryptoBrowser.spec.ts new file mode 100644 index 0000000..1002196 --- /dev/null +++ b/libs/plugin/tests/SubtleCryptoBrowser.spec.ts @@ -0,0 +1,31 @@ +import { SubtleCryptoBrowser, Subtle } from '../lib'; + +describe('SubtleCryptoBrowser', () => { + it('should create a SubtleCryptoBrowser', () => { + + const subtleBrowser = new SubtleCryptoBrowser(); + expect(() => subtleBrowser.getSubtleCrypto()).toThrowError('window is not defined. Must be defined in browser.'); + expect(() => SubtleCryptoBrowser.getSubtleCrypto()).toThrowError('window is not defined. Must be defined in browser.'); + }); + it('should test algorithmTransform', () => { + let subtle = new SubtleCryptoBrowser(); + let alg: any = {test: 'name'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + alg = {foo: 'fighters'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + }); + it('should test keyImportTransform', () => { + let subtle: any = new SubtleCryptoBrowser(); + let jwk: any = {test: 'name'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + }); + it('should test keyExportTransform', () => { + let subtle: any = new SubtleCryptoBrowser(); + let jwk: any = {test: 'name'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + }); +}); \ No newline at end of file diff --git a/libs/plugin/tests/SubtleCryptoBrowser.spec.ts.ts b/libs/plugin/tests/SubtleCryptoBrowser.spec.ts.ts deleted file mode 100644 index ec0e114..0000000 --- a/libs/plugin/tests/SubtleCryptoBrowser.spec.ts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SubtleCryptoBrowser } from '../lib'; - -describe('SubtleCryptoBrowser', () => { - it('should create a SubtleCryptoBrowser', () => { - - const subtleBrowser = new SubtleCryptoBrowser(); - expect(() => subtleBrowser.getSubtleCrypto()).toThrowError('window is not defined. Must be defined in browser.'); - }); -}); \ No newline at end of file diff --git a/libs/plugin/tests/SubtleCryptoExtension.spec.ts b/libs/plugin/tests/SubtleCryptoExtension.spec.ts index 559d12d..5d0510e 100644 --- a/libs/plugin/tests/SubtleCryptoExtension.spec.ts +++ b/libs/plugin/tests/SubtleCryptoExtension.spec.ts @@ -5,7 +5,7 @@ import { SubtleCryptoNode, CryptoFactory, CryptoFactoryScope, CryptoHelpers, SubtleCryptoExtension } from '../lib'; import { KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; import EcPrivateKey from 'verifiablecredentials-crypto-sdk-typescript-keys/dist/lib/ec/EcPrivateKey'; -import { PublicKey, JsonWebKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; +import { PublicKey } from 'verifiablecredentials-crypto-sdk-typescript-keys'; import base64url from 'base64url'; const clone = require('clone'); @@ -335,7 +335,7 @@ describe('SubtleCryptoExtension', () => { // roundtrip back to R||S const derArray = new Uint8Array(der); const roundtripRS = SubtleCryptoExtension.fromDer(derArray); - const r = SubtleCryptoExtension.toPaddedNumber(roundtripRS[0]); + const r = SubtleCryptoExtension.toPaddedNumber(roundtripRS[0], 32); const s = SubtleCryptoExtension.toPaddedNumber(roundtripRS[1]); const rsHex = Buffer.from(r).toString('hex').toUpperCase() + Buffer.from(s).toString('hex').toUpperCase(); expect(rsHex).toEqual(scenario.rs, scenario.scenario + " back to R||S"); From 161344864fa8263665cd00d97b91928b12bd038e Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 14:55:53 +0000 Subject: [PATCH 06/16] last tests for plugin --- libs/plugin/lib/CryptoHelpers.ts | 2 +- libs/plugin/tests/CryptoHelpers.spec.ts | 3 +++ libs/plugin/tests/Subtle.spec.ts | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/plugin/lib/CryptoHelpers.ts b/libs/plugin/lib/CryptoHelpers.ts index a247c28..7da3104 100644 --- a/libs/plugin/lib/CryptoHelpers.ts +++ b/libs/plugin/lib/CryptoHelpers.ts @@ -151,7 +151,7 @@ export default class CryptoHelpers { if (hash.name) { return (hash.name).toUpperCase().replace('SHA-', ''); } - return (hash || 'SHA-256').toUpperCase().replace('SHA-', ''); + return '256'; } private static getRegexMatch(matches: RegExpExecArray, index: number): string { diff --git a/libs/plugin/tests/CryptoHelpers.spec.ts b/libs/plugin/tests/CryptoHelpers.spec.ts index 289035c..29b6363 100644 --- a/libs/plugin/tests/CryptoHelpers.spec.ts +++ b/libs/plugin/tests/CryptoHelpers.spec.ts @@ -95,4 +95,7 @@ describe('CryptoHelpers', () => { // Negative cases expect(() => CryptoHelpers.getKeyImportAlgorithm({ name: 'SHA-1'}, jwk)).toThrowError(`Algorithm '{"name":"SHA-1"}' is not supported`); }); + it('should test getHash', () => { + expect((CryptoHelpers).getHash({'test': ''})).toEqual('256'); + }) }); \ No newline at end of file diff --git a/libs/plugin/tests/Subtle.spec.ts b/libs/plugin/tests/Subtle.spec.ts index de2b33c..e9b4ad8 100644 --- a/libs/plugin/tests/Subtle.spec.ts +++ b/libs/plugin/tests/Subtle.spec.ts @@ -21,6 +21,8 @@ describe('Subtle', () => { expect(subtle.algorithmTransform(alg)).toEqual({ crv: 'K-256' }); alg = { crv: 'SECP256K1' }; expect(subtle.algorithmTransform(alg)).toEqual({ crv: 'K-256' }); + alg = { crv: 'XXX' }; + expect(subtle.algorithmTransform(alg)).toEqual({ crv: 'XXX' }); }); it('should test keyImportTransform', () => { let subtle: any = new Subtle(); From 9088c9ce03f36212abb5f0cf410dedd8c490903c Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 15:04:39 +0000 Subject: [PATCH 07/16] last test for KeyStore --- libs/keyStore/tests/KeyStoreInMemory.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/keyStore/tests/KeyStoreInMemory.spec.ts b/libs/keyStore/tests/KeyStoreInMemory.spec.ts index fd927a8..b289a00 100644 --- a/libs/keyStore/tests/KeyStoreInMemory.spec.ts +++ b/libs/keyStore/tests/KeyStoreInMemory.spec.ts @@ -44,6 +44,10 @@ describe('KeyStoreInMemory', () => { let list = await keyStore.list(); expect(list).toEqual({}); + (keyStore).store = new Map(); + (keyStore).store.set('1', undefined); + expect(await keyStore.list()).toEqual({}); + await keyStore.save(new KeyReference('1'), key1); await keyStore.save(new KeyReference('1'), key2); await keyStore.save(new KeyReference('2'), key3); From 18f6cde70d88d3e7f3d4bbffb7d37ad9af3b0fdc Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 5 Jan 2021 16:17:42 +0000 Subject: [PATCH 08/16] Add unit tests to elliptic --- .../src/SubtleCryptoElliptic.ts | 6 --- .../tests/SubtleCryptoElliptic.spec.ts | 36 ++++++++++++++++ libs/plugin-elliptic/tests/ed25519.spec.ts | 8 ++++ libs/plugin-elliptic/tests/secp256k1.spec.ts | 42 +++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 libs/plugin-elliptic/tests/SubtleCryptoElliptic.spec.ts diff --git a/libs/plugin-elliptic/src/SubtleCryptoElliptic.ts b/libs/plugin-elliptic/src/SubtleCryptoElliptic.ts index 1686d93..bcaa135 100644 --- a/libs/plugin-elliptic/src/SubtleCryptoElliptic.ts +++ b/libs/plugin-elliptic/src/SubtleCryptoElliptic.ts @@ -21,12 +21,6 @@ export default class SubtleCryptoElliptic extends SubtleCrypto implements ISubtl this.providers.set(new EllipticEcDsaProvider(crypto)); this.providers.set(new EllipticEdDsaProvider(crypto)); } - - checkRequiredArguments(args: any[], size: number, methodName: string) { - if (methodName !== 'generateKey' && args.length !== size) { - throw new TypeError(`Failed to execute '${methodName}' on 'SubtleCrypto': ${size} arguments required, but only ${args.length} present`); - } - } /** * Returns the @class SubtleCrypto implementation for the nodes environment diff --git a/libs/plugin-elliptic/tests/SubtleCryptoElliptic.spec.ts b/libs/plugin-elliptic/tests/SubtleCryptoElliptic.spec.ts new file mode 100644 index 0000000..d5405b2 --- /dev/null +++ b/libs/plugin-elliptic/tests/SubtleCryptoElliptic.spec.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import SubtleCryptoElliptic from '../src/SubtleCryptoElliptic'; +import { Subtle } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; + +describe('SubtleCryptoElliptic', () => { + it('should create a SubtleCryptoElliptic', () => { + const subtleCryptoElliptic = new SubtleCryptoElliptic(new Subtle()); + expect(subtleCryptoElliptic.getSubtleCrypto().constructor.name).toEqual('SubtleCryptoElliptic'); + }); + + it('should test algorithmTransform', () => { + let subtle = new SubtleCryptoElliptic(new Subtle()); + let alg: any = {test: 'name'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + alg = {foo: 'fighters'}; + expect(subtle.algorithmTransform(alg)).toEqual(alg); + }); + it('should test keyImportTransform', () => { + let subtle: any = new SubtleCryptoElliptic(new Subtle()); + let jwk: any = {test: 'name'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyImportTransform(jwk)).toEqual(jwk); + }); + it('should test keyExportTransform', () => { + let subtle: any = new SubtleCryptoElliptic(new Subtle()); + let jwk: any = {test: 'name'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + jwk = {foo: 'fighters'}; + expect(subtle.keyExportTransform(jwk)).toEqual(jwk); + }); +}); \ No newline at end of file diff --git a/libs/plugin-elliptic/tests/ed25519.spec.ts b/libs/plugin-elliptic/tests/ed25519.spec.ts index ff6d266..ac209da 100644 --- a/libs/plugin-elliptic/tests/ed25519.spec.ts +++ b/libs/plugin-elliptic/tests/ed25519.spec.ts @@ -6,6 +6,7 @@ import { SubtleCryptoElliptic, EllipticCurveKey } from '../src/index'; import base64url from 'base64url'; import { Subtle } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; +import EllipticEdDsaProvider from '../src/EllipticEdDsaProvider'; const algGenerate = { @@ -289,4 +290,11 @@ describe('ed25519 - EdDSA', () => { }); expect(throws).toEqual(true); }); + + it('should instantiate EllipticEdDsaProvider', () => { + const ellipticEdDsaProvider = new EllipticEdDsaProvider(crypto); + expect(ellipticEdDsaProvider.getCurve('ed25519')).toBeDefined(); + expect(() => ellipticEdDsaProvider.getCurve('SECP256K1')).toThrowError(`The requested curve 'SECP256K1' is not supported in EllipticEcDsaProvider`); + }); + }); diff --git a/libs/plugin-elliptic/tests/secp256k1.spec.ts b/libs/plugin-elliptic/tests/secp256k1.spec.ts index 5814bdc..77ee86d 100644 --- a/libs/plugin-elliptic/tests/secp256k1.spec.ts +++ b/libs/plugin-elliptic/tests/secp256k1.spec.ts @@ -6,6 +6,7 @@ import SubtleCryptoElliptic from '../src/SubtleCryptoElliptic'; import EllipticCurveKey from '../src/EllipticCurveKey'; import { Subtle } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; +import EllipticEcDsaProvider from '../src/EllipticEcDsaProvider'; // tslint:disable:mocha-no-side-effect-code const EC = require('elliptic').ec; @@ -124,4 +125,45 @@ describe('secp256k1 - ECDSA', () => { }); expect(throws).toEqual(true); }); + + it('should instantiate EllipticEcDsaProvider', () => { + const ellipticEcDsaProvider = new EllipticEcDsaProvider(crypto); + expect(ellipticEcDsaProvider.getCurve('SECP256K1')).toBeDefined(); + expect(() => ellipticEcDsaProvider.getCurve('EdDSA')).toThrowError(`The requested curve 'EdDSA' is not supported in EllipticEcDsaProvider`); + }); + + it('should generate key in EllipticEcDsaProvider', async () => { + const ellipticEcDsaProvider = new EllipticEcDsaProvider(crypto); + const ec = ellipticEcDsaProvider.getCurve('SECP256K1'); + const keyPair = ec.genKeyPair(); + const genKeyPairSpy: jasmine.Spy = spyOn(ec, 'genKeyPair').and.callFake(() => { + delete keyPair.pub; + return keyPair; + }); + const algGenerate = { + name: 'ECDSA', + namedCurve: 'secp256k1' + }; + expect((await ellipticEcDsaProvider.generateKey(algGenerate, true, ['sign'])).privateKey).toBeDefined(); + + genKeyPairSpy.and.callFake(() => { + keyPair.pub = keyPair.getPublic(); + return keyPair; + }); + expect((await ellipticEcDsaProvider.generateKey(algGenerate, true, ['sign'])).privateKey).toBeDefined(); + }); + + it('should sign/verify in EllipticEcDsaProvider', async () => { + const ellipticEcDsaProvider = new EllipticEcDsaProvider(crypto); + let algGenerate: any = { + name: 'ECDSA', + namedCurve: 'secp256k1', + hash: undefined + }; + const keyPair = (await ellipticEcDsaProvider.generateKey(algGenerate, true, ['sign', 'verify'])); + let signature = await ellipticEcDsaProvider.sign(algGenerate, keyPair.privateKey, new Uint8Array([1, 2, 3])); + expect(signature).toBeDefined(); + expect(await ellipticEcDsaProvider.verify(algGenerate, keyPair.publicKey, signature, new Uint8Array([1, 2, 3]))); + }); + }); From 6c18f93c2a820456e693049265f25872c24e4336 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Tue, 12 Jan 2021 09:09:13 +0000 Subject: [PATCH 09/16] Update to keyvault unit tests --- .../tests/KeyStoreKeyVault.spec.ts | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts b/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts index 1930a34..7c4bcb1 100644 --- a/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts +++ b/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts @@ -12,6 +12,8 @@ import { KeyClient } from '@azure/keyvault-keys'; import { SecretClient } from '@azure/keyvault-secrets'; import { CryptoKey } from 'webcrypto-core'; import Credentials from './Credentials'; +import base64url from 'base64url'; +const clone = require('clone'); // Sample config const tenantId = Credentials.tenantGuid; @@ -48,6 +50,18 @@ describe('KeyStoreKeyVault', () => { return; } + it('should create an instance', () =>{ + const cache = new KeyStoreInMemory(); + const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + let vault = 'https://example.keyvault.com'; + let keyStore = new KeyStoreKeyVault(credential, vault, cache); + expect(keyStore.cache).toEqual(cache); + expect((keyStore).vaultUri).toEqual(vault + '/'); + keyStore = new KeyStoreKeyVault(credential, vault + '/', cache); + expect(keyStore.cache).toEqual(cache); + expect((keyStore).vaultUri).toEqual(vault + '/'); + }); + it('should list a named generated key', async () => { const name = 'KvTest-KeyStoreKeyVault' + Math.random().toString(10).substr(2); const cache = new KeyStoreInMemory(); @@ -58,7 +72,8 @@ describe('KeyStoreKeyVault', () => { await provider.onGenerateKey(alg, false, ['sign'], { keyReference: new KeyReference(name) }); let list = await keyStore.list('key', new KeyStoreOptions({ latestVersion: false })); expect(list[name]).toBeDefined(); - const key = await keyStore.get(new KeyReference(name, 'key'), new KeyStoreOptions({ latestVersion: false })); + // Two requests should hit cache + let key = await keyStore.get(new KeyReference(name, 'key'), new KeyStoreOptions({ latestVersion: false })); expect(key).toBeDefined(); expect((await cache.list())[name]).toBeDefined(); } finally { @@ -66,6 +81,86 @@ describe('KeyStoreKeyVault', () => { } }); + it('should list a named stored secret k', async () => { + const name = 'KvTest-KeyStoreKeyVault' + Math.random().toString(10).substr(2); + const cache = new KeyStoreInMemory(); + const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); + try { + const secret1 = base64url.encode(name); + const secret2 = base64url.encode(name+'2'); + + //save two versions + await keyStore.save( new KeyReference(name), secret1); + await keyStore.save( new KeyReference(name), secret2); + + let list = await keyStore.list('secret', new KeyStoreOptions({ latestVersion: false })); + expect(list[name]).toBeDefined(); + + // get latest version only + let key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: true })); + expect(key.keys.length).toEqual(1); + //TODO BUG. k is reported as object + expect((await cache.list())[name]).toBeDefined(); + + // get all versions + key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: false })); + expect(key.keys.length).toEqual(2); + } finally { + await (keyStore.getKeyStoreClient('secret')).beginDeleteSecret(name); + } + }); + it('should list a named stored secret with EC', async () => { + const name = 'KvTest-KeyStoreKeyVault' + Math.random().toString(10).substr(2); + const cache = new KeyStoreInMemory(); + const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); + const subtle = new Subtle(); + try { + + const keyPair = await subtle.generateKey({ name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, true, ["sign", "verify"]); + const jwk: any = await subtle.exportKey('jwk', keyPair.privateKey); + + //save two versions + await keyStore.save( new KeyReference(name), jwk); + + // save okp version + let okp = clone(jwk); + okp.kty = 'OKP'; + await keyStore.save( new KeyReference(name), jwk); + + let list = await keyStore.list('secret', new KeyStoreOptions({ latestVersion: false })); + expect(list[name]).toBeDefined(); + + // get latest version only + let key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: true })); + expect(key.keys.length).toEqual(1); + //TODO BUG. k is reported as object + expect((await cache.list())[name]).toBeDefined(); + + // get all versions + key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: false })); + expect(key.keys.length).toEqual(2); + + // get public key only + key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ publicKeyOnly: true })); + expect(key.keys.length).toEqual(1); + + // negative cases + /** ROB TODO + getKeyStoreClientSpy: jasmine.Spy = spyOn(keyStore, 'getKeyStoreClient').and.callFake(() => () => { + return { + keys: { + + } + } + }); + */ + } finally { + await (keyStore.getKeyStoreClient('secret')).beginDeleteSecret(name); + } + }); + it('should list a specific version of the key', async () => { const name = 'KvTest-KeyStoreKeyVault' + Math.random().toString(10).substr(2); const cache = new KeyStoreInMemory(); From 5a9b7e4795209f5edf69524a7db29c0988a0dc43 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Wed, 13 Jan 2021 10:02:48 +0000 Subject: [PATCH 10/16] Update unit tests for key vault --- CHANGE_LOG.md | 5 + libs/keyStore/package.json | 4 +- libs/keys/package.json | 2 +- libs/plugin-cryptofactory-suites/package.json | 8 +- libs/plugin-elliptic/package.json | 4 +- libs/plugin-factory/package.json | 10 +- libs/plugin-keyvault/package.json | 10 +- .../src/keyStore/KeyStoreKeyVault.ts | 9 +- .../src/plugin/KeyVaultEcdsaProvider.ts | 27 +-- .../tests/CryptoFactoryKeyVault.spec.ts | 20 +++ .../tests/KeyStoreKeyVault.spec.ts | 65 +++++--- .../tests/KeyVaultPlugin.spec.ts | 155 ++++++++++++++---- .../tests/SubtleCryptoKeyVault.spec.ts | 76 +++++++++ libs/plugin/package.json | 6 +- libs/protocol-jose/package.json | 12 +- libs/protocols-common/package.json | 8 +- libs/sdk/package.json | 20 +-- 17 files changed, 326 insertions(+), 115 deletions(-) create mode 100644 libs/plugin-keyvault/tests/CryptoFactoryKeyVault.spec.ts create mode 100644 libs/plugin-keyvault/tests/SubtleCryptoKeyVault.spec.ts diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 7d27f22..ff6c01c 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,8 @@ +# version 1.1.12-preview.1 +## Keyvault exportKey did not add the kid in the jwk. +**Type of change:** engineering +**Customer impact:** low + # version 1.1.12-preview.0 ## Remove all console.log calls from the SDK **Type of change:** engineering diff --git a/libs/keyStore/package.json b/libs/keyStore/package.json index 5a7f298..16d632d 100644 --- a/libs/keyStore/package.json +++ b/libs/keyStore/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-keystore", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "description": "Package for managing keys in a key store.", "repository": { "type": "git", @@ -35,7 +35,7 @@ "typescript": "4.0.3" }, "dependencies": { - "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.1", "@types/node": "14.6.2", "base64url": "^3.0.1", "clone": "^2.1.2", diff --git a/libs/keys/package.json b/libs/keys/package.json index 8204bbc..18c3acf 100644 --- a/libs/keys/package.json +++ b/libs/keys/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-keys", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "description": "Package for managing keys in the DID space.", "repository": { "type": "git", diff --git a/libs/plugin-cryptofactory-suites/package.json b/libs/plugin-cryptofactory-suites/package.json index 75880d0..7f058f0 100644 --- a/libs/plugin-cryptofactory-suites/package.json +++ b/libs/plugin-cryptofactory-suites/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "description": "Package crypto factory suites.", "repository": { "type": "git", @@ -36,9 +36,9 @@ "typescript": "4.0.3" }, "dependencies": { - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic": "1.1.12-preview.1", "base64url": "^3.0.1", "clone": "2.1.2", "webcrypto-core": "1.1.8" diff --git a/libs/plugin-elliptic/package.json b/libs/plugin-elliptic/package.json index db888f7..d727f28 100644 --- a/libs/plugin-elliptic/package.json +++ b/libs/plugin-elliptic/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "repository": { "type": "git", "url": "https://github.com/microsoft/VerifiableCredentials-Crypto-SDK-Typescript.git" @@ -29,7 +29,7 @@ "elliptic": "6.5.3", "minimalistic-crypto-utils": "1.0.1", "sha.js": "^2.4.11", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", "webcrypto-core": "1.1.8" }, "devDependencies": { diff --git a/libs/plugin-factory/package.json b/libs/plugin-factory/package.json index 63f124e..69a2e23 100644 --- a/libs/plugin-factory/package.json +++ b/libs/plugin-factory/package.json @@ -1,7 +1,7 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-plugin-factory", "description": "Factory Package for crypto plugins.", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "repository": { "type": "git", "url": "https://github.com/microsoft/VerifiableCredentials-Crypto-SDK-Typescript.git" @@ -39,10 +39,10 @@ "dependencies": { "@azure/identity": "1.0.0", "lru-cache": "6.0.0", - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-keyvault": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-keyvault": "1.1.12-preview.1", "@types/node": "14.6.2", "webcrypto-core": "1.1.8" }, diff --git a/libs/plugin-keyvault/package.json b/libs/plugin-keyvault/package.json index cb71836..47e0154 100644 --- a/libs/plugin-keyvault/package.json +++ b/libs/plugin-keyvault/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-plugin-keyvault", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "repository": { "type": "git", "url": "https://github.com/microsoft/VerifiableCredentials-Crypto-SDK-Typescript.git" @@ -50,10 +50,10 @@ "@azure/keyvault-keys": "4.0.2", "@azure/keyvault-secrets": "4.0.2", "lru-cache": "6.0.0", - "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic": "1.1.12-preview.1", "base64url": "3.0.1", "clone": "2.1.2", "webcrypto-core": "1.1.8" diff --git a/libs/plugin-keyvault/src/keyStore/KeyStoreKeyVault.ts b/libs/plugin-keyvault/src/keyStore/KeyStoreKeyVault.ts index b035bdf..6f73523 100644 --- a/libs/plugin-keyvault/src/keyStore/KeyStoreKeyVault.ts +++ b/libs/plugin-keyvault/src/keyStore/KeyStoreKeyVault.ts @@ -135,7 +135,7 @@ export default class KeyStoreKeyVault implements IKeyStore { keyContainerItem = options.publicKeyOnly ? new RsaPublicKey(version.key ? version.key : version.value as any) : new RsaPrivateKey(version.key ? version.key : version.value as any); } else { - throw new Error(`Non supported key type ${kty}`); + return Promise.reject(new Error(`Non supported key type ${kty}`)); } } else { if (kty === 'EC') { @@ -159,15 +159,14 @@ export default class KeyStoreKeyVault implements IKeyStore { } if (!container) { - throw new Error(`The secret with reference '${keyName}' has not usable secrets`); + return Promise.reject(new Error(`The secret with reference '${keyName}' has not usable secrets`)); } return container; } catch (e) { console.error(`Could not retrieve ${JSON.stringify(keyReference)}. Error: ${e}`); - throw e; + return Promise.reject(e); } - } /** @@ -179,7 +178,7 @@ export default class KeyStoreKeyVault implements IKeyStore { */ async save(keyReference: KeyReference, key: CryptographicKey | string, options: KeyStoreOptions = new KeyStoreOptions()): Promise { if (!keyReference || !keyReference.keyReference) { - throw new Error(`Key reference needs to be specified`); + return Promise.reject(new Error(`Key reference needs to be specified`)); } const keyName = keyReference.remoteKeyReference || keyReference.keyReference; diff --git a/libs/plugin-keyvault/src/plugin/KeyVaultEcdsaProvider.ts b/libs/plugin-keyvault/src/plugin/KeyVaultEcdsaProvider.ts index c60c7bc..1c07cdd 100644 --- a/libs/plugin-keyvault/src/plugin/KeyVaultEcdsaProvider.ts +++ b/libs/plugin-keyvault/src/plugin/KeyVaultEcdsaProvider.ts @@ -52,7 +52,7 @@ export default class KeyVaultEcdsaProvider extends KeyVaultProvider { const kid = (key.algorithm).kid; if (!kid) { - throw new CryptoError(algorithm, 'Missing kid in algortihm'); + return Promise.reject(new CryptoError(algorithm, 'Missing kid in algortihm')); } const client = (this.keyStore).getCryptoClient(kid); @@ -65,7 +65,7 @@ export default class KeyVaultEcdsaProvider extends KeyVaultProvider { // Added for legacy. Used by keys generated with crv: SECP256K1 signature = await client.sign('ECDSA256', new Uint8Array(hash)); } else { - throw exception; + return Promise.reject(exception); } } @@ -85,28 +85,28 @@ export default class KeyVaultEcdsaProvider extends KeyVaultProvider { jwk: JsonWebKey, _algorithm: EcKeyImportParams, _extractable: boolean, keyUsages: KeyUsage[]): Promise { if (format !== 'jwk') { - throw new Error(`Import key only supports jwk`); + return Promise.reject(new Error(`Import key only supports jwk`)); } if (jwk.kty?.toUpperCase() !== 'EC') { - throw new Error(`Import key only supports kty EC`); + return Promise.reject(new Error(`Import key only supports kty EC`)); } if (jwk.crv?.toUpperCase() === 'SECP256K1') { jwk.crv = 'P-256K'; } else if (jwk.crv?.toUpperCase() !== 'P-256K') { - throw new Error(`Import key only supports crv P-256K`); + return Promise.reject(new Error(`Import key only supports crv P-256K`)); } - if (!jwk.kid && jwk.kid!.startsWith('https://')) { - throw new Error(`Imported key must have a kid in the format https:///keys//`); + if (!(jwk.kid && jwk.kid.startsWith('https://'))) { + return Promise.reject(new Error(`Imported key must have a kid in the format https:///keys//`)); } const kidParts = jwk.kid!.split('/'); let secretType: boolean = kidParts[3] === 'secrets'; - if (!['keys', 'secrets'].includes(kidParts[3])) { - throw new Error(`Imported key must be of type keys or secrets`); + if (!(kidParts.length >= 5 && ['keys', 'secrets'].includes(kidParts[3]))) { + return Promise.reject(new Error(`Imported key must be of type keys or secrets`)); } if (kidParts.length <= 5) { @@ -189,8 +189,13 @@ export default class KeyVaultEcdsaProvider extends KeyVaultProvider { */ async onExportKey(format: KeyFormat, key: CryptoKey): Promise { if (format !== 'jwk') { - throw new Error(`Export key only supports jwk`); + return Promise.reject(new Error(`Export key only supports jwk`)); } - return >this.subtle.exportKey(format, key); + + const jwk: any = await this.subtle.exportKey(format, key); + if (!jwk.kid) { + jwk.kid = (key.algorithm).kid; + } + return >jwk; } } \ No newline at end of file diff --git a/libs/plugin-keyvault/tests/CryptoFactoryKeyVault.spec.ts b/libs/plugin-keyvault/tests/CryptoFactoryKeyVault.spec.ts new file mode 100644 index 0000000..7b52db5 --- /dev/null +++ b/libs/plugin-keyvault/tests/CryptoFactoryKeyVault.spec.ts @@ -0,0 +1,20 @@ +import { ClientSecretCredential } from '@azure/identity'; +import { KeyReference, KeyStoreInMemory } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; +import { CryptoFactoryScope, Subtle } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; +import { CryptoFactoryKeyVault, KeyStoreKeyVault } from '../src'; + +describe('CryptoFactoryKeyVault', () => { + it('should create a CryptoFactoryKeyVault', () => { + const cache = new KeyStoreInMemory(); + const credential = new ClientSecretCredential('tenant', 'clientid', 'secret'); + const keyStore = new KeyStoreKeyVault(credential, 'https://example.vault.com', cache); + const subtle = new Subtle(); + + const crypto = new CryptoFactoryKeyVault(keyStore, subtle); + expect(crypto.getMessageSigner('ES256K', CryptoFactoryScope.Private, new KeyReference('key', 'key')).constructor.name).toEqual('SubtleCryptoKeyVault'); + expect(crypto.getMessageSigner('ECDSA', CryptoFactoryScope.Private, new KeyReference('key', 'key')).constructor.name).toEqual('SubtleCryptoKeyVault'); + expect(crypto.getKeyEncrypter('RSA-OAEP-256', CryptoFactoryScope.Private, new KeyReference('key', 'key')).constructor.name).toEqual('SubtleCryptoKeyVault'); + expect(crypto.getKeyEncrypter('RSA-OAEP', CryptoFactoryScope.Private, new KeyReference('key', 'key')).constructor.name).toEqual('SubtleCryptoKeyVault'); + expect(crypto.getMessageSigner('RSASSA-PKCS1-v1_5', CryptoFactoryScope.Private, new KeyReference('key', 'key')).constructor.name).toEqual('Subtle'); + }) +}); \ No newline at end of file diff --git a/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts b/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts index 7c4bcb1..d1f6ced 100644 --- a/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts +++ b/libs/plugin-keyvault/tests/KeyStoreKeyVault.spec.ts @@ -46,11 +46,11 @@ afterEach(() => { describe('KeyStoreKeyVault', () => { const alg = { name: 'ECDSA', namedCurve: 'SECP256K1', hash: { name: 'SHA-256' } }; if (!keyVaultEnable) { - console.log('Key vault is enabled. Add your credentials to Credentials.ts') + console.log('Key vault is not enabled. Add your credentials to Credentials.ts') return; } - it('should create an instance', () =>{ + it('should create an instance', () => { const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); let vault = 'https://example.keyvault.com'; @@ -74,6 +74,7 @@ describe('KeyStoreKeyVault', () => { expect(list[name]).toBeDefined(); // Two requests should hit cache let key = await keyStore.get(new KeyReference(name, 'key'), new KeyStoreOptions({ latestVersion: false })); + key = await keyStore.get(new KeyReference(name, 'key'), new KeyStoreOptions({ latestVersion: false })); expect(key).toBeDefined(); expect((await cache.list())[name]).toBeDefined(); } finally { @@ -88,11 +89,11 @@ describe('KeyStoreKeyVault', () => { const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); try { const secret1 = base64url.encode(name); - const secret2 = base64url.encode(name+'2'); + const secret2 = base64url.encode(name + '2'); //save two versions - await keyStore.save( new KeyReference(name), secret1); - await keyStore.save( new KeyReference(name), secret2); + await keyStore.save(new KeyReference(name), secret1); + await keyStore.save(new KeyReference(name), secret2); let list = await keyStore.list('secret', new KeyStoreOptions({ latestVersion: false })); expect(list[name]).toBeDefined(); @@ -111,6 +112,7 @@ describe('KeyStoreKeyVault', () => { } }); it('should list a named stored secret with EC', async () => { + let cleaned = false; const name = 'KvTest-KeyStoreKeyVault' + Math.random().toString(10).substr(2); const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); @@ -122,18 +124,19 @@ describe('KeyStoreKeyVault', () => { const jwk: any = await subtle.exportKey('jwk', keyPair.privateKey); //save two versions - await keyStore.save( new KeyReference(name), jwk); + await keyStore.save(new KeyReference(name), jwk); // save okp version let okp = clone(jwk); okp.kty = 'OKP'; - await keyStore.save( new KeyReference(name), jwk); + await keyStore.save(new KeyReference(name), jwk); let list = await keyStore.list('secret', new KeyStoreOptions({ latestVersion: false })); expect(list[name]).toBeDefined(); - // get latest version only + // get latest version only, second should let key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: true })); + key = await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: true })); expect(key.keys.length).toEqual(1); //TODO BUG. k is reported as object expect((await cache.list())[name]).toBeDefined(); @@ -147,17 +150,22 @@ describe('KeyStoreKeyVault', () => { expect(key.keys.length).toEqual(1); // negative cases - /** ROB TODO - getKeyStoreClientSpy: jasmine.Spy = spyOn(keyStore, 'getKeyStoreClient').and.callFake(() => () => { - return { - keys: { - - } - } - }); - */ + cleaned = true; + await (keyStore.getKeyStoreClient('secret')).beginDeleteSecret(name); + const getKeyStoreClientSpy: jasmine.Spy = spyOn(keyStore, 'getKeyStoreClient').and.callFake(() => { + throw new Error('some error'); + }); + try { + await keyStore.get(new KeyReference(name, 'secret'), new KeyStoreOptions({ latestVersion: true })); + fail('get should have thrown'); + } catch (exception) { + expect(exception.message).toEqual('some error'); + + } } finally { - await (keyStore.getKeyStoreClient('secret')).beginDeleteSecret(name); + if (!cleaned) { + await (keyStore.getKeyStoreClient('secret')).beginDeleteSecret(name); + } } }); @@ -173,11 +181,11 @@ describe('KeyStoreKeyVault', () => { await provider.onGenerateKey(alg, false, ['sign'], { keyReference: new KeyReference(name) }); let parts = (keyPair.publicKey.algorithm).kid.split('/'); - const keyName = `${parts[parts.length-2]}/${parts[parts.length-1]}`; + const keyName = `${parts[parts.length - 2]}/${parts[parts.length - 1]}`; let list = await keyStore.list('key', new KeyStoreOptions({ latestVersion: false })); expect(list[name].kids[0].includes(keyName) || list[name].kids[1].includes(keyName)).toBeTruthy(); - const key = await keyStore.get(new KeyReference(name, 'key',keyName), new KeyStoreOptions({ latestVersion: false })); + const key = await keyStore.get(new KeyReference(name, 'key', keyName), new KeyStoreOptions({ latestVersion: false })); expect(key.keys.length).toEqual(1); console.log(`name: ${keyName}`); console.log(`${JSON.stringify(key.keys[0])}`); @@ -209,17 +217,20 @@ describe('KeyStoreKeyVault', () => { const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); - let throwed = false; + await keyStore.save(new KeyReference(name, 'secret'), 'abcdefg'); + let list = await keyStore.list('secret', new KeyStoreOptions({ latestVersion: false })); + expect(list[name]).toBeDefined(); try { - await keyStore.save(new KeyReference(name, 'secret'), 'abcdefg'); - let list = await keyStore.list('secret', new KeyStoreOptions({ latestVersion: false })); - expect(list[name]).toBeDefined(); await cache.get(new KeyReference(name, 'secret')); - expect(throwed).toBeTruthy(); + fail('Should have thrown during get: should set a secret'); } catch (err) { - throwed = true; expect(err.message).toEqual(`${name} not found`) - + } + try { + await keyStore.save(undefined, ''); + fail('Should have thrown during save: should set a secret'); + } catch (err) { + expect(err.message).toEqual(`Key reference needs to be specified`) } finally { await (keyStore.getKeyStoreClient('secret')).beginDeleteSecret(name); } diff --git a/libs/plugin-keyvault/tests/KeyVaultPlugin.spec.ts b/libs/plugin-keyvault/tests/KeyVaultPlugin.spec.ts index 0543eea..c7f43df 100644 --- a/libs/plugin-keyvault/tests/KeyVaultPlugin.spec.ts +++ b/libs/plugin-keyvault/tests/KeyVaultPlugin.spec.ts @@ -8,9 +8,9 @@ import KeyVaultEcdsaProvider from '../src/plugin/KeyVaultEcdsaProvider'; import KeyVaultRsaOaepProvider from '../src/plugin/KeyVaultRsaOaepProvider'; import { KeyStoreOptions, KeyStoreInMemory, KeyReference } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; import { KeyClient } from '@azure/keyvault-keys'; -import { Subtle, CryptoFactoryScope, IKeyGenerationOptions } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; +import { Subtle, IKeyGenerationOptions } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; import Credentials from './Credentials'; -import { KeyVaultProvider, CryptoFactoryKeyVault, SubtleCryptoKeyVault } from '../src'; +import { KeyVaultProvider, SubtleCryptoKeyVault } from '../src'; const clone = require('clone'); // Sample config @@ -20,7 +20,7 @@ const clientSecret = encodeURI(Credentials.clientSecret); const vaultUri = Credentials.vaultUri; const keyVaultEnable = vaultUri.startsWith('https://'); -const subtle = new Subtle(); +const subtleCrypto = new Subtle(); const random = (length: number) => Math.random().toString(36).substring(2, length + 2); const logging = require('adal-node').Logging; logging.setLoggingOptions({ @@ -43,7 +43,7 @@ afterEach(() => { describe('KeyVaultPlugin', () => { if (!keyVaultEnable) { - console.log('Key vault is enabled. Add your credentials to Credentials.ts') + console.log('Key vault is not enabled. Add your credentials to Credentials.ts') return; } @@ -53,13 +53,25 @@ describe('KeyVaultPlugin', () => { const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); try { - const result: CryptoKeyPair = await plugin.onGenerateKey(alg, false, ['sign']); - expect((result.publicKey).algorithm.namedCurve).toEqual('K-256'); - expect(result.publicKey.algorithm.name).toEqual('ECDSA'); - expect((result.publicKey.algorithm).kid.startsWith('https')).toBeTruthy(); - expect((result.publicKey.algorithm).kid.includes(name)).toBeTruthy(); + const keypair: CryptoKeyPair = await plugin.onGenerateKey(alg, false, ['sign']); + expect((keypair.publicKey).algorithm.namedCurve).toEqual('K-256'); + expect(keypair.publicKey.algorithm.name).toEqual('ECDSA'); + expect((keypair.publicKey.algorithm).kid.startsWith('https')).toBeTruthy(); + expect((keypair.publicKey.algorithm).kid.includes(name)).toBeTruthy(); + + const jwk: any = await plugin.exportKey('jwk', keypair.publicKey); + expect(jwk.kid.startsWith('https://')).toBeTruthy(); + + // negative cases + try { + await plugin.exportKey('raw', keypair.publicKey); + fail('export key raw should fail'); + } catch(exception) { + expect(exception.message).toEqual('Export key only supports jwk'); + } + } finally { await (keyStore.getKeyStoreClient('key')).beginDeleteKey(name); } @@ -70,7 +82,7 @@ describe('KeyVaultPlugin', () => { const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); try { let keyReference = new KeyReference(name, 'key'); let curve = 'P-256K'; @@ -91,7 +103,7 @@ describe('KeyVaultPlugin', () => { const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); try { let keyReference = new KeyReference(name, 'key', remoteName); @@ -113,19 +125,42 @@ describe('KeyVaultPlugin', () => { const cache = new KeyStoreInMemory(); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); - const subtle = new SubtleCryptoKeyVault(new Subtle(), keyStore); + const subtleKv = new SubtleCryptoKeyVault(new Subtle(), keyStore); try { const keyReference = new KeyReference(name, 'key', remoteName); const curve = 'P-256K'; const alg = { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }; - const keypair = await subtle.generateKey(alg, false, ['sign', 'verify'], { keyReference, curve }); + const keypair = await subtleKv.generateKey(alg, false, ['sign', 'verify'], { keyReference, curve }); const payload = Buffer.from('hello Houston'); - const signature = await subtle.sign(alg, keypair.publicKey, payload); + const signature = await subtleKv.sign(alg, keypair.publicKey, payload); expect(signature.byteLength).toEqual(64); - const jwk = await subtle.exportKey('jwk', keypair.publicKey); + const jwk = await subtleKv.exportKey('jwk', keypair.publicKey); expect(jwk.kty).toEqual('EC'); + + // negative cases + let publicKey = clone(keypair.publicKey); + delete (publicKey.algorithm).kid; + try { + await subtleKv.sign(alg, publicKey, payload); + fail('sign should throw'); + } catch(exception) { + expect(exception.message).toEqual('Missing kid in algortihm'); + } + + let getCryptoClientSpy: jasmine.Spy = spyOn(keyStore, 'getCryptoClient').and.callFake(() => { + return { + sign: () => Promise.reject(new Error('spy signing error')) + }; + }); + try { + await subtleKv.sign(alg, keypair.publicKey, payload); + fail('sign should throw'); + } catch(exception) { + expect(exception.message).toEqual('spy signing error'); + } + } finally { await (keyStore.getKeyStoreClient('key')).beginDeleteKey(remoteName); } @@ -139,7 +174,7 @@ describe('KeyVaultPlugin', () => { try { let list = await keyStore.list('key', new KeyStoreOptions({ latestVersion: false })); const versions = list[name]; - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); // Generate EC let keyPair: CryptoKeyPair = await plugin.onGenerateKey(alg, false, ['sign', 'verify']); @@ -183,7 +218,7 @@ describe('KeyVaultPlugin', () => { const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); try { - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); const payload = Buffer.from('test'); console.log(payload); @@ -198,8 +233,8 @@ describe('KeyVaultPlugin', () => { const webCryptoAlg = clone(alg); webCryptoAlg.namedCurve = 'K-256'; const jwk = await (await cache.get(new KeyReference(name, 'key'), keyPair.publicKey)).getKey(); - const cryptoKey = await subtle.importKey('jwk', jwk, webCryptoAlg, true, ['verify']); - const result = await subtle.verify(webCryptoAlg, cryptoKey, Buffer.from(signature), payload); + const cryptoKey = await subtleCrypto.importKey('jwk', jwk, webCryptoAlg, true, ['verify']); + const result = await subtleCrypto.verify(webCryptoAlg, cryptoKey, Buffer.from(signature), payload); expect(result).toBeTruthy(); expect((await cache.list())[name]).toBeDefined(); } finally { @@ -215,15 +250,15 @@ describe('KeyVaultPlugin', () => { const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); try { - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); const payload = Buffer.from('test'); console.log(payload); // import reference key const keyReference = new KeyReference(name, 'key'); - let cryptoKey: any = await subtle.generateKey(alg, true, ['sign'], { keyReference }); - let jwk: any = await subtle.exportKey('jwk', cryptoKey.privateKey); + let cryptoKey: any = await subtleCrypto.generateKey(alg, true, ['sign'], { keyReference }); + let jwk: any = await subtleCrypto.exportKey('jwk', cryptoKey.privateKey); jwk.kid = name; await keyStore.save(keyReference, jwk, new KeyStoreOptions()); @@ -231,17 +266,65 @@ describe('KeyVaultPlugin', () => { const cachedPublic = await (await cache.get(keyReference)).getKey(); - cryptoKey = await plugin.importKey('jwk', cachedPublic, alg, false, ['sign', 'verify']); - const signature = await plugin.onSign(alg, cryptoKey, payload); + cryptoKey = await plugin.importKey('jwk', cachedPublic, alg, false, ['sign']); + const signature = await plugin.sign(alg, cryptoKey, payload); // Set verify key const webCryptoAlg = clone(alg); webCryptoAlg.namedCurve = 'K-256'; jwk = (await cache.get(new KeyReference(name, 'key'), new KeyStoreOptions({ publicKeyOnly: true }))).getKey(); - cryptoKey = await subtle.importKey('jwk', jwk, webCryptoAlg, true, ['verify']); - const result = await subtle.verify(webCryptoAlg, cryptoKey, signature, payload); + cryptoKey = await subtleCrypto.importKey('jwk', jwk, webCryptoAlg, true, ['verify']); + const result = await subtleCrypto.verify(webCryptoAlg, cryptoKey, signature, payload); expect(result).toBeTruthy(); expect((await cache.list())[name]).toBeDefined(); + + // negative cases + try { + await plugin.importKey('raw', new Uint8Array([1,2,3,4]), webCryptoAlg, true, ['sign']); + fail('import raw should fail'); + } catch (exception) { + expect(exception.message).toEqual('Import key only supports jwk'); + } + let clonedJwk = clone(jwk); + clonedJwk.kty = 'RSA' + try { + await plugin.importKey('jwk', clonedJwk, webCryptoAlg, true, ['sign']); + fail('import RSA should fail'); + } catch (exception) { + expect(exception.message).toEqual('Import key only supports kty EC'); + } + clonedJwk = clone(jwk); + clonedJwk.crv = 'ed25519'; + try { + await plugin.importKey('jwk', clonedJwk, webCryptoAlg, true, ['sign']); + fail('import crv should fail'); + } catch (exception) { + expect(exception.message).toEqual('Import key only supports crv P-256K'); + } + clonedJwk = clone(jwk); + delete clonedJwk.kid; + try { + await plugin.importKey('jwk', clonedJwk, webCryptoAlg, true, ['sign']); + fail('import crv should fail'); + } catch (exception) { + expect(exception.message).toEqual('Imported key must have a kid in the format https:///keys//'); + } + clonedJwk = clone(jwk); + clonedJwk.kid = 'vaultUri'; + try { + await plugin.importKey('jwk', clonedJwk, webCryptoAlg, true, ['sign']); + fail('import crv should fail'); + } catch (exception) { + expect(exception.message).toEqual('Imported key must have a kid in the format https:///keys//'); + } + clonedJwk = clone(jwk); + clonedJwk.kid = 'https://vault.com'; + try { + await plugin.importKey('jwk', clonedJwk, webCryptoAlg, true, ['sign']); + fail('import crv should fail'); + } catch (exception) { + expect(exception.message).toEqual('Imported key must be of type keys or secrets'); + } } finally { await (keyStore.getKeyStoreClient('key')).beginDeleteKey(name); } @@ -266,7 +349,7 @@ describe('KeyVaultPlugin', () => { const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); try { - const plugin = new KeyVaultEcdsaProvider(subtle, keyStore); + const plugin = new KeyVaultEcdsaProvider(subtleCrypto, keyStore); // Generate EC let keyReference = new KeyReference(name, 'key'); @@ -303,19 +386,31 @@ describe('rsa-oaep', () => { const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); try { - const plugin = new KeyVaultRsaOaepProvider(subtle, keyStore); + const plugin = new KeyVaultRsaOaepProvider(subtleCrypto, keyStore); const payload = Buffer.from('hello houston'); // generate key const keyPair: CryptoKeyPair = await plugin.onGenerateKey(alg, false, ['decrypt', 'encrypt']); // Encrypt with subtle - const cipher = await subtle.encrypt(alg, keyPair.publicKey, payload); + const cipher = await subtleCrypto.encrypt(alg, keyPair.publicKey, payload); // decrypt with key vault const decrypt = await plugin.onDecrypt(alg, keyPair.publicKey, cipher); expect(Buffer.from(decrypt)).toEqual(payload); expect((await cache.list())[name]).toBeDefined(); + + // negative cases + let clonedPk = clone(keyPair.publicKey); + delete clonedPk.algorithm.kid; + + try { + await plugin.decrypt(alg, clonedPk, cipher); + fail('decrypt RSA should fail'); + } catch (exception) { + expect(exception.message).toEqual('Missing kid in algortihm'); + } + } finally { await (keyStore.getKeyStoreClient('key')).beginDeleteKey(name); } diff --git a/libs/plugin-keyvault/tests/SubtleCryptoKeyVault.spec.ts b/libs/plugin-keyvault/tests/SubtleCryptoKeyVault.spec.ts new file mode 100644 index 0000000..4403183 --- /dev/null +++ b/libs/plugin-keyvault/tests/SubtleCryptoKeyVault.spec.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { KeyStoreInMemory } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; +import { KeyStoreKeyVault, SubtleCryptoKeyVault } from '../src'; +import { ClientSecretCredential } from '@azure/identity'; +import { Subtle } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; +import Credentials from './Credentials'; + +describe('SubtleCryptoKeyVault', () => { + // Sample config + const tenantId = Credentials.tenantGuid; + const clientId = Credentials.clientId; + const clientSecret = encodeURI(Credentials.clientSecret); + const vaultUri = Credentials.vaultUri; + const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + const keyVaultEnable = vaultUri.startsWith('https://'); + + const cache = new KeyStoreInMemory(); + const keyStore = new KeyStoreKeyVault(credential, vaultUri, cache); + const subtle = new Subtle(); + const subtleKv: any = new SubtleCryptoKeyVault(subtle, keyStore); + + if (!keyVaultEnable) { + console.log('Key vault is not enabled. Add your credentials to Credentials.ts') + return; + } + + const genKey = async () => { + const cryptoKey = await subtleKv.generateKey({ name: "ECDSA", hash: { name: "SHA-256" }, namedCurve: 'secp256k1' }, true, ["sign", "verify"]); + return cryptoKey; + } + + it('should create instance', () => { + let subtleKv: any = new SubtleCryptoKeyVault(subtle, keyStore); + expect(subtleKv.getSubtleCrypto().constructor.name).toEqual('SubtleCryptoKeyVault'); + }); + + it('should generate key', async () => { + const cryptoKey = await genKey(); + expect(cryptoKey).toBeDefined(); + }); + + it('should test algorithmTransform', () => { + let alg: any = {test: 'name'}; + expect(subtleKv.algorithmTransform(alg)).toEqual(alg); + alg = {foo: 'fighters'}; + expect(subtleKv.algorithmTransform(alg)).toEqual(alg); + }); + it('should test keyImportTransform', () => { + let jwk: any = { test: 'name' }; + expect(subtleKv.keyImportTransform(jwk)).toEqual(jwk); + jwk = { foo: 'fighters' }; + expect(subtleKv.keyImportTransform(jwk)).toEqual(jwk); + jwk = { crv: 'P-256K' }; + expect(subtleKv.keyImportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + jwk = { crv: 'SECP256K1' }; + expect(subtleKv.keyImportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + jwk = { crv: 'XXX' }; + expect(subtleKv.keyImportTransform(jwk)).toEqual({ crv: 'XXX' }); + + }); + it('should test keyExportTransform', () => { + let jwk: any = { test: 'name' }; + expect(subtleKv.keyExportTransform(jwk)).toEqual(jwk); + jwk = { foo: 'fighters' }; + expect(subtleKv.keyExportTransform(jwk)).toEqual(jwk); + jwk = { crv: 'P-256K' }; + expect(subtleKv.keyExportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + jwk = { crv: 'K-256' }; + expect(subtleKv.keyExportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + jwk = { crv: 'SECP256K1' }; + expect(subtleKv.keyExportTransform(jwk)).toEqual({ crv: 'SECP256K1' }); + }); +}); \ No newline at end of file diff --git a/libs/plugin/package.json b/libs/plugin/package.json index d62b1ee..59f2f19 100644 --- a/libs/plugin/package.json +++ b/libs/plugin/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-plugin", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "description": "Package for plugeable crypto based on subtle crypto.", "repository": { "type": "git", @@ -35,8 +35,8 @@ "typescript": "4.0.3" }, "dependencies": { - "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", "@peculiar/webcrypto": "1.1.3", "@types/node": "14.6.2", "base64url": "^3.0.1", diff --git a/libs/protocol-jose/package.json b/libs/protocol-jose/package.json index 4e765dd..53592a9 100644 --- a/libs/protocol-jose/package.json +++ b/libs/protocol-jose/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-protocol-jose", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "repository": { "type": "git", "url": "https://github.com/microsoft/VerifiableCredentials-Crypto-SDK-Typescript.git" @@ -39,11 +39,11 @@ "typescript": "4.0.3" }, "dependencies": { - "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-protocols-common": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-protocols-common": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites": "1.1.12-preview.1", "base64url": "^3.0.1", "typescript-map": "0.0.7", "webcrypto-core": "1.1.8" diff --git a/libs/protocols-common/package.json b/libs/protocols-common/package.json index 1294b1e..de73407 100644 --- a/libs/protocols-common/package.json +++ b/libs/protocols-common/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript-protocols-common", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "repository": { "type": "git", "url": "https://github.com/microsoft/VerifiableCredentials-Crypto-SDK-Typescript.git" @@ -39,9 +39,9 @@ "typescript": "4.0.3" }, "dependencies": { - "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", "base64url": "3.0.1", "typescript-map": "0.0.7", "webcrypto-core": "1.1.8" diff --git a/libs/sdk/package.json b/libs/sdk/package.json index cb9daa6..641ba0e 100644 --- a/libs/sdk/package.json +++ b/libs/sdk/package.json @@ -1,6 +1,6 @@ { "name": "verifiablecredentials-crypto-sdk-typescript", - "version": "1.1.12-preview.0", + "version": "1.1.12-preview.1", "repository": { "type": "git", "url": "https://github.com/microsoft/VerifiableCredentials-Crypto-SDK-Typescript.git" @@ -53,15 +53,15 @@ "jsonld": "2.0.2", "typescript-map": "0.0.7", "uuid": "^8.3.1", - "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-factory": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-plugin-keyvault": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-protocol-jose": "1.1.12-preview.0", - "verifiablecredentials-crypto-sdk-typescript-protocols-common": "1.1.12-preview.0", + "verifiablecredentials-crypto-sdk-typescript-keys": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-keystore": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-cryptofactory-suites": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-elliptic": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-factory": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-plugin-keyvault": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-protocol-jose": "1.1.12-preview.1", + "verifiablecredentials-crypto-sdk-typescript-protocols-common": "1.1.12-preview.1", "webcrypto-core": "1.1.8" }, "nyc": { From 38255517700a7b3a6b4807340ca181fa15f4b9b8 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Wed, 13 Jan 2021 12:04:35 +0000 Subject: [PATCH 11/16] jsw protocols unit tests --- libs/protocol-jose/lib/JoseProtocol.ts | 4 +- libs/protocol-jose/package.json | 1 + libs/protocol-jose/tests/JoseHelpers.spec.ts | 18 +- libs/protocol-jose/tests/JoseToken.spec.ts | 26 ++ libs/protocol-jose/tests/JwsToken.spec.ts | 387 +++++++++++-------- 5 files changed, 275 insertions(+), 161 deletions(-) create mode 100644 libs/protocol-jose/tests/JoseToken.spec.ts diff --git a/libs/protocol-jose/lib/JoseProtocol.ts b/libs/protocol-jose/lib/JoseProtocol.ts index 229afd7..f73e459 100644 --- a/libs/protocol-jose/lib/JoseProtocol.ts +++ b/libs/protocol-jose/lib/JoseProtocol.ts @@ -107,7 +107,7 @@ export default class JoseProtocol implements IPayloadProtection { const cipher: JweToken = JweToken.fromCryptoToken(token, options); return cipher.serialize(protocolFormat); default: - throw new CryptoProtocolError(JoseConstants.Jose, `Serialization format '${format}' is not supported`); + throw new CryptoProtocolError(JoseConstants.Jose, `Serialization format '${protocolFormat}' is not supported`); } } @@ -131,7 +131,7 @@ export default class JoseProtocol implements IPayloadProtection { const jweProtectOptions = JweToken.fromPayloadProtectionOptions(options); return JweToken.toCryptoToken(protocolFormat, JweToken.deserialize(token, jweProtectOptions), options); default: - throw new CryptoProtocolError(JoseConstants.Jose, `Serialization format '${format}' is not supported`); + throw new CryptoProtocolError(JoseConstants.Jose, `Serialization format '${protocolFormat}' is not supported`); } } diff --git a/libs/protocol-jose/package.json b/libs/protocol-jose/package.json index 53592a9..f6485ef 100644 --- a/libs/protocol-jose/package.json +++ b/libs/protocol-jose/package.json @@ -28,6 +28,7 @@ "devDependencies": { "@types/jasmine": "^2.8.9", "@types/node": "14.6.2", + "clone": "^2.1.2", "jasmine": "3.6.3", "jasmine-reporters": "^2.3.2", "jasmine-spec-reporter": "^6.0.0", diff --git a/libs/protocol-jose/tests/JoseHelpers.spec.ts b/libs/protocol-jose/tests/JoseHelpers.spec.ts index c7df166..062798d 100644 --- a/libs/protocol-jose/tests/JoseHelpers.spec.ts +++ b/libs/protocol-jose/tests/JoseHelpers.spec.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { JoseHelpers } from "../lib/index"; import { CryptoProtocolError } from 'verifiablecredentials-crypto-sdk-typescript-protocols-common'; -import { KeyType } from 'verifiablecredentials-crypto-sdk-typescript-keys'; +import { JoseConstants, KeyType, KeyUse } from 'verifiablecredentials-crypto-sdk-typescript-keys'; import { TSMap } from 'typescript-map'; describe('JoseHelpers', () => { @@ -46,9 +46,21 @@ describe('JoseHelpers', () => { it(`should return the key type for 'EC' via JWA`, () => { expect(JoseHelpers.createTypeViaJwa('ES256K')).toEqual(KeyType.EC); }); - + it(`should return the key type for 'RSA' via JWA`, () => { expect(JoseHelpers.createTypeViaJwa('RS256')).toEqual(KeyType.RSA); }); - + + it('should create use via jwa', () => { + expect(JoseHelpers.createUseViaJwa(JoseConstants.Rs256)).toEqual(KeyUse.Signature); + expect(JoseHelpers.createUseViaJwa(JoseConstants.Rs384)).toEqual(KeyUse.Signature); + expect(JoseHelpers.createUseViaJwa(JoseConstants.Rs512)).toEqual(KeyUse.Signature); + expect(JoseHelpers.createUseViaJwa(JoseConstants.RsaOaep)).toEqual(KeyUse.Encryption); + expect(JoseHelpers.createUseViaJwa(JoseConstants.Es256K)).toEqual(KeyUse.Signature); + expect(JoseHelpers.createUseViaJwa('EdDSA')).toEqual(KeyUse.Signature); + + expect(() => JoseHelpers.createUseViaJwa(JoseConstants.AesGcm128)).toThrowError(`The algorithm 'AES-GCM' is not supported`); + expect(() => JoseHelpers.createUseViaJwa(JoseConstants.AesGcm192)).toThrowError(`The algorithm 'AES-GCM' is not supported`); + expect(() => JoseHelpers.createUseViaJwa(JoseConstants.AesGcm256)).toThrowError(`The algorithm 'AES-GCM' is not supported`); + }) }); diff --git a/libs/protocol-jose/tests/JoseToken.spec.ts b/libs/protocol-jose/tests/JoseToken.spec.ts new file mode 100644 index 0000000..dde48ea --- /dev/null +++ b/libs/protocol-jose/tests/JoseToken.spec.ts @@ -0,0 +1,26 @@ +import { TSMap } from "typescript-map"; +import { JoseConstants } from "verifiablecredentials-crypto-sdk-typescript-keys"; +import { KeyStoreInMemory } from "verifiablecredentials-crypto-sdk-typescript-keystore"; +import { CryptoFactory, SubtleCryptoNode } from "verifiablecredentials-crypto-sdk-typescript-plugin"; +import { IPayloadProtectionOptions } from "verifiablecredentials-crypto-sdk-typescript-protocols-common"; +import { JoseProtocol, JoseToken } from "../lib"; + +describe('JoseToken', () => { + const keyStore = new KeyStoreInMemory(); + const cryptoFactory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()) + const options: IPayloadProtectionOptions = { + cryptoFactory, + options: new TSMap([ + [JoseConstants.optionProtectedHeader, new TSMap([['typ', 'JWT']])] + ]), + payloadProtection: new JoseProtocol() + }; + + it('should instantiate a token', () => { + let token = new JoseToken(options); + expect(() => token.tokenFormat()).toThrowError('The token format is not found'); + + token.set(JoseConstants.tokenFormat, 'JwsCompactJson'); + expect(token.tokenFormat()).toEqual('JwsCompactJson'); + }) +}); \ No newline at end of file diff --git a/libs/protocol-jose/tests/JwsToken.spec.ts b/libs/protocol-jose/tests/JwsToken.spec.ts index 277c442..b3d6d0d 100644 --- a/libs/protocol-jose/tests/JwsToken.spec.ts +++ b/libs/protocol-jose/tests/JwsToken.spec.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { JwsToken, JoseConstants, IJwsSigningOptions, JoseProtocol } from '../lib/index' -import { IPayloadProtectionOptions } from 'verifiablecredentials-crypto-sdk-typescript-protocols-common'; -import { KeyStoreInMemory, ProtectionFormat, KeyReference, KeyStoreOptions } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; +import { CryptoProtocolError, IPayloadProtectionOptions } from 'verifiablecredentials-crypto-sdk-typescript-protocols-common'; +import { KeyStoreInMemory, ProtectionFormat, KeyReference, KeyStoreOptions, CryptoError } from 'verifiablecredentials-crypto-sdk-typescript-keystore'; import { CryptoFactory, SubtleCryptoNode, SubtleCryptoExtension } from 'verifiablecredentials-crypto-sdk-typescript-plugin'; import { OctKey, PublicKey, KeyContainer } from 'verifiablecredentials-crypto-sdk-typescript-keys'; import { TSMap } from "typescript-map"; +const clone = require('clone'); describe('JwsToken', () => { it('should create a jws token', async () => { @@ -18,20 +19,61 @@ describe('JwsToken', () => { await keyStore.save(new KeyReference(seedReference), new OctKey('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); const subtle = SubtleCryptoNode.getSubtleCrypto(); const options: IJwsSigningOptions = { - algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, - cryptoFactory: new CryptoFactory(keyStore, subtle) - }; - const generate = new SubtleCryptoExtension(options.cryptoFactory); + algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, + cryptoFactory: new CryptoFactory(keyStore, subtle) + }; + const generate = new SubtleCryptoExtension(options.cryptoFactory); - const privateKey = await generate.generatePairwiseKey(options.algorithm, seedReference, 'did:personaId', 'did:peerId'); - (privateKey).alg = 'ES256K'; - (privateKey).defaultSignAlgorithm = 'ES256K'; - - await keyStore.save(new KeyReference('key'), privateKey); - const jwsToken = new JwsToken(options); - const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsGeneralJson); - expect(signature).toBeDefined(); + const privateKey = await generate.generatePairwiseKey(options.algorithm, seedReference, 'did:personaId', 'did:peerId'); + (privateKey).alg = 'ES256K'; + (privateKey).defaultSignAlgorithm = 'ES256K'; + + await keyStore.save(new KeyReference('key'), privateKey); + const jwsToken = new JwsToken(options); + const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsGeneralJson); + expect(signature).toBeDefined(); + + expect(jwsToken.serialize()).toBeDefined(); + expect(jwsToken.serialize(ProtectionFormat.JwsGeneralJson)).toBeDefined(); + expect(jwsToken.serialize(ProtectionFormat.JwsFlatJson)).toBeDefined(); + expect(jwsToken.serialize(ProtectionFormat.JwsCompactJson)).toBeDefined(); + + // negative cases + expect(() => jwsToken.serialize('aaa')).toThrow(new CryptoProtocolError(JoseConstants.Jws,`The format 'JwsGeneralJson' is not supported`)); +/* + let clonedJwsToken: JwsToken = clone(jwsToken); + clonedJwsToken.signatures[0] = undefined; + expect(() => jwsToken.serialize()).toThrowError(`The format 'JwsGeneralJson' is not supported`); +*/ }); + it('should create a jws token in compact', async () => { + const payload = 'test payload'; + const keyStore = new KeyStoreInMemory(); + const seedReference = 'seed'; + await keyStore.save(new KeyReference(seedReference), new OctKey('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + const subtle = SubtleCryptoNode.getSubtleCrypto(); + const options: IJwsSigningOptions = { + algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, + cryptoFactory: new CryptoFactory(keyStore, subtle) + }; + const generate = new SubtleCryptoExtension(options.cryptoFactory); + + const privateKey = (await generate.generateKey(options.algorithm, true, ['sign', 'verify'])).privateKey; + const jwk: any = await generate.exportKey('jwk', privateKey); + + (jwk).alg = 'ES256K'; + (jwk).defaultSignAlgorithm = 'ES256K'; + + await keyStore.save(new KeyReference('key'), jwk); + const jwsToken = new JwsToken(options); + const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsCompactJson); + expect(signature).toBeDefined(); + const serialized = signature.serialize(); + expect(serialized.split('.').length).toEqual(3); + const token = JwsToken.deserialize(serialized); + expect(token.payload.length).toEqual(12); + }); + it('should create a jws token with JWT header', async () => { const payload = 'test payload'; const keyStore = new KeyStoreInMemory(); @@ -39,20 +81,20 @@ describe('JwsToken', () => { await keyStore.save(new KeyReference(seedReference), new OctKey('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); const subtle = SubtleCryptoNode.getSubtleCrypto(); const options: IJwsSigningOptions = { - protected: new TSMap([['typ', 'JWT']]), - algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, - cryptoFactory: new CryptoFactory(keyStore, subtle) - }; - const generate = new SubtleCryptoExtension(options.cryptoFactory); + protected: new TSMap([['typ', 'JWT']]), + algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, + cryptoFactory: new CryptoFactory(keyStore, subtle) + }; + const generate = new SubtleCryptoExtension(options.cryptoFactory); - const privateKey = await generate.generatePairwiseKey(options.algorithm, seedReference, 'did:personaId', 'did:peerId'); - (privateKey).alg = 'ES256K'; - (privateKey).defaultSignAlgorithm = 'ES256K'; - - await keyStore.save(new KeyReference('key'), privateKey); - const jwsToken = new JwsToken(options); - const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsGeneralJson); - expect((>signature.signatures[0].protected).get('typ')).toEqual('JWT'); + const privateKey = await generate.generatePairwiseKey(options.algorithm, seedReference, 'did:personaId', 'did:peerId'); + (privateKey).alg = 'ES256K'; + (privateKey).defaultSignAlgorithm = 'ES256K'; + + await keyStore.save(new KeyReference('key'), privateKey); + const jwsToken = new JwsToken(options); + const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsGeneralJson); + expect((>signature.signatures[0].protected).get('typ')).toEqual('JWT'); }); it('should create a jws token by means of key reference options', async () => { const payload = 'test payload'; @@ -61,36 +103,36 @@ describe('JwsToken', () => { await keyStore.save(new KeyReference(seedReference), new OctKey('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); const subtle = SubtleCryptoNode.getSubtleCrypto(); const options: IJwsSigningOptions = { - algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, - cryptoFactory: new CryptoFactory(keyStore, subtle) - }; - const generate = new SubtleCryptoExtension(options.cryptoFactory); + algorithm: { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' } }, + cryptoFactory: new CryptoFactory(keyStore, subtle) + }; + const generate = new SubtleCryptoExtension(options.cryptoFactory); - const privateKey = await generate.generatePairwiseKey(options.algorithm, seedReference, 'did:personaId', 'did:peerId'); - (privateKey).alg = 'ES256K'; - (privateKey).defaultSignAlgorithm = 'ES256K'; - - await keyStore.save(new KeyReference('key'), privateKey); - const jwsToken = new JwsToken(options); - const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsGeneralJson); - expect(signature).toBeDefined(); + const privateKey = await generate.generatePairwiseKey(options.algorithm, seedReference, 'did:personaId', 'did:peerId'); + (privateKey).alg = 'ES256K'; + (privateKey).defaultSignAlgorithm = 'ES256K'; + + await keyStore.save(new KeyReference('key'), privateKey); + const jwsToken = new JwsToken(options); + const signature = await jwsToken.sign(new KeyReference('key'), Buffer.from(payload), ProtectionFormat.JwsGeneralJson); + expect(signature).toBeDefined(); }); it('should create, validate and serialize a JwsToken', async () => { - const payload = 'The true sign of intelligence is not knowledge but imagination.'; + const payload = 'The true sign of intelligence is not knowledge but imagination.'; const keyStore = new KeyStoreInMemory(); await keyStore.save(new KeyReference('seed'), new OctKey('ABEE')); const cryptoFactory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()) const options: IPayloadProtectionOptions = { - cryptoFactory, - options: new TSMap([ - [JoseConstants.optionProtectedHeader, new TSMap([['typ', 'JWT']]) ] - ]), - payloadProtection: new JoseProtocol() + cryptoFactory, + options: new TSMap([ + [JoseConstants.optionProtectedHeader, new TSMap([['typ', 'JWT']])] + ]), + payloadProtection: new JoseProtocol() }; - + const alg = { name: 'RSASSA-PKCS1-V1_5', hash: 'SHA-256', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]) }; const generator = new SubtleCryptoExtension(cryptoFactory); - const privateKey = await generator.generatePairwiseKey(alg, 'seed', 'persona','peer'); + const privateKey = await generator.generatePairwiseKey(alg, 'seed', 'persona', 'peer'); expect((privateKey).alg).toBeUndefined(); (privateKey).alg = 'RS256'; await keyStore.save(new KeyReference('key'), privateKey); @@ -116,9 +158,11 @@ describe('JwsToken', () => { expect(deSignatures[0].protected).toEqual(signatures[0].protected); expect(deSignatures[0].signature).toEqual(signatures[0].signature); expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); + let staticDeserialize = JoseProtocol.deserialize(serialized, options); + expect(deserialized).toEqual(staticDeserialize); // validate - const publicKeyContainer = (await keyStore.get(new KeyReference('key'), new KeyStoreOptions({publicKeyOnly: true}))).getKey(); + const publicKeyContainer = (await keyStore.get(new KeyReference('key'), new KeyStoreOptions({ publicKeyOnly: true }))).getKey(); const result = await options.payloadProtection.verify([publicKeyContainer], Buffer.from(payload), signature, options); expect(result.result).toBeTruthy(); @@ -128,130 +172,161 @@ describe('JwsToken', () => { expect(parsed['payload']).toBeDefined(); expect(parsed['protected']).toBeDefined(); expect(parsed['signature']).toBeDefined(); - + deserialized = options.payloadProtection.deserialize(serialized, 'JwsFlatJson', options); deSignatures = deserialized.get(JoseConstants.tokenSignatures); expect(deSignatures[0].protected).toEqual(signatures[0].protected); expect(deSignatures[0].signature).toEqual(signatures[0].signature); expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); + staticDeserialize = JoseProtocol.deserialize(serialized, options); + expect(deserialized).toEqual(staticDeserialize); + + // Compact serialization + serialized = options.payloadProtection.serialize(signature, 'JwsCompactJson', options); + parsed = serialized.split('.'); + expect(parsed.length).toEqual(3); + + deserialized = options.payloadProtection.deserialize(serialized, 'JwsCompactJson', options); + deSignatures = deserialized.get(JoseConstants.tokenSignatures); + expect(deSignatures[0].protected).toEqual(signatures[0].protected); + expect(deSignatures[0].signature).toEqual(signatures[0].signature); + expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); + staticDeserialize = JoseProtocol.deserialize(serialized, options); + expect(deserialized).toEqual(staticDeserialize); + + // negative cases + let getProtectionFormatSpy: jasmine.Spy = spyOn(options.payloadProtection, 'getProtectionFormat').and.callFake(() => { + throw new Error('spy bad format'); + }); + try { + options.payloadProtection.serialize(signature, 'JwsCompactJson', options); + } catch (exception) { + expect(exception.message).toEqual('spy bad format'); + } + try { + options.payloadProtection.deserialize(serialized, 'JwsCompactJson', options); + } catch (exception) { + expect(exception.message).toEqual('spy bad format'); + } + getProtectionFormatSpy.and.callFake(() => { + return 'aaa'; + }); + try { + options.payloadProtection.serialize(signature, 'JwsCompactJson', options); + } catch (exception) { + expect(exception.message).toEqual( `Serialization format 'aaa' is not supported`); + } + try { + options.payloadProtection.deserialize(serialized, 'JwsCompactJson', options); + } catch (exception) { + expect(exception.message).toEqual(`Serialization format 'aaa' is not supported`); + } + }); + + // tslint:disable-next-line: max-func-body-length + it('should set headers in JwsToken', async () => { + const payload = 'The true sign of intelligence is not knowledge but imagination.'; + const keyStore = new KeyStoreInMemory(); + await keyStore.save(new KeyReference('seed'), new OctKey('ABEE')); + const cryptoFactory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()) + const options: IPayloadProtectionOptions = { + cryptoFactory: cryptoFactory, + options: new TSMap([ + [JoseConstants.optionHeader, new TSMap([['test', 'ES256K']])], + [JoseConstants.optionProtectedHeader, new TSMap([['test', 'elo'], ['kid', 'random']])] + ]), + payloadProtection: new JoseProtocol() + }; + + const alg = { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' }, format: 'DER' }; + const generator = new SubtleCryptoExtension(cryptoFactory); + const privateKey = await generator.generatePairwiseKey(alg, 'seed', 'persona', 'peer'); + (privateKey).alg = 'ES256K'; + await keyStore.save(new KeyReference('key'), privateKey); + let publicKey = await keyStore.get(new KeyReference('key')); + + // sign + const signature = await options.payloadProtection.sign(new KeyReference('key'), Buffer.from(payload), 'JwsGeneralJson', options); + const signatures = signature.get(JoseConstants.tokenSignatures); + expect(signatures[0].protected.get('test')).toEqual('elo'); + expect(signatures[0].protected.get('kid')).toEqual('random'); + expect(signatures[0].header.get('test')).toEqual('ES256K'); + expect(signatures[0].signature).toBeDefined(); + expect(signature.get(JoseConstants.tokenPayload)).toEqual(Buffer.from(payload)); + + // serialize + let serialized = options.payloadProtection.serialize(signature, 'JwsGeneralJson', options); + let parsed = JSON.parse(serialized); + expect(parsed['payload']).toBeDefined(); + expect(parsed['signatures']).toBeDefined(); + + // deserialize + let deserialized = options.payloadProtection.deserialize(serialized, 'JwsGeneralJson', options); + let deSignatures = deserialized.get(JoseConstants.tokenSignatures); + expect(deSignatures[0].protected).toEqual(signatures[0].protected); + expect(deSignatures[0].header).toEqual(signatures[0].header); + expect(deSignatures[0].signature).toEqual(signatures[0].signature); + expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); + + // validate + const publicKeyContainer = (await keyStore.get(new KeyReference('key'), new KeyStoreOptions({ publicKeyOnly: true }))).getKey(); + const result = await options.payloadProtection.verify([publicKeyContainer], Buffer.from(payload), signature, options); + expect(result.result).toBeTruthy(); + + // Flat serialization + serialized = options.payloadProtection.serialize(signature, 'JwsFlatJson', options); + parsed = JSON.parse(serialized); + expect(parsed['payload']).toBeDefined(); + expect(parsed['protected']).toBeDefined(); + expect(parsed['header']).toBeDefined(); + expect(parsed['signature']).toBeDefined(); + + deserialized = options.payloadProtection.deserialize(serialized, 'JwsFlatJson', options); + deSignatures = deserialized.get(JoseConstants.tokenSignatures); + expect(deSignatures[0].protected).toEqual(signatures[0].protected); + expect(deSignatures[0].header).toEqual(signatures[0].header); + expect(deSignatures[0].signature).toEqual(signatures[0].signature); + expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); // Compact serialization serialized = options.payloadProtection.serialize(signature, 'JwsCompactJson', options); parsed = serialized.split('.'); expect(parsed.length).toEqual(3); - + deserialized = options.payloadProtection.deserialize(serialized, 'JwsCompactJson', options); deSignatures = deserialized.get(JoseConstants.tokenSignatures); expect(deSignatures[0].protected).toEqual(signatures[0].protected); expect(deSignatures[0].signature).toEqual(signatures[0].signature); expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); - }); + // negative cases + let throwed = false; + try { + options.payloadProtection.serialize(signature, 'bluesky', options); + } catch (err) { + throwed = true; + expect(err.message).toEqual(`Format 'bluesky' is not supported`); + } + expect(throwed).toBeTruthy(); -// tslint:disable-next-line: max-func-body-length - it('should set headers in JwsToken', async () => { - const payload = 'The true sign of intelligence is not knowledge but imagination.'; - const keyStore = new KeyStoreInMemory(); - await keyStore.save(new KeyReference('seed'), new OctKey('ABEE')); - const cryptoFactory = new CryptoFactory(keyStore, SubtleCryptoNode.getSubtleCrypto()) - const options: IPayloadProtectionOptions = { - cryptoFactory: cryptoFactory, - options: new TSMap([ - [JoseConstants.optionHeader, new TSMap([['test', 'ES256K']]) ], - [JoseConstants.optionProtectedHeader, new TSMap([['test', 'elo'], ['kid', 'random']]) ] - ]), - payloadProtection: new JoseProtocol() - }; - - const alg = { name: 'ECDSA', namedCurve: 'secp256k1', hash: { name: 'SHA-256' }, format: 'DER' }; - const generator = new SubtleCryptoExtension(cryptoFactory); - const privateKey = await generator.generatePairwiseKey(alg, 'seed', 'persona','peer'); - (privateKey).alg = 'ES256K'; - await keyStore.save(new KeyReference('key'), privateKey); - let publicKey = await keyStore.get(new KeyReference('key')); + throwed = false; + try { + options.payloadProtection.deserialize(serialized, 'bluesky', options); + } catch (err) { + throwed = true; + expect(err.message).toEqual(`Format 'bluesky' is not supported`); + } + expect(throwed).toBeTruthy(); - // sign - const signature = await options.payloadProtection.sign(new KeyReference('key'), Buffer.from(payload), 'JwsGeneralJson', options); - const signatures = signature.get(JoseConstants.tokenSignatures); - expect(signatures[0].protected.get('test')).toEqual('elo'); - expect(signatures[0].protected.get('kid')).toEqual('random'); - expect(signatures[0].header.get('test')).toEqual('ES256K'); - expect(signatures[0].signature).toBeDefined(); - expect(signature.get(JoseConstants.tokenPayload)).toEqual(Buffer.from(payload)); - - // serialize - let serialized = options.payloadProtection.serialize(signature, 'JwsGeneralJson', options); - let parsed = JSON.parse(serialized); - expect(parsed['payload']).toBeDefined(); - expect(parsed['signatures']).toBeDefined(); - - // deserialize - let deserialized = options.payloadProtection.deserialize(serialized, 'JwsGeneralJson', options); - let deSignatures = deserialized.get(JoseConstants.tokenSignatures); - expect(deSignatures[0].protected).toEqual(signatures[0].protected); - expect(deSignatures[0].header).toEqual(signatures[0].header); - expect(deSignatures[0].signature).toEqual(signatures[0].signature); - expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); - - // validate - const publicKeyContainer = (await keyStore.get(new KeyReference('key'), new KeyStoreOptions({publicKeyOnly: true}))).getKey(); - const result = await options.payloadProtection.verify([publicKeyContainer], Buffer.from(payload), signature, options); - expect(result.result).toBeTruthy(); - - // Flat serialization - serialized = options.payloadProtection.serialize(signature, 'JwsFlatJson', options); - parsed = JSON.parse(serialized); - expect(parsed['payload']).toBeDefined(); - expect(parsed['protected']).toBeDefined(); - expect(parsed['header']).toBeDefined(); - expect(parsed['signature']).toBeDefined(); - - deserialized = options.payloadProtection.deserialize(serialized, 'JwsFlatJson', options); - deSignatures = deserialized.get(JoseConstants.tokenSignatures); - expect(deSignatures[0].protected).toEqual(signatures[0].protected); - expect(deSignatures[0].header).toEqual(signatures[0].header); - expect(deSignatures[0].signature).toEqual(signatures[0].signature); - expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); - - // Compact serialization - serialized = options.payloadProtection.serialize(signature, 'JwsCompactJson', options); - parsed = serialized.split('.'); - expect(parsed.length).toEqual(3); - - deserialized = options.payloadProtection.deserialize(serialized, 'JwsCompactJson', options); - deSignatures = deserialized.get(JoseConstants.tokenSignatures); - expect(deSignatures[0].protected).toEqual(signatures[0].protected); - expect(deSignatures[0].signature).toEqual(signatures[0].signature); - expect(deserialized.get(JoseConstants.tokenPayload)).toEqual(signature.get(JoseConstants.tokenPayload)); - - // negative cases - let throwed = false; - try { - options.payloadProtection.serialize(signature, 'bluesky', options); - } catch (err) { - throwed = true; - expect(err.message).toEqual(`Format 'bluesky' is not supported`); - } - expect(throwed).toBeTruthy(); - - throwed = false; - try { - options.payloadProtection.deserialize(serialized, 'bluesky', options); - } catch (err) { - throwed = true; - expect(err.message).toEqual(`Format 'bluesky' is not supported`); - } - expect(throwed).toBeTruthy(); - - const sigs = signature.get(JoseConstants.tokenSignatures); - sigs[0].protected.set('alg', ''); - throwed = false; - try { - await options.payloadProtection.verify([publicKey], Buffer.from(payload), signature, options); - } catch (err) { - expect(err.message).toEqual('Unable to validate signature as no signature algorithm has been specified in the header.'); - throwed = true; - } - expect(throwed).toBeTruthy(); - }); + const sigs = signature.get(JoseConstants.tokenSignatures); + sigs[0].protected.set('alg', ''); + throwed = false; + try { + await options.payloadProtection.verify([publicKey], Buffer.from(payload), signature, options); + } catch (err) { + expect(err.message).toEqual('Unable to validate signature as no signature algorithm has been specified in the header.'); + throwed = true; + } + expect(throwed).toBeTruthy(); }); +}); From 25fba69c15979008f93dfd76ce37403c475d2d81 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Fri, 15 Jan 2021 09:14:28 +0000 Subject: [PATCH 12/16] Protocol unit tests --- libs/protocol-jose/tests/JwsToken.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/protocol-jose/tests/JwsToken.spec.ts b/libs/protocol-jose/tests/JwsToken.spec.ts index b3d6d0d..a50bd38 100644 --- a/libs/protocol-jose/tests/JwsToken.spec.ts +++ b/libs/protocol-jose/tests/JwsToken.spec.ts @@ -35,8 +35,6 @@ describe('JwsToken', () => { expect(jwsToken.serialize()).toBeDefined(); expect(jwsToken.serialize(ProtectionFormat.JwsGeneralJson)).toBeDefined(); - expect(jwsToken.serialize(ProtectionFormat.JwsFlatJson)).toBeDefined(); - expect(jwsToken.serialize(ProtectionFormat.JwsCompactJson)).toBeDefined(); // negative cases expect(() => jwsToken.serialize('aaa')).toThrow(new CryptoProtocolError(JoseConstants.Jws,`The format 'JwsGeneralJson' is not supported`)); From c7cf05a56444a621f3f33ea433008b1b31f6123d Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Fri, 15 Jan 2021 09:18:18 +0000 Subject: [PATCH 13/16] prepare for merge --- CHANGE_LOG.md | 4 ++++ libs/protocol-jose/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index ff6c01c..f35f5d7 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -3,6 +3,10 @@ **Type of change:** engineering **Customer impact:** low +## Round one to come to 100% line coverage. +**Type of change:** engineering +**Customer impact:** low + # version 1.1.12-preview.0 ## Remove all console.log calls from the SDK **Type of change:** engineering diff --git a/libs/protocol-jose/package.json b/libs/protocol-jose/package.json index f6485ef..3d113e8 100644 --- a/libs/protocol-jose/package.json +++ b/libs/protocol-jose/package.json @@ -1,5 +1,5 @@ { - "name": "verifiablecredentials-crypto-sdk-typescript-protocol-jose", + "name": "pnp", "version": "1.1.12-preview.1", "repository": { "type": "git", From 2205924ca174d709935d5fb47a8750ced24f7f59 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Fri, 15 Jan 2021 10:03:30 +0000 Subject: [PATCH 14/16] Make sure all async functions reject --- libs/plugin-elliptic/src/EllipticEcDsaProvider.ts | 4 ++-- libs/plugin-elliptic/src/EllipticEdDsaProvider.ts | 4 ++-- libs/plugin-keyvault/src/plugin/KeyVaultRsaOaepProvider.ts | 2 +- libs/plugin/lib/Pairwise/EcPairwiseKey.ts | 2 +- libs/protocol-jose/lib/jwe/JweToken.ts | 4 ++-- libs/protocol-jose/lib/jws/JwsToken.ts | 4 ++-- libs/sdk/lib/LongFormDid.ts | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/plugin-elliptic/src/EllipticEcDsaProvider.ts b/libs/plugin-elliptic/src/EllipticEcDsaProvider.ts index a1f5325..036ca38 100644 --- a/libs/plugin-elliptic/src/EllipticEcDsaProvider.ts +++ b/libs/plugin-elliptic/src/EllipticEcDsaProvider.ts @@ -115,7 +115,7 @@ export default class EllipticEcDsaProvider extends EllipticDsaProvider { async onExportKey (format: KeyFormat, key: CryptoKey): Promise { //const ec = this.getCurve((key.algorithm).namedCurve); if (format !== 'jwk') { - throw new Error(`Export key only supports jwk`); + return Promise.reject(new Error(`Export key only supports jwk`)); } const cryptoKey: any = ( key).key; @@ -151,7 +151,7 @@ export default class EllipticEcDsaProvider extends EllipticDsaProvider { async onImportKey (format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { if (format !== 'jwk') { - throw new Error(`Import key only supports jwk`); + return Promise.reject(new Error(`Import key only supports jwk`)); } const ec = this.getCurve(algorithm.namedCurve); const jwkKey: JsonWebKey = keyData; diff --git a/libs/plugin-elliptic/src/EllipticEdDsaProvider.ts b/libs/plugin-elliptic/src/EllipticEdDsaProvider.ts index d7216e7..92a4709 100644 --- a/libs/plugin-elliptic/src/EllipticEdDsaProvider.ts +++ b/libs/plugin-elliptic/src/EllipticEdDsaProvider.ts @@ -116,7 +116,7 @@ export default class EllipticEdDsaProvider extends EllipticDsaProvider { async onImportKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { if (format !== 'jwk') { - throw new Error(`Import key only supports jwk`); + return Promise.reject(new Error(`Import key only supports jwk`)); } const ec = this.getCurve(algorithm.namedCurve); const jwkKey: any = keyData; @@ -144,7 +144,7 @@ export default class EllipticEdDsaProvider extends EllipticDsaProvider { async onExportKey(format: KeyFormat, key: CryptoKey): Promise { //const ec = this.getCurve((key.algorithm).namedCurve); if (format !== 'jwk') { - throw new Error(`Export key only supports jwk`); + return Promise.reject(new Error(`Export key only supports jwk`)); } const ecKey: any = (key).key; diff --git a/libs/plugin-keyvault/src/plugin/KeyVaultRsaOaepProvider.ts b/libs/plugin-keyvault/src/plugin/KeyVaultRsaOaepProvider.ts index 746fa86..545b4df 100644 --- a/libs/plugin-keyvault/src/plugin/KeyVaultRsaOaepProvider.ts +++ b/libs/plugin-keyvault/src/plugin/KeyVaultRsaOaepProvider.ts @@ -46,7 +46,7 @@ export default class KeyVaultRsaOaepProvider extends KeyVaultProvider { async onDecrypt (algorithm: Algorithm, key: CryptoKey, data: ArrayBuffer): Promise { const kid = (key.algorithm).kid; if (!kid) { - throw new CryptoError(algorithm, 'Missing kid in algortihm'); + return Promise.reject(new CryptoError(algorithm, 'Missing kid in algortihm')); } const client = (this.keyStore).getCryptoClient(kid); diff --git a/libs/plugin/lib/Pairwise/EcPairwiseKey.ts b/libs/plugin/lib/Pairwise/EcPairwiseKey.ts index 3e09d12..b8fd12d 100644 --- a/libs/plugin/lib/Pairwise/EcPairwiseKey.ts +++ b/libs/plugin/lib/Pairwise/EcPairwiseKey.ts @@ -46,7 +46,7 @@ const SUPPORTED_CURVES = ['K-256', 'P-256K', 'secp256k1', 'ed25519']; const pairwiseKeySeed = await crypto.sign(alg, key, Buffer.from(peerId)); if (SUPPORTED_CURVES.indexOf(algorithm.namedCurve) === -1) { - throw new CryptoError(algorithm, `Curve ${algorithm.namedCurve} is not supported`); + return Promise.reject(new CryptoError(algorithm, `Curve ${algorithm.namedCurve} is not supported`)); } let privateKey = new BN(Buffer.from(pairwiseKeySeed)); diff --git a/libs/protocol-jose/lib/jwe/JweToken.ts b/libs/protocol-jose/lib/jwe/JweToken.ts index e3f345b..d2fa880 100644 --- a/libs/protocol-jose/lib/jwe/JweToken.ts +++ b/libs/protocol-jose/lib/jwe/JweToken.ts @@ -443,7 +443,7 @@ export default class JweToken implements IJweGeneralJson { let keyEncryptionAlgorithm: string | undefined = publicKey.alg; if (!keyEncryptionAlgorithm) { if (publicKey.kty == KeyType.EC) { - throw new Error('EC encryption not implemented'); + return Promise.reject(new Error('EC encryption not implemented')); } else { // Default RSA algorithm keyEncryptionAlgorithm = JoseConstants.RsaOaep256; @@ -553,7 +553,7 @@ export default class JweToken implements IJweGeneralJson { } if (!contentEncryptionKey) { - throw new CryptoProtocolError(JoseConstants.Jwe, 'Cannot decrypt the content encryption key because of missing key'); + return Promise.reject(new CryptoProtocolError(JoseConstants.Jwe, 'Cannot decrypt the content encryption key because of missing key')); } // Decrypt content diff --git a/libs/protocol-jose/lib/jws/JwsToken.ts b/libs/protocol-jose/lib/jws/JwsToken.ts index 28831ab..be477b7 100644 --- a/libs/protocol-jose/lib/jws/JwsToken.ts +++ b/libs/protocol-jose/lib/jws/JwsToken.ts @@ -559,10 +559,10 @@ export default class JwsToken implements IJwsGeneralJson { } if (!alg) { - throw new CryptoProtocolError( + return Promise.reject(new CryptoProtocolError( JoseConstants.Jws, 'Unable to validate signature as no signature algorithm has been specified in the header.' - ); + )); } const algorithm = CryptoHelpers.jwaToWebCrypto(alg); const encodedProtected = !protectedHeader ? '' : JoseHelpers.encodeHeader(protectedHeader); diff --git a/libs/sdk/lib/LongFormDid.ts b/libs/sdk/lib/LongFormDid.ts index 49fe511..e9b2c3f 100644 --- a/libs/sdk/lib/LongFormDid.ts +++ b/libs/sdk/lib/LongFormDid.ts @@ -20,11 +20,11 @@ export default class LongFormDid { // See https://github.com/diafygi/webcrypto-examples for examples how to use the W3C web Crypto stamdard if (!this.crypto.builder.signingKeyReference) { - throw new Error(`No signing key reference. Use CryptoBuilder.useSigningKeyReference.`) + return Promise.reject(new Error(`No signing key reference. Use CryptoBuilder.useSigningKeyReference.`)); } if (!this.crypto.builder.recoveryKeyReference) { - throw new Error(`No recovery key reference. Use CryptoBuilder.useRecoveryKeyReference.`) + return Promise.reject(new Error(`No recovery key reference. Use CryptoBuilder.useRecoveryKeyReference.`)); } let signingPublic = await (await this.crypto.builder.keyStore.get(this.crypto.builder.signingKeyReference, new KeyStoreOptions({publicKeyOnly: true}))).getKey(); From e598a6ffee2e219460e7c7c8cf2b98567dc21f98 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Fri, 15 Jan 2021 10:08:58 +0000 Subject: [PATCH 15/16] Make sure all async methods reject --- libs/plugin/lib/Pairwise/PairwiseKey.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/plugin/lib/Pairwise/PairwiseKey.ts b/libs/plugin/lib/Pairwise/PairwiseKey.ts index 7abf5c8..7492abe 100644 --- a/libs/plugin/lib/Pairwise/PairwiseKey.ts +++ b/libs/plugin/lib/Pairwise/PairwiseKey.ts @@ -50,7 +50,7 @@ import EcPairwiseKey from "./EcPairwiseKey"; return RsaPairwiseKey.generate(this.cryptoFactory, personaMasterKey, algorithm, peerId); default: - throw new CryptoError(algorithm, `Pairwise key for type '${keyType}' is not supported.`); + return Promise.reject(new CryptoError(algorithm, `Pairwise key for type '${keyType}' is not supported.`)); } } From 5b6e2dfbebf4c4abe65094971e8c7614b862a743 Mon Sep 17 00:00:00 2001 From: Ronny Bjones Date: Fri, 15 Jan 2021 13:24:38 +0000 Subject: [PATCH 16/16] Update package for jose --- CHANGE_LOG.md | 4 ++++ libs/protocol-jose/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index f35f5d7..0cb5284 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -3,6 +3,10 @@ **Type of change:** engineering **Customer impact:** low +## async functions no longer throw but reject an Error +**Type of change:** engineering +**Customer impact:** low + ## Round one to come to 100% line coverage. **Type of change:** engineering **Customer impact:** low diff --git a/libs/protocol-jose/package.json b/libs/protocol-jose/package.json index 3d113e8..f6485ef 100644 --- a/libs/protocol-jose/package.json +++ b/libs/protocol-jose/package.json @@ -1,5 +1,5 @@ { - "name": "pnp", + "name": "verifiablecredentials-crypto-sdk-typescript-protocol-jose", "version": "1.1.12-preview.1", "repository": { "type": "git",