Part 8 — Layer 6: Presentation
Job
Make sure both sides agree on what the bytes mean: character encoding, data serialisation, compression, and encryption.
In our story
Four presentation transformations happen to your order before it hits the wire:
JS object { cartId: "c_8812", paymentMethodId: "pm_77" }
│ 1. SERIALISE JSON.stringify → text
▼
'{"cartId":"c_8812","paymentMethodId":"pm_77"}'
│ 2. ENCODE text → UTF-8 bytes (43 bytes)
▼
7b 22 63 61 72 74 49 64 22 3a 22 63 5f 38 38 31 32 22 ...
│ 3. COMPRESS headers compressed with HPACK (HTTP/2); body too small to gzip
▼
│ 4. ENCRYPT TLS 1.3 record: AES-128-GCM with this session's keys
▼
17 03 03 00 5a <90 bytes of ciphertext + 16-byte authentication tag>
On the server, the ALB decrypts (TLS terminates there). It then either re-encrypts towards the pod (end-to-end TLS) or sends plaintext inside the VPC. Finally the pod's framework decompresses, decodes, and deserialises the bytes back into an object.
How it works
1. The TLS 1.3 handshake (when there's no warm connection to reuse):
Client Server (ALB)
│── ClientHello ───────────────────────────────────────────────▶│
│ supported versions: TLS 1.3 │
│ cipher suites: TLS_AES_128_GCM_SHA256, CHACHA20_POLY1305 │
│ key_share: X25519 public key ← key exchange starts NOW
│ SNI: shop.example.com ← which certificate do I want (plaintext!)
│ ALPN: h2, http/1.1 ← which L7 protocol next
│ │
│◀── ServerHello (key_share) ───────────────────────────────────│
│◀── {EncryptedExtensions, Certificate, CertificateVerify, ──── │ ← already encrypted
│ Finished} │
│ │
│── {Finished} ── {HTTP request} ──────────────────────────────▶│ 1 RTT total
The four things TLS gives you:
| Property | Mechanism |
|---|---|
| Confidentiality | Symmetric encryption (AES-GCM / ChaCha20) with keys from an ephemeral ECDHE exchange (X25519) |
| Integrity | AEAD authentication tag on every record. Any tampering → connection fails |
| Authentication | Certificate chain: shop.example.com → intermediate CA → a root CA in your OS/browser trust store; server proves it owns the private key (CertificateVerify) |
| Forward secrecy | Ephemeral keys: stealing the server's private key later doesn't decrypt recorded past traffic |
TLS 1.2 needed 2 RTTs. TLS 1.3 needs 1. QUIC combines the transport and TLS handshakes into 1 as well.
What's still visible on the wire: the destination IP, the port, SNI (the hostname, unless Encrypted Client Hello is used), packet sizes, and timing. Encryption hides content, not metadata.
2. Serialisation formats.
| Format | Strengths | Gotchas |
|---|---|---|
| JSON | Human-readable, universal | No integer type: JS loses precision above 2⁵³−1, so send 64-bit IDs as strings. No binary, no dates (agree on ISO-8601), verbose |
| Protobuf / gRPC | Compact, typed, schema evolution | Needs schema discipline: never reuse field numbers; not human-readable |
| Avro / Parquet | Schema registry, analytics | Writer and reader schema compatibility rules |
| XML / SOAP | Mature validation | Heavy; XXE attacks if parsers are misconfigured |
3. Character encoding. Always UTF-8 on the wire. Declare it
(Content-Type: application/json; charset=utf-8). Mojibake (Café instead of
Café) means one side decoded UTF-8 bytes as Latin-1. Database column
encodings, CSV exports, and email are where this still happens.
4. Compression. Accept-Encoding: gzip, br → Content-Encoding: br.
Brotli/gzip typically shrink JSON 70–90%. HTTP/2 compresses headers with
HPACK (HTTP/3 uses QPACK). Security note: compressing secrets together with
attacker-controlled input under encryption enables CRIME/BREACH-style attacks.
Try it yourself
# Full TLS handshake details: protocol, cipher, certificate chain, expiry
openssl s_client -connect shop.example.com:443 -servername shop.example.com </dev/null 2>/dev/null \
| grep -E 'Protocol|Cipher|subject=|issuer=|Verify return'
# Certificate dates and names (expiry monitoring in one line)
echo | openssl s_client -connect shop.example.com:443 -servername shop.example.com 2>/dev/null \
| openssl x509 -noout -dates -subject -ext subjectAltName
# Did the server compress the response?
curl -s -o /dev/null -H 'Accept-Encoding: br, gzip' -w '%{size_download} bytes\n' https://shop.example.com/
curl -sI -H 'Accept-Encoding: br, gzip' https://shop.example.com/ | grep -i content-encoding
# Inspect bytes / encoding of a payload
printf 'Café' | xxd # c3 a9 = é in UTF-8
What breaks
| Symptom | Presentation cause |
|---|---|
Sudden total outage, browsers show NET::ERR_CERT_DATE_INVALID |
Expired certificate |
Works in Chrome, fails in curl/Java/Android |
Missing intermediate certificate (browsers sometimes fetch it themselves; other clients don't) |
| Some clients fail TLS, others fine | Clock skew on the client; old clients without TLS 1.2+/SNI; cipher mismatch |
| Wrong certificate served | SNI not sent (old client, raw IP), or the wrong default cert on the LB |
| Order IDs off by one in the UI | 64-bit integer in JSON parsed into a JS Number |
Café shows as Café |
UTF-8 decoded as Latin-1 somewhere in the pipeline |
| gRPC consumer crashes after a deploy | Protobuf field number reused or type changed |
War story: "The certificate that expired on a Saturday"
A company's API certificate was renewed manually every year by one engineer, who left. On a Saturday the certificate expired. Every mobile app version pinned to that certificate's public key also broke, and fixing that needed an app-store release that took four days. Fixes: automated issuance and renewal (ACME / cloud certificate manager), expiry monitoring alerting 30 days ahead, and pinning the CA/intermediate key rather than the leaf, with a backup pin. Lesson for managers: certificate expiry is one of the most common preventable outages. Track it as an owned, monitored asset, not tribal knowledge.
Why it matters to you
- Decide where TLS terminates, and say so. At the CDN? The LB? The pod (end-to-end / mTLS via a service mesh)? Every terminating hop can read plaintext. That's a compliance (PCI/HIPAA) and threat-model decision.
- Encoding and serialisation are API contracts. Put them in your API guidelines: UTF-8, ISO-8601 timestamps in UTC, string IDs, explicit nulls vs missing fields, protobuf evolution rules.
- Automate certificates end to end. A manual certificate is an incident on a timer.
Go deeper in the series
- TLS 1.3 in depth: HTTPS guide, Chapter 4.
- Inspect any server's TLS from Go, with named failure modes:
tlsinspect, TCP/IP Chapter 29. - Certificate operations (inventory, chains, ACME, 47-day lifetimes): TCP/IP Chapter 56 and HTTPS Chapter 16.
- Serialisation and framing in Go: Go guide §23 (JSON) and §34 (framing your own protocol).
- The cryptography underneath (hashing vs encryption, AEAD, certificates, running a CA): Security from Zero; envelope encryption with per-tenant keys, as a Go lab: Security Engineering in Depth, Chapter 9.