Skip to content
Permalink
Browse files

refactor(maker): use makeCert from electron-windows-store

* Fix #160
* Use upstream code to interface with `makecert.exe`
  • Loading branch information
jacobq authored and malept committed Mar 30, 2017
1 parent 45afdfb commit c31ceef6653630c26a9ee7d625a9eb085df229ae
Showing with 100 additions and 20 deletions.
  1. +1 −0 package.json
  2. BIN res/default.pvk
  3. +37 −20 src/makers/win32/appx.js
  4. +62 −0 test/fast/maker_appx_spec.js
@@ -123,6 +123,7 @@
"nugget": "^2.0.1",
"opn": "^5.0.0",
"ora": "^1.1.0",
"parse-author": "^2.0.0",
"pify": "^3.0.0",
"resolve-package": "^1.0.1",
"s3": "^4.4.0",
BIN -1.17 KB res/default.pvk
Binary file not shown.
@@ -1,7 +1,10 @@
import fs from 'fs';
import path from 'path';
import { spawnPromise, findActualExecutable } from 'spawn-rx';
import parseAuthor from 'parse-author';
import windowsStore from 'electron-windows-store';
import { isValidPublisherName, makeCert } from 'electron-windows-store/lib/sign.js';

import { findActualExecutable } from 'spawn-rx';
import { ensureDirectory } from '../../util/ensure-output';
import configFn from '../../util/config-fn';

@@ -23,40 +26,54 @@ function findSdkTool(exe) {
}

if (!fs.existsSync(sdkTool)) {
throw new Error(`Can't find ${exe} in PATH, you probably need to install the Windows SDK`);
throw new Error(`Can't find ${exe} in PATH. You probably need to install the Windows SDK.`);
}

return sdkTool;
}

function spawnSdkTool(exe, params) {
return spawnPromise(findSdkTool(exe), params);
export async function createDefaultCertificate(publisherName, { certFilePath, certFileName, install, program }) {
const makeCertOptions = {
publisherName,
certFilePath: certFilePath || process.cwd(),
certFileName: certFileName || 'default',
install: typeof install === 'boolean' ? install : false,
program: program || { windowsKit: path.dirname(findSdkTool('makecert.exe')) },
};

if (!isValidPublisherName(publisherName)) {
throw new Error(`Received invalid publisher name: '${publisherName}' did not conform to X.500 distinguished name syntax for MakeCert.`);
}

return await makeCert(makeCertOptions);
}

export async function createDefaultCertificate(publisherName, outPath) {
const defaultPvk = path.resolve(__dirname, '..', '..', '..', 'res', 'default.pvk');
const targetCert = path.join(outPath, 'default.cer');
const targetPfx = path.join(outPath, 'default.pfx');
export function getDistinguishedNameFromAuthor(author) {
let publisher = author || '';

await spawnSdkTool(
'makecert.exe',
['-r', '-h', '0', '-n', `CN=${publisherName}`, '-eku', '1.3.6.1.5.5.7.3.3', '-pe', '-sv', defaultPvk, targetCert]);
if (typeof publisher === 'string') {
publisher = parseAuthor(publisher);
}

await spawnSdkTool('pvk2pfx.exe', ['-pvk', defaultPvk, '-spc', targetCert, '-pfx', targetPfx]);
if (typeof publisher.name === 'string') {
publisher = publisher.name;
}

return targetPfx;
if (typeof publisher !== 'string') {
publisher = '';
}

return `CN=${publisher}`;
}

export default async ({ dir, appName, targetArch, forgeConfig, packageJSON }) => {
const windowsStore = require('electron-windows-store');

const outPath = path.resolve(dir, `../make/appx/${targetArch}`);
await ensureDirectory(outPath);

const userConfig = configFn(forgeConfig.windowsStoreConfig, targetArch);

const opts = Object.assign({
publisher: packageJSON.author,
publisher: getDistinguishedNameFromAuthor(packageJSON.author),
flatten: false,
deploy: false,
packageVersion: `${packageJSON.version}.0`,
@@ -70,12 +87,12 @@ export default async ({ dir, appName, targetArch, forgeConfig, packageJSON }) =>
outputDirectory: outPath,
});

if (!opts.devCert) {
opts.devCert = await createDefaultCertificate(opts.publisher, outPath);
if (!opts.publisher) {
throw 'Please set config.forge.windowsStoreConfig.publisher or author.name in package.json for the appx target';
}

if (!opts.publisher.match(/^CN=/)) {
opts.publisher = `CN=${opts.publisher}`;
if (!opts.devCert) {
opts.devCert = await createDefaultCertificate(opts.publisher, { certFilePath: outPath, program: opts });
}

if (opts.packageVersion.match(/-/)) {
@@ -0,0 +1,62 @@
import { tmpdir } from 'os';
import { join } from 'path';
import { copy, ensureDir, readFile, remove } from 'fs-promise';
import { expect } from 'chai';

import { getDistinguishedNameFromAuthor, createDefaultCertificate } from '../../src/makers/win32/appx.js';

describe('appx maker', () => {
describe('createDefaultCertificate', () => {
const tmpDir = join(tmpdir(), `electron-forge-maker-appx-test-${Date.now()}`);

before(async () => {
await ensureDir(tmpDir);
});

after(async () => {
await remove(tmpDir);
});

if (process.platform === 'win32') {
it('should create a .pfx file', async () => {
await copy(join(__dirname, '..', '..', 'node_modules',
'electron-windows-store', 'test', 'lib', 'bogus-private-key.pvk'),
join(tmpDir, 'dummy.pvk'));
const outputCertPath = await createDefaultCertificate('CN=Test', {
certFilePath: tmpDir,
certFileName: 'dummy',
install: false,
});

const fileContents = await readFile(outputCertPath);
expect(fileContents).to.be.an.instanceof(Buffer);
expect(fileContents.length).to.be.above(0);
});
}
});

describe('getDistinguishedNameFromAuthor', () => {
[{
author: 'First Last',
expectedReturnValue: 'CN=First Last',
}, {
author: 'First Last <first.last@example.com>',
expectedReturnValue: 'CN=First Last',
}, {
author: {
name: 'First Last',
},
expectedReturnValue: 'CN=First Last',
}, {
author: undefined,
expectedReturnValue: 'CN=',
}, {
author: '',
expectedReturnValue: 'CN=',
}].forEach((scenario) => {
it(`${JSON.stringify(scenario.author)} -> "${scenario.expectedReturnValue}"`, () => {
expect(getDistinguishedNameFromAuthor(scenario.author)).to.equal(scenario.expectedReturnValue);
});
});
});
});

0 comments on commit c31ceef

Please sign in to comment.
You can’t perform that action at this time.