1package com.peachpasswords.license23import android.content.Context4import androidx.security.crypto.EncryptedSharedPreferences5import androidx.security.crypto.MasterKey6import dagger.hilt.android.qualifiers.ApplicationContext7import java.math.BigInteger8import java.security.KeyFactory9import java.security.Signature10import java.security.spec.X509EncodedKeySpec11import javax.inject.Inject12import javax.inject.Singleton1314@Singleton15class LicenseRepository @Inject constructor(@ApplicationContext context: Context) {16 companion object {17 private const val PUBLIC_KEY_HEX =18 "c7dc52d2c484696cf8566dbe6cfe69025d114cb47f6cf53c25088ecfd539bdfa"1920 private const val PREFS_FILE = "peach_license_prefs"21 private const val KEY_LICENSE = "peach_license"22 private const val KEY_PRO_CELEBRATED = "peach_pro_celebrated"2324 private const val BASE58_ALPHABET =25 "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"2627 const val PAYLOAD_SIZE = 1228 const val SIGNATURE_SIZE = 6429 const val TOTAL_SIZE = 763031 const val PRODUCT_TAG = 0x01.toByte()32 const val PLAN_TAG = 0x01.toByte()33 const val VERSION_TAG = 0x01.toByte()34 }3536 private val masterKey = MasterKey.Builder(context)37 .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)38 .build()3940 private val prefs = EncryptedSharedPreferences.create(41 context,42 PREFS_FILE,43 masterKey,44 EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,45 EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,46 )4748 fun storeLicense(key: String) {49 prefs.edit().putString(KEY_LICENSE, key).apply()50 prefs.edit().remove(KEY_PRO_CELEBRATED).apply()51 }5253 fun getLicenseKey(): String? = prefs.getString(KEY_LICENSE, null)5455 fun getLicenseIssuedAtMillis(): Long? = getLicenseKey()?.let(::licenseIssuedAtMillis)5657 fun clearLicense() = prefs.edit().remove(KEY_LICENSE).apply()5859 fun isProCelebrated(): Boolean = prefs.getBoolean(KEY_PRO_CELEBRATED, false)6061 fun markProCelebrated() = prefs.edit().putBoolean(KEY_PRO_CELEBRATED, true).apply()6263 fun isProActive(): Boolean = getLicenseKey()?.let(::validateLicense) == true6465 fun hasFeature(feature: ProFeature): Boolean = isProActive()6667 fun validateLicense(licenseKey: String): Boolean {68 return try {69 val decoded = decodeLicenseKey(licenseKey) ?: return false70 if (decoded.size != TOTAL_SIZE) return false71 val payload = decoded.copyOfRange(0, PAYLOAD_SIZE)72 val signature = decoded.copyOfRange(PAYLOAD_SIZE, TOTAL_SIZE)73 if (payload[0] != PRODUCT_TAG) return false74 if (payload[1] != PLAN_TAG) return false75 if (payload[2] != VERSION_TAG) return false76 verifySignature(payload, signature)77 } catch (_: Exception) {78 false79 }80 }8182 fun licenseIssuedAtMillis(licenseKey: String): Long? {83 val decoded = decodeLicenseKey(licenseKey) ?: return null84 if (decoded.size != TOTAL_SIZE) return null85 val payload = decoded.copyOfRange(0, PAYLOAD_SIZE)86 if (payload[0] != PRODUCT_TAG || payload[1] != PLAN_TAG || payload[2] != VERSION_TAG) return null87 val days = (payload[3].toInt() and 0xff) or88 ((payload[4].toInt() and 0xff) shl 8) or89 ((payload[5].toInt() and 0xff) shl 16) or90 ((payload[6].toInt() and 0xff) shl 24)91 return days.toLong() * 86_400_000L92 }9394 private fun decodeLicenseKey(key: String): ByteArray? {95 if (!key.startsWith("PPK-")) return null96 val encoded = key.removePrefix("PPK-").replace("-", "")97 return base58Decode(encoded)98 }99100 private fun base58Decode(input: String): ByteArray? {101 return try {102 var n = BigInteger.ZERO103 for (char in input) {104 val index = BASE58_ALPHABET.indexOf(char)105 if (index < 0) return null106 n = n.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(index.toLong()))107 }108 val bytes = n.toByteArray()109 if (bytes.isNotEmpty() && bytes[0] == 0.toByte()) bytes.copyOfRange(1, bytes.size) else bytes110 } catch (_: Exception) {111 null112 }113 }114115 private fun verifySignature(payload: ByteArray, signature: ByteArray): Boolean {116 return try {117 val publicKeyBytes = hexToBytes(PUBLIC_KEY_HEX)118 val keyFactory = KeyFactory.getInstance("Ed25519")119 val derPrefix = byteArrayOf(120 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65,121 0x70, 0x03, 0x21, 0x00,122 )123 val encoded = derPrefix + publicKeyBytes124 val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(encoded))125 val sig = Signature.getInstance("Ed25519")126 sig.initVerify(publicKey)127 sig.update(payload)128 sig.verify(signature)129 } catch (_: Exception) {130 false131 }132 }133134 private fun hexToBytes(hex: String): ByteArray {135 val len = hex.length136 val data = ByteArray(len / 2)137 for (i in 0 until len step 2) {138 data[i / 2] = ((Character.digit(hex[i], 16) shl 4) +139 Character.digit(hex[i + 1], 16)).toByte()140 }141 return data142 }143}