Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Trusted Signing #716

Merged
merged 13 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Azure.Core" Version="1.38.0" />
<PackageVersion Include="Azure.CodeSigning.Sdk" Version="0.1.96" />
<PackageVersion Include="Azure.Identity" Version="1.11.4" />
<PackageVersion Include="Azure.Security.KeyVault.Certificates" Version="4.6.0" />
<PackageVersion Include="AzureSign.Core" Version="4.0.1" />
Expand Down Expand Up @@ -31,4 +33,4 @@
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
<PackageVersion Include="Microsoft.Dynamics.BusinessCentral.Sip.Main" Version="24.0.15760" />
</ItemGroup>
</Project>
</Project>
14 changes: 14 additions & 0 deletions sign.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sign.SignatureProviders.Cer
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sign.SignatureProviders.CertificateStore.Test", "test\Sign.SignatureProviders.CertificateStore.Test\Sign.SignatureProviders.CertificateStore.Test.csproj", "{3AE48DC2-8422-4E3A-AFBC-12551D50DBCA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sign.SignatureProviders.TrustedSigning", "src\Sign.SignatureProviders.TrustedSigning\Sign.SignatureProviders.TrustedSigning.csproj", "{060800AF-42FC-493C-AD99-9C87212BA969}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sign.SignatureProviders.TrustedSigning.Test", "test\Sign.SignatureProviders.TrustedSigning.Test\Sign.SignatureProviders.TrustedSigning.Test.csproj", "{A81695AF-088A-436A-9A38-4D0B0DB2D826}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -84,6 +88,14 @@ Global
{3AE48DC2-8422-4E3A-AFBC-12551D50DBCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AE48DC2-8422-4E3A-AFBC-12551D50DBCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AE48DC2-8422-4E3A-AFBC-12551D50DBCA}.Release|Any CPU.Build.0 = Release|Any CPU
{060800AF-42FC-493C-AD99-9C87212BA969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{060800AF-42FC-493C-AD99-9C87212BA969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{060800AF-42FC-493C-AD99-9C87212BA969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{060800AF-42FC-493C-AD99-9C87212BA969}.Release|Any CPU.Build.0 = Release|Any CPU
{A81695AF-088A-436A-9A38-4D0B0DB2D826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A81695AF-088A-436A-9A38-4D0B0DB2D826}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A81695AF-088A-436A-9A38-4D0B0DB2D826}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A81695AF-088A-436A-9A38-4D0B0DB2D826}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -98,6 +110,8 @@ Global
{47F03ADD-A646-44C8-92FA-9594CD4506E6} = {780818DD-6B52-47C8-AC54-71448DF822BD}
{68104303-9832-4841-89AB-B98712C4E618} = {92C73EE1-4EF3-4721-B6A9-9F458A673CA3}
{3AE48DC2-8422-4E3A-AFBC-12551D50DBCA} = {780818DD-6B52-47C8-AC54-71448DF822BD}
{060800AF-42FC-493C-AD99-9C87212BA969} = {92C73EE1-4EF3-4721-B6A9-9F458A673CA3}
{A81695AF-088A-436A-9A38-4D0B0DB2D826} = {780818DD-6B52-47C8-AC54-71448DF822BD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7AA1043F-37A2-404F-8EC3-34C747C1CEB7}
Expand Down
51 changes: 51 additions & 0 deletions src/Sign.Cli/AzureCredentialOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE.txt file in the project root for more information.

using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Azure.Core;
using Azure.Identity;

namespace Sign.Cli
{
internal sealed class AzureCredentialOptions
{
internal Option<bool?> ManagedIdentityOption { get; } = new(["-kvm", "--azure-key-vault-managed-identity"], Resources.ManagedIdentityOptionDescription);
internal Option<string?> TenantIdOption { get; } = new(["-kvt", "--azure-key-vault-tenant-id"], Resources.TenantIdOptionDescription);
internal Option<string?> ClientIdOption { get; } = new(["-kvi", "--azure-key-vault-client-id"], Resources.ClientIdOptionDescription);
internal Option<string?> ClientSecretOption { get; } = new(["-kvs", "--azure-key-vault-client-secret"], Resources.ClientSecretOptionDescription);

internal void AddOptionsToCommand(Command command)
{
command.AddOption(ManagedIdentityOption);
command.AddOption(TenantIdOption);
command.AddOption(ClientIdOption);
command.AddOption(ClientSecretOption);
}

internal TokenCredential? CreateTokenCredential(InvocationContext context)
{
bool? useManagedIdentity = context.ParseResult.GetValueForOption(ManagedIdentityOption);

if (useManagedIdentity is not null)
{
context.Console.Out.WriteLine(Resources.ManagedIdentityOptionObsolete);
}

string? tenantId = context.ParseResult.GetValueForOption(TenantIdOption);
string? clientId = context.ParseResult.GetValueForOption(ClientIdOption);
string? secret = context.ParseResult.GetValueForOption(ClientSecretOption);

if (!string.IsNullOrEmpty(tenantId) &&
!string.IsNullOrEmpty(clientId) &&
!string.IsNullOrEmpty(secret))
{
return new ClientSecretCredential(tenantId, clientId, secret);
}

return new DefaultAzureCredential();
}
}
}
55 changes: 10 additions & 45 deletions src/Sign.Cli/AzureKeyVaultCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,16 @@
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Azure.Core;
using Azure.Identity;
using Sign.Core;
using Sign.SignatureProviders.KeyVault;

namespace Sign.Cli
{
internal sealed class AzureKeyVaultCommand : Command
{
private readonly CodeCommand _codeCommand;

internal Option<string> CertificateOption { get; } = new(["-kvc", "--azure-key-vault-certificate"], AzureKeyVaultResources.CertificateOptionDescription);
internal Option<string?> ClientIdOption { get; } = new(["-kvi", "--azure-key-vault-client-id"], AzureKeyVaultResources.ClientIdOptionDescription);
internal Option<string?> ClientSecretOption { get; } = new(["-kvs", "--azure-key-vault-client-secret"], AzureKeyVaultResources.ClientSecretOptionDescription);
internal Option<bool> ManagedIdentityOption { get; } = new(["-kvm", "--azure-key-vault-managed-identity"], getDefaultValue: () => false, AzureKeyVaultResources.ManagedIdentityOptionDescription);
internal Option<string?> TenantIdOption { get; } = new(["-kvt", "--azure-key-vault-tenant-id"], AzureKeyVaultResources.TenantIdOptionDescription);
internal Option<Uri> UrlOption { get; } = new(["-kvu", "--azure-key-vault-url"], AzureKeyVaultResources.UrlOptionDescription);
internal Option<string> CertificateOption { get; } = new(["-kvc", "--azure-key-vault-certificate"], AzureKeyVaultResources.CertificateOptionDescription);
internal AzureCredentialOptions AzureCredentialOptions { get; } = new();

internal Argument<string?> FileArgument { get; } = new("file(s)", Resources.FilesArgumentDescription);

Expand All @@ -31,19 +25,12 @@ internal AzureKeyVaultCommand(CodeCommand codeCommand, IServiceProviderFactory s
ArgumentNullException.ThrowIfNull(codeCommand, nameof(codeCommand));
ArgumentNullException.ThrowIfNull(serviceProviderFactory, nameof(serviceProviderFactory));

_codeCommand = codeCommand;

CertificateOption.IsRequired = true;
UrlOption.IsRequired = true;

ManagedIdentityOption.SetDefaultValue(false);

AddOption(UrlOption);
AddOption(TenantIdOption);
AddOption(ClientIdOption);
AddOption(ClientSecretOption);
AddOption(CertificateOption);
AddOption(ManagedIdentityOption);
AzureCredentialOptions.AddOptionsToCommand(this);

AddArgument(FileArgument);

Expand All @@ -67,41 +54,19 @@ internal AzureKeyVaultCommand(CodeCommand codeCommand, IServiceProviderFactory s
return;
}

TokenCredential? credential = AzureCredentialOptions.CreateTokenCredential(context);
if (credential is null)
{
return;
}

// Some of the options are required and that is why we can safely use
// the null-forgiving operator (!) to simplify the code.
Uri url = context.ParseResult.GetValueForOption(UrlOption)!;
string? tenantId = context.ParseResult.GetValueForOption(TenantIdOption);
string? clientId = context.ParseResult.GetValueForOption(ClientIdOption);
string? secret = context.ParseResult.GetValueForOption(ClientSecretOption);
string certificateId = context.ParseResult.GetValueForOption(CertificateOption)!;
bool useManagedIdentity = context.ParseResult.GetValueForOption(ManagedIdentityOption);

TokenCredential? credential = null;

if (useManagedIdentity)
{
credential = new DefaultAzureCredential();
}
else
{
if (string.IsNullOrEmpty(tenantId) ||
string.IsNullOrEmpty(clientId) ||
string.IsNullOrEmpty(secret))
{
context.Console.Error.WriteFormattedLine(
AzureKeyVaultResources.InvalidClientSecretCredential,
TenantIdOption,
ClientIdOption,
ClientSecretOption);
context.ExitCode = ExitCode.NoInputsFound;
return;
}

credential = new ClientSecretCredential(tenantId, clientId, secret);
}

KeyVaultServiceProvider keyVaultServiceProvider = new(credential, url, certificateId);
await _codeCommand.HandleAsync(context, serviceProviderFactory, keyVaultServiceProvider, fileArgument);
await codeCommand.HandleAsync(context, serviceProviderFactory, keyVaultServiceProvider, fileArgument);
});
}
}
Expand Down
45 changes: 0 additions & 45 deletions src/Sign.Cli/AzureKeyVaultResources.Designer.cs

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

16 changes: 0 additions & 16 deletions src/Sign.Cli/AzureKeyVaultResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -123,25 +123,9 @@
<data name="ClickOnceExtensionNotSupported" xml:space="preserve">
<value>ClickOnce signing via the legacy .clickonce ZIP workaround is no longer supported. See documentation.</value>
</data>
<data name="ClientIdOptionDescription" xml:space="preserve">
<value>Client ID to authenticate to Azure Key Vault.</value>
</data>
<data name="ClientSecretOptionDescription" xml:space="preserve">
<value>Client secret to authenticate to Azure Key Vault.</value>
</data>
<data name="CommandDescription" xml:space="preserve">
<value>Use Azure Key Vault.</value>
</data>
<data name="InvalidClientSecretCredential" xml:space="preserve">
<value>If not using a managed identity, all of these options are required: {0}, {1}, and {2}.</value>
<comment>{NumberedPlaceholder="{0}", "{1}", "{2}"} are option names and should not be localized.</comment>
</data>
<data name="ManagedIdentityOptionDescription" xml:space="preserve">
<value>Managed identity to authenticate to Azure Key Vault.</value>
</data>
<data name="TenantIdOptionDescription" xml:space="preserve">
<value>Tenant ID to authenticate to Azure Key Vault.</value>
</data>
<data name="UrlOptionDescription" xml:space="preserve">
<value>URL to an Azure Key Vault.</value>
</data>
Expand Down
6 changes: 1 addition & 5 deletions src/Sign.Cli/CertificateStoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ namespace Sign.Cli
{
internal sealed class CertificateStoreCommand : Command
{
private readonly CodeCommand _codeCommand;

internal Option<string?> CertificateFingerprintOption { get; } = new(["-cfp", "--certificate-fingerprint"], ParseCertificateFingerprint, description: CertificateStoreResources.CertificateFingerprintOptionDescription);
internal Option<string?> CertificateFileOption { get; } = new(["-cf", "--certificate-file"], CertificateStoreResources.CertificateFileOptionDescription);
internal Option<string?> CertificatePasswordOption { get; } = new(["-p", "--password"], CertificateStoreResources.CertificatePasswordOptionDescription);
Expand All @@ -32,8 +30,6 @@ internal CertificateStoreCommand(CodeCommand codeCommand, IServiceProviderFactor
ArgumentNullException.ThrowIfNull(codeCommand, nameof(codeCommand));
ArgumentNullException.ThrowIfNull(serviceProviderFactory, nameof(serviceProviderFactory));

_codeCommand = codeCommand;

CertificateFingerprintOption.IsRequired = true;

AddOption(CertificateFingerprintOption);
Expand Down Expand Up @@ -111,7 +107,7 @@ internal CertificateStoreCommand(CodeCommand codeCommand, IServiceProviderFactor
certificatePassword,
useMachineKeyContainer);

await _codeCommand.HandleAsync(context, serviceProviderFactory, certificateStoreServiceProvider, fileArgument);
await codeCommand.HandleAsync(context, serviceProviderFactory, certificateStoreServiceProvider, fileArgument);
});
}

Expand Down
45 changes: 45 additions & 0 deletions src/Sign.Cli/Resources.Designer.cs

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

Loading