Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cfb6dd1
Add service addition functionality to ServiceProviderFactory
clairernovotny Dec 11, 2024
30cf2cd
Refactor TrustedSigningService for DI and add Azure support
clairernovotny Dec 11, 2024
bf49764
cleanup
clairernovotny Dec 11, 2024
b23dbea
Rename private field `servicesToAdd` to `_servicesToAdd`
clairernovotny Dec 11, 2024
69a9cdf
Fix parameter name in test
clairernovotny Dec 11, 2024
41b08eb
Some formatting and renames based on feedback
clairernovotny Dec 11, 2024
6771ae1
Update editorconfig to match VS output, add additioal rules and sort …
clairernovotny Dec 11, 2024
991901d
Wrapped the Stream object in a using statement to ensure it is proper…
clairernovotny Dec 12, 2024
b76bd5b
Enhance Azure Key Vault integration
clairernovotny Dec 12, 2024
7f3a169
Add additional logging
clairernovotny Dec 12, 2024
92d6692
Refactor KeyVaultService and update tests
clairernovotny Dec 12, 2024
808959c
Update tests
clairernovotny Dec 13, 2024
0b2abdd
Finish tests
clairernovotny Dec 13, 2024
23f8be2
Revert editorconfig update. separate pr for that
clairernovotny Dec 13, 2024
a65bd80
Update src/Sign.SignatureProviders.TrustedSigning/Resources.resx
clairernovotny Dec 13, 2024
9bba4f3
Ensure the cert details are printed on a new line
clairernovotny Dec 13, 2024
e6f51ab
Handle the error with a better output message instead of a stack trac…
clairernovotny Dec 13, 2024
d2407d3
Apply feedback.
dtivel Feb 11, 2025
cff5177
Bump dependency version.
dtivel Feb 11, 2025
d715764
Apply feedback.
dtivel Feb 11, 2025
9836878
Add test
dtivel Feb 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.3.0" />
<PackageVersion Include="Microsoft.Dynamics.BusinessCentral.Sip.Main" Version="24.0.15760" />
<PackageVersion Include="Microsoft.Extensions.Azure" Version="1.10.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.1" />
Expand Down
49 changes: 48 additions & 1 deletion src/Sign.Cli/AzureKeyVaultCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Azure.Core;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sign.Core;
using Sign.SignatureProviders.KeyVault;

Expand Down Expand Up @@ -65,7 +71,48 @@ internal AzureKeyVaultCommand(CodeCommand codeCommand, IServiceProviderFactory s
Uri url = context.ParseResult.GetValueForOption(UrlOption)!;
string certificateId = context.ParseResult.GetValueForOption(CertificateOption)!;

KeyVaultServiceProvider keyVaultServiceProvider = new(credential, url, certificateId);
// Construct the URI for the certificate and the key from user parameters. We'll validate those with the SDK
var certUri = new Uri($"{url.Scheme}://{url.Authority}/certificates/{certificateId}");

if (!KeyVaultCertificateIdentifier.TryCreate(certUri, out var certId))
{
context.Console.Error.WriteLine(AzureKeyVaultResources.InvalidKeyVaultUrl);
context.ExitCode = ExitCode.InvalidOptions;
return;
}

// The key uri is similar and the key name matches the certificate name
var keyUri = new Uri($"{url.Scheme}://{url.Authority}/keys/{certificateId}");

if (!KeyVaultKeyIdentifier.TryCreate(certUri, out var keyId))
{
context.Console.Error.WriteLine(AzureKeyVaultResources.InvalidKeyVaultUrl);
context.ExitCode = ExitCode.InvalidOptions;
return;
}

serviceProviderFactory.AddServices(services =>
{
services.AddAzureClients(builder =>
{
builder.AddCertificateClient(certId.VaultUri);
builder.AddKeyClient(keyId.VaultUri);
builder.UseCredential(credential);
builder.ConfigureDefaults(options => options.Retry.Mode = RetryMode.Exponential);
});

services.AddSingleton<KeyVaultService>(serviceProvider =>
{
return new KeyVaultService(
serviceProvider.GetRequiredService<CertificateClient>(),
serviceProvider.GetRequiredService<CryptographyClient>(),
certId.Name,
serviceProvider.GetRequiredService<ILogger<KeyVaultService>>());
});
});

KeyVaultServiceProvider keyVaultServiceProvider = new();

await codeCommand.HandleAsync(context, serviceProviderFactory, keyVaultServiceProvider, fileArgument);
});
}
Expand Down
9 changes: 9 additions & 0 deletions src/Sign.Cli/AzureKeyVaultResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Sign.Cli/AzureKeyVaultResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@
<data name="CommandDescription" xml:space="preserve">
<value>Use Azure Key Vault.</value>
</data>
<data name="InvalidKeyVaultUrl" xml:space="preserve">
<value>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</value>
</data>
<data name="UrlOptionDescription" xml:space="preserve">
<value>URL to an Azure Key Vault.</value>
</data>
Expand Down
27 changes: 26 additions & 1 deletion src/Sign.Cli/TrustedSigningCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Azure.CodeSigning;
using Azure.CodeSigning.Extensions;
using Azure.Core;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sign.Core;
using Sign.SignatureProviders.TrustedSigning;

Expand Down Expand Up @@ -49,6 +54,7 @@ internal TrustedSigningCommand(CodeCommand codeCommand, IServiceProviderFactory
}

TokenCredential? credential = AzureCredentialOptions.CreateTokenCredential(context);

if (credential is null)
{
return;
Expand All @@ -60,7 +66,26 @@ internal TrustedSigningCommand(CodeCommand codeCommand, IServiceProviderFactory
string accountName = context.ParseResult.GetValueForOption(AccountOption)!;
string certificateProfileName = context.ParseResult.GetValueForOption(CertificateProfileOption)!;

TrustedSigningServiceProvider trustedSigningServiceProvider = new(credential, endpointUrl, accountName, certificateProfileName);
serviceProviderFactory.AddServices(services =>
{
services.AddAzureClients(builder =>
{
builder.AddCertificateProfileClient(endpointUrl);
builder.UseCredential(credential);
builder.ConfigureDefaults(options => options.Retry.Mode = RetryMode.Exponential);
});

services.AddSingleton<TrustedSigningService>(serviceProvider =>
{
return new TrustedSigningService(
serviceProvider.GetRequiredService<CertificateProfileClient>(),
accountName,
certificateProfileName,
serviceProvider.GetRequiredService<ILogger<TrustedSigningService>>());
});
});

TrustedSigningServiceProvider trustedSigningServiceProvider = new();

await codeCommand.HandleAsync(context, serviceProviderFactory, trustedSigningServiceProvider, fileArgument);
});
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Použijte Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Adresa URL Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Verwenden Sie Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">URL zu einem Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Use Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Dirección URL a un Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Utilisez Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">URL d’un Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Usare Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">URL a un Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Azure Key Vault を使用してください。</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Azure Key Vault への URL。</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Azure Key Vault를 사용합니다.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Azure Key Vault에 대한 URL입니다.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Użyj usługi Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Adres URL do usługi Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.pt-BR.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Use o Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">URL para um Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.ru.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Использование Azure Key Vault.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">URL-адрес для Azure Key Vault.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.tr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">Azure Key Vault’u kullanın.</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Azure Key Vault URL’si.</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.zh-Hans.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">使用 Azure Key Vault。</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">指向 Azure Key Vault 的 URL。</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Sign.Cli/xlf/AzureKeyVaultResources.zh-Hant.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<target state="translated">使用 Azure Key Vault。</target>
<note />
</trans-unit>
<trans-unit id="InvalidKeyVaultUrl">
<source>URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</source>
<target state="new">URL must only contain the protocol and host. (e.g.: https://&lt;vault-name&gt;.vault.azure.net/)</target>
<note />
</trans-unit>
<trans-unit id="UrlOptionDescription">
<source>URL to an Azure Key Vault.</source>
<target state="translated">Azure Key Vault 的 URL。</target>
Expand Down
2 changes: 1 addition & 1 deletion src/Sign.Core/DataFormatSigners/AzureSignToolSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ await Parallel.ForEachAsync(files, async (file, state) =>
{
string message = string.Format(CultureInfo.CurrentCulture, Resources.SigningFailed, file.FullName);

throw new Exception(message);
throw new SigningException(message);
}
});
}
Expand Down
4 changes: 3 additions & 1 deletion src/Sign.Core/IServiceProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ IServiceProvider Create(
LogLevel logLevel = LogLevel.Information,
ILoggerProvider? loggerProvider = null,
Action<IServiceCollection>? addServices = null);

void AddServices(Action<IServiceCollection> addServices);
}
}
}
17 changes: 16 additions & 1 deletion src/Sign.Core/ServiceProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,27 @@ namespace Sign.Core
{
internal sealed class ServiceProviderFactory : IServiceProviderFactory
{
private Action<IServiceCollection>? _addServices;

public IServiceProvider Create(
LogLevel logLevel = LogLevel.Information,
ILoggerProvider? loggerProvider = null,
Action<IServiceCollection>? addServices = null)
{

if (_addServices is not null)
{
addServices += _addServices;
}

return ServiceProvider.CreateDefault(logLevel, loggerProvider, addServices);
}

public void AddServices(Action<IServiceCollection> addServices)
{
ArgumentNullException.ThrowIfNull(addServices, nameof(addServices));

_addServices += addServices;
}
}
}
}
1 change: 1 addition & 0 deletions src/Sign.Core/Sign.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="AzureSign.Core" PrivateAssets="analyzers;build;compile;contentfiles" />
<PackageReference Include="Microsoft.Dynamics.BusinessCentral.Sip.Main" />
<PackageReference Include="Microsoft.Extensions.Azure" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" />
Expand Down
7 changes: 6 additions & 1 deletion src/Sign.Core/Signer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ await Parallel.ForEachAsync(inputFiles, parallelOptions, async (input, token) =>
_logger.LogError(e, e.Message);
return ExitCode.Failed;
}
catch (SigningException)
{
_logger.LogError(Resources.SigningFailedAfterAllAttempts);
return ExitCode.Failed;
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
Expand All @@ -186,4 +191,4 @@ private static string ExpandFilePath(DirectoryInfo baseDirectory, string file)
return file;
}
}
}
}
Loading