NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Sentinel for Laravel

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 partner

Importované 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, programmatic

Predvolený 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:

SignatureRejectionVýznam
missing_signatureChýba Signature / Signature-Input.
malformedNečitateľné polia, duplicitné komponenty, prepísaná metóda.
ambiguous_signatureNie je jediný label na overenie.
missing_parameterChýba keyid, created alebo nonce.
missing_componentPovinný komponent nie je pokrytý alebo chýba.
unsupported_componentParametrizovaný alebo nepodporovaný komponent.
unsupported_algorithmAlgoritmus, ktorý RFC 9421 neregistruje — alg ako rsa-pss-sha512 alebo kľúč ako hmac-sha512.
unknown_key, revoked_keyVyhľadanie kľúča.
algorithm_mismatchParameter alg iný než algoritmus kľúča.
algorithm_not_allowedAlgoritmus kľúča je mimo allowlistu profilu alebo kruhu.
tag_mismatchNesprávny tag.
not_yet_valid, too_old, expiredČasové okno.
digest_mismatch, unsupported_digestContent-Digest
invalid_signaturePodpis sa neoverí.
replayedNonce 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ľbaPredvolenéÚčinok
componentsoutbound.componentsPokryté komponenty.
labeloutbound.label (sig1)Label podpisu.
expiresInoutbound.expires_inSekundy do parametra expires (predvolene žiadny).
tagoutbound.tagParameter tag (predvolene žiadny).
includeAlgoutbound.include_alg (false)Posielať parameter alg.
digestoutbound.digest (sha-256)Algoritmus Content-Digest.
ringoutbound.ring (http)Kruh, v ktorom sa hľadá id kľúča.
noncetruefalse 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 kryptomien

Odoslaní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.