Нашел в своих закромах одну вещь и решил поделиться.
Когда-то давно, когда я сопровождал систему 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Алексей, создайте необходимую тему на форуме, так гораздо быстрее можно получить ответ.
Авторизуйтесь, чтобы написать комментарий