Skip to content

Commit

Permalink
Extend web test to cover embedded signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
tniessen committed Dec 12, 2024
1 parent de057f0 commit 2d6f4a3
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 15 deletions.
34 changes: 25 additions & 9 deletions web/test/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ async function requireOK(response) {
return response;
}

function hex(binary) {
if (binary instanceof ArrayBuffer) {
binary = new Uint8Array(binary);
}
if (!(binary instanceof Uint8Array)) {
throw new TypeError('Expected ArrayBuffer or Uint8Array');
}
let ret = '';
for (let i = 0; i < binary.length; i++) {
ret += binary[i].toString(16).padStart(2, '0');
}
return ret;
}

const algorithmNames = [
...PQClean.kem.supportedAlgorithms.map(({ name }) => name),
...PQClean.sign.supportedAlgorithms.map(({ name }) => name)
Expand Down Expand Up @@ -35,15 +49,15 @@ async function testKEM(algorithm) {

typeof onTestProgress === 'function' && onTestProgress(algorithm.name, 'export');
const exportedPrivateKey = privateKey.export();
const result = new Uint8Array(exportedPrivateKey.byteLength + encryptedKey.byteLength + oneTimePaddedChallenge.byteLength);
result.set(new Uint8Array(exportedPrivateKey));
result.set(new Uint8Array(encryptedKey), exportedPrivateKey.byteLength);
result.set(new Uint8Array(oneTimePaddedChallenge), exportedPrivateKey.byteLength + encryptedKey.byteLength);

typeof onTestProgress === 'function' && onTestProgress(algorithm.name, 'submit result');
await fetch(new URL(`submit/${algorithm.name}`, import.meta.url), {
method: 'POST',
body: result
body: JSON.stringify({
privateKey: hex(exportedPrivateKey),
encryptedKey: hex(encryptedKey),
ciphertext: hex(oneTimePaddedChallenge),
}),
}).then(requireOK);

typeof onTestDone === 'function' && onTestDone(algorithm.name);
Expand All @@ -60,17 +74,19 @@ async function testSign(algorithm) {

typeof onTestProgress === 'function' && onTestProgress(algorithm.name, 'sign');
const signature = await privateKey.sign(challenge);
const signedMessage = await privateKey.signEmbed(challenge);

typeof onTestProgress === 'function' && onTestProgress(algorithm.name, 'export');
const exportedPublicKey = publicKey.export();
const result = new Uint8Array(exportedPublicKey.byteLength + signature.byteLength);
result.set(new Uint8Array(exportedPublicKey));
result.set(new Uint8Array(signature), exportedPublicKey.byteLength);

typeof onTestProgress === 'function' && onTestProgress(algorithm.name, 'submit result');
await fetch(new URL(`submit/${algorithm.name}`, import.meta.url), {
method: 'POST',
body: result
body: JSON.stringify({
publicKey: hex(exportedPublicKey),
signature: hex(signature),
signedMessage: hex(signedMessage),
}),
}).then(requireOK);
typeof onTestDone === 'function' && onTestDone(algorithm.name);
}
Expand Down
21 changes: 15 additions & 6 deletions web/test/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,33 @@ const server = createServer((req, res) => {
body = Buffer.concat([body, chunk]);
}

const submittedFields = JSON.parse(body.toString('utf8'));

const kemAlgorithm = PQClean.kem.supportedAlgorithms.find((a) => a.name === name);
const signatureAlgorithm = PQClean.sign.supportedAlgorithms.find((a) => a.name === name);
if (kemAlgorithm) {
let offset;
const privateKeyBytes = body.subarray(offset = 0, offset += kemAlgorithm.privateKeySize);
const encryptedKey = body.subarray(offset, offset += kemAlgorithm.encryptedKeySize);
const oneTimePaddedChallenge = body.subarray(offset);
const privateKeyBytes = Buffer.from(submittedFields.privateKey, 'hex');
const encryptedKey = Buffer.from(submittedFields.encryptedKey, 'hex');
const oneTimePaddedChallenge = Buffer.from(submittedFields.ciphertext, 'hex');
const privateKey = new PQClean.kem.PrivateKey(name, privateKeyBytes);
const key = await privateKey.decryptKey(encryptedKey);
if (!new Uint8Array(key).every((v, i) => v ^ oneTimePaddedChallenge[i] === challenge.nonce[i])) {
throw new Error('Invalid one-time padded ciphertext');
}
} else {
const publicKeyBytes = body.subarray(0, signatureAlgorithm.publicKeySize);
const signature = body.subarray(signatureAlgorithm.publicKeySize);
const publicKeyBytes = Buffer.from(submittedFields.publicKey, 'hex');
if (publicKeyBytes.byteLength !== signatureAlgorithm.publicKeySize) {
throw new Error('Invalid public key size');
}
const signature = Buffer.from(submittedFields.signature, 'hex');
const signedMessage = Buffer.from(submittedFields.signedMessage, 'hex');
const publicKey = new PQClean.sign.PublicKey(name, publicKeyBytes);
const ok = await publicKey.verify(challenge.nonce, signature);
if (!ok) throw new Error('Invalid signature');
const message = await publicKey.open(signedMessage);
if (!Buffer.from(message).equals(challenge.nonce)) {
throw new Error('Invalid signed message');
}
}

if (!challenge.remaining.has(name)) {
Expand Down

0 comments on commit 2d6f4a3

Please sign in to comment.