fuzzy study

仕事・趣味で勉強したことのメモ

UnityのHttpWebRequestで自己署名証明書のローカルHTTPSサーバに通信したらいろいろハマった時の記録(結局ローカルやめてFaaSにした)

あるUnityアプリのテストのためにローカルにHTTPのAPIサーバを立てていたのだけど、 ある日うまく通信ができなくなってしまい、原因を探った時の記録。

事象1

エラー

E/Unity(20792): java.io.IOException: Cleartext HTTP traffic to xxx.xxx.xxx.xxx not permitted

HTTPなんか信頼できないから繋ぎたくないよ!と仰っています。

Android 9 (pie)になって、基本的にHTTPSへの接続しか許可されなくなったことによるエラーらしい。

Android 9(Pie)でHTTP通信を有効にする - Qiita

Android側でHTTP接続を許可する道もあるけど、本番APIHTTPSだしセキュリティ的にもイマイチだなーということで、ローカルAPIサーバをHTTPS化する方針で。

対処:mkcertで自己署名証明書を作る

mkcertで簡単にオレオレ証明書を発行する – RE:ENGINES

mkcert

github.com

このGitHubのReadMeに従って作業したら、難なく作れてしまった。。

FastAPIでHTTPSサーバを起動する

ローカルAPIサーバはFastAPIを使って構築していた。

通常起動はuvicorn main:appだが、HTTPSサーバとして起動する場合はuvicorn main:app --ssl-keyfile=./key.pem --ssl-certfile=./cert.pemとなる。

で、ビルドしたUnityアプリから接続してみる。

事象2

エラー

E/Unity(14330): javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

自己証明書は信頼できないので繋ぎたくないよ!と駄々をこねています。

対処:UnityWebRequestのcertificateHandlerに自己証明書に対応した独自ロジックを追加

※最終的に自分ではうまくいかなかった方法になりますので参考までに。

こちらの内容を参考に。

How to accept self signed certificate - Unity Answers

自前CertificateHandlerのコードは以下。

PUB_KEYに指定するのは、certificateファイル中のpublic keyのDER形式のバイナリを文字列で16進表記したもの。

using UnityEngine.Networking;

using System.Security.Cryptography.X509Certificates;

using UnityEngine;


public class MyCertificateHandler : CertificateHandler {

    private static string PUB_KEY = @"my pub key";

    

    protected override bool ValidateCertificate(byte[] certificateData)

    {

        X509Certificate2 cert = new X509Certificate2(certificateData);

        string pk = cert.GetPublicKeyString();

        Debug.Log(cert.ToString());

        if (pk.Equals(PUB_KEY)) return true;

        return false;

    }

}

ValidateCertificateがtrueを返すと信頼、というロジックを組めるようです。

※常にtrueを返す実装は絶対やっちゃダメ!

UnityWebRequestを使う部分ではこのように指定。

        UnityWebRequest www = UnityWebRequest.Post(url, formData);

        // 自己証明書に対応するため、自作の証明書確認ロジックを追加

        www.certificateHandler = new MyCertificateHandler();

        yield return www.SendWebRequest();

とやったんだけど、最終的に自分の証明書のpublic keyと、Unityで取得した証明書のpublic keyの値がどうしても一致せず、この実装は断念・・・(原因不明)

根本対処:FaaSを使う

今回自分の目的としては、Unityアプリのデバッグのために決まりきった内容のレスポンスを返すHTTPSAPIがほしかっただけ。

なので、代わりにGCP Cloud Functionsを使うことにした。

で、30分くらいでできた。

共同開発者にも使ってもらえるしこれでええやん。クラウド最高。