Skip to content

Trusted Publisher Checks for Azure Trusted Signing - #25824

Open
Jordan Borean (jborean93) wants to merge 5 commits into
PowerShell:masterfrom
jborean93:trusted-publisher-az-ts
Open

Jordan Borean (jborean93) wants to merge 5 commits into
PowerShell:masterfrom
jborean93:trusted-publisher-az-ts

Conversation

@jborean93

@jborean93 Jordan Borean (jborean93) commented Aug 6, 2025 •

Copy link
Copy Markdown
Collaborator

PR Summary

Add support for checking the Azure Trusted Signing publisher identifier alongside the thumbprint. This check will verify whether the unique Azure TS OID present in the EKU is in any certificate in the TrustedPublishers store. It will also check whether the OID is present in the untrusted store making it possible to distrust all certificates for that Azure identity.

PR Context

Fixes: #21550

I need to spend some time to figure out how I could potentially add tests for this. It should be theoretically possible to generate certs with the same ID or at least have some test hook that changes what ID to check for but I'll think about it.

The behaviour of the untrusted check might need to be adjusted to only check the thumbprint only. Currently if any cert with the Azure identity OID is present in the untrusted store then all certs with that same OID will be untrusted. If the PowerShell team only wants to reject specific certs then that logic will have to be adjusted.

Edit: Tests added and untrusted check moved back to only untrusting by thumbprint. It is not easy to get the root CA thumbprint for an untrusted cert because Windows treats it as rejected and won't work with the X509Chain class. Upon reflection I think this behaviour is nicer as it allows you to reject specific timeframes/certs rather than the whole identity at once. Maybe an extra location could be used to reject identities entirely if desired.

PR Checklist

Add support for checking the Azure Trusted Signing publisher identifier
alongside the thumbprint. This check will verify whether the unique
Azure TS OID present in the EKU is in any certificate in the
TrustedPublishers store. It will also check whether the OID is present
in the untrusted store making it possible to distrust all certificates
for that Azure identity.
@ianjmcm

Copy link
Copy Markdown

This looks really good.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for trusted publisher checks when using Azure Trusted Signing certificates in PowerShell's execution policy validation. The implementation allows PowerShell to trust Azure Trusted Signing certificates based on their unique publisher identifier (extracted from EKU OIDs) rather than only by certificate thumbprint.

  • Introduces Azure Trusted Signing publisher identifier checking alongside existing thumbprint validation
  • Adds comprehensive test coverage for the new trusted publisher functionality
  • Enhances security by validating certificates against both trusted and disallowed certificate stores

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
SecurityManager.cs Implements Azure Trusted Signing publisher ID extraction and validation logic in certificate trust checking
ExecutionPolicy.Tests.ps1 Adds comprehensive test suite covering various Azure Trusted Signing certificate trust scenarios

Comment thread src/System.Management.Automation/security/SecurityManager.cs Outdated
azurePublisherId == trustedIdentifier)
{
isTrusted = true;
break;

Copilot AI Aug 11, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The break statement exits the loop early when an Azure publisher ID match is found, but the thumbprint check continues through all certificates. For consistency and performance, consider adding a break after setting isTrusted = true for the thumbprint match as well.

Copilot uses AI. Check for mistakes.

@SteveL-MSFT Steve Lee (SteveL-MSFT) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is coming in late for 7.6, can you make this an Experimental Feature?

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Aug 11, 2025
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot removed the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Aug 11, 2025
@jborean93

Copy link
Copy Markdown
Collaborator Author

Steve Lee (@SteveL-MSFT) can do. Do you see any problems with this method or do you prefer the registry route mentioned in the issue? Any concerns about the EKU + thumbprint method for the unique identifier and whether the root or immediate issuer should be used?

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Review - Needed The PR is being reviewed label Aug 19, 2025
@TravisEz13 Travis Plunk (TravisEz13) added CL-Experimental Indicates that a PR should be marked as an Experimental Feature in the Change Log WG-Security security related areas such as JEA Needs-Triage The issue is new and needs to be triaged by a work group. labels Sep 23, 2025
@SteveL-MSFT

Steve Lee (SteveL-MSFT) commented Nov 24, 2025 •

Copy link
Copy Markdown
Member

Steve Lee (@SteveL-MSFT) can do. Do you see any problems with this method or do you prefer the registry route mentioned in the issue? Any concerns about the EKU + thumbprint method for the unique identifier and whether the root or immediate issuer should be used?

Was discussed in Security-WG, seems fine to us. Thanks!

@microsoft-github-policy-service microsoft-github-policy-service Bot removed the Review - Needed The PR is being reviewed label Nov 24, 2025

// Do a final check to verify that the certificate has not been
// explicitly added to the "Disallowed" store.
if (isTrusted && !IsUntrustedPublisher(signerCertificate))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect IsUntrustedPublisher() on top of the method - it makes no sense to check trust if certificate is blocked.
Also it would be nice to add the check in trace too.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Review - Needed The PR is being reviewed label Dec 2, 2025
@SteveL-MSFT Steve Lee (SteveL-MSFT) removed the Needs-Triage The issue is new and needs to be triaged by a work group. label May 19, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot removed the Review - Needed The PR is being reviewed label May 19, 2026
@SteveL-MSFT Steve Lee (SteveL-MSFT) added this to the 7.7-Consider milestone May 19, 2026
@SteveL-MSFT Steve Lee (SteveL-MSFT) added the WG-Reviewed A Working Group has reviewed this and made a recommendation label Jul 6, 2026

@TravisEz13 Travis Plunk (TravisEz13) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m good with this. Assuming the Azure Trusted Signing cert shape follows the documented format, the trust logic appears fail-secure and the added tests give good coverage of the intended behavior. Approved.

@TravisEz13
Travis Plunk (TravisEz13) requested a review from a team as a code owner July 15, 2026 20:27
#region constructor

/// <summary>
/// The EKU OID that identifies a certificate is from Azure Trusted Signing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small nit, Trusted Signing has been rebranded to Artifact Signing, any chance we could include this rename on this PR?

I am also good with submitting a subsequent PR focusing on the rebranding.

@kilasuit Ryan Yates (kilasuit) left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the Service Name change we should make these changes in the code so that we aren't linking to old, out of date service names.

I'd also recommend making similar changes in the title & body section so that in the release notes it aligns to the new name as well.

#region constructor

/// <summary>
/// The EKU OID that identifies a certificate is from Azure Trusted Signing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The EKU OID that identifies a certificate is from Azure Trusted Signing.
/// The EKU OID that identifies a certificate is from Azure Artifact Signing.

/// <summary>
/// The EKU OID that identifies a certificate is from Azure Trusted Signing.
/// </summary>
private const string _azureTrustedSigningIdentifier = "1.3.6.1.4.1.311.97.1.0";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private const string _azureTrustedSigningIdentifier = "1.3.6.1.4.1.311.97.1.0";
private const string _azureArtifactSigningIdentifier = "1.3.6.1.4.1.311.97.1.0";

private const string _azureTrustedSigningIdentifier = "1.3.6.1.4.1.311.97.1.0";

/// <summary>
/// The OID prefix that uniquely identifies a certificate issued by Azure Trusted Signing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The OID prefix that uniquely identifies a certificate issued by Azure Trusted Signing.
/// The OID prefix that uniquely identifies a certificate issued by Azure Artifact Signing.

/// <summary>
/// The OID prefix that uniquely identifies a certificate issued by Azure Trusted Signing.
/// </summary>
private const string _azureTrustedSigningIdPrefix = "1.3.6.1.4.1.311.97.";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private const string _azureTrustedSigningIdPrefix = "1.3.6.1.4.1.311.97.";
private const string _azureArtifactSigningIdPrefix = "1.3.6.1.4.1.311.97.";

/// <summary>
/// Checks if the publisher is trusted by checking whether the
/// certificate thumbprint is in the "Trusted Publishers" store or
/// the Azure Trusted Signer Publisher ID is present in the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// the Azure Trusted Signer Publisher ID is present in the
/// the Azure Artifact Signer Publisher ID is present in the

/// <param name="certificate">The certificate to check.</param>
/// <param name="publisherId">An opaque blob that uniquely identifies the publisher if present.</param>
/// <returns>True when the certificate has the Azure Trusted Signer Publisher ID EKU.</returns>
private static bool TryGetAzureTrustedSignerPublisherId(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static bool TryGetAzureTrustedSignerPublisherId(
private static bool TryGetAzureArtifactSignerPublisherId(

X509Certificate2 certificate,
[NotNullWhen(true)] out string? publisherId)
{
bool containsAzTSIdentifier = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool containsAzTSIdentifier = false;
bool containsAzASIdentifier = false;

Comment on lines +558 to +564
if (oid.Value == _azureTrustedSigningIdentifier)
{
containsAzTSIdentifier = true;
}
else if (oid.Value?.StartsWith(_azureTrustedSigningIdPrefix) == true)
{
azurePubOid = oid.Value;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (oid.Value == _azureTrustedSigningIdentifier)
{
containsAzTSIdentifier = true;
}
else if (oid.Value?.StartsWith(_azureTrustedSigningIdPrefix) == true)
{
azurePubOid = oid.Value;
if (oid.Value == _azureArtifactSigningIdentifier)
{
containsAzASIdentifier = true;
}
else if (oid.Value?.StartsWith(_azureArtifactSigningIdPrefix) == true)
{
azurePubOid = oid.Value;

}

string? caThumbprint = null;
if (containsAzTSIdentifier && azurePubOid is not null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (containsAzTSIdentifier && azurePubOid is not null)
if (containsAzASIdentifier && azurePubOid is not null)

string? caThumbprint = null;
if (containsAzTSIdentifier && azurePubOid is not null)
{
s_tracer.WriteLine("Certificate {0} has Azure Trusted Signer EKU OID {1}.",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
s_tracer.WriteLine("Certificate {0} has Azure Trusted Signer EKU OID {1}.",
s_tracer.WriteLine("Certificate {0} has Azure Artifact Signer EKU OID {1}.",

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Jul 16, 2026
@jborean93

Copy link
Copy Markdown
Collaborator Author

Appreciate the reviews but I don't really have time to work on this again and go through it all. You are more than welcome to follow this up in a separate PR or use the changes here as a baseline for another PR before merging.

@microsoft-github-policy-service microsoft-github-policy-service Bot removed the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Jul 17, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added the Review - Needed The PR is being reviewed label Sep 25, 2026
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-Experimental Indicates that a PR should be marked as an Experimental Feature in the Change Log Review - Needed The PR is being reviewed WG-Reviewed A Working Group has reviewed this and made a recommendation WG-Security security related areas such as JEA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Enterprise signed scripts

8 participants