forked from markeytos/code-sign-action
-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.ts
200 lines (185 loc) · 6.74 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import * as core from '@actions/core';
import { promises as fs } from 'fs';
import path from 'path';
import util from 'util';
import { exec } from 'child_process';
import { env } from 'process';
const asyncExec = util.promisify(exec);
const certificateFileName = env['TEMP'] + '\\certificate.pfx';
interface ChildProcessError extends Error {
code : number;
stdout : string;
stderr : string;
}
function isChildProcessError(obj: any): obj is ChildProcessError {
return obj && obj instanceof Error && 'code' in obj && 'stdout' in obj && 'stderr' in obj;
}
const signtoolFileExtensions = [
'.dll', '.exe', '.sys', '.vxd',
'.msix', '.msixbundle', '.appx',
'.appxbundle', '.msi', '.msp',
'.msm', '.cab', '.cat', '.js',
'.vbs', '.wsf', '.ps1', '.psm1',
'.efi', '.scr', '.ocx'
];
async function createCertificatePfx() {
const base64Certificate = core.getInput('certificate');
const certificate = Buffer.from(base64Certificate, 'base64');
if (certificate.length == 0) {
throw new Error("Required certificate is an empty string")
}
console.log(`Writing ${certificate.length} bytes to ${certificateFileName}.`);
await fs.writeFile(certificateFileName, certificate);
}
async function addCertificateToStore(){
const password : string= core.getInput('password');
if (password == ''){
throw new Error("Required Password to store certificate is an empty string");
}
var command = `certutil -f -p ${password} -importpfx ${certificateFileName}`
try {
const { stdout } = await asyncExec(command);
console.log(stdout);
} catch( err) {
if(isChildProcessError(err)) {
console.log(err.stdout);
console.error('Process to add certificate exited with code %d.', err.code);
console.error(err.stderr)
}
throw err;
}
}
async function printCertificateExpirationDate() {
const password : string= core.getInput('password');
var infoCommand = `openssl pkcs12 -in "${certificateFileName}" -nodes -passin pass:"${password}"| openssl x509 -noout -enddate"`
try {
const { stdout } = await asyncExec(infoCommand);
console.log(`Certificate valid until ${stdout.trim().split('=')[1]}`);
} catch( err) {
if(isChildProcessError(err)) {
console.log('Skipping print certificate expiration date due to error (Return code %d).', err.code);
console.log('Ensure openssl is installed on system.');
}
}
}
async function signWithSigntool(signtool: string, fileName: string) {
// see https://docs.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
var vitalParameterIncluded = false;
var timestampUrl : string = core.getInput('timestampUrl');
if (timestampUrl === '') {
timestampUrl = 'http://timestamp.digicert.com';
}
var command = `"${signtool}" sign /sm /tr ${timestampUrl} /td SHA256 /fd SHA256`
const sha1 : string= core.getInput('certificatesha1');
if (sha1 != ''){
command = command + ` /sha1 "${sha1}"`
vitalParameterIncluded = true;
}
const name : string= core.getInput('certificatename');
if (name != ''){
vitalParameterIncluded = true;
command = command + ` /n "${name}"`
}
const desc : string= core.getInput('description');
if (desc != ''){
vitalParameterIncluded = true;
command = command + ` /d "${desc}"`
}
if (!vitalParameterIncluded){
console.warn("You need to include a NAME or a SHA1 Hash for the certificate to sign with.")
}
command = command + ` ${fileName}`;
console.log("Signing command: " + command);
try {
const { stdout } = await asyncExec(command);
console.log(stdout);
} catch(err) {
if( isChildProcessError(err) ) {
console.log(err.stdout);
console.error('Process to sign file exited with code %d.', err.code);
console.error(err.stderr);
}
throw err;
}
}
async function trySignFile(signtool: string, fileName: string) {
console.log(`Signing ${fileName}.`);
const extension = path.extname(fileName);
if (signtoolFileExtensions.includes(extension)) {
await signWithSigntool(signtool, fileName);
}
}
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) || extension == '.nupkg')
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';
const signtool = await getSigntoolLocation()
for await (const file of getFiles(folder, recursive)) {
await trySignFile(signtool, file);
}
}
/**
* Searches the installed Windows SDKs for the most recent signtool.exe version
* Taken from https://github.com/dlemstra/code-sign-action
* @returns Path to most recent signtool.exe (x86 version)
*/
async function getSigntoolLocation() {
const windowsKitsFolder = 'C:/Program Files (x86)/Windows Kits/10/bin/';
const folders = await fs.readdir(windowsKitsFolder);
let fileName = '';
let maxVersion = 0;
for (const folder of folders) {
if (!folder.endsWith('.0')) {
continue;
}
const folderVersion = parseInt(folder.replace(/\./g,''));
if (folderVersion > maxVersion) {
const signtoolFilename = `${windowsKitsFolder}${folder}/x64/signtool.exe`;
try {
const stat = await fs.stat(signtoolFilename);
if (stat.isFile()) {
fileName = signtoolFilename;
maxVersion = folderVersion;
}
}
catch {
console.warn('Skipping %s due to error.', signtoolFilename);
}
}
}
if(fileName == '') {
throw new Error('Unable to find signtool.exe in ' + windowsKitsFolder);
}
console.log(`Signtool location is ${fileName}.`);
return fileName;
}
async function run() {
core.setSecret(core.getInput('password'));
try {
await createCertificatePfx();
await addCertificateToStore();
await printCertificateExpirationDate();
await signFiles();
} catch (err) {
if (err instanceof Error) {
core.setFailed(err);
} else {
core.setFailed(`Action failed with response: ${err}`);
}
}
}
run();