So I ‘m in the process of deploying SCOM 2007 R2 on several computers. Many of these are not members of the domain where SCOM is running and several of them are not even placed in our datacenter. I created a (I think) simple guide on how to install our root CA cert, request a computer certificate, install SCOM tell SCOM to use the certificate, patch it, and accept the client.
Apparently that isn't *that* easy again. The whole certificate thing was taking the most time, so I’ve looked into a way to automate this process. Playing around with different scenarios I stumbled across this blog post. Later while mocking up some code to facilitate it, I also stumbled across this post by same guy. I agree with what he's saying about where to generate key etc. but I was tasked with automating the process so here is my “modified” version. I stripped out authentication and various checks, to simplify it all, for anyone who wants to go down the same road as me. I run the PowerShell script he wrote (rewritten to VB.NET) then submit the BASE64 certificate request to a web service. I then send the request to our internal CA, and then send back the certificate. Then the VB.NET code installs the certificate in the local computer store.
First up, add a cert template according to he's guide here. Next a small console application that will be run at the client. You need to add a reference to certcli.dll and CertEnroll.dll
Then add this class
Imports CERTENROLLLib
Imports CERTCLIENTLib
Imports System.Security.Cryptography.X509Certificates
Public Class SCOMCertWrapper
Sub ImportPfxCertificate(ByVal filename As String, ByVal Store As StoreName, ByVal location As StoreLocation, ByVal password As String)
Dim pfxcert As New X509Certificate2
pfxcert.Import(filename, password, X509KeyStorageFlags.Exportable And X509KeyStorageFlags.PersistKeySet)
Dim X509Store As New X509Store(Store, location)
X509Store.Open(OpenFlags.MaxAllowed)
X509Store.Add(pfxcert)
X509Store.Close()
End Sub
Sub import509Certificate(ByVal filename As String, ByVal Store As StoreName, ByVal location As StoreLocation)
Dim pfxcert As New X509Certificate2
pfxcert.Import(filename)
Dim X509Store As New X509Store(Store, location)
X509Store.Open(OpenFlags.MaxAllowed)
X509Store.Add(pfxcert)
X509Store.Close()
End Sub
Function CreateRequest(ByVal DNSName As String, ByVal CertificateTemplate As String) As String
' create certificate Subject field in X500 Distinguished Name format
Dim SubjectDN As New CX500DistinguishedName
SubjectDN.Encode("CN=" & DNSName, X500NameFlags.XCN_CERT_NAME_STR_NONE)
Dim OIDs As New CObjectIds
' add created OIDs to EnchancedKeyUsages certificate extension
Dim OID As New CObjectId
OID.InitializeFromValue("1.3.6.1.5.5.7.3.1")
OID = New CObjectId
OID.InitializeFromValue("1.3.6.1.5.5.7.3.2")
OIDs.Add(OID)
' add created OIDs to EnchancedKeyUsages certificate extension
Dim EKU As New CX509ExtensionEnhancedKeyUsage
EKU.InitializeEncode(OIDs)
' generate private key
Dim PrivateKey As New CX509PrivateKey
PrivateKey.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
' the private key will be used by computer account
PrivateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE
' the private key is supposed for Key Encipherment
PrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES
PrivateKey.Length = 2048
PrivateKey.MachineContext = True
PrivateKey.Create()
' create certificate request template
Dim PKCS10 As New CX509CertificateRequestPkcs10
PKCS10.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, PrivateKey, "")
' add necessary fields to certificate request template
PKCS10.Subject = SubjectDN
If CertificateTemplate <> "" Then
Dim template As New CX509ExtensionTemplateName
template.InitializeEncode(CertificateTemplate)
PKCS10.X509Extensions.Add(template)
Else
PKCS10.X509Extensions.Add(EKU)
End If
' generate request file
Dim Request As New CX509Enrollment
Request.InitializeFromRequest(PKCS10)
' certificate request will be saved in Base64 format with request header and footer
'Dim Base64 As String = Request.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER)
Dim Base64 As String = Request.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64)
Return Base64
'strRequest = objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64)
End Function
Private Const CC_DEFAULTCONFIG As Integer = 0
Private Const CC_UIPICKCONFIG As Integer = &H1
Private Const CR_IN_BASE64 As Integer = &H1
Private Const CR_IN_FORMATANY As Integer = 0
Private Const CR_IN_PKCS10 As Integer = &H100
Private Const CR_DISP_ISSUED As Integer = &H3
Private Const CR_DISP_UNDER_SUBMISSION As Integer = &H5
Private Const CR_OUT_BASE64 As Integer = &H1
Private Const CR_OUT_CHAIN As Integer = &H100
' Submit request to CA and get response
Public Function submitRequest(ByVal CA As String, ByVal base64 As String) As String
' Create all the objects that will be required
Dim objCertConfig As CCertConfig = New CCertConfig()
Dim objCertRequest As CCertRequest = New CCertRequest()
Dim strCAConfig As String
'Dim strRequest As String
Dim iDisposition As Integer
Dim strDisposition As String
Dim strCert As String
Try
If CA = "" Then
' Get CA config from UI
'strCAConfig = objCertConfig.GetConfig(CC_DEFAULTCONFIG);
strCAConfig = objCertConfig.GetConfig(CC_UIPICKCONFIG)
Else
strCAConfig = CA ' "AD01.int.wingu.dk\int-AD01-CA"
End If
' Submit the request
iDisposition = objCertRequest.Submit(CR_IN_BASE64 Or CR_IN_FORMATANY, base64, Nothing, strCAConfig)
' Check the submission status
If CR_DISP_ISSUED <> iDisposition Then
' Not enrolled
strDisposition = objCertRequest.GetDispositionMessage()
If CR_DISP_UNDER_SUBMISSION = iDisposition Then
' Pending
Console.WriteLine("The submission is pending: " & strDisposition)
Return ""
Else
Throw New Exception("The submission failed: " & strDisposition & vbCrLf & "Last status: " & objCertRequest.GetLastStatus().ToString())
' Failed
Console.WriteLine("The submission failed: " & strDisposition)
Console.WriteLine("Last status: " & objCertRequest.GetLastStatus().ToString())
Return ""
End If
End If
' Get the certificate
strCert = objCertRequest.GetCertificate(CR_OUT_BASE64 Or CR_OUT_CHAIN)
Return strCert
Catch ex As Exception
Throw ex
Console.WriteLine(ex.Message)
End Try
End Function
' Install response from CA
Public Sub accept(ByVal BASE64 As String)
' Create all the objects that will be required
Dim objEnroll As CX509Enrollment = New CX509Enrollment()
'Dim strCert As String
Try
'strCert = responseTextText
' Install the certificate
objEnroll.Initialize(X509CertificateEnrollmentContext.ContextMachine)
objEnroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedRoot, BASE64, EncodingType.XCN_CRYPT_STRING_BASE64, Nothing)
Console.WriteLine("Certificate installed!")
Catch ex As Exception
Throw ex
Console.WriteLine(ex.Message)
End Try
End Sub
End Class
In main() add
Module Module1
Function myFQDN() As String
Dim fqdn As String
Dim domainName As String = Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName
Dim hostname As String = Net.Dns.GetHostName()
If Not hostname.Contains(domainName) Then
fqdn = hostname & "." & domainName
Else
fqdn = hostname
End If
Return fqdn.ToLower
End Function
Sub Main()
Dim wsClientCertificate As New ClientCertificate.ClientCertificate
Dim fqdn As String = myFQDN()
Console.WriteLine("My hostname is: " & fqdn)
Console.WriteLine("Downloading Root CA certificate")
Dim webcli As New Net.WebClient
webcli.DownloadFile("https://somedomain/certnew.cer", "certnew.cer")
Console.WriteLine("Importing Root CA certificate to Trusted Root Certificate Authorities ")
Dim cer As New SCOMCertWrapper
cer.import509Certificate("certnew.cer", StoreName.AuthRoot, StoreLocation.LocalMachine)
Console.WriteLine("Creating a new certificate request for local computer: " & fqdn)
Dim base64req As String = cer.CreateRequest(fqdn, "OpsMgrAgentV2")
Console.WriteLine("Submitting certificate request to webservice.")
Dim base64cer As String = wsClientCertificate.submitRequest(base64req)
Console.WriteLine("importing certificate to local machine personal certificates")
cer.accept(base64cer)
Console.WriteLine("done.")
Console.ReadLine()
End Sub
End Module
create a web service, add references like in console application. place root CA somewhere accessible, and also add the class ScomCertWrapper
Public Function submitRequest(ByVal base64_request As String) As String
Dim cert As New MomCert
Dim base64_cert As String = cert.submitRequest("fqnd.of.your.CA\int-AD01-CA", base64_request)
Return base64_cert
End Function
If your really lazy you can also start a process in the client calling MOMCertImport.exe
I properly don’t need to point this out, but I’ll do it anyway. You need to impersonate a (or run the application pool as a ) user who have permission to Enroll certificates based on the template, you should also add some kind authentication on the web service.
If you don’t know what to type as parameter 1 to submitRequest. Set a line break at line 105 in the class, call it from a console application, without specifying CA and choose it from the list that will popup, then inspect strCAConfig and you’ll know what to supply.
Update: so testing this code, I started getting "The requested certificate template is not supported by this CA. 0x80094800". The error was generated at InitializeFromPrivateKey while doing the creating of the Certificate request. Turns out that the last parameter CertificateTemplate requires the Template to be in the clients cache. Well that will never happen we are doing this from non-domain members or foreign domain members. Just set the parameter to an empty string or Nothing and it will work, but then you need to remember to add the template name to the request. If you don’t supply either you will get “Denied by Policy Module 0x80094801, The request does not contain a certificate template extension or the CertificateTemplate request attribute.” when submitting the request to the CA.
Ingen kommentarer:
Send en kommentar