Skip to content
Open
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
1 change: 1 addition & 0 deletions src/AzureSignTool/HRESULT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ internal static class HRESULT
public const int E_ALL_FAILED = unchecked((int)0xA0000002);

public const int TRUST_E_SUBJECT_FORM_UNKNOWN = unchecked((int)0x800B0003);
public const int ERROR_TIMEOUT = unchecked((int)0x800705B5);
}
}
47 changes: 28 additions & 19 deletions src/AzureSignTool/KeyVaultConfigurationDiscoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

namespace AzureSignTool
Expand All @@ -18,7 +19,7 @@ public KeyVaultConfigurationDiscoverer(ILogger logger)
_logger = logger;
}

public async Task<ErrorOr<AzureKeyVaultMaterializedConfiguration>> Materialize(AzureKeyVaultSignConfigurationSet configuration)
public async Task<ErrorOr<AzureKeyVaultMaterializedConfiguration>> Materialize(AzureKeyVaultSignConfigurationSet configuration, int timeoutSeconds)
{
TokenCredential credential;
if (configuration.ManagedIdentity)
Expand Down Expand Up @@ -48,30 +49,38 @@ public async Task<ErrorOr<AzureKeyVaultMaterializedConfiguration>> Materialize(A

X509Certificate2 certificate;
KeyVaultCertificate azureCertificate;
try
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
{
var certClient = new CertificateClient(configuration.AzureKeyVaultUrl, credential);

if (!string.IsNullOrWhiteSpace(configuration.AzureKeyVaultCertificateVersion))
try
{
_logger.LogTrace($"Retrieving version [{configuration.AzureKeyVaultCertificateVersion}] of certificate {configuration.AzureKeyVaultCertificateName}.");
azureCertificate = (await certClient.GetCertificateVersionAsync(configuration.AzureKeyVaultCertificateName, configuration.AzureKeyVaultCertificateVersion).ConfigureAwait(false)).Value;
var certClient = new CertificateClient(configuration.AzureKeyVaultUrl, credential);

if (!string.IsNullOrWhiteSpace(configuration.AzureKeyVaultCertificateVersion))
{
_logger.LogTrace($"Retrieving version [{configuration.AzureKeyVaultCertificateVersion}] of certificate {configuration.AzureKeyVaultCertificateName}.");
azureCertificate = (await certClient.GetCertificateVersionAsync(configuration.AzureKeyVaultCertificateName, configuration.AzureKeyVaultCertificateVersion, cts.Token).ConfigureAwait(false)).Value;
}
else
{
_logger.LogTrace($"Retrieving current version of certificate {configuration.AzureKeyVaultCertificateName}.");
azureCertificate = (await certClient.GetCertificateAsync(configuration.AzureKeyVaultCertificateName, cts.Token).ConfigureAwait(false)).Value;
}
_logger.LogTrace($"Retrieved certificate with Id {azureCertificate.Id}.");

certificate = X509CertificateLoader.LoadCertificate(azureCertificate.Cer);
}
else
catch (OperationCanceledException)
{
_logger.LogTrace($"Retrieving current version of certificate {configuration.AzureKeyVaultCertificateName}.");
azureCertificate = (await certClient.GetCertificateAsync(configuration.AzureKeyVaultCertificateName).ConfigureAwait(false)).Value;
_logger.LogError($"Timeout connecting to Azure Key Vault at {configuration.AzureKeyVaultUrl}. The operation exceeded {timeoutSeconds} seconds.");
return new TimeoutException($"The connection to Azure Key Vault timed out after {timeoutSeconds} seconds.");
}
_logger.LogTrace($"Retrieved certificate with Id {azureCertificate.Id}.");

certificate = X509CertificateLoader.LoadCertificate(azureCertificate.Cer);
}
catch (Exception e)
{
_logger.LogError($"Failed to retrieve certificate {configuration.AzureKeyVaultCertificateName} from Azure Key Vault. Please verify the name of the certificate and the permissions to the certificate. Error message: {e.Message}.");
_logger.LogTrace(e.ToString());
catch (Exception e)
{
_logger.LogError($"Failed to retrieve certificate {configuration.AzureKeyVaultCertificateName} from Azure Key Vault. Please verify the name of the certificate and the permissions to the certificate. Error message: {e.Message}.");
_logger.LogTrace(e.ToString());

return e;
return e;
}
}
var keyId = azureCertificate.KeyId;

Expand Down
13 changes: 12 additions & 1 deletion src/AzureSignTool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ internal sealed class SignCommand : Command
internal bool SkipSignedFiles { get; set; }
internal bool AppendSignature { get; set; }
internal string? AzureAuthority { get; set; }
internal int KeyVaultTimeoutSeconds { get; set; } = 30;

internal HashSet<string> AllFiles
{
Expand Down Expand Up @@ -185,6 +186,7 @@ public SignCommand() : base("sign", "Sign a file.", null)
this.Add("s|skip-signed", "Skip files that are already signed.", v => SkipSignedFiles = v is not null);
this.Add("as|append-signature", "Append the signature, has no effect with --skip-signed.", v => AppendSignature = v is not null);
this.Add("au|azure-authority=", "The Azure Authority for Azure Key Vault.", v => AzureAuthority = v);
this.Add("kvto|azure-key-vault-timeout=", "The timeout in seconds for connecting to Azure Key Vault. Default is 30 seconds.", (int v) => KeyVaultTimeoutSeconds = v);
this.Add("<>", "[files]*", Files);
Action = Run;
}
Expand Down Expand Up @@ -263,14 +265,17 @@ private async ValueTask<int> RunSign()
}
bool appendSignature = AppendSignature;
var configurationDiscoverer = new KeyVaultConfigurationDiscoverer(logger);
var materializedResult = await configurationDiscoverer.Materialize(configuration);
var materializedResult = await configurationDiscoverer.Materialize(configuration, KeyVaultTimeoutSeconds);
AzureKeyVaultMaterializedConfiguration materialized;

switch (materializedResult)
{
case ErrorOr<AzureKeyVaultMaterializedConfiguration>.Ok ok:
materialized = ok.Value;
break;
case ErrorOr<AzureKeyVaultMaterializedConfiguration>.Err err when err.Error is TimeoutException:
logger.LogError("Timeout connecting to Azure Key Vault. The Key Vault URL could not be reached within {timeout} seconds.", KeyVaultTimeoutSeconds);
return ERROR_TIMEOUT;
default:
logger.LogError("Failed to get configuration from Azure Key Vault.");
return E_INVALIDARG;
Expand Down Expand Up @@ -487,6 +492,12 @@ private bool ValidateArguments(CommandRunContext context)
valid = false;
}

if (KeyVaultTimeoutSeconds <= 0)
{
context.Error.WriteLine("'--azure-key-vault-timeout' must be a positive integer.");
valid = false;
}

if (AllFiles.Count == 0)
{
context.Error.WriteLine("At least one file must be specified to sign.");
Expand Down