Формирование запроса и выдача сертификата на PowerShell

8 13

Нашел в своих закромах одну вещь и решил поделиться.

Когда-то давно, когда я сопровождал систему DIRECTUM ранней версии (вроде это было на 4.5) на предыдущем месте работы, у меня была одна из задач – выдавать сертификаты для подписи пользователям.

Эта задача тогда была никак не автоматизирована, и все делалось вручную. А именно (по памяти):

  • открыть портал сервера сертификатов,
  • формирование запроса,
  • заполнение необходимых полей (ФИО, подразделение и т.д.),
  • выбор типа сертификата
  • генерация сертификата
  • установка в личное хранилище
  • экспорт открытого ключа
  • установка открытого ключа пользователю в DIRECTUM

Мне тогда был интересен PowerShell и возникла идея это автоматизировать как раз на нем. В итоге после, чтения многочисленных мануалов был рожден скрипт, который в принципе делает тоже самое, что я делал руками, но плюс отправляет закрытый ключ пользователю в задаче по DIRECTUM.


#путь до СА
$ServerCA = "server-ibm\CC"

#путь для экспорта *.cer *.pfx
$Path = "\\server-ibm\CertConfig\" 

#установка значений
$ContextUser = 0x1

$XCN_AT_SIGNATURE = 0x2
$XCN_CERT_NAME_STR_NONE = 0x0
$XCN_CERT_NAME_STR_NO_QUOTING_FLAG = 0x10000000
$XCN_NCRYPT_ALLOW_EXPORT_FLAG = 0x1

$XCN_CRYPT_STRING_BASE64 = 0x1
$XCN_CRYPT_STRING_BINARY = 0x2
        
$CR_IN_FORMATANY = 0x0
$CR_IN_BASE64 = 0x1
$CR_IN_BINARY = 0x2
        
$CR_OUT_BASE64 = 0x1
$CR_OUT_BINARY = 0x2
        
$AllowUntrustedCertificate = 0x2        

$CERT_DATA_ENCIPHERMENT_KEY_USAGE    = 0x10
$CERT_KEY_ENCIPHERMENT_KEY_USAGE     = 0x20
$CERT_NON_REPUDIATION_KEY_USAGE      = 0x40
$CERT_DIGITAL_SIGNATURE_KEY_USAGE    = 0x80

$CR_DISP_ISSUED = 3

$SYSREQ_USERS_USER_CERTIFICATE = "ТекстТ2" # Сертификат 
$SYSREQ_USERS_USER_CERTIFICATE_INFO = "ISBCertificateInfo" # Информация о сертификате 
$SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_NAME = "ISBStartObjectName" # Модуль шифрования 
$SYSREQ_USERS_USER_CERTIFICATE_STATE = "СостояниеТ2" # Состояние сертификата 
$SYSREQ_USERS_USER_CERTIFICATE_SUBJECT_NAME = "СтрокаТ2" # Кому выдан сертификат 
$SYSREQ_USERS_USER_CERTIFICATE_THUMBPRINT = "СодержаниеТ2" # ИД сертификата
$SYSREQ_USERS_USER_DEFAULT_CERTIFICATE = "ISBDefaultCert" # Признак сертификата по умолч.
 
$CERTIFICATE_TYPE_ENCRYPT = "Шифрование"
$CERTIFICATE_TYPE_SIGN = "ЭЦП"
$CERTIFICATE_TYPE_SIGN_AND_ENCRYPT = "ЭЦП и шифрование"

$PluginName = "{B1B27433-D685-47F8-8500-CF9525407145}" #CAPICOM

$vmSelect = 1
$mrOK = 1
$dseBeforeInsert = 9
$ctReference = 1
$waPerformers = 1
$jkJob = 0
$ActiveState = "Действующая"

#получаем COM-объект DIRECTUM
$d_Logon = New-Object -ComObject SBLogon.LoginPoint
$d_App = $d_Logon.GetApplication("SystemCode=DIRECTUM")

#открываем справочник пользователей для мульти-выбора
#$pol = $d_App.ReferencesFactory.ReferenceFactory("ПОЛ").GetComponent
$pol = $d_App.GetComponent($ctReference,"ПОЛ") 
$Form = $pol.ComponentForm
$View = $Form.View
$Form.View.Viewmode = $vmSelect
$Form.View.MultiSelection = $True
$View.Viewmode = $vmSelect
$View.MultiSelection = $True

$Form.ShowModal()
$SelectionResult = $Form.Result

#если выбрали пользователя(ей)
if ($mrOK -eq $SelectionResult)
{
    $i = 0
    
    #проходимся по ним
    while ($i -le $View.SelectedRecordCount - 1)
    {
      $id = $View.SelectedRecordsID($i)
      if ($pol.Locate("ИД", $id))
      {
        #получаем IUSER
        $User = $d_App.ServiceFactory.GetUserByID($id)
        #и его данные
        $FIO = $User.FullName
        $UserCode = $User.Code
        $UserName = $User.Name
        $UserID = $User.ID
        
        $Subject = 'CN='+$FIO
#        <#

        #получаем подразделение, Email
        $Department = ""
        $Email = ""
        $PersonCode = ""
        $PersonName = ""
        $Emp = $d_App.GetComponent($ctReference,"РАБ")
        $EmpWhere = $Emp.AddWhere("Polzovatel = " + $UserID + " and MBAnalit.Sost = 'Д'")
        $Emp.Open()
                
        #if ($Emp.Locate(("Пользователь", "Состояние"), ($UserCode, $ActiveState)))
        if ($Emp.RecordCount -gt 0)
        {
          $Department = $Emp.Requisites("Подразделение").DisplayText          
          $PersonCode = $Emp.Requisites("Персона").asString 
          $PersonName = $Emp.Requisites("Персона").DisplayText 
          $PersonID = $Emp.Requisites("Персона").Field.asString 
          
          $Contacts = $d_App.GetComponent($ctReference,"КНТ")          
          if ($PersonID -ne $null)
          {
              $ContactsWhere = $Contacts.AddWhere("Persona = " + $PersonID + " and MBAnalit.Sost = 'Д'")
              $Contacts.Open()
          
          
              #if $Contacts.Locate(("Персона", "Состояние"), ($PersonCode, $ActiveState))
              if ($Contacts.RecordCount -gt 0)
              {
                $Email = $Contacts.Requisites('Строка2').AsString  
              }
              $ContactsWhere = $null
              $Contacts.Close()
          }
        }
        $EmpWhere = $null
        $Emp.Close()
        
        #формируем строку для запроса
        if ($Department -ne "") {$Subject = $Subject + ', OU='+$Department}
        if ($Email.trim() -ne "") {$Subject = $Subject + ', E='+$Email}
        
#        #>
#        $Subject = 'CN='+$FIO+', O=ОАО Компания, S=Тюменксая обасть, L=Тюмень, C=RU'
        $Subject = $Subject + ', O=ОАО Компания, S=Тюменксая обасть, L=Тюмень, C=RU'
        #"CN=NameUser, OU=OrgUnit, C=RU, S=State; L=City, O=Org, E=EMail"
        $KeyLength = 512
#        <#

        #далее код для создания сертификата подписи и шифрования данных
        $SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName
        $SubjectDN.Encode($Subject, $XCN_CERT_NAME_STR_NONE)
        
        $OID = New-Object -ComObject X509Enrollment.CObjectID
        $OID.InitializeFromValue("1.3.6.1.5.5.7.3.3")
        $OIDs = New-Object -ComObject X509Enrollment.CObjectIDs
        $OIDs.Add($OID)
        
        $EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage
        $EKU.InitializeEncode($OIDs)
        $EKU2 = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage
        $EKU2.InitializeEncode($CERT_DATA_ENCIPHERMENT_KEY_USAGE -bor $CERT_KEY_ENCIPHERMENT_KEY_USAGE -bor $CERT_NON_REPUDIATION_KEY_USAGE -bor $CERT_DIGITAL_SIGNATURE_KEY_USAGE)
        
        
        $PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey
        $PrivateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0"
        $PrivateKey.KeySpec = $XCN_AT_SIGNATURE
        $PrivateKey.Length = $KeyLength
        $MC = $false #user
        $PrivateKey.MachineContext = $MC
    	$PrivateKey.ExportPolicy = $XCN_NCRYPT_ALLOW_EXPORT_FLAG
        $PrivateKey.Create()
        
        $CertRequestPkcs10 = New-Object -ComObject X509Enrollment.CX509CertificateRequestPkcs10        
        $CertRequestPkcs10.InitializeFromPrivateKey($ContextUser,$PrivateKey,"")
        $CertRequestPkcs10.Subject=$SubjectDN
        $CertRequestPkcs10.X509Extensions.Add($EKU)
        $CertRequestPkcs10.X509Extensions.Add($EKU2)
            
        $Enroll = New-Object -ComObject X509Enrollment.CX509enrollment
        $Enroll.InitializeFromRequest($CertRequestPkcs10)
        
        $Request = $Enroll.CreateRequest($XCN_CRYPT_STRING_BASE64)
        
        #запрос к СА
        $CertRequest = New-Object -ComObject CertificateAuthority.Request.1
        $RequestResult = $CertRequest.Submit($CR_IN_BASE64,$Request,$null,$ServerCA)   
#        #>

        #если сертификат выдан
        if ($CR_DISP_ISSUED -ne $RequestResult){Write-host "Cert not enrolled" -ForegroundColor red} else {
        
            $Cert=$CertRequest.GetCertificate($CR_OUT_BASE64)
            
            #устанавливаем в личное хранилище
            $Enroll.InstallResponse($AllowUntrustedCertificate,$Cert,$XCN_CRYPT_STRING_BASE64,"")

            #личное хранилище
            $certs = (dir cert:\currentuser\my)
            
            #находим сертификат текущего пользователя
            $cert = $certs | ? {$_.subject -like "*"+$FIO+"*"}
            
            #если не нашли, то выдаем сообщение
            if ($cert -eq $null) {Write-host "Cert not found" -ForegroundColor red} else
            {
                #если нашли не один, то выдаем сообщение
                if ($cert.getType().BaseType.Name -like "Array"){Write-host "Cert is array" -ForegroundColor red} else 
                {
                    #если все норм, идем дальше
                    $Thumbprint = $cert.Thumbprint
                    #$Thumbprint
                    #$FIO #owner
                    
                    #экспорт закрытого ключа
                    $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx
                    #$password = "123"                    
                    #генерируем пароль
                    $password = ""
                    $rand = New-Object System.Random
                    1..10 | ForEach { $password = $password + [char]$rand.next(33,127)}
                    $bytes = $cert.Export($type, $password)
                    
                    [System.IO.File]::WriteAllBytes($Path+$FIO+'.pfx', $bytes)
                    
                    #экспорт открытого ключа
                    $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
                    $bytes = $cert.Export($type)
                    [System.IO.File]::WriteAllBytes($Path+$FIO+'.cer', $bytes)
                    
                    #удаляем из личного хранилища
                    $store = New-Object system.security.cryptography.X509Certificates.X509Store "My", "CurrentUser"
                    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
                    $store.Remove($cert)
                    $store.Close()
                    
                    #регисрируем открытый ключ текущему пользователю
                    $pol.OpenRecord()
                    #получим табл. часть - сертификаты
                    $DetailDataSet = $pol.DataSet.DetailDataSet(2)
                    $Events = $DetailDataSet.Events
                    $Events.AddCheckPoint()
                    $Events.Events($dseBeforeInsert).Enabled = $False
                    #$pol.Locate("ИД",118777)
                    #выставляем всем записям состояние закрытая, чтоб при сохранении не было ошибки
                    $DetailDataSet.First()
                    #foreach($dd in $DetailDataSet)
                    #for($j=0; $j -le $DetailDataSet.RecordCount-1; $j++)
                    1..$DetailDataSet.RecordCount | ForEach 
                    {
                        #write $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_STATE).AsString
                        $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_STATE).AsString = "Закрытая"                    
                        $DetailDataSet.Next()
                    }                    
                    $DetailDataSet.Append()
                    $Events.ReleaseCheckPoint()
                    
                    #присваиваем значения полям записи сертификата
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_NAME).AsString = $PluginName
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_SUBJECT_NAME).AsString = $FIO
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_THUMBPRINT).AsString = $Thumbprint  
                    #загружаем открытый ключ  
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE).LoadFromFile($Path+$FIO+'.cer')
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_STATE).AsString = "Действующая" #Д=Действующая;З=Закрытая
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_CERTIFICATE_INFO).AsString = "ЭЦП " + $FIO
                    $DetailDataSet.Requisites($SYSREQ_USERS_USER_DEFAULT_CERTIFICATE).AsString = "Нет" #Д=Да;Н=Нет
                    $DetailDataSet.Requisites("ISBCertificateType").AsString = $CERTIFICATE_TYPE_SIGN_AND_ENCRYPT #Э=ЭЦП;Ш=Шифрование;и=ЭЦП и шифрование
                    $DetailDataSet.Last()
                    $pol.Save()
                    #$pol.Cancel()
                                        
                    #$PFX = $d_App.EDocumentFactory.GetObjectByID(340331)
                    
                    #создаем ЭД закрытого ключа в DIRECTUM, назначаем права
                    $PFX = $d_App.EDocumentFactory.CreateNewFromFile("ПЭА", "Д000106", "PFX", $Path+$FIO+'.pfx')
                    $PFX.Requisites("Дополнение").asString = ""
                    $PFX.Requisites("Дата4").asString = ""
                    $PFX.Requisites("Дополнение3").asString = "Сертификат пользователя "+$FIO
                    $Readers = $PFX.AccessRights.Readers
                    $Readers.Clear()
                    $Managers = $PFX.AccessRights.Managers
                    $Managers.Add($User)
                    $PFX.Save()
                    
                    #создаем задачу 
                    $Task = $d_App.TaskFactory.CreateNew()
                    #настраиваем права
                    $Task.AccessRights.AccessType = $waPerformers
                    #добавляем пользователя
                    $RouteStep = $d_App.TaskFactory.CreateRouteStep($Task.Route.Count, $User, $jkJob, "", "", "")
                    $Task.Route.Add($RouteStep)
                    #вкладываем документ
                    $Task.GetAttachments($False).Add($PFX.Info)
                    #устанавливаем тему, текст
                    $Task.Requisites('Subject').AsString = "Импорт сертификата"
                    $Task.ActiveText = "Прошу ипортировать сертификат. Пароль: " + $password
                    #стартуем
                    $Task.Start()
                    $Task.Cancel()
                    
                    $PFX = $null
                    $Task = $null
                }
            }
        }
            
      }
    $i++
    }
}

Данное решение не самое лучшее. Сейчас многие автоматизируют выдачу сертификата разными способами, например выдача через AD и установка скриптом в DIRECTUM, также у вендора есть свое тех.решение.

Скрипт предоставляется «как есть», в учебных целях. Все что вы натворите в своей системе, сами несете за это ответственность.

Возможно, кому-то он пригодится как основа к автоматизации чего-то большего или как пример работы с DIRECTUM из PowerShell.

Еще была идея это все переписать на ISBL, но руки не дошли.

Алексей Пестерев

Хорошая статья, одно время думал об этом же и очень много подчеркнул в замечательном блоге по PowerShell и PKI: http://www.sysadmins.lv/default.aspx 

Рашит Сафиев

Спасибо, Алексей. По большей части черпал информацию с этого самого ресурса.

Николай Николаев

Все прекрасно за исключением экспортируемого закрытого ключа. :)

Рашит Сафиев

Николай, тоже  думал об этом. Можете предложить вариант лучше?

Николай Николаев

Можно переориентировать скрипт для запуска на пользовательском рабочем месте. Формируем запрос на сертификат, используя контекст текущего пользователя, экспортируемый закрытый ключ в таком случае уже не нужен, при успешной генерации сертификата открытый ключ отправляем для настройки в системе администратору.

Рашит Сафиев

Согласен. Но тут надо смотреть по ограничениям, навскидку необходимо чтобы политика безопасности позволяла запускать скрипты у пользователя, версия ОС Win 7 и выше ну и др.

Алексей Пестерев

Соглашусь с Рашитом, тем более, что запуск скриптов многие СБ запрещают. Боятся несанкционированных действий. Я думаю, что экспорт ключа лучше выполнять на защищенный носитель, а дальше уже руками устанавливать на рабочем месте, либо просить это сделать самого сотрудника.

Николай Николаев

Да, все зависит от политики компании относительно различных моментов по части безопасности. Кому-то критичнее гарантии отсутствия доступа к закрытому ключу (и его компрометации) на любом этапе кого-либо, кроме самого владельца. В любом случае возможны варианты. smiley

Алексей Кириченко

горите в аду сертификаты!!!!

Рашит Сафиев

Алексей, вы видимо очень намучились с сертификатами :)

Алексей Пестерев

Алексей Кириченко, если есть проблемы с сертификатами, то предлагаю обсудить это на форуме в разделе ЭП 

Алексей Кириченко

Проблемы с программным созданием сертификатов ч\з powershell..
Борюсь с переменным успехом!!!Но огромнейший респект за выложенный код, упростил жизнь мега мега..сейчас ошибка падает в строке:

 $Events.Events($dseBeforeInsert).Enabled = $False

Исключение при задании "Enabled" : "Метод Set_Enabled доступен только в защищенном контексте.^"
$Events.Events($dseBeforeInsert).Enabled = $False
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Алексей Пестерев

Алексей, создайте необходимую тему на форуме, так гораздо быстрее можно получить ответ.

Авторизуйтесь, чтобы написать комментарий