Skip to content

디지털 서명

v0.3.38에서 추가되었습니다. PDF Oxide는 /AcroForm /Fields → /Sig에서 서명을 읽고, /Contents 내부의 CMS 봉투를 검사하며, 내장 인증서에 대해 RFC 5652 §5.4 서명자 속성 검사를 실행합니다. 또한 호출자가 제공한 문서 바이트에 대해 §11.2 messageDigest 검사도 수행합니다.

범위. 이 페이지는 기존 서명의 읽기 및 검증을 다룹니다: 열거, 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-PSSECDSAUnknown / 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 CMS 봉투의 UTC 서명 시간 (C#에서는 DateTimeOffset?)
.pades_level / padesLevel() CMS 속성만으로 분류된 PAdES 기준선 (B_B/B_T). B_LT는 문서 /DSS도 필요 — 아래 dss와 함께 사용.
.has_timestamp() / hasTimestamp() 이 서명에 RFC 3161 타임스탬프가 내장되어 있으면 True.
.add_timestamp(ts) / addTimestamp(_:) 파싱된 Timestamp를 서명에 첨부합니다 (C ABI / Swift).
.verify() / .Verify() 내장 인증서에 대해 RFC 5652 §5.4 서명자 속성 검사 실행. Valid / Invalid / Unknown 반환.
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) 호출자가 제공한 문서 바이트에 대해 RFC 5652 §11.2 messageDigest 검사를 추가로 실행.
.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

x509-parser를 사용해 /Contents 바이너리 블롭에서 X.509 인증서를 추출합니다. v0.3.38부터 모든 바인딩에서 사용 가능합니다 (Python / Go / WASM 접근자는 초기 릴리스 직후 추가됨).

속성 / 메서드 설명
.subject 인증서 보유자의 Distinguished Name
.issuer 발급 CA의 DN
.serial 일련번호 (빅엔디안 바이트 또는 문자열)
.not_before 유효 기간 시작 (DateTimeOffset)
.not_after 유효 기간 종료
.is_valid not_before ≤ now ≤ not_after일 때 True

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 CRL (/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 항목 (/Contents의 대문자 16진 SHA-1)

B-LTA 아카이브 /DocTimeStamp(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 TSA가 확인한 UTC 시간
.serial 이 타임스탬프의 고유 일련번호
.policy_oid TSA 정책 OID
.tsa_name TSA 식별자
.hash_algorithm message_imprint에 사용된 해시 알고리즘
.message_imprint 서명된 페이로드의 해시
.verify() 내장 TSA 인증서에 대해 TSTInfo 서명 확인

TsaClient — RFC 3161 HTTP 클라이언트

HTTP를 통해 타임스탬프 기관에 새 타임스탬프를 요청합니다. tsa-client Cargo 피처가 필요합니다. 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는 nonce와 HTTP Basic 인증 (username / password가 설정된 경우)을 포함한 RFC 3161 TimeStampReq를 HTTP POST로 전송합니다. 응답은 언래핑되어 Timestamp::parse로 파싱됩니다.

바인딩 지원 현황

v0.3.38 기준 완전 동등 지원: WASM에서의 TsaClient라는 단일 의도적 예외를 제외하고, 모든 서명 인터페이스가 모든 바인딩에서 제공됩니다.

인터페이스 Rust Python Node C# Go WASM
Signature 열거 및 검증
Certificate 검사
Dss (/DSS) 검사
Timestamp 파싱 및 검증
TsaClient HTTP 요청
서명 (sign_pdf_bytes / PAdES)

TsaClient × WASM의 는 의도적이고 영구적입니다: ureqwasm32로 컴파일되지 않습니다. 브라우저에서 응답을 검사해야 한다면 서버 측 바인딩에서 TsaClient를 호출하고, 원시 타임스탬프 바이트를 WASM의 Timestamp.parse()에 전달하세요.

관련 페이지