[APIv2/Vulcan] Add faster request signing.

This commit is contained in:
Kuba Szczodrzyński 2019-10-22 22:34:13 +02:00
parent 3540b09623
commit b8f58328cb
6 changed files with 79 additions and 9 deletions

View File

@ -156,7 +156,8 @@ dependencies {
debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1" debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op: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 { repositories {
mavenCentral() mavenCentral()

View File

@ -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_NAME = "VULCAN-Android-ModulUcznia"
const val VULCAN_API_APP_VERSION = "19.4.1.436" const val VULCAN_API_APP_VERSION = "19.4.1.436"
const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06" const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06"
const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB"
val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}" val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}"
const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat" const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat"

View File

@ -16,7 +16,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix() fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix()
&& apiCertificateKey.isNotNullNorEmpty() && apiCertificateKey.isNotNullNorEmpty()
&& apiCertificatePfx.isNotNullNorEmpty() && apiCertificatePrivate.isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty() && symbol.isNotNullNorEmpty()
override fun satisfyLoginMethods() { 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 } get() { mApiCertificatePfx = mApiCertificatePfx ?: loginStore.getLoginData("certificatePfx", null); return mApiCertificatePfx }
set(value) { loginStore.putLoginData("certificatePfx", value); mApiCertificatePfx = value } 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 private var mApiCertificateExpiryTime: Int? = null
var apiCertificateExpiryTime: Int var apiCertificateExpiryTime: Int
get() { mApiCertificateExpiryTime = mApiCertificateExpiryTime ?: loginStore.getLoginData("certificateExpiryTime", 0); return mApiCertificateExpiryTime ?: 0 } get() { mApiCertificateExpiryTime = mApiCertificateExpiryTime ?: loginStore.getLoginData("certificateExpiryTime", 0); return mApiCertificateExpiryTime ?: 0 }

View File

@ -4,12 +4,10 @@
package pl.szczodrzynski.edziennik.api.v2.vulcan.data package pl.szczodrzynski.edziennik.api.v2.vulcan.data
import android.util.Base64
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.signContent
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan 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
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import java.io.ByteArrayInputStream
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.util.* import java.util.*
@ -114,9 +111,12 @@ open class VulcanApi(open val data: DataVulcan) {
.userAgent(VULCAN_API_USER_AGENT) .userAgent(VULCAN_API_USER_AGENT)
.addHeader("RequestCertificateKey", data.apiCertificateKey) .addHeader("RequestCertificateKey", data.apiCertificateKey)
.addHeader("RequestSignatureValue", .addHeader("RequestSignatureValue",
Utils.VulcanRequestEncryptionUtils.signContent( try {
finalPayload.toString().toByteArray(), signContent(
ByteArrayInputStream(Base64.decode(data.apiCertificatePfx, Base64.DEFAULT)))) data.apiCertificatePrivate ?: "",
finalPayload.toString()
)
} catch (e: Exception) {e.printStackTrace();""})
.apply { .apply {
when (method) { when (method) {
GET -> get() GET -> get()

View File

@ -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)
}

View File

@ -9,10 +9,14 @@ import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan 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 pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_BAD_REQUEST import java.net.HttpURLConnection.HTTP_BAD_REQUEST
import java.util.* import java.util.*
@ -28,6 +32,18 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
onSuccess() onSuccess()
} }
else { 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()) { if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) {
loginWithToken() loginWithToken()
} }