OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1', ]); if ($resource === false || !openssl_pkey_export($resource, $privateKey)) { throw self::openSslFailure('Unable to create test EC private key.'); } return self::$ecPrivateKey = $privateKey; } /** * 生成并缓存测试使用的 RSA 密钥对。 * * @return array{0:string,1:string} */ public static function keyPair(): array { if (self::$keyPair !== null) { return self::$keyPair; } return self::$keyPair = self::generateKeyPair(); } /** * 返回与商户/应用密钥相互独立的平台测试 RSA 密钥对。 * * @return array{0:string,1:string} */ public static function platformKeyPair(): array { if (self::$platformKeyPair !== null) { return self::$platformKeyPair; } return self::$platformKeyPair = self::generateKeyPair(); } /** * @return array{0:string,1:string} */ private static function generateKeyPair(): array { self::clearOpenSslErrors(); $resource = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); if ($resource === false) { throw self::openSslFailure('Unable to create test RSA key pair.'); } if (!openssl_pkey_export($resource, $privateKey)) { throw self::openSslFailure('Unable to export test RSA private key.'); } $details = openssl_pkey_get_details($resource); if (!is_array($details) || !is_string($details['key'] ?? null)) { throw self::openSslFailure('Unable to read test RSA public key.'); } return [$privateKey, $details['key']]; } private static function clearOpenSslErrors(): void { while (openssl_error_string() !== false); } private static function openSslFailure(string $message): \RuntimeException { $errors = []; while (($error = openssl_error_string()) !== false) { $errors[] = $error; } $details = $errors === [] ? 'OpenSSL returned no diagnostic details.' : implode(' | ', $errors); $config = getenv('OPENSSL_CONF'); return new \RuntimeException(sprintf( '%s %s OPENSSL_CONF=%s', $message, $details, is_string($config) && $config !== '' ? $config : '(not set)', )); } private static function pemBody(string $pem): string { return (string)preg_replace('/-----BEGIN [^-]+-----|-----END [^-]+-----|\s+/', '', $pem); } private static function asn1Integer(string $value): string { $value = ltrim($value, "\0"); if ($value === '') { $value = "\0"; } elseif ((ord($value[0]) & 0x80) !== 0) { $value = "\0" . $value; } return "\x02" . self::asn1Length(strlen($value)) . $value; } private static function asn1Sequence(string ...$values): string { $value = implode('', $values); return "\x30" . self::asn1Length(strlen($value)) . $value; } private static function asn1Length(int $length): string { if ($length < 0x80) { return chr($length); } $bytes = ''; while ($length > 0) { $bytes = chr($length & 0xFF) . $bytes; $length >>= 8; } return chr(0x80 | strlen($bytes)) . $bytes; } }