Sync your WordPress posts to standard.site records on your PDS
at main 151 lines 4.0 kB view raw
1<?php 2declare(strict_types=1); 3/** 4 * DPoP (Demonstration of Proof of Possession) support using oauth2-dpop library. 5 * 6 * @package Wireservice 7 */ 8 9namespace Wireservice; 10 11if (! defined('ABSPATH')) { 12 exit; 13} 14 15use danielburger1337\OAuth2\DPoP\DPoPProofFactory; 16use danielburger1337\OAuth2\DPoP\Encoder\WebTokenFrameworkDPoPTokenEncoder; 17use danielburger1337\OAuth2\DPoP\Model\AccessTokenModel; 18use Jose\Component\Core\AlgorithmManager; 19use Jose\Component\Core\JWK; 20use Jose\Component\Signature\Algorithm\ES256; 21use Symfony\Component\Clock\NativeClock; 22 23class DPoP 24{ 25 /** 26 * Supported algorithms for AT Protocol. 27 * 28 * @var array 29 */ 30 private const SUPPORTED_ALGORITHMS = ["ES256"]; 31 32 /** 33 * Cached factory instances keyed by JWK hash. 34 * 35 * @var array<string, DPoPProofFactory> 36 */ 37 private static array $factories = []; 38 39 /** 40 * Cached JWK thumbprints keyed by JWK hash. 41 * 42 * @var array<string, string> 43 */ 44 private static array $thumbprints = []; 45 46 /** 47 * Generate a DPoP proof JWT. 48 * 49 * @param array $jwk The JWK to sign with (must include private key 'd'). 50 * @param string $method The HTTP method (GET, POST, etc.). 51 * @param string $url The full URL being requested. 52 * @param string|null $nonce Optional server-provided nonce. 53 * @param string|null $access_token Optional access token for ath claim. 54 * @return string|false The DPoP proof JWT or false on failure. 55 */ 56 public static function generate_proof( 57 array $jwk, 58 string $method, 59 string $url, 60 ?string $nonce = null, 61 ?string $access_token = null, 62 ): string|false { 63 try { 64 $factory = self::get_factory($jwk); 65 66 // Store nonce if provided (for the library to pick up). 67 if ($nonce !== null) { 68 self::store_nonce($jwk, $url, $nonce); 69 } 70 71 // Create the proof. 72 $bindTo = null; 73 if ($access_token !== null) { 74 $thumbprint = self::get_thumbprint($jwk); 75 $bindTo = new AccessTokenModel($access_token, $thumbprint); 76 } 77 78 $proof = $factory->createProof( 79 strtoupper($method), 80 $url, 81 self::SUPPORTED_ALGORITHMS, 82 $bindTo, 83 ); 84 85 return $proof->proof; 86 } catch (\Throwable $e) { 87 wp_trigger_error(__METHOD__, "DPoP proof generation failed: " . $e->getMessage()); 88 return false; 89 } 90 } 91 92 /** 93 * Store a nonce received from a server response. 94 * 95 * @param array $jwk The JWK used in the request. 96 * @param string $url The request URL. 97 * @param string $nonce The nonce from the response header. 98 */ 99 public static function store_nonce(array $jwk, string $url, string $nonce): void 100 { 101 $factory = self::get_factory($jwk); 102 $jwkInterface = $factory->getJwkToBind(self::SUPPORTED_ALGORITHMS); 103 $factory->storeNextNonce($nonce, $jwkInterface, $url); 104 } 105 106 /** 107 * Get or create a DPoPProofFactory for the given JWK. 108 * 109 * @param array $jwk The JWK array. 110 * @return DPoPProofFactory The factory instance. 111 */ 112 private static function get_factory(array $jwk): DPoPProofFactory 113 { 114 $key = md5(wp_json_encode($jwk)); 115 116 if (!isset(self::$factories[$key])) { 117 $jwtJwk = new JWK($jwk); 118 $algorithmManager = new AlgorithmManager([new ES256()]); 119 $encoder = new WebTokenFrameworkDPoPTokenEncoder($jwtJwk, $algorithmManager); 120 $nonceStorage = new NonceStorage(); 121 $clock = new NativeClock(); 122 123 self::$factories[$key] = new DPoPProofFactory( 124 $clock, 125 $encoder, 126 $nonceStorage, 127 ); 128 } 129 130 return self::$factories[$key]; 131 } 132 133 /** 134 * Get the JWK thumbprint. 135 * 136 * @param array $jwk The JWK array. 137 * @return string The thumbprint. 138 */ 139 private static function get_thumbprint(array $jwk): string 140 { 141 $key = md5(wp_json_encode($jwk)); 142 143 if (!isset(self::$thumbprints[$key])) { 144 $jwtJwk = new JWK($jwk); 145 self::$thumbprints[$key] = $jwtJwk->thumbprint("sha256"); 146 } 147 148 return self::$thumbprints[$key]; 149 } 150 151}