Repository navigation
Trusted Publisher Checks for Azure Trusted Signing - #25824
Jordan Borean (jborean93) wants to merge 5 commits into
Conversation
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.
|
This looks really good. |
There was a problem hiding this comment.
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 |
| azurePublisherId == trustedIdentifier) | ||
| { | ||
| isTrusted = true; | ||
| break; |
There was a problem hiding this comment.
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.
Steve Lee (SteveL-MSFT)
left a comment
There was a problem hiding this comment.
Since this is coming in late for 7.6, can you make this an Experimental Feature?
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
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! |
|
|
||
| // Do a final check to verify that the certificate has not been | ||
| // explicitly added to the "Disallowed" store. | ||
| if (isTrusted && !IsUntrustedPublisher(signerCertificate)) |
There was a problem hiding this comment.
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.
Travis Plunk (TravisEz13)
left a comment
There was a problem hiding this comment.
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.
| #region constructor | ||
|
|
||
| /// <summary> | ||
| /// The EKU OID that identifies a certificate is from Azure Trusted Signing. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
| /// 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"; |
There was a problem hiding this comment.
| 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. |
There was a problem hiding this comment.
| /// 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."; |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
| /// 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( |
There was a problem hiding this comment.
| private static bool TryGetAzureTrustedSignerPublisherId( | |
| private static bool TryGetAzureArtifactSignerPublisherId( |
| X509Certificate2 certificate, | ||
| [NotNullWhen(true)] out string? publisherId) | ||
| { | ||
| bool containsAzTSIdentifier = false; |
There was a problem hiding this comment.
| bool containsAzTSIdentifier = false; | |
| bool containsAzASIdentifier = false; |
| if (oid.Value == _azureTrustedSigningIdentifier) | ||
| { | ||
| containsAzTSIdentifier = true; | ||
| } | ||
| else if (oid.Value?.StartsWith(_azureTrustedSigningIdPrefix) == true) | ||
| { | ||
| azurePubOid = oid.Value; |
There was a problem hiding this comment.
| 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) |
There was a problem hiding this comment.
| 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}.", |
There was a problem hiding this comment.
| s_tracer.WriteLine("Certificate {0} has Azure Trusted Signer EKU OID {1}.", | |
| s_tracer.WriteLine("Certificate {0} has Azure Artifact Signer EKU OID {1}.", |
|
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. |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
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
X509Chainclass. 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
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header