Нашел в своих закромах одну вещь и решил поделиться.
Когда-то давно, когда я сопровождал систему DIRECTUM ранней версии (вроде это было на 4.5) на предыдущем месте работы, у меня была одна из задач – выдавать сертификаты для подписи пользователям.
Эта задача тогда была никак не автоматизирована, и все делалось вручную. А именно (по памяти):
Мне тогда был интересен 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 и выше ну и др.
Соглашусь с Рашитом, тем более, что запуск скриптов многие СБ запрещают. Боятся несанкционированных действий. Я думаю, что экспорт ключа лучше выполнять на защищенный носитель, а дальше уже руками устанавливать на рабочем месте, либо просить это сделать самого сотрудника.
Да, все зависит от политики компании относительно различных моментов по части безопасности. Кому-то критичнее гарантии отсутствия доступа к закрытому ключу (и его компрометации) на любом этапе кого-либо, кроме самого владельца. В любом случае возможны варианты.
горите в аду сертификаты!!!!
Алексей, вы видимо очень намучились с сертификатами :)
Алексей Кириченко, если есть проблемы с сертификатами, то предлагаю обсудить это на форуме в разделе ЭП
Проблемы с программным созданием сертификатов ч\з powershell..
Борюсь с переменным успехом!!!Но огромнейший респект за выложенный код, упростил жизнь мега мега..сейчас ошибка падает в строке:
$Events.Events($dseBeforeInsert).Enabled = $False
Исключение при задании "Enabled" : "Метод Set_Enabled доступен только в защищенном контексте.^"
$Events.Events($dseBeforeInsert).Enabled = $False
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Алексей, создайте необходимую тему на форуме, так гораздо быстрее можно получить ответ.
Авторизуйтесь, чтобы написать комментарий