From 14368b408f792b639accb93f702eb8855dcedc9e Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Oct 2023 07:27:43 +0000 Subject: [PATCH 1/8] Add unwarpKey to typescript interface --- js/ccf-app/src/crypto.ts | 5 +++ js/ccf-app/src/global.ts | 12 +++++++ js/ccf-app/src/polyfill.ts | 56 +++++++++++++++++++++++++++++ js/ccf-app/test/crypto.ts | 60 -------------------------------- js/ccf-app/test/polyfill.test.ts | 7 ++-- 5 files changed, 76 insertions(+), 64 deletions(-) diff --git a/js/ccf-app/src/crypto.ts b/js/ccf-app/src/crypto.ts index a5c23806ebe3..d0047ebb4236 100644 --- a/js/ccf-app/src/crypto.ts +++ b/js/ccf-app/src/crypto.ts @@ -42,6 +42,11 @@ export const generateEddsaKeyPair = ccf.crypto.generateEddsaKeyPair; */ export const wrapKey = ccf.crypto.wrapKey; +/** + * @inheritDoc global!CCFCrypto.unwrapKey + */ +export const unwrapKey = ccf.crypto.unwrapKey; + /** * @inheritDoc global!CCFCrypto.sign */ diff --git a/js/ccf-app/src/global.ts b/js/ccf-app/src/global.ts index b8441530888c..fe7baf2d393d 100644 --- a/js/ccf-app/src/global.ts +++ b/js/ccf-app/src/global.ts @@ -387,6 +387,18 @@ export interface CCFCrypto { wrapAlgo: WrapAlgoParams, ): ArrayBuffer; + /** + * Unwraps a key using a wrapping key. + * + * Constraints on the `key` and `wrappingKey` parameters depend + * on the wrapping algorithm that is used (`wrapAlgo`). + */ + unwrapKey( + key: ArrayBuffer, + wrappingKey: ArrayBuffer, + wrapAlgo: WrapAlgoParams, + ): ArrayBuffer; + /** * Generate a digest (hash) of the given data. */ diff --git a/js/ccf-app/src/polyfill.ts b/js/ccf-app/src/polyfill.ts index a6cc01ee2440..f62fd4cd9824 100644 --- a/js/ccf-app/src/polyfill.ts +++ b/js/ccf-app/src/polyfill.ts @@ -311,6 +311,54 @@ class CCFPolyfill implements CCF { throw new Error("unsupported wrapAlgo.name"); } }, + unwrapKey( + wrappedKey: ArrayBuffer, + unwrappingKey: ArrayBuffer, + unwrapAlgo: WrapAlgoParams, + ): ArrayBuffer { + if (unwrapAlgo.name == "RSA-OAEP") { + return nodeBufToArrBuf( + jscrypto.privateDecrypt( + { + key: Buffer.from(unwrappingKey), + oaepHash: "sha256", + padding: jscrypto.constants.RSA_PKCS1_OAEP_PADDING, + }, + new Uint8Array(wrappedKey), + ), + ); + } else if (unwrapAlgo.name == "AES-KWP") { + const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649 + const decipher = jscrypto.createDecipheriv( + "id-aes256-wrap-pad", + new Uint8Array(unwrappingKey), + iv, + ); + return nodeBufToArrBuf( + Buffer.concat([ + decipher.update(new Uint8Array(wrappedKey)), + decipher.final(), + ]), + ); + } else if (unwrapAlgo.name == "RSA-OAEP-AES-KWP") { + const keyInfo = jscrypto.createPrivateKey(Buffer.from(unwrappingKey)); + // asymmetricKeyDetails added in Node.js 15.7.0, we're at 14. + console.log(`Modulus length: `, keyInfo?.asymmetricKeyDetails?.modulusLength); + const modulusLengthInBytes = (keyInfo?.asymmetricKeyDetails?.modulusLength || 2048) / 8; + + const wrap1 = wrappedKey.slice(0, modulusLengthInBytes); + const wrap2 = wrappedKey.slice(modulusLengthInBytes); + const aesKey = this.unwrapKey(wrap1, unwrappingKey, { + name: "RSA-OAEP", + label: unwrapAlgo.label, + }); + return this.unwrapKey(wrap2, aesKey, { + name: "AES-KWP", + }); + } else { + throw new Error("unsupported unwrapAlgo.name"); + } + }, digest(algorithm: DigestAlgorithm, data: ArrayBuffer): ArrayBuffer { if (algorithm === "SHA-256") { return nodeBufToArrBuf( @@ -533,6 +581,14 @@ class CCFPolyfill implements CCF { return this.crypto.wrapKey(key, wrappingKey, parameters); } + unwrapKey( + key: ArrayBuffer, + wrappingKey: ArrayBuffer, + parameters: WrapAlgoParams, + ): ArrayBuffer { + return this.crypto.unwrapKey(key, wrappingKey, parameters); + } + digest(algorithm: DigestAlgorithm, data: ArrayBuffer): ArrayBuffer { return this.crypto.digest(algorithm, data); } diff --git a/js/ccf-app/test/crypto.ts b/js/ccf-app/test/crypto.ts index 3302708cc523..77ecafa481e2 100644 --- a/js/ccf-app/test/crypto.ts +++ b/js/ccf-app/test/crypto.ts @@ -1,65 +1,5 @@ import * as crypto from "crypto"; import forge from "node-forge"; -import { WrapAlgoParams } from "../src/global.js"; - -function nodeBufToArrBuf(buf: Buffer): ArrayBuffer { - // Note: buf.buffer is not safe, see docs. - const arrBuf = new ArrayBuffer(buf.byteLength); - buf.copy(new Uint8Array(arrBuf)); - return arrBuf; -} - -export function unwrapKey( - wrappedKey: ArrayBuffer, - unwrappingKey: ArrayBuffer, - unwrapAlgo: WrapAlgoParams, -): ArrayBuffer { - if (unwrapAlgo.name == "RSA-OAEP") { - return nodeBufToArrBuf( - crypto.privateDecrypt( - { - key: Buffer.from(unwrappingKey), - oaepHash: "sha256", - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - }, - new Uint8Array(wrappedKey), - ), - ); - } else if (unwrapAlgo.name == "AES-KWP") { - const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649 - const decipher = crypto.createDecipheriv( - "id-aes256-wrap-pad", - new Uint8Array(unwrappingKey), - iv, - ); - return nodeBufToArrBuf( - Buffer.concat([ - decipher.update(new Uint8Array(wrappedKey)), - decipher.final(), - ]), - ); - } else if (unwrapAlgo.name == "RSA-OAEP-AES-KWP") { - /* - const keyInfo = crypto.createPrivateKey(unwrappingKey); - // asymmetricKeyDetails added in Node.js 15.7.0, we're at 14. - const modulusLengthInBytes = keyInfo.asymmetricKeyDetails.modulusLength / 8; - */ - // For now, hard-coded for the test in polyfill.test.ts. - const modulusLengthInBytes = 2048 / 8; - - const wrap1 = wrappedKey.slice(0, modulusLengthInBytes); - const wrap2 = wrappedKey.slice(modulusLengthInBytes); - const aesKey = unwrapKey(wrap1, unwrappingKey, { - name: "RSA-OAEP", - label: unwrapAlgo.label, - }); - return unwrapKey(wrap2, aesKey, { - name: "AES-KWP", - }); - } else { - throw new Error("unsupported unwrapAlgo.name"); - } -} export function generateSelfSignedCert() { const keys = crypto.generateKeyPairSync("rsa", { diff --git a/js/ccf-app/test/polyfill.test.ts b/js/ccf-app/test/polyfill.test.ts index b7553d3c34e6..ec87f593e922 100644 --- a/js/ccf-app/test/polyfill.test.ts +++ b/js/ccf-app/test/polyfill.test.ts @@ -9,7 +9,6 @@ import { RsaOaepParams, } from "../src/global.js"; import { - unwrapKey, generateSelfSignedCert, generateCertChain, } from "./crypto.js"; @@ -92,7 +91,7 @@ describe("polyfill", function () { ccf.strToBuf(wrappingKey.publicKey), wrapAlgo, ); - const unwrapped = unwrapKey( + const unwrapped = ccf.crypto.unwrapKey( wrapped, ccf.strToBuf(wrappingKey.privateKey), wrapAlgo, @@ -106,7 +105,7 @@ describe("polyfill", function () { name: "AES-KWP", }; const wrapped = ccf.crypto.wrapKey(key, wrappingKey, wrapAlgo); - const unwrapped = unwrapKey(wrapped, wrappingKey, wrapAlgo); + const unwrapped = ccf.crypto.unwrapKey(wrapped, wrappingKey, wrapAlgo); assert.deepEqual(unwrapped, key); }); it("performs RSA-OAEP-AES-KWP wrapping correctly", function () { @@ -121,7 +120,7 @@ describe("polyfill", function () { ccf.strToBuf(wrappingKey.publicKey), wrapAlgo, ); - const unwrapped = unwrapKey( + const unwrapped = ccf.crypto.unwrapKey( wrapped, ccf.strToBuf(wrappingKey.privateKey), wrapAlgo, From 9c231d28351643c35f45d47dadd61d81e721a55d Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Oct 2023 07:44:10 +0000 Subject: [PATCH 2/8] Ran prettier --- js/ccf-app/src/polyfill.ts | 62 ++++++++++--------- js/ccf-app/test/polyfill.test.ts | 101 +++++++++++++++---------------- 2 files changed, 82 insertions(+), 81 deletions(-) diff --git a/js/ccf-app/src/polyfill.ts b/js/ccf-app/src/polyfill.ts index f62fd4cd9824..c0cfc5a10d35 100644 --- a/js/ccf-app/src/polyfill.ts +++ b/js/ccf-app/src/polyfill.ts @@ -68,7 +68,7 @@ class KvMapPolyfill implements KvMap { this.map.clear(); } forEach( - callback: (value: ArrayBuffer, key: ArrayBuffer, kvmap: KvMap) => void, + callback: (value: ArrayBuffer, key: ArrayBuffer, kvmap: KvMap) => void ): void { this.map.forEach((value, key, _) => { callback(value, unbase64(key), this); @@ -108,7 +108,7 @@ class CCFPolyfill implements CCF { handle: number, startSeqno: number, endSeqno: number, - secondsUntilExpiry: number, + secondsUntilExpiry: number ) { throw new Error("Not implemented"); }, @@ -131,7 +131,7 @@ class CCFPolyfill implements CCF { sign( algorithm: SigningAlgorithm, key: string, - data: ArrayBuffer, + data: ArrayBuffer ): ArrayBuffer { if (algorithm.name === "HMAC") { const hashAlg = (algorithm.hash as string) @@ -176,7 +176,7 @@ class CCFPolyfill implements CCF { algorithm: SigningAlgorithm, key: string, signature: ArrayBuffer, - data: ArrayBuffer, + data: ArrayBuffer ): boolean { let padding = undefined; const pubKey = jscrypto.createPublicKey(key); @@ -202,7 +202,7 @@ class CCFPolyfill implements CCF { null, new Uint8Array(data), pubKey, - new Uint8Array(signature), + new Uint8Array(signature) ); } const hashAlg = (algorithm.hash as string).replace("-", "").toLowerCase(); @@ -214,7 +214,7 @@ class CCFPolyfill implements CCF { dsaEncoding: "ieee-p1363", padding: padding, }, - new Uint8Array(signature), + new Uint8Array(signature) ); }, generateAesKey(size: number): ArrayBuffer { @@ -269,7 +269,7 @@ class CCFPolyfill implements CCF { wrapKey( key: ArrayBuffer, wrappingKey: ArrayBuffer, - parameters: WrapAlgoParams, + parameters: WrapAlgoParams ): ArrayBuffer { if (parameters.name === "RSA-OAEP") { return nodeBufToArrBuf( @@ -282,18 +282,18 @@ class CCFPolyfill implements CCF { : undefined, padding: jscrypto.constants.RSA_PKCS1_OAEP_PADDING, }, - new Uint8Array(key), - ), + new Uint8Array(key) + ) ); } else if (parameters.name === "AES-KWP") { const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649 const cipher = jscrypto.createCipheriv( "id-aes256-wrap-pad", new Uint8Array(wrappingKey), - iv, + iv ); return nodeBufToArrBuf( - Buffer.concat([cipher.update(new Uint8Array(key)), cipher.final()]), + Buffer.concat([cipher.update(new Uint8Array(key)), cipher.final()]) ); } else if (parameters.name === "RSA-OAEP-AES-KWP") { const randomAesKey = this.generateAesKey(parameters.aesKeySize); @@ -305,7 +305,7 @@ class CCFPolyfill implements CCF { name: "AES-KWP", }); return nodeBufToArrBuf( - Buffer.concat([Buffer.from(wrap1), Buffer.from(wrap2)]), + Buffer.concat([Buffer.from(wrap1), Buffer.from(wrap2)]) ); } else { throw new Error("unsupported wrapAlgo.name"); @@ -314,7 +314,7 @@ class CCFPolyfill implements CCF { unwrapKey( wrappedKey: ArrayBuffer, unwrappingKey: ArrayBuffer, - unwrapAlgo: WrapAlgoParams, + unwrapAlgo: WrapAlgoParams ): ArrayBuffer { if (unwrapAlgo.name == "RSA-OAEP") { return nodeBufToArrBuf( @@ -324,28 +324,32 @@ class CCFPolyfill implements CCF { oaepHash: "sha256", padding: jscrypto.constants.RSA_PKCS1_OAEP_PADDING, }, - new Uint8Array(wrappedKey), - ), + new Uint8Array(wrappedKey) + ) ); } else if (unwrapAlgo.name == "AES-KWP") { const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649 const decipher = jscrypto.createDecipheriv( "id-aes256-wrap-pad", new Uint8Array(unwrappingKey), - iv, + iv ); return nodeBufToArrBuf( Buffer.concat([ decipher.update(new Uint8Array(wrappedKey)), decipher.final(), - ]), + ]) ); } else if (unwrapAlgo.name == "RSA-OAEP-AES-KWP") { const keyInfo = jscrypto.createPrivateKey(Buffer.from(unwrappingKey)); // asymmetricKeyDetails added in Node.js 15.7.0, we're at 14. - console.log(`Modulus length: `, keyInfo?.asymmetricKeyDetails?.modulusLength); - const modulusLengthInBytes = (keyInfo?.asymmetricKeyDetails?.modulusLength || 2048) / 8; - + console.log( + `Modulus length: `, + keyInfo?.asymmetricKeyDetails?.modulusLength + ); + const modulusLengthInBytes = + (keyInfo?.asymmetricKeyDetails?.modulusLength || 2048) / 8; + const wrap1 = wrappedKey.slice(0, modulusLengthInBytes); const wrap2 = wrappedKey.slice(modulusLengthInBytes); const aesKey = this.unwrapKey(wrap1, unwrappingKey, { @@ -362,7 +366,7 @@ class CCFPolyfill implements CCF { digest(algorithm: DigestAlgorithm, data: ArrayBuffer): ArrayBuffer { if (algorithm === "SHA-256") { return nodeBufToArrBuf( - jscrypto.createHash("sha256").update(new Uint8Array(data)).digest(), + jscrypto.createHash("sha256").update(new Uint8Array(data)).digest() ); } else { throw new Error("unsupported algorithm"); @@ -388,14 +392,14 @@ class CCFPolyfill implements CCF { return true; } else { throw new Error( - "X509 validation unsupported, Node.js version too old (< 15.6.0)", + "X509 validation unsupported, Node.js version too old (< 15.6.0)" ); } }, isValidX509CertChain(chain: string, trusted: string): boolean { if (!("X509Certificate" in jscrypto)) { throw new Error( - "X509 validation unsupported, Node.js version too old (< 15.6.0)", + "X509 validation unsupported, Node.js version too old (< 15.6.0)" ); } try { @@ -407,7 +411,7 @@ class CCFPolyfill implements CCF { } const pems = items.slice(0, -1).map((p) => p + sep); const arr = pems.map( - (pem) => new (jscrypto).X509Certificate(pem), + (pem) => new (jscrypto).X509Certificate(pem) ); return arr; }; @@ -432,7 +436,7 @@ class CCFPolyfill implements CCF { } } throw new Error( - "none of the chain certificates are identical to or issued by a trusted certificate", + "none of the chain certificates are identical to or issued by a trusted certificate" ); } catch (e: any) { console.error(`certificate chain validation failed: ${e.message}`); @@ -576,7 +580,7 @@ class CCFPolyfill implements CCF { wrapKey( key: ArrayBuffer, wrappingKey: ArrayBuffer, - parameters: WrapAlgoParams, + parameters: WrapAlgoParams ): ArrayBuffer { return this.crypto.wrapKey(key, wrappingKey, parameters); } @@ -584,7 +588,7 @@ class CCFPolyfill implements CCF { unwrapKey( key: ArrayBuffer, wrappingKey: ArrayBuffer, - parameters: WrapAlgoParams, + parameters: WrapAlgoParams ): ArrayBuffer { return this.crypto.unwrapKey(key, wrappingKey, parameters); } @@ -616,7 +620,7 @@ class OpenEnclavePolyfill implements OpenEnclave { verifyOpenEnclaveEvidence( format: string | undefined, evidence: ArrayBuffer, - endorsements?: ArrayBuffer, + endorsements?: ArrayBuffer ): EvidenceClaims { throw new Error("Method not implemented."); } @@ -629,7 +633,7 @@ class SnpAttestationPolyfill implements SnpAttestation { evidence: ArrayBuffer, endorsements: ArrayBuffer, uvm_endorsements?: ArrayBuffer, - endorsed_tcb?: string, + endorsed_tcb?: string ): SnpAttestationResult { throw new Error("Method not implemented."); } diff --git a/js/ccf-app/test/polyfill.test.ts b/js/ccf-app/test/polyfill.test.ts index ec87f593e922..394dbebdf873 100644 --- a/js/ccf-app/test/polyfill.test.ts +++ b/js/ccf-app/test/polyfill.test.ts @@ -8,10 +8,7 @@ import { RsaOaepAesKwpParams, RsaOaepParams, } from "../src/global.js"; -import { - generateSelfSignedCert, - generateCertChain, -} from "./crypto.js"; +import { generateSelfSignedCert, generateCertChain } from "./crypto.js"; beforeEach(function () { // clear KV before each test @@ -40,7 +37,7 @@ describe("polyfill", function () { assert.equal(ccf.crypto.generateAesKey(256).byteLength, 32); assert.notDeepEqual( ccf.crypto.generateAesKey(256), - ccf.crypto.generateAesKey(256), + ccf.crypto.generateAesKey(256) ); }); }); @@ -89,12 +86,12 @@ describe("polyfill", function () { const wrapped = ccf.crypto.wrapKey( key, ccf.strToBuf(wrappingKey.publicKey), - wrapAlgo, + wrapAlgo ); const unwrapped = ccf.crypto.unwrapKey( wrapped, ccf.strToBuf(wrappingKey.privateKey), - wrapAlgo, + wrapAlgo ); assert.deepEqual(unwrapped, key); }); @@ -118,12 +115,12 @@ describe("polyfill", function () { const wrapped = ccf.crypto.wrapKey( key, ccf.strToBuf(wrappingKey.publicKey), - wrapAlgo, + wrapAlgo ); const unwrapped = ccf.crypto.unwrapKey( wrapped, ccf.strToBuf(wrappingKey.privateKey), - wrapAlgo, + wrapAlgo ); assert.deepEqual(unwrapped, key); }); @@ -148,7 +145,7 @@ describe("polyfill", function () { hash: "SHA-256", }, privateKey, - data, + data ); { @@ -161,8 +158,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature), - ), + new Uint8Array(signature) + ) ); } @@ -175,8 +172,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); { @@ -189,8 +186,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature), - ), + new Uint8Array(signature) + ) ); } }); @@ -213,7 +210,7 @@ describe("polyfill", function () { hash: "SHA-256", }, privateKey, - data, + data ); { @@ -226,8 +223,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature), - ), + new Uint8Array(signature) + ) ); } @@ -240,8 +237,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); { @@ -254,8 +251,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature), - ), + new Uint8Array(signature) + ) ); } }); @@ -276,7 +273,7 @@ describe("polyfill", function () { name: "EdDSA", }, privateKey, - data, + data ); assert.isTrue( @@ -284,8 +281,8 @@ describe("polyfill", function () { null, new Uint8Array(data), publicKey, - new Uint8Array(signature), - ), + new Uint8Array(signature) + ) ); // Also `signature` should be verified successfully with the JS API @@ -296,8 +293,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); assert.isFalse( @@ -305,8 +302,8 @@ describe("polyfill", function () { null, new Uint8Array(ccf.strToBuf("bar")), publicKey, - new Uint8Array(signature), - ), + new Uint8Array(signature) + ) ); }); it("performs HMAC sign correctly", function () { @@ -328,7 +325,7 @@ describe("polyfill", function () { hash: ccfHash as DigestAlgorithm, }, key, - data, + data ); { @@ -372,8 +369,8 @@ describe("polyfill", function () { }, cert, signature, - data, - ), + data + ) ); assert.isTrue( ccf.crypto.verifySignature( @@ -383,8 +380,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); assert.isNotTrue( ccf.crypto.verifySignature( @@ -394,8 +391,8 @@ describe("polyfill", function () { }, cert, signature, - ccf.strToBuf("bar"), - ), + ccf.strToBuf("bar") + ) ); assert.throws(() => ccf.crypto.verifySignature( @@ -405,8 +402,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); }); it("performs ECDSA validation correctly", function () { @@ -439,8 +436,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); assert.isNotTrue( ccf.crypto.verifySignature( @@ -450,8 +447,8 @@ describe("polyfill", function () { }, publicKey, signature, - ccf.strToBuf("bar"), - ), + ccf.strToBuf("bar") + ) ); assert.throws(() => ccf.crypto.verifySignature( @@ -461,8 +458,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); }); it("performs EdDSA validation correctly", function () { @@ -480,7 +477,7 @@ describe("polyfill", function () { const signature = crypto.sign( null, new Uint8Array(data), - crypto.createPrivateKey(privateKey), + crypto.createPrivateKey(privateKey) ); assert.isTrue( ccf.crypto.verifySignature( @@ -489,8 +486,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); assert.isNotTrue( ccf.crypto.verifySignature( @@ -499,8 +496,8 @@ describe("polyfill", function () { }, publicKey, signature, - ccf.strToBuf("bar"), - ), + ccf.strToBuf("bar") + ) ); assert.throws(() => ccf.crypto.verifySignature( @@ -510,8 +507,8 @@ describe("polyfill", function () { }, publicKey, signature, - data, - ), + data + ) ); }); }); From af78fea5185c5d377b49c327a86fc013321f8723 Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Oct 2023 07:56:35 +0000 Subject: [PATCH 3/8] prettier check --- js/ccf-app/src/polyfill.ts | 54 +++++++++--------- js/ccf-app/test/polyfill.test.ts | 96 ++++++++++++++++---------------- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/js/ccf-app/src/polyfill.ts b/js/ccf-app/src/polyfill.ts index c0cfc5a10d35..8e61a046215f 100644 --- a/js/ccf-app/src/polyfill.ts +++ b/js/ccf-app/src/polyfill.ts @@ -68,7 +68,7 @@ class KvMapPolyfill implements KvMap { this.map.clear(); } forEach( - callback: (value: ArrayBuffer, key: ArrayBuffer, kvmap: KvMap) => void + callback: (value: ArrayBuffer, key: ArrayBuffer, kvmap: KvMap) => void, ): void { this.map.forEach((value, key, _) => { callback(value, unbase64(key), this); @@ -108,7 +108,7 @@ class CCFPolyfill implements CCF { handle: number, startSeqno: number, endSeqno: number, - secondsUntilExpiry: number + secondsUntilExpiry: number, ) { throw new Error("Not implemented"); }, @@ -131,7 +131,7 @@ class CCFPolyfill implements CCF { sign( algorithm: SigningAlgorithm, key: string, - data: ArrayBuffer + data: ArrayBuffer, ): ArrayBuffer { if (algorithm.name === "HMAC") { const hashAlg = (algorithm.hash as string) @@ -176,7 +176,7 @@ class CCFPolyfill implements CCF { algorithm: SigningAlgorithm, key: string, signature: ArrayBuffer, - data: ArrayBuffer + data: ArrayBuffer, ): boolean { let padding = undefined; const pubKey = jscrypto.createPublicKey(key); @@ -202,7 +202,7 @@ class CCFPolyfill implements CCF { null, new Uint8Array(data), pubKey, - new Uint8Array(signature) + new Uint8Array(signature), ); } const hashAlg = (algorithm.hash as string).replace("-", "").toLowerCase(); @@ -214,7 +214,7 @@ class CCFPolyfill implements CCF { dsaEncoding: "ieee-p1363", padding: padding, }, - new Uint8Array(signature) + new Uint8Array(signature), ); }, generateAesKey(size: number): ArrayBuffer { @@ -269,7 +269,7 @@ class CCFPolyfill implements CCF { wrapKey( key: ArrayBuffer, wrappingKey: ArrayBuffer, - parameters: WrapAlgoParams + parameters: WrapAlgoParams, ): ArrayBuffer { if (parameters.name === "RSA-OAEP") { return nodeBufToArrBuf( @@ -282,18 +282,18 @@ class CCFPolyfill implements CCF { : undefined, padding: jscrypto.constants.RSA_PKCS1_OAEP_PADDING, }, - new Uint8Array(key) - ) + new Uint8Array(key), + ), ); } else if (parameters.name === "AES-KWP") { const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649 const cipher = jscrypto.createCipheriv( "id-aes256-wrap-pad", new Uint8Array(wrappingKey), - iv + iv, ); return nodeBufToArrBuf( - Buffer.concat([cipher.update(new Uint8Array(key)), cipher.final()]) + Buffer.concat([cipher.update(new Uint8Array(key)), cipher.final()]), ); } else if (parameters.name === "RSA-OAEP-AES-KWP") { const randomAesKey = this.generateAesKey(parameters.aesKeySize); @@ -305,7 +305,7 @@ class CCFPolyfill implements CCF { name: "AES-KWP", }); return nodeBufToArrBuf( - Buffer.concat([Buffer.from(wrap1), Buffer.from(wrap2)]) + Buffer.concat([Buffer.from(wrap1), Buffer.from(wrap2)]), ); } else { throw new Error("unsupported wrapAlgo.name"); @@ -314,7 +314,7 @@ class CCFPolyfill implements CCF { unwrapKey( wrappedKey: ArrayBuffer, unwrappingKey: ArrayBuffer, - unwrapAlgo: WrapAlgoParams + unwrapAlgo: WrapAlgoParams, ): ArrayBuffer { if (unwrapAlgo.name == "RSA-OAEP") { return nodeBufToArrBuf( @@ -324,28 +324,28 @@ class CCFPolyfill implements CCF { oaepHash: "sha256", padding: jscrypto.constants.RSA_PKCS1_OAEP_PADDING, }, - new Uint8Array(wrappedKey) - ) + new Uint8Array(wrappedKey), + ), ); } else if (unwrapAlgo.name == "AES-KWP") { const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649 const decipher = jscrypto.createDecipheriv( "id-aes256-wrap-pad", new Uint8Array(unwrappingKey), - iv + iv, ); return nodeBufToArrBuf( Buffer.concat([ decipher.update(new Uint8Array(wrappedKey)), decipher.final(), - ]) + ]), ); } else if (unwrapAlgo.name == "RSA-OAEP-AES-KWP") { const keyInfo = jscrypto.createPrivateKey(Buffer.from(unwrappingKey)); // asymmetricKeyDetails added in Node.js 15.7.0, we're at 14. console.log( `Modulus length: `, - keyInfo?.asymmetricKeyDetails?.modulusLength + keyInfo?.asymmetricKeyDetails?.modulusLength, ); const modulusLengthInBytes = (keyInfo?.asymmetricKeyDetails?.modulusLength || 2048) / 8; @@ -366,7 +366,7 @@ class CCFPolyfill implements CCF { digest(algorithm: DigestAlgorithm, data: ArrayBuffer): ArrayBuffer { if (algorithm === "SHA-256") { return nodeBufToArrBuf( - jscrypto.createHash("sha256").update(new Uint8Array(data)).digest() + jscrypto.createHash("sha256").update(new Uint8Array(data)).digest(), ); } else { throw new Error("unsupported algorithm"); @@ -392,14 +392,14 @@ class CCFPolyfill implements CCF { return true; } else { throw new Error( - "X509 validation unsupported, Node.js version too old (< 15.6.0)" + "X509 validation unsupported, Node.js version too old (< 15.6.0)", ); } }, isValidX509CertChain(chain: string, trusted: string): boolean { if (!("X509Certificate" in jscrypto)) { throw new Error( - "X509 validation unsupported, Node.js version too old (< 15.6.0)" + "X509 validation unsupported, Node.js version too old (< 15.6.0)", ); } try { @@ -411,7 +411,7 @@ class CCFPolyfill implements CCF { } const pems = items.slice(0, -1).map((p) => p + sep); const arr = pems.map( - (pem) => new (jscrypto).X509Certificate(pem) + (pem) => new (jscrypto).X509Certificate(pem), ); return arr; }; @@ -436,7 +436,7 @@ class CCFPolyfill implements CCF { } } throw new Error( - "none of the chain certificates are identical to or issued by a trusted certificate" + "none of the chain certificates are identical to or issued by a trusted certificate", ); } catch (e: any) { console.error(`certificate chain validation failed: ${e.message}`); @@ -580,7 +580,7 @@ class CCFPolyfill implements CCF { wrapKey( key: ArrayBuffer, wrappingKey: ArrayBuffer, - parameters: WrapAlgoParams + parameters: WrapAlgoParams, ): ArrayBuffer { return this.crypto.wrapKey(key, wrappingKey, parameters); } @@ -588,7 +588,7 @@ class CCFPolyfill implements CCF { unwrapKey( key: ArrayBuffer, wrappingKey: ArrayBuffer, - parameters: WrapAlgoParams + parameters: WrapAlgoParams, ): ArrayBuffer { return this.crypto.unwrapKey(key, wrappingKey, parameters); } @@ -620,7 +620,7 @@ class OpenEnclavePolyfill implements OpenEnclave { verifyOpenEnclaveEvidence( format: string | undefined, evidence: ArrayBuffer, - endorsements?: ArrayBuffer + endorsements?: ArrayBuffer, ): EvidenceClaims { throw new Error("Method not implemented."); } @@ -633,7 +633,7 @@ class SnpAttestationPolyfill implements SnpAttestation { evidence: ArrayBuffer, endorsements: ArrayBuffer, uvm_endorsements?: ArrayBuffer, - endorsed_tcb?: string + endorsed_tcb?: string, ): SnpAttestationResult { throw new Error("Method not implemented."); } diff --git a/js/ccf-app/test/polyfill.test.ts b/js/ccf-app/test/polyfill.test.ts index 394dbebdf873..965f4f5a4848 100644 --- a/js/ccf-app/test/polyfill.test.ts +++ b/js/ccf-app/test/polyfill.test.ts @@ -37,7 +37,7 @@ describe("polyfill", function () { assert.equal(ccf.crypto.generateAesKey(256).byteLength, 32); assert.notDeepEqual( ccf.crypto.generateAesKey(256), - ccf.crypto.generateAesKey(256) + ccf.crypto.generateAesKey(256), ); }); }); @@ -86,12 +86,12 @@ describe("polyfill", function () { const wrapped = ccf.crypto.wrapKey( key, ccf.strToBuf(wrappingKey.publicKey), - wrapAlgo + wrapAlgo, ); const unwrapped = ccf.crypto.unwrapKey( wrapped, ccf.strToBuf(wrappingKey.privateKey), - wrapAlgo + wrapAlgo, ); assert.deepEqual(unwrapped, key); }); @@ -115,12 +115,12 @@ describe("polyfill", function () { const wrapped = ccf.crypto.wrapKey( key, ccf.strToBuf(wrappingKey.publicKey), - wrapAlgo + wrapAlgo, ); const unwrapped = ccf.crypto.unwrapKey( wrapped, ccf.strToBuf(wrappingKey.privateKey), - wrapAlgo + wrapAlgo, ); assert.deepEqual(unwrapped, key); }); @@ -145,7 +145,7 @@ describe("polyfill", function () { hash: "SHA-256", }, privateKey, - data + data, ); { @@ -158,8 +158,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature) - ) + new Uint8Array(signature), + ), ); } @@ -172,8 +172,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); { @@ -186,8 +186,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature) - ) + new Uint8Array(signature), + ), ); } }); @@ -210,7 +210,7 @@ describe("polyfill", function () { hash: "SHA-256", }, privateKey, - data + data, ); { @@ -223,8 +223,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature) - ) + new Uint8Array(signature), + ), ); } @@ -237,8 +237,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); { @@ -251,8 +251,8 @@ describe("polyfill", function () { key: publicKey, dsaEncoding: "ieee-p1363", }, - new Uint8Array(signature) - ) + new Uint8Array(signature), + ), ); } }); @@ -273,7 +273,7 @@ describe("polyfill", function () { name: "EdDSA", }, privateKey, - data + data, ); assert.isTrue( @@ -281,8 +281,8 @@ describe("polyfill", function () { null, new Uint8Array(data), publicKey, - new Uint8Array(signature) - ) + new Uint8Array(signature), + ), ); // Also `signature` should be verified successfully with the JS API @@ -293,8 +293,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); assert.isFalse( @@ -302,8 +302,8 @@ describe("polyfill", function () { null, new Uint8Array(ccf.strToBuf("bar")), publicKey, - new Uint8Array(signature) - ) + new Uint8Array(signature), + ), ); }); it("performs HMAC sign correctly", function () { @@ -325,7 +325,7 @@ describe("polyfill", function () { hash: ccfHash as DigestAlgorithm, }, key, - data + data, ); { @@ -369,8 +369,8 @@ describe("polyfill", function () { }, cert, signature, - data - ) + data, + ), ); assert.isTrue( ccf.crypto.verifySignature( @@ -380,8 +380,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); assert.isNotTrue( ccf.crypto.verifySignature( @@ -391,8 +391,8 @@ describe("polyfill", function () { }, cert, signature, - ccf.strToBuf("bar") - ) + ccf.strToBuf("bar"), + ), ); assert.throws(() => ccf.crypto.verifySignature( @@ -402,8 +402,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); }); it("performs ECDSA validation correctly", function () { @@ -436,8 +436,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); assert.isNotTrue( ccf.crypto.verifySignature( @@ -447,8 +447,8 @@ describe("polyfill", function () { }, publicKey, signature, - ccf.strToBuf("bar") - ) + ccf.strToBuf("bar"), + ), ); assert.throws(() => ccf.crypto.verifySignature( @@ -458,8 +458,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); }); it("performs EdDSA validation correctly", function () { @@ -477,7 +477,7 @@ describe("polyfill", function () { const signature = crypto.sign( null, new Uint8Array(data), - crypto.createPrivateKey(privateKey) + crypto.createPrivateKey(privateKey), ); assert.isTrue( ccf.crypto.verifySignature( @@ -486,8 +486,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); assert.isNotTrue( ccf.crypto.verifySignature( @@ -496,8 +496,8 @@ describe("polyfill", function () { }, publicKey, signature, - ccf.strToBuf("bar") - ) + ccf.strToBuf("bar"), + ), ); assert.throws(() => ccf.crypto.verifySignature( @@ -507,8 +507,8 @@ describe("polyfill", function () { }, publicKey, signature, - data - ) + data, + ), ); }); }); From e4c6a854432b63abddc94433bca0612b43be13be Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Oct 2023 10:20:22 +0000 Subject: [PATCH 4/8] Adding js bridge in CCF --- js/ccf-app/src/polyfill.ts | 2 +- src/js/crypto.cpp | 124 +++++++++++++++++++++++++++++++++++++ src/js/wrap.cpp | 5 ++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/js/ccf-app/src/polyfill.ts b/js/ccf-app/src/polyfill.ts index 8e61a046215f..9b8735c6366f 100644 --- a/js/ccf-app/src/polyfill.ts +++ b/js/ccf-app/src/polyfill.ts @@ -342,7 +342,7 @@ class CCFPolyfill implements CCF { ); } else if (unwrapAlgo.name == "RSA-OAEP-AES-KWP") { const keyInfo = jscrypto.createPrivateKey(Buffer.from(unwrappingKey)); - // asymmetricKeyDetails added in Node.js 15.7.0, we're at 14. + // asymmetricKeyDetails added in Node.js 15.7.0, we're at 16. console.log( `Modulus length: `, keyInfo?.asymmetricKeyDetails?.modulusLength, diff --git a/src/js/crypto.cpp b/src/js/crypto.cpp index 105a1b9bb1dd..dcce93eafd2b 100644 --- a/src/js/crypto.cpp +++ b/src/js/crypto.cpp @@ -660,6 +660,130 @@ namespace ccf::js } } + static JSValue js_unwrap_key( + JSContext* ctx, JSValueConst, int argc, JSValueConst* argv) + { + if (argc != 3) + return JS_ThrowTypeError( + ctx, "Passed %d arguments, but expected 3", argc); + + // API loosely modeled after + // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/wrapKey. + + size_t key_size; + uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]); + if (!key) + { + return ccf::js::constants::Exception; + } + + size_t unwrapping_key_size; + uint8_t* unwrapping_key = + JS_GetArrayBuffer(ctx, &unwrapping_key_size, argv[1]); + if (!unwrapping_key) + { + return ccf::js::constants::Exception; + } + + js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx); + + auto parameters = argv[2]; + auto wrap_algo_name_val = jsctx(JS_GetPropertyStr(ctx, parameters, "name")); + JS_CHECK_EXC(wrap_algo_name_val); + + auto wrap_algo_name_str = jsctx.to_str(wrap_algo_name_val); + if (!wrap_algo_name_str) + { + return ccf::js::constants::Exception; + } + + try + { + auto algo_name = std::string(*wrap_algo_name_str); + if (algo_name == "RSA-OAEP") + { + // key can in principle be arbitrary data (see note on maximum size + // in rsa_key_pair.h). unwrapping_key is a private RSA key. + + auto label_val = jsctx(JS_GetPropertyStr(ctx, parameters, "label")); + JS_CHECK_EXC(label_val); + + size_t label_buf_size = 0; + uint8_t* label_buf = JS_GetArrayBuffer(ctx, &label_buf_size, label_val); + + std::optional> label_opt = std::nullopt; + if (label_buf && label_buf_size > 0) + { + label_opt = {label_buf, label_buf + label_buf_size}; + } + + auto unwrapped_key = crypto::ckm_rsa_pkcs_oaep_unwrap( + crypto::Pem(unwrapping_key, unwrapping_key_size), + {key, key + key_size}, + label_opt); + + return JS_NewArrayBufferCopy( + ctx, unwrapped_key.data(), unwrapped_key.size()); + } + else if (algo_name == "AES-KWP") + { + std::vector unwrapped_key = crypto::ckm_aes_key_unwrap_pad( + {unwrapping_key, unwrapping_key + unwrapping_key_size}, + {key, key + key_size}); + + return JS_NewArrayBufferCopy( + ctx, unwrapped_key.data(), unwrapped_key.size()); + } + else if (algo_name == "RSA-OAEP-AES-KWP") + { + auto aes_key_size_value = + jsctx(JS_GetPropertyStr(ctx, parameters, "aesKeySize")); + JS_CHECK_EXC(aes_key_size_value); + + int32_t aes_key_size = 0; + if (JS_ToInt32(ctx, &aes_key_size, aes_key_size_value) < 0) + { + return ccf::js::constants::Exception; + } + + auto label_val = jsctx(JS_GetPropertyStr(ctx, parameters, "label")); + JS_CHECK_EXC(label_val); + + size_t label_buf_size = 0; + uint8_t* label_buf = JS_GetArrayBuffer(ctx, &label_buf_size, label_val); + + std::optional> label_opt = std::nullopt; + if (label_buf && label_buf_size > 0) + { + label_opt = {label_buf, label_buf + label_buf_size}; + } + + auto unwrapped_key = crypto::ckm_rsa_aes_key_unwrap( + crypto::Pem(unwrapping_key, unwrapping_key_size), + {key, key + key_size}, + label_opt); + + return JS_NewArrayBufferCopy( + ctx, unwrapped_key.data(), unwrapped_key.size()); + } + else + { + return JS_ThrowRangeError( + ctx, + "unsupported key unwrapping algorithm, supported: RSA-OAEP, AES-KWP, " + "RSA-OAEP-AES-KWP"); + } + } + catch (std::exception& ex) + { + return JS_ThrowInternalError(ctx, "Failed to unwrap key: %s", ex.what()); + } + catch (...) + { + return JS_ThrowRangeError(ctx, "caught unknown exception"); + } + } + static JSValue js_sign( JSContext* ctx, JSValueConst, int argc, JSValueConst* argv) { diff --git a/src/js/wrap.cpp b/src/js/wrap.cpp index 2fbf7dd4bf58..5f7f8b53796e 100644 --- a/src/js/wrap.cpp +++ b/src/js/wrap.cpp @@ -2229,6 +2229,11 @@ namespace ccf::js ctx, js_generate_eddsa_key_pair, "generateEddsaKeyPair", 1)); JS_SetPropertyStr( ctx, crypto, "wrapKey", JS_NewCFunction(ctx, js_wrap_key, "wrapKey", 3)); + JS_SetPropertyStr( + ctx, + crypto, + "unwrapKey", + JS_NewCFunction(ctx, js_unwrap_key, "unwrapKey", 3)); JS_SetPropertyStr( ctx, crypto, "digest", JS_NewCFunction(ctx, js_digest, "digest", 2)); JS_SetPropertyStr( From 43724476bafa3da88fabbb74e41ad2e6ebdd8a09 Mon Sep 17 00:00:00 2001 From: beejones Date: Mon, 30 Oct 2023 15:10:22 +0000 Subject: [PATCH 5/8] Add unit tests for CCF bridge --- src/js/crypto.cpp | 18 +++++++-- tests/js-modules/modules.py | 56 ++++++++++++++++++++++++--- tests/npm-app/app.json | 42 +++++++++++++++++++- tests/npm-app/package.json | 2 +- tests/npm-app/src/endpoints/crypto.ts | 32 +++++++++++++++ 5 files changed, 139 insertions(+), 11 deletions(-) diff --git a/src/js/crypto.cpp b/src/js/crypto.cpp index dcce93eafd2b..04cbc79b342e 100644 --- a/src/js/crypto.cpp +++ b/src/js/crypto.cpp @@ -574,7 +574,7 @@ namespace ccf::js try { - auto algo_name = std::string(*wrap_algo_name_str); + auto algo_name = *wrap_algo_name_str; if (algo_name == "RSA-OAEP") { // key can in principle be arbitrary data (see note on maximum size @@ -597,6 +597,8 @@ namespace ccf::js {key, key + key_size}, label_opt); + OPENSSL_cleanse(wrapping_key, wrapping_key_size); + return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); } @@ -606,6 +608,8 @@ namespace ccf::js {wrapping_key, wrapping_key + wrapping_key_size}, {key, key + key_size}); + OPENSSL_cleanse(wrapping_key, wrapping_key_size); + return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); } @@ -639,6 +643,8 @@ namespace ccf::js {key, key + key_size}, label_opt); + OPENSSL_cleanse(wrapping_key, wrapping_key_size); + return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); } @@ -668,7 +674,7 @@ namespace ccf::js ctx, "Passed %d arguments, but expected 3", argc); // API loosely modeled after - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/wrapKey. + // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/unwrapKey. size_t key_size; uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]); @@ -699,7 +705,7 @@ namespace ccf::js try { - auto algo_name = std::string(*wrap_algo_name_str); + auto algo_name = *wrap_algo_name_str; if (algo_name == "RSA-OAEP") { // key can in principle be arbitrary data (see note on maximum size @@ -722,6 +728,8 @@ namespace ccf::js {key, key + key_size}, label_opt); + OPENSSL_cleanse(unwrapping_key, unwrapping_key_size); + return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); } @@ -731,6 +739,8 @@ namespace ccf::js {unwrapping_key, unwrapping_key + unwrapping_key_size}, {key, key + key_size}); + OPENSSL_cleanse(unwrapping_key, unwrapping_key_size); + return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); } @@ -763,6 +773,8 @@ namespace ccf::js {key, key + key_size}, label_opt); + OPENSSL_cleanse(unwrapping_key, unwrapping_key_size); + return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); } diff --git a/tests/js-modules/modules.py b/tests/js-modules/modules.py index 5894c6d5e31c..e3176acefdf6 100644 --- a/tests/js-modules/modules.py +++ b/tests/js-modules/modules.py @@ -556,10 +556,25 @@ def test_npm_app(network, args): }, }, ) + wrappedKey = r.body.data() + assert wrappedKey is not None assert r.status_code == http.HTTPStatus.OK, r.status_code - unwrapped = infra.crypto.unwrap_key_rsa_oaep( - r.body.data(), wrapping_key_priv_pem, label.encode("ascii") + + r = c.post( + "/app/unwrapKey", + { + "key": b64encode(wrappedKey).decode(), + "unwrappingKey": b64encode( + bytes(wrapping_key_priv_pem, "ascii") + ).decode(), + "wrapAlgo": { + "name": "RSA-OAEP", + "label": b64encode(bytes(label, "ascii")).decode(), + }, + }, ) + assert r.status_code == http.HTTPStatus.OK, r.status_code + unwrapped = r.body.data() assert unwrapped == aes_key_to_wrap aes_wrapping_key = infra.crypto.generate_aes_key(256) @@ -571,9 +586,22 @@ def test_npm_app(network, args): "wrapAlgo": {"name": "AES-KWP"}, }, ) + wrappedKey = r.body.data() + assert wrappedKey is not None assert r.status_code == http.HTTPStatus.OK, r.status_code - unwrapped = infra.crypto.unwrap_key_aes_pad(r.body.data(), aes_wrapping_key) - assert unwrapped == aes_key_to_wrap + + r = c.post( + "/app/unwrapKey", + { + "key": b64encode(wrappedKey).decode(), + "unwrappingKey": b64encode(aes_wrapping_key).decode(), + "wrapAlgo": {"name": "AES-KWP"}, + }, + ) + + assert r.status_code == http.HTTPStatus.OK, r.status_code + wrappedKey = r.body.data() + assert wrappedKey == aes_key_to_wrap wrapping_key_priv_pem, wrapping_key_pub_pem = infra.crypto.generate_rsa_keypair( 2048 @@ -592,9 +620,25 @@ def test_npm_app(network, args): }, ) assert r.status_code == http.HTTPStatus.OK, r.status_code - unwrapped = infra.crypto.unwrap_key_rsa_oaep_aes_pad( - r.body.data(), wrapping_key_priv_pem, label.encode("ascii") + wrappedKey = r.body.data() + assert wrappedKey is not None + + r = c.post( + "/app/unwrapKey", + { + "key": b64encode(wrappedKey).decode(), + "unwrappingKey": b64encode( + bytes(wrapping_key_priv_pem, "ascii") + ).decode(), + "wrapAlgo": { + "name": "RSA-OAEP-AES-KWP", + "aesKeySize": 256, + "label": b64encode(bytes(label, "ascii")).decode(), + }, + }, ) + assert r.status_code == http.HTTPStatus.OK, r.status_code + unwrapped = r.body.data() assert unwrapped == aes_key_to_wrap # Test RSA signing + verification diff --git a/tests/npm-app/app.json b/tests/npm-app/app.json index 9d5b91d9dc5d..4223e296ccf2 100644 --- a/tests/npm-app/app.json +++ b/tests/npm-app/app.json @@ -282,7 +282,7 @@ "wrappingKey": { "type": "string" }, - "parameters": { + "wrapAlgo": { "type": "object" } }, @@ -302,6 +302,46 @@ } } }, + "/unwrapKey": { + "post": { + "js_module": "endpoints/crypto.js", + "js_function": "unwrapKey", + "forwarding_required": "sometimes", + "authn_policies": ["user_cert"], + "mode": "readonly", + "openapi": { + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "key": { + "type": "string" + }, + "unwrappingKey": { + "type": "string" + }, + "wrapAlgo": { + "type": "object" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Unwrapped key", + "content": { + "application/octet-stream": {} + } + } + } + } + } + }, "/sign": { "post": { "js_module": "endpoints/crypto.js", diff --git a/tests/npm-app/package.json b/tests/npm-app/package.json index 873ea59f1f5a..6ced2cdffcb7 100644 --- a/tests/npm-app/package.json +++ b/tests/npm-app/package.json @@ -24,7 +24,7 @@ "@rollup/plugin-typescript": "^8.2.0", "@types/jsrsasign": "^8.0.7", "@types/lodash-es": "^4.17.3", - "del-cli": "^5.0.0", + "del-cli": "^5.1.0", "http-server": "^0.13.0", "rollup": "^2.41.0", "tslib": "^2.0.1", diff --git a/tests/npm-app/src/endpoints/crypto.ts b/tests/npm-app/src/endpoints/crypto.ts index f84bbd198122..ca78bd1a2653 100644 --- a/tests/npm-app/src/endpoints/crypto.ts +++ b/tests/npm-app/src/endpoints/crypto.ts @@ -132,6 +132,38 @@ export function wrapKey( return { body: wrappedKey }; } +interface UnwrapKeyRequest { + key: Base64; // typically an AES key + unwrappingKey: Base64; // base64 encoding of PEM-encoded RSA private key or AES key bytes + wrapAlgo: WrapAlgoParams; // Wrapping algorithm parameters +} + +export function unwrapKey( + request: ccfapp.Request, +): ccfapp.Response { + const r = request.body.json(); + const key = b64ToBuf(r.key); + const unwrappingKey = b64ToBuf(r.unwrappingKey); + let unwrappedKey: ArrayBuffer; + if (r.wrapAlgo.name == "RSA-OAEP") { + const label = r.wrapAlgo.label ? b64ToBuf(r.wrapAlgo.label) : undefined; + unwrappedKey = ccfcrypto.unwrapKey(key, unwrappingKey, { + name: r.wrapAlgo.name, + label: label, + }); + } else if (r.wrapAlgo.name == "RSA-OAEP-AES-KWP") { + const label = r.wrapAlgo.label ? b64ToBuf(r.wrapAlgo.label) : undefined; + unwrappedKey = ccfcrypto.unwrapKey(key, unwrappingKey, { + name: r.wrapAlgo.name, + aesKeySize: r.wrapAlgo.aesKeySize, + label: label, + }); + } else { + unwrappedKey = ccfcrypto.unwrapKey(key, unwrappingKey, r.wrapAlgo); + } + return { body: unwrappedKey }; +} + interface SignRequest { algorithm: ccfcrypto.SigningAlgorithm; key: string; From 0eef9d9427d517f282bb1d46cc16e3c5dfff2b62 Mon Sep 17 00:00:00 2001 From: beejones Date: Tue, 31 Oct 2023 10:28:57 +0000 Subject: [PATCH 6/8] Fix OPENSSL_cleanse usage --- src/js/crypto.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/js/crypto.cpp b/src/js/crypto.cpp index 04cbc79b342e..28868d853dfd 100644 --- a/src/js/crypto.cpp +++ b/src/js/crypto.cpp @@ -597,18 +597,18 @@ namespace ccf::js {key, key + key_size}, label_opt); - OPENSSL_cleanse(wrapping_key, wrapping_key_size); - return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); } else if (algo_name == "AES-KWP") { + std::vector privateKey(wrapping_key, wrapping_key + wrapping_key_size); std::vector wrapped_key = crypto::ckm_aes_key_wrap_pad( - {wrapping_key, wrapping_key + wrapping_key_size}, + privateKey, {key, key + key_size}); - OPENSSL_cleanse(wrapping_key, wrapping_key_size); + OPENSSL_cleanse(&privateKey, sizeof(privateKey)); + return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); @@ -723,23 +723,25 @@ namespace ccf::js label_opt = {label_buf, label_buf + label_buf_size}; } + auto pemPrivateUnwrappingKey = crypto::Pem(unwrapping_key, unwrapping_key_size); auto unwrapped_key = crypto::ckm_rsa_pkcs_oaep_unwrap( - crypto::Pem(unwrapping_key, unwrapping_key_size), + pemPrivateUnwrappingKey, {key, key + key_size}, label_opt); - OPENSSL_cleanse(unwrapping_key, unwrapping_key_size); + OPENSSL_cleanse(pemPrivateUnwrappingKey.data(), pemPrivateUnwrappingKey.size()); return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); } else if (algo_name == "AES-KWP") { + std::vector privateKey(unwrapping_key, unwrapping_key + unwrapping_key_size); std::vector unwrapped_key = crypto::ckm_aes_key_unwrap_pad( - {unwrapping_key, unwrapping_key + unwrapping_key_size}, + privateKey, {key, key + key_size}); - OPENSSL_cleanse(unwrapping_key, unwrapping_key_size); + OPENSSL_cleanse(&privateKey, sizeof(privateKey)); return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); @@ -768,12 +770,13 @@ namespace ccf::js label_opt = {label_buf, label_buf + label_buf_size}; } + auto privPemUnwrappingKey = crypto::Pem(unwrapping_key, unwrapping_key_size); auto unwrapped_key = crypto::ckm_rsa_aes_key_unwrap( - crypto::Pem(unwrapping_key, unwrapping_key_size), + privPemUnwrappingKey, {key, key + key_size}, label_opt); - OPENSSL_cleanse(unwrapping_key, unwrapping_key_size); + OPENSSL_cleanse(privPemUnwrappingKey.data(), privPemUnwrappingKey.size()); return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); From ff40cc01d8144457090b9b2b03b6906a44746162 Mon Sep 17 00:00:00 2001 From: beejones Date: Tue, 31 Oct 2023 10:33:54 +0000 Subject: [PATCH 7/8] code format fix --- src/js/crypto.cpp | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/js/crypto.cpp b/src/js/crypto.cpp index 28868d853dfd..9162a871017e 100644 --- a/src/js/crypto.cpp +++ b/src/js/crypto.cpp @@ -602,13 +602,12 @@ namespace ccf::js } else if (algo_name == "AES-KWP") { - std::vector privateKey(wrapping_key, wrapping_key + wrapping_key_size); - std::vector wrapped_key = crypto::ckm_aes_key_wrap_pad( - privateKey, - {key, key + key_size}); + std::vector privateKey( + wrapping_key, wrapping_key + wrapping_key_size); + std::vector wrapped_key = + crypto::ckm_aes_key_wrap_pad(privateKey, {key, key + key_size}); OPENSSL_cleanse(&privateKey, sizeof(privateKey)); - return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); @@ -723,23 +722,23 @@ namespace ccf::js label_opt = {label_buf, label_buf + label_buf_size}; } - auto pemPrivateUnwrappingKey = crypto::Pem(unwrapping_key, unwrapping_key_size); + auto pemPrivateUnwrappingKey = + crypto::Pem(unwrapping_key, unwrapping_key_size); auto unwrapped_key = crypto::ckm_rsa_pkcs_oaep_unwrap( - pemPrivateUnwrappingKey, - {key, key + key_size}, - label_opt); + pemPrivateUnwrappingKey, {key, key + key_size}, label_opt); - OPENSSL_cleanse(pemPrivateUnwrappingKey.data(), pemPrivateUnwrappingKey.size()); + OPENSSL_cleanse( + pemPrivateUnwrappingKey.data(), pemPrivateUnwrappingKey.size()); return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); } else if (algo_name == "AES-KWP") { - std::vector privateKey(unwrapping_key, unwrapping_key + unwrapping_key_size); - std::vector unwrapped_key = crypto::ckm_aes_key_unwrap_pad( - privateKey, - {key, key + key_size}); + std::vector privateKey( + unwrapping_key, unwrapping_key + unwrapping_key_size); + std::vector unwrapped_key = + crypto::ckm_aes_key_unwrap_pad(privateKey, {key, key + key_size}); OPENSSL_cleanse(&privateKey, sizeof(privateKey)); @@ -770,13 +769,13 @@ namespace ccf::js label_opt = {label_buf, label_buf + label_buf_size}; } - auto privPemUnwrappingKey = crypto::Pem(unwrapping_key, unwrapping_key_size); + auto privPemUnwrappingKey = + crypto::Pem(unwrapping_key, unwrapping_key_size); auto unwrapped_key = crypto::ckm_rsa_aes_key_unwrap( - privPemUnwrappingKey, - {key, key + key_size}, - label_opt); + privPemUnwrappingKey, {key, key + key_size}, label_opt); - OPENSSL_cleanse(privPemUnwrappingKey.data(), privPemUnwrappingKey.size()); + OPENSSL_cleanse( + privPemUnwrappingKey.data(), privPemUnwrappingKey.size()); return JS_NewArrayBufferCopy( ctx, unwrapped_key.data(), unwrapped_key.size()); From eb1786f7d578a1cb5732a0b9311a26e7735cdc8a Mon Sep 17 00:00:00 2001 From: beejones Date: Thu, 2 Nov 2023 08:49:38 +0000 Subject: [PATCH 8/8] missed OPENSSL_cleanse --- src/js/crypto.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/crypto.cpp b/src/js/crypto.cpp index 9162a871017e..3f995f08fb5c 100644 --- a/src/js/crypto.cpp +++ b/src/js/crypto.cpp @@ -642,8 +642,6 @@ namespace ccf::js {key, key + key_size}, label_opt); - OPENSSL_cleanse(wrapping_key, wrapping_key_size); - return JS_NewArrayBufferCopy( ctx, wrapped_key.data(), wrapped_key.size()); }