# New-SelfSignedCertificateEx.ps1 [OutputType('[System.Security.Cryptography.X509Certificates.X509Certificate2]')] [CmdletBinding(DefaultParameterSetName = '__store')] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$Subject, [Parameter(Position = 1)] [datetime]$NotBefore = [DateTime]::Now.AddDays(-1), [Parameter(Position = 2)] [datetime]$NotAfter = $NotBefore.AddDays(365), [string]$SerialNumber, [Alias('CSP')] [string]$ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0", [string]$AlgorithmName = "RSA", [int]$KeyLength = 2048, [validateSet("Exchange","Signature")] [string]$KeySpec = "Exchange", [Alias('EKU')] [Security.Cryptography.Oid[]]$EnhancedKeyUsage, [Alias('KU')] [Security.Cryptography.X509Certificates.X509KeyUsageFlags]$KeyUsage, [Alias('SAN')] [String[]]$SubjectAlternativeName, [bool]$IsCA, [int]$PathLength = -1, [Security.Cryptography.X509Certificates.X509ExtensionCollection]$CustomExtension, [ValidateSet('MD5','SHA1','SHA256','SHA384','SHA512')] [string]$SignatureAlgorithm = "SHA1", [string]$FriendlyName, [Parameter(ParameterSetName = '__store')] [Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = "CurrentUser", [Parameter(Mandatory = $true, ParameterSetName = '__file')] [Alias('OutFile','OutPath','Out')] [IO.FileInfo]$Path, [Parameter(Mandatory = $true, ParameterSetName = '__file')] [Security.SecureString]$Password, [switch]$AllowSMIME, [switch]$Exportable ) $ErrorActionPreference = "Stop" if ([Environment]::OSVersion.Version.Major -lt 6) { $NotSupported = New-Object NotSupportedException -ArgumentList "Windows XP and Windows Server 2003 are not supported!" throw $NotSupported } $ExtensionsToAdd = @() #region constants # contexts New-Variable -Name UserContext -Value 0x1 -Option Constant New-Variable -Name MachineContext -Value 0x2 -Option Constant # encoding New-Variable -Name Base64Header -Value 0x0 -Option Constant New-Variable -Name Base64 -Value 0x1 -Option Constant New-Variable -Name Binary -Value 0x3 -Option Constant New-Variable -Name Base64RequestHeader -Value 0x4 -Option Constant # SANs New-Variable -Name OtherName -Value 0x1 -Option Constant New-Variable -Name RFC822Name -Value 0x2 -Option Constant New-Variable -Name DNSName -Value 0x3 -Option Constant New-Variable -Name DirectoryName -Value 0x5 -Option Constant New-Variable -Name URL -Value 0x7 -Option Constant New-Variable -Name IPAddress -Value 0x8 -Option Constant New-Variable -Name RegisteredID -Value 0x9 -Option Constant New-Variable -Name Guid -Value 0xa -Option Constant New-Variable -Name UPN -Value 0xb -Option Constant # installation options New-Variable -Name AllowNone -Value 0x0 -Option Constant New-Variable -Name AllowNoOutstandingRequest -Value 0x1 -Option Constant New-Variable -Name AllowUntrustedCertificate -Value 0x2 -Option Constant New-Variable -Name AllowUntrustedRoot -Value 0x4 -Option Constant # PFX export options New-Variable -Name PFXExportEEOnly -Value 0x0 -Option Constant New-Variable -Name PFXExportChainNoRoot -Value 0x1 -Option Constant New-Variable -Name PFXExportChainWithRoot -Value 0x2 -Option Constant #endregion #region Subject processing # http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx $SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName $SubjectDN.Encode($Subject, 0x0) #endregion #region Extensions #region Enhanced Key Usages processing if ($EnhancedKeyUsage) { $OIDs = New-Object -ComObject X509Enrollment.CObjectIDs $EnhancedKeyUsage | ForEach-Object { $OID = New-Object -ComObject X509Enrollment.CObjectID $OID.InitializeFromValue($_.Value) # http://msdn.microsoft.com/en-us/library/aa376785(VS.85).aspx $OIDs.Add($OID) } # http://msdn.microsoft.com/en-us/library/aa378132(VS.85).aspx $EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage $EKU.InitializeEncode($OIDs) $ExtensionsToAdd += "EKU" } #endregion #region Key Usages processing if ($KeyUsage -ne $null) { $KU = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage $KU.InitializeEncode([int]$KeyUsage) $KU.Critical = $true $ExtensionsToAdd += "KU" } #endregion #region Basic Constraints processing if ($PSBoundParameters.Keys.Contains("IsCA")) { # http://msdn.microsoft.com/en-us/library/aa378108(v=vs.85).aspx $BasicConstraints = New-Object -ComObject X509Enrollment.CX509ExtensionBasicConstraints if (!$IsCA) {$PathLength = -1} $BasicConstraints.InitializeEncode($IsCA,$PathLength) $BasicConstraints.Critical = $IsCA $ExtensionsToAdd += "BasicConstraints" } #endregion #region SAN processing if ($SubjectAlternativeName) { $SAN = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames $Names = New-Object -ComObject X509Enrollment.CAlternativeNames foreach ($altname in $SubjectAlternativeName) { $Name = New-Object -ComObject X509Enrollment.CAlternativeName if ($altname.Contains("@")) { $Name.InitializeFromString($RFC822Name,$altname) } else { try { $Bytes = [Net.IPAddress]::Parse($altname).GetAddressBytes() $Name.InitializeFromRawData($IPAddress,$Base64,[Convert]::ToBase64String($Bytes)) } catch { try { $Bytes = [Guid]::Parse($altname).ToByteArray() $Name.InitializeFromRawData($Guid,$Base64,[Convert]::ToBase64String($Bytes)) } catch { try { $Bytes = ([Security.Cryptography.X509Certificates.X500DistinguishedName]$altname).RawData $Name.InitializeFromRawData($DirectoryName,$Base64,[Convert]::ToBase64String($Bytes)) } catch {$Name.InitializeFromString($DNSName,$altname)} } } } $Names.Add($Name) } $SAN.InitializeEncode($Names) $ExtensionsToAdd += "SAN" } #endregion #region Custom Extensions if ($CustomExtension) { $count = 0 foreach ($ext in $CustomExtension) { # http://msdn.microsoft.com/en-us/library/aa378077(v=vs.85).aspx $Extension = New-Object -ComObject X509Enrollment.CX509Extension $EOID = New-Object -ComObject X509Enrollment.CObjectId $EOID.InitializeFromValue($ext.Oid.Value) $EValue = [Convert]::ToBase64String($ext.RawData) $Extension.Initialize($EOID,$Base64,$EValue) $Extension.Critical = $ext.Critical New-Variable -Name ("ext" + $count) -Value $Extension $ExtensionsToAdd += ("ext" + $count) $count++ } } #endregion #endregion #region Private Key # http://msdn.microsoft.com/en-us/library/aa378921(VS.85).aspx $PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey $PrivateKey.ProviderName = $ProviderName $AlgID = New-Object -ComObject X509Enrollment.CObjectId $AlgID.InitializeFromValue(([Security.Cryptography.Oid]$AlgorithmName).Value) $PrivateKey.Algorithm = $AlgID # http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx $PrivateKey.KeySpec = switch ($KeySpec) {"Exchange" {1}; "Signature" {2}} $PrivateKey.Length = $KeyLength # key will be stored in current user certificate store switch ($PSCmdlet.ParameterSetName) { '__store' { $PrivateKey.MachineContext = if ($StoreLocation -like "LocalMachine") {$true} else {$false} } '__file' { $PrivateKey.MachineContext = $false } } $PrivateKey.ExportPolicy = if ($Exportable) {1} else {0} $PrivateKey.Create() #endregion # http://msdn.microsoft.com/en-us/library/aa377124(VS.85).aspx $Cert = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate if ($PrivateKey.MachineContext) { $Cert.InitializeFromPrivateKey($MachineContext,$PrivateKey,"") } else { $Cert.InitializeFromPrivateKey($UserContext,$PrivateKey,"") } $Cert.Subject = $SubjectDN $Cert.Issuer = $Cert.Subject $Cert.NotBefore = $NotBefore $Cert.NotAfter = $NotAfter foreach ($item in $ExtensionsToAdd) {$Cert.X509Extensions.Add((Get-Variable -Name $item -ValueOnly))} if (![string]::IsNullOrEmpty($SerialNumber)) { if ($SerialNumber -match "[^0-9a-fA-F]") {throw "Invalid serial number specified."} if ($SerialNumber.Length % 2) {$SerialNumber = "0" + $SerialNumber} $Bytes = $SerialNumber -split "(.{2})" | Where-Object {$_} | ForEach-Object{[Convert]::ToByte($_,16)} $ByteString = [Convert]::ToBase64String($Bytes) $Cert.SerialNumber.InvokeSet($ByteString,1) } if ($AllowSMIME) {$Cert.SmimeCapabilities = $true} $SigOID = New-Object -ComObject X509Enrollment.CObjectId $SigOID.InitializeFromValue(([Security.Cryptography.Oid]$SignatureAlgorithm).Value) $Cert.SignatureInformation.HashAlgorithm = $SigOID # completing certificate request template building $Cert.Encode() # interface: http://msdn.microsoft.com/en-us/library/aa377809(VS.85).aspx $Request = New-Object -ComObject X509Enrollment.CX509enrollment $Request.InitializeFromRequest($Cert) $Request.CertificateFriendlyName = $FriendlyName $endCert = $Request.CreateRequest($Base64) $Request.InstallResponse($AllowUntrustedCertificate,$endCert,$Base64,"") switch ($PSCmdlet.ParameterSetName) { '__file' { $PFXString = $Request.CreatePFX( [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)), $PFXExportEEOnly, $Base64 ) Set-Content -Path $Path -Value ([Convert]::FromBase64String($PFXString)) -Encoding Byte } } [Byte[]]$CertBytes = [Convert]::FromBase64String($endCert) New-Object Security.Cryptography.X509Certificates.X509Certificate2 @(,$CertBytes)