diff --git a/.github/workflows/pipeline-release.yml b/.github/workflows/pipeline-release.yml index cd3afea..3a7b867 100644 --- a/.github/workflows/pipeline-release.yml +++ b/.github/workflows/pipeline-release.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download artifacts id: download diff --git a/.github/workflows/shared-build.yml b/.github/workflows/shared-build.yml index dda9afc..e169e79 100644 --- a/.github/workflows/shared-build.yml +++ b/.github/workflows/shared-build.yml @@ -60,13 +60,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 # Setup .NET SDK - name: Set up .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '10.x' # Use the latest .NET 10 SDK @@ -137,19 +137,21 @@ jobs: if: ${{ inputs.store-artifacts }} run: | dotnet publish ${{ env.AzureCertToolsPath }}/AzureCreateRootCert/AzureCreateRootCert.csproj --configuration ${{ inputs.configuration }} --no-restore -p:PublishProfile=FolderProfile + dotnet pack ${{ env.AzureCertToolsPath }}/AzureCreateRootCert/AzureCreateRootCert.csproj --configuration ${{ inputs.configuration }} --no-build --no-restore shell: pwsh - name: 'Build: Publish AzureCertTools/AzureCreateIntermediateCert' if: ${{ inputs.store-artifacts }} run: | - dotnet publish ${{ env.AzureCertToolsPath }}/AzureCreateRootCert/AzureCreateRootCert.csproj --configuration ${{ inputs.configuration }} --no-restore -p:PublishProfile=FolderProfile + dotnet publish ${{ env.AzureCertToolsPath }}/AzureCreateIntermediateCert/AzureCreateIntermediateCert.csproj --configuration ${{ inputs.configuration }} --no-restore -p:PublishProfile=FolderProfile + dotnet pack ${{ env.AzureCertToolsPath }}/AzureCreateIntermediateCert/AzureCreateIntermediateCert.csproj --configuration ${{ inputs.configuration }} --no-build --no-restore shell: pwsh - name: 'Build: Publish AzureCertTools/AzureCreateSigningCert' if: ${{ inputs.store-artifacts }} run: | dotnet publish ${{ env.AzureCertToolsPath }}/AzureCreateSigningCert/AzureCreateSigningCert.csproj --configuration ${{ inputs.configuration }} -p:PublishProfile=FolderProfile - dotnet pack ${{ env.AzureCertToolsPath }}/AzureCreateSigningCert/AzureCreateSigningCert.csproj --configuration ${{ inputs.configuration }} + dotnet pack ${{ env.AzureCertToolsPath }}/AzureCreateSigningCert/AzureCreateSigningCert.csproj --configuration ${{ inputs.configuration }} --no-build --no-restore shell: pwsh - name: 'Build: Publish AzureCertTools/AzureCreateSslServerCert' @@ -162,6 +164,7 @@ jobs: if: ${{ inputs.store-artifacts }} run: | dotnet publish ${{ env.AzureCertToolsPath }}/AzureDeleteCert/AzureDeleteCert.csproj --configuration ${{ inputs.configuration }} --no-restore -p:PublishProfile=FolderProfile + dotnet pack ${{ env.AzureCertToolsPath }}/AzureDeleteCert/AzureDeleteCert.csproj --configuration ${{ inputs.configuration }} --no-build --no-restore shell: pwsh # Login to Azure using a ServicePrincipal configured to authenticate against a GitHub Action @@ -188,14 +191,16 @@ jobs: - name: 'Build: Sign NuGet packages' if: ${{ inputs.store-artifacts && inputs.sign-binaries }} shell: pwsh - run: > - sign code azure-key-vault - **/*.nupkg - --base-directory ${{ env.BinaryOutputPath }} - --azure-credential-type "azure-cli" - --azure-key-vault-url "${{ secrets.AZURE_KEYVAULT_URL }}" - --azure-key-vault-certificate "${{ secrets.AZURE_KEYVAULT_CERT_NAME }}" - --timestamp-url "${{ inputs.azure-timestamp-url }}" + run: | + $packages = Get-ChildItem -Path "${{ env.BinaryOutputPath }}" -Recurse -Filter *.nupkg -File + + foreach ($package in $packages) { + sign code azure-key-vault $package.FullName ` + --azure-credential-type "azure-cli" ` + --azure-key-vault-url "${{ secrets.AZURE_KEYVAULT_URL }}" ` + --azure-key-vault-certificate "${{ secrets.AZURE_KEYVAULT_CERT_NAME }}" ` + --timestamp-url "${{ inputs.azure-timestamp-url }}" + } - name: 'Store Artifacts: Binaries' uses: actions/upload-artifact@v4 diff --git a/README.md b/README.md index c4c1a33..777562e 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,14 @@ Where: ### AzureCreateRootCert ``` -AzureCreateRootCert --Subject --CertificateName --ExpireMonth --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +AzureCreateRootCert --Subject --CertificateName --ExpireMonths [--PathLengthConstraint ] --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] ``` Where: * Subject: The subject of the certificate in form "CN=\". * CertificateName: The name of the certificate in Azure Key Vault. * KeyVaultUri: The URI of the Azure Key Vault to store the certificate (like https://some-name.vault.azure.net/). +* PathLengthConstraint: If specified, the generated CA certificate will have a path length constraint extension with the provided length. This limits the maximum number of intermediate CA certificates that can be created under this root CA certificate. If not specified, no path length constraint will be set. * TenantId: The Entra ID tenant ID. * ClientId: The client ID of the service principal used to access the Key Vault. * ClientSecret: The client secret of the service principal used to access the Key Vault. @@ -66,18 +67,52 @@ Where: * Interactive: If set, the tool will use interactive login to Entra ID to access the Key Vault. * ExpiryMonths: The number of months the certificate is valid, default is 240. +The tool will create a the certificate in the supplied Azure Key Vault under the name. The certificate will be created using: +* Private key marked as non exportable +* Cipher Mode: RSA, 4096 Bit +* Signing: SHA384 + +Required permissions on Azure KeyVault: +- Sign with Key (Microsoft.KeyVault/vaults/keys/sign/action) +- Read Certificate Properties (Microsoft.KeyVault/vaults/certificates/read) +- Create Certificate (Microsoft.KeyVault/vaults/certificates/create/action) + +### AzureCreateIntermediateCert +``` +AzureCreateIntermediateCert --Subject --CertificateName --ExpireMonths [--PathLengthConstraint ]--KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +``` + +Where: +* Subject: The subject of the certificate in form "CN=\". +* CertificateName: The name of the certificate in Azure Key Vault. +* SignerCertificateName: The name of the CA certificate in Azure Key Vault used for signing the leaf certificate. +* KeyVaultUri: The URI of the Azure Key Vault to store the certificate (like https://some-name.vault.azure.net/). +* PathLengthConstraint: If specified, the generated CA certificate will have a path length constraint extension with the provided length. This limits the maximum number of intermediate CA certificates that can be created under this root CA certificate. If not specified, no path length constraint will be set. +* TenantId: The Entra ID tenant ID. +* ClientId: The client ID of the service principal used to access the Key Vault. +* ClientSecret: The client secret of the service principal used to access the Key Vault. +* WorkloadIdentity: If set, the tool will use an Entra ID Managed Identity [Workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation) to access the Key Vault. Use this option when running the tool in an Azure Pipeline or a GitHub Action with workload identity federation configured. +* Interactive: If set, the tool will use interactive login to Entra ID to access the Key Vault. +* ExpiryMonths: The number of months the certificate is valid, default is 240. + +The tool will create the certificate in the supplied Azure Key Vault under the name, signed by the certificate. The certificate will be created using: +* Private key marked as non exportable +* Cipher Mode: RSA, 4096 Bit +* Signing: SHA384 + Required permissions on Azure KeyVault: - Sign with Key (Microsoft.KeyVault/vaults/keys/sign/action) - Read Certificate Properties (Microsoft.KeyVault/vaults/certificates/read) - Create Certificate (Microsoft.KeyVault/vaults/certificates/create/action) + ### AzureCreateSigningCert ``` AzureCreateSigningCert --Subject --CertificateName --SignerCertificateName --ExpireMonth --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] ``` or ``` -AzureCreateSigningCert --Subject --FileName --SignerCertificateName --ExpireMonth --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +AzureCreateSigningCert --Subject --FileName --SignerCertificateName --ExpireMonths --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] ``` Where: @@ -85,7 +120,7 @@ Where: * CertificateName: The name of the certificate in Azure Key Vault. * FileName: Absolute path to PFX file holding the certificate (:\\.pfx) * Password: The password to protect the private key contained in the PFX file, required with FileName option. -* SignerCertificateName: The name of the root CA certificate in Azure Key Vault used for signing the leaf certificate. +* SignerCertificateName: The name of the CA certificate in Azure Key Vault used for signing the leaf certificate. * KeyVaultUri: The URI of the Azure Key Vault to store the certificate (like https://some-name.vault.azure.net/). * TenantId: The Entra ID tenant ID. * ClientId: The client ID of the service principal used to access the Key Vault. @@ -93,7 +128,13 @@ Where: * WorkloadIdentity: If set, the tool will a Entra ID Managed Identity [Workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation) to access the Key Vault. Use this option when running the tool in an Azure Pipeline or an GitHub Action with workload identity federation configured. * Interactive: If set, the tool will use interactive login to Entra ID to access the Key Vault. * ExpiryMonths: The number of months the certificate is valid, default is 1. - + +The tool will create the certificate in the supplied Azure Key Vault under the name, signed by the certificate. The certificate will be created using: +* Private key marked as non exportable +* Cipher Mode: RSA, 4096 Bit +* Signing: SHA384 + + Required permissions on Azure KeyVault: - Sign with Key (Microsoft.KeyVault/vaults/keys/sign/action) - Read Certificate Properties (Microsoft.KeyVault/vaults/certificates/read) diff --git a/eng/actions/git-version/action.yml b/eng/actions/git-version/action.yml index 3375330..680c47d 100644 --- a/eng/actions/git-version/action.yml +++ b/eng/actions/git-version/action.yml @@ -30,11 +30,11 @@ runs: steps: - id: gitversion-install - uses: gittools/actions/gitversion/setup@v4.0.1 + uses: gittools/actions/gitversion/setup@v4.5 with: versionSpec: '6.x' - id: gitversion-execute - uses: gittools/actions/gitversion/execute@v4.0.1 + uses: gittools/actions/gitversion/execute@v4.5 with: configFilePath: GitVersion.yml \ No newline at end of file diff --git a/src/AzureCertTools/AzureCreateIntermediateCert/AzureCreateIntermediateCert.csproj b/src/AzureCertTools/AzureCreateIntermediateCert/AzureCreateIntermediateCert.csproj index 5515b0c..0c9c463 100644 --- a/src/AzureCertTools/AzureCreateIntermediateCert/AzureCreateIntermediateCert.csproj +++ b/src/AzureCertTools/AzureCreateIntermediateCert/AzureCreateIntermediateCert.csproj @@ -4,19 +4,34 @@ CertTools.AzureCreateIntermediateCert Intermediate CA Certificate Creation Tool for Azure KeyVault $(Title) + A console application to create a x.509 intermediate CA certificate to issue code signing certificates. +The tool will create the certificate in the designated Azure Key Vault. AzureCreateIntermediateCert.ico Exe win-x64 10.0.26100.0 - net10.0-windows10.0.26100.0 true partial + PackageLogo.png + readme.md + MK.CertTools.AzureCreateIntermediateCert + + + \ + True + + + \ + True + + + diff --git a/src/AzureCertTools/AzureCreateIntermediateCert/CertificateWorker.cs b/src/AzureCertTools/AzureCreateIntermediateCert/CertificateWorker.cs index f951853..83ff4a8 100644 --- a/src/AzureCertTools/AzureCreateIntermediateCert/CertificateWorker.cs +++ b/src/AzureCertTools/AzureCreateIntermediateCert/CertificateWorker.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // // Copyright (c) Michael Koster. All rights reserved. // Licensed under the MIT License. @@ -23,10 +23,12 @@ internal static class CertificateWorker /// /// The Azure Key Vault certificate name. /// The subject name for the certificate. + /// The Azure Key Vault certificate name of the signer certificate. /// The number of month until the certificate expires. + /// The path length constraint for the intermediate CA certificate, null if no constraint should be set. /// The URI to the Azure Key Vault. /// The Azure Key Vault token credential. - public static async Task CreateIntermediateCertAsync(string certificateName, string subjectNameValue, string signerCertificateName, int expireMonths, Uri vaultUri, TokenCredential tokenCredential) + public static async Task CreateIntermediateCertAsync(string certificateName, string subjectNameValue, string signerCertificateName, int expireMonths, int? pathLengthConstraint, Uri vaultUri, TokenCredential tokenCredential) { var client = new CertificateClient(vaultUri, tokenCredential); @@ -34,7 +36,7 @@ public static async Task CreateIntermediateCertAsync(string certificateN (var signerName, var signerSignaturGenerator) = await CertificateWorkerCore.KeyVaultGetSignerCertificateAsync(signerCertificateName, client, tokenCredential); // create a CSR - var csr = await KeyVaultCreateCertificateRequestAsync(certificateName, subjectNameValue, client, expireMonths); + var csr = await KeyVaultCreateCertificateRequestAsync(certificateName, subjectNameValue, client, expireMonths, pathLengthConstraint); // Sign the CSR var cert = CertificateWorkerCore.SignCertificateRequest(csr, signerName, signerSignaturGenerator, expireMonths); @@ -45,7 +47,7 @@ public static async Task CreateIntermediateCertAsync(string certificateN return certificateName; } - private static async Task KeyVaultCreateCertificateRequestAsync(string certificateName, string subjectNameValue, CertificateClient client, int expireMonth) + private static async Task KeyVaultCreateCertificateRequestAsync(string certificateName, string subjectNameValue, CertificateClient client, int expireMonth, int? pathLengthConstraint) { var certificatePolicy = new CertificatePolicy(WellKnownIssuerNames.Unknown, subjectNameValue) { @@ -69,7 +71,7 @@ private static async Task KeyVaultCreateCertificateRequestAs signerHashAlgorithm: HashAlgorithmName.SHA384, signerSignaturePadding: RSASignaturePadding.Pkcs1); // Stage 5: Add required extensions for a CA certificate - certSigningRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, true, 0, true)); + certSigningRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, pathLengthConstraint.HasValue, pathLengthConstraint ?? 0, true)); certSigningRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.DigitalSignature, false)); certSigningRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certSigningRequest.PublicKey, false)); certSigningRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension([ diff --git a/src/AzureCertTools/AzureCreateIntermediateCert/Options.cs b/src/AzureCertTools/AzureCreateIntermediateCert/Options.cs index a109efb..e5d8ef1 100644 --- a/src/AzureCertTools/AzureCreateIntermediateCert/Options.cs +++ b/src/AzureCertTools/AzureCreateIntermediateCert/Options.cs @@ -26,4 +26,10 @@ internal class Options : OptionsCreateBase /// [Option("ExpireMonths", Required = false, HelpText = "The number of months until the certificate expires, default if not specified is 240 months.")] public int ExpireMonths { get; set; } = 240; + + /// + /// Gets or sets the path length constraint for the certificate. This is only relevant if the certificate is a CA certificate. If not specified, there will be no path length constraint. + /// + [Option("PathLengthConstraint", Required = false, HelpText = "The path length constraint for the certificate, default not present")] + public int? PathLengthConstraint { get; set; } = null; } diff --git a/src/AzureCertTools/AzureCreateIntermediateCert/Program.cs b/src/AzureCertTools/AzureCreateIntermediateCert/Program.cs index a16934a..0b6aa38 100644 --- a/src/AzureCertTools/AzureCreateIntermediateCert/Program.cs +++ b/src/AzureCertTools/AzureCreateIntermediateCert/Program.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // // Copyright (c) Michael Koster. All rights reserved. // Licensed under the MIT License. @@ -48,7 +48,7 @@ static void Main(string[] args) Uri keyVaultUri = new(options.KeyVaultUri); - var cert = CertificateWorker.CreateIntermediateCertAsync(options.CertificateName, options.Subject, options.SignerCertificateName, options.ExpireMonths, keyVaultUri, credentials).Result; + var cert = CertificateWorker.CreateIntermediateCertAsync(options.CertificateName, options.Subject, options.SignerCertificateName, options.ExpireMonths, options.PathLengthConstraint, keyVaultUri, credentials).Result; Console.WriteLine($"Certificate created: name={cert}, Key Vault={keyVaultUri}"); } diff --git a/src/AzureCertTools/AzureCreateIntermediateCert/Properties/PublishProfiles/FolderProfile.pubxml b/src/AzureCertTools/AzureCreateIntermediateCert/Properties/PublishProfiles/FolderProfile.pubxml index b87c49b..1ca80e0 100644 --- a/src/AzureCertTools/AzureCreateIntermediateCert/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/AzureCertTools/AzureCreateIntermediateCert/Properties/PublishProfiles/FolderProfile.pubxml @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. ..\..\..\bin FileSystem <_TargetId>Folder - net10.0-windows10.0.26100.0 + net10.0 win-x64 true true diff --git a/src/AzureCertTools/AzureCreateIntermediateCert/readme.md b/src/AzureCertTools/AzureCreateIntermediateCert/readme.md new file mode 100644 index 0000000..63988d0 --- /dev/null +++ b/src/AzureCertTools/AzureCreateIntermediateCert/readme.md @@ -0,0 +1,54 @@ +# Usage +``` +AzureCreateIntermediateCert --Subject --CertificateName --SignerCertificateName --ExpireMonths [--PathLengthConstraint ] --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +``` + +Where: +* Subject: The subject of the certificate in form "CN=\". +* CertificateName: The name of the certificate in Azure Key Vault. +* SignerCertificateName: The name of the CA certificate in Azure Key Vault used for signing the leaf certificate. +* KeyVaultUri: The URI of the Azure Key Vault to store the certificate (like https://some-name.vault.azure.net/). +* PathLengthConstraint: If specified, the generated CA certificate will have a path length constraint extension with the provided length. This limits the maximum number of intermediate CA certificates that can be created under this root CA certificate. If not specified, no path length constraint will be set. +* TenantId: The Entra ID tenant ID. +* ClientId: The client ID of the service principal used to access the Key Vault. +* ClientSecret: The client secret of the service principal used to access the Key Vault. +* WorkloadIdentity: If set, the tool will use an Entra ID Managed Identity [Workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation) to access the Key Vault. Use this option when running the tool in an Azure Pipeline or a GitHub Action with workload identity federation configured. +* Interactive: If set, the tool will use interactive login to Entra ID to access the Key Vault. +* ExpiryMonths: The number of months the certificate is valid, default is 240. + +The tool will create the certificate in the supplied Azure Key Vault under the name, signed by the certificate. The certificate will be created using: +* Private key marked as non exportable +* Cipher Mode: RSA, 4096 Bit +* Signing: SHA384 + +Required permissions on Azure KeyVault: +- Sign with Key (Microsoft.KeyVault/vaults/keys/sign/action) +- Read Certificate Properties (Microsoft.KeyVault/vaults/certificates/read) +- Create Certificate (Microsoft.KeyVault/vaults/certificates/create/action) + +The WorkloadIdentity parameter relies on the [Azure Identity SDK for .NET](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.workloadidentitycredential?view=azure-dotnet) and requires the following environment variables to be set: +- AZURE_CLIENT_ID: The client ID of the Entra ID application representing the workload identity. +- AZURE_TENANT_ID: The tenant ID of the Entra ID tenant. +- AZURE_FEDERATED_TOKEN_FILE: The path to the file containing the OIDC token issued by the workload identity provider. + +The following example shows how to use the tools in an Azure Pipeline. +```yaml +steps: +- task: AzureCLI@2 + inputs: + azureSubscription: 'My-WIF-Service-Connection' # Must be WIF-enabled + addSpnToEnvironment: true + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + # write the OIDC JWT into a temp file + $tokenPath = "$(Agent.TempDirectory)\federated-token.jwt" + Set-Content -Path $tokenPath -Value $env:idToken + + # export the values the SDK needs + $env:AZURE_CLIENT_ID = $env:servicePrincipalId + $env:AZURE_TENANT_ID = $env:tenantId + $env:AZURE_FEDERATED_TOKEN_FILE = $tokenPath + + .\AzureCreateIntermediateCert --CertificateName "MySigningCA" --KeyVaultUri "https://my-key-vault.vault.azure.net/" --WorkloadIdentity +``` diff --git a/src/AzureCertTools/AzureCreateRootCert/AzureCreateRootCert.csproj b/src/AzureCertTools/AzureCreateRootCert/AzureCreateRootCert.csproj index d2d9842..983ba17 100644 --- a/src/AzureCertTools/AzureCreateRootCert/AzureCreateRootCert.csproj +++ b/src/AzureCertTools/AzureCreateRootCert/AzureCreateRootCert.csproj @@ -4,18 +4,34 @@ CertTools.AzureCreateRootCert Self Signed Root CA Certificate Creation Tool for Azure KeyVault $(Title) + A console application to create a x.509 root CA certificate to issue other CA or signing certificates. +The tool will create the certificate in the designated Azure Key Vault. AzureCreateRootCert.ico Exe win-x64 10.0.26100.0 - net10.0-windows10.0.26100.0 + true partial + PackageLogo.png + MK.CertTools.AzureCreateRootCert + readme.md + + + True + \ + + + True + \ + + + diff --git a/src/AzureCertTools/AzureCreateRootCert/CertificateWorker.cs b/src/AzureCertTools/AzureCreateRootCert/CertificateWorker.cs index ba1f74e..5973b17 100644 --- a/src/AzureCertTools/AzureCreateRootCert/CertificateWorker.cs +++ b/src/AzureCertTools/AzureCreateRootCert/CertificateWorker.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // // Copyright (c) Michael Koster. All rights reserved. // Licensed under the MIT License. @@ -24,9 +24,10 @@ internal static class CertificateWorker /// The Azure Key Vault certificate name. /// The subject name for the certificate. /// The number of month until the certificate expires. + /// The path length constraint for the certificate, if null, there will be no path length constraint. /// The URI to the Azure Key Vault. /// The Azure Key Vault token credential. - public static async Task CreateRootCertAsync(string certificateName, string subjectNameValue, int expireMonth, Uri vaultUri, TokenCredential tokenCredential) + public static async Task CreateRootCertAsync(string certificateName, string subjectNameValue, int expireMonth, int? pathLengthConstraint, Uri vaultUri, TokenCredential tokenCredential) { var client = new CertificateClient(vaultUri, tokenCredential); @@ -35,7 +36,7 @@ public static async Task CreateRootCertAsync(string certificateName, str var signerName = new X500DistinguishedName(subjectNameValue); // create a CSR and sign it with the temporary cert - var csr = await KeyVaultCreateRootCertificateRequestAsync(certificateName, subjectNameValue, client, expireMonth); + var csr = await KeyVaultCreateRootCertificateRequestAsync(certificateName, subjectNameValue, client, expireMonth, pathLengthConstraint); using var certificate = CertificateWorkerCore.SignCertificateRequest(csr, signerName, signatureGenerator, expireMonth); // Merge with the pending certificate operation @@ -44,7 +45,7 @@ public static async Task CreateRootCertAsync(string certificateName, str return certificateName; } - private static async Task KeyVaultCreateRootCertificateRequestAsync(string certificateName, string subjectNameValue, CertificateClient client, int expireMonth) + private static async Task KeyVaultCreateRootCertificateRequestAsync(string certificateName, string subjectNameValue, CertificateClient client, int expireMonth, int? pathLengthConstraint) { var certificatePolicy = new CertificatePolicy(WellKnownIssuerNames.Unknown, subjectNameValue) { @@ -65,10 +66,10 @@ private static async Task KeyVaultCreateRootCertificateReque // Stage 4: Get the .NET CSR object var certSigningRequest = CertificateRequest.LoadSigningRequest(pkcs10: certOperationCertSigningRequest, - signerHashAlgorithm: HashAlgorithmName.SHA384, signerSignaturePadding: RSASignaturePadding.Pkcs1); + signerHashAlgorithm: HashAlgorithmName.SHA384, signerSignaturePadding: RSASignaturePadding.Pkcs1); // Stage 5: Add the required extensions for a root CA - certSigningRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, true, 0, true)); + certSigningRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, pathLengthConstraint.HasValue, pathLengthConstraint ?? 0, true)); certSigningRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.DigitalSignature, false)); certSigningRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certSigningRequest.PublicKey, false)); certSigningRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension([ diff --git a/src/AzureCertTools/AzureCreateRootCert/Options.cs b/src/AzureCertTools/AzureCreateRootCert/Options.cs index c02a3e8..5fa1637 100644 --- a/src/AzureCertTools/AzureCreateRootCert/Options.cs +++ b/src/AzureCertTools/AzureCreateRootCert/Options.cs @@ -20,4 +20,10 @@ internal class Options : OptionsCreateBase /// [Option("ExpireMonths", Required = false, HelpText = "The number of months until the certificate expires, default if not specified is 240 months.")] public int ExpireMonths { get; set; } = 240; + + /// + /// Gets or sets the path length constraint for the certificate. This is only relevant if the certificate is a CA certificate. If not specified, there will be no path length constraint. + /// + [Option("PathLengthConstraint", Required = false, HelpText = "The path length constraint for the certificate, default not present")] + public int? PathLengthConstraint { get; set; } = null; } diff --git a/src/AzureCertTools/AzureCreateRootCert/Program.cs b/src/AzureCertTools/AzureCreateRootCert/Program.cs index 4f05968..8ea01d4 100644 --- a/src/AzureCertTools/AzureCreateRootCert/Program.cs +++ b/src/AzureCertTools/AzureCreateRootCert/Program.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // // Copyright (c) Michael Koster. All rights reserved. // Licensed under the MIT License. @@ -48,7 +48,7 @@ static async Task Main(string[] args) Uri keyVaultUri = new(options.KeyVaultUri); - var cert = await CertificateWorker.CreateRootCertAsync(options.CertificateName, options.Subject, options.ExpireMonths, keyVaultUri, credentials); + var cert = await CertificateWorker.CreateRootCertAsync(options.CertificateName, options.Subject, options.ExpireMonths, options.PathLengthConstraint, keyVaultUri, credentials); Console.WriteLine($"Certificate created: name={cert}, Key Vault={keyVaultUri}"); } diff --git a/src/AzureCertTools/AzureCreateRootCert/Properties/PublishProfiles/FolderProfile.pubxml b/src/AzureCertTools/AzureCreateRootCert/Properties/PublishProfiles/FolderProfile.pubxml index b87c49b..1ca80e0 100644 --- a/src/AzureCertTools/AzureCreateRootCert/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/AzureCertTools/AzureCreateRootCert/Properties/PublishProfiles/FolderProfile.pubxml @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. ..\..\..\bin FileSystem <_TargetId>Folder - net10.0-windows10.0.26100.0 + net10.0 win-x64 true true diff --git a/src/AzureCertTools/AzureCreateRootCert/readme.md b/src/AzureCertTools/AzureCreateRootCert/readme.md new file mode 100644 index 0000000..a9943b6 --- /dev/null +++ b/src/AzureCertTools/AzureCreateRootCert/readme.md @@ -0,0 +1,52 @@ +# Usage +``` +AzureCreateRootCert --Subject --CertificateName --ExpireMonths [--PathLengthConstraint ] --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +``` + +Where: +* CertificateName: The name of the certificate in Azure Key Vault. +* KeyVaultUri: The URI of the Azure Key Vault to store the certificate (like https://some-name.vault.azure.net/). +* PathLengthConstraint: If specified, the generated CA certificate will have a path length constraint extension with the provided length. This limits the maximum number of intermediate CA certificates that can be created under this root CA certificate. If not specified, no path length constraint will be set. +* TenantId: The Entra ID tenant ID. +* ClientId: The client ID of the service principal used to access the Key Vault. +* ClientSecret: The client secret of the service principal used to access the Key Vault. +* WorkloadIdentity: If set, the tool will use an Entra ID Managed Identity [Workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation) to access the Key Vault. Use this option when running the tool in an Azure Pipeline or a GitHub Action with workload identity federation configured. +* Interactive: If set, the tool will use interactive login to Entra ID to access the Key Vault. +* ExpiryMonths: The number of months the certificate is valid, default is 240. + +The tool will create a the certificate in the supplied Azure Key Vault under the name. The certificate will be created using: +* Private key marked as non exportable +* Cipher Mode: RSA, 4096 Bit +* Signing: SHA384 + +Required permissions on Azure KeyVault: +- Sign with Key (Microsoft.KeyVault/vaults/keys/sign/action) +- Read Certificate Properties (Microsoft.KeyVault/vaults/certificates/read) +- Create Certificate (Microsoft.KeyVault/vaults/certificates/create/action) + +The WorkloadIdentity parameter relies on the [Azure Identity SDK for .NET](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.workloadidentitycredential?view=azure-dotnet) and requires the following environment variables to be set: +- AZURE_CLIENT_ID: The client ID of the Entra ID application representing the workload identity. +- AZURE_TENANT_ID: The tenant ID of the Entra ID tenant. +- AZURE_FEDERATED_TOKEN_FILE: The path to the file containing the OIDC token issued by the workload identity provider. + +The following example shows how to use the tools in an Azure Pipeline. +```yaml +steps: +- task: AzureCLI@2 + inputs: + azureSubscription: 'My-WIF-Service-Connection' # Must be WIF-enabled + addSpnToEnvironment: true + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + # write the OIDC JWT into a temp file + $tokenPath = "$(Agent.TempDirectory)\federated-token.jwt" + Set-Content -Path $tokenPath -Value $env:idToken + + # export the values the SDK needs + $env:AZURE_CLIENT_ID = $env:servicePrincipalId + $env:AZURE_TENANT_ID = $env:tenantId + $env:AZURE_FEDERATED_TOKEN_FILE = $tokenPath + + .\AzureCreateRootCert --Subject "CN=My Signing Root CA" --CertificateName "MySigningCA" --ExpireMonths 240 --KeyVaultUri "https://my-key-vault.vault.azure.net/" --WorkloadIdentity +``` diff --git a/src/AzureCertTools/AzureCreateSigningCert/AzureCreateSigningCert.csproj b/src/AzureCertTools/AzureCreateSigningCert/AzureCreateSigningCert.csproj index d0adef4..802e321 100644 --- a/src/AzureCertTools/AzureCreateSigningCert/AzureCreateSigningCert.csproj +++ b/src/AzureCertTools/AzureCreateSigningCert/AzureCreateSigningCert.csproj @@ -4,23 +4,17 @@ CertTools.AzureCreateSigningCert Code Signing Certificate Creation Tool for Azure Key Vault $(Title) - - A console application to create a x.509 code signing certificate to sign binary files (EXE, DLL) and installation packages (MSIX, MSI) - The tool will create the certificate in the designated Azure Key Vault. - + A console application to create a x.509 code signing certificate to sign binary files (EXE, DLL) and installation packages (MSIX, MSI) +The tool will create the certificate in the designated Azure Key Vault. AzureCreateSigningCert.ico Exe win-x64 10.0.26100.0 true partial - True - MIT MK.CertTools.AzureCreateSigningCert - ..\..\..\bin readme.md - verified_128.png - https://github.com/MichaelKoster70/CryptoTools + PackageLogo.png @@ -28,13 +22,13 @@ - - \ + True + \ - \ True + \ diff --git a/src/AzureCertTools/AzureCreateSigningCert/readme.md b/src/AzureCertTools/AzureCreateSigningCert/readme.md index 0bbf048..9129ebb 100644 --- a/src/AzureCertTools/AzureCreateSigningCert/readme.md +++ b/src/AzureCertTools/AzureCreateSigningCert/readme.md @@ -2,13 +2,17 @@ ``` AzureCreateSigningCert --Subject --CertificateName --SignerCertificateName --ExpireMonth --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] ``` +or +``` +AzureCreateSigningCert --Subject --FileName --SignerCertificateName --ExpireMonths --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +``` Where: * Subject: The subject of the certificate in form "CN=\". * CertificateName: The name of the certificate in Azure Key Vault. * FileName: Absolute path to PFX file holding the certificate (:\\.pfx) -* Password: The password protecting the PFX file. -* SignerCertificateName: The name of the root CA certificate in Azure Key Vault used for signing the leaf certificate. +* Password: The password to protect the private key contained in the PFX file, required with FileName option. +* SignerCertificateName: The name of the CA certificate in Azure Key Vault used for signing the leaf certificate. * KeyVaultUri: The URI of the Azure Key Vault to store the certificate (like https://some-name.vault.azure.net/). * TenantId: The Entra ID tenant ID. * ClientId: The client ID of the service principal used to access the Key Vault. @@ -16,7 +20,12 @@ Where: * WorkloadIdentity: If set, the tool will a Entra ID Managed Identity [Workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation) to access the Key Vault. Use this option when running the tool in an Azure Pipeline or an GitHub Action with workload identity federation configured. * Interactive: If set, the tool will use interactive login to Entra ID to access the Key Vault. * ExpiryMonths: The number of months the certificate is valid, default is 1. - + +The tool will create the certificate in the supplied Azure Key Vault under the name, signed by the certificate. The certificate will be created using: +* Private key marked as non exportable +* Cipher Mode: RSA, 4096 Bit +* Signing: SHA384 + Required permissions on Azure KeyVault: - Sign with Key (Microsoft.KeyVault/vaults/keys/sign/action) - Read Certificate Properties (Microsoft.KeyVault/vaults/certificates/read) diff --git a/src/AzureCertTools/AzureCreateSslServerCert/AzureCreateSslServerCert.csproj b/src/AzureCertTools/AzureCreateSslServerCert/AzureCreateSslServerCert.csproj index acc4f3c..5db5419 100644 --- a/src/AzureCertTools/AzureCreateSslServerCert/AzureCreateSslServerCert.csproj +++ b/src/AzureCertTools/AzureCreateSslServerCert/AzureCreateSslServerCert.csproj @@ -8,13 +8,20 @@ Exe win-x64 10.0.26100.0 - net10.0-windows10.0.26100.0 true partial - + PackageLogo.png + + + + + + True + \ + diff --git a/src/AzureCertTools/AzureCreateSslServerCert/CertificateWorker.cs b/src/AzureCertTools/AzureCreateSslServerCert/CertificateWorker.cs index 147dc5c..ee39a10 100644 --- a/src/AzureCertTools/AzureCreateSslServerCert/CertificateWorker.cs +++ b/src/AzureCertTools/AzureCreateSslServerCert/CertificateWorker.cs @@ -1,19 +1,14 @@ -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // // Copyright (c) Michael Koster. All rights reserved. // Licensed under the MIT License. // // ---------------------------------------------------------------------------- -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Net; -using System.Runtime.ConstrainedExecution; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; using Azure.Core; using Azure.Security.KeyVault.Certificates; using CertTools.AzureCertCore; @@ -111,6 +106,7 @@ private static async Task KeyVaultCreateCertificateRequestAs return certSigningRequest; } + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Tools are Windows only")] private static async Task LocalCreateCertificateRequestAsync(string fullQualifiedDomainName) { var distinguishedName = "CN=" + fullQualifiedDomainName; diff --git a/src/AzureCertTools/AzureCreateSslServerCert/Properties/PublishProfiles/FolderProfile.pubxml b/src/AzureCertTools/AzureCreateSslServerCert/Properties/PublishProfiles/FolderProfile.pubxml index b87c49b..1ca80e0 100644 --- a/src/AzureCertTools/AzureCreateSslServerCert/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/AzureCertTools/AzureCreateSslServerCert/Properties/PublishProfiles/FolderProfile.pubxml @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. ..\..\..\bin FileSystem <_TargetId>Folder - net10.0-windows10.0.26100.0 + net10.0 win-x64 true true diff --git a/src/AzureCertTools/AzureDeleteCert/AzureDeleteCert.csproj b/src/AzureCertTools/AzureDeleteCert/AzureDeleteCert.csproj index 6e8d2dd..f748fd7 100644 --- a/src/AzureCertTools/AzureDeleteCert/AzureDeleteCert.csproj +++ b/src/AzureCertTools/AzureDeleteCert/AzureDeleteCert.csproj @@ -5,21 +5,15 @@ Certificate Deletion Tool for Azure Key Vault $(Title) AzureDeleteCert.ico - - A console application to delete a x.509 certificate from the designated Azure Key Vault. - + A console application to delete a x.509 certificate from the designated Azure Key Vault. Exe win-x64 10.0.26100.0 true partial - True - MIT MK.CertTools.AzureDeleteCert - ..\..\..\bin readme.md - verified_128.png - https://github.com/MichaelKoster70/CryptoTools + PackageLogo.png @@ -27,13 +21,13 @@ - - \ + True + \ - \ True + \ diff --git a/src/AzureCertTools/AzureDeleteCert/readme.md b/src/AzureCertTools/AzureDeleteCert/readme.md index b7182f0..fe32c37 100644 --- a/src/AzureCertTools/AzureDeleteCert/readme.md +++ b/src/AzureCertTools/AzureDeleteCert/readme.md @@ -1,6 +1,6 @@ # Usage ``` -AzureDeleteSigningCert --CertificateName --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] +AzureDeleteCert --CertificateName --KeyVaultUri --TenantId --ClientId [--ClientSecret | --Interactive | --WorkloadIdentity] ``` Where: diff --git a/src/AzureCertTools/Directory.Build.props b/src/AzureCertTools/Directory.Build.props index 8559381..4669025 100644 --- a/src/AzureCertTools/Directory.Build.props +++ b/src/AzureCertTools/Directory.Build.props @@ -13,9 +13,13 @@ Michael Koster - (c) 2025 Michael Koster + (c) 2026 Michael Koster Michael Koster Crypto Tools + True + MIT + ..\..\..\bin + https://github.com/MichaelKoster70/CryptoTools $(GITHUB_SHA) diff --git a/src/AzureCertTools/Directory.Packages.props b/src/AzureCertTools/Directory.Packages.props index c6552d3..1b84c3b 100644 --- a/src/AzureCertTools/Directory.Packages.props +++ b/src/AzureCertTools/Directory.Packages.props @@ -1,16 +1,13 @@ - true - - + - + - \ No newline at end of file diff --git a/src/AzureCertTools/PackageLogo.png b/src/AzureCertTools/PackageLogo.png new file mode 100644 index 0000000..46ccffa Binary files /dev/null and b/src/AzureCertTools/PackageLogo.png differ diff --git a/src/CertTools/CertCore/CertCore.csproj b/src/CertTools/CertCore/CertCore.csproj index 40db7a5..04cf351 100644 --- a/src/CertTools/CertCore/CertCore.csproj +++ b/src/CertTools/CertCore/CertCore.csproj @@ -1,9 +1,6 @@  - net10.0 - enable - enable CertTools.CertCore diff --git a/src/CertTools/CreateRootCert/CertificateWorker.cs b/src/CertTools/CreateRootCert/CertificateWorker.cs index 450fbc4..47a945d 100644 --- a/src/CertTools/CreateRootCert/CertificateWorker.cs +++ b/src/CertTools/CreateRootCert/CertificateWorker.cs @@ -8,7 +8,6 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using CertTools.CertCore; -using Windows.Devices.PointOfService; namespace CertTools.CreateRootCert; diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md index 25c3581..c9f5b2d 100644 --- a/src/ReleaseHistory.md +++ b/src/ReleaseHistory.md @@ -3,11 +3,13 @@ New Tools and infrastructure updates. ## Features - Added tool to delete Azure Key Vault certificates. +- Made the certificate path length constraint configurable for CA certs. - Upgraded to .NET 10. - Changed NuGet packages to use a proper prefix ## Bug Fixes - None + # 1.7.0 Package as dotnet tool diff --git a/src/ReleaseNotes.md b/src/ReleaseNotes.md index 53cce10..8255e81 100644 --- a/src/ReleaseNotes.md +++ b/src/ReleaseNotes.md @@ -3,8 +3,10 @@ New Tools and infrastructure updates. ## Features - Added tool to delete Azure Key Vault certificates. +- Added NuGet packages for all Azure KeyVault tools. +- Made the certificate path length constraint configurable for CA certs. - Upgraded to .NET 10. -- Changed NuGet packages to use a proper prefix +- Changed NuGet packages to use a proper prefix. ## Bug Fixes - none \ No newline at end of file