|
| 1 | +# ------------------------------------------------------------------------------ |
| 2 | +# Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. |
| 3 | +# ------------------------------------------------------------------------------ |
| 4 | + |
| 5 | +<# |
| 6 | +.SYNOPSIS |
| 7 | + Generates an object representing all the values contained in a certificate file that can be used in Entra ID for configuring CertificateUserIDs in Certificate-Based Authentication. |
| 8 | +
|
| 9 | +.DESCRIPTION |
| 10 | + Retrieves and returns an object with the properties 'PrincipalName', 'RFC822Name', 'IssuerAndSubject', 'Subject', 'SKI', 'SHA1PublicKey', and 'IssuerAndSerialNumber' from a certificate file for use in CertificateUserIDs configuration in Certificate-Based Authentication, according to the guidelines outlined in the Microsoft documentation for certificate-based authentication |
| 11 | +
|
| 12 | +.PARAMETER Path |
| 13 | + The path to the certificate file. The file can be in .cer or .pem format. |
| 14 | +
|
| 15 | +.PARAMETER Certificate |
| 16 | + An X509Certificate2 object |
| 17 | +
|
| 18 | +.PARAMETER CertificateMapping |
| 19 | + The certificate mapping property to retrieve. Valid values are PrincipalName, RFC822Name, IssuerAndSubject, Subject, SKI, SHA1PublicKey, and IssuerAndSerialNumber. |
| 20 | +
|
| 21 | +.EXAMPLE |
| 22 | + PS > Get-MsIdCBACertificateUserIdFromCertificate -Path "C:\path\to\certificate.cer" |
| 23 | +
|
| 24 | + This command retrieves all the possible certificate mappings and returns an object to represent them. |
| 25 | +
|
| 26 | +.EXAMPLE |
| 27 | + PS > Get-MsIdCBACertificateUserIdFromCertificate -Certificate $cert |
| 28 | +
|
| 29 | + This command retrieves all the possible certificate mappings and returns an object to represent them. |
| 30 | +
|
| 31 | +.EXAMPLE |
| 32 | + PS > Get-MsIdCBACertificateUserIdFromCertificate -Path "C:\path\to\certificate.cer" -CertificateMapping Subject |
| 33 | +
|
| 34 | + This command retrieves and returns the PrincipalName property. |
| 35 | +
|
| 36 | +.OUTPUTS |
| 37 | + Returns an object containing the certificateUserIDs that can be used with the givin certificate. |
| 38 | +
|
| 39 | + @{ |
| 40 | + PrincipalName = "X509:<PN>[email protected]" |
| 41 | + RFC822Name = "X509:<RFC822>[email protected]" |
| 42 | + IssuerAndSubject = "X509:<I>DC=com,DC=contoso,CN=CONTOSO-DC-CA<S>DC=com,DC=contoso,OU=UserAccounts,CN=mfatest" |
| 43 | + Subject = "X509:<S>DC=com,DC=contoso,OU=UserAccounts,CN=mfatest" |
| 44 | + SKI = "X509:<SKI>aB1cD2eF3gH4iJ5kL6mN7oP8qR" |
| 45 | + SHA1PublicKey = "X509:<SHA1-PUKEY>cD2eF3gH4iJ5kL6mN7oP8qR9sT" |
| 46 | + IssuerAndSerialNumber = "X509:<I>DC=com,DC=contoso,CN=CONTOSO-DC-CA<SR>eF3gH4iJ5kL6mN7oP8qR9sT0uV" |
| 47 | + } |
| 48 | +
|
| 49 | +#> |
| 50 | + |
| 51 | +function Get-MsIdCBACertificateUserIdFromCertificate { |
| 52 | + param ( |
| 53 | + [Parameter(Mandatory = $false)] |
| 54 | + [string]$Path, |
| 55 | + [Parameter(Mandatory = $false)] |
| 56 | + [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, |
| 57 | + [Parameter(Mandatory = $false)] |
| 58 | + [ValidateSet("PrincipalName", "RFC822Name", "IssuerAndSubject", "Subject", "SKI", "SHA1PublicKey", "IssuerAndSerialNumber")] |
| 59 | + [string]$CertificateMapping |
| 60 | + ) |
| 61 | + |
| 62 | + function Get-Certificate { |
| 63 | + param ( |
| 64 | + [string]$filePath |
| 65 | + ) |
| 66 | + if ($filePath.EndsWith(".cer")) { |
| 67 | + return [System.Security.Cryptography.X509Certificates.X509Certificate]::new($filePath) |
| 68 | + } elseif ($filePath.EndsWith(".pem")) { |
| 69 | + $pemContent = Get-Content -Path $filePath -Raw |
| 70 | + $pemContent = $pemContent -replace "-----BEGIN CERTIFICATE-----", "" |
| 71 | + $pemContent = $pemContent -replace "-----END CERTIFICATE-----", "" |
| 72 | + $pemContent = $pemContent -replace "(\r\n|\n|\r)", "" |
| 73 | + $pemBytes = [Convert]::FromBase64String($pemContent) |
| 74 | + $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate]::new($pemBytes) |
| 75 | + |
| 76 | + return $certificate |
| 77 | + } else { |
| 78 | + throw "Unsupported certificate format. Please provide a .cer or .pem file." |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + function Get-DistinguishedNameAsString { |
| 83 | + param ( |
| 84 | + [System.Security.Cryptography.X509Certificates.X500DistinguishedName]$distinguishedName |
| 85 | + ) |
| 86 | + |
| 87 | + $dn = $distinguishedName.Decode([System.Security.Cryptography.X509Certificates.X500DistinguishedNameFlags]::UseNewLines -bor [System.Security.Cryptography.X509Certificates.X500DistinguishedNameFlags]::DoNotUsePlusSign) |
| 88 | + |
| 89 | + $dn = $dn -replace "(\r\n|\n|\r)", "," |
| 90 | + return $dn.TrimEnd(',') |
| 91 | + } |
| 92 | + |
| 93 | + function Get-SerialNumberAsLittleEndianHexString { |
| 94 | + param ( |
| 95 | + [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert |
| 96 | + ) |
| 97 | + |
| 98 | + $littleEndianSerialNumber = $cert.GetSerialNumber() |
| 99 | + |
| 100 | + if ($littleEndianSerialNumber.Length -eq 0) |
| 101 | + { |
| 102 | + return "" |
| 103 | + } |
| 104 | + |
| 105 | + [System.Array]::Reverse($littleEndianSerialNumber) |
| 106 | + $hexString = -join ($littleEndianSerialNumber | ForEach-Object { $_.ToString("x2") }) |
| 107 | + return $hexString |
| 108 | + } |
| 109 | + |
| 110 | + function Get-SubjectKeyIdentifier { |
| 111 | + param ( |
| 112 | + [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert |
| 113 | + ) |
| 114 | + foreach ($extension in $cert.Extensions) { |
| 115 | + if ($extension.Oid.Value -eq "2.5.29.14") { |
| 116 | + $ski = New-Object System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension -ArgumentList $extension, $false |
| 117 | + return $ski.SubjectKeyIdentifier |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + return "" |
| 122 | + } |
| 123 | + |
| 124 | + # Function to generate certificate mapping fields |
| 125 | + function Get-CertificateMappingFields { |
| 126 | + param ( |
| 127 | + [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert |
| 128 | + ) |
| 129 | + $subject = Get-DistinguishedNameAsString -distinguishedName $cert.SubjectName |
| 130 | + $issuer = Get-DistinguishedNameAsString -distinguishedName $cert.IssuerName |
| 131 | + $serialNumber = Get-SerialNumberAsLittleEndianHexString -cert $cert |
| 132 | + $thumbprint = $cert.Thumbprint |
| 133 | + $principalName = $cert.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::UpnName, $false) |
| 134 | + $emailName = $cert.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::EmailName, $false) |
| 135 | + $subjectKeyIdentifier = Get-SubjectKeyIdentifier -cert $cert |
| 136 | + $sha1PublicKey = $cert.GetCertHashString() |
| 137 | + |
| 138 | + return @{ |
| 139 | + "SubjectName" = $subject |
| 140 | + "IssuerName" = $issuer |
| 141 | + "SerialNumber" = $serialNumber |
| 142 | + "Thumbprint" = $thumbprint |
| 143 | + "PrincipalName" = $principalName |
| 144 | + "EmailName" = $emailName |
| 145 | + "SubjectKeyIdentifier" = $subjectKeyIdentifier |
| 146 | + "Sha1PublicKey" = $sha1PublicKey |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + function Get-CertificateUserIds { |
| 151 | + param ( |
| 152 | + [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert |
| 153 | + ) |
| 154 | + |
| 155 | + $mappingFields = Get-CertificateMappingFields -cert $cert |
| 156 | + |
| 157 | + $certUserIDs = @{ |
| 158 | + "PrincipalName" = "" |
| 159 | + "RFC822Name" = "" |
| 160 | + "IssuerAndSubject" = "" |
| 161 | + "Subject" = "" |
| 162 | + "SKI" = "" |
| 163 | + "SHA1PublicKey" = "" |
| 164 | + "IssuerAndSerialNumber" = "" |
| 165 | + } |
| 166 | + |
| 167 | + if (-not [string]::IsNullOrWhiteSpace($mappingFields.PrincipalName)) |
| 168 | + { |
| 169 | + $certUserIDs.PrincipalName = "X509:<PN>$($mappingFields.PrincipalName)" |
| 170 | + } |
| 171 | + |
| 172 | + if (-not [string]::IsNullOrWhiteSpace($mappingFields.EmailName)) |
| 173 | + { |
| 174 | + $certUserIDs.RFC822Name = "X509:<RFC822>$($mappingFields.EmailName)" |
| 175 | + } |
| 176 | + |
| 177 | + if ((-not [string]::IsNullOrWhiteSpace($mappingFields.IssuerName)) -and (-not [string]::IsNullOrWhiteSpace($mappingFields.SubjectName))) |
| 178 | + { |
| 179 | + $certUserIDs.IssuerAndSubject = "X509:<I>$($mappingFields.IssuerName)<S>$($mappingFields.SubjectName)" |
| 180 | + } |
| 181 | + |
| 182 | + if (-not [string]::IsNullOrWhiteSpace($mappingFields.SubjectName)) |
| 183 | + { |
| 184 | + $certUserIDs.Subject = "X509:<S>$($mappingFields.SubjectName)" |
| 185 | + } |
| 186 | + |
| 187 | + if (-not [string]::IsNullOrWhiteSpace($mappingFields.SubjectKeyIdentifier)) |
| 188 | + { |
| 189 | + $certUserIDs.SKI = "X509:<SKI>$($mappingFields.SubjectKeyIdentifier)" |
| 190 | + } |
| 191 | + |
| 192 | + if (-not [string]::IsNullOrWhiteSpace($mappingFields.Sha1PublicKey)) |
| 193 | + { |
| 194 | + $certUserIDs.SHA1PublicKey = "X509:<SHA1-PUKEY>$($mappingFields.Sha1PublicKey)" |
| 195 | + } |
| 196 | + |
| 197 | + if ((-not [string]::IsNullOrWhiteSpace($mappingFields.IssuerName)) -and (-not [string]::IsNullOrWhiteSpace($mappingFields.SerialNumber))) |
| 198 | + { |
| 199 | + $certUserIDs.IssuerAndSerialNumber = "X509:<I>$($mappingFields.IssuerName)<SR>$($mappingFields.SerialNumber)" |
| 200 | + } |
| 201 | + |
| 202 | + return $certUserIDs |
| 203 | + } |
| 204 | + |
| 205 | + function Main |
| 206 | + { |
| 207 | + $cert = $Certificate |
| 208 | + if ($null -eq $cert) |
| 209 | + { |
| 210 | + $cert = Get-Certificate -filePath $Path |
| 211 | + } |
| 212 | + |
| 213 | + $mappings = Get-CertificateUserIds -cert $cert |
| 214 | + |
| 215 | + if ($CertificateMapping -eq "") |
| 216 | + { |
| 217 | + return $mappings |
| 218 | + } |
| 219 | + else |
| 220 | + { |
| 221 | + $value = $mappings[$CertificateMapping] |
| 222 | + return "$($value)" |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + # Call main function |
| 227 | + return Main |
| 228 | +} |
0 commit comments