@@ -64,7 +64,8 @@ class JWT
6464 'RS256 ' => ['openssl ' , 'SHA256 ' ],
6565 'RS384 ' => ['openssl ' , 'SHA384 ' ],
6666 'RS512 ' => ['openssl ' , 'SHA512 ' ],
67- 'EdDSA ' => ['sodium_crypto ' , 'EdDSA ' ],
67+ 'PS256 ' => ['openssl ' , 'SHA256 ' ],
68+ 'EdDSA ' => ['sodium_crypto ' , 'EdDSA ' ]
6869 ];
6970
7071 /**
@@ -250,7 +251,7 @@ public static function encode(
250251 * @param string $msg The message to sign
251252 * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
252253 * @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
253- * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
254+ * 'HS384', 'HS512', 'RS256', 'RS384', 'PS256' and 'RS512'
254255 *
255256 * @return string An encrypted message
256257 *
@@ -273,6 +274,9 @@ public static function sign(
273274 self ::validateHmacKeyLength ($ key , $ algorithm );
274275 return \hash_hmac ($ algorithm , $ msg , $ key , true );
275276 case 'openssl ' :
277+ if ($ alg === 'PS256 ' ) {
278+ return self ::signPS256 ($ key , $ msg );
279+ }
276280 $ signature = '' ;
277281 if (!$ key = openssl_pkey_get_private ($ key )) {
278282 throw new DomainException ('OpenSSL unable to validate key ' );
@@ -329,6 +333,9 @@ private static function verify(
329333 list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
330334 switch ($ function ) {
331335 case 'openssl ' :
336+ if ($ alg === 'PS256 ' ) {
337+ return self ::verifyPS256 ($ keyMaterial , $ msg , $ signature );
338+ }
332339 if (!$ key = openssl_pkey_get_public ($ keyMaterial )) {
333340 throw new DomainException ('OpenSSL unable to validate key ' );
334341 }
@@ -751,4 +758,76 @@ private static function validateEdDSAKey(#[\SensitiveParameter] $keyMaterial): s
751758 }
752759 return $ key ;
753760 }
761+
762+ /**
763+ * Signs a message with a PS256 algorithm
764+ *
765+ * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key
766+ * @param string $message
767+ * @throws DomainException Provided key is invalid
768+ */
769+ private static function signPS256 (
770+ #[\SensitiveParameter] string |OpenSSLAsymmetricKey |OpenSSLCertificate $ key ,
771+ string $ message
772+ ): string {
773+ if (!class_exists ('\phpseclib3\Crypt\RSA ' )) {
774+ throw new DomainException ('phpseclib/phpseclib is required for PS256 support ' );
775+ }
776+
777+ if ($ key instanceof OpenSSLCertificate) {
778+ throw new DomainException ('Cannot sign with an X.509 certificate. A private key is required. ' );
779+ }
780+
781+ if ($ key instanceof OpenSSLAsymmetricKey) {
782+ if (!openssl_pkey_export ($ key , $ pem )) {
783+ throw new DomainException ('OpenSSL unable to export the AsymmetricKey ' );
784+ }
785+ $ key = $ pem ;
786+ }
787+
788+ /** @var \phpseclib3\Crypt\RSA\PrivateKey $rsa */
789+ $ rsa = \phpseclib3 \Crypt \PublicKeyLoader::load ($ key );
790+
791+ return $ rsa ->withPadding (\phpseclib3 \Crypt \RSA ::SIGNATURE_PSS )
792+ ->withHash ('sha256 ' )
793+ ->sign ($ message );
794+ }
795+
796+ /**
797+ * Validates a PS256 algorithm signature.
798+ *
799+ * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key
800+ * @param string $message
801+ * @param string $signature
802+ * @throws DomainException Provided key is invalid
803+ */
804+ private static function verifyPS256 (
805+ #[\SensitiveParameter] string |OpenSSLAsymmetricKey |OpenSSLCertificate $ key ,
806+ string $ message ,
807+ string $ signature
808+ ): bool {
809+ if (!class_exists ('\phpseclib3\Crypt\RSA ' )) {
810+ throw new DomainException ('phpseclib/phpseclib is required for PS256 support ' );
811+ }
812+
813+ if ($ key instanceof OpenSSLAsymmetricKey) {
814+ $ details = openssl_pkey_get_details ($ key );
815+ if (!$ details || !isset ($ details ['key ' ])) {
816+ throw new DomainException ('OpenSSL unable to extract public key ' );
817+ }
818+ $ key = $ details ['key ' ];
819+ } elseif ($ key instanceof OpenSSLCertificate) {
820+ if (!openssl_x509_export ($ key , $ pem )) {
821+ throw new DomainException ('OpenSSL unable to export certificate ' );
822+ }
823+ $ key = $ pem ;
824+ }
825+
826+ /** @var \phpseclib3\Crypt\RSA\PublicKey $rsa */
827+ $ rsa = \phpseclib3 \Crypt \PublicKeyLoader::load ($ key );
828+
829+ return $ rsa ->withPadding (\phpseclib3 \Crypt \RSA ::SIGNATURE_PSS )
830+ ->withHash ('sha256 ' )
831+ ->verify ($ message , $ signature );
832+ }
754833}
0 commit comments