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
- A KB article entitled “How to enable LDAP over SSL with a third party certification authority“
- 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:
- Start the Active Directory Administration Tool (Ldp.exe).
- On the Connection menu, click Connect.
- Type the name of the domain controller to which you want to connect.
- Type 636 as the port number.
- Click OK
Update: I’ve just updated the adLDAP Wiki documentation to reflect this.