Laravel AT Protocol Client (alpha & unstable)
at main 2.2 kB view raw
1<?php 2 3namespace SocialDept\AtpClient\Auth; 4 5use Firebase\JWT\JWT; 6use phpseclib3\Crypt\EC; 7use SocialDept\AtpClient\Contracts\KeyStore; 8use SocialDept\AtpClient\Data\DPoPKey; 9 10class DPoPKeyManager 11{ 12 public function __construct( 13 protected KeyStore $keyStore 14 ) {} 15 16 /** 17 * Generate new ES256 key pair 18 */ 19 public function generateKey(string $sessionId): DPoPKey 20 { 21 // Generate P-256 elliptic curve key pair 22 $privateKey = EC::createKey('secp256r1'); 23 $publicKey = $privateKey->getPublicKey(); 24 $keyId = $this->generateKeyId($publicKey); 25 26 $dpopKey = new DPoPKey($privateKey, $publicKey, $keyId); 27 28 // Store the key 29 $this->keyStore->store($sessionId, $dpopKey); 30 31 return $dpopKey; 32 } 33 34 /** 35 * Create DPoP proof JWT 36 */ 37 public function createProof( 38 DPoPKey $key, 39 string $method, 40 string $url, 41 string $nonce = '', 42 ?string $accessToken = null 43 ): string { 44 $now = time(); 45 46 $payload = [ 47 'jti' => bin2hex(random_bytes(16)), 48 'htm' => $method, 49 'htu' => $url, 50 'iat' => $now, 51 'exp' => $now + 60, // 1 minute validity 52 ]; 53 54 // Only include nonce if provided (first request may not have one) 55 if ($nonce !== '') { 56 $payload['nonce'] = $nonce; 57 } 58 59 if ($accessToken) { 60 $payload['ath'] = $this->hashAccessToken($accessToken); 61 } 62 63 $header = [ 64 'typ' => 'dpop+jwt', 65 'alg' => 'ES256', 66 'jwk' => $key->getPublicJwk(), 67 ]; 68 69 return JWT::encode( 70 payload: $payload, 71 key: $key->toPEM(), 72 alg: 'ES256', 73 head: $header 74 ); 75 } 76 77 /** 78 * Hash access token for DPoP proof 79 */ 80 protected function hashAccessToken(string $token): string 81 { 82 return rtrim(strtr(base64_encode(hash('sha256', $token, true)), '+/', '-_'), '='); 83 } 84 85 /** 86 * Generate key ID from public key 87 */ 88 protected function generateKeyId($publicKey): string 89 { 90 $jwk = $publicKey->toString('JWK'); 91 92 return hash('sha256', $jwk); 93 } 94}