-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
windowsSignAzureManager.ts
140 lines (126 loc) · 6.18 KB
/
windowsSignAzureManager.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
import { asArray, InvalidConfigurationError, log } from "builder-util"
import { WindowsAzureSigningConfiguration, WindowsConfiguration } from "../options/winOptions"
import { WinPackager } from "../winPackager"
import { getPSCmd, WindowsSignOptions } from "./windowsCodeSign"
import { Lazy } from "lazy-val"
import { SignManager } from "./signManager"
import { MemoLazy } from "builder-util-runtime"
import { CertificateFromStoreInfo, FileCodeSigningInfo } from "./windowsSignToolManager"
export class WindowsSignAzureManager implements SignManager {
private readonly platformSpecificBuildOptions: WindowsConfiguration
readonly computedPublisherName = new Lazy<Array<string> | null>(() => {
const publisherName = this.platformSpecificBuildOptions.azureSignOptions?.publisherName
if (publisherName === null) {
return Promise.resolve(null)
} else if (publisherName != null) {
return Promise.resolve(asArray(publisherName))
}
// TODO: Is there another way to automatically pull Publisher Name from AzureTrusted service?
// For now return null.
return Promise.resolve(null)
})
constructor(private readonly packager: WinPackager) {
this.platformSpecificBuildOptions = packager.platformSpecificBuildOptions
}
async initialize() {
const vm = await this.packager.vm.value
const ps = await getPSCmd(vm)
log.info(null, "installing required module (TrustedSigning) with scope CurrentUser")
try {
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", "Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser"])
} catch (error: any) {
// Might not be needed, seems GH runners already have NuGet set up.
// Logging to debug just in case users run into this. If NuGet isn't present, Install-Module -Name TrustedSigning will fail, so we'll get the logs at that point
log.debug({ message: error.message || error.stack }, "unable to install PackageProvider Nuget. Might be a false alarm though as some systems already have it installed")
}
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", "Install-Module -Name TrustedSigning -RequiredVersion 0.4.1 -Force -Repository PSGallery -Scope CurrentUser"])
// Preemptively check env vars once during initialization
// Options: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition
log.info(null, "verifying env vars for authenticating to Microsoft Entra ID")
this.verifyRequiredEnvVars()
if (!(this.verifyPrincipleSecretEnv() || this.verifyPrincipleCertificateEnv() || this.verifyUsernamePasswordEnv())) {
throw new InvalidConfigurationError(
`Unable to find valid azure env configuration for signing. Missing field(s) can be debugged via "DEBUG=electron-builder". Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition`
)
}
}
verifyRequiredEnvVars() {
;["AZURE_TENANT_ID", "AZURE_CLIENT_ID"].forEach(field => {
if (!process.env[field]) {
throw new InvalidConfigurationError(
`Unable to find valid azure env field ${field} for signing. Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition`
)
}
})
}
verifyPrincipleSecretEnv() {
if (!process.env.AZURE_CLIENT_SECRET) {
log.debug({ envVar: "AZURE_CLIENT_SECRET" }, "no secret found for authenticating to Microsoft Entra ID")
return false
}
return true
}
verifyPrincipleCertificateEnv() {
if (!process.env.AZURE_CLIENT_CERTIFICATE_PATH) {
log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PATH" }, "no path found for signing certificate for authenticating to Microsoft Entra ID")
return false
}
if (!process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD) {
log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PASSWORD" }, "(optional) certificate password not found, assuming no password")
}
if (!process.env.AZURE_CLIENT_SEND_CERTIFICATE_CHAIN) {
log.debug({ envVar: "AZURE_CLIENT_SEND_CERTIFICATE_CHAIN" }, "(optional) certificate chain not found")
}
return true
}
verifyUsernamePasswordEnv() {
if (!process.env.AZURE_USERNAME) {
log.debug({ envVar: "AZURE_USERNAME" }, "no username found for authenticating to Microsoft Entra ID")
if (!process.env.AZURE_PASSWORD) {
log.debug({ envVar: "AZURE_PASSWORD" }, "no password found for authenticating to Microsoft Entra ID")
}
return false
}
return true
}
computePublisherName(): Promise<string> {
return Promise.resolve(this.packager.platformSpecificBuildOptions.azureSignOptions!.publisherName)
}
readonly cscInfo = new MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>(
() => this.packager.platformSpecificBuildOptions,
_selected => Promise.resolve(null)
)
// prerequisite: requires `initializeProviderModules` to already have been executed
async signFile(options: WindowsSignOptions): Promise<boolean> {
const vm = await this.packager.vm.value
const ps = await getPSCmd(vm)
const {
publisherName: _publisher, // extract from `extraSigningArgs`
endpoint,
certificateProfileName,
codeSigningAccountName,
fileDigest,
timestampRfc3161,
timestampDigest,
...extraSigningArgs
}: WindowsAzureSigningConfiguration = options.options.azureSignOptions!
const params = {
...extraSigningArgs,
Endpoint: endpoint,
CertificateProfileName: certificateProfileName,
CodeSigningAccountName: codeSigningAccountName,
TimestampRfc3161: timestampRfc3161 || "http://timestamp.acs.microsoft.com",
TimestampDigest: timestampDigest || "SHA256",
FileDigest: fileDigest || "SHA256",
Files: `"${options.path}"`,
}
const paramsString = Object.entries(params)
.filter(([_, value]) => value != null)
.reduce((res, [field, value]) => {
return [...res, `-${field}`, value]
}, [] as string[])
.join(" ")
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", `Invoke-TrustedSigning ${paramsString}`])
return true
}
}