Skip to content

Commit 10d1785

Browse files
authored
Update DnsNameList for X509Certificate2 to use X509SubjectAlternativeNameExtension.EnumerateDnsNames Method (PowerShell#24714)
1 parent 8cdb728 commit 10d1785

2 files changed

Lines changed: 102 additions & 45 deletions

File tree

src/Microsoft.PowerShell.Security/security/CertificateProvider.cs

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3309,82 +3309,63 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert)
33093309
public sealed class DnsNameProperty
33103310
{
33113311
private readonly List<DnsNameRepresentation> _dnsList = new();
3312-
private readonly System.Globalization.IdnMapping idnMapping = new();
3312+
private readonly IdnMapping idnMapping = new();
33133313

3314-
private const string dnsNamePrefix = "DNS Name=";
33153314
private const string distinguishedNamePrefix = "CN=";
33163315

33173316
/// <summary>
33183317
/// Get property of DnsNameList.
33193318
/// </summary>
3320-
public List<DnsNameRepresentation> DnsNameList
3319+
public List<DnsNameRepresentation> DnsNameList => _dnsList;
3320+
3321+
private DnsNameRepresentation GetDnsNameRepresentation(string dnsName)
33213322
{
3322-
get
3323+
string unicodeName;
3324+
3325+
try
3326+
{
3327+
unicodeName = idnMapping.GetUnicode(dnsName);
3328+
}
3329+
catch (ArgumentException)
33233330
{
3324-
return _dnsList;
3331+
// The name is not valid Punycode, assume it's valid ASCII.
3332+
unicodeName = dnsName;
33253333
}
3334+
3335+
return new DnsNameRepresentation(dnsName, unicodeName);
33263336
}
33273337

33283338
/// <summary>
33293339
/// Constructor for DnsNameProperty.
33303340
/// </summary>
33313341
public DnsNameProperty(X509Certificate2 cert)
33323342
{
3333-
string name;
3334-
string unicodeName;
3335-
DnsNameRepresentation dnsName;
33363343
_dnsList = new List<DnsNameRepresentation>();
33373344

33383345
// extract DNS name from subject distinguish name
33393346
// if it exists and does not contain a comma
33403347
// a comma, indicates it is not a DNS name
3341-
if (cert.Subject.StartsWith(distinguishedNamePrefix, System.StringComparison.OrdinalIgnoreCase) &&
3348+
if (cert.Subject.StartsWith(distinguishedNamePrefix, StringComparison.OrdinalIgnoreCase) &&
33423349
!cert.Subject.Contains(','))
33433350
{
3344-
name = cert.Subject.Substring(distinguishedNamePrefix.Length);
3345-
try
3346-
{
3347-
unicodeName = idnMapping.GetUnicode(name);
3348-
}
3349-
catch (System.ArgumentException)
3350-
{
3351-
// The name is not valid punyCode, assume it's valid ascii.
3352-
unicodeName = name;
3353-
}
3354-
3355-
dnsName = new DnsNameRepresentation(name, unicodeName);
3351+
string parsedSubjectDistinguishedDnsName = cert.Subject.Substring(distinguishedNamePrefix.Length);
3352+
DnsNameRepresentation dnsName = GetDnsNameRepresentation(parsedSubjectDistinguishedDnsName);
33563353
_dnsList.Add(dnsName);
33573354
}
33583355

3356+
// Extract DNS names from SAN extensions
33593357
foreach (X509Extension extension in cert.Extensions)
33603358
{
3361-
// Filter to the OID for Subject Alternative Name
3362-
if (extension.Oid.Value == "2.5.29.17")
3359+
if (extension is X509SubjectAlternativeNameExtension sanExtension)
33633360
{
3364-
string[] names = extension.Format(true).Split(Environment.NewLine);
3365-
foreach (string nameLine in names)
3361+
foreach (string dnsNameEntry in sanExtension.EnumerateDnsNames())
33663362
{
3367-
// Get the part after 'DNS Name='
3368-
if (nameLine.StartsWith(dnsNamePrefix, System.StringComparison.InvariantCultureIgnoreCase))
3369-
{
3370-
name = nameLine.Substring(dnsNamePrefix.Length);
3371-
try
3372-
{
3373-
unicodeName = idnMapping.GetUnicode(name);
3374-
}
3375-
catch (System.ArgumentException)
3376-
{
3377-
// The name is not valid punyCode, assume it's valid ascii.
3378-
unicodeName = name;
3379-
}
3380-
3381-
dnsName = new DnsNameRepresentation(name, unicodeName);
3363+
DnsNameRepresentation dnsName = GetDnsNameRepresentation(dnsNameEntry);
33823364

3383-
// Only add the name if it is not the same as an existing name.
3384-
if (!_dnsList.Contains(dnsName))
3385-
{
3386-
_dnsList.Add(dnsName);
3387-
}
3365+
// Only add the name if it is not the same as an existing name.
3366+
if (!_dnsList.Contains(dnsName))
3367+
{
3368+
_dnsList.Add(dnsName);
33883369
}
33893370
}
33903371
}

test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,80 @@ Describe "Certificate Provider tests" -Tags "Feature" {
292292
$certs.Thumbprint | Should -BeExactly $thumbprint
293293
}
294294
}
295+
296+
Context "SAN DNS Name Tests" {
297+
BeforeAll {
298+
$configFilePath = Join-Path -Path $TestDrive -ChildPath 'openssl.cnf'
299+
$keyFilePath = Join-Path -Path $TestDrive -ChildPath 'privateKey.key'
300+
$certFilePath = Join-Path -Path $TestDrive -ChildPath 'certificate.crt'
301+
$pfxFilePath = Join-Path -Path $TestDrive -ChildPath 'certificate.pfx'
302+
$password = New-CertificatePassword | ConvertFrom-SecureString -AsPlainText
303+
304+
$config = @"
305+
[ req ]
306+
default_bits = 2048
307+
distinguished_name = req_distinguished_name
308+
req_extensions = v3_req
309+
prompt = no
310+
311+
[ req_distinguished_name ]
312+
CN = yourdomain.com
313+
314+
[ v3_req ]
315+
subjectAltName = @alt_names
316+
317+
[ alt_names ]
318+
DNS.1 = yourdomain.com
319+
DNS.2 = www.yourdomain.com
320+
DNS.3 = api.yourdomain.com
321+
DNS.4 = xn--mnchen-3ya.com
322+
DNS.5 = xn--80aaxitdbjr.com
323+
DNS.6 = xn--caf-dma.com
324+
"@
325+
326+
# Write the configuration to the specified path
327+
Set-Content -Path $configFilePath -Value $config
328+
329+
# Generate the self-signed certificate with SANs
330+
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $keyFilePath -out $certFilePath -config $configFilePath -extensions v3_req
331+
332+
# Create the PFX file
333+
openssl pkcs12 -export -out $pfxFilePath -inkey $keyFilePath -in $certFilePath -passout pass:$password
334+
}
335+
336+
It "Should set DNSNameList from SAN extensions" {
337+
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxFilePath, $password)
338+
339+
$expectedDnsNameList = @(
340+
[PSCustomObject]@{
341+
Punycode = "yourdomain.com"
342+
Unicode = "yourdomain.com"
343+
}
344+
[PSCustomObject]@{
345+
Punycode = "www.yourdomain.com"
346+
Unicode = "www.yourdomain.com"
347+
}
348+
[PSCustomObject]@{
349+
Punycode = "api.yourdomain.com"
350+
Unicode = "api.yourdomain.com"
351+
}
352+
[PSCustomObject]@{
353+
Punycode = "xn--mnchen-3ya.com"
354+
Unicode = "münchen.com"
355+
}
356+
[PSCustomObject]@{
357+
Punycode = "xn--80aaxitdbjr.com"
358+
Unicode = "папитрока.com"
359+
}
360+
[PSCustomObject]@{
361+
Punycode = "xn--caf-dma.com"
362+
Unicode = "café.com"
363+
}
364+
)
365+
366+
$cert | Should -Not -BeNullOrEmpty
367+
$cert.DnsNameList | Should -HaveCount 6
368+
($cert.DnsNameList | ConvertTo-Json -Compress) | Should -BeExactly ($expectedDnsNameList | ConvertTo-Json -Compress)
369+
}
370+
}
295371
}

0 commit comments

Comments
 (0)