Skip to content
Merged
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
61 changes: 44 additions & 17 deletions packages/plugin-verification/src/verification.test.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
import { createPackageFromStreams } from '@electron/asar';
import crypto from 'crypto';
import os from 'os';
import path from 'path';
import { Readable } from 'stream';
import { v4 as uuidV4 } from 'uuid';
import { beforeAll, expect, test } from 'vitest';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { CogsPluginManifest } from '../../javascript/src';
import { addSignatureToPlugin, verifyPluginSignature } from './verification';

// Test key pair for signing/verifying .cogsplugin files
beforeAll(() => {
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' });
process.env.COGS_PRIVATE_KEY = privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
process.env.COGS_PUBLIC_KEY = publicKey.export({ type: 'spki', format: 'pem' }).toString();
// Test key pair for signing .cogsplugin files
const testKeys = vi.hoisted(() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const cryptoMod = require('crypto') as typeof import('crypto');
const { privateKey, publicKey } = cryptoMod.generateKeyPairSync('ec', { namedCurve: 'P-256' });
return {
privateKeyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
publicKeyPem: publicKey.export({ type: 'spki', format: 'pem' }).toString(),
};
});

test('unsigned plugin', async () => {
const pluginPath = await makePluginAsar('unsigned-plugin');
describe.each(['environment variable', 'explicit parameter'] as const)('%s', (keySource) => {
let privateKey: string | undefined = undefined;
let publicKey: string | undefined = undefined;

beforeEach(() => {
privateKey = testKeys.privateKeyPem;
publicKey = testKeys.publicKeyPem;

expect(await verifyPluginSignature(pluginPath)).toEqual({
verified: false,
error: 'No verification signature found',
switch (keySource) {
case 'environment variable':
process.env.COGS_PRIVATE_KEY = privateKey;
process.env.COGS_PUBLIC_KEY = publicKey;
privateKey = undefined;
publicKey = undefined;
break;

case 'explicit parameter':
privateKey = testKeys.privateKeyPem;
publicKey = testKeys.publicKeyPem;
break;
}
});

test('unsigned plugin', async () => {
const pluginPath = await makePluginAsar('unsigned-plugin');

expect(await verifyPluginSignature(pluginPath, { publicKey })).toEqual({
verified: false,
error: 'No verification signature found',
});
});
});

test('signed plugin', async () => {
const pluginPath = await addSignatureToPlugin(await makePluginAsar('signed-plugin'));
test('signed plugin', async () => {
const pluginPath = await addSignatureToPlugin(await makePluginAsar('signed-plugin'), { privateKey });

expect(await verifyPluginSignature(pluginPath)).toEqual({
verified: true,
expect(await verifyPluginSignature(pluginPath, { publicKey })).toEqual({
verified: true,
});
});
});

Expand Down
16 changes: 10 additions & 6 deletions packages/plugin-verification/src/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ export interface VerificationResult {
/**
* Check the signature of a verified .cogsplugin file.
*
* The public key is loaded from the COGS_PUBLIC_KEY environment variable.
* The public key is loaded from the COGS_PUBLIC_KEY environment variable if `publicKey` is not set.
*/
export async function verifyPluginSignature(cogsPluginPath: string): Promise<VerificationResult> {
export async function verifyPluginSignature(
cogsPluginPath: string,
{ publicKey = process.env.COGS_PUBLIC_KEY }: { publicKey?: string } = {},
): Promise<VerificationResult> {
try {
const buffer = await readCogsPluginBuffer(cogsPluginPath);
const signature = extractSignatureFromCogsPlugin(buffer);
Expand All @@ -35,7 +38,6 @@ export async function verifyPluginSignature(cogsPluginPath: string): Promise<Ver
verifier.update(asarBytes);
verifier.end();

const publicKey = process.env.COGS_PUBLIC_KEY;
if (!publicKey) {
throw new Error('COGS_PUBLIC_KEY environment variable is not set');
}
Expand All @@ -56,11 +58,13 @@ export async function verifyPluginSignature(cogsPluginPath: string): Promise<Ver
*
* Overwrites the plugin file with the signed version.
*
* The private key is loaded from the COGS_PRIVATE_KEY environment variable.
* The private key is loaded from the COGS_PRIVATE_KEY environment variable if `privateKey` is not set.
* Never commit the private key to the repository.
*/
export async function addSignatureToPlugin(cogsPluginPath: string): Promise<string> {
const privateKey = process.env.COGS_PRIVATE_KEY;
export async function addSignatureToPlugin(
cogsPluginPath: string,
{ privateKey = process.env.COGS_PRIVATE_KEY }: { privateKey?: string } = {},
): Promise<string> {
if (!privateKey) {
throw new Error('COGS_PRIVATE_KEY environment variable is not set');
}
Expand Down
Loading