Цифровые подписи
Добавлено в v0.3.38. PDF Oxide читает подписи из /AcroForm /Fields → /Sig, инспектирует CMS-конверт внутри /Contents и выполняет проверку атрибутов подписанта по RFC 5652 §5.4 против встроенного сертификата, а также проверку messageDigest по §11.2 против переданных вызывающей стороной байтов документа.
Область применения. Эта страница посвящена чтению и проверке существующих подписей: перечислению, криптографической проверке CMS, классификации по уровню PAdES, инспекции DSS и меткам времени RFC 3161. Чтобы создать подпись — загрузить учётные данные PKCS#12/PEM и сформировать подпись CMS или PAdES B-B/B-T/B-LT — смотрите Подписание PDF: цифровые подписи и PAdES (выпущено в v0.3.50, issue #235).
Что проверяется
- RSA-PKCS#1 v1.5 over SHA-1 / SHA-256 / SHA-384 / SHA-512 — схема дополнения, используемая практически во всех подписанных PDF — возвращает
Valid/Invalid. - RSA-PSS и ECDSA возвращаются как
Unknown/UnsupportedFeatureException. Вызывающий код, которому нужны эти алгоритмы, может получить встроенный сертификат черезSignature.GetCertificate()/.get_certificate()и выполнить проверку самостоятельно. - Поиск доверенного корня, окно срока действия и DN подписанта берутся из встроенного сертификата и записываются в результат проверки.
Быстрый пример
Rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("signed.pdf")?;
for sig in doc.signatures() {
println!("{} → {:?}", sig.signer_name(), sig.verify()?);
// End-to-end with document bytes
let pdf_bytes = std::fs::read("signed.pdf")?;
println!("detached ok = {:?}", sig.verify_detached(&pdf_bytes)?);
// Cert inspection + trust-root / expiry / signer DN
let result = pdf_oxide::SignatureVerifier::verify(&sig, &pdf_bytes)?;
println!("signer DN = {}", result.signer_dn);
}
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("signed.pdf")
for sig in doc.signatures():
print(sig.signer_name, "→", sig.verify())
# End-to-end with document bytes
with open("signed.pdf", "rb") as f:
pdf_bytes = f.read()
for sig in doc.signatures():
print("detached ok =", sig.verify_detached(pdf_bytes))
Node / TypeScript
import { PdfDocument } from "pdf-oxide";
import { readFileSync } from "fs";
const doc = PdfDocument.open("signed.pdf");
for (const sig of doc.signatures()) {
console.log(sig.signerName, "→", sig.verify());
console.log("detached ok =", sig.verifyDetached(readFileSync("signed.pdf")));
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("signed.pdf");
foreach (var sig in doc.Signatures)
{
Console.WriteLine($"{sig.SignerName} → {sig.Verify()}");
var cert = sig.GetCertificate();
Console.WriteLine($"subject={cert.Subject} issuer={cert.Issuer} valid={cert.IsValid}");
}
Go (CGo-only)
import (
"fmt"
"os"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
doc, _ := pdfoxide.Open("signed.pdf")
defer doc.Close()
pdfBytes, _ := os.ReadFile("signed.pdf")
sigs, _ := doc.Signatures()
for _, sig := range sigs {
ok, _ := sig.Verify()
fmt.Printf("%s → %v\n", sig.SignerName, ok)
// End-to-end with document bytes
detachedOk, _ := sig.VerifyDetached(pdfBytes)
fmt.Println("detached ok =", detachedOk)
}
WASM
import init, { PdfDocument } from "pdf-oxide-wasm/web";
await init();
const doc = new PdfDocument(bytes);
for (const sig of doc.signatures()) {
console.log(sig.signerName, "→", sig.verify());
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
#include <fstream>
auto doc = pdf_oxide::Document::open("signed.pdf");
std::ifstream f("signed.pdf", std::ios::binary);
std::vector<std::uint8_t> pdf_bytes((std::istreambuf_iterator<char>(f)), {});
for (int i = 0; i < doc.get_signature_count(); ++i) {
auto sig = doc.get_signature(i);
std::cout << sig.signer_name() << " → " << sig.verify() << "\n"; // 1 valid / 0 invalid / -1 unknown
std::cout << "detached ok = " << sig.verify_detached(pdf_bytes) << "\n";
}
Swift
import PdfOxide
import Foundation
let doc = try Document.open("signed.pdf")
let pdfBytes = [UInt8](try Data(contentsOf: URL(fileURLWithPath: "signed.pdf")))
for i in 0..<(try doc.signatureCount()) {
guard let sig = try doc.signature(i) else { continue }
print(try sig.signerName(), "→", try sig.verify()) // 1 valid / 0 invalid / -1 unknown
print("detached ok =", try sig.verifyDetached(pdfBytes))
}
Dart
import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('signed.pdf');
final pdfBytes = File('signed.pdf').readAsBytesSync();
for (var i = 0; i < doc.getSignatureCount(); i++) {
final sig = doc.getSignature(i);
print('${sig.signerName} → ${sig.verify()}'); // 1 valid / 0 invalid / -1 unknown
print('detached ok = ${sig.verifyDetached(pdfBytes)}');
}
R
library(pdfoxide)
doc <- pdf_open("signed.pdf")
pdf_bytes <- readBin("signed.pdf", "raw", file.info("signed.pdf")$size)
for (i in seq_len(pdf_signature_count(doc)) - 1L) {
sig <- pdf_get_signature(doc, i)
cat(pdf_signature_signer_name(sig), "→", pdf_signature_verify(sig), "\n") # 1 valid / 0 invalid / -1 unknown
cat("detached ok =", pdf_signature_verify_detached(sig, pdf_bytes), "\n")
}
Julia
using PdfOxide
doc = open_document("signed.pdf")
pdf_bytes = read("signed.pdf")
for i in 0:(get_signature_count(doc) - 1)
sig = get_signature(doc, i)
println(signature_get_signer_name(sig), " → ", signature_verify(sig)) # 1 valid / 0 invalid / -1 unknown
println("detached ok = ", signature_verify_detached(sig, pdf_bytes))
end
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("signed.pdf");
const pdf_bytes = try std.fs.cwd().readFileAlloc(a, "signed.pdf", 1 << 30);
var i: i32 = 0;
const count = try doc.signatureCount();
while (i < count) : (i += 1) {
var sig = try doc.signature(i);
const name = try sig.signerName(a);
std.debug.print("{s} → {}\n", .{ name, try sig.verify() }); // 1 valid / 0 invalid / -1 unknown
std.debug.print("detached ok = {}\n", .{try sig.verifyDetached(pdf_bytes)});
}
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"signed.pdf" error:&err];
NSData *pdfBytes = [NSData dataWithContentsOfFile:@"signed.pdf"];
int32_t count = [doc signatureCountWithError:&err];
for (int32_t i = 0; i < count; i++) {
POXSignatureInfo *sig = [doc signatureAtIndex:i error:&err];
NSLog(@"%@ → %d", [sig signerNameError:&err], [sig verifyError:&err]); // 1 valid / 0 invalid / -1 unknown
NSLog(@"detached ok = %d", [sig verifyDetached:pdfBytes error:&err]);
}
Elixir
{:ok, doc} = PdfOxide.open("signed.pdf")
pdf_bytes = File.read!("signed.pdf")
{:ok, count} = PdfOxide.signature_count(doc)
for i <- 0..(count - 1) do
{:ok, sig} = PdfOxide.signature(doc, i)
{:ok, name} = PdfOxide.signature_signer_name(sig)
{:ok, verdict} = PdfOxide.signature_verify(sig) # 1 valid / 0 invalid / -1 unknown
{:ok, detached} = PdfOxide.signature_verify_detached(sig, pdf_bytes)
IO.puts("#{name} → #{verdict}")
IO.puts("detached ok = #{detached}")
end
Signature
Перечисление и инспекция подписей. Доступно во всех привязках.
| Свойство / Метод | Описание |
|---|---|
.signer_name / .SignerName |
Common Name из сертификата подписанта. C ABI / Swift: signing_name / signerName(). |
.reason / .Reason |
Причина подписания (например, «Я одобряю этот документ»). C ABI / Swift: signing_reason / signingReason(). |
.location / .Location |
Поле местоположения. C ABI / Swift: signing_location / signingLocation(). |
.contact_info |
Поле контактной информации |
.signing_time / .SigningTime |
UTC-время подписания из CMS-конверта (DateTimeOffset? в C#) |
.pades_level / padesLevel() |
Уровень PAdES, определённый только по атрибутам CMS (B_B/B_T). Для B_LT дополнительно нужен /DSS — используйте вместе с dss ниже. |
.has_timestamp() / hasTimestamp() |
True, если подпись содержит встроенную метку времени RFC 3161. |
.add_timestamp(ts) / addTimestamp(_:) |
Прикрепляет разобранный Timestamp к подписи (C ABI / Swift). |
.verify() / .Verify() |
Проверка атрибутов подписанта по RFC 5652 §5.4 против встроенного сертификата. Возвращает Valid / Invalid / Unknown. |
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) |
Добавляет проверку messageDigest по RFC 5652 §11.2 против переданных байтов документа. |
.get_certificate() / .GetCertificate() |
Возвращает объект Certificate для детальной инспекции. |
На уровне документа verify_all_signatures() / verifyAllSignatures() выполняет проверку подписанта для каждой подписи и возвращает единый сводный результат: 1 = все действительны, 0 = хотя бы одна недействительна, -1 = хотя бы одна неизвестна/не поддерживается.
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("signed.pdf")?;
for sig in doc.signatures() {
println!("level={:?} timestamped={}", sig.pades_level(), sig.has_timestamp());
}
Certificate
Сертификат X.509, извлечённый из бинарного блока /Contents с помощью x509-parser. Доступно во всех привязках начиная с v0.3.38 (аксессоры Python / Go / WASM появились сразу после первого выпуска).
| Свойство / Метод | Описание |
|---|---|
.subject |
Distinguished Name владельца сертификата |
.issuer |
DN выдающего удостоверяющего центра |
.serial |
Серийный номер (в виде big-endian байт или строки) |
.not_before |
Начало срока действия (DateTimeOffset) |
.not_after |
Конец срока действия |
.is_valid |
True, если not_before ≤ now ≤ not_after |
Dss — Document Security Store
/DSS (ISO 32000-2 §12.8.4.3) хранит материалы для долгосрочной проверки PAdES-B-LT подписей: DER-сертификаты на уровне документа, CRL, OCSP-ответы и ключи VRI для каждой подписи. Читается из документа; None/null означает, что PDF не содержит DSS (не является ошибкой). Доступно в Rust, Python, Go, C# и Swift.
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::signatures::read_dss;
let doc = PdfDocument::open("ltv.pdf")?;
if let Some(dss) = read_dss(&doc)? {
println!("certs={} crls={} ocsps={} vri={}",
dss.certificates.len(), dss.crls.len(), dss.ocsp_responses.len(), dss.vri.len());
}
Python
import pdf_oxide
doc = pdf_oxide.PdfDocument("ltv.pdf")
dss = doc.dss()
if dss is not None:
print("certs", len(dss.certs), "crls", len(dss.crls),
"ocsps", len(dss.ocsps), "vri", len(dss.vri))
Go
doc, _ := pdfoxide.Open("ltv.pdf")
defer doc.Close()
dss, _ := doc.DSS() // nil when the PDF has no /DSS
if dss != nil {
fmt.Printf("certs=%d crls=%d ocsps=%d vri=%d\n",
len(dss.Certs), len(dss.CRLs), len(dss.OCSPs), dss.VRICount)
}
C#
using var doc = PdfDocument.Open("ltv.pdf");
var dss = doc.GetDss(); // null when the PDF has no /DSS
if (dss is not null)
{
Console.WriteLine($"certs={dss.Certificates.Count} crls={dss.Crls.Count} " +
$"ocsps={dss.OcspResponses.Count} vri={dss.VriCount}");
}
Swift
if let dss = try doc.dss() {
print("certs=\(try dss.certCount()) crls=\(try dss.crlCount()) " +
"ocsps=\(try dss.ocspCount()) vri=\(try dss.vriCount())")
}
C++
try {
auto dss = doc.get_dss(); // throws if the PDF has no /DSS
std::cout << "certs=" << dss.cert_count() << " crls=" << dss.crl_count()
<< " ocsps=" << dss.ocsp_count() << " vri=" << dss.vri_count() << "\n";
} catch (const pdf_oxide::Error&) {
// no DSS present
}
Dart
try {
final dss = doc.getDss(); // throws if the PDF has no /DSS
print('certs=${dss.certCount} crls=${dss.crlCount} '
'ocsps=${dss.ocspCount} vri=${dss.vriCount}');
} on PdfOxideError {
// no DSS present
}
R
dss <- pdf_get_dss(doc) # NULL when the PDF has no /DSS
if (!is.null(dss)) {
cat("certs=", pdf_dss_cert_count(dss), "crls=", pdf_dss_crl_count(dss),
"ocsps=", pdf_dss_ocsp_count(dss), "vri=", pdf_dss_vri_count(dss), "\n")
}
Julia
dss = document_get_dss(doc) # nothing when the PDF has no /DSS
if dss !== nothing
println("certs=", dss_cert_count(dss), " crls=", dss_crl_count(dss),
" ocsps=", dss_ocsp_count(dss), " vri=", dss_vri_count(dss))
end
Zig
var dss = try doc.dss(); // error if the PDF has no /DSS
std.debug.print("certs={} crls={} ocsps={} vri={}\n", .{
try dss.certCount(), try dss.crlCount(), try dss.ocspCount(), try dss.vriCount(),
});
Objective-C
POXDss *dss = [doc dssWithError:&err]; // nil when the PDF has no /DSS
if (dss != nil) {
NSLog(@"certs=%d crls=%d ocsps=%d vri=%d",
[dss certCount], [dss crlCount], [dss ocspCount], [dss vriCount]);
}
Elixir
case PdfOxide.document_dss(doc) do
{:ok, dss} ->
IO.puts("certs=#{PdfOxide.dss_cert_count(dss)} crls=#{PdfOxide.dss_crl_count(dss)} " <>
"ocsps=#{PdfOxide.dss_ocsp_count(dss)} vri=#{PdfOxide.dss_vri_count(dss)}")
_ -> :no_dss # the PDF has no /DSS
end
| Метод C ABI / Swift | Эквивалент Python / Go / C# | Описание |
|---|---|---|
cert_count() |
len(dss.certs) / len(dss.Certs) / dss.Certificates.Count |
DER-сертификаты уровня документа (/Certs) |
crl_count() |
len(dss.crls) / len(dss.CRLs) / dss.Crls.Count |
DER-списки отзыва уровня документа (/CRLs) |
ocsp_count() |
len(dss.ocsps) / len(dss.OCSPs) / dss.OcspResponses.Count |
DER OCSP-ответы уровня документа (/OCSPs) |
vri_count() |
len(dss.vri) / dss.VRICount / dss.VriCount |
Записи /VRI для каждой подписи (SHA-1 от /Contents в верхнем регистре hex) |
Чтобы проверить наличие архивной метки /DocTimeStamp формата B-LTA (сигнал уровня документа, который pades_level не сообщает), вызовите has_document_timestamp(pdf_bytes) (Rust/Python), doc.HasDocumentTimestamp() (Go/C#) или doc.hasTimestamp() (Swift).
Timestamp — RFC 3161 TSTInfo
Разбирает бинарный блок метки времени из атрибута TimeStampToken подписи или из автономного ответа RFC 3161. Доступно во всех привязках (поддержка Node появилась после выпуска v0.3.38).
Rust
use pdf_oxide::Timestamp;
let ts = Timestamp::parse(&tst_bytes)?;
println!("{} serial={} tsa={}", ts.time(), ts.serial(), ts.tsa_name());
Python
from pdf_oxide import Timestamp
ts = Timestamp.parse(tst_bytes)
print(ts.time, ts.serial, ts.policy_oid, ts.tsa_name, ts.hash_algorithm)
C#
var ts = Timestamp.Parse(tstBytes);
Console.WriteLine($"{ts.Time} serial={ts.Serial} tsa={ts.TsaName}");
Node / TypeScript
import { Timestamp } from "pdf-oxide";
const ts = Timestamp.parse(tstBytes);
console.log(ts.time, ts.serial, ts.policyOid, ts.tsaName, ts.hashAlgorithm);
ts.close();
Go
ts, _ := pdfoxide.ParseTimestamp(tstBytes)
fmt.Println(ts.Time, ts.Serial, ts.PolicyOid, ts.TsaName, ts.HashAlgorithm)
WASM
import init, { Timestamp } from "pdf-oxide-wasm/web";
await init();
const ts = Timestamp.parse(tstBytes);
console.log(ts.time, ts.serial, ts.policyOid, ts.tsaName, ts.hashAlgorithm);
C++
auto ts = pdf_oxide::Timestamp::parse(tst_bytes);
std::cout << ts.time() << " serial=" << ts.serial() << " tsa=" << ts.tsa_name()
<< " policy=" << ts.policy_oid() << " hash=" << ts.hash_algorithm() << "\n";
Swift
let ts = try Timestamp.parse(tstBytes)
print(try ts.time(), "serial=\(try ts.serial())", "tsa=\(try ts.tsaName())",
"policy=\(try ts.policyOid())", "hash=\(try ts.hashAlgorithm())")
Dart
final ts = Timestamp.parse(tstBytes);
print('${ts.time} serial=${ts.serial} tsa=${ts.tsaName} '
'policy=${ts.policyOid} hash=${ts.hashAlgorithm}');
R
ts <- pdf_timestamp_parse(tst_bytes)
cat(pdf_timestamp_time(ts), "serial=", pdf_timestamp_serial(ts),
"tsa=", pdf_timestamp_tsa_name(ts), "policy=", pdf_timestamp_policy_oid(ts),
"hash=", pdf_timestamp_hash_algorithm(ts), "\n")
Julia
ts = timestamp_parse(tst_bytes)
println(timestamp_get_time(ts), " serial=", timestamp_get_serial(ts),
" tsa=", timestamp_get_tsa_name(ts), " policy=", timestamp_get_policy_oid(ts),
" hash=", timestamp_get_hash_algorithm(ts))
Zig
const a = std.heap.page_allocator;
var ts = try pdf_oxide.Timestamp.parse(tst_bytes);
const serial = try ts.serial(a);
const tsa = try ts.tsaName(a);
std.debug.print("{} serial={s} tsa={s} hash={}\n", .{
try ts.time(), serial, tsa, try ts.hashAlgorithm(),
});
Objective-C
POXTimestamp *ts = [POXTimestamp parse:tstBytes error:&err];
NSLog(@"%lld serial=%@ tsa=%@ policy=%@ hash=%d",
[ts timeError:&err], [ts serialError:&err], [ts tsaNameError:&err],
[ts policyOidError:&err], [ts hashAlgorithmError:&err]);
Elixir
{:ok, ts} = PdfOxide.timestamp_parse(tst_bytes)
{:ok, time} = PdfOxide.timestamp_time(ts)
{:ok, serial} = PdfOxide.timestamp_serial(ts)
{:ok, tsa} = PdfOxide.timestamp_tsa_name(ts)
IO.puts("#{time} serial=#{serial} tsa=#{tsa}")
| Свойство | Описание |
|---|---|
.time |
UTC-время, удостоверённое TSA |
.serial |
Уникальный серийный номер этой метки времени |
.policy_oid |
OID политики TSA |
.tsa_name |
Идентификатор TSA |
.hash_algorithm |
Алгоритм хэширования, используемый для message_imprint |
.message_imprint |
Хэш подписываемой полезной нагрузки |
.verify() |
Проверяет подпись TSTInfo против встроенного сертификата TSA |
TsaClient — HTTP-клиент RFC 3161
Запрашивает новую метку времени у службы меток времени (TSA) по HTTP. Требует Cargo-фичи tsa-client. Доступно во всех привязках, кроме WASM (поддержка Node появилась после v0.3.38; WASM намеренно не поддерживается — ureq не компилируется в wasm32).
Rust
use pdf_oxide::TsaClient;
let client = TsaClient::new("https://freetsa.org/tsr")
.with_timeout(std::time::Duration::from_secs(30))
.with_hash_algorithm(pdf_oxide::HashAlgorithm::Sha256)
.with_nonce(true);
let ts = client.request_timestamp(&pdf_bytes)?;
println!("{} serial={}", ts.time(), ts.serial());
Python
from pdf_oxide import TsaClient
client = TsaClient(
url="https://freetsa.org/tsr",
username=None,
password=None,
timeout_seconds=30,
hash_algorithm=2, # 2 = SHA-256
use_nonce=True,
cert_req=True,
)
ts = client.request_timestamp(pdf_bytes)
print(ts.time, ts.serial)
Node / TypeScript
import { TsaClient } from "pdf-oxide";
const client = new TsaClient({
url: "https://freetsa.org/tsr",
timeoutSeconds: 30,
hashAlgorithm: 2, // 2 = SHA-256
useNonce: true,
certReq: true,
});
const ts = client.requestTimestamp(pdfBytes);
console.log(ts.time, ts.serial);
ts.close();
client.close();
C#
var client = new TsaClient("https://freetsa.org/tsr");
var ts = client.RequestTimestamp(pdfBytes);
Console.WriteLine($"{ts.Time} serial={ts.Serial}");
Go
client := pdfoxide.NewTsaClient("https://freetsa.org/tsr")
ts, err := client.RequestTimestamp(pdfBytes)
if err != nil { log.Fatal(err) }
fmt.Println(ts.Time, ts.Serial)
C++
auto client = pdf_oxide::TsaClient::create("https://freetsa.org/tsr");
auto ts = client.request_timestamp(pdf_bytes);
std::cout << ts.time() << " serial=" << ts.serial() << "\n";
Swift
let client = try TsaClient.create(url: "https://freetsa.org/tsr")
let ts = try client.requestTimestamp(pdfBytes)
print(try ts.time(), "serial=\(try ts.serial())")
Dart
final client = TsaClient.create('https://freetsa.org/tsr');
final ts = client.requestTimestamp(pdfBytes);
print('${ts.time} serial=${ts.serial}');
R
client <- pdf_tsa_client_create("https://freetsa.org/tsr")
ts <- pdf_tsa_request_timestamp(client, pdf_bytes)
cat(pdf_timestamp_time(ts), "serial=", pdf_timestamp_serial(ts), "\n")
Julia
client = tsa_client_create("https://freetsa.org/tsr")
ts = tsa_request_timestamp(client, pdf_bytes)
println(timestamp_get_time(ts), " serial=", timestamp_get_serial(ts))
Zig
const a = std.heap.page_allocator;
var client = try pdf_oxide.TsaClient.create("https://freetsa.org/tsr", "", "", 30, 0, true, true);
var ts = try client.requestTimestamp(pdf_bytes);
const serial = try ts.serial(a);
std.debug.print("{} serial={s}\n", .{ try ts.time(), serial });
Objective-C
POXTsaClient *client = [POXTsaClient createWithUrl:@"https://freetsa.org/tsr"
username:nil password:nil
timeout:30 hashAlgo:0
useNonce:YES certReq:YES error:&err];
POXTimestamp *ts = [client requestTimestamp:pdfBytes error:&err];
NSLog(@"%lld serial=%@", [ts timeError:&err], [ts serialError:&err]);
Elixir
{:ok, client} = PdfOxide.tsa_client("https://freetsa.org/tsr")
{:ok, ts} = PdfOxide.tsa_request_timestamp(client, pdf_bytes)
{:ok, time} = PdfOxide.timestamp_time(ts)
{:ok, serial} = PdfOxide.timestamp_serial(ts)
IO.puts("#{time} serial=#{serial}")
TsaClient отправляет RFC 3161 TimeStampReq через HTTP POST с одноразовым номером (nonce) и HTTP Basic-аутентификацией (если заданы username / password). Ответ распаковывается и разбирается через Timestamp::parse.
Сводка покрытия привязок
Полное соответствие начиная с v0.3.38: все интерфейсы для работы с подписями доступны в каждой привязке, за единственным намеренным исключением — TsaClient в WASM (ureq несовместим с wasm).
| Интерфейс | Rust | Python | Node | C# | Go | WASM |
|---|---|---|---|---|---|---|
Signature перечисление и проверка |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Certificate инспекция |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Dss (/DSS) инспекция |
✓ | ✓ | — | ✓ | ✓ | — |
Timestamp разбор и проверка |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TsaClient HTTP-запрос |
✓ | ✓ | ✓ | ✓ | ✓ | — |
Подписание (sign_pdf_bytes / PAdES) |
✓ | ✓ | — | ✓ | ✓ | — |
— для TsaClient × WASM является намеренным и постоянным: ureq не компилируется в wasm32. Вызывайте TsaClient из серверной привязки, а сырые байты метки времени передавайте в Timestamp.parse() в WASM, если нужно инспектировать ответ в браузере.
Связанные страницы
- Подписание PDF: цифровые подписи и PAdES — создание подписей CMS и PAdES B-B/B-T/B-LT
- Метаданные и XMP — извлечение метаданных уровня документа
- Справочник API — интерфейс
SignatureVerifierна стороне Rust