['http/1.1']]); * fwrite($s, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); * STARTTLS(既に接続しているソケット): * $s = Tls::enable($socket, ['peer_name' => 'smtp.example.com']); * サーバー(テスト等): * $s = Tls::accept($client, ['cert' => $certPem, 'key' => $keyPem]); * * オプション(PHPのsslコンテキストと同じ名前): * peer_name 確認するホスト名(SNIにも使う) * verify_peer 証明書を確認する(既定true) * verify_peer_name ホスト名を確認する(既定true) * allow_self_signed 自己署名の証明書を許す(既定false) * cafile 信頼する証明書のPEMファイル(省略するとTLS_CAFILE、又はシステムの証明書) * SNI_enabled SNIを送る(既定true) * alpn ALPNのプロトコルの配列(例:['http/1.1']) * groups 鍵交換のグループ(既定['x25519', 'P-256', 'P-384']、最初の鍵を最初に送る) * min_version・max_version 使うバージョン('1.2'又は'1.3'、既定は1.2から1.3) * ciphers 暗号スイートの番号の配列(Connection::SUITES・SUITES12のキー、既定は全部) * cert・key サーバーの証明書(チェーン)と秘密鍵(PEM又はファイル、local_cert・local_pkも可) */ class Tls { /** * TCPで接続してTLSを開始する * * @param string $host ホスト名又はIPアドレス * @param int $port ポート * @param array $opt オプション(peer_nameを省略すると$host) * @param float $timeout 接続と読み込みのタイムアウト(秒) * @return resource ストリーム * @throws TlsException 接続又はハンドシェイクに失敗した場合 */ public static function connect(string $host, int $port, array $opt = [], float $timeout = 10) { $addr = str_contains($host, ':') && !str_starts_with($host, '[') ? "[{$host}]" : $host; $errno = 0; $errstr = ''; $sock = @stream_socket_client("tcp://{$addr}:{$port}", $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, stream_context_create()); if ($sock === false) throw new TlsException("接続出来ません:{$errstr}({$errno})", Connection::ALERT_INTERNAL_ERROR, true); stream_set_timeout($sock, (int)$timeout, (int)(fmod($timeout, 1) * 1000000)); $opt += ['peer_name' => trim($host, '[]')]; try { return self::enable($sock, $opt); } catch (\Throwable $e) { @fclose($sock); throw $e; } } /** * 既に接続しているソケットでTLSを開始する(クライアント、STARTTLS等) * * @param resource $socket ソケット(返したストリームを閉じると、これも閉じる) * @param array $opt オプション * @return resource ストリーム * @throws TlsException */ public static function enable($socket, array $opt = []) { $conn = new Connection($socket, $opt, false); $conn->handshake(); return Stream::wrap($conn); } /** * 既に接続しているソケットでTLSを開始する(サーバー) * * @param resource $socket 受け付けたソケット * @param array $opt オプション(cert・keyが必要) * @return resource ストリーム * @throws TlsException */ public static function accept($socket, array $opt) { $conn = new Connection($socket, $opt, true); $conn->handshake(); return Stream::wrap($conn); } /** * TLSのストリームの情報 * * @param resource $stream * @return array|null version、cipher、group、alpn、peer_certificates、server_name、TLSでなければnull */ public static function info($stream): ?array { return Stream::connection($stream)?->info(); } /** * 読み込み中のエラー(feof()がtrueになった理由) * * @param resource $stream * @return TlsException|null */ public static function error($stream): ?TlsException { return Stream::connection($stream)?->lastError(); } /** * 読んでいないデータが残っているかどうか(stream_select()を使うサーバー用:TLSの中に溜まったデータは見えない) * * @param resource $stream * @return bool */ public static function pending($stream): bool { if (!is_resource($stream)) return false; return (stream_get_meta_data($stream)['unread_bytes'] ?? 0) > 0 || (bool)Stream::connection($stream)?->pending(); } /** * 最後の読み込みがタイムアウトしたかどうか * * @param resource $stream * @return bool */ public static function timedOut($stream): bool { return (bool)Stream::connection($stream)?->timedOut(); } }