Skip to content

Commit 12a1b40

Browse files
committed
Add code signing framework and config
1 parent e2596a2 commit 12a1b40

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

Config.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,71 @@ public class InstallationConfig : ITomlMetadataProvider
178178
[DataMember(Name = "source_dirs_are_recursive")]
179179
public bool? DirsRecursive { get; set; }
180180

181+
/// <summary>
182+
/// Configuration for code signing the MSI and installed files.
183+
/// </summary>
184+
[DataMember(Name = "signing")]
185+
public SigningConfig? Signing { get; set; }
186+
public class SigningConfig : ITomlMetadataProvider
187+
{
188+
[IgnoreDataMember]
189+
public TomlPropertiesMetadata? PropertiesMetadata { get; set; }
190+
191+
/// <summary>
192+
/// Name of the PFX File or the certificate in the certificate store to use for signing.
193+
/// </summary>
194+
[DataMember(IsRequired = true, Name = "cert_name")]
195+
public string CertificateName { get; set; } = "";
196+
197+
/// <summary>
198+
/// PFX file password. Shouldn't be provided in the config for security reasons.
199+
/// </summary>
200+
[IgnoreDataMember] public string? Password { get; set; } = null;
201+
202+
/// <summary>
203+
/// Description to include in the signature. E.g. My Company MyApp.
204+
/// </summary>
205+
[DataMember(IsRequired = true, Name = "description")]
206+
public string Description { get; set; } = "";
207+
208+
/// <summary>
209+
/// URL to the timestamping server.
210+
/// </summary>
211+
[DataMember(Name = "time_url")]
212+
public string? TimeUrl { get; set; }
213+
214+
/// <summary>
215+
/// Type of the certificate store. May be "sha1", "name" or "pfx". Defaults to "pfx".
216+
/// </summary>
217+
[DataMember(Name = "store_type")]
218+
public string? StoreType { get; set; }
219+
220+
/// <summary>
221+
/// Hash algorithm to use for signing. May be "sha1" or "sha256". Defaults to "sha256".
222+
/// </summary>
223+
[DataMember(Name = "algorithm")]
224+
public string? HashAlgorithm { get; set; }
225+
226+
/// <summary>
227+
/// Additional arguments to pass to signtool
228+
/// </summary>
229+
[DataMember(Name = "extra_arguments")]
230+
public string? ExtraArguments { get; set; }
231+
232+
/// <summary>
233+
/// Folder where signtool.exe is located. If not specified, it is assumed to be in the PATH. May include multiple ; separated paths.
234+
/// </summary>
235+
[DataMember(Name = "signtool_location")]
236+
public string? SignToolLocation { get; set; }
237+
238+
/// <summary>
239+
/// Sign all files embedded in the MSI as well. This includes Program files and DLLs of the App you're shipping unless they are already signed.
240+
/// Note: The signing is performed in place on your source files, so make sure to have backups if needed.
241+
/// </summary>
242+
[DataMember(Name = "sign_embedded")]
243+
public bool? SignEmbeddedFiles { get; set; }
244+
}
245+
181246
[DataMember(Name = "env_vars")]
182247
public List<EnvVarConfig> EnvironmentVariables { get; } = [];
183248
public class EnvVarConfig : ITomlMetadataProvider

ConfigExtensions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,39 @@ internal static class ConfigExtensions
120120
_ => null
121121
};
122122
}
123+
124+
/// <summary>
125+
/// Determines the hash algorithm type specified by the file hash configuration.
126+
/// </summary>
127+
/// <remarks>If the configuration specifies an unrecognized algorithm, the method defaults to
128+
/// HashAlgorithmType.sha1.</remarks>
129+
/// <param name="config">The file hash configuration from which to determine the hash algorithm. Cannot be null.</param>
130+
/// <returns>A value of the HashAlgorithmType enumeration that corresponds to the algorithm specified in the configuration.
131+
/// Returns HashAlgorithmType.sha1 if the algorithm is not recognized.</returns>
132+
public static HashAlgorithmType? GetHashAlgorithm(this Config.InstallationConfig.SigningConfig config)
133+
{
134+
return config.HashAlgorithm switch
135+
{
136+
"sha1" => HashAlgorithmType.sha1,
137+
"sha256" => HashAlgorithmType.sha256,
138+
139+
// default to sha1
140+
null => HashAlgorithmType.sha256,
141+
_ => null
142+
};
143+
}
144+
145+
public static StoreType? GetStoreType(this Config.InstallationConfig.SigningConfig config)
146+
{
147+
return config.StoreType switch
148+
{
149+
"sha1" => StoreType.sha1Hash,
150+
"name" => StoreType.commonName,
151+
"pfx" => StoreType.file,
152+
153+
// default to file
154+
null => StoreType.file,
155+
_ => null
156+
};
157+
}
123158
}

MsiEngine.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,38 @@ public void ConfigureMsi(Config config)
117117

118118
_msi.ResolveWildCards();
119119

120+
print.VerboseLine("Configuring code signing...");
121+
if (config.Installation?.Signing is not null)
122+
{
123+
var signature = new DigitalSignature()
124+
{
125+
CertificateId = config.Installation.Signing.CertificateName,
126+
Password = config.Installation.Signing.Password,
127+
Description = config.Installation.Signing.Description,
128+
129+
WellKnownLocations = config.Installation.Signing.SignToolLocation,
130+
OptionalArguments = config.Installation.Signing.ExtraArguments,
131+
132+
OutputLevel = print.IsVerbose ? SignOutputLevel.Verbose : SignOutputLevel.Standard
133+
};
134+
135+
if (config.Installation.Signing.TimeUrl is {} url)
136+
{
137+
if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
138+
throw new ArgumentException("Timestamp URL is not valid", nameof(config));
139+
140+
signature.TimeUrl = new Uri(url);
141+
}
142+
143+
signature.HashAlgorithm = config.Installation.Signing.GetHashAlgorithm() ??
144+
throw new ArgumentException("Signing has algorithm is invalid", nameof(config));
145+
signature.CertificateStore = config.Installation.Signing.GetStoreType() ??
146+
throw new ArgumentException("Signing certificate store is invalid", nameof(config));
147+
148+
_msi.DigitalSignature = signature;
149+
_msi.SignAllFiles = config.Installation.Signing.SignEmbeddedFiles == true;
150+
}
151+
120152
foreach (var shortcut in config.Installation?.Shortcuts ?? [])
121153
{
122154
var files = _msi.FindFile(f => f.Name.EndsWith(shortcut.TargetFile));

0 commit comments

Comments
 (0)