@@ -229,6 +229,7 @@ class URLSessionWebSocketChannel: NSObject, URLSessionDelegate, URLSessionTaskDe
229229 ) {
230230 // コードを短くするために変数を定義
231231 let ps = challenge. protectionSpace
232+ let authMethod = ps. authenticationMethod
232233 let previousFailureCount = challenge. previousFailureCount
233234
234235 // 既に失敗している場合はチャレンジを中止する
@@ -250,12 +251,141 @@ class URLSessionWebSocketChannel: NSObject, URLSessionDelegate, URLSessionTaskDe
250251 " [ \( host) ] \( #function) : challenge= \( ps. host) : \( ps. port) , \( ps. authenticationMethod) previousFailureCount: \( previousFailureCount) "
251252 )
252253
253- // Basic 認証のみに対応している
254- // それ以外の認証方法は .performDefaultHandling で処理を続ける
255- guard ps. authenticationMethod == NSURLAuthenticationMethodHTTPBasic else {
254+ // 認証方式によって処理を分岐
255+ switch authMethod {
256+ case NSURLAuthenticationMethodServerTrust:
257+ // サーバー証明書検証
258+ handleServerTrustChallenge ( challenge, completionHandler: completionHandler)
259+ case NSURLAuthenticationMethodHTTPBasic:
260+ // basic 認証
261+ handleBasicAuthenticationChallenge ( challenge, completionHandler: completionHandler)
262+
263+ default :
264+ Logger . debug (
265+ type: . webSocketChannel,
266+ message:
267+ " [ \( host) ] \( #function) : Unsupported or unhandled authentication method ( \( authMethod) ), performing default handling. "
268+ )
269+ completionHandler ( . performDefaultHandling, nil )
270+ }
271+ }
272+
273+ private func loadCustomCertificate( ) -> SecCertificate ? {
274+ // 文字列として ca cert を定義する
275+ let caCertString = """
276+ """
277+ print ( caCertString)
278+ // PEM 文字列からヘッダー/フッターを除去し、Base64 部分を結合
279+ let base64String =
280+ caCertString
281+ . split ( separator: " \n " ) // 行ごとに分割
282+ . filter { !$0. hasPrefix ( " ----- " ) } // ヘッダー/フッター行を除外
283+ . joined ( ) // 残りの行 (Base64) を結合
284+
285+ // Base64 文字列を Data にデコード
286+ // options: .ignoreUnknownCharacters を指定すると、改行などが残っていても無視してくれる
287+ guard let certificateData = Data ( base64Encoded: base64String, options: . ignoreUnknownCharacters)
288+ else {
289+ Logger . error (
290+ type: . webSocketChannel,
291+ message: " [ \( host) ] Failed to decode Base64 certificate string from hardcoded PEM. " )
292+ return nil
293+ }
294+ return SecCertificateCreateWithData ( nil , certificateData as CFData )
295+ }
296+
297+ private func handleServerTrustChallenge(
298+ _ challenge: URLAuthenticationChallenge ,
299+ completionHandler: @escaping ( URLSession . AuthChallengeDisposition , URLCredential ? ) -> Void
300+ ) {
301+ Logger . debug ( type: . webSocketChannel, message: " handleServerTrustChallenge " )
302+ guard let serverTrust = challenge. protectionSpace. serverTrust else {
303+ Logger . debug ( type: . webSocketChannel, message: " handleServerTrustChallenge: no serverTrust " )
304+ completionHandler ( . performDefaultHandling, nil )
305+ return
306+ }
307+ guard let customCACert = loadCustomCertificate ( ) else {
308+ Logger . debug ( type: . webSocketChannel, message: " handleServerTrustChallenge: no customCACert " )
256309 completionHandler ( . performDefaultHandling, nil )
257310 return
258311 }
312+ // SecTrust オブジェクトにカスタム CA を信頼アンカーとして設定
313+ let policy = SecPolicyCreateBasicX509 ( ) // 基本的な X.509 ポリシーを使用
314+ var ossStatus = SecTrustSetPolicies ( serverTrust, policy)
315+ guard ossStatus == errSecSuccess else {
316+ Logger . warn ( type: . webSocketChannel, message: " SecTrustSetPolicies failed " )
317+ completionHandler ( . cancelAuthenticationChallenge, nil )
318+ return
319+ }
320+ // カスタム CA を信頼アンカーとして追加
321+ // ここで追加されたアンカー以外の証明書は無視される(システムの証明書も無視される
322+ // https://developer.apple.com/documentation/security/sectrustsetanchorcertificatesonly(_:_:)
323+ let anchorCertificates = [ customCACert]
324+ ossStatus = SecTrustSetAnchorCertificates ( serverTrust, anchorCertificates as CFArray )
325+ if ossStatus != errSecSuccess {
326+ Logger . warn ( type: . webSocketChannel, message: " SecTrustSetAnchorCertificates failed: \( ossStatus) " )
327+ completionHandler ( . cancelAuthenticationChallenge, nil )
328+ return
329+ }
330+
331+ // システムの信頼ストアではなく、提供されたアンカーのみを使用するように強制
332+ // これにより、カスタムCAによって署名されていること *のみ* を検証できる
333+ let setOnlyStatus = SecTrustSetAnchorCertificatesOnly ( serverTrust, true )
334+ guard setOnlyStatus == errSecSuccess else {
335+ // ログ出力はするが、致命的エラーとはしない場合もある
336+ Logger . warn ( type: . webSocketChannel, message: " Warning: Could not set anchor certificates only: \( setOnlyStatus) " )
337+ completionHandler ( . cancelAuthenticationChallenge, nil )
338+ return
339+ }
340+
341+ // ホスト名の検証ポリシーを追加(デフォルトで含まれている場合もあるが明示的に行うのが安全)
342+ // 注意: wss:// スキームの場合、ポリシー作成時に "wss" ではなく "https" を使うことが多い
343+ // または、ホスト名検証をSecTrustEvaluateの後で別途行う方法もある
344+ //Logger.debug(type: .webSocketChannel, message: "\(host) を HTTPS サーバー名として検証")
345+ //let sslpolicy = SecPolicyCreateSSL(true, host as CFString)
346+ //let sslpolicyStatus = SecTrustSetPolicies(serverTrust, sslpolicy) // 必要に応じて
347+ //guard sslpolicyStatus == errSecSuccess else {
348+ // Logger.warn(type: .webSocketChannel, message: "SecTrustSetPolicies failed: \(sslpolicyStatus)")
349+ // completionHandler(.cancelAuthenticationChallenge, nil)
350+ // return
351+ //}
352+
353+ // サーバー証明書を評価
354+ var error : CFError ?
355+ let trustResult = SecTrustEvaluateWithError ( serverTrust, & error)
356+
357+ if trustResult {
358+ // 信頼できると評価された場合はサーバーの証明書を使って認証情報を作成する
359+ Logger . debug ( type: . webSocketChannel, message: " Server trust evaluation succeeded " )
360+ completionHandler ( . useCredential, URLCredential ( trust: serverTrust) )
361+ } else {
362+ Logger . warn (
363+ type: . webSocketChannel,
364+ message: " Server trust evaluation failed: \( error? . localizedDescription ?? " Unknown error " ) "
365+ )
366+ completionHandler ( . cancelAuthenticationChallenge, nil )
367+ }
368+
369+ }
370+
371+ // --- Helper: Proxy Authentication ---
372+ private func handleBasicAuthenticationChallenge(
373+ _ challenge: URLAuthenticationChallenge ,
374+ completionHandler: @escaping ( URLSession . AuthChallengeDisposition , URLCredential ? ) -> Void
375+ ) {
376+ // 既存の Proxy Basic 認証ロジックを流用
377+ let ps = challenge. protectionSpace
378+ let previousFailureCount = challenge. previousFailureCount
379+
380+ // 既に失敗している場合はチャレンジを中止する
381+ guard previousFailureCount == 0 else {
382+ let message =
383+ " [ \( host) ] \( #function) : Proxy authentication failed (previous failure count: \( previousFailureCount) ). Proxy => \( String ( describing: proxy) ) "
384+ Logger . info ( type: . webSocketChannel, message: message)
385+ completionHandler ( . cancelAuthenticationChallenge, nil )
386+ disconnect ( error: SoraError . signalingChannelError ( reason: message) ) // disconnect は適切か要確認
387+ return
388+ }
259389
260390 // username と password をチェック
261391 guard let username = proxy? . username, let password = proxy? . password else {
0 commit comments