EPdfPkcs11Error = class(Exception);
TPdfPkcs11Options = record
ModulePath: string;
SlotId: UInt64;
UseSpecificSlot: Boolean;
Pin: string;
CertificateLabel: string;
class function Default: TPdfPkcs11Options; static;
end;
TPdfPkcs11SignerProvider = class(TInterfacedObject, IPadesSignerProvider,
IPdfPkcs11SignerProvider)
constructor Create(const Options: TPdfPkcs11Options);
destructor Destroy; override;
function BackendName: string;
function Resolve(const Options: TPadesSignerOptions): TPadesResolvedSigner;
function EnumerateSlots: TPdfPkcs11Slots;
function EnumerateCertificates(SlotID: UInt64): TPdfPkcs11Certificates;
procedure CloseProtectedSessions;
end;
function Pkcs11ModuleAvailable(const ModulePath: string): Boolean;
function Pkcs11ModuleVersion(const ModulePath: string): string;
function Pkcs11AbiLayout: string;
function ConfigurePkcs11SignerProvider(
const Options: TPdfPkcs11Options): IPdfPkcs11SignerProvider;
FPdfPkcs11 — переносимый бэкенд подписи PAdES и аналог бэкенда CNG на Windows. Он получает сертификат подписанта и его закрытый ключ из токена PKCS#11, а затем возвращает TPadesResolvedSigner, чей ICmsSigner формирует подпись SignerInfo через токен, так что ключ его никогда не покидает
Модуль загружается во время выполнения по пути — opensc-pkcs11.so, libsofthsm2.so, DLL производителя, — поэтому при сборке ничего не линкуется, а отсутствие модуля сообщается, а не становится фатальным. По имени разрешается только C_GetFunctionList; все остальные точки входа берутся из таблицы функций, которую он возвращает, — именно так стандарт предполагает обращаться с модулем
ConfigurePkcs11SignerProvider создаёт TPdfPkcs11SignerProvider из значения TPdfPkcs11Options и устанавливает его активным бэкендом подписантов PAdES, возвращая интерфейс IPdfPkcs11SignerProvider, который несёт также методы перечисления токена
Pkcs11ModuleAvailable и Pkcs11ModuleVersion сообщают, загружается ли данный модуль и предоставляет ли он пригодную таблицу функций, поэтому приложение может решить при запуске, возможна ли подпись через токен на этой машине. Загруженные модули кешируются по пути, потому что стандарт допускает только одну инициализацию модуля на процесс
Resolve выбирает сертификат по TPadesSignerOptions.CertificateThumbprint, если отпечаток передан, иначе по CertificateLabel, и затем находит объект закрытого ключа, несущий тот же идентификатор токена, что и сертификат. Алгоритм ключа сертификата определяет семейство алгоритмов: ключ EC подписывает через ECDSA, и его сырая подпись перекодируется в DER SEQUENCE, которую ожидает CMS, а ключ RSA следует запрошенной схеме дополнения
EnumerateSlots и EnumerateCertificates служат для пользовательских интерфейсов выбора: приложение может перечислить слоты, которые сообщает модуль, и сертификаты выбранного слота, прежде чем какой-либо вариант подписи будет зафиксирован
| Function | Description |
|---|---|
Pkcs11ModuleAvailable | Возвращает True, когда модуль по указанному пути загружается и предоставляет пригодную таблицу функций |
Pkcs11ModuleVersion | Возвращает строку версии, которую сообщает модуль, либо пустую строку, если модуль не загружается |
Pkcs11AbiLayout | Возвращает раскладку ABI, которую определила эта сборка, в виде ulong=<n> attr=<n> pss=<n> table=<n> |
ConfigurePkcs11SignerProvider | Создаёт провайдера из переданных опций, устанавливает его бэкендом подписантов PAdES и возвращает как IPdfPkcs11SignerProvider |
| Type | Purpose |
|---|---|
EPdfPkcs11Error | Исключение, возникающее, когда путь к модулю не настроен, модуль не загружается, вызов токена завершается ошибкой или не найден подходящий сертификат либо закрытый ключ |
TPdfPkcs11Options | Запись, задающая модуль, слот, PIN и сертификат, с которыми работает провайдер |
TPdfPkcs11SignerProvider | Класс-провайдер, реализующий IPadesSignerProvider и IPdfPkcs11SignerProvider поверх модуля PKCS#11 |
| Field | Description |
|---|---|
ModulePath | Путь к модулю PKCS#11; обязателен, потому что выбор модуля, обслуживающего токен, — решение развёртывания, и полезного значения по умолчанию нет |
SlotId | Открываемый слот токена; учитывается, только когда UseSpecificSlot равно True |
UseSpecificSlot | Оставьте False, чтобы взять первый слот, сообщающий о присутствии токена |
Pin | Пользовательский PIN; провайдер стирает свою копию, как только токен её использовал, а вызывающий код по-прежнему отвечает за время жизни переданной строки |
CertificateLabel | Необязательная объектная метка сертификата для подписи; используется, только когда TPadesSignerOptions.CertificateThumbprint пуст |
Default | Статическая фабрика, возвращающая опции с пустым путём модуля, слотом 0, автоматическим выбором слота и без PIN и метки |
| Member | Description |
|---|---|
Create | Создаёт провайдера из значения TPdfPkcs11Options; сам модуль открывается при первом использовании |
Destroy | Закрывает любой открытый сеанс токена перед освобождением экземпляра |
BackendName | Возвращает имя бэкенда, с добавленным именем файла модуля, если путь к модулю настроен |
Resolve | Превращает TPadesSignerOptions в TPadesResolvedSigner с токеновым подписантом и DER сертификата подписанта |
EnumerateSlots | Возвращает все слоты, которые сообщает модуль, как TPdfPkcs11Slots, включая слоты без присутствующего токена |
EnumerateCertificates | Возвращает сертификаты одного слота как TPdfPkcs11Certificates, сначала открывая сеанс на этом слоте |
CloseProtectedSessions | Выполняет выход там, где был выполнен вход, и закрывает открытый сеанс, освобождая токен для других потребителей |
CK_ULONG следует за C-типом unsigned long, поэтому занимает 4 байта на Windows (LLP64) и 8 байт на Linux и macOS (LP64); каждый дескриптор, счётчик и тип атрибута — это CK_ULONGPkcs11AbiLayout сообщает раскладку, которую определила эта сборка, поэтому расхождение диагностируемо, а не является тихим крахом: Win32 сообщает ulong=4 attr=12 pss=12 table=2, Win64 — ulong=4 attr=16 pss=12 table=2, а цель LP64 — ulong=8 attr=24 pss=24 table=8CertificateLabel побеждает первый сертификат на токене — это подходит для токена с единственным сертификатом и ни для какого производственного развёртывания