Skip to content

Commit

Permalink
Merge pull-request #41
Browse files Browse the repository at this point in the history
  • Loading branch information
r-n-o committed May 13, 2024
2 parents 24041c6 + 21fa7c4 commit 405433f
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 53 deletions.
44 changes: 25 additions & 19 deletions export/index.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,11 @@ <h2>Message log</h2>

/**
* Function to verify enclave signature on import bundle received from the server.
* @param {string} enclaveQuorumPublic uncompressed public key for the quorum key which produced the signature
* @param {string} publicSignature signature bytes encoded as a hexadecimal string
* @param {string} signedData signed bytes encoded as a hexadecimal string. This could be public key bytes directly, or JSON-encoded bytes
*/
async function verifyEnclaveSignature(enclaveQuorumPublic, publicSignature, publicKey) {
async function verifyEnclaveSignature(enclaveQuorumPublic, publicSignature, signedData) {
/** Turnkey Signer enclave's public keys */
const TURNKEY_SIGNERS_ENCLAVES = {
"prod": "04cf288fe433cc4e1aa0ce1632feac4ea26bf2f5a09dcfe5a42c398e06898710330f0572882f4dbdf0f5304b8fc8703acd69adca9a4bbf7f5d00d20a5e364b2569",
Expand All @@ -355,8 +358,8 @@ <h2>Message log</h2>

// The ECDSA signature is ASN.1 DER encoded but WebCrypto uses raw format
const publicSignatureBuf = fromDerSignature(publicSignature);
const publicKeyBuf = uint8arrayFromHexString(publicKey);
return await crypto.subtle.verify({ name: "ECDSA", namedCurve: "P-256", hash: {name: "SHA-256" }}, quorumKey, publicSignatureBuf, publicKeyBuf);
const signedDataBuf = uint8arrayFromHexString(signedData);
return await crypto.subtle.verify({ name: "ECDSA", namedCurve: "P-256", hash: {name: "SHA-256" }}, quorumKey, publicSignatureBuf, signedDataBuf);
}

/**
Expand Down Expand Up @@ -689,7 +692,7 @@ <h2>Message log</h2>
* Example: {"encappedPublic":"04912cb4200c40f04ae4a162f4c870c78cb4498a8efda0b94f4a9cb848d611bd40e9acccab2bf73cee1e269d8350a02f4df71864921097838f05c288d944fa2f8b","encappedPublicSignature":"304502200cd19a3c5892f1eeab88fe0cdd7cca63736a7d15fc364186fb3c913e1e01568b022100dea49557c176f6ca052b27ad164f077cf64d2aa55fbdc4757a14767f8b8c6b48","ciphertext":"0e5d5503f43721135818051e4c5b77b3365b66ec4020b0051d59ea9fc773c67bd4b61ed34a97b07a3074a85546721ae4","enclaveQuorumPublic":"04cf288fe433cc4e1aa0ce1632feac4ea26bf2f5a09dcfe5a42c398e06898710330f0572882f4dbdf0f5304b8fc8703acd69adca9a4bbf7f5d00d20a5e364b2569"}
* @param {string} bundle
* @param {string} organizationId
*/
*/
async function decryptBundle(bundle, organizationId) {
let encappedKeyBuf;
let ciphertextBuf;
Expand Down Expand Up @@ -725,38 +728,41 @@ <h2>Message log</h2>
if (!bundleObj.data) {
throw new Error('missing "data" in bundle');
}

if (!bundleObj.data.encappedPublic) {
throw new Error('missing "data.encappedPublic" in bundle');
}

if (!bundleObj.data.ciphertext) {
throw new Error('missing "data.ciphertext" in bundle');
}

if (!bundleObj.dataSignature) {
throw new Error('missing "dataSignature" in bundle');
}
if (!bundleObj.enclaveQuorumPublic) {
throw new Error('missing "enclaveQuorumPublic" in bundle');
}

// Verify enclave signature
if (!TKHQ.verifyEnclaveSignature) {
throw new Error("method not loaded");
}
verified = await TKHQ.verifyEnclaveSignature(bundleObj.enclaveQuorumPublic, bundleObj.dataSignature, bundleObj.data.encappedPublic);
verified = await TKHQ.verifyEnclaveSignature(bundleObj.enclaveQuorumPublic, bundleObj.dataSignature, bundleObj.data);
if (!verified) {
throw new Error(`failed to verify enclave signature: ${bundle}`);
}

// Parse the signed data. The data is produced by JSON encoding followed by hex encoding. We reverse this here.
const signedData = JSON.parse(new TextDecoder().decode(TKHQ.uint8arrayFromHexString(bundleObj.data)));

// Validate fields match
if (!organizationId) {
// todo: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend using v2.0.0 iframe stamper to pass the "organizationId" for security purposes.');
} else if (!bundleObj.data.organizationId || bundleObj.data.organizationId !== organizationId) {
throw new Error(`organization id does not match expected value. Expected: ${organizationId}. Found: ${bundleObj.data.organizationId}.`);
console.warn('we highly recommend a version of @turnkey/iframe-stamper >= v2.0.0 to pass "organizationId" for security purposes.');
} else if (!signedData.organizationId || signedData.organizationId !== organizationId) {
throw new Error(`organization id does not match expected value. Expected: ${organizationId}. Found: ${signedData.organizationId}.`);
}

encappedKeyBuf = TKHQ.uint8arrayFromHexString(bundleObj.data.encappedPublic);
ciphertextBuf = TKHQ.uint8arrayFromHexString(bundleObj.data.ciphertext);
if (!signedData.encappedPublic) {
throw new Error('missing "encappedPublic" in bundle signed data');
}
if (!signedData.ciphertext) {
throw new Error('missing "ciphertext" in bundle signed data');
}
encappedKeyBuf = TKHQ.uint8arrayFromHexString(signedData.encappedPublic);
ciphertextBuf = TKHQ.uint8arrayFromHexString(signedData.ciphertext);
break;
default:
throw new Error(`unsupported version: ${bundleObj.version}`);
Expand Down
42 changes: 25 additions & 17 deletions import/index.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,11 @@

/**
* Function to verify enclave signature on import bundle received from the server.
* @param {string} enclaveQuorumPublic uncompressed public key for the quorum key which produced the signature
* @param {string} publicSignature signature bytes encoded as a hexadecimal string
* @param {string} signedData signed bytes encoded as a hexadecimal string. This could be public key bytes directly, or JSON-encoded bytes
*/
async function verifyEnclaveSignature(enclaveQuorumPublic, publicSignature, publicKey) {
async function verifyEnclaveSignature(enclaveQuorumPublic, publicSignature, signedData) {
/** Turnkey Signer enclave's public keys */
const TURNKEY_SIGNERS_ENCLAVES = {
"prod": "04cf288fe433cc4e1aa0ce1632feac4ea26bf2f5a09dcfe5a42c398e06898710330f0572882f4dbdf0f5304b8fc8703acd69adca9a4bbf7f5d00d20a5e364b2569",
Expand All @@ -312,8 +315,8 @@

// The ECDSA signature is ASN.1 DER encoded but WebCrypto uses raw format
const publicSignatureBuf = fromDerSignature(publicSignature);
const publicKeyBuf = uint8arrayFromHexString(publicKey);
return await crypto.subtle.verify({ name: "ECDSA", namedCurve: "P-256", hash: {name: "SHA-256" }}, quorumKey, publicSignatureBuf, publicKeyBuf);
const signedDataBuf = uint8arrayFromHexString(signedData);
return await crypto.subtle.verify({ name: "ECDSA", namedCurve: "P-256", hash: {name: "SHA-256" }}, quorumKey, publicSignatureBuf, signedDataBuf);
}

/**
Expand Down Expand Up @@ -533,14 +536,12 @@
if (!bundleObj.data) {
throw new Error('missing "data" in bundle');
}

if (!bundleObj.data.targetPublic) {
throw new Error('missing "data.targetPublic" in bundle');
}

if (!bundleObj.dataSignature) {
throw new Error('missing "dataSignature" in bundle');
}
if (!bundleObj.enclaveQuorumPublic) {
throw new Error('missing "enclaveQuorumPublic" in bundle');
}

// Verify enclave signature
if (!TKHQ.verifyEnclaveSignature) {
Expand All @@ -551,22 +552,29 @@
throw new Error(`failed to verify enclave signature: ${bundle}`);
}

// Parse the signed data. The data is produced by JSON encoding followed by hex encoding. We reverse this here.
const signedData = JSON.parse(new TextDecoder().decode(TKHQ.uint8arrayFromHexString(bundleObj.data)));

// Validate fields match
if (!organizationId) {
// todo: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend using v2.0.0 iframe stamper to pass the "organizationId" for security purposes.');
} else if (!bundleObj.data.organizationId || bundleObj.data.organizationId !== organizationId) {
throw new Error(`organization id does not match expected value. Expected: ${organizationId}. Found: ${bundleObj.data.organizationId}.`);
// TODO: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend a version of @turnkey/iframe-stamper >= v2.0.0 to pass "organizationId" for security purposes.');
} else if (!signedData.organizationId || signedData.organizationId !== organizationId) {
throw new Error(`organization id does not match expected value. Expected: ${organizationId}. Found: ${signedData.organizationId}.`);
}
if (!userId) {
// todo: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend using v2.0.0 iframe stamper to pass the "userId" for security purposes.');
} else if (!bundleObj.data.userId || bundleObj.data.userId !== userId) {
throw new Error(`user id does not match expected value. Expected: ${userId}. Found: ${bundleObj.data.userId}.`);
// TODO: throw error if user id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend a version of @turnkey/iframe-stamper >= v2.0.0 to pass "userId" for security purposes.');
} else if (!signedData.userId || signedData.userId !== userId) {
throw new Error(`user id does not match expected value. Expected: ${userId}. Found: ${signedData.userId}.`);
}

if (!signedData.targetPublic) {
throw new Error('missing "targetPublic" in bundle signed data');
}

// Load target public key generated from enclave and set in local storage
targetPublicBuf = TKHQ.uint8arrayFromHexString(bundleObj.data.targetPublic);
targetPublicBuf = TKHQ.uint8arrayFromHexString(signedData.targetPublic);
break;
default:
throw new Error(`unsupported version: ${bundleObj.version}`);
Expand Down
42 changes: 25 additions & 17 deletions import/standalone.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,11 @@ <h2>Message log</h2>

/**
* Function to verify enclave signature on import bundle received from the server.
* @param {string} enclaveQuorumPublic uncompressed public key for the quorum key which produced the signature
* @param {string} publicSignature signature bytes encoded as a hexadecimal string
* @param {string} signedData signed bytes encoded as a hexadecimal string. This could be public key bytes directly, or JSON-encoded bytes
*/
async function verifyEnclaveSignature(enclaveQuorumPublic, publicSignature, publicKey) {
async function verifyEnclaveSignature(enclaveQuorumPublic, publicSignature, signedData) {
/** Turnkey Signer enclave's public keys */
const TURNKEY_SIGNERS_ENCLAVES = {
"prod": "04cf288fe433cc4e1aa0ce1632feac4ea26bf2f5a09dcfe5a42c398e06898710330f0572882f4dbdf0f5304b8fc8703acd69adca9a4bbf7f5d00d20a5e364b2569",
Expand All @@ -364,8 +367,8 @@ <h2>Message log</h2>

// The ECDSA signature is ASN.1 DER encoded but WebCrypto uses raw format
const publicSignatureBuf = fromDerSignature(publicSignature);
const publicKeyBuf = uint8arrayFromHexString(publicKey);
return await crypto.subtle.verify({ name: "ECDSA", namedCurve: "P-256", hash: {name: "SHA-256" }}, quorumKey, publicSignatureBuf, publicKeyBuf);
const signedDataBuf = uint8arrayFromHexString(signedData);
return await crypto.subtle.verify({ name: "ECDSA", namedCurve: "P-256", hash: {name: "SHA-256" }}, quorumKey, publicSignatureBuf, signedDataBuf);
}

/**
Expand Down Expand Up @@ -530,14 +533,12 @@ <h2>Message log</h2>
if (!bundleObj.data) {
throw new Error('missing "data" in bundle');
}

if (!bundleObj.data.targetPublic) {
throw new Error('missing "data.targetPublic" in bundle');
}

if (!bundleObj.dataSignature) {
throw new Error('missing "dataSignature" in bundle');
}
if (!bundleObj.enclaveQuorumPublic) {
throw new Error('missing "enclaveQuorumPublic" in bundle');
}

// Verify enclave signature
if (!TKHQ.verifyEnclaveSignature) {
Expand All @@ -548,22 +549,29 @@ <h2>Message log</h2>
throw new Error(`failed to verify enclave signature: ${bundle}`);
}

// Parse the signed data. The data is produced by JSON encoding followed by hex encoding. We reverse this here.
const signedData = JSON.parse(new TextDecoder().decode(TKHQ.uint8arrayFromHexString(bundleObj.data)));

// Validate fields match
if (!organizationId) {
// todo: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend using v2.0.0 iframe stamper to pass the "organizationId" for security purposes.');
} else if (!bundleObj.data.organizationId || bundleObj.data.organizationId !== organizationId) {
throw new Error(`organization id does not match expected value. Expected: ${organizationId}. Found: ${bundleObj.data.organizationId}.`);
// TODO: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend a version of @turnkey/iframe-stamper >= v2.0.0 to pass "organizationId" for security purposes.');
} else if (!signedData.organizationId || signedData.organizationId !== organizationId) {
throw new Error(`organization id does not match expected value. Expected: ${organizationId}. Found: ${signedData.organizationId}.`);
}
if (!userId) {
// todo: throw error if organization id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend using v2.0.0 iframe stamper to pass the "userId" for security purposes.');
} else if (!bundleObj.data.userId || bundleObj.data.userId !== userId) {
throw new Error(`user id does not match expected value. Expected: ${userId}. Found: ${bundleObj.data.userId}.`);
// TODO: throw error if user id is undefined once we've fully transitioned to v1.0.0 server messages and v2.0.0 iframe-stamper
console.warn('we highly recommend a version of @turnkey/iframe-stamper >= v2.0.0 to pass "userId" for security purposes.');
} else if (!signedData.userId || signedData.userId !== userId) {
throw new Error(`user id does not match expected value. Expected: ${userId}. Found: ${signedData.userId}.`);
}

if (!signedData.targetPublic) {
throw new Error('missing "targetPublic" in bundle signed data');
}

// Load target public key generated from enclave and set in local storage
targetPublicBuf = TKHQ.uint8arrayFromHexString(bundleObj.data.targetPublic);
targetPublicBuf = TKHQ.uint8arrayFromHexString(signedData.targetPublic);
break;
default:
throw new Error(`unsupported version: ${bundleObj.version}`);
Expand Down

0 comments on commit 405433f

Please sign in to comment.