Installing a SSL certificate on your Domain Controller

We’ve recently had the need to install an SSL certificate on our Domain Controllers.  Without SSL you cannot change Active Directory passwords or create user accounts (with a password) over LDAP.

There are two Microsoft Documents that help this process along

  1. A KB article entitled “How to enable LDAP over SSL with a third party certification authority
  2. A TechNet article “Advanced Certificate Enrollment and Management

Certificate Authority

In the first instance, we are installing a standalone CA (certificate authority) and a seperate Windows Server 2008 system.

Generate the Request

On the Domain controller we create a file called request.inf

;----------------- request.inf -----------------
[Version]
Signature="$Windows NT$
[NewRequest]
Subject = "CN=<DC fqdn>" ; replace with the FQDN of the DC
KeySpec = 1
KeyLength = 1024
; Can be 1024, 2048, 4096, 8192, or 16384.
; Larger key sizes are more secure, but have
; a greater impact on performance.
Exportable = TRUE
MachineKeySet = TRUE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.1 ; this is for Server Authentication
;-----------------------------------------------

From the command line we then run

certreq -new request.inf request.req

A file called request.req is created

Change the Request Settings

We then copy the request.req file over to the CA.  This is where things get more complicated.

Again from the command line we need to run

certreq -attrib "CertificateTemplate:DomainController" request.req

This will give us a request ID, this is important, make a note of it.

The next stage mentions we need to copy the .asn file from the domain controller to the CA.  Hang on a moment, what .asn file and where is it?

Microsoft gloss over this and it took us around 2 hours to find the answer to this, it’s mentioned in Appendix 2, basically create and run this vbs script and it will create your .inf and your .asn files for you.

Set oArgs = WScript.Arguments
Set oShell = WScript.CreateObject("WScript.Shell")
'
' Parse command line
'
if oArgs.Count < 1 then
    sTemplateName = "DomainController"
    sType = "E"
else
    if ((oArgs(0) = "-?") or (oARgs.Count < 2)) then
        Wscript.Echo "Usage: reqdccert.vbs [Templatename] [Type]"
        Wscript.Echo "[Templatename] is the name of a V2 template"
        Wscript.Echo "[Type]         can be E for Email and A for Authentication certificate"
        Wscript.Echo "If no option is specified, the DomainController certificate template is used."
        Wscript.Quit 1
    else
        sTemplateName = oArgs(0)
                sType = oArgs(1)
    end if
end if
Set oFilesystem = CreateObject("Scripting.FileSystemObject")
Set objSysInfo = CreateObject("ADSystemInfo")
Set objDC = GetObject("LDAP://" & objSysInfo.ComputerName)
sGUID = objDC.GUID
sDNShostname = objDC.DNShostname
sHostname = objDC.cn
'##############################################################################
'
' Create the ASN.1 file
'
'##############################################################################
Dim aASNsubstring(2, 5)
Const HEX_DATA_LENGTH = 1
Const ASCIIDATA = 2
Const HEXDATA = 3
Const HEX_BLOB_LENGTH = 4
Const HEX_TYPE = 5
aASNsubstring(0, ASCIIDATA) = sDNShostname
aASNsubstring(0, HEX_TYPE) = "82"
'
' Convert DNS name into Hexadecimal
'
For i = 1 to Len(aASNsubstring(0, ASCIIDATA))
    aASNsubstring(0, HEXDATA) = aASNsubstring(0, HEXDATA) & _
                                    Hex(Asc(Mid(aASNsubstring(0, ASCIIDATA), i, 1)))
Next
aASNsubstring(0, HEX_DATA_LENGTH) = ComputeASN1 (Len(aASNsubstring(0, HEXDATA)) / 2)
'
' Build the ASN.1 blob for DNS name
'
sASN = aASNsubstring(0, HEX_TYPE) & _
       aASNsubstring(0, HEX_DATA_LENGTH) & _
       aASNsubstring(0, HEXDATA)
'
' Append the GUID as other name
'
if (sType = "E") then
    aASNsubstring(1, HEXDATA) = sGUID
    aASNsubstring(1, HEX_TYPE) = "A0"
    aASNsubstring(1, HEX_DATA_LENGTH) = ComputeASN1 (Len(aASNsubstring(1, HEXDATA)) / 2)
    sASN = sASN & _
           "A01F06092B0601040182371901" & _
           aASNsubstring(1, HEX_TYPE) & _
           "120410" & _
           aASNsubstring(1, HEXDATA)
end if
'
' Write the ASN.1 blob into a file
'
Set oFile = oFilesystem.CreateTextFile(sHostname & ".asn")
'
' Put sequence, total length and ASN1 blob into the file
'
oFile.WriteLine "30" & ComputeASN1 (Len(sASN) / 2) & sASN
oFile.Close
'
' Use certutil to convert the hexadecimal string into bin
'
oShell.Run "certutil -f -decodehex " & sHostname & ".asn " & _
                                       sHostname & ".bin", 0, True
'
' Use certutil to convert the bin into base64
'
oShell.Run "certutil -f -encode " & sHostname & ".bin " & _
                                    sHostname & ".b64", 0, True
'##############################################################################
'
' Create the INF file
'
'##############################################################################
Set iFile = oFilesystem.OpenTextFile(sHostname & ".b64")
Set oFile = oFilesystem.CreateTextFile(sHostname & ".inf")
oFile.WriteLine "[Version]"
oFile.WriteLine "Signature= " & Chr(34) & "$Windows NT$" & Chr(34)
oFile.WriteLine ""
oFile.WriteLine "[NewRequest]"
oFile.WriteLine "KeySpec = 1"
oFile.WriteLine "KeyLength = 1024"
oFile.WriteLine "Exportable = TRUE"
oFile.WriteLine "MachineKeySet = TRUE"
oFile.WriteLine "SMIME = FALSE"
oFile.WriteLine "PrivateKeyArchive = FALSE"
oFile.WriteLine "UserProtected = FALSE"
oFile.WriteLine "UseExistingKeySet = FALSE"
oFile.WriteLine "ProviderName = " & Chr(34) & _
                "Microsoft RSA SChannel Cryptographic Provider" & Chr(34)
oFile.WriteLine "ProviderType = 12"
oFile.WriteLine "RequestType = PKCS10"
oFile.WriteLine "KeyUsage = 0xa0"
oFile.WriteLine ""
oFile.WriteLine "[EnhancedKeyUsageExtension]"
oFile.WriteLine "OID=1.3.6.1.5.5.7.3.1"
oFile.WriteLine "OID=1.3.6.1.5.5.7.3.2"
oFile.WriteLine ";"
oFile.WriteLine "; The subject alternative name (SAN) can be included in the INF-file"
oFile.WriteLine "; for a Windows 2003 CA."
oFile.WriteLine "; You don't have to specify the SAN when submitting the request."
oFile.WriteLine ";"
oFile.WriteLine "[Extensions]"
iLine = 0
Do While iFile.AtEndOfStream <> True
    sLine = iFile.Readline
    If sLine = "-----END CERTIFICATE-----" then
        Exit Do
    end if
    if sLine <> "-----BEGIN CERTIFICATE-----" then
        if iLine = 0 then
            oFile.WriteLine "2.5.29.17=" & sLine
        else
            oFile.WriteLine "_continue_=" & sLine
        end if
        iLine = iLine + 1
    end if
Loop
oFile.WriteLine "Critical=2.5.29.17"
oFile.WriteLine ";"
oFile.WriteLine "; The template name can be included in the INF-file for any CA."
oFile.WriteLine "; You don't have to specify the template when submitting the request."
oFile.WriteLine ";"
oFile.WriteLine ";[RequestAttributes]"
oFile.WriteLine ";CertificateTemplate=" & sTemplateName
oFile.Close
iFile.Close
'##############################################################################
'
' Create the certreq.exe command-line to submit the certificate request
'
'##############################################################################
Set oFile = oFilesystem.CreateTextFile(sHostname & "-req.bat")
oFile.WriteLine "CERTREQ -attrib " _
                 & Chr(34) & "CertificateTemplate:" & sTemplateName _
                 & Chr(34) & " " & sHostname & ".req"
'
' The GUID structure needs to be reconstructed. The GUID is read
' as a string like f4aaa8576e6828418712b6ca89fbf5bc however the
' format that is required for the certreq command looks like
' 57a8aaf4-686e-4128-8712-b6ca89fbf5bc. The bytes are reordered
' in the following way:
'
'                            11111111112222222222333
'             Position 12345678901234567890123456789012
'                      |------|--|--|--|--------------|
' Original GUID:       f4aaa8576e6828418712b6ca89fbf5bc
'
'                            11 1 1111 1112 222222222333
'             Position 78563412 1290 5634 7890 123456789012
'                      |------- |--- |--- |--- |----------|
' Reformatted GUID:    57a8aaf4-686e-4128-8712-b6ca89fbf5bc
'
oFile.WriteLine "REM "
oFile.WriteLine "REM !!! Only valid for Windows 2003 or later versions !!!"
oFile.WriteLine "REM If you do not specify certificate extensions in the *.INF file"
oFile.WriteLine "REM they can be specified here like the following example"
oFile.WriteLine "REM "
oFile.WriteLine "REM CERTREQ -submit -attrib " _
                 & Chr(34) & "CertificateTemplate:" & sTemplateName _
                 & "\n" _
                 & "SAN:guid=" _
                 & Mid(sGUID, 7, 2) _
                 & Mid(sGUID, 5, 2) _
                 & Mid(sGUID, 3, 2) _
                 & Mid(sGUID, 1, 2) & "-" _
                 & Mid(sGUID, 11, 2) _
                 & Mid(sGUID, 9, 2) & "-" _
                 & Mid(sGUID, 15, 2) _
                 & Mid(sGUID, 13, 2) & "-" _
                 & Mid(sGUID, 17, 4) & "-" _
                 & Mid(sGUID, 21, 12) _
                 & "&DNS=" & sDNShostname & Chr(34) & " " & sHostname & ".req"
oFile.Close
'##############################################################################
'
' Create the certificate verification script
'
'##############################################################################
Set oFile = oFilesystem.CreateTextFile(sHostname & "-vfy.bat")
oFile.WriteLine "certutil -viewstore " & Chr(34) & objDC.distinguishedname & _
                "?usercertificate" & chr(34)
oFile.Close
'##############################################################################
'
' Compute the ASN1 string
'
'##############################################################################
Function ComputeASN1 (iStrLen)
    If Len(Hex(iStrLen)) Mod 2 = 0 then
        sLength = Hex(iStrLen)
    else
        sLength = "0" & Hex(iStrLen)
    end if
    if iStrLen > 127 then
        ComputeASN1 = Hex (128 + (Len(sLength) / 2)) & sLength
    else
        ComputeASN1 = sLength
    End If
End Function

So now we have generated a .req and a .asn file we can copy the .asn to the CA.  We actually used the .inf file created from this VBS script, but the contents look similar enough.

certutil -setextension <RequestID> 2.5.29.17 1 @<dcname>.asn

What Microsoft also doesn’t tell you here, is that if you are using Windows Server 2008, this command will not work unless you have loaded the command prompt with elevated priviledges (UAC). The subject alternative name, which is identified by the object identifier 2.5.29.17, is set with the attributes that are defined in the <dcname>.asn file. The fourth parameter that is set to “1” marks the extension as critical.

The TechNet ‘Processing Domain Controller Certificates‘ article mentions how to validate the certificate request is good, however we are assuming these tests come back as positive so next we need to issue and retrieve the certificate.

Retrieve the Certificate

From the CA now run

certutil –resubmit <RequestID>

and then finally

CERTREQ -retrieve <RequestID> <dcname>.cer <dcname>.p7b

You will now have a .cer and a .p7b file which you can copy back to the domain controller.

Install the Certificate

To install the certificate, from the command line on the domain controller

CERTREQ -ACCEPT <dcname>.p7b

Microsoft tells you to restart the Domain Controller.  In our experience this was not necessary and SSL over LDAP was available immediately

Test LDAP over SSL

After a certificate is installed, follow these steps to verify that LDAPS is enabled:

  1. Start the Active Directory Administration Tool (Ldp.exe).
  2. On the Connection menu, click Connect.
  3. Type the name of the domain controller to which you want to connect.
  4. Type 636 as the port number.
  5. Click OK

Update:  I’ve just updated the adLDAP Wiki documentation to reflect this.

Leave a Reply