Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
85db1fa
Initial plan
Copilot Oct 20, 2025
a642604
Add SubjectAlternativeName property to Signature class and update bui…
Copilot Oct 20, 2025
7f2629b
Add tests for SubjectAlternativeName property
Copilot Oct 20, 2025
05f026f
Revert unrelated changes to PowerShell.Common.props and docs/building…
Copilot Oct 20, 2025
9136af8
Enhance SubjectAlternativeName test to verify content
Copilot Oct 20, 2025
d883fa8
Add test to verify SubjectAlternativeName is null when cert has no SAN
Copilot Oct 20, 2025
1fa77a6
Use CurrentUser certificate store instead of LocalMachine store in tests
Copilot Oct 20, 2025
b5fb9be
Remove unnecessary PSSession for certificate creation in test
Copilot Oct 20, 2025
87c80f2
Remove nullable reference type annotations from MshSignature.cs
Copilot Oct 20, 2025
432a797
Use named constant for SubjectAlternativeName OID and remove obvious …
Copilot Oct 21, 2025
2daa634
Change SubjectAlternativeName to string array and split multiline output
Copilot Oct 21, 2025
ac7e7b1
Merge branch 'master' into copilot/add-subjectalternative-name
TravisEz13 Oct 21, 2025
391245e
Update src/System.Management.Automation/security/MshSignature.cs
TravisEz13 Oct 27, 2025
3a98730
Merge branch 'master' into copilot/add-subjectalternative-name
TravisEz13 Oct 27, 2025
f18e969
Revert changes from commit 391245eb - restore string array splitting …
Copilot Feb 7, 2026
8c69c16
Handle all line separator types in SubjectAlternativeName splitting
Copilot Feb 8, 2026
2e580fa
Remove redundant null check before calling GetSubjectAlternativeName
Copilot Feb 8, 2026
6d988ca
Fix hanging test by using PSSession with LocalMachine stores
Copilot Feb 8, 2026
c6ba5c3
Fix type assertion to check individual array elements as strings
Copilot Feb 8, 2026
dded0d6
Add explicit type check for SubjectAlternativeName as string array
Copilot Feb 9, 2026
6ba74cb
Fix type checking to verify individual array elements explicitly
Copilot Feb 9, 2026
6596a9f
Use standard PowerShell pattern for array type checking with comma op…
Copilot Feb 9, 2026
9e60d5e
Use Should -BeExactly for precise array element validation
Copilot Feb 9, 2026
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
37 changes: 37 additions & 0 deletions src/System.Management.Automation/security/MshSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ public string Path
/// </summary>
public bool IsOSBinary { get; internal set; }

/// <summary>
/// Gets the Subject Alternative Name from the signer certificate.
/// </summary>
public string[] SubjectAlternativeName { get; private set; }

/// <summary>
/// Constructor for class Signature
///
Expand Down Expand Up @@ -277,6 +282,9 @@ private void Init(string filePath,
_statusMessage = GetSignatureStatusMessage(isc,
error,
filePath);

// Extract Subject Alternative Name from the signer certificate
SubjectAlternativeName = GetSubjectAlternativeName(signer);
}

private static SignatureStatus GetSignatureStatusFromWin32Error(DWORD error)
Expand Down Expand Up @@ -389,5 +397,34 @@ private static string GetSignatureStatusMessage(SignatureStatus status,

return message;
}

/// <summary>
/// Extracts the Subject Alternative Name from the certificate.
/// </summary>
/// <param name="certificate">The certificate to extract SAN from.</param>
/// <returns>Array of SAN entries or null if not found.</returns>
private static string[] GetSubjectAlternativeName(X509Certificate2 certificate)
{
if (certificate == null)
{
return null;
}

foreach (X509Extension extension in certificate.Extensions)
{
if (extension.Oid != null && extension.Oid.Value == CertificateFilterInfo.SubjectAlternativeNameOid)
{
string formatted = extension.Format(multiLine: true);
if (string.IsNullOrEmpty(formatted))
{
return null;
}

return formatted.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
Comment thread
iSazonov marked this conversation as resolved.
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ internal DateTime Expiring
// The OID arc 1.3.6.1.4.1.311.80 is assigned to PowerShell. If we need
// new OIDs, we can assign them under this branch.
internal const string DocumentEncryptionOid = "1.3.6.1.4.1.311.80.1";
internal const string SubjectAlternativeNameOid = "2.5.29.17";
}
}

Expand Down
101 changes: 101 additions & 0 deletions test/powershell/engine/Security/FileSignature.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Describe "Windows platform file signatures" -Tags 'Feature' {
$signature | Should -Not -BeNullOrEmpty
$signature.Status | Should -BeExactly 'Valid'
$signature.SignatureType | Should -BeExactly 'Catalog'

# Verify that SubjectAlternativeName property exists
$signature.PSObject.Properties.Name | Should -Contain 'SubjectAlternativeName'
}
}

Expand Down Expand Up @@ -185,4 +188,102 @@ Describe "Windows file content signatures" -Tags @('Feature', 'RequireAdminOnWin
$actual.SignerCertificate.Thumbprint | Should -Be $certificate.Thumbprint
$actual.Status | Should -Be 'Valid'
}

It "Verifies SubjectAlternativeName is populated for certificate with SAN" {
$session = New-PSSession -UseWindowsPowerShell
try {
$sanThumbprint = Invoke-Command -Session $session -ScriptBlock {
$testPrefix = 'SelfSignedTestSAN'

$enhancedKeyUsage = [Security.Cryptography.OidCollection]::new()
$null = $enhancedKeyUsage.Add('1.3.6.1.5.5.7.3.3')

$caParams = @{
Extension = @(
[Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true, $false, 0, $true),
[Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new('KeyCertSign', $false),
[Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($enhancedKeyUsage, $false)
)
CertStoreLocation = 'Cert:\CurrentUser\My'
NotAfter = (Get-Date).AddDays(1)
Type = 'Custom'
}
$sanCA = PKI\New-SelfSignedCertificate @caParams -Subject "CN=$testPrefix-CA"

$rootStore = Get-Item -Path Cert:\LocalMachine\Root
$rootStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
try {
$rootStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sanCA.RawData))
} finally {
$rootStore.Close()
}

$certParams = @{
CertStoreLocation = 'Cert:\CurrentUser\My'
KeyUsage = 'DigitalSignature'
TextExtension = @(
"2.5.29.37={text}1.3.6.1.5.5.7.3.3",
"2.5.29.19={text}",
"2.5.29.17={text}DNS=test.example.com&DNS=*.example.com"
)
Type = 'Custom'
}
$sanCert = PKI\New-SelfSignedCertificate @certParams -Subject "CN=$testPrefix-Signed" -Signer $sanCA

$publisherStore = Get-Item -Path Cert:\LocalMachine\TrustedPublisher
$publisherStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
try {
$publisherStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sanCert.RawData))
} finally {
$publisherStore.Close()
}

$sanCA | Remove-Item
$sanCA.Thumbprint, $sanCert.Thumbprint
}
} finally {
$session | Remove-PSSession
}

$sanCARootThumbprint = $sanThumbprint[0]
$sanCertThumbprint = $sanThumbprint[1]
$sanCertificate = Get-Item -Path Cert:\CurrentUser\My\$sanCertThumbprint

try {
Set-Content -Path testdrive:\test.ps1 -Value 'Write-Output "Test SAN"' -Encoding UTF8NoBOM

$scriptPath = Join-Path $TestDrive test.ps1
$status = Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $sanCertificate
$status.Status | Should -Be 'Valid'

$actual = Get-AuthenticodeSignature -FilePath $scriptPath
$actual.SubjectAlternativeName | Should -Not -BeNullOrEmpty
,$actual.SubjectAlternativeName | Should -BeOfType [string[]]
$actual.SubjectAlternativeName.Count | Should -Be 2
$actual.SubjectAlternativeName[0] | Should -BeExactly 'DNS Name=test.example.com'
$actual.SubjectAlternativeName[1] | Should -BeExactly 'DNS Name=*.example.com'
} finally {
Remove-Item -Path "Cert:\LocalMachine\Root\$sanCARootThumbprint" -Force -ErrorAction Ignore
Remove-Item -Path "Cert:\LocalMachine\TrustedPublisher\$sanCertThumbprint" -Force -ErrorAction Ignore
Remove-Item -Path "Cert:\CurrentUser\My\$sanCertThumbprint" -Force -ErrorAction Ignore
}
}

It "Verifies SubjectAlternativeName is null when certificate has no SAN" {
Set-Content -Path testdrive:\test.ps1 -Value 'Write-Output "Test No SAN"' -Encoding UTF8NoBOM

$scriptPath = Join-Path $TestDrive test.ps1
$status = Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $certificate
$status.Status | Should -Be 'Valid'

$actual = Get-AuthenticodeSignature -FilePath $scriptPath
$actual.SignerCertificate.Thumbprint | Should -Be $certificate.Thumbprint
$actual.Status | Should -Be 'Valid'

# Verify that SubjectAlternativeName property exists
$actual.PSObject.Properties.Name | Should -Contain 'SubjectAlternativeName'

# Verify the content is null when certificate has no SAN extension
$actual.SubjectAlternativeName | Should -BeNullOrEmpty
}
}
Loading