Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/keyResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ export type ResolveKeyResult = ResolveKeySuccess | ResolveKeyFailure;

function asPublicKey(raw: string): ReturnType<typeof createPublicKey> {
const trimmed = raw.trim();
if (trimmed.includes('BEGIN PUBLIC KEY')) {
return createPublicKey(trimmed);
const key = trimmed.includes('BEGIN PUBLIC KEY')
? createPublicKey(trimmed)
: createPublicKey({ key: Buffer.from(trimmed, 'base64'), format: 'der', type: 'spki' });

if (key.asymmetricKeyType !== 'ed25519') {
throw new Error(`unsupported public key type: ${key.asymmetricKeyType ?? 'unknown'}`);
}
const der = Buffer.from(trimmed, 'base64');
return createPublicKey({ key: der, format: 'der', type: 'spki' });

return key;
}

type RemoteKeyRecord = {
Expand Down
21 changes: 21 additions & 0 deletions tests/verify.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { generateKeyPairSync, sign } from 'node:crypto';
import { describe, expect, it } from 'vitest';
import { canonicalizeReceiptBytes } from '../src/canonicalize.js';
import { verifyReceipt } from '../src/verify.js';

async function loadJson(name: string): Promise<unknown> {
Expand Down Expand Up @@ -59,4 +61,23 @@ describe('verifyReceipt', () => {
expect(result.errorCode).toBe('SIGNATURE_INVALID');
}
});

it('rejects non-Ed25519 signing keys', async () => {
const receipt = (await loadJson('valid.json')) as Record<string, unknown>;
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
receipt.signatureKeyId = 'rsa-test';
receipt.signatureValue = sign(null, canonicalizeReceiptBytes(receipt), privateKey).toString('base64');

const encodedKey = publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
const keyUrl = `data:application/json,${encodeURIComponent(
JSON.stringify({ keyId: receipt.signatureKeyId, publicKey: encodedKey }),
)}`;
const result = await verifyReceipt(receipt, { keyUrl });

expect(result.verified).toBe(false);
if (!result.verified) {
expect(result.exitCode).toBe(4);
expect(result.errorCode).toBe('KEY_RESOLUTION_FAILED');
}
});
});