-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2345bbb
Showing
8 changed files
with
599 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
lib | ||
*.swp | ||
/compile.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Oops, something went wrong.