İçeriğe geç

Doğrulama

Tamamlanan her zarfı birbirinden bağımsız iki kanıt korur:

  1. PDF’in içindeki mühür: herkese açık bir zaman damgası otoritesinden alınan PAdES belge zaman damgası. Adobe Acrobat ve Reader bunu kontrol eder ve belgeyi mühürlendiğinden beri değiştirilmemiş olarak gösterir.
  2. PDF’in dışındaki çapa: belgenin parmak izi, yalnızca ekleme yapılabilen bir Merkle ağacında bir yapraktır ve Bona Fide her ağaç kökünü Ed25519 ile imzalar. Herkes bu kanıtı yayımlanan açık anahtarlarla kontrol edebilir; hesap gerekmez ve bir PDF görüntüleyicisine güvenmek gerekmez.

Son alıcı imzaladığında zarf tamamlanır (envelope.completed) ve mühürleyici devreye girer:

  1. Orijinalin SHA-256 değerini, zarf oluşturulurken kaydedilen değerle karşılaştırır.
  2. Belgeyi düzleştirir: alan değerleri (metin, onay kutuları, çizilen, yazılan veya yüklenen imzalar) sayfa içeriğine çizilir ve her form alanı, ek açıklama, JavaScript eylemi ve gömülü dosya kaldırılır.
  3. Tamamlama sertifikasını ekler: zarf kimliği, orijinalin SHA-256 değeri, gerçekte kullanılan doğrulama yöntemiyle birlikte her imzacı (örneğin E-posta bağlantısı (… adresine gönderilen benzersiz bağlantı) veya E-posta bağlantısı + tek kullanımlık kod; bkz. imzacı doğrulaması), zaman damgaları, IP adresi ve cihaz ile olay zaman çizelgesi. Her sayfaya şu alt bilgi eklenir: Bona Fide Envelope ID … · Verify at bonafidesign.com/verify.
  4. Herkese açık bir zaman damgası otoritesinden (otomatik yedeğe geçişli, herkesçe güvenilen birden fazla sağlayıcı) bir PAdES belge zaman damgası (/DocTimeStamp, ETSI.RFC3161, SHA-256) ekler ve TSA’nın sertifika zincirinin doğrulama verilerini (DSS içinde OCSP/CRL) gömer; böylece belge uzun vadede doğrulanabilir kalır (LTV).
  5. Kendi çıktısını doğrular, saklar ve mühürlü SHA-256 değerini (sealedDocumentHash) ve çapa kaydı için bir yaprağı kaydeder.

Ardından envelope.sealed tetiklenir ve GET /v1/envelopes/{id}/document?version=sealed dosyayı döndürür. Hiçbir TSA kullanılamıyorsa mühürleme yeniden denenir; bu sırada zarf seal: "pending" ile completed durumunda kalır.

Mühürlenen her zarf, aşağıdakilerin kanonik JSON’u (anahtarlar sıralı, boşluksuz) olan bir yaprak ekler:

{
"completed_at": "2026-10-13T14:09:52.004Z",
"envelope_id": "env_8Gm2kQ4rT1",
"event_chain_head": "<head of the envelope's audit hash chain>",
"original_document_sha256": "<SHA-256 of the document as sent>",
"sealed_document_sha256": "<SHA-256 of the sealed PDF>"
}

Her saat, bir önceki çapadan bu yana kaydedilen yapraklar bir Merkle ağacına dönüşür (RFC 6962 / RFC 9162: leaf = SHA-256(0x00 ‖ leaf JSON), node = SHA-256(0x01 ‖ left ‖ right)) ve kökü imzalı bir çapa olarak yayımlanır:

GET /v1/public/anchors/latest
{
"id": "…",
"version": "bona-fide-anchor-v1",
"sequence": 412,
"root": "<hex Merkle root>",
"previous_root": "<root of anchor 411>",
"leaf_count": 37,
"created_at": "2026-10-13T15:00:00.118Z",
"key_id": "<first 16 hex of SHA-256(public key)>",
"signature": "<base64url Ed25519 signature>",
"signed_message": "bona-fide-anchor-v1|412|<root>|<previous_root>|2026-10-13T15:00:00.118Z|37"
}

Ed25519 imzası bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8) değerini kapsar. previous_root her çapayı bir öncekine zincirler (ilki 64 sıfırı gösterir); böylece bir çapa zincir bozulmadan kaldırılamaz veya yeniden sıralanamaz ve çapa tabloları yalnızca ekleme yapılabilir.

İmzalama anahtarları geçerlilik dönemleriyle birlikte yayımlanır; eski çapaların doğrulanabilir kalması için kullanımdan kaldırılan anahtarlar listede kalır:

GET /.well-known/bona-fide/keys.json
{
"issuer": "Bona Fide",
"anchor_format": "bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8, Ed25519; root = RFC 6962 SHA-256 Merkle tree)",
"keys": [
{
"key_id": "<16 hex>",
"algorithm": "Ed25519",
"public_key": "<base64url raw 32-byte key>",
"public_key_pem": "-----BEGIN PUBLIC KEY-----…",
"valid_from": "2026-10-14T00:00:00.000Z",
"valid_until": null,
"status": "active"
}
]
}

En hızlı yol herkese açık sayfadır: bonafidesign.com/verify. Sayfa PDF’in hash’ini tarayıcınızda hesaplar (dosya hiçbir zaman yüklenmez), hash’i sorgular ve kanıtı ile imzayı cihazınızda kontrol eder. Çapa kaydı her çapayı listeler.

Aynı sorgu herkese açık bir API olarak da sunulur: anahtar gerekmez, her kaynaktan çağrılabilir, IP başına hız limitine tabidir. Yalnızca SHA-256 değerini gönderin:

Terminal window
curl https://api.bonafidesign.com/v1/public/verify \
-H "Content-Type: application/json" \
-d "{\"sha256\": \"$(shasum -a 256 signed.pdf | cut -d' ' -f1)\"}"
{
"sha256": "…",
"status": "verified",
"results": [
{
"status": "verified",
"matched": "sealed_document",
"envelope": {
"id": "env_8Gm2kQ4rT1",
"status": "completed",
"completed_at": "2026-10-13T14:09:52.004Z",
"sealed_at": "2026-10-13T14:10:31.442Z",
"organization_name": "Acme Robotics, Inc.",
"signer_count": 1,
"signers": [{ "name": "Jane Doe", "email": "jane@acme.dev", "role": "client", "verification_method": "Email OTP", "signed_at": "…" }],
"verification_methods": ["Email OTP"],
"test_mode": false
},
"leaf": { "envelope_id": "env_8Gm2kQ4rT1", "…": "…" },
"leaf_hash": "…",
"proof": { "leaf_index": 12, "tree_size": 37, "hashes": ["…", "…"] },
"anchor": { "sequence": 412, "…": "…" },
"links": { "anchor": "…", "keys": "…/.well-known/bona-fide/keys.json" }
}
]
}

status değeri verified, pending_anchor (tamamlandı ve kaydedildi, saatlik bir sonraki çapa bekleniyor) veya not_found olur: hiçbir tamamlanmış zarf bu dosyanın ta kendisine sahip değildir; yani bu dosya imzalanan belge değildir (veya o zamandan beri değiştirilmiştir). Hem mühürlü dosya hem de gönderildiği hâliyle orijinal eşleşir.

API’nin sözüne güvenmek zorunda değilsiniz: yanıtı kontrol etmek için gereken her şey yanıtta ve keys.json içindedir.

  1. Belge: dosyanızın SHA-256 değeri leaf.sealed_document_sha256 (veya original_document_sha256) değerine eşittir.
  2. Yaprak: SHA-256(0x00 ‖ canonical JSON of leaf) değeri leaf_hash değerine eşittir.
  3. Dahil olma: yaprak hash’inden başlayarak proof.hashes üzerinde ilerlemek (RFC 9162 §2.1.3.2, leaf_index ve tree_size == anchor.leaf_count ile) anchor.root değerini verir.
  4. Anahtar: anchor.key_id keys.json içindedir, anahtar kimliği ham açık anahtarın SHA-256 değerinin ilk 16 onaltılık karakteridir ve anchor.created_at anahtarın geçerlilik dönemi içindedir.
  5. İmza: Ed25519 imzası bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count üzerinde doğrulanır.
# pip install requests cryptography
import base64, hashlib, json, sys, requests
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API = "https://api.bonafidesign.com"
b64url = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
sha = lambda b: hashlib.sha256(b).digest()
def root_from_proof(leaf: bytes, index: int, size: int, path: list[bytes]) -> bytes:
fn, sn, r = index, size - 1, leaf
for p in path:
if sn == 0: raise ValueError("proof too long")
if fn % 2 == 1 or fn == sn:
r = sha(b"\x01" + p + r)
while fn % 2 == 0 and fn != 0:
fn, sn = fn // 2, sn // 2
else:
r = sha(b"\x01" + r + p)
fn, sn = fn // 2, sn // 2
if sn != 0: raise ValueError("proof too short")
return r
digest = hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest()
res = requests.post(f"{API}/v1/public/verify", json={"sha256": digest}).json()
keys = {k["key_id"]: k for k in requests.get(f"{API}/.well-known/bona-fide/keys.json").json()["keys"]}
for m in res["results"]:
leaf, proof, anchor = m["leaf"], m["proof"], m["anchor"]
if not anchor:
print("pending anchor"); continue
assert digest in (leaf["sealed_document_sha256"], leaf["original_document_sha256"])
leaf_json = json.dumps(leaf, sort_keys=True, separators=(",", ":")).encode()
leaf_hash = sha(b"\x00" + leaf_json)
assert proof["tree_size"] == anchor["leaf_count"]
root = root_from_proof(leaf_hash, proof["leaf_index"], proof["tree_size"], [bytes.fromhex(h) for h in proof["hashes"]])
assert root.hex() == anchor["root"], "not included under the anchor root"
key = keys[anchor["key_id"]]
raw = b64url(key["public_key"])
assert hashlib.sha256(raw).hexdigest()[:16] == anchor["key_id"]
message = "|".join([anchor["version"], str(anchor["sequence"]), anchor["root"], anchor["previous_root"],
anchor["created_at"], str(anchor["leaf_count"])]).encode()
Ed25519PublicKey.from_public_bytes(raw).verify(b64url(anchor["signature"]), message) # raises if invalid
print(f"VERIFIED {leaf['envelope_id']} in anchor #{anchor['sequence']}")

Yaprakta yalnızca ASCII anahtarlı dize değerleri bulunur; bu nedenle json.dumps(…, sort_keys=True, separators=(",", ":")) aynı kanonik JSON’u verir. Anahtarın valid_from / valid_until değerlerini de anchor["created_at"] ile karşılaştırın.

Çapalar GET /v1/public/anchors?limit=20&before=<sequence> ile en yeniden eskiye doğru listelenir (geriye doğru sayfalamak için next_before iletin) ve GET /v1/public/anchors/{sequence} ile tek tek getirilir.