Skip to content

Conformance

Conformance

Why this page exists

A round-trip test — sign, then verify with the same code — passes even when the canonical string is wrong, because both halves are wrong in the same way. It proves self-consistency, not conformance.

The test that catches a misread specification is one where the signature came from somebody else's implementation. Every bundled scheme now has one.

Verified against a published vector

The provider publishes a worked example with an expected signature, and the suite asserts that exact string.

Scheme Source
github The worked example in GitHub's webhook documentation
slack The worked example in Slack's request-signing documentation
standard-webhooks The test vector published with the specification
twilio The worked example in Twilio's security documentation

Plus the primitive itself: Support\Hmac is checked against RFC 4231 HMAC-SHA256 test cases 1, 2 and 4 — the one thing whose failure would break every driver at once.

See tests/Feature/PublishedVectorsTest.php.

Verified against a foreign implementation

No published vector exists for these, so the signature was generated by the provider's own SDK, captured, and pinned into the suite. The values live in tests/Feature/CrossImplementationVectorsTest.php; the suite needs no network and no vendor dependency to run them.

Scheme Signature produced by
stripe stripe/stripe-php v21.1.1 — Stripe\WebhookSignature
shopify shopify/shopify-api v6.1.1 — Webhooks\Registry::validateProcessHmac
mailgun mailgun/mailgun-phpApi\Webhook::verifyWebhookSignature
standard-webhooks standard-webhooks/standard-webhooks v1.0.2, the reference library
postal openssl dgst -sha256 -sign and -sha1 -sign
cbox The signer already deployed in the Cbox billing service, written independently of this package

Each was also checked in the reverse direction at capture time — the foreign implementation accepting a signature this package produced — and the Shopify check was confirmed to reject a forgery, so the vendor's validator was demonstrably running rather than passing everything. For Stripe the two implementations produced byte-identical headers.

The generic HMAC driver

hmac has no provider and therefore no foreign implementation to check against. It is covered by round-trip tests across digests and encodings, by tampering tests, and by validation tests asserting that a definition it cannot honour exactly is refused rather than silently defaulted.

That is the honest limit of what a configurable driver can be tested to. Its correctness for your provider depends on the definition you write, which is why an unhonourable definition throws at boot instead of guessing.

What "verified" does and does not mean

It means: given the same inputs, this implementation produces the byte string that the other implementation produced or accepts.

It does not mean the package has had a third-party security audit, holds any certification, or has been assessed against any standards framework. None of those have happened, and none are claimed anywhere in this repository.

Note also what a vector cannot cover: it proves the wire format, not the surrounding policy. Window enforcement, secret rotation, downgrade refusal and single-use enforcement are covered by their own behavioural tests, because no captured signature can express them.

The cryptography itself

Nothing here is hand-rolled.

Symmetric schemes compute every MAC with PHP's hash_hmac() and compare with hash_equals(), both in Support\Hmac — so the constant-time comparison and the no-early-exit loop over configured secrets are properties of one file rather than of nine.

The asymmetric scheme (postal) parses keys with openssl_pkey_get_public() and verifies with openssl_verify(), in Support\Rsa. No padding, ASN.1 or digest handling is implemented here. Constant-time comparison does not apply on that path and is not claimed: the configured value is a public key, so a timing difference reveals nothing an attacker cannot compute for themselves.

Algorithms are pinned per scheme and never read off the wire. GitHub's legacy SHA-1 header is refused rather than accepted alongside SHA-256, and Postal's SHA-256 header — when present — is the only one checked, so a corrupted strong signature cannot be downgraded to the weak one.