mandag den 7. marts 2011

SCOM / mom certificates on non domain members

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

'  Add the CertEnroll namespace
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)
    End Sub

    Sub import509Certificate(ByVal filename As String, ByVal Store As StoreName, ByVal location As StoreLocation)
        Dim pfxcert As New X509Certificate2
        Dim X509Store As New X509Store(Store, location)
    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 = New CObjectId
        ' add created OIDs to EnchancedKeyUsages certificate extension
        Dim EKU As New CX509ExtensionEnhancedKeyUsage

        ' 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

        ' 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
        End If

        ' generate request file

        Dim Request As New CX509Enrollment
        ' 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

            If CA = "" Then
                ' Get CA config from UI
                'strCAConfig = objCertConfig.GetConfig(CC_DEFAULTCONFIG);
                strCAConfig = objCertConfig.GetConfig(CC_UIPICKCONFIG)
                strCAConfig = CA ' "\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 ""
                    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
        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

            'strCert = responseTextText

            ' Install the certificate
            objEnroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedRoot, BASE64, EncodingType.XCN_CRYPT_STRING_BASE64, Nothing)

            Console.WriteLine("Certificate installed!")
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

End Class

In main() add

Imports System.Security.Cryptography.X509Certificates

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
            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")


    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

<WebMethod()> _
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 Smile

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