Podpisy HTTP správ
Partneri dokazujú, že požiadavka pochádza od nich a nebola zmenená ani prehraná: HTTP Message Signatures (RFC 9421) s hmac-sha256, ed25519, ecdsa-p256-sha256 alebo ecdsa-p384-sha384, Content-Digest podľa RFC 9530 a štruktúrované polia podľa RFC 9651 (vlastný parser a serializér). Ich kľúče žijú v kruhu http — tajomstvo partnera nikdy nevytvorí ani neoverí pečať.
Kľúče partnerov
use RoundlyConsulting\Sentinel\Enums\Algorithm;
// The partner sends you their public key (PEM or base64:…) — import it, no deploy:
Sentinel::keys()->ring('http')->import('acme-2026-10', Algorithm::Ed25519, $partnerPublicKeyPem, owner: $partner);
// …or you generate the pair and share the public half:
$key = Sentinel::keys()->ring('http')->generate(Algorithm::Ed25519, keyId: 'acme-2027-01', owner: $partner);
$key->publicKey; // base64:… — share it with the partnerImportované kľúče partnerov sú len na overovanie a viazané na model partnera — bez nasadenia pre každého partnera. Alternatíva bez databázy ich uvedie ako kľúče len na overovanie v konfigurácii, pričom kruh číta oba drivery — jedno nasadenie na partnera a kľúč bez vlastníka:
SENTINEL_HTTP_KEY_DRIVER=chain
SENTINEL_HTTP_KEYS="partner-ed|ed25519|base64:<32-byte public key>,partner-hmac|hmac-sha256|base64:<shared secret>"Zdieľané tajomstvo partnera je kľúč len na overovanie. Ak ho nastavíte ako aktuálny kľúč kruhu http (SENTINEL_HTTP_KEY), stane sa kľúčom, ktorým podpisuje vaša aplikácia, a prichádzajúce požiadavky podpísané ním sa odmietnu (unknown_key). Uveďte ho medzi predošlými kľúčmi kruhu alebo ho importujte len na overovanie; accept_signing_keys nastavte iba vtedy, ak tým istým tajomstvom aj sami podpisujete odchádzajúce požiadavky.
Overovanie prichádzajúcich požiadaviek
Route::post('/partner/events', PartnerEvents::class)->middleware('sentinel.signed'); // default profile
Route::post('/partner/orders', PartnerOrders::class)->middleware('sentinel.signed:partners'); // a named profile
$signature = Sentinel::signatures()->current($request); // ?VerifiedSignature — what sentinel.signed verified
$partner = Sentinel::signatures()->owner($request); // the key's owner model (database keys), or null
$signature = Sentinel::signatures()->verify($request, 'partners'); // the same check, programmaticPredvolený profil vyžaduje created, keyid a nonce, prijíma podpisy staré najviac 300 sekúnd (± 30 sekúnd rozdielu hodín) a vyžaduje content-digest vždy, keď má požiadavka telo. Profily pridáte v sentinel.signatures.profiles a odkážete na ne menom: sentinel.signed:partners. Ak podpisy autentifikujú klientov API, zaraďte sentinel.signed pred auth.
@scheme a @authority sa berú z požiadavky Laravelu, takže za load balancerom alebo proxy sa riadia nastavením dôveryhodných proxy (X-Forwarded-Proto, X-Forwarded-Host). Proxy nastavte ako dôveryhodnú, inak sa požiadavka, ktorú partner podpísal pre https://api.example.com, poskladá s internou schémou a hostiteľom a skončí ako invalid_signature.
VerifiedSignature nesie label, ring, keyId, algorithm, created a expires (Unix sekundy), nonce, tag, components a ownerType / ownerId kľúča (pri kľúčoch z konfigurácie null). verify() ho vráti, alebo vyhodí HttpSignatureException (401; reason() vráti SignatureRejection, keyId() deklarované id kľúča).
Profil v poradí krokov
- Label: nakonfigurovaný, inak jediný člen, inak prvý, ktorého tag sa zhoduje s tagom profilu, inak ambiguous_signature.
- Parametre: keyid, created, nonce pri require_nonce, tag, ak je nakonfigurovaný; expires sa vynúti, ak je prítomné.
- Pokrytie: požiadavka musí vykonať metódu, s ktorou bola odoslaná (prepis metódy → malformed); pokryté musia byť komponenty profilu, @query pri query reťazci a content-digest pri tele.
- Čas (vrátane hraníc): created v budúcnosti nad clock_skew → not_yet_valid; staršie než max_age plus clock_skew → too_old; expires uplynulé o viac než clock_skew → expired.
- Kľúč: hľadá sa v kruhu profilu — chýbajúci, čakajúci, poškodený či vyradený → unknown_key; odvolaný → revoked_key; alg iný než algoritmus kľúča → algorithm_mismatch; mimo allowlistov → algorithm_not_allowed; kľúč, ktorým aplikácia sama podpisuje → unknown_key, pokiaľ nie je zapnuté accept_signing_keys.
- Digest: každý podporovaný člen Content-Digest musí zodpovedať surovému telu (digest_mismatch); žiadny podporovaný → unsupported_digest.
- Podpis: základ sa zostaví znova a overí → invalid_signature.
- Nonce: až po platnom podpise, zapamätá sa na čas okna platnosti → replayed.
Odmietnutia
Odmietnutie odpovie 401 application/problem+json s kódom signature_rejected — presný dôvod len pri app.debug, aby nevzniklo orákulum na overovanie — a pri signatures.advertise s nápovedou Accept-Signature. HttpSignatureRejected sa spustí s presným dôvodom:
| SignatureRejection | Význam |
|---|---|
missing_signature | Chýba Signature / Signature-Input. |
malformed | Nečitateľné polia, duplicitné komponenty, prepísaná metóda. |
ambiguous_signature | Nie je jediný label na overenie. |
missing_parameter | Chýba keyid, created alebo nonce. |
missing_component | Povinný komponent nie je pokrytý alebo chýba. |
unsupported_component | Parametrizovaný alebo nepodporovaný komponent. |
unsupported_algorithm | Algoritmus, ktorý RFC 9421 neregistruje — alg ako rsa-pss-sha512 alebo kľúč ako hmac-sha512. |
unknown_key, revoked_key | Vyhľadanie kľúča. |
algorithm_mismatch | Parameter alg iný než algoritmus kľúča. |
algorithm_not_allowed | Algoritmus kľúča je mimo allowlistu profilu alebo kruhu. |
tag_mismatch | Nesprávny tag. |
not_yet_valid, too_old, expired | Časové okno. |
digest_mismatch, unsupported_digest | Content-Digest |
invalid_signature | Podpis sa neoverí. |
replayed | Nonce už bol videný. |
Podpísané multipart požiadavky zlyhajú bezpečne
PHP rozparsuje telo multipart/form-data do $_POST / $_FILES ešte pred Laravelom a surové bajty si neponechá, takže Content-Digest sa nedá overiť: požiadavka sa odmietne s digest_mismatch, aj keď ju partner podpísal správne. Dohodnite sa s partnerom, aby súbor posielal ako surové telo (Content-Type: application/octet-stream, metadáta v pokrytých hlavičkách alebo v query) alebo ako JSON. Ak sa multipart nedá obísť, obsluhujte daný endpoint z PHP poolu s enable_post_data_reading = Off a php://input si rozparsujte sami.
Podpisovanie odchádzajúcich požiadaviek
Podpisujte kľúčom odchádzajúceho kruhu — musí byť aktívny a mať súkromný materiál; importovaný kľúč partnera nikdy nepodpisuje:
use RoundlyConsulting\Sentinel\DataTransferObjects\SigningOptions;
use RoundlyConsulting\Sentinel\Enums\DigestAlgorithm;
Http::withSignature('acme-2027-01')->post('https://partner.example/events', $event);
Http::withSignature('acme-2027-01', new SigningOptions(expiresIn: 60, tag: 'acme', includeAlg: true))
->post('https://partner.example/events', $event);
// Any PSR-7 request — here without a nonce, for a peer that does not de-duplicate them:
$signed = Sentinel::signatures()->sign($psrRequest, 'acme-2027-01', new SigningOptions(
components: ['@method', '@authority', '@path', 'content-digest'],
digest: DigestAlgorithm::Sha512,
nonce: false,
));
Sentinel::signatures()->verifyResponse($response, 'partners'); // a signed response (PSR-7 or Laravel client)
Sentinel::signatures()->contentDigest('{"hello": "world"}'); // 'sha-256=:X48E…:'Http::withSignature() podpíše finálnu požiadavku, až po každom middleware požiadavky a callbacku beforeSending() (opakovanie či presmerovanie sa podpíše nanovo). Pridá Content-Digest, ak telo nie je prázdne a content-digest je komponentom, vynechá @query bez query reťazca aj hlavičky, ktoré požiadavka nemá, a nastaví created, keyid a nový nonce (plus expires, tag a alg, ak sú nakonfigurované). Stream, ktorý sa nedá pretočiť, sa najprv uloží do kópie s možnosťou posunu, vytvorenej cez HttpFactory z PSR-7 balíka Guzzle, ktorý prichádza s illuminate/http — bez ďalšej závislosti. Každé pole SigningOptions ponechané na null použije sentinel.signatures.outbound.*; nastavené sa overí rovnako ako jeho náprotivok v konfigurácii:
| Voľba | Predvolené | Účinok |
|---|---|---|
components | outbound.components | Pokryté komponenty. |
label | outbound.label (sig1) | Label podpisu. |
expiresIn | outbound.expires_in | Sekundy do parametra expires (predvolene žiadny). |
tag | outbound.tag | Parameter tag (predvolene žiadny). |
includeAlg | outbound.include_alg (false) | Posielať parameter alg. |
digest | outbound.digest (sha-256) | Algoritmus Content-Digest. |
ring | outbound.ring (http) | Kruh, v ktorom sa hľadá id kľúča. |
nonce | true | false vynechá parameter nonce. |
Ploché tvary a akcie
use RoundlyConsulting\Sentinel\Actions\Signatures\SignRequestAction;
$signed = Sentinel::signRequest($psrRequest, 'acme-2026-10', new SigningOptions(expiresIn: 60));
$signature = Sentinel::verifyRequestSignature($request, 'partners');
$signature = Sentinel::verifyResponseSignature($clientResponse);
$partner = Sentinel::signatureOwner($request);
$signed = app(SignRequestAction::class)->execute($psrRequest, 'acme-2026-10', new SigningOptions);Interoperabilita je overená voči prílohe B RFC 9421: B.2.5 (hmac-sha256) a B.2.6 (ed25519) sa podpíšu bajt po bajte a overia, B.2.4 (ecdsa-p256-sha256) sa overí a RSA vektory sa odmietnu. Nepodporované: parametre komponentov, @request-target, @query-param, politiky viacerých podpisov nad rámec jedného vybraného labelu a spracovanie Accept-Signature protistrany.
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.