From b8f58328cbdb95a4d63a092b7f8bdb8ff6aca0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 22 Oct 2019 22:34:13 +0200 Subject: [PATCH] [APIv2/Vulcan] Add faster request signing. --- app/build.gradle | 3 +- .../edziennik/api/v2/Constants.kt | 1 + .../edziennik/api/v2/vulcan/DataVulcan.kt | 7 ++- .../edziennik/api/v2/vulcan/data/VulcanApi.kt | 12 ++--- .../edziennik/api/v2/vulcan/data/signer.kt | 47 +++++++++++++++++++ .../api/v2/vulcan/login/VulcanLoginApi.kt | 18 ++++++- 6 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/signer.kt diff --git a/app/build.gradle b/app/build.gradle index df5dde57..5e5494aa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,7 +156,8 @@ dependencies { debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1" - implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT' + //implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT' + //implementation 'com.github.kuba2k2.uonet-request-signer:android:master-63f094b14a-1' } repositories { mavenCentral() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt index 178b3bc9..51cedea1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt @@ -52,6 +52,7 @@ const val VULCAN_API_USER_AGENT = "MobileUserAgent" const val VULCAN_API_APP_NAME = "VULCAN-Android-ModulUcznia" const val VULCAN_API_APP_VERSION = "19.4.1.436" const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06" +const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB" val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}" const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt index 7d3f8367..6e647a87 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/DataVulcan.kt @@ -16,7 +16,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix() && apiCertificateKey.isNotNullNorEmpty() - && apiCertificatePfx.isNotNullNorEmpty() + && apiCertificatePrivate.isNotNullNorEmpty() && symbol.isNotNullNorEmpty() override fun satisfyLoginMethods() { @@ -145,6 +145,11 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app get() { mApiCertificatePfx = mApiCertificatePfx ?: loginStore.getLoginData("certificatePfx", null); return mApiCertificatePfx } set(value) { loginStore.putLoginData("certificatePfx", value); mApiCertificatePfx = value } + private var mApiCertificatePrivate: String? = null + var apiCertificatePrivate: String? + get() { mApiCertificatePrivate = mApiCertificatePrivate ?: loginStore.getLoginData("certificatePrivate", null); return mApiCertificatePrivate } + set(value) { loginStore.putLoginData("certificatePrivate", value); mApiCertificatePrivate = value } + private var mApiCertificateExpiryTime: Int? = null var apiCertificateExpiryTime: Int get() { mApiCertificateExpiryTime = mApiCertificateExpiryTime ?: loginStore.getLoginData("certificateExpiryTime", 0); return mApiCertificateExpiryTime ?: 0 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/VulcanApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/VulcanApi.kt index b0c66a66..d826dfa2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/VulcanApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/VulcanApi.kt @@ -4,12 +4,10 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan.data -import android.util.Base64 import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.JsonCallbackHandler -import io.github.wulkanowy.signer.signContent import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan @@ -17,7 +15,6 @@ import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.models.Date -import java.io.ByteArrayInputStream import java.net.HttpURLConnection import java.util.* @@ -114,9 +111,12 @@ open class VulcanApi(open val data: DataVulcan) { .userAgent(VULCAN_API_USER_AGENT) .addHeader("RequestCertificateKey", data.apiCertificateKey) .addHeader("RequestSignatureValue", - Utils.VulcanRequestEncryptionUtils.signContent( - finalPayload.toString().toByteArray(), - ByteArrayInputStream(Base64.decode(data.apiCertificatePfx, Base64.DEFAULT)))) + try { + signContent( + data.apiCertificatePrivate ?: "", + finalPayload.toString() + ) + } catch (e: Exception) {e.printStackTrace();""}) .apply { when (method) { GET -> get() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/signer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/signer.kt new file mode 100644 index 00000000..5f0447d8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/data/signer.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Wulkanowy kiedyƛ tam. + */ + +package pl.szczodrzynski.edziennik.api.v2.vulcan.data + +import android.util.Base64 +import java.io.ByteArrayInputStream +import java.security.KeyFactory +import java.security.KeyStore +import java.security.PrivateKey +import java.security.Signature +import java.security.spec.PKCS8EncodedKeySpec + +fun signContent(password: String, certificate: String?, content: String): String { + val keystore = KeyStore.getInstance("pkcs12").apply { + load(ByteArrayInputStream(Base64.decode(certificate, Base64.DEFAULT)), password.toCharArray()) + } + val signature = Signature.getInstance("SHA1WithRSA").apply { + initSign(keystore.getKey("LoginCert", password.toCharArray()) as PrivateKey) + update(content.toByteArray()) + } + return Base64.encodeToString(signature.sign(), Base64.NO_WRAP) +} + +fun signContent(privateKey: String, content: String): String { + val key = PKCS8EncodedKeySpec(Base64.decode(privateKey, Base64.DEFAULT)).let { + KeyFactory.getInstance("RSA").generatePrivate(it) + } + val signature = Signature.getInstance("SHA1WithRSA").apply { + initSign(key) + update(content.toByteArray()) + } + return Base64.encodeToString(signature.sign(), Base64.NO_WRAP) +} + +fun getPrivateKeyFromCert(password: String, certificate: String): String { + val keystore = KeyStore.getInstance("pkcs12").apply { + load(ByteArrayInputStream(Base64.decode(certificate, Base64.DEFAULT)), password.toCharArray()) + } + val keyFactory = KeyFactory.getInstance("RSA") + val keySpec = keyFactory.getKeySpec( + keystore.getKey("LoginCert", password.toCharArray()) as PrivateKey, + PKCS8EncodedKeySpec::class.java + ) + return Base64.encodeToString(keySpec.encoded, Base64.NO_WRAP) +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt index 7de629ac..0aab5950 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/vulcan/login/VulcanLoginApi.kt @@ -9,10 +9,14 @@ import com.google.gson.JsonObject import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.JsonCallbackHandler -import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan +import pl.szczodrzynski.edziennik.api.v2.vulcan.data.getPrivateKeyFromCert +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.utils.Utils.d import java.net.HttpURLConnection.HTTP_BAD_REQUEST import java.util.* @@ -28,6 +32,18 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) { onSuccess() } else { + if (data.apiCertificatePfx.isNotNullNorEmpty()) { + try { + data.apiCertificatePrivate = getPrivateKeyFromCert( + if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD, + data.apiCertificatePfx ?: "" + ) + onSuccess() + return@run + } catch (e: Throwable) { + e.printStackTrace() + } + } if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) { loginWithToken() }