cd ..

X.509 Encryption Service

Context: Independent, 2025 | Live: encryption.yaqoobahmed.com (opens in new tab) | Runtime: Netlify Functions (Node.js, ESM) | Scope: Teaching demo, not production crypto
Node.js (ESM) Netlify Functions esbuild X.509 (self-signed CA) RSA-OAEP node-forge Live demo

Most engineers consume PKI: they accept a server certificate, trust the TLS handshake and move on. I wanted to understand it from the inside, so I built the smallest system that exercises every layer: a script that creates a certificate authority and signs a server certificate with it, and a serverless service that uses that certificate's key pair for application-layer encryption on top of TLS. It's live as a public demo.

The service exposes two endpoints, /api/encrypt and /api/decrypt, on Netlify Functions. The interesting parts are below the surface: issuing the X.509 certificates, choosing the padding, and getting ESM code to bundle cleanly into a serverless target.

Architecture

Client
[ Browser / curl ]
  │ POST /api/encrypt { "data": … }
Serverless functions (Netlify)
[ /api/encrypt ]
  ├──► public key from server cert
  └──► RSA-OAEP encrypt ──► base64
[ /api/decrypt ]
  ├──► server private key
  └──► RSA-OAEP decrypt ──► plaintext
PKI
[ Self-signed teaching CA ]
  └──► signs [ Server certificate ]
       └──► [ RSA key pair ]
Throwaway demo keys · shipped with
the function · trusted nowhere

Why Build It

I built this to understand X.509 well enough to read a certificate without a parser: which OIDs map to which fields, why OAEP is preferred over PKCS#1 v1.5 padding, and what serverless bundlers do to ESM imports under the hood. The fastest way to learn that was to build the smallest system that touches every layer.

Public key infrastructure from scratch

A Node script generates a CA with a 2048-bit RSA key pair, marks it as a CA with the Basic Constraints extension, self-signs it, and uses it to sign a server certificate with SHA-256. The service encrypts with the public key from that certificate and decrypts with the matching private key. Building the CA and its certificate by hand made the chain-validation logic in the TLS Certificate Chain Inspector click in a way that reading the spec hadn't.

OAEP, not PKCS#1 v1.5

RSA encryption needs padding. The classic option is PKCS#1 v1.5; the modern one is OAEP (Optimal Asymmetric Encryption Padding). PKCS#1 v1.5 encryption is open to padding-oracle attacks: Bleichenbacher's attack and its many variants showed that an adversary who can tell decryption errors apart can recover plaintext. OAEP was designed to resist that class of attack.

Decision: OAEP, even though PKCS#1 v1.5 would have been simpler. Cryptographic choices ratchet one way: once clients depend on a deprecated mode, replacing it means coordinating with every one of them. Pick the right mode the first time.

ESM in a bundled serverless target

Netlify Functions historically ran CommonJS. This code is ESM (.mjs files, import / export, "type": "module" in package.json) and is bundled with esbuild. Bundling ESM for a serverless target surfaces subtle problems: import.meta.url resolves differently in local development than in bundled output, __dirname doesn't exist in ESM, and node-forge's CommonJS exports need the right import shape.

A small path-resolution shim works in both contexts, node-forge loads through the bundler without require() escape hatches, and the certificate and key files ship inside the function bundle rather than in environment variables, which AWS Lambda (and so Netlify Functions) caps at 4 KB in total across all variables.

API

Encrypt

POST /api/encrypt

Request: { "data": "hello world" }

Response: { "originalData": "hello world", "encryptedData": "<base64 RSA-OAEP ciphertext>", … }

Decrypt

POST /api/decrypt

Request: { "encryptedData": "<base64 from /api/encrypt>" }

Response: { "encryptedData": "…", "decryptedData": "hello world", … }

Stateless: the server remembers nothing between calls.

Limitations Acknowledged

This is a teaching artefact, not production cryptography for a real workload:

What I Took From It

Three things this project taught me concretely, beyond what reading the specs would have:

Try it: encryption.yaqoobahmed.com (opens in new tab). The encrypt and decrypt endpoints round-trip in the browser.