VeriDoc

VeriDoc — Secure Document Verification System

What it is: a web-based system that lets authorized organizations issue digitally signed PDF documents, and lets anyone verify their authenticity and integrity via a QR code or a PDF upload — without contacting the issuing organization.

Also known as the final-year project: "Design and Implementation of a Secure Document Verification System Using QR Codes and Digital Signatures."

Don't pretend it's an AI project. Its value is that it proves I understand security fundamentals — authentication, authorization, cryptography, integrity, secure workflows, auditability, testing, backend engineering — before moving into AI security. It is version 0 of the security-engineering story, not an artifact to discard.

Source material: 00 PROJECT PROPOSAL · 01 COVER PAGE · 02 PRELIMINARY PAGES · 03 CHAPTER ONE · 04 CHAPTER TWO · 05 CHAPTER THREE · 06 CHAPTER FOUR · 07 CHAPTER FIVE · 08 APPENDICES


What it demonstrates

Application security: authentication → authorization → cryptography → integrity → secure workflows → auditability → testing → backend engineering.

Built with React, Node.js, Express, PostgreSQL, Prisma, better-auth, UploadThing, PDF-lib, Vitest, using SHA-256 hashing and RSA-2048 digital signatures, with AES-256-GCM protecting private keys.


System specification

Purpose

Threat model

Defended against:

Out of scope / not defended:

Trust model

Actors

Two major external actors:

Architecture

Layered web application on the PERN stack:

  1. Presentation — React frontend.
  2. Application — Node.js + Express.js backend (routes, controllers, middleware, services).
  3. Authentication & Organization — better-auth.
  4. Cryptographic — SHA-256 hashing + RSA signature services.
  5. Data — PostgreSQL accessed through Prisma.
  6. Document storage — external file-storage service (UploadThing); the DB stores metadata and references.
  7. Public verification interface — public-facing, unauthenticated.

General flow: Client → React → Express API → { Prisma/PostgreSQL, Crypto service, File storage }.

Authentication

Handled by better-auth: user registration, login, logout, session management, protected routes, and account management. Backed by the User, Session, Account and Verification entities.

Authorization

Key management

Signing process

  1. Authorized organization user uploads a PDF.
  2. The system validates the file.
  3. A unique verification token is generated — crypto.randomUUID().
  4. A public verification URL is built: {host}/verification?token={token}.
  5. The URL is encoded into a QR code (PNG).
  6. The QR code is embedded into the first page of the PDF (PDF-lib).
  7. The SHA-256 hash is computed over the final QR-embedded PDF.
  8. The hash is signed with the organization's RSA private key: crypto.createSign("SHA256") → base64 signature (RSA with Node's default PKCS#1 v1.5 padding).
  9. The final PDF is stored in the file-storage service.
  10. Metadata is recorded (title, filename, organization, hash, signature, token, status).

Ordering matters: the QR code is embedded before hashing and signing, so the cryptographic information corresponds to the final issued PDF.

Verification process

QR-code verification

  1. Verifier scans the QR code → URL containing the token.
  2. The token locates the document record and the issuing organization.
  3. The organization's public key and the stored PDF are retrieved.
  4. The required hash is computed and the digital signature verified with the public key; the document status is checked.
  5. A result is returned and the attempt is logged.

QR verification verifies the server-stored document associated with the token. The server does not receive a separate PDF from the scanner.

PDF-upload verification

  1. Verifier submits a PDF through the public interface.
  2. The server computes its SHA-256 hash and searches for an exact matching record.
  3. If matched: signature and status are verified.
  4. If not matched: fuzzy matching (TLSH) attempts to identify a possibly modified version (broad threshold, e.g. diff < 300), refined by text extraction.
  5. A result is returned and the attempt is logged.

Verification result states

The five modelled document states are:

Separately, an operational failure to retrieve the stored document from storage surfaces as a STORAGE ERROR result (UI: "STORAGE ERROR"), rather than as one of the document states above.

QR token design

PDF processing

Database model

PostgreSQL via Prisma. Nine major entities: User, Session, Account, Verification, Organization, Member, Invitation, SignedDocument, VerificationLog.

Security assumptions

Known limitations

  1. PDF only — Word, spreadsheets, images are out of scope.
  2. No HSM / enterprise key management — private keys are protected in-application only.
  3. Verification depends on the service — if the service, DB or storage is unavailable, real-time verification is impossible.
  4. Requires internet connectivity.
  5. No external institutional database integration.
  6. DB stores references + metadata; PDFs live in the storage service; no large-scale archival.
  7. Crypto scope limited to SHA-256 + RSA.
  8. Not a formal audit or penetration test.
  9. Not all paths tested — large files, concurrency and rate-limiting need further evaluation.
  10. Test-coverage limits: frontend statement coverage 63.32%; some PDF tests mocked; no complete end-to-end signing→verification→revocation test.

Open decisions

Resolved (now documented from the implementation):

Still open / not documented in the thesis:

Deployment

Not documented in the thesis. Known environment: Linux-based development, separate frontend and backend connected through the backend API, PostgreSQL, external file storage (UploadThing), and a verification host derived from the request origin. [TODO: document the actual deployment/staging/production setup.]

Backup / recovery

[TODO: not documented.] Recommendation from Chapter 5: organizations should maintain reliable document storage, backup and recovery procedures, because QR verification depends on retrieving the stored document associated with a verification record.

Incident response

[TODO: not documented.] Related Chapter 5 recommendations: regular security testing (especially after changes to auth, authorization, document processing or crypto), restrict access to private signing keys, and establish key backup, recovery and replacement procedures.

Future changes (Chapter 5 suggestions)


Test evidence

Cryptographic tests (CT01–CT09, all passed): RSA key-pair generation; identical/different content hashing; valid signature verification; modified signed data fails; wrong public key fails; modified signature fails; private-key encrypt/decrypt; tampered ciphertext fails.

Document & authorization tests (DT01–DT09, all passed): validation rejection; non-PDF rejected; valid signing; signing without auth → 401; without membership / insufficient privileges → 403; missing organization keys rejected; unauthorized revocation rejected; authorized revocation succeeds.

Public verification tests (VT01–VT06, all passed): original → AUTHENTIC CREDENTIAL; revoked → REVOKED CREDENTIAL; modified → MODIFIED VERSION DETECTED; tampered → TAMPERED / INVALID; unregistered → NOT REGISTERED; storage failure → STORAGE ERROR.

Cross-organization key test: a document signed with one organization's private key could not be verified with another organization's public key.


Candidate next step

Turn VeriDoc into a Secure AI Document Verification Agent: receive a document → inspect → retrieve records → verify signatures → explain discrepancies → request evidence → produce a report.

Then attack it: PDF prompt injection, manipulated extraction, fake approvals, unauthorized tool calls, memory poisoning, exfiltration, approval bypass, confused-deputy. Document each as:

Attack → Vulnerability → Exploit → Impact → Mitigation → Test

That upgrades the signal from "I can build a web app" to "I understand how to secure an AI-enabled application."

Architecture decisions to record (ADRs)

Now that the decisions exist, capture the reasoning, not just the outcome:

Goal of these docs: someone should be able to disagree with me without having to ask what I meant.