diff --git a/README.md b/README.md index 9d4a82a47c44a272fc911f1ab214b4727e8f3685..c5c13fb74d6834680782ffa36777e6e4cd49a879 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ implementation 'org.bouncycastle:bcprov-jdk15to18:1.71' | auth_ts | 是 | String | 验签时间戳 | | background_timeout | 是 | String | 页面非活跃状态超时时长 (取值\[16, 3600\],单位是s) | | available_playtime | 是 | String | 单次连接可用时长 (值为0时,无超时判断) | - | touch_timeout | 否 | String | 单次连接可用时长 (值为0时,无超时判断) | + | touch_timeout | 否 | String | 无触控时长 (值为0时,无超时判断) | | user_id | 否 | String | 用户id | | client_mode | 是 | String | direct/management | | region_id | 否 | String | 云手机所在region的id | diff --git a/app/src/main/assets/certificates/CPH_background.crt b/app/src/main/assets/certificates/CPH_background.crt new file mode 100644 index 0000000000000000000000000000000000000000..c608aceee97cd464007bf8107c416d7d9cf10446 Binary files /dev/null and b/app/src/main/assets/certificates/CPH_background.crt differ diff --git a/app/src/main/assets/certificates/myhuaweicloud.com.crt b/app/src/main/assets/certificates/myhuaweicloud.com.crt new file mode 100644 index 0000000000000000000000000000000000000000..619b81c0e04f487f5d4570492814fb7ef7700477 --- /dev/null +++ b/app/src/main/assets/certificates/myhuaweicloud.com.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIETjCCAzagAwIBAgINAe5fIh38YjvUMzqFVzANBgkqhkiG9w0BAQsFADBMMSAw +HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFs +U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xODExMjEwMDAwMDBaFw0yODEx +MjEwMDAwMDBaMFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52 +LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdaydUMGCEAI9WXD+uu3Vxoa2uP +UGATeoHLl+6OimGUSyZ59gSnKvuk2la77qCk8HuKf1UfR5NhDW5xUTolJAgvjOH3 +idaSz6+zpz8w7bXfIa7+9UQX/dhj2S/TgVprX9NHsKzyqzskeU8fxy7quRU6fBhM +abO1IFkJXinDY+YuRluqlJBJDrnw9UqhCS98NE3QvADFBlV5Bs6i0BDxSEPouVq1 +lVW9MdIbPYa+oewNEtssmSStR8JvA+Z6cLVwzM0nLKWMjsIYPJLJLnNvBhBWk0Cq +o8VS++XFBdZpaFwGue5RieGKDkFNm5KQConpFmvv73W+eka440eKHRwup08CAwEA +AaOCASkwggElMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G +A1UdDgQWBBT473/yzXhnqN5vjySNiPGHAwKz6zAfBgNVHSMEGDAWgBSP8Et/qC5F +JK5NUPpjmove4t0bvDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6 +Ly9vY3NwMi5nbG9iYWxzaWduLmNvbS9yb290cjMwNgYDVR0fBC8wLTAroCmgJ4Yl +aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9yb290LXIzLmNybDBHBgNVHSAEQDA+ +MDwGBFUdIAAwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5j +b20vcmVwb3NpdG9yeS8wDQYJKoZIhvcNAQELBQADggEBAJmQyC1fQorUC2bbmANz +EdSIhlIoU4r7rd/9c446ZwTbw1MUcBQJfMPg+NccmBqixD7b6QDjynCy8SIwIVbb +0615XoFYC20UgDX1b10d65pHBf9ZjQCxQNqQmJYaumxtf4z1s4DfjGRzNpZ5eWl0 +6r/4ngGPoJVpjemEuunl1Ig423g7mNA2eymw0lIYkN5SQwCuaifIFJ6GlazhgDEw +fpolu4usBCOmmQDo8dIm7A9+O4orkjgTHY+GzYZSR+Y0fFukAj6KYXwidlNalFMz +hriSqHKvoflShx8xpfywgVcvzfTO3PYkz6fiNJBonf6q8amaEsybwMbDqKWwIX7e +SPY= +-----END CERTIFICATE----- diff --git a/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudLoginActivity.java b/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudLoginActivity.java index 056d0d92ee6366c28d65502f58532e596b0166a1..bd6eca0e9cc89c99df8f197a9b5077b1e5fba421 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudLoginActivity.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudLoginActivity.java @@ -31,7 +31,9 @@ import static com.huawei.cloudapp.utils.CasConstantsUtil.USERNAME; import static com.huawei.cloudapp.utils.CasConstantsUtil.USER_INFO; import android.app.Activity; +import android.content.Context; import android.content.Intent; +import android.content.res.AssetManager; import android.os.Bundle; import android.util.Log; @@ -48,14 +50,23 @@ import com.huawei.cloudapp.presenter.UserPresenter; import com.huawei.cloudapp.ui.views.MaterialLoginView; import com.huawei.cloudapp.utils.CasAESKeystoreUtils; import com.huawei.cloudapp.utils.CasCommonUtils; +import com.huawei.cloudapp.utils.CasHttpUtils; +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.List; +import javax.net.ssl.SSLHandshakeException; + public class CasCloudLoginActivity extends Activity implements IHandleData { private static final String TAG = "CasCloudLoginActivity"; + private static final String CERT_DIR = "certificates"; private MaterialLoginView loginView; private CasRecord mUserRecord; private UserPresenter mUserPresenter; @@ -81,9 +92,34 @@ public class CasCloudLoginActivity extends Activity implements IHandleData selectedRegion = REGION_NAME.get(Integer.parseInt(selectedRegionPosition)); } } + loadCertificates(this); initView(); } + private void loadCertificates(Context context) { + AssetManager assetManager = context.getAssets(); + String[] certificates; + try { + certificates = assetManager.list(CERT_DIR); + } catch (IOException e) { + Log.e(TAG, "get certificates failed."); + e.printStackTrace(); + return; + } + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + for (String certificate : certificates) { + InputStream in = assetManager.open(CERT_DIR + "/" + certificate); + CasHttpUtils.addCertificate((X509Certificate) cf.generateCertificate(in)); + in.close(); + } + } catch (CertificateException e) { + throw new RuntimeException(e); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void initView() { setContentView(R.layout.activity_cas_cloud_login); loginView = findViewById(R.id.login); @@ -223,8 +259,10 @@ public class CasCloudLoginActivity extends Activity implements IHandleData toastStr = getResources().getString(R.string.no_permission); } else if (e instanceof CustomException.ServerError) { toastStr = getResources().getString(R.string.service_internal_error); - } else if (e instanceof CustomException.ServiceUnavailableException) { + } else if (e instanceof CustomException.ServiceUnavailableException) { toastStr = getResources().getString(R.string.cas_phone_connect_server_fail_tip_message); + } else if (e instanceof SSLHandshakeException) { + toastStr = getResources().getString(R.string.cas_phone_certificate_exception_tip_message); } else { toastStr = getResources().getString(R.string.failed_to_login); Log.e(TAG, "handleError: ", e); diff --git a/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java b/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java index 5ccf1b3a97ec8436cb2f1c64f82046926912bad9..53755ebb446709628a59d3eb53446d1a72859e61 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java @@ -135,6 +135,8 @@ import java.util.List; import java.util.Timer; import java.util.TimerTask; +import javax.net.ssl.SSLHandshakeException; + public class CasCloudPhoneActivity extends FragmentActivity implements IHandleData { private static final String TAG = "CasCloudPhoneActivity"; @@ -1135,6 +1137,8 @@ public class CasCloudPhoneActivity extends FragmentActivity implements IHandleDa } else if (e instanceof CustomException.ParamInvalidException || e instanceof CustomException.ResourceNotFoundException) { message = getResources().getString(R.string.param_invalid); + } else if (e instanceof SSLHandshakeException) { + message = getResources().getString(R.string.cas_phone_certificate_exception_tip_message); } else { message = getResources().getString(R.string.request_failed); } diff --git a/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java b/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java index b369e65997c142059991c0a70117cb47d57fbcdc..245c2e9ec8ab61b3ddcf00feb727dbcb1a728e8f 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/fragment/home/PhoneListFragment.java @@ -96,6 +96,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import javax.net.ssl.SSLHandshakeException; + public class PhoneListFragment extends CloudPhoneFragment implements IHandleData, OnRefreshListener, OnLoadMoreListener { private static final String TAG = "PhoneListFragment"; @@ -854,6 +856,8 @@ public class PhoneListFragment extends CloudPhoneFragment implements IHandleData e instanceof CustomException.ServerNotFoundException || e instanceof CustomException.ResourceNotFoundException) { message = getResources().getString(R.string.param_invalid); + } else if (e instanceof SSLHandshakeException) { + message = getResources().getString(R.string.cas_phone_certificate_exception_tip_message); } else { message = getResources().getString(R.string.request_failed); } diff --git a/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java b/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java index 3ecbdc9b5457b89a066254a142b0fd5b2e4e8c5c..f4a39498a17850670fdc4ef0bd10c125a3684e1d 100644 --- a/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java +++ b/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java @@ -221,7 +221,7 @@ public class CasConstantsUtil { public static final String PERSONAL = "personal"; public static final String ENTERPRISE = "enterprise"; - public static final String MANAGEMENT_DEV_ENV_URL = "https://140.210.196.28:18000"; + public static final String MANAGEMENT_DEV_ENV_URL = "https://139.9.235.129:18000"; public static final String MANAGEMENT_PROD_ENV_URL = "https://139.9.146.101:18000"; public static final String PHONE = "phone"; public static final String LIST = "list"; diff --git a/app/src/main/java/com/huawei/cloudapp/utils/CasHttpUtils.java b/app/src/main/java/com/huawei/cloudapp/utils/CasHttpUtils.java index fb608e133f92b9d088eff6a7cf03e99aa1b88c8b..ce6837a07729234474e5b874c7268697bb4fb61c 100644 --- a/app/src/main/java/com/huawei/cloudapp/utils/CasHttpUtils.java +++ b/app/src/main/java/com/huawei/cloudapp/utils/CasHttpUtils.java @@ -2,6 +2,7 @@ package com.huawei.cloudapp.utils; import android.os.Handler; import android.os.Message; +import android.util.Base64; import android.util.Log; import androidx.annotation.NonNull; @@ -9,9 +10,17 @@ import androidx.annotation.NonNull; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -29,6 +38,11 @@ public class CasHttpUtils { public static final String TAG = "CasHttpUtils"; public static final MediaType JSON = MediaType.get("application/json"); + private static final List TRUSTED_CERTS = new ArrayList<>(); + + public static void addCertificate(X509Certificate cert) { + TRUSTED_CERTS.add(cert); + } public static void post(String url, Map header, String json, Handler handler, int msgCode) { RequestBody body = RequestBody.create(json, JSON); @@ -44,24 +58,25 @@ public class CasHttpUtils { .post(body) .build(); - OKHttpClientBuilder.buildOKHttpClient().build().newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull IOException e) { - Message message = new Message(); - message.what = msgCode; - message.obj = null; - handler.sendMessage(message); - Log.e(TAG, "Failed to get response."); - } + OKHttpClientBuilder.buildOKHttpClient(request.url().host()) + .build().newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + Message message = new Message(); + message.what = msgCode; + message.obj = null; + handler.sendMessage(message); + Log.e(TAG, "Failed to get response."); + } - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - Message message = new Message(); - message.what = msgCode; - message.obj = response.body().string(); - handler.sendMessage(message); - } - }); + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + Message message = new Message(); + message.what = msgCode; + message.obj = response.body().string(); + handler.sendMessage(message); + } + }); } public static void post(String url, Map header, String bodyStr, Callback callback) { @@ -78,7 +93,7 @@ public class CasHttpUtils { .post(body) .build(); - OKHttpClientBuilder.buildOKHttpClient() + OKHttpClientBuilder.buildOKHttpClient(request.url().host()) .callTimeout(10, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS) .build().newCall(request).enqueue(callback); @@ -95,14 +110,14 @@ public class CasHttpUtils { Request request = builder.url(url) .build(); - OKHttpClientBuilder.buildOKHttpClient() + OKHttpClientBuilder.buildOKHttpClient(request.url().host()) .callTimeout(10, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS) .build().newCall(request).enqueue(callback); } public static class OKHttpClientBuilder { - public static OkHttpClient.Builder buildOKHttpClient() { + public static OkHttpClient.Builder buildOKHttpClient(String serverHost) { try { TrustManager[] trustAllCerts = buildTrustManagers(); final SSLContext sslContext = SSLContext.getInstance("SSL"); @@ -111,7 +126,10 @@ public class CasHttpUtils { final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]); - builder.hostnameVerifier((hostname, session) -> true); + builder.hostnameVerifier((hostname, session) -> { + HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); + return hv.verify(serverHost, session) || serverHost.equals(hostname); + }); return builder; } catch (NoSuchAlgorithmException | KeyManagementException e) { e.printStackTrace(); @@ -127,7 +145,26 @@ public class CasHttpUtils { } @Override - public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + for (java.security.cert.X509Certificate cert : chain) { + try { + cert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException e) { + Log.e(TAG, "certificate [" + cert.getSubjectDN() + "] check failed, error msg: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + String publicKey = Base64.encodeToString(cert.getPublicKey().getEncoded(), Base64.DEFAULT); + for (X509Certificate trustedCert : TRUSTED_CERTS) { + String trustedPublicKey = Base64.encodeToString(trustedCert.getPublicKey().getEncoded(), Base64.DEFAULT); + if (trustedPublicKey.equalsIgnoreCase(publicKey)) { + Log.i(TAG, "verify certificate success: " + cert.getSubjectDN()); + return; + } + } + } + Log.e(TAG, "Failed to verify the certificate public key."); + throw new CertificateException("Failed to verify the certificate public key."); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ede62b7ae4d0c89cde38ce05b35f0aa58fc79102..f61ab515916138c31310bfe1df32bc3281a76f02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ 初始化状态错误,请重试 初始化失败,请重试 登录信息失效,请重新登录 + 证书错误,请使用最新版客户端 云手机ip: 进入云手机 云手机port: