Skip to content

Commit

Permalink
first version
Browse files Browse the repository at this point in the history
  • Loading branch information
nextgens committed Jul 30, 2020
0 parents commit 2345bbb
Show file tree
Hide file tree
Showing 8 changed files with 599 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
lib
*.swp
/compile.sh
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Authenticode cloud signer

This action signs files that are supported by `signtool.exe` with a key hosted on google KMS. This enables EV code-signing certificates to be used in a CI pipeline. It only works on Windows and should run on `windows-latest`.

This is a forked/cloudified version of dlemstra/code-sign-action/

## Inputs

### `certificate`

**Required** The base64 encoded certificate chain in PEM format.

### `key-uri`

**Required** The google KMS resource ID to use.

### `credentials`

**Required** The base64 encoded JSON credentials to use.

### `folder`

**Required** The folder that contains the libraries to sign.

### `recursive`

**Optional** Recursively search for DLL files.

## Example usage

```
runs-on: windows-latest
steps:
uses: nextgens/authenticode-sign-action@v1
with:
certificate: '${{ secrets.CERTIFICATES }}'
key-uri: 'projects/myProject/locations/europe-west2/keyRings/code-signing/cryptoKeys/ev/cryptoKeyVersions/1'
credentials: '${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}'
folder: 'files'
recursive: true
```
24 changes: 24 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: 'Authenticode cloud signer'
description: 'Code-sign files using a key hosted on google KMS.'
branding:
icon: 'award'
color: 'green'
inputs:
key-uri:
description: 'The google KMS resource ID to use.'
required: true
certificate:
description: 'The base64 encoded certificate chain to use (PEM).'
required: true
credentials:
description: 'The base64 encoded JSON credentials to use.'
required: true
folder:
description: 'The folder that contains the files to sign.'
required: true
recursive:
description: 'Recursively search for supported files.'
required: false
runs:
using: 'node12'
main: 'dist/index.js'
1 change: 1 addition & 0 deletions dist/index.js

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as core from '@actions/core';
import { promises as fs } from 'fs';
import { unlinkSync, existsSync, createWriteStream } from 'fs';
import path from 'path';
import util from 'util';
import { exec } from 'child_process';
import { env } from 'process';
const request = require('request');

const asyncExec = util.promisify(exec);
const certificateFileName = env['TEMP'] + '\\cert.pem';
const signtool = env['TEMP'] + '\\signtool.exe';
const credentialsFileName = env['TEMP'] + '\\creds.json';
const timestampUrl = 'http://timestamp.digicert.com';
const toSignFileName = env['TEMP'] + '\\tosign.txt';

const signtoolFileExtensions = [
'.dll', '.exe', '.sys', '.vxd',
'.msix', '.msixbundle', '.appx',
'.appxbundle', '.msi', '.msp',
'.msm', '.cab', '.ps1', '.psm1'
];

function sleep(seconds: number) {
if (seconds > 0)
console.log(`Waiting for ${seconds} seconds.`);
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}

async function createCertificate() {
const base64Certificate = core.getInput('certificate');
const certificate = Buffer.from(base64Certificate, 'base64');
if (certificate.length == 0) {
console.log('The value for "certificate" is not set.');
return false;
}
console.log(`Writing ${certificate.length} bytes to ${certificateFileName}.`);
await fs.writeFile(certificateFileName, certificate);
return true;
}

async function createCredentials() {
const base64Certificate = core.getInput('credentials');
const credentials = Buffer.from(base64Certificate, 'base64');
if (credentials.length == 0) {
console.log('The value for "credentials" is not set.');
return false;
}
console.log(`Writing ${credentials.length} bytes to ${credentialsFileName}.`);
await fs.writeFile(credentialsFileName, credentials);
return true;
}

function downloadCloudSignTool() {
if (existsSync(signtool)) {
return;
}

console.log(`Downloading signtool.exe.`);

request('https://github.com/nextgens/CloudSignTool/releases/download/1.0.0/SignTool.exe').pipe(createWriteStream(signtool));
}

async function signWithCloudSigntool() {
try {
const { stdout } = await asyncExec(`"${signtool}" sign -kac "${credentialsFileName}" -ac "${certificateFileName}" -tr "${timestampUrl}" -k "${core.getInput('key-uri')}" -ph -ifl "${toSignFileName}"`);
console.log(stdout);
return true;
} catch(err) {
console.log(err.stdout);
console.log(err.stderr);
return false;
}
}

async function* getFiles(folder: string, recursive: boolean): any {
const files = await fs.readdir(folder);
for (const file of files) {
const fullPath = `${folder}/${file}`;
const stat = await fs.stat(fullPath);
if (stat.isFile()) {
const extension = path.extname(file);
if (signtoolFileExtensions.includes(extension))
yield fullPath;
}
else if (stat.isDirectory() && recursive) {
yield* getFiles(fullPath, recursive);
}
}
}

async function signFiles() {
const folder = core.getInput('folder', { required: true });
const recursive = core.getInput('recursive') == 'true';

console.log(`Getting ready to sign the following files:`);
let buffer: string[] = [];
for await (const file of getFiles(folder, recursive)) {
console.log(` ${file}`);
buffer.push(file);
}
if(buffer.length > 0) {
await fs.writeFile(toSignFileName, buffer.join("\r\n"));
console.log(`Getting ready to talk to the cloud.`);
for (let i=0;i<10;i++) {
await sleep(i);
if(await signWithCloudSigntool()) { return; }
}
throw `Failed to sign`;
}
}

async function run() {
try {
await createCredentials();
downloadCloudSignTool();
if (await createCertificate())
await signFiles();
}
catch (err) {
core.setFailed(`Action failed with error: ${err}`);
}
unlinkSync(credentialsFileName);
}

run();
Loading

0 comments on commit 2345bbb

Please sign in to comment.