С 1.06.2023 году вступили в действие новые требования к сертификатам для подписи кода (aka CodeSigning), которые значительно осложнили жизнь разработчиков ПО. Суть изменений - прощай старый добрый PFX, закрытые ключи теперь должны быть неизвлекаемыми и некопируемыми. Примеры изменений у поставщиков: раз, два, три - в общем-то у всех одно и тоже.
Мы, как российские разработчики, оказались точно также затронуты этими нововведениями. До этого у нас уже был действующий сертификат, который был задеплоен на нужные виртуальные машины, где собираемый софт без проблем подписывался CI/CD-скриптами. Но часы тикали, и до окончательного отыквивания сертификата с ключом в PFX необходимо было решить, как жить дальше.
Что предлагает нам рынок
Для хранения закрытых ключей поставщики предлагают +/- одни и те же три варианта:
USB-токены.
HSM (hardware security module).
Облачные криптопровайдеры.
Каждый из этих вариантов имеет свои плюсы и минусы, которые можно свести к этой таблице:
Особенность |
USB |
HSM |
Cloud |
---|---|---|---|
Цена |
Низкая |
Высокая |
Средняя |
Физическая надёжность носителя |
Средняя |
Высокая |
Высокая |
Сложность настройки |
Низкая |
Высокая |
Низкая |
Безопасность |
Средняя |
Очень высокая |
В теории - высокая, на практике возможны нюансы |
Далее моё личное краткое резюме по всем классам.
HSM
Отдельный специализированный компьютер для хранения ключей и проведения криптографических операций. Очень специфическая штука, редкая, обычно сертифицированная по всем необходимым стандартам и нормам, и крайне дорогая, используется в основном там, где это действительно необходимо: центры сертификации, государственные организации, банки, и прочие, где безопасности уделяется серьёзное внимание. Например, облачные криптопровайдеры по сути продают доступ к массиву своих HSM как сервис.
Облачные криптопровайдеры
Как упомянул выше, облачный криптопровайдер - это сервис, предлагающий дистанционное использование массива провайдерских HSM. Облако обеспечивает: генерацию и хранение ключей, выполнение операций, требующих закрытого ключа (шифрование и генерация ЭП). Естественно, всё это доступно через интернет, гарантируется N девяток к доступности и пр. Хороший вариант, если вы: доверяете облачному провайдеру (во всех смыслах - см. типичную ситуацию с облаками после февраля 2022), а также готовы (и имеете право!) хранить ключи шифрования на стороне. Цены у всех разные, но довольно кусачие, ибо HSM и остальные инфраструктура, обязательные сертификации и аудиты, квалифицированный персонал для обслуживания всего этого стоят дорого.
USB-токен
Самый доступный и дешёвый вариант. Хорошо известные устройства в России: Рутокен, JaCarta и им подобные. Надёжность носителя не ахти, от частого включения/отключения могут ломаться разъёмы, сами устройство иногда дохнут - за такую-то цену и не удивительно. Зато легко настроить и использовать.
Как показывает практика, большинство небольших компаний, выбирают этот вариант как наиболее доступный. Поэтому дальше речь пойдёт именно про него и только в контексте вопросов организации подписи кода.
Проблемы
И так, вы наконец-то купили USB-токен и выпустили сертификат. С какими проблемами возможно придётся столкнуться? В общем-то, все они проистекают из требований о неизвлекаемости и некопируемости закрытого ключа.
Ключ один, а использовать нужно на нескольких ПК
В отличие от PFX, который можно раскидать по нужным компьютерам, USB-токен можно физически подключить только на одной машине одновременно. Вариантов решения не так уж и много - устройства USB-over-IP. Как ставший уже классикой AnywhereUSB, так и его отечественный аналоги, например такой. Слышал, что есть и чисто софтверные решения под для организации подобного доступа к USB по IP. Сам, правда, не сталкивался, так что если кому-то интересно, то гугл в помощь. Но, в любом исполнении, подобное решение избавляет только от необходимости физически переставлять токен из компьютера в компьютер. Одновременная работа токена на нескольких клиентских устройствах всё также не поддерживается.
Ключ в физическом USB-разъёме, а использовать его надо в виртуальной машине
Типичная проблема USB-устройств, далеко не каждый гипервизор умеет их пробрасывать в гостевые ВМ. Например, VMWare позволяет, а Hyper-V - нет. Для тех, кто не позволяет, остаётся только ранее рассмотренный вариант с USB-over-IP. Но и здесь получается, что токен работает только одной виртуальной машине одновременно.
Необходимость ввода PIN-кода
Для использования закрытого ключа необходимо вводить PIN-код. В режиме ручной подписи - это допустимо, но любые средства автоматизированной подписи, в т. ч. CI/CD-скрипты, не предполагают отображение каких-либо диалоговых окон. Даже если такое окно и отобразится, то никто его не увидит на сервере, который находится неизвестно где, и на кнопку ОК не нажмёт. В итоге, операция подписи или отвалится по таймауту, или просто зависнет - зависит от реализации драйверов.
Ситуация усугубляется тем, что утилита signtool
, используемая для подписи исполняемых файлов и установочных пакетов под Windows, не позволяет передать PIN-код как параметр. Да и разработчики драйверов носителей ключей не стремятся добавлять (или по крайней мере, широко афишировать) опции сохранения PIN-кодов и/или отключения UI для их ввода. Ведь предполагается, что USB-токены, являющиеся личными устройством, как раз должны обеспечивать безопасность пользователя. Что совершенно понятно в случаях персональных ключей электронных подписей. Ведь ни пользователю, ни производителю устройств не нужно, чтобы какой-нибудь примитивный зловред подписал за вас пару платёжных поручений, переводящих все нажитые накопления какому-нибудь Хулио из Колумбии.
Но, учитывая сложившиеся практики повсеместного использование подписи кода в автоматизированных системах сборки, производители устройств выкручиваются как могут. Например, для Рутокенов существует специальный тред на их форуме техподдержки.
Про сохранение PIN-кодов
Да, хранить PIN-коды для автоматизированной обработки - совсем не по фэншую, и все возможные руководства не советуют так делать. Но мы тут насущные проблемы решаем, а не боремся за абстрактную безопасность.
И что со всем этим делать?
Рассказываю, как я решил для себя эту задачу. После некоторых проб и экспериментов оформилось решение в виде системы их двух компонентов:
Веб-сервер на ASP.NET Core с генерацией электронной подписи и ввода PIN-кода посредством вызова функций WinAPI CNG (Cryptography API: Next Generation).
Клиентская часть в виде Powershell-скрипта, запускающего
signtool
со специальными ключами и обращающегося к нашему серверу за операцией генерации подписи.
Hidden text
Почему именно CNG, а не старый добрый CryptoAPI, будет понятно далее.
Теперь всё тоже самое, но по порядку и с подробностями.
Суть решения
Недолгий гуглинг показал, что signtool
умеет разделять подпись файла на четыре этапа.
С помощью вызова с параметром -dg формируем только заготовку электронной подписи в формате CMS, при этом сам хэш, который необходимо подписать, сохраняется в отдельном файле в кодировке Base64. Например, следующий вызов для подписи нашего пробного приложения
c:\temp\myapp.exe
сертификатомc:\temp\cert.cer
:
signtool sign -f c:\temp\cert.cer -fd sha256 -dg c:\temp -v c:\temp\myapp.exe
сгенерирует два новых файла в рабочем каталоге (в нашем примере c:\temp
). Первый - myapp.exe.p7u
- содержит заготовку CMS-подписи, нам он пока не интересен. Второй - myapp.exe.sig
- как раз содержит нужное нам значение рассчитанного хэша, закодированное в Base64.
2. Теперь необходимо как-то подписать хэш из файла myapp.exe.dig
. Этим будет заниматься наш веб-сервис, который предполагается установить на компьютер, к которому подключён USB-токен (физически или через USB-over-IP, не принципиально). На вход сервису будет передаваться значение хэша, алгоритм хэширования (SHA256
в примере выше) и отпечаток сертификата, результатом будет рассчитанная электронная подпись. Получив подпись, её необходимо сохранить в том же рабочем каталоге в файле myapp.exe.dig.signed
, тоже в кодировке Base64.
3. Всё готово для сборки подписи и вставке её в исполняемый файл. Вызываем:
signtool sign -di c:\temp -v c:\temp\myapp.exe
Параметр -di предписывает утилите signtool
взять сырое значение электронной подписи из файла myapp.exe.dig.signed
, завершить формирование CMS-подписи, и вставить её в исполняемый файл.
4. При такой схеме нельзя выполнить подпись и простановку штампа времени за один вызов signtool
, поэтому заставляем её проставить штамп времени на подпись отдельным вызовом:
signtool timestamp -tr http://timestamp.globalsign.com/tsa/r6advanced1 -td sha256 -v c:\temp\myapp.exe
Voilà, файл подписан, что и требовалось.
Реализация серверной части
Для обслуживания запросов от разных наших клиентов (CI/CD-скрипты) я написал простенький веб-сервис на ASP.NET Core, который развёрнут в виде Windows-службы на сервере, в который вставлен USB-токен.
Веб-часть до ужаса примитивна. Есть один minimal API endpoint, который и обрабатывает наши POST-запросы на генерацию подписи, и есть блок считывания настроек используемых сертификатов.
Начнём с настроек
Список доступных сертификатов и PIN-кодов к ним будем хранить в стандартном файле настроек приложения appsettings.json
в виде JSON. У меня он выглядит так:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Signer": {
"Certificates": [
{
"Thumbprint": "fc276a503b9b2f24d644424fff823bdf6bf6f603",
"KeyName": "2a63f603f43743848ea243130bf1702e",
"IsMachineKey": false,
"Pin": "12345678"
}
]
}
}
Первые два раздела стандартные для ASP.NET, нас интересует последний, Signer. В нём есть только один параметр с массивом сертификатов, ключами которых мы планируем подписывать что-либо. Каждый сертификат задаётся четырьмя параметрами:
Thumbprint - текстовое представление SHA1-отпечатка сертификата, используется для идентификации. Именно это значение будет передаваться клиентом как один из параметров нашего сервиса. Можно подсмотреть в свойствах сертификата любым способом.
-
KeyName - имя ключевого контейнера в криптопровайдере Microsoft Smart Card Key Storage Provider.
Для ключа на Рутокен это имя можно подсмотреть в панели управления Рутокен
Для других устройств смотрите соответствующую документацию разработчика, или извлекайте кодом с помощью перечисления ключевых контейнеров с функцией
NCryptEnumKeys
(полеpszName
структурыNCryptKeyName
). IsMachineKey - признак что ключ принадлежит компьютеру, а не текущему пользователю.
Pin - наш сохранённый PIN-код для USB-токена.
В самом приложении эти настройки инкапсулированы в пару классов:
public sealed class Certificate
{
public required string Thumbprint { get; set; }
public required string KeyName { get; set; }
public bool IsMachineKey { get; set; }
public required string Pin { get; set; }
}
public sealed class SignerOptions
{
public List<Certificate> Certificates { get; set; } = [];
}
При инициализации приложения классы с настройками регистрируются в DI-контейнере стандартным вызовом:
builder.Services.Configure<SignerOptions>(builder.Configuration.GetSection("Signer"));
Обработчик запросов
Для обработки входящих запросов на подпись используем Minimal API:
app.MapPost("/sign/base64hash",
async ([FromQuery] string base64hash, [FromQuery] string certificateThumbrint, [FromQuery] string hashAlgId, IOptionsMonitor<SignerOptions> options, CancellationToken ct) =>
await SignBase64HashAsync(base64hash, certificateThumbrint, hashAlgId, options.CurrentValue, ct));
Сервис реализован в виде метода POST по адресу /sign/base64hash
с тремя параметрами, которые передаются через параметры запроса:
base64hash - кодированное в Base64 значение хэша, которого нужно подписать.
certificateThumbrint - текстовое представление отпечатка сертификата, которым необходимо подписать хэш.
hashAlgId - название алгоритм хэширования, который должен совпадать с константами из CNG.
SignBase64HashAsync
- наш метод, который будет выполнять всю работу, рассмотрим его подробнее:
private static async Task<IResult> SignBase64HashAsync(string base64hash, string certificateThumbrint, string hashAlgId, SignerOptions options, CancellationToken ct)
{
var certificate = options.Certificates.FirstOrDefault(c => c.Thumbprint.Equals(certificateThumbrint, StringComparison.OrdinalIgnoreCase));
if (certificate is null)
return Results.Problem($"The certificate with the thumbprint '{certificateThumbrint}' is not allowed.", null, StatusCodes.Status403Forbidden);
var hash = Convert.FromBase64String(base64hash);
var semaphore = _keySemaphores.GetOrAdd(certificate, c => new(() => new(1))).Value;
await semaphore.WaitAsync(ct);
try
{
var signature = SignCore(hash, hashAlgId, certificate);
return Results.Bytes(Encoding.ASCII.GetBytes(Convert.ToBase64String(signature, Base64FormattingOptions.None)), "application/octet-stream");
}
finally
{
semaphore.Release();
}
}
На вход он получает параметры запроса и указатель на объект SignerOptions
с настройками приложения. Сначала функция выполняет поиск сертификата с заданным отпечатком, если он не найден, то на клиента возвращается ошибка в формате Problem Details.
Если сертификат найден в настройках, то выполняется поиск и при необходимости создание семафора, обеспечивающего доступ к ключу только одному потоку приложения одновременно. Сам словарь семафоров объявлен так:
private static ConcurrentDictionary<Certificate, Lazy<SemaphoreSlim>> _keySemaphores = new();
Далее происходит вход в семафор и выполняется метод SignCore
, который и выполняет непосредственно подписание. Полученный байтовый массив - это и есть значение подписи, которое кодируется в Base64 и отправляется клиенту.
Подпись
Сначала я хотел использовать для подписи CryptoAPI (ранее немного освещал его использование именно для .NET: раз и два). Но с ним я зашёл в тупик. Оказалось, что функция CryptSignHash принимает на вход ссылку HCRYPTHASH
, то есть ссылку на объект, выполняющий хэширование. У нас же хэш уже вычислен, и нужно только подписать его. Но способа передать только значение хэша без выполнения самого хэширования я не нашёл. Да ещё, и весь CryptoAPI объявлен deprecated, и сама MS настоятельно рекомендует перейти на CNG, который как ни странно, появился ещё в составе Windows Vista, но не снискал особой популярности, оставаясь в тени своего предшественника.
После небольшого исследования стало понятно, что NCryptSignHash
- функция подписи в CNG - отделена от хэширования и принимает на вход произвольный байтовый массив, что нам и требуется.
Итак, сам код. Для P/Invoke я использовал свою библиотеку функций (nuget, github), вы можете использовать любую другую или написать свои переходники к функциям WinAPI.
private static unsafe byte[] SignCore(ReadOnlySpan<byte> inData, ReadOnlySpan<char> hashAlg, Certificate certificate)
{
nint hProvider;
fixed (char* providerName = MS_SMART_CARD_KEY_STORAGE_PROVIDER)
NCryptOpenStorageProvider(&hProvider, providerName, 0).VerifyWinapiErrorCode();
try
{
nint hKey;
fixed (char* pKeyName = certificate.KeyName)
NCryptOpenKey(hProvider, &hKey, pKeyName, 0U, NCRYPT_SILENT_FLAG | (certificate.IsMachineKey ? NCRYPT_MACHINE_KEY_FLAG : 0U)).VerifyWinapiErrorCode();
try
{
fixed (char* pPin = certificate.Pin, pParam = NCRYPT_PIN_PROPERTY)
NCryptSetProperty(hKey, pParam, pPin, (uint)(certificate.Pin.Length + 1) * 2, 0U).VerifyWinapiErrorCodeInList(ERROR_SUCCESS);
var inDataLen = (uint)inData.Length;
uint outDataLen;
fixed (char* pHashAlg = BCRYPT_SHA256_ALGORITHM)
fixed (byte* pInData = inData)
{
var padding = new BCRYPT_PKCS1_PADDING_INFO() { pszAlgId = pHashAlg };
NCryptSignHash(hKey, &padding, pInData, inDataLen, null, 0, &outDataLen, NCRYPT_PAD_PKCS1_FLAG).VerifyWinapiErrorCode();
var outData = new byte[(int)outDataLen];
fixed (byte* pOutData = outData)
NCryptSignHash(hKey, &padding, pInData, inDataLen, pOutData, outDataLen, &outDataLen, NCRYPT_PAD_PKCS1_FLAG).VerifyWinapiErrorCode();
return outData;
}
}
finally
{
NCryptFreeObject(hKey);
}
}
finally
{
NCryptFreeObject(hProvider);
}
}
Код прост и прямолинеен:
Вызовом
NCryptOpenStorageProvider
получаем ссылку на встроенный провайдер смарт-карт (константаMS_SMART_CARD_KEY_STORAGE_PROVIDER
).С помощью
NCryptOpenKey
получаем ссылку на наш ключевой контейнер по имени. Важно, что контейнер открывается с флагомNCRYPT_SILENT_FLAG
, что блокирует отображение криптопровайдером любых диалоговых окон наподобие "Вставьте ключ", "Введите PIN-код". Нам как раз и нужно такое поведение.Передаём PIN-код ключа с помощью
NCryptSetProperty
и константыNCRYPT_PIN_PROPERTY
.Наконец, генерируем саму подпись парой вызовов
NCryptSignHash
. Первый вызов вернёт размер буфера под подпись, второй - заставит USB-токен рассчитать её значение.Очищаем все выделенные неуправляемые ресурсы: ссылки на ключ и на провайдер - вызовами
NCryptFreeObject
.
На этом с сервером почти всё. Осталось две задачи:
Обеспечить некоторое разграничение прав. Делаете любым подходящим вам способом, я делал с помощью API Key по этой статье на Хабре.
Настроить хостинг в Windows-службе согласно стандартной доке. Кстати, службе не обязательно давать права
LocalSystem
, у меня хватило иLocalService
.
Реализация клиентской части
Для использования только что созданного веб-сервиса и избавления от повторяющихся вызовов signtool
я написал простенький Powershell-скрипт Sign-ExecutableFile.ps1
, выполняющий всю грязную работу:
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline = $true, HelpMessage="A target file name")]
[ValidateNotNullOrEmpty()]
[string] $TargetFileName,
[Parameter(Mandatory=$true, HelpMessage="A full path to the SignTool utility")]
[ValidateNotNullOrEmpty()]
[string] $SignToolPath,
[Parameter(Mandatory=$true, HelpMessage="A certificate file name")]
[ValidateNotNullOrEmpty()]
[string] $CertFileName,
[Parameter(Mandatory=$true, HelpMessage="An URI of signing service")]
[ValidateNotNullOrEmpty()]
[string] $SigningServiceUri,
[Parameter(Mandatory=$true, HelpMessage="An API key for signing service")]
[ValidateNotNullOrEmpty()]
[string] $SigningServiceApiKey,
[Parameter(Mandatory=$false, HelpMessage="A digest algorithm name for signing")]
[ValidateSet("SHA256", "SHA384", "SHA512")]
[string] $DigestAlgorithm = "SHA256",
[Parameter(Mandatory=$false, HelpMessage="A timestamp authority URI")]
[ValidateNotNullOrEmpty()]
[string] $TsaUri = $null,
[Parameter(Mandatory=$false, HelpMessage="A digest algorithm name for timestamping")]
[ValidateSet("SHA256", "SHA384", "SHA512")]
[string] $TsaDigestAlgorithm = "SHA256",
[Parameter(Mandatory=$false, HelpMessage="A working directory for placing intermediate results")]
[string] $WorkingDirectory
)
begin {
$ErrorActionPreference = "Stop"
if (![System.String]::IsNullOrEmpty($WorkingDirectory))
{
$TargetWorkingDirectory = $WorkingDirectory
}
else
{
Write-Warning "The working folder is not specified, temporary files will be placed next to the executable files."
}
$certBytes = [System.IO.File]::ReadAllText($CertFileName)
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertFileName)
}
process {
Write-Host
Write-Host ("Signing the file '" + $TargetFileName + "'") -ForegroundColor White
if ([System.String]::IsNullOrEmpty($WorkingDirectory))
{
$TargetWorkingDirectory = [System.IO.Path]::GetDirectoryName($TargetFileName)
}
else
{
$TargetWorkingDirectory = $WorkingDirectory
}
$tempFilePrefix = [System.IO.Path]::Combine($TargetWorkingDirectory, [System.IO.Path]::GetFileName($TargetFileName))
$digestFileName = $tempFilePrefix + ".dig"
$signatureFileName = $digestFileName + ".signed"
$cmsFileName = $tempFilePrefix + ".p7u"
try
{
Write-Host ("Generating a digest for the file '" + $TargetFileName + "'") -ForegroundColor Cyan
& $SignToolPath sign -f "$CertFileName" -fd $DigestAlgorithm -ph -dg "$TargetWorkingDirectory" -q "$TargetFileName"
Write-Host ("Calculating a signature for the digest from the file '" + $digestFileName + "'" ) -ForegroundColor Cyan
$serviceUri = [System.Uri]::new($SigningServiceUri)
$serviceUri = [System.Uri]::new($serviceUri, ("sign/base64hash?base64hash=" + [System.Uri]::EscapeDataString([System.IO.File]::ReadAllText($digestFileName)) + "&hashAlgId=" + $DigestAlgorithm + "&certificateThumbrint=" + [System.Uri]::EscapeDataString($cert.Thumbprint)))
Invoke-RestMethod -Uri $serviceUri -Method Post -Headers @{ 'X-API-Key' = $SigningServiceApiKey } -OutFile $signatureFileName
Write-Host ("Injecting a signature into the file '" + $TargetFileName+ "'" ) -ForegroundColor Cyan
& $SignToolPath sign -di "$TargetWorkingDirectory" -q "$TargetFileName"
if (-not [System.String]::IsNullOrEmpty($TsaUri))
{
Write-Host ("Injecting a timestamp into the file '" + $TargetFileName+ "'" ) -ForegroundColor Cyan
& $SignToolPath timestamp -tr "$TsaUri" -td $TsaDigestAlgorithm -q "$TargetFileName"
}
}
finally
{
Remove-Item -Path $digestFileName -Force -ErrorAction SilentlyContinue
Remove-Item -Path $signatureFileName -Force -ErrorAction SilentlyContinue
Remove-Item -Path $cmsFileName -Force -ErrorAction SilentlyContinue
}
}
end {
}
Скрипт принимает все необходимые значения через параметры, назначение которых вполне очевидно из исходника. Для каждого элемента из входной коллекции файлов скрипт выполняет нужную череду вызовов signtool
и обращений к нашему сервису подписи. В конце производится зачистка всех временных файлов, созданных утилитой signtool
.
Пример вызова для подписи всех EXE-файлов в каталоге c:\temp
, включая подкаталоги:
gci -Path "c:\temp\*.exe" -Recurse | .\Sign-ExecutableFile.ps1 -CertFileName c:\temp -SignToolPath "c:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe" -SigningServiceUri "http://localhost:8145" -SigningServiceApiKey "myApiKey" -TsaUri "http://timestamp.globalsign.com/tsa/r6advanced1"
Заключение
В сухом остатке, задача по подписи кода из CI/CD с ключами на USB-токенах была успешно решена, "веб-сервис крутится, сигнатурки мутятся". Буду рад, если предложенное решение облегчит кому-то рутинные операции.
Комментарии (20)
mvv-rus
10.02.2025 01:48С 1.06.2023 году вступили в действие новые требования к сертификатам для подписи кода
А можно с этого места поподробнее: где они вступили, кто требует их выполнения и как проверяется, что ключ действительно хранится в соответствии с требованиями, а не в файле PFX? А то мне в уже далекие годы приходилось иметь дело с технологией (некий Alladin SecretDisk), которая как раз требовала использования такого вот брелка. И мне помнится, что там вполне можно было записать на брелок сертификат из .pfx.
Мы, как российские разработчики, оказались точно также затронуты этими нововведениями.
А в законодательстве РФ или в позаконных актах уполномоченнного органа это как-нибудь отражено? А то, при взгляде на английский текст меня терзают смутные сомнения.
Может, стоит, по русскому обычаю, забить на эти ужасы?
AntonLarinLive Автор
10.02.2025 01:48где они вступили, кто требует их выполнения и как проверяется, что ключ действительно хранится в соответствии с требованиями, а не в файле PFX?
Ссылка в самом начале статьи. Их выполнение обязательно для центров сертификации. Ни один публичный авторизованный ЦС не выпустит сертификат подписи кода без хранения закрытого ключа на устройстве. Им же совершенно не хочется терять авторизацию.
А в законодательстве РФ или в подзаконных актах уполномоченного органа это как-нибудь отражено?
Нет конечно, это решает не законодатель, а продавец услуги. Как в анекдоте: "Не нравится предложение? - А ты походи по базару, поищи получше".
AntonLarinLive Автор
10.02.2025 01:48А то мне в уже далекие годы приходилось иметь дело с технологией (некий Alladin SecretDisk), которая как раз требовала использования такого вот брелка. И мне помнится, что там вполне можно было записать на брелок сертификат из .pfx.
Требуется носитель с неизвлекаемым закрытым ключом. Сейчас на рынке вообще один ЦС остался, который официально работает с российскими клиентами: GlobalSign. Он предлагает носитель Рутокен ЭЦП 3.0, который поддерживает неизвлекаемые ключи. И я не слышал, чтобы кому-то удалось извлечь их из него.
mvv-rus
10.02.2025 01:48Их выполнение обязательно для центров сертификации. Ни один публичный авторизованный ЦС не выпустит сертификат подписи кода без хранения закрытого ключа на устройстве. Им же совершенно не хочется терять авторизацию.
Вы так рассуждаете, как будто эти правила выпущены органом, имеющим властные полномчия. А по факту они - междусобойчик частных фирм, которые просто тянут с людей деньги за навязанную им услугу. Потому что технически CA - это очень простая штука, если чо, у меня на домашнем ПК в виртуалке стоит, от экспериментов остался. Вопрос тут в доверии, и я не понимаю, почему человек, как частно лицо или, к примеру, в роли руковдителя службы ИБ предприятия, должен доверять этому междусобойчику и не доверять кому-то другому. Государство, конечно, тоже заслуживает мало доверия, но корпорации заслуживают доверия ещё меньше, потому что забота об ощем благе в их интересы не входит даже декларативно.
Нет конечно, это решает не законодатель, а продавец услуги.
Нет, право решать продавца должно, исходя из интересов общего блага, быть ограничено. Если продавец теряет берега, то должно прийти государство (например, в лице антимонопольщиков), и ему эти берега указать. А в данном случае монопольный картельный сговор налицо.
Но конкретно тут государство РФ лоханулось. Ещё когда обсуждался закон о пресловутом суверенном Рунете, я тогда сразу обратил внимание на крайний непрофессионализм обсуждения, что ни одна из сторон - ни власть, ни оппозиция - не представила публики явным образом своей модели угроз, на основании которой можно было найти решение. Оппозиция заботилась лишь о своем праве вести пропаганду, а власть выступала с хорошо знакомой позицией, что "там, наверху, и без вас разберутся". А я ещё тогда, посмотрев детали предлагаемых мер, не увидел ничего про обеспечение суверенной инфраструктуры открытых ключей. Но решил забесплатно не умничать, а заниматься своими делами.
А вот после 22-го года этот провал вылез наружу - то есть, как мы видим, там, наверху, не разобрались (что, в общем-то и следовало ожидать).
Требуется носитель с неизвлекаемым закрытым ключом.
Вы, похоже, не поняли идею: пусть ключ и неизвлекаемый, но на носитель записывается сгенерированный другим способом ключ, который потом извлечь нельзя. А копия ключа остается и может использоваться нормально. Но насколько это технически осуществимо, и возможен ли такой способ обойти дурную навязанную процедуру - в этом как раз и вопрос.
Уточнение: надеюсь, ЦС не пытаются навязывать свою ключевую пару, а просто подписывают сертификат с открытм ключом, который заявитель сгенерировал сам? А то это было бы полнейшим безобразием - позволять какому-то мутному ЦС (не важно, частному или государственному) доступ к своему закрытому ключу.Сейчас на рынке вообще один ЦС остался, который официально работает с российскими клиентами: GlobalSign.
Это, конечно, безобразие, но это уже зона ответственности государства. А, как я писал выше, российское государство этот момент откровенно зевнуло, когда соответствующий закон принимало.
Короче, есть технические вопросы, как жить в существующих условиях - и относительно их статья вполне удовлетворительна. Разве что, неплохо было бы подробнее объяснить контекст тем, кто не в теме: я, например, не понял, каких продуктов это касается.
И есть вопросы человеческие - как относиться к существующим условиям. И выраженная в статье позиция - как будто все происходит так, как должно , своего рода "политика выполнения", если брать исторический аналог - в этом отношении мне не нравится. Впрочем на ценность статьи как технической это практически не влияет.AntonLarinLive Автор
10.02.2025 01:48Вас понесло в абстрактные рассуждения. В реальности есть перечень аккредитованных публичных ЦС. У них есть регламент, они его придерживаются. Если он вам не нравится, то есть два пути:
Принять реальность.
Создать свой ЦС, с БДиШ (государственный, частный, домашний - без разницы). Всего-то надо добавить его корневой сертификат в список доверенных на все уже развёрнутые и будущие операционные системы, браузеры и устройства страны/мира - и дело в шляпе.
mvv-rus
10.02.2025 01:48Ну, я чувствую, что до п.2. российское государство доберется, и скорее рано, чем поздно, поменяет-таки реальность - оно это умеет. И мало не покажется никому.
Но если даже без абстрактных рассуждений (хотя они про чисто конкретные проблемы), то, раз вы обозначили статью как "Туториал", стоило бы IMHO раскрыть контекст - для какого типа прложений это существенно. Хотя бы - ссылкой.
А то, гляжу, эти, типа, нормы-то вступили уж полтора года как, а на столь любимом многими (мной в том числе) бэкенде на ASP.NET Core с MVC и Minimal API они никаких заметных заморочек не вызвали.AntonLarinLive Автор
10.02.2025 01:48Моя статья - туториал по решению конкретной проблемы. Если вам интересно, что такое вообще подпись кода, то это немного другая тема; можно почитать, например, здесь же на хабре.
Если кратко, для внутреннего употребления программы подписывать в большинстве случаев не обязательно. Но если вы распространяете бинарники/установочные пакеты клиентам, то уже крайне желательно их подписывать, чтобы антивирусы и операционные системы не ругались на код из недоверенных источников. Более того, корпоративные службы ИБ очень любят запрещать в своих сетях запуск непонятно чьего кода через политики SRP . И здесь электронная подпись ПО помогает идентифицировать его и пометить как доверенное.
mayorovp
10.02.2025 01:48А то, гляжу, эти, типа, нормы-то вступили уж полтора года как, а на столь любимом многими (мной в том числе) бэкенде на ASP.NET Core с MVC и Minimal API они никаких заметных заморочек не вызвали.
А почему вы вообще ожидали, что нормы для массовых десктопных виндовых приложений как-то затронут бекенд, предназначенный для работы в ограниченном числе экземпляров, да ещё и небось на линуксе?
mvv-rus
10.02.2025 01:48А почему вы вообще ожидали, что нормы для массовых десктопных виндовых приложений
А я этого и не ожидал. Я ожидал, что автор укажет, для какой области применения нужно его решение, и не более того - а не начнет статью так, будто это нужно всем разработчикам под .NET. Даже если ему некогда писать много букв в статье - есть же теги. А тегах я как раз не вижу Windows Desktop, а вижу очень даже asp.net.
И, насчет "массовых виндовых десктопных приложений". Насколько я помню, подписывание приложений нужно было только весьма специфическому их классу - UWP/Windows Store(или как он там сейчас называется), а на обычные приложения под Win32 такого требования не было: подписать было можно, но можно было обойтись и без этого. Что-то поменялось?
AntonLarinLive Автор
10.02.2025 01:48Подписываются исполняемые файлы, DLL и установочные пакеты. Desktop, ASP.NET, драйверы - не важно. .NET тут фигурирует как платформа для разработки сервера подписи.
mvv-rus
10.02.2025 01:48Я в курсе, что можно подписать. Но меня, если смотреть с позиции администратора инфраструктуры предприятия, каковым я был некогда, интересует, во-первых, что подписывать обязательно, и где требуется подпись не от корпоративного CA, а именно от внешней сертифицирующей организации.
AntonLarinLive Автор
10.02.2025 01:48Теперь посмотрите с точки зрения поставщика ПО. Ему неведомо, какой там внутренний ЦС у каждого потенциального клиента. Зато ему нужно, чтобы ПО без проблем работало у всех. Поэтому дорога одна: в публичный аккредитованный ЦС.
mvv-rus
10.02.2025 01:48Говорят, дорог всегда, минимум, две - даже если вас проглотили.
Внутренний ЦС - это область ответственности админа (или ИБ) предприятия
И админ предприятия, например, вполне может самостоятельно воспользоваться тем же самым SignTool. Да и обычных пользователей не надо недооценивать: к примеру, рутовать телефоны для установки нужных им приложений у них вполне получается. А, с другой стороны, у "публичных аккредитованных ЦС" есть свои недостатки, вполне заметные, особенно - в специфических российских условиях.
Так что выбор есть, и это хорошо. Но, наверное, обсуждать этот вопрос дальше - это для вас слишком абстрактно будет.
mayorovp
10.02.2025 01:48Вы хоть раз видели как на винде SmartScreen работает? Тот самый, который всплывает при запуске скачанных из интернета программ?
Вот если программа не подписана, то в этом диалоге пишется "неизвестный издатель", а если подписана - то имя того, кто подписывал. Для того подпись и нужна.
AntonLarinLive Автор
10.02.2025 01:48Ну, я чувствую, что до п.2. российское государство доберется, и скорее рано, чем поздно, поменяет-таки реальность - оно это умеет. И мало не покажется никому.
Они уже несколько лет пытаются пропихнуть свой корневой сертификат Минцифры на все устройства. Правда, пока плохо получается.
mvv-rus
10.02.2025 01:48Правда, пока плохо получается.
Вы таки решили дальше поговорить об абстрактном? Я-то могу, и про чисто конкретные проблемы из-за этого - тоже. Но вам же, это, вроде как, не понравилось, так?
ShadowMaster
10.02.2025 01:48Как-то так https://www.ssl.com/how-to/key-generation-and-attestation-with-yubikey/#attestation
Проверяются ключи, вшитые в конкретный брелок (с привязкой к сформированному в памяти брелка закрытому ключу). Выполнения требуют центры сертификации, которые и выдают сертификаты. В российских законах, разумеется этого нет и вы можете спокойно забить на все эти правила. Правда, вы не получите подписанный бинарник для windows. Только если у вас есть старый многолетний сертификат, полученный по старым правилам.
ShadowMaster
Во-первых, есть дешевая альтернатива всей этой возни с брелками - Azure Trusted Signing. 10 долларов в месяц, 120 долларов в год - это получается на уровне дешевых электронных подписей типа ssl.com. Ограничение - 10000 подписываний в месяц, за глаза должно хватить. Из преимуществ - не нужно покупать брелок или платить за его пересылку, сертификат - аналог EV, подписание привычным signtool, хоть и новой версии с аддоном, нет привязки к физическому брелку, помесячная оплата. Из недостатков - достаточно замороченная настройка в Azure, но это нужно делать один раз, помесячная оплата и в любой момент ее могут повысить. Но если повысят, то можно быстро вернуться к привычному способу подписания.
Во-вторых, центры сертификации присылают либо свой брелок, либо клиент может использовать собственный. Но если использовать собственный, то выбор сужается только до Yubikey 5 FIPS. Свой Yubikey по-любому выходит дешевле. Никаких там рутокенов. И для Yubikey вроде бы есть возможность передать PIN-код через командную строку.
Сам еще с этими трудностями еще не сталкивался, мы умудрились оформить сертификат за два месяца до введения новых правил.
AntonLarinLive Автор
Я использовал свой Рутокен при заказе, хотя предлагали купить. С облаками связываться не хочется как раз по причине, что их могут в любой момент отключить.