2016年8月1日 星期一

「Android」Mattermost Android 遇到 Self-signed certificate 解法


Mattermost Android版在遇到SSL使用Self-signed certificate  時, 會無法連接,

解決方法:

這裡下載原始碼開始Debug,一開始什麼都不改,先來看到底

登入時跳出的exception為


 javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

看了CODE之後發現,由於Mattermost的HTTP 是透過Okhttp 這個套件,

所以不能輕易的叫他吞下去當做沒看到。

換個方式,既然certification path not found. 我們就指定給它。 

(1)首先我們要把Self-signed certificate放到專案中的raw資料夾











































接下來在MattermostService.java中新增一個setCertificates函式,

用來將我們放在raw資料夾中的憑證指定給 Okhttp Client

public void setCertificates(InputStream... certificates)
    {
        try
        {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates)
            {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

                try
                {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e)
                {
                }
            }

            SSLContext sslContext = SSLContext.getInstance("TLS");

            TrustManagerFactory trustManagerFactory =
                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

            trustManagerFactory.init(keyStore);
            sslContext.init
                    (
                            null,
                            trustManagerFactory.getTrustManagers(),
                            new SecureRandom()
                    );
            client.setSslSocketFactory(sslContext.getSocketFactory());


        } catch (Exception e)
        {
            e.printStackTrace();
        }

    }

接下來修改MattermostService.java建構子,

新增一行呼叫setCertificates來讀取self cert


//add self cert
setCertificates(context.getResources().openRawResource(R.raw.nginx));

 public MattermostService(Context context) {
        this.context = context;

        String userAgent = context.getResources().getString(R.string.app_user_agent);

        cookieStore = new WebkitCookieManagerProxy();

        client.setCookieHandler(cookieStore);
        //add self cert
        setCertificates(context.getResources().openRawResource(R.raw.nginx));
        preferences = context.getSharedPreferences("App", Context.MODE_PRIVATE);
    }


到這裡, 你己經可以登入了, 但是會發現白白的一片, 因為還需要修改WebView SSL Error

的情況, 修改一下MainActivity.java

找到protected void setWebViewClient(WebView view)

增加一段處理SSL Error的程式碼


 @Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    // super.onReceivedSslError(view, handler, error);
    handler.proceed();
}


 protected void setWebViewClient(WebView view) {
        view.setWebViewClient(new WebViewClient() {

            @Override
            public void onPageFinished(WebView view, String url) {
                dialog.hide();
                Log.i("onPageFinished", "onPageFinished while loading");
                Log.i("onPageFinished", url);

                if (url.equals("about:blank")) {
                    AlertDialog.Builder alert = new AlertDialog.Builder(MainActivity.this);
                    alert.setTitle(R.string.error_retry);

                    alert.setPositiveButton(R.string.error_logout, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            MainActivity.this.onLogout();
                        }
                    });

                    alert.setNegativeButton(R.string.error_refresh, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            MainActivity.this.loadRootView();
                        }
                    });

                    alert.show();
                }
            }

            @Override
            public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
                Log.e("onReceivedHttpError", "onReceivedHttpError while loading");
                StringBuilder total = new StringBuilder();

                if (errorResponse.getData() != null) {
                    BufferedReader r = new BufferedReader(new InputStreamReader(errorResponse.getData()));
                    String line;
                    try {
                        while ((line = r.readLine()) != null) {
                            total.append(line);
                        }
                    } catch (IOException e) {
                        total.append("failed to read data");
                    }
                } else {
                    total.append("no data");
                }

                Log.e("onReceivedHttpError", total.toString());
            }

            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
               // super.onReceivedSslError(view, handler, error);
                handler.proceed();
            }

            @Override
            public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) {
                Log.e("onReceivedErrord", "onReceivedError while loading (d)");
                Log.e("onReceivedErrord", errorCode + " " + description + " " + failingUrl);
                webView.loadUrl("about:blank");
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                Uri uri = Uri.parse(url);

                // Do not open in other browser if gitlab
                if (uri.getPath().contains("/oauth/authorize")) {
                    return false;
                }

                // Do not open in other browser if gitlab
                if (uri.getPath().contains("/oauth/token")) {
                    return false;
                }

                // Do not open in other browser if gitlab
                if (uri.getPath().contains("/api/v3/user")) {
                    return false;
                }

                // Do not open in other browser if gitlab
                if (uri.getPath().contains("/users/sign_in")) {
                    return false;
                }

                if (!uri.getHost().equalsIgnoreCase(appUri.getHost())) {
                    openUrl(uri);
                    return true;
                }

                if (uri.getPath().startsWith("/static/help")) {
                    openUrl(uri);
                    return true;
                }

                if (uri.getPath().contains("/files/get/")) {
                    openUrl(uri);
                    return true;
                }

                return super.shouldOverrideUrlLoading(view, url);
            }

            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

                // Check to see if we need to attach the device Id
                if (url.toLowerCase().contains("/channels/")) {
                    if (!MattermostService.service.isAttached()) {
                        Log.i("MainActivity", "Attempting to attach device id");
                        MattermostService.service.init(MattermostService.service.getBaseUrl());
                        Promise<User> p = MattermostService.service.attachDevice();
                        if (p != null) {
                            p.then(new IResultListener<User>() {
                                @Override
                                public void onResult(Promise<User> promise) {
                                    if (promise.getError() != null) {
                                        Log.e("AttachDeviceId", promise.getError());
                                    } else {
                                        Log.i("AttachDeviceId", "Attached device_id to session");
                                        MattermostService.service.SetAttached();
                                    }
                                }
                            });
                        }
                    }
                }

                // Check if deviceID is missing
                if (url.toLowerCase().contains("/login")) {
                    MattermostService.service.SetAttached(false);
                }

                // Check to see if the user was trying to logout
                if (url.toLowerCase().endsWith("/logout")) {
                    MattermostApplication.handler.post(new Runnable() {
                        @Override
                        public void run() {
                            onLogout();
                        }
                    });
                }

                return super.shouldInterceptRequest(view, url);
            }
        });
    }

透過以上的修改後, Android版就可以正常登入Self-signed certificate 的Mattermost網頁

沒有留言:

張貼留言