Skip to content

Цифровые подписи

Добавлено в 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, если нужно инспектировать ответ в браузере.

Связанные страницы