Safeguard
Application Security•

ASVS 5.0 V4.1.5: Sign Sensitive Messages Beyond TLS

For highly sensitive or multi hop requests, add a per message signature on top of TLS. See when it applies and how to sign and verify safely.

Safeguard Security Team
3 min read

ASVS 5.0 V4.1.5 (level 3) asks for per message digital signatures, on top of transport protection, for requests or transactions that are highly sensitive or that pass through several systems.

TLS protects a single connection. If a payment instruction passes through a gateway, a queue and two internal services, each hop terminates TLS and could read or change the message. A signature made by the original sender travels with the message and proves it was not altered.

Why it matters

Hop by hop encryption does not give end to end integrity or proof of origin. Intermediaries, compromised internal services and replay attacks sit in that gap. For money movement, regulatory filings, webhooks and cross organization APIs, you want the receiver to verify who sent this exact payload.

This is a level 3 item because it adds real cost: key management, clock handling and replay defense. Use it where the risk justifies it.

How to implement it

Sign a canonical string made of the method, path, a timestamp, a unique id and a digest of the body. HTTP Message Signatures (RFC 9421) standardizes this, and a simple HMAC or asymmetric scheme works for private APIs.

A small Node example using an Ed25519 key:

import { sign, verify, createHash } from 'node:crypto';

function canonical(req, body, ts, nonce) {
  const digest = createHash('sha256').update(body).digest('base64');
  return [req.method, req.path, ts, nonce, digest].join('\n');
}

// sender
const sig = sign(null, Buffer.from(canonical(req, body, ts, nonce)), privateKey);

// receiver
const ok = verify(null, Buffer.from(canonical(req, body, ts, nonce)), publicKey, sig);
const fresh = Math.abs(Date.now() - Number(ts)) < 300000;
if (!ok || !fresh || await nonceSeen(nonce)) reject();

Store seen nonces for the freshness window, rotate keys with a key id in the message, and keep private keys in a KMS or HSM.

How to test it

Take a valid signed request and change one thing at a time: the body, the path, the timestamp, the nonce. Each variant must be rejected. Then replay the untouched request a second time inside the freshness window. The second copy must fail on the nonce check. Finally send a request signed with a retired key id and confirm it is refused.

Common mistakes

  • Signing only the body and leaving the path, method or timestamp unprotected.
  • No replay defense, so a captured valid request works forever.
  • Comparing signatures with a plain string equality in HMAC schemes. Use a constant time compare.
  • Sharing one symmetric key across every partner.
  • Verifying in the gateway but forwarding unsigned messages to services that trust them.

Quick check

Why is TLS not enough for a request that crosses several services?

Each hop ends the TLS session, so any hop can read or change the message. A signature from the original sender lets the final receiver verify the exact payload.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.