Skip to main content

PowerShell Crypto Guy's weblog

Go Search
Blog-EN
Blog-RU
PS PKI Module
  

 About



E-mail - Send mail to the author(s)
Live Messenger -
My main blog -
Links
Archive

 ‭(Hidden)‬ Admin Links

Locations of visitors to this page
с блек-джеком и шлюхами
Self-signed certificate creation with PowerShell

Hello folks, today I want to present you my another product in PKI integration with Windows PowerShell. I worked hardly on server-side extensions: PowerShell PKI Module, which is (so far) the biggest project I developed.

Now I got a time to work on client side extensions. Some prototypes are already published in this blog. The first complete tool (which is a part of client-side extensions) is self-signed certificate creation for testing purposes. The reason why I developed this tool is that makecert.exe (from Windows SDK) is now deprecated. The blog post provides a replacement for makecert — certreq.exe tool. Although, certreq is very cool, there are few things to note:

1) certreq uses external INF file which may be a bit complicated.

2) if there is a mistake in the INF file, certreq raises exception message box. It is a pain when the message box is raised in PowerShell remoting session. You never will see it and unable to close it!

My script relies on the same APIs as certreq and more flexible. Also it demonstrates the techniques of CertEnroll API usage in action.

The script defines the following parameters:

  • Subject — specifies the certificate subject in a X500 distinguished name format. Example: CN=Test Cert, OU=Sandbox
  • NotBefore — Specifies the date and time when the certificate become valid. By default previous day date is used.
  • NotAfter  — Specifies the date and time when the certificate expires. By default, the certificate is valid for 1 year.
  • SerialNumber — Specifies the desired serial number in a hex format. Example: 01a4ff2
  • ProviderName — Specifies the Cryptography Service Provider (CSP) name. You can use either legacy CSP and Key Storage Providers (KSP). By default "Microsoft Enhanced Cryptographic Provider v1.0" CSP is used.
  • AlgorithmName — Specifies the public key algorithm. By default RSA algorithm is used. RSA is the only algorithm supported by legacy CSPs. With key storage providers (KSP) you can use CNG algorithms, like ECDH. For CNG algorithms you must use full name:
        ECDH_P256
        ECDH_P384
        ECDH_P521
     
        
    In addition, KeyLength parameter must be specified explicitly when non-RSA algorithm is used.
  • KeyLength — Specifies the key length to generate. By default 2048-bit key is generated.
  • KeySpec — Specifies the public key operations type. The possible values are: Exchange and Signature. Default value is Exchange.
  • EnhancedKeyUsage — Specifies the intended uses of the public key contained in a certificate. You can specify either, EKU friendly name (for example 'Server Authentication') or object identifier (OID) value (for example '1.3.6.1.5.5.7.3.1').
  • KeyUsage — Specifies restrictions on the operations that can be performed by the public key contained in the certificate. Possible values (and their respective integer values to make bitwise operations) are:
        EncipherOnly
        CrlSign
        KeyCertSign
        KeyAgreement
        DataEncipherment
        KeyEncipherment
        NonRepudiation
        DigitalSignature
        DecipherOnly
       
    you can combine key usages values by using bitwise OR operation. when combining multiple flags, they must be enclosed in quotes and separated by a comma character. For example, to combine KeyEncipherment and DigitalSignature flags you should type: "KeyEncipherment, DigitalSignature".
        
    If the certificate is CA certificate (see IsCA parameter), key usages extension is generated automatically with the following key usages: Certificate Signing, Off-line CRL Signing, CRL Signing.
  • SubjectAlternativeName — Specifies alternative names for the subject. Unlike Subject field, this extension allows to specify more than one name. Also, multiple types of alternative names are supported. The cmdlet supports the following SAN types:
        RFC822 Name
        IP address (both, IPv4 and IPv6)
        Guid
        Directory name
        DNS name
  • IsCA — Specifies whether the certificate is CA (IsCA = $true) or end entity (IsCA = $false) certificate. If this parameter is set to $false, PathLength parameter is ignored. Basic Constraints extension is marked as critical.

  • PathLength — Specifies the number of additional CA certificates in the chain under this certificate. If PathLength parameter is set to zero, then no additional (subordinate) CA certificates are permitted under this CA.

  • CustomExtension — Specifies the custom extension to include to a self-signed certificate. This parameter must not be used to specify the extension that is supported via other parameters. In order to use this parameter, the extension must be formed in a collection of initialized X509Extension objects.

  • SignatureAlgorithm — Specifies signature algorithm used to sign the certificate. By default 'SHA1' algorithm is used.

  • FriendlyName — Specifies friendly name for the certificate.

  • StoreLocation — Specifies the store location to store self-signed certificate. Possible values are: 'CurrentUser' and 'LocalMachine'. 'CurrentUser' store is intended for user certificates and computer (as well as CA) certificates must be stored in 'LocalMachine' store.

  • StoreName — Specifies the container name in the certificate store. Possible container names are:
        AddressBook
        AuthRoot
        CertificateAuthority
        Disallowed
        My
        Root
        TrustedPeople
        TrustedPublisher

  • Path — Specifies the path to a PFX file to export a self-signed certificate.

  • Password — Specifies the password for PFX file.

  • AllowSMIME — Enables Secure/Multipurpose Internet Mail Extensions for the certificate.

  • Exportable — Marks private key as exportable. Smart card providers usually do not allow
        exportable keys.

And several useful examples:

New-SelfsignedCertificateEx -Subject "CN=Test Code Signing" -EKU "Code Signing" -KeySpec "Signature" `
-KeyUsage "DigitalSignature" -FriendlyName "Test code signing" -NotAfter [datetime]::now.AddYears(5)

Creates a self-signed certificate intended for code signing and which is valid for 5 years. Certificate is saved in the Personal store of the current user account.

New-SelfsignedCertificateEx -Subject "CN=www.domain.com" -EKU "Server Authentication", "Client authentication" `
-KeyUsage "KeyEcipherment, DigitalSignature" -SAN "sub.domain.com","www.domain.com","192.168.1.1" `
-AllowSMIME -Path C:\test\ssl.pfx -Password (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force) -Exportable `
-StoreLocation "LocalMachine"

Creates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the certificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable, so you can export the certificate with a associated private key to a file at any time. The certificate includes SMIME capabilities.

New-SelfsignedCertificateEx -Subject "CN=www.domain.com" -EKU "Server Authentication", "Client authentication" `
-KeyUsage "KeyEcipherment, DigitalSignature" -SAN "sub.domain.com","www.domain.com","192.168.1.1" `
-StoreLocation "LocalMachine" -ProviderName "Microsoft Software Key Storae Provider" -AlgorithmName ecdh_256 `
-KeyLength 256 -SignatureAlgorithm sha256

Creates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the certificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable, so you can export the certificate with a associated private key to a file at any time. Certificate uses Elliptic Curve Cryptography (ECC) key algorithm ECDH with 256-bit key. The certificate is signed by using SHA256 algorithm.

New-SelfsignedCertificateEx -Subject "CN=Test Root CA, OU=Sandbox" -IsCA $true -ProviderName `
"Microsoft Software Key Storage Provider" -Exportable

Creates self-signed root CA certificate.

Here is the code of the script. If you don’t want to read all this mess, scroll down and download ready file:

#####################################################################
# New-SelfSignedCertificateEx.ps1
# Version 1.0
#
# Creates self-signed certificate. This tool is a base replacement
# for deprecated makecert.exe
#
# Vadims Podans (c) 2013
# http://en-us.sysadmins.lv/
#####################################################################
#requires -Version 2.0

function New-SelfSignedCertificateEx {
[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(ParameterSetName = '__store')]
        [Security.Cryptography.X509Certificates.StoreName]$StoreName = "My",
        [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 | %{
            $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 -eq "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})" | ?{$_} | %{[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
        }
    }
}

HTH

Certificate Enrollment Web Service (CES) doesn’t work correctly on Windows Server 2012

Update 14.03.2013: added workaround information


SYMPTOMS

Consider the following scenario. You install and configure Certificate Enrollment Web Service (CES) against a Certification Authority (CA) that has spaces and other disallowed by HTML URL scheme characters in the certificate name. When you attempt to use the service for certificate enrollment, the following message appears:

image

STATUS: Failed. The endpoint address URL is invalid.

Additionally, 2 events are registered in the Application eventlog:

EventID=10000 from CertificationAuthorityClient-CertCli source:

Certificate Enrollment Server https://www.company.com/My Test CA-1_CES_Kerberos/service.svc/CES was not able to process the request: The endpoint address URL is invalid. 0x803d0020 (-2143485920)

EventID=13 from CertificateServicesClient-CertEnroll source:

Certificate enrollment for Local system failed to enroll for a OfflineOCSPResponseSigning certificate with request ID N/A from https://www.company.com/My Test CA-1_CES_Kerberos/service.svc/CES (The endpoint address URL is invalid. 0x803d0020 (-2143485920)).


CAUSE

This issue is caused because Certificate Enrollment Web Service (CES) URL is not properly escaped. Spaces and other special characters must be escaped in the HTTP URL.

For example, you configure CES to work with Certification Authority (CA) named “My Test CA-1” and use Kerberos for authentication. The following URL is generated by CES installer (host name may be different):

  • https://www.company.com/My Test CA-1_CES_Kerberos/service.svc/CES

While the correct URL is:

  • https://www.company.com/My%20Test%20CA-1_CES_Kerberos/service.svc/CES

STATUS

Microsoft is informed about this issue. No bug fixes are available. See Workaround section for example steps to overcome the issue.


RESOLUTION

No resolution available.


WORKAROUND

You can use the following PowerShell scripts to:

1) Update URL in IIS server. The caller must be granted local Administrator permissions:

# import required assemblies and modules
Add-Type -AssemblyName System.Web
Import-Module WebAdministration
# retrieve Certificate Enrollment Service applications on the server
$Apps = Get-WebApplication | Where-Object {$_.physicalpath.StartsWith("$($env:windir)\SystemData\CES")}
if (!$Apps) {return}
# loop over each CES app
foreach ($app in $Apps) {
    # retrieve application settings
    $webConfigStore = [Web.Configuration.WebConfigurationManager]::OpenWebConfiguration($app.path)
    # retireve CES URL
    $url = $webConfigStore.AppSettings.Settings["uri"].Value
    if (!$url) {return}
    # encode and write back new URL to application settings
    $webConfigStore.AppSettings.Settings["uri"].Value = [web.httputility]::UrlPathEncode($url)
    # save changes in IIS
    $webConfigStore.Save("Modified")
}
# restart IIS service
Get-Service IISADMIN | Restart-Service

2) Update CES URL in Active Directory. The caller must be granted either Enterprise Admins permissions or delegated permissions to manage Public Key Infrastructure container:

Add-Type -AssemblyName System.Web
# Connect to Enrollment Services container in configuration naming context
$RootDSE = [ADSI]"LDAP://RootDSE"
$ConfigContext = "CN=Enrollment Services,CN=Public Key Services,CN=Services," + $RootDSE.configurationNamingContext
$adsi = [ADSI]"LDAP://$ConfigContext"
$adsi.psbase.Children | ForEach-Object {
    if (!$_) {return}
    # prepare an array for new (fixed) URLs
    $entries = @()
    # enumerate all CES URLs for the current CA server
    $_."msPKI-Enrollment-Servers" | ForEach-Object {
        if (!$_) {return}
        # retrieve priority, authentication and renewal settings. They are not changed.
        $priority = $_[0].ToString()
        $authentication = $_[2].ToString()
        $RenewalOnly = $_[4].ToString()
        # extract current URL. We use regexp to grab only required part
        if ($_ -match "https.+service\.svc/CES") {
            $url = $matches[0]
            # encode extracted URL if necessary. If no encoding is required, the URL is not changed
            $url = [web.httputility]::UrlPathEncode($url)
            # build final entry for current CES entry and add it to array
            $entries += $priority + "`n" + $authentication + "`n" + $RenewalOnly + "`n" + $url
        }
    }
    # write encoded URLs back to Active Directory
    $_."msPKI-Enrollment-Servers" = $entries
    $_.SetInfo()
}

For manual URL change, use the steps provided in the following TechNet wiki article: Implementing Certificate Enrollment Web Services in Windows Server® 2012 that uses an Issuing CA with spaces in the name.


APPLIES TO

  • Windows Server 2012 (all editions)
Understanding Active Directory Certificate Services containers in Active Directory

Hello folks! Today I want to explain in details about Active Directory containers related to ADCS (Active Directory Certificate Services), their purposes and how they work.

Intro

All ADCS related containers are stored in configuration naming context under Public Key Services container:

CN=Public Key Services, CN=Services, CN=Configuration, DC={forest root domain}

Since Public Key Services container is stored in configuration naming context, any it’s content is replicated between all domain controllers in the current forest (not only current domain) and are available to any client in the forest. This means that there is no way to limit PKI containers only to specific domain or domains.

Here is a screenshot from ADSIEdit.msc tool:

PKI containers in ADSI Editor

Under Public Key Services container you find the following subcontainers:

  • AIA
  • CDP
  • Certificate Templates
  • Certification Authorities
  • Enrollment Services
  • KRA
  • OID

and the following entry (not a container):

  • NTAuthCertificates

And here a description of each container.

AIA

This container is used to store intermediate CA certificates and cross-certificates. This container may contain entries of certificateAuthority type. CA certificates are written to cACertificate attribute and cross-certificates are written to crossCertificatePair attribute.

When you install new Enterprise CA, it’s certificate is automatically installed to AIA container.

you can programmatically install CA certificate to this container by running the following certutil.exe command:

certutil –dspublish –f <PathToCertFile.cer> SubCA

Replace <PathToCertFile.cer> with actual path and certificate name file.

All certificates from this container are propagated to each client as a part of group policy processing to client’s Intermediate Certification Authorities container.

CDP

This container is used to store certificate revocation lists (CRL). To differentiate CRLs a separate container is created for each CA. Typically CA host NetBIOS name is used. For example, if CA server runs on a computer ca01.company.com, the following path is created for that CA:

CN=ca01, CN=CDP, CN=Public Key Services, CN=Services, CN=Configuration, DC={forest root domain}

And this container may contain records of cRLDistributionPoint type. Base CRL is written to certificateRevocationList attribute. Delta CRLs (multiple delta CRLs are allowed) are written to deltaRevocationList attribute.

When you install new Enterprise CA, it automatically publishes first CRLs to CDP container.

you can programmatically install certificate revocation list to this container by running the following certutil.exe command:

certutil –dspublish –f <PathToCRLFile.crl> <SubcontainerName>

Replace <PathToCertFile.cer> with actual path and certificate name file. And replace <SubcontainerName> with required name. Usually subcontainer name is CA host short (NetBIOS) name.

CRLs from CDP containers are NOT propagated to clients and is used only when a certificate refers to a particular cRLDistributionPoint entry in CDP container.

Certificate Templates

This container contains enterprise certificate templates used by Enterprise CAs. You should not edit templates directly. Consider to use Certificate Templates (certtmpl.msc) MMC snap-in for template management.

Certification Authorities

This container is used to store trusted root certificates. This container may contain entries of certificateAuthority type. CA certificates are written to cACertificate attribute.

When you install Enterprise Root CA, it’s certificate is automatically installed to Certification Authority container.

you can programmatically install Root CA certificate to this container by running the following certutil.exe command:

certutil –dspublish –f <PathToCertFile.cer> RootCA

Replace <PathToCertFile.cer> with actual path and certificate name file. Note that a copy of root CA certificate is also installed in AIA container too.

All certificates from this container are propagated to each client as a part of group policy processing to client’s Trusted Root Certification Authorities container.

Enrollment Services

This container is used to store Enterprise CA objects. Clients use this container to locate Enterprise CAs in the forest.

When you install new Enterprise CA, a new record in the Enrollment Services container is created.

All certificates from this container are propagated to each client as a part of group policy processing to client’s Intermediate Certification Authorities container. Also, this container is enumerated during certificate enrollment process.

KRA

This container is used to store key recovery agent certificates for each Enterprise CA. When Enterprise CA issues a certificate based on Key Recovery Agent template, it automatically adds it to a corresponding entry. If this is first KRA certificate, CA creates a record for itself and writes KRA certificate to userCertificate attribute.

Certificates from KRA container are exposed only when you assign new key recovery agent to CA server.

OID

This container is used to store object identifiers (OID) registered in enterprise. OID container can hold object identifier definitions for custom Application Policies, Issuance (Certificate) Policies and certificate templates. When client is a member of the Active Directory forest, it uses OID container to resolve object identifiers along with local OID database.

New OIDs should be registered via Certificate Templates (certtmpl.msc) MMC snap-in by adding new Application or Issuance (Certificate) Policy in certificate template Extension tab.

Alternatively, you can use PowerShell PKI module which contains commands to add or remove OID from Active Directory: Get-ObjectIdentifierEx, Register-ObjectIdentifier and Unregister-ObjectIdentifier.

NTAuthCertificates

This entry is used to store certificates for CAs that are eligible to issue smart card logon certificates. During smart card logon, domain controller checks whether issuer is presented in the NTAuthCertificates entry. If it doesn’t, the logon attempt is denied immediately.

you can programmatically install CA certificate to this container by running the following certutil.exe command:

certutil –dspublish –f <PathToCertFile.cer> NTAuthCA

Replace <PathToCertFile.cer> with actual path and certificate name file.

When you install new Enterprise CA, its certificate is added to NTAuthCertificates entry. Note that a copy of CA certificate is also installed in AIA container too.

All certificates from this container are propagated to each client as a part of group policy processing to client’s Intermediate Certification Authorities container.

Alternate AD container management options

Although, ADSIEdit.msc allows you to view and edit extended details of the Public Key Services container, it is not very user-friendly and cannot render binary data (certificates and CRLs) in UI. To view container contents in UI you can use PKI Health Monitor (PKIView.msc) tool.

  1. Log on the computer where ADCS management tools (RSAT) are installed, run the PKIView.msc console.
  2. In the opened console, select top node named Enterprise PKI.
  3. Click Action menu and select Manage AD Containers.

Manage AD Containers in PKIView.msc

In this window you can view and delete entries for all containers, except Certificate Templates and OID.

Also, this tool allows you to add CA certificates only to NTAuthCertificates containers. To add certificates or CRLs to other containers (AIA, CDP, Certification Authorities) you should use certutil.exe tool as described above.

Permissions

By default only members of Enterprise Admins group have permissions to modify the contents of the Public Key Services. If necessary, you can delegate appropriate permissions to other user or group (must be either global or universal) via ADSIEdit.msc tool.

PowerShell Cmdlet Help Editor on CodePlex

Just let you know, it is pushed (with sources) to CodePlex.

>> Welcome <<

PowerShell Cmdlet Help Editor 2.0

Finally!!!

Abstract

As you already know, last time I worked on my next PowerShell PKI module and encountered in an issue with Microsoft’s Cmdlet Help Editor. When I tried to open my module (and any other built-in module) I got very nice message:

image

Ok, I tried to download sources (thanks, they are available for download) and was stuck with WPF. I heard that WPF is a modern replacement for WinForms, and that was the only what I knew about WPF. I was able to fix mentioned message issue, but failed with application layout. The form has fixed size and no scroll bars. So I couldn’t access textboxes and commands which are outside of my screen (even if I worked on a 1680*1050 display). And I decided to create my own Help Editor with with “blackjack and hookers”.

How it was

The first question was: what to use, WinForms or modern super-puper WPF. I have a very little experience in WinForms (example: Bulk file signing with PowerShell and user interface), so in any way I had to learn any of them. Eventually WPF won.

I started new clean WPF project in my visual studio and tried to make simple tasks: place button, attach events and get the result. Not enough for solid project, but in any way. Step-by-step I dived deeper and deeper. I learned containers, controls, their properties, features, how they can be combined.

The first solid achievement for me was user control usage. I just wrote simple user control and added it to my main window. It was epic WIN! The next win was DockPanel. I figured out how to dock controls within a window. I used it to develop about 10 user controls and they are logically added to my form and loaded/unloaded on events.

After I got very basic UI (yet no scrollbars, just a set of textboxes, textblocks and listview controls), I started with object model. Just created a set of base classes to represent cmdlet object and it’s help content. Then I figured out how to bind collection to a ListView/ListBox. The next step was ListView/ListBox display change when the data was changed. In about 1 day I figured such things as Observable Collections and INotifyPropertyChange event implementation. Another win.

After a time I got almost usable UI with a ton of stubs in code-behind. This week I (assuming, today is sunday) got a very nice article about Model-View-ViewModel design pattern and bindings. Also another good guy Sergey Zwezdin (who is a friend of mine and ASP.NET MVP) explained me some advanced stuff about how to work with data bindings, dependency properties (this was the most complex thing for me and couldn’t figure it myself) and relay commands. Another several days were spent to rewrite the code. Create View Model classes and move all the code from code-behind to view Model files. It was crazy, almost everything in my application works via direct bindings rather than code-behind event handlers. This is the most major WIN.

When I started to learn WPF, I hated it, because everything there was unobvious and quite complex for me. After each (even very little) win I liked WPF more and more. By now, I can tell that I like WPF more than WinForms. Fact.

Finally I attached XML Reader and XML Writer (as PowerShell uses XML help format) from original cmdlet help editor (though, most code parts were optimized and rewrited in a more convenient way). And this weekend I ended with UX details (buttons, menu’s toolbars, behaviors and other things).

Even though the mainstream UI is Metro UI, I always liked Office 2003 UI style and finished with it. If you don’t like it, then you just can ignore it and write your own help.

How it works

When you start the editor, it will load modules in background (without locking current UI thread):

image

Once modules are loaded, they are displayed in the ListView (in a way they looked in original help editor):

image

you can either open existing help file or create a new help file. After module commands are loaded (again, they are loaded in background), you will see main editor UI:

image

I decided to use TabView control to separate help sections, as it is easier to implement and doesn’t look worse than TreeView in original help editor. One important thing. For Parameters, Examples and Related Links tabs I added ListView controls where you have to select an item you want to edit:

image

In the Example and Related Links tab I added two useful buttons which you can use to move selected example up or down:

image

Also, I want to note you that you can resize panels:

image

I used GridSplitter control to add an ability to extend cmdlet list in the case if you have long command names and you don’t want to scroll them all the time. Each panel has separate scrollbar, so there should not be any inaccessible element.

End Titles

Currently I don’t have a project page (and even not sure if I need it), so you can download the editor from my SkyDrive:

>> Download PowerShell Help Editor <<

 

Note: this application requires .NET 4.5 Framework.

Even though I don’t have project page, I’m accepting feature requests and bugs in comments, or via email. Any feedback is highly appreciated.

Credits are going to Sergey Zwezdin, Wassim Fayed and Google (really, I learned WPF through google search). Enjoy!

1 - 5 Next