diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt index 035d7dd1..490f90fd 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt @@ -6,6 +6,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.RadioGroup import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.idormy.sms.forwarder.App @@ -38,9 +39,7 @@ import com.xuexiang.xutil.XUtil @Suppress("PrivatePropertyName", "PropertyName") @Page(name = "主动控制·客户端") -class ClientFragment : BaseFragment(), - View.OnClickListener, - RecyclerViewHolder.OnItemClickListener { +class ClientFragment : BaseFragment(), View.OnClickListener, RecyclerViewHolder.OnItemClickListener { val TAG: String = ClientFragment::class.java.simpleName private var appContext: App? = null @@ -113,6 +112,41 @@ class ClientFragment : BaseFragment(), } }) + //安全措施 + var safetyMeasuresId = R.id.rb_safety_measures_none + when (HttpServerUtils.clientSafetyMeasures) { + 1 -> { + safetyMeasuresId = R.id.rb_safety_measures_sign + binding!!.tvSignKey.text = getString(R.string.sign_key) + } + 2 -> { + safetyMeasuresId = R.id.rb_safety_measures_rsa + binding!!.tvSignKey.text = getString(R.string.public_key) + } + else -> { + binding!!.layoutSignKey.visibility = View.GONE + } + } + binding!!.rgSafetyMeasures.check(safetyMeasuresId) + binding!!.rgSafetyMeasures.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> + var safetyMeasures = 0 + binding!!.layoutSignKey.visibility = View.GONE + when (checkedId) { + R.id.rb_safety_measures_sign -> { + safetyMeasures = 1 + binding!!.tvSignKey.text = getString(R.string.sign_key) + binding!!.layoutSignKey.visibility = View.VISIBLE + } + R.id.rb_safety_measures_rsa -> { + safetyMeasures = 2 + binding!!.tvSignKey.text = getString(R.string.public_key) + binding!!.layoutSignKey.visibility = View.VISIBLE + } + else -> {} + } + HttpServerUtils.clientSafetyMeasures = safetyMeasures + } + binding!!.etSignKey.setText(HttpServerUtils.clientSignKey) binding!!.etSignKey.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} @@ -136,30 +170,46 @@ class ClientFragment : BaseFragment(), } Log.d(TAG, "serverHistory = $serverHistory") - MaterialDialog.Builder(requireContext()) - .title(R.string.server_history) - .items(serverHistory.keys) - .itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> - //XToastUtils.info("$which: $text") - val matches = Regex("【(.*)】(.*)", RegexOption.IGNORE_CASE).findAll(text).toList().flatMap(MatchResult::groupValues) - Log.i(TAG, "matches = $matches") - if (matches.isNotEmpty()) { - binding!!.etServerAddress.setText(matches[2]) + MaterialDialog.Builder(requireContext()).title(R.string.server_history).items(serverHistory.keys).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> + //XToastUtils.info("$which: $text") + val matches = Regex("【(.*)】(.*)", RegexOption.IGNORE_CASE).findAll(text).toList().flatMap(MatchResult::groupValues) + Log.i(TAG, "matches = $matches") + if (matches.isNotEmpty()) { + binding!!.etServerAddress.setText(matches[2]) + } else { + binding!!.etServerAddress.setText(text) + } + val signKey = serverHistory[text].toString() + if (!TextUtils.isEmpty(signKey)) { + val keyMatches = Regex("(.*)##(.*)", RegexOption.IGNORE_CASE).findAll(signKey).toList().flatMap(MatchResult::groupValues) + Log.i(TAG, "keyMatches = $keyMatches") + if (keyMatches.isNotEmpty()) { + binding!!.etSignKey.setText(keyMatches[1]) + var safetyMeasuresId = R.id.rb_safety_measures_none + when (keyMatches[2]) { + "1" -> { + safetyMeasuresId = R.id.rb_safety_measures_sign + binding!!.tvSignKey.text = getString(R.string.sign_key) + } + "2" -> { + safetyMeasuresId = R.id.rb_safety_measures_rsa + binding!!.tvSignKey.text = getString(R.string.public_key) + } + else -> { + binding!!.tvSignKey.visibility = View.GONE + binding!!.etSignKey.visibility = View.GONE + } + } + binding!!.rgSafetyMeasures.check(safetyMeasuresId) } else { - binding!!.etServerAddress.setText(text) + binding!!.etSignKey.setText(serverHistory[text]) } - binding!!.etSignKey.setText(serverHistory[text]) - true // allow selection } - .positiveText(R.string.select) - .negativeText(R.string.cancel) - .neutralText(R.string.clear_history) - .neutralColor(ResUtils.getColors(R.color.red)) - .onNeutral { _: MaterialDialog?, _: DialogAction? -> - serverHistory.clear() - HttpServerUtils.serverHistory = "" - } - .show() + true // allow selection + }.positiveText(R.string.select).negativeText(R.string.cancel).neutralText(R.string.clear_history).neutralColor(ResUtils.getColors(R.color.red)).onNeutral { _: MaterialDialog?, _: DialogAction? -> + serverHistory.clear() + HttpServerUtils.serverHistory = "" + }.show() } R.id.btn_server_test -> { if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) { @@ -183,22 +233,12 @@ class ClientFragment : BaseFragment(), XToastUtils.error(getString(R.string.click_test_button_first)) return } - if (serverConfig != null && ( - (item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) - || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) - || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) - || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) - || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) - || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol) - ) - ) { + if (serverConfig != null && ((item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol))) { XToastUtils.error(getString(R.string.disabled_on_the_server)) return } - @Suppress("UNCHECKED_CAST") - PageOption.to(Class.forName(item.classPath) as Class) //跳转的fragment - .setNewActivity(true) - .open(this) + @Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(item.classPath) as Class) //跳转的fragment + .setNewActivity(true).open(this) } catch (e: Exception) { e.printStackTrace() XToastUtils.error(e.message.toString()) @@ -212,58 +252,87 @@ class ClientFragment : BaseFragment(), val msgMap: MutableMap = mutableMapOf() val timestamp = System.currentTimeMillis() msgMap["timestamp"] = timestamp - val clientSignKey = HttpServerUtils.clientSignKey - if (!TextUtils.isEmpty(clientSignKey)) { - msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + + val clientSignKey = HttpServerUtils.clientSignKey.toString() + if ((HttpServerUtils.clientSafetyMeasures == 1 || HttpServerUtils.clientSafetyMeasures == 2) && TextUtils.isEmpty(clientSignKey)) { + if (needToast) XToastUtils.error("请输入签名密钥或RSA公钥") + return + } + + if (HttpServerUtils.clientSafetyMeasures == 1 && !TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey) } val dataMap: MutableMap = mutableMapOf() msgMap["data"] = dataMap - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - if (needToast) mCountDownHelper?.start() - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s - .cacheMode(CacheMode.NO_CACHE) - //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 - //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 - //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 - .timeStamp(true) - .execute(object : SimpleCallBack() { + .cacheMode(CacheMode.NO_CACHE).timeStamp(true) - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - if (needToast) mCountDownHelper?.finish() - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) - if (resp.code == 200) { - serverConfig = resp.data!! - if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - //删除3.0.8之前保存的记录 - serverHistory.remove(HttpServerUtils.serverAddress.toString()) - //添加到历史记录 - val key = "【${serverConfig?.extraDeviceMark}】${HttpServerUtils.serverAddress.toString()}" - serverHistory[key] = HttpServerUtils.clientSignKey ?: "" - HttpServerUtils.serverHistory = Gson().toJson(serverHistory) - HttpServerUtils.serverConfig = Gson().toJson(serverConfig) + if (needToast) mCountDownHelper?.start() + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + if (needToast) mCountDownHelper?.finish() + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) + } + val resp: BaseResponse = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + serverConfig = resp.data!! + if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + //删除3.0.8之前保存的记录 + serverHistory.remove(HttpServerUtils.serverAddress.toString()) + //添加到历史记录 + val key = "【${serverConfig?.extraDeviceMark}】${HttpServerUtils.serverAddress.toString()}" + if (TextUtils.isEmpty(HttpServerUtils.clientSignKey)) { + serverHistory[key] = "SMSFORWARDER##" + HttpServerUtils.clientSafetyMeasures.toString() } else { - if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + serverHistory[key] = HttpServerUtils.clientSignKey + "##" + HttpServerUtils.clientSafetyMeasures.toString() } - } catch (e: Exception) { - e.printStackTrace() - if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + HttpServerUtils.serverHistory = Gson().toJson(serverHistory) + HttpServerUtils.serverConfig = Gson().toJson(serverConfig) + } else { + if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) } if (needToast) mCountDownHelper?.finish() + } catch (e: Exception) { + e.printStackTrace() + if (needToast) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + mCountDownHelper?.finish() + } } - - }) + } + }) } override fun onDestroyView() { diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt index 231897f8..42f9da85 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt @@ -10,6 +10,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CompoundButton +import android.widget.RadioGroup import com.hjq.permissions.OnPermissionCallback import com.hjq.permissions.Permission import com.hjq.permissions.XXPermissions @@ -18,20 +19,19 @@ import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.databinding.FragmentServerBinding import com.idormy.sms.forwarder.service.HttpService -import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT -import com.idormy.sms.forwarder.utils.HttpServerUtils -import com.idormy.sms.forwarder.utils.RandomUtils -import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.* import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xpage.annotation.Page import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.button.SmoothCheckBox import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xui.widget.picker.XSeekBar import com.xuexiang.xutil.app.ServiceUtils import com.xuexiang.xutil.net.NetworkUtils import com.xuexiang.xutil.system.ClipboardUtils import java.io.File import java.net.InetAddress +import java.security.KeyPairGenerator @Suppress("PrivatePropertyName") @@ -79,6 +79,65 @@ class ServerFragment : BaseFragment(), View.OnClickListe //启动更新UI定时器 handler.post(runnable) + //安全措施 + var safetyMeasuresId = R.id.rb_safety_measures_none + when (HttpServerUtils.safetyMeasures) { + 1 -> { + safetyMeasuresId = R.id.rb_safety_measures_sign + binding!!.layoutSignKey.visibility = View.VISIBLE + binding!!.layoutTimeTolerance.visibility = View.VISIBLE + } + 2 -> { + safetyMeasuresId = R.id.rb_safety_measures_rsa + binding!!.layoutPrivateKey.visibility = View.VISIBLE + binding!!.layoutPublicKey.visibility = View.VISIBLE + } + else -> {} + } + binding!!.rgSafetyMeasures.check(safetyMeasuresId) + binding!!.rgSafetyMeasures.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> + var safetyMeasures = 0 + binding!!.layoutSignKey.visibility = View.GONE + binding!!.layoutTimeTolerance.visibility = View.GONE + binding!!.layoutPrivateKey.visibility = View.GONE + binding!!.layoutPublicKey.visibility = View.GONE + when (checkedId) { + R.id.rb_safety_measures_sign -> { + safetyMeasures = 1 + binding!!.layoutSignKey.visibility = View.VISIBLE + binding!!.layoutTimeTolerance.visibility = View.VISIBLE + } + R.id.rb_safety_measures_rsa -> { + safetyMeasures = 2 + binding!!.layoutPrivateKey.visibility = View.VISIBLE + binding!!.layoutPublicKey.visibility = View.VISIBLE + } + else -> {} + } + HttpServerUtils.safetyMeasures = safetyMeasures + } + + //RSA公私钥 + binding!!.btnCopyPublicKey.setOnClickListener(this) + binding!!.btnGenerateKey.setOnClickListener(this) + binding!!.etPublicKey.setText(HttpServerUtils.serverPublicKey) + binding!!.etPublicKey.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + HttpServerUtils.serverPublicKey = binding!!.etPublicKey.text.toString().trim() + } + }) + binding!!.etPrivateKey.setText(HttpServerUtils.serverPrivateKey) + binding!!.etPrivateKey.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + HttpServerUtils.serverPrivateKey = binding!!.etPrivateKey.text.toString().trim() + } + }) + + //签名密钥 binding!!.btnSignKey.setOnClickListener(this) binding!!.btnPathPicker.setOnClickListener(this) binding!!.etSignKey.setText(HttpServerUtils.serverSignKey) @@ -89,6 +148,13 @@ class ServerFragment : BaseFragment(), View.OnClickListe HttpServerUtils.serverSignKey = binding!!.etSignKey.text.toString().trim() } }) + //时间容差 + binding!!.xsbTimeTolerance.setDefaultValue(HttpServerUtils.timeTolerance) + binding!!.xsbTimeTolerance.setOnSeekBarListener { _: XSeekBar?, newValue: Int -> + HttpServerUtils.timeTolerance = newValue + } + + //web客户端 binding!!.etWebPath.setText(HttpServerUtils.serverWebPath) binding!!.etWebPath.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} @@ -155,6 +221,29 @@ class ServerFragment : BaseFragment(), View.OnClickListe } refreshButtonText() } + R.id.btn_generate_key -> { + val generator = KeyPairGenerator.getInstance("RSA") //密钥生成器 + generator.initialize(2048) + val keyPair = generator.genKeyPair() + val publicKey = keyPair.public + val privateKey = keyPair.private + + val publicKeyEncoded = Base64.encode(publicKey.encoded) + val privateKeyEncoded = Base64.encode(privateKey.encoded) + + println("publicKey=$publicKeyEncoded") + println("privateKey=$privateKeyEncoded") + + binding!!.etPublicKey.setText(publicKeyEncoded) + binding!!.etPrivateKey.setText(privateKeyEncoded) + + ClipboardUtils.copyText(publicKeyEncoded) + XToastUtils.info(getString(R.string.rsa_key_tips)) + } + R.id.btn_copy_public_key -> { + ClipboardUtils.copyText(binding!!.etPublicKey.text) + XToastUtils.info(getString(R.string.rsa_key_tips2)) + } R.id.btn_sign_key -> { val sign = RandomUtils.getRandomNumbersAndLetters(16) ClipboardUtils.copyText(sign) diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt index 47aca929..0045a796 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt @@ -10,9 +10,7 @@ import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.databinding.FragmentClientBatteryQueryBinding import com.idormy.sms.forwarder.entity.BatteryInfo import com.idormy.sms.forwarder.server.model.BaseResponse -import com.idormy.sms.forwarder.utils.HttpServerUtils -import com.idormy.sms.forwarder.utils.SettingUtils -import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.* import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.callback.SimpleCallBack @@ -60,53 +58,71 @@ class BatteryQueryFragment : BaseFragment() val dataMap: MutableMap = mutableMapOf() msgMap["data"] = dataMap - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) - //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 - //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 - //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - val batteryInfo = resp.data ?: return + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } - val groupListView = binding!!.infoList - val section = XUIGroupListView.newSection(context) - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {} - if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {} - if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {} - if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {} - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {} - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {} - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {} - section.addTo(groupListView) - - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } - } + val resp: BaseResponse = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + val batteryInfo = resp.data ?: return + + val groupListView = binding!!.infoList + val section = XUIGroupListView.newSection(context) + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {} + if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {} + if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {} + if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {} + section.addTo(groupListView) + + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + }) - }) } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt index 7f23502b..4ce778c7 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt @@ -207,46 +207,65 @@ class CallQueryFragment : BaseFragment() { if (refresh) pageNum = 1 msgMap["data"] = CallQueryData(callType, pageNum, pageSize, keyword) - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse?> = Gson().fromJson(response, object : TypeToken?>>() {}.type) - if (resp.code == 200) { - //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - pageNum++ - if (refresh) { - mAdapter!!.refresh(resp.data) - binding!!.refreshLayout.finishRefresh() - binding!!.recyclerView.scrollToPosition(0) - } else { - mAdapter!!.loadMore(resp.data) - binding!!.refreshLayout.finishLoadMore() - } - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } + val resp: BaseResponse?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + pageNum++ + if (refresh) { + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + mAdapter!!.loadMore(resp.data) + binding!!.refreshLayout.finishLoadMore() + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } - - }) + } + }) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt index 92106958..120c8959 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt @@ -18,10 +18,8 @@ import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding import com.idormy.sms.forwarder.entity.CloneInfo import com.idormy.sms.forwarder.server.model.BaseResponse -import com.idormy.sms.forwarder.utils.CommonUtils -import com.idormy.sms.forwarder.utils.HttpServerUtils -import com.idormy.sms.forwarder.utils.SettingUtils -import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.Base64 import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.cache.model.CacheMode @@ -181,12 +179,7 @@ class CloneFragment : BaseFragment(), View.OnClickL XToastUtils.error(getString(R.string.export_failed)) } } catch (e: Exception) { - XToastUtils.error( - String.format( - getString(R.string.export_failed_tips), - e.message - ) - ) + XToastUtils.error(String.format(getString(R.string.export_failed_tips), e.message)) } } //导入配置 @@ -225,12 +218,7 @@ class CloneFragment : BaseFragment(), View.OnClickL XToastUtils.error(getString(R.string.import_failed)) } } catch (e: Exception) { - XToastUtils.error( - String.format( - getString(R.string.import_failed_tips), - e.message - ) - ) + XToastUtils.error(String.format(getString(R.string.import_failed_tips), e.message)) } } } @@ -258,43 +246,59 @@ class CloneFragment : BaseFragment(), View.OnClickL } msgMap["data"] = HttpServerUtils.exportSettings() - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) - //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 - //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 - //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse = Gson().fromJson( - response, - object : TypeToken>() {}.type - ) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + pushCountDownHelper?.finish() + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } + val resp: BaseResponse = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } - - }) + pushCountDownHelper?.finish() + } + }) } @@ -323,62 +327,80 @@ class CloneFragment : BaseFragment(), View.OnClickL dataMap["version_code"] = AppUtils.getAppVersionCode() msgMap["data"] = dataMap - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) - //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 - //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 - //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - //替换Date字段为当前时间 - val builder = GsonBuilder() - builder.registerTypeAdapter( - Date::class.java, - JsonDeserializer { _, _, _ -> Date() }) - val gson = builder.create() - val resp: BaseResponse = gson.fromJson( - response, - object : TypeToken>() {}.type - ) - if (resp.code == 200) { - val cloneInfo = resp.data - Log.d(TAG, "cloneInfo = $cloneInfo") + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + exportCountDownHelper?.finish() + } - if (cloneInfo == null) { - XToastUtils.error(ResUtils.getString(R.string.request_failed)) - return - } - - //判断版本是否一致 - HttpServerUtils.compareVersion(cloneInfo) - - if (HttpServerUtils.restoreSettings(cloneInfo)) { - XToastUtils.success(getString(R.string.import_succeeded)) - } - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } - } - }) + //替换Date字段为当前时间 + val builder = GsonBuilder() + builder.registerTypeAdapter( + Date::class.java, + JsonDeserializer { _, _, _ -> Date() }) + val gson = builder.create() + val resp: BaseResponse = gson.fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + val cloneInfo = resp.data + Log.d(TAG, "cloneInfo = $cloneInfo") + + if (cloneInfo == null) { + XToastUtils.error(ResUtils.getString(R.string.request_failed)) + return + } + + //判断版本是否一致 + HttpServerUtils.compareVersion(cloneInfo) + + if (HttpServerUtils.restoreSettings(cloneInfo)) { + XToastUtils.success(getString(R.string.import_succeeded)) + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + exportCountDownHelper?.finish() + } + }) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt index ec5a8828..d821b66a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt @@ -190,43 +190,60 @@ class ContactQueryFragment : BaseFragment() else ContactQueryData(1, 20, null, keyword) - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) - //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 - //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 - //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse?> = Gson().fromJson(response, object : TypeToken?>>() {}.type) - if (resp.code == 200) { - //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - mAdapter!!.refresh(resp.data) - binding!!.refreshLayout.finishRefresh() - binding!!.recyclerView.scrollToPosition(0) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } + val resp: BaseResponse?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } - - }) + } + }) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt index eed73e0a..53d36a65 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt @@ -197,46 +197,66 @@ class SmsQueryFragment : BaseFragment() { if (refresh) pageNum = 1 msgMap["data"] = SmsQueryData(smsType, pageNum, pageSize, keyword) - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse?> = Gson().fromJson(response, object : TypeToken?>>() {}.type) - if (resp.code == 200) { - //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - pageNum++ - if (refresh) { - mAdapter!!.refresh(resp.data) - binding!!.refreshLayout.finishRefresh() - binding!!.recyclerView.scrollToPosition(0) - } else { - mAdapter!!.loadMore(resp.data) - binding!!.refreshLayout.finishLoadMore() - } - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } + val resp: BaseResponse?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + pageNum++ + if (refresh) { + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + mAdapter!!.loadMore(resp.data) + binding!!.refreshLayout.finishLoadMore() + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } - - }) + } + }) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt index 89786374..1545fb05 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt @@ -112,43 +112,60 @@ class SmsSendFragment : BaseFragment(), View.OnCl dataMap["msg_content"] = msgContent msgMap["data"] = dataMap - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - mCountDownHelper?.start() - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) - //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 - //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 - //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - mCountDownHelper?.finish() - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + mCountDownHelper?.start() + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + mCountDownHelper?.finish() + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } - mCountDownHelper?.finish() + val resp: BaseResponse = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } - - }) + mCountDownHelper?.finish() + } + }) } else -> {} } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt index 3955dd03..10567b9d 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt @@ -10,9 +10,7 @@ import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.databinding.FragmentClientWolSendBinding import com.idormy.sms.forwarder.server.model.BaseResponse -import com.idormy.sms.forwarder.utils.HttpServerUtils -import com.idormy.sms.forwarder.utils.SettingUtils -import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.* import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.cache.model.CacheMode @@ -144,46 +142,63 @@ class WolSendFragment : BaseFragment(), View.OnCl dataMap["port"] = port msgMap["data"] = dataMap - val requestMsg: String = Gson().toJson(msgMap) + var requestMsg: String = Gson().toJson(msgMap) Log.i(TAG, "requestMsg:$requestMsg") - mCountDownHelper?.start() - XHttp.post(requestUrl) - .upJson(requestMsg) + val postRequest = XHttp.post(requestUrl) .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .cacheMode(CacheMode.NO_CACHE) .timeStamp(true) - .execute(object : SimpleCallBack() { - override fun onError(e: ApiException) { - XToastUtils.error(e.displayMessage) - mCountDownHelper?.finish() - } + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + try { + requestMsg = Base64.encode(requestMsg.toByteArray()) + requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) + Log.i(TAG, "requestMsg: $requestMsg") + } catch (e: Exception) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + e.printStackTrace() + return + } + postRequest.upString(requestMsg) + } else { + postRequest.upJson(requestMsg) + } - override fun onSuccess(response: String) { - Log.i(TAG, response) - try { - val resp: BaseResponse = Gson().fromJson( - response, - object : TypeToken>() {}.type - ) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - //添加到历史记录 - wolHistory[mac] = ip - HttpServerUtils.wolHistory = Gson().toJson(wolHistory) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + mCountDownHelper?.start() + postRequest.execute(object : SimpleCallBack() { + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + mCountDownHelper?.finish() + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + var json = response + if (HttpServerUtils.clientSafetyMeasures == 2) { + val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) + json = RSACrypt.decryptByPublicKey(json, publicKey) + json = String(Base64.decode(json)) } - mCountDownHelper?.finish() + val resp: BaseResponse = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + //添加到历史记录 + wolHistory[mac] = ip + HttpServerUtils.wolHistory = Gson().toJson(wolHistory) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } - - }) + mCountDownHelper?.finish() + } + }) } else -> {} } diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt index 80f429bb..43f8f247 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt @@ -1,13 +1,17 @@ package com.idormy.sms.forwarder.server.component -import android.text.TextUtils import android.util.Log import com.google.gson.GsonBuilder import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.idormy.sms.forwarder.utils.RSACrypt +import com.xuexiang.xrouter.utils.TextUtils import com.yanzhenjie.andserver.annotation.Converter +import com.yanzhenjie.andserver.error.HttpException import com.yanzhenjie.andserver.framework.MessageConverter import com.yanzhenjie.andserver.framework.body.JsonBody +import com.yanzhenjie.andserver.framework.body.StringBody import com.yanzhenjie.andserver.http.ResponseBody import com.yanzhenjie.andserver.util.IOUtils import com.yanzhenjie.andserver.util.MediaType @@ -25,7 +29,17 @@ class AppMessageConverter : MessageConverter { override fun convert(output: Any?, mediaType: MediaType?): ResponseBody { //返回统一结构报文 - return JsonBody(HttpServerUtils.response(output)) + var response = HttpServerUtils.response(output) + Log.d(TAG, "response: $response") + + if (HttpServerUtils.safetyMeasures != 2) { + return JsonBody(response) + } + + val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString()) + response = Base64.encode(response.toByteArray()) + response = RSACrypt.encryptByPrivateKey(response, privateKey) + return StringBody(response) } @Throws(IOException::class) @@ -33,9 +47,21 @@ class AppMessageConverter : MessageConverter { val charset: Charset? = mediaType?.charset Log.d(TAG, "Charset: $charset") - val json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset) + var json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset) Log.d(TAG, "Json: $json") + if (HttpServerUtils.safetyMeasures == 2) { + if (TextUtils.isEmpty(HttpServerUtils.serverPrivateKey)) { + Log.e(TAG, "RSA解密失败: 私钥为空") + throw HttpException(500, "服务端未配置私钥") + } + + val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString()) + json = RSACrypt.decryptByPrivateKey(json, privateKey) + json = String(Base64.decode(json)) + Log.d(TAG, "Json: $json") + } + //修改接口数据中的null、“”为默认值 val builder = GsonBuilder() builder.registerTypeAdapter(Int::class.java, IntegerDefaultAdapter()) @@ -45,7 +71,7 @@ class AppMessageConverter : MessageConverter { Log.d(TAG, "Bean: $t") //校验时间戳(时间误差不能超过1小时)&& 签名 - if (!TextUtils.isEmpty(HttpServerUtils.serverSignKey)) { + if (HttpServerUtils.safetyMeasures == 1) { HttpServerUtils.checkSign(t as BaseRequest<*>) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Base64.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Base64.kt new file mode 100644 index 00000000..ae6ea9c7 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Base64.kt @@ -0,0 +1,88 @@ +package com.idormy.sms.forwarder.utils + +import java.io.UnsupportedEncodingException + +/** + * Base64编码解码 + */ +object Base64 { + + private val base64EncodeChars = charArrayOf('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/') + + private val base64DecodeChars = byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1) + + fun encode(data: ByteArray): String { + val sb = StringBuffer() + val len = data.size + var i = 0 + var b1: Int + var b2: Int + var b3: Int + while (i < len) { + b1 = ((data[i++]).toInt() and 0xff) + if (i == len) { + sb.append(base64EncodeChars[b1.ushr(2)]) + sb.append(base64EncodeChars[b1 and 0x3 shl 4]) + sb.append("==") + break + } + b2 = (data[i++]).toInt() and 0xff + if (i == len) { + sb.append(base64EncodeChars[b1.ushr(2)]) + sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0).ushr(4)]) + sb.append(base64EncodeChars[b2 and 0x0f shl 2]) + sb.append("=") + break + } + b3 = (data[i++]).toInt() and 0xff + sb.append(base64EncodeChars[b1.ushr(2)]) + sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0).ushr(4)]) + sb.append(base64EncodeChars[b2 and 0x0f shl 2 or (b3 and 0xc0).ushr(6)]) + sb.append(base64EncodeChars[b3 and 0x3f]) + } + return sb.toString() + } + + @Throws(UnsupportedEncodingException::class) + fun decode(str: String): ByteArray { + val sb = StringBuffer() + val data = str.toByteArray(charset("US-ASCII")) + val len = data.size + var i = 0 + var b1: Int + var b2: Int + var b3: Int + var b4: Int + while (i < len) { + /* b1 */ + do { + b1 = base64DecodeChars[(data[i++]).toInt()].toInt() + } while (i < len && b1 == -1) + if (b1 == -1) break + /* b2 */ + do { + b2 = base64DecodeChars[(data[i++]).toInt()].toInt() + } while (i < len && b2 == -1) + if (b2 == -1) break + sb.append((b1 shl 2 or (b2 and 0x30).ushr(4)).toChar()) + /* b3 */ + do { + b3 = data[i++].toInt() + if (b3 == 61) return sb.toString().toByteArray(charset("ISO-8859-1")) + b3 = base64DecodeChars[b3].toInt() + } while (i < len && b3 == -1) + if (b3 == -1) break + sb.append((b2 and 0x0f shl 4 or (b3 and 0x3c).ushr(2)).toChar()) + /* b4 */ + do { + b4 = data[i++].toInt() + if (b4 == 61) return sb.toString().toByteArray(charset("ISO-8859-1")) + b4 = base64DecodeChars[b4].toInt() + } while (i < len && b4 == -1) + if (b4 == -1) break + sb.append((b3 and 0x03 shl 6 or b4).toChar()) + } + return sb.toString().toByteArray(charset("ISO-8859-1")) + } + +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 9abee3f6..5bfb308c 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -322,7 +322,11 @@ const val HTTP_SUCCESS_CODE: Int = 200 const val HTTP_FAILURE_CODE: Int = 500 const val SP_ENABLE_SERVER = "enable_server" const val SP_ENABLE_SERVER_AUTORUN = "enable_server_autorun" +const val SP_SERVER_SAFETY_MEASURES = "server_safety_measures" const val SP_SERVER_SIGN_KEY = "server_sign_key" +const val SP_SERVER_TIME_TOLERANCE = "server_time_tolerance" +const val SP_SERVER_PUBLIC_KEY = "server_public_key" +const val SP_SERVER_PRIVATE_KEY = "server_private_key" const val SP_SERVER_WEB_PATH = "server_web_path" const val SP_ENABLE_API_CLONE = "enable_api_clone" const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send" @@ -335,6 +339,7 @@ const val SP_WOL_HISTORY = "wol_history" const val SP_SERVER_ADDRESS = "server_address" const val SP_SERVER_HISTORY = "server_history" const val SP_SERVER_CONFIG = "server_config" +const val SP_CLIENT_SAFETY_MEASURES = "client_safety_measures" const val SP_CLIENT_SIGN_KEY = "client_sign_key" var CLIENT_FRAGMENT_LIST = listOf( PageInfo( diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt index 108d3895..ee0f16a7 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt @@ -32,6 +32,30 @@ class HttpServerUtils private constructor() { MMKVUtils.put(SP_ENABLE_SERVER_AUTORUN, enableServerAutorun) } + //服务端安全设置 + @JvmStatic + var safetyMeasures: Int + get() = MMKVUtils.getInt(SP_SERVER_SAFETY_MEASURES, if (TextUtils.isEmpty(serverSignKey)) 0 else 1) + set(safetyMeasures) { + MMKVUtils.put(SP_SERVER_SAFETY_MEASURES, safetyMeasures) + } + + //服务端RSA公钥 + @JvmStatic + var serverPublicKey: String? + get() = MMKVUtils.getString(SP_SERVER_PUBLIC_KEY, "") + set(serverPublicKey) { + MMKVUtils.put(SP_SERVER_PUBLIC_KEY, serverPublicKey) + } + + //服务端RSA私钥 + @JvmStatic + var serverPrivateKey: String? + get() = MMKVUtils.getString(SP_SERVER_PRIVATE_KEY, "") + set(serverPrivateKey) { + MMKVUtils.put(SP_SERVER_PRIVATE_KEY, serverPrivateKey) + } + //服务端签名密钥 @JvmStatic var serverSignKey: String? @@ -40,6 +64,14 @@ class HttpServerUtils private constructor() { MMKVUtils.put(SP_SERVER_SIGN_KEY, serverSignKey) } + //时间容差 + @JvmStatic + var timeTolerance: Int + get() = MMKVUtils.getInt(SP_SERVER_TIME_TOLERANCE, 600) + set(timeTolerance) { + MMKVUtils.put(SP_SERVER_TIME_TOLERANCE, timeTolerance) + } + //自定义web客户端目录 @JvmStatic var serverWebPath: String? @@ -72,7 +104,15 @@ class HttpServerUtils private constructor() { MMKVUtils.put(SP_SERVER_CONFIG, serverConfig) } - //客户端签名密钥 + //服务端安全设置 + @JvmStatic + var clientSafetyMeasures: Int + get() = MMKVUtils.getInt(SP_CLIENT_SAFETY_MEASURES, if (TextUtils.isEmpty(clientSignKey)) 0 else 1) + set(clientSafetyMeasures) { + MMKVUtils.put(SP_CLIENT_SAFETY_MEASURES, clientSafetyMeasures) + } + + //客户端签名密钥/RSA公钥 @JvmStatic var clientSignKey: String? get() = MMKVUtils.getString(SP_CLIENT_SIGN_KEY, "") @@ -164,8 +204,9 @@ class HttpServerUtils private constructor() { val timestamp = System.currentTimeMillis() val diffTime = kotlin.math.abs(timestamp - req.timestamp) - if (diffTime > 3600000L) { - throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, diffTime)) + val tolerance = timeTolerance * 1000L + if (diffTime > tolerance) { + throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, timeTolerance, diffTime)) } val sign = calcSign(req.timestamp.toString(), signSecret.toString()) @@ -306,7 +347,7 @@ class HttpServerUtils private constructor() { if (output != null) { resp["data"] = output } - if (!TextUtils.isEmpty(serverSignKey)) { + if (safetyMeasures == 1) { resp["sign"] = calcSign(timestamp.toString(), serverSignKey.toString()) } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RSACrypt.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/RSACrypt.kt new file mode 100644 index 00000000..14141e39 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/RSACrypt.kt @@ -0,0 +1,210 @@ +package com.idormy.sms.forwarder.utils + +import java.io.ByteArrayOutputStream +import java.security.KeyFactory +import java.security.PrivateKey +import java.security.PublicKey +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec +import javax.crypto.Cipher + +/** + * 非对称加密RSA加密和解密 + */ +@Suppress("unused") +object RSACrypt { + + private const val transformation = "RSA" + private const val ENCRYPT_MAX_SIZE = 245 + private const val DECRYPT_MAX_SIZE = 256 + + /** + * 私钥加密 + * @param input 原文 + * @param privateKey 私钥 + */ + fun encryptByPrivateKey(input: String, privateKey: PrivateKey): String { + + //创建cipher对象 + val cipher = Cipher.getInstance(transformation) + //初始化cipher + cipher.init(Cipher.ENCRYPT_MODE, privateKey) + + //****非对称加密**** + val byteArray = input.toByteArray() + + //分段加密 + var temp: ByteArray? + var offset = 0 //当前偏移的位置 + + val outputStream = ByteArrayOutputStream() + + //拆分input + while (byteArray.size - offset > 0) { + //每次最大加密245个字节 + if (byteArray.size - offset >= ENCRYPT_MAX_SIZE) { + //剩余部分大于245 + //加密完整245 + temp = cipher.doFinal(byteArray, offset, ENCRYPT_MAX_SIZE) + //重新计算偏移位置 + offset += ENCRYPT_MAX_SIZE + } else { + //加密最后一块 + temp = cipher.doFinal(byteArray, offset, byteArray.size - offset) + //重新计算偏移位置 + offset = byteArray.size + } + //存储到临时的缓冲区 + outputStream.write(temp) + } + outputStream.close() + + return Base64.encode(outputStream.toByteArray()) + + } + + /** + * 公钥加密 + * @param input 原文 + * @param publicKey 公钥 + */ + fun encryptByPublicKey(input: String, publicKey: PublicKey): String { + + //创建cipher对象 + val cipher = Cipher.getInstance(transformation) + //初始化cipher + cipher.init(Cipher.ENCRYPT_MODE, publicKey) + + //****非对称加密**** + val byteArray = input.toByteArray() + + var temp: ByteArray? + var offset = 0 //当前偏移的位置 + + val outputStream = ByteArrayOutputStream() + + //拆分input + while (byteArray.size - offset > 0) { + //每次最大加密117个字节 + if (byteArray.size - offset >= ENCRYPT_MAX_SIZE) { + //剩余部分大于117 + //加密完整117 + temp = cipher.doFinal(byteArray, offset, ENCRYPT_MAX_SIZE) + //重新计算偏移位置 + offset += ENCRYPT_MAX_SIZE + } else { + //加密最后一块 + temp = cipher.doFinal(byteArray, offset, byteArray.size - offset) + //重新计算偏移位置 + offset = byteArray.size + } + //存储到临时的缓冲区 + outputStream.write(temp) + } + outputStream.close() + + return Base64.encode(outputStream.toByteArray()) + + } + + /** + * 私钥解密 + * @param input 秘文 + * @param privateKey 私钥 + */ + fun decryptByPrivateKey(input: String, privateKey: PrivateKey): String { + + //创建cipher对象 + val cipher = Cipher.getInstance(transformation) + //初始化cipher + cipher.init(Cipher.DECRYPT_MODE, privateKey) + + //****非对称加密**** + val byteArray = Base64.decode(input) + + //分段解密 + var temp: ByteArray? + var offset = 0 //当前偏移的位置 + + val outputStream = ByteArrayOutputStream() + + //拆分input + while (byteArray.size - offset > 0) { + //每次最大解密256个字节 + if (byteArray.size - offset >= DECRYPT_MAX_SIZE) { + + temp = cipher.doFinal(byteArray, offset, DECRYPT_MAX_SIZE) + //重新计算偏移位置 + offset += DECRYPT_MAX_SIZE + } else { + //加密最后一块 + temp = cipher.doFinal(byteArray, offset, byteArray.size - offset) + //重新计算偏移位置 + offset = byteArray.size + } + //存储到临时的缓冲区 + outputStream.write(temp) + } + outputStream.close() + + return String(outputStream.toByteArray()) + + } + + /** + * 公钥解密 + * @param input 秘文 + * @param publicKey 公钥 + */ + fun decryptByPublicKey(input: String, publicKey: PublicKey): String { + + //创建cipher对象 + val cipher = Cipher.getInstance(transformation) + //初始化cipher + cipher.init(Cipher.DECRYPT_MODE, publicKey) + + //****非对称加密**** + val byteArray = Base64.decode(input) + + //分段解密 + var temp: ByteArray? + var offset = 0 //当前偏移的位置 + + val outputStream = ByteArrayOutputStream() + + //拆分input + while (byteArray.size - offset > 0) { + //每次最大解密256个字节 + if (byteArray.size - offset >= DECRYPT_MAX_SIZE) { + + temp = cipher.doFinal(byteArray, offset, DECRYPT_MAX_SIZE) + //重新计算偏移位置 + offset += DECRYPT_MAX_SIZE + } else { + //加密最后一块 + temp = cipher.doFinal(byteArray, offset, byteArray.size - offset) + //重新计算偏移位置 + offset = byteArray.size + } + //存储到临时的缓冲区 + outputStream.write(temp) + } + outputStream.close() + + return String(outputStream.toByteArray()) + + } + + fun getPrivateKey(privateKeyStr: String): PrivateKey { + //字符串转成秘钥对对象 + val generator = KeyFactory.getInstance("RSA") + return generator.generatePrivate(PKCS8EncodedKeySpec(Base64.decode(privateKeyStr))) + } + + fun getPublicKey(publicKeyStr: String): PublicKey { + //字符串转成秘钥对对象 + val kf = KeyFactory.getInstance("RSA") + return kf.generatePublic(X509EncodedKeySpec(Base64.decode(publicKeyStr))) + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_client.xml b/app/src/main/res/layout/fragment_client.xml index 64eff136..cfbb8fb0 100644 --- a/app/src/main/res/layout/fragment_client.xml +++ b/app/src/main/res/layout/fragment_client.xml @@ -61,20 +61,55 @@ android:layout_weight="1" android:hint="@string/service_address_hint" /> - + + + + + android:text="@string/safety_measures" + android:textStyle="bold" /> + + + + + + + + + + - + android:layout_weight="1" + android:gravity="top" + android:importantForAutofill="no" + android:inputType="textMultiLine" + android:maxLines="5" + android:minLines="1" + android:scrollbars="vertical" + android:textSize="10sp" + tools:ignore="LabelFor,SmallSp" /> + + + + + + - - + + @@ -134,10 +134,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + android:layout_weight="1" + android:gravity="top" + android:hint="@string/private_key_tips" + android:importantForAutofill="no" + android:inputType="textMultiLine" + android:maxLines="5" + android:minLines="2" + android:scrollbars="vertical" + android:textSize="10sp" + tools:ignore="SmallSp" /> + + + + + + + + + + + + + + + + + + + + + tools:ignore="RelativeOverlap,TooManyViews" /> Server Settings It is recommended to enable signature settings, click "Random" to generate and copy to clipboard Sign Key + Key pair generated and copied to clipboard + Copied to clipboard Key generated and copied to clipboard Copy Random @@ -867,7 +869,7 @@ Sign verify failed version_code required The app versions of the client and server are inconsistent - The timestamp verification failed, and the difference with the server time (%s) cannot exceed 1 hour (diffTime=%s) + The timestamp verification failed, and the difference with the server time (%s) cannot exceed %s sec. (diffTime=%s) Main title Subtitle @@ -921,5 +923,18 @@ User ID Auto delete logs N days ago 0=disabled, scan when battery change - + Day + Safety Measures + None + Sign + Encrypt + See Github Wiki, download to Download directory + Time Tolerance + Minimize time tolerance to avoid request replay attacks + Private Key + Private key is used on the server: the private key of the server response message is encrypted, and the client public key is decrypted + Generate + Public Key + Public key is used on the client: client request message public key encryption, server private key decryption + Copy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b7512ae..b3597095 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -781,6 +781,8 @@ 复制 随机生成 签名密钥 + 已生成公私钥对,并复制公钥到剪贴板 + 已复制公钥到剪贴板 已生成密钥,并复制到剪贴板 启用功能 按需选择您要启用远程控制的功能 @@ -832,9 +834,9 @@ 充电器:%s 历史记录 - 测试接口 + 登录服务 无效的服务地址!\n格式:http://127.0.0.1:5000 或 https://smsf.demo.com - 请先点击【测试接口】按钮,获取服务端已启用的功能列表 + 请先点击【登录服务】按钮,获取服务端已启用的功能列表 服务端禁用此功能 Frpc运行失败 删除成功 @@ -868,7 +870,7 @@ 签名校验失败 version_code节点必传 客户端与服务端的App版本不一致 - timestamp校验失败,与服务器时间(%s)误差不能超过1小时(diffTime=%s) + timestamp校验失败,与服务器时间(%s)误差不能超过%s秒(diffTime=%s) 主标题 副标题 @@ -923,4 +925,17 @@ 自动删除N天前的转发记录 0=禁用,触发机制:每次电量变化时扫描 + 安全措施 + 不需要 + 校验签名 + 加密传输 + 参见 Github Wiki,下载到 Download 目录 + 客户端与服务端时间容差 + 尽量缩短时间容差,避免请求重放攻击 + RSA私钥 + RSA私钥用在服务端:服务端应答报文私钥加密,客户端公钥解密 + 生成密钥 + RSA公钥 + RSA公钥用在客户端:客户端请求报文公钥加密,服务端私钥解密 + 复制公钥