Skip to content
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
98 changes: 98 additions & 0 deletions docs/artifact-signing-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Artifact Signing integration for Sign CLI

This document explains how to use the Sign CLI with a Artifact Signing account to perform code signing using the Artifact Signing provider. See `docs/signing-tool-spec.md` for higher-level background of this tool and the implementation at `src/Sign.SignatureProviders.TrustedSigning` for details.

## Overview

The Sign CLI includes a `artifact-signing` provider that invokes the Artifact Signing service to obtain certificates and perform remote sign operations. The CLI uses the Azure SDK (`Azure.Identity`) for authentication.

Key concepts for this provider:
- Endpoint: the service URL for the Artifact Signing account.
- Account name: the account within the Artifact Signing service.
- Certificate profile: the certificate profile configured in the account that will be used to sign.

For more information, see the Artifact Signing [setup documentation](https://learn.microsoft.com/azure/artifact-signing/quickstart).

## Prerequisites

- An Azure subscription and a Artifact Signing account with at least one active certificate profile.
- An identity (user, service principal, or managed identity) that has the `Artifact Signing Certificate Profile Signer` permission to perform signing.

## How the CLI authenticates

Sign CLI uses Azure.Identity's credential chain by default (DefaultAzureCredential). This means the CLI will try an authentication flow automatically (Azure CLI login, environment variables for a service principal, managed identity, etc.). You may also explicitly choose a credential type with `--azure-credential-type`.

## CLI options for Artifact Signing

The Artifact Signing subcommand is `sign code artifact-signing` and it requires the following options (short forms shown):

- `--artifact-signing-endpoint`, `-ase` : the Artifact Signing service endpoint (URL).
- `--artifact-signing-account`, `-asa` : the account name in the Artifact Signing service.
- `--artifact-signing-certificate-profile`, `-ascp` : the certificate profile name to use for signing.

The Azure authentication options are available on the same command and include `--azure-credential-type` (`-act`) and managed identity options such as `--managed-identity-client-id` (`-mici`). By default, the CLI uses DefaultAzureCredential.

## Examples

Replace placeholders with your values.

Example — sign a file using your current Azure CLI login (DefaultAzureCredential):

```powershell
# Ensure you're signed into Azure CLI
az login

# Sign a file using Artifact Signing
sign code artifact-signing `
-ase https://<your-artifact-signing-endpoint> `
-asa <your-account-name> `
-ascp <your-certificate-profile> `
C:\path\to\artifact.dll
```

Example — service principal (PowerShell session variables; prefer secrets or pipeline variables in CI):

```powershell
$env:AZURE_CLIENT_ID = 'your-client-id'
$env:AZURE_TENANT_ID = 'your-tenant-id'

sign code artifact-signing `
-ase https://<your-artifact-signing-endpoint> `
-asa <your-account-name> `
-ascp <your-certificate-profile> `
C:\path\to\artifact.dll
```

Example — managed identity (useful for Azure-hosted agents):

```powershell
# Use managed identity by selecting the credential type explicitly and, if needed, the client id
sign code artifact-signing `
-ase https://<your-artifact-signing-endpoint> `
-asa <your-account-name> `
-ascp <your-certificate-profile> `
-act managed-identity `
-mici <managed-identity-client-id> `
C:\path\to\artifact.dll
```

Notes:
- If you omit `-act`, the CLI uses DefaultAzureCredential, which already supports Azure CLI, environment variables for service principals, managed identities, and workload identity flows.
- The endpoint URL and exact account/profile names are provided by your Artifact Signing onboarding or Azure portal.

## CI/CD integration tips

- Prefer federated identity (OIDC) or managed identities for CI agents to avoid long-lived secrets. Sign CLI supports workload and managed identity credential flows.
- Store any required values (endpoint, account, certificate profile) as pipeline secrets or protected variables.

## Troubleshooting

- Authentication errors: verify the authentication method (Azure CLI login, environment variables, or managed identity) and that the identity has permission to the Artifact Signing account.
- Permission errors: ensure your principal has the necessary rights on the Artifact Signing account and certificate profile. If unsure, contact your Azure admin or the team that provisioned the Artifact Signing account.
- Endpoint/profile not found: confirm the exact endpoint URL, account name, and certificate profile name from your Artifact Signing account metadata or onboarding docs.
- See the [Artifact Signing FAQ](https://learn.microsoft.com/azure/artifact-signing/faq) for more information.

## Where to look in this repository

- Implementation of the provider: `src/Sign.SignatureProviders.ArtifactSigning` (see `ArtifactSigningService.cs`, `RSAArtifactSigning.cs` and `ArtifactSigningServiceProvider.cs`).
- CLI wiring: `src/Sign.Cli/ArtifactSigningCommand.cs` (shows required flags and how Azure credentials are constructed).
98 changes: 0 additions & 98 deletions docs/trusted-signing-integration.md

This file was deleted.

4 changes: 2 additions & 2 deletions sign.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ 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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sign.SignatureProviders.ArtifactSigning", "src\Sign.SignatureProviders.ArtifactSigning\Sign.SignatureProviders.ArtifactSigning.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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sign.SignatureProviders.ArtifactSigning.Test", "test\Sign.SignatureProviders.ArtifactSigning.Test\Sign.SignatureProviders.ArtifactSigning.Test.csproj", "{A81695AF-088A-436A-9A38-4D0B0DB2D826}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
110 changes: 110 additions & 0 deletions src/Sign.Cli/ArtifactSigningCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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 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.ArtifactSigning;

namespace Sign.Cli
{
internal sealed class ArtifactSigningCommand : Command
{
internal Option<Uri> EndpointOption { get; }
internal Option<string> AccountOption { get; }
internal Option<string> CertificateProfileOption { get; }
internal AzureCredentialOptions AzureCredentialOptions { get; } = new();

internal Argument<List<string>?> FilesArgument { get; }

internal ArtifactSigningCommand(CodeCommand codeCommand, IServiceProviderFactory serviceProviderFactory)
: base("artifact-signing", ArtifactSigningResources.CommandDescription)
{
ArgumentNullException.ThrowIfNull(codeCommand, nameof(codeCommand));
ArgumentNullException.ThrowIfNull(serviceProviderFactory, nameof(serviceProviderFactory));

EndpointOption = new Option<Uri>("--artifact-signing-endpoint", "-ase")
{
CustomParser = CodeCommand.ParseUrl,
Description = ArtifactSigningResources.EndpointOptionDescription,
Required = true
};
AccountOption = new Option<string>("--artifact-signing-account", "-asa")
{
Description = ArtifactSigningResources.AccountOptionDescription,
Required = true
};
CertificateProfileOption = new Option<string>("--artifact-signing-certificate-profile", "-ascp")
{
Description = ArtifactSigningResources.CertificateProfileOptionDescription,
Required = true
};
FilesArgument = new Argument<List<string>?>("file(s)")
{
Description = Resources.FilesArgumentDescription,
Arity = ArgumentArity.OneOrMore
};

Options.Add(EndpointOption);
Options.Add(AccountOption);
Options.Add(CertificateProfileOption);
AzureCredentialOptions.AddOptionsToCommand(this);

Arguments.Add(FilesArgument);

SetAction((ParseResult parseResult, CancellationToken cancellationToken) =>
{
List<string>? filesArgument = parseResult.GetValue(FilesArgument);

if (filesArgument is not { Count: > 0 })
{
Console.Error.WriteLine(Resources.MissingFileValue);

return Task.FromResult(ExitCode.InvalidOptions);
}

TokenCredential? credential = AzureCredentialOptions.CreateTokenCredential(parseResult);

if (credential is null)
{
return Task.FromResult(ExitCode.Failed);
}

// Some of the options are required and that is why we can safely use
// the null-forgiving operator (!) to simplify the code.
Uri endpointUrl = parseResult.GetValue(EndpointOption)!;
string accountName = parseResult.GetValue(AccountOption)!;
string certificateProfileName = parseResult.GetValue(CertificateProfileOption)!;

serviceProviderFactory.AddServices(services =>
{
services.AddAzureClients(builder =>
{
builder.AddCertificateProfileClient(endpointUrl);
builder.UseCredential(credential);
builder.ConfigureDefaults(options => options.Retry.Mode = RetryMode.Exponential);
});

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

ArtifactSigningServiceProvider trustedSigningServiceProvider = new();

return codeCommand.HandleAsync(parseResult, serviceProviderFactory, trustedSigningServiceProvider, filesArgument);
});
}
}
}
Loading