$value JSON value. * @return string */ public static function canonical_json( $value ) { $normalized = self::canonical_value( $value ); $json = json_encode( $normalized, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); if ( false === $json ) { throw new \RuntimeException( 'Unable to canonicalize JSON.' ); } return $json; } /** * @param mixed $value JSON value. * @return mixed */ private static function canonical_value( $value ) { if ( null === $value || is_string( $value ) || is_bool( $value ) || is_int( $value ) || is_float( $value ) ) { if ( is_float( $value ) && ! is_finite( $value ) ) { throw new \InvalidArgumentException( 'Non-finite JSON number.' ); } return $value; } if ( ! is_array( $value ) ) { throw new \InvalidArgumentException( 'Unsupported JSON value.' ); } $keys = array_keys( $value ); $is_list = empty( $value ) || range( 0, count( $value ) - 1 ) === $keys; if ( $is_list ) { return array_map( array( __CLASS__, 'canonical_value' ), $value ); } ksort( $value, SORT_STRING ); foreach ( $value as $key => $item ) { $value[ $key ] = self::canonical_value( $item ); } return $value; } /** * @param string $payload Signed payload. * @param string $signature Base64url signature without prefix. * @param string $public_key_raw Base64url-encoded 32-byte Ed25519 key. * @return bool */ public static function verify_ed25519( $payload, $signature, $public_key_raw ) { if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ) { return false; } $sig = self::base64url_decode( $signature ); $key = self::base64url_decode( $public_key_raw ); if ( false === $sig || false === $key || SODIUM_CRYPTO_SIGN_BYTES !== strlen( $sig ) || SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key ) ) { return false; } return sodium_crypto_sign_verify_detached( $sig, $payload, $key ); } /** * @param string $canonical_payload Canonical JSON bytes. * @param string $signature Base64url ASN.1 DER signature. * @param string $public_key_pem P-256 public key PEM. * @return bool */ public static function verify_p256( $canonical_payload, $signature, $public_key_pem ) { $sig = self::base64url_decode( $signature ); if ( false === $sig || ! function_exists( 'openssl_verify' ) ) { return false; } $key = openssl_pkey_get_public( $public_key_pem ); if ( false === $key ) { return false; } return 1 === openssl_verify( $canonical_payload, $sig, $key, OPENSSL_ALGO_SHA256 ); } }