1package com.peachpasswords.license23import android.content.Context4import androidx.security.crypto.EncryptedSharedPreferences5import androidx.security.crypto.MasterKey6import dagger.hilt.android.qualifiers.ApplicationContext7import java.math.BigInteger8import javax.inject.Inject9import javax.inject.Singleton10import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters11import org.bouncycastle.crypto.signers.Ed25519Signer1213@Singleton14class LicenseRepository @Inject constructor(@ApplicationContext context: Context) {15 companion object {16 private const val PUBLIC_KEY_HEX =17 "c7dc52d2c484696cf8566dbe6cfe69025d114cb47f6cf53c25088ecfd539bdfa"1819 private const val PREFS_FILE = "peach_license_prefs"20 private const val KEY_LICENSE = "peach_license"21 private const val KEY_PRO_CELEBRATED = "peach_pro_celebrated"2223 private const val BASE58_ALPHABET =24 "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"2526 const val PAYLOAD_SIZE = 1227 const val SIGNATURE_SIZE = 6428 const val TOTAL_SIZE = 762930 const val PRODUCT_TAG = 0x01.toByte()31 const val PLAN_TAG = 0x01.toByte()32 const val VERSION_TAG = 0x01.toByte()33 }3435 private val masterKey = MasterKey.Builder(context)36 .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)37 .build()3839 private val prefs = EncryptedSharedPreferences.create(40 context,41 PREFS_FILE,42 masterKey,43 EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,44 EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,45 )4647 fun storeLicense(key: String) {48 prefs.edit().putString(KEY_LICENSE, key).apply()49 prefs.edit().remove(KEY_PRO_CELEBRATED).apply()50 }5152 fun getLicenseKey(): String? = prefs.getString(KEY_LICENSE, null)5354 fun getLicenseIssuedAtMillis(): Long? = getLicenseKey()?.let(::licenseIssuedAtMillis)5556 fun clearLicense() = prefs.edit().remove(KEY_LICENSE).apply()5758 fun isProCelebrated(): Boolean = prefs.getBoolean(KEY_PRO_CELEBRATED, false)5960 fun markProCelebrated() = prefs.edit().putBoolean(KEY_PRO_CELEBRATED, true).apply()6162 fun isProActive(): Boolean = getLicenseKey()?.let(::validateLicense) == true6364 fun hasFeature(feature: ProFeature): Boolean = isProActive()6566 fun validateLicense(licenseKey: String): Boolean {67 return try {68 val decoded = decodeLicenseKey(licenseKey) ?: return false69 if (decoded.size != TOTAL_SIZE) return false70 val payload = decoded.copyOfRange(0, PAYLOAD_SIZE)71 val signature = decoded.copyOfRange(PAYLOAD_SIZE, TOTAL_SIZE)72 if (payload[0] != PRODUCT_TAG) return false73 if (payload[1] != PLAN_TAG) return false74 if (payload[2] != VERSION_TAG) return false75 verifySignature(payload, signature)76 } catch (_: Exception) {77 false78 }79 }8081 fun licenseIssuedAtMillis(licenseKey: String): Long? {82 val decoded = decodeLicenseKey(licenseKey) ?: return null83 if (decoded.size != TOTAL_SIZE) return null84 val payload = decoded.copyOfRange(0, PAYLOAD_SIZE)85 if (payload[0] != PRODUCT_TAG || payload[1] != PLAN_TAG || payload[2] != VERSION_TAG) return null86 val days = (payload[3].toInt() and 0xff) or87 ((payload[4].toInt() and 0xff) shl 8) or88 ((payload[5].toInt() and 0xff) shl 16) or89 ((payload[6].toInt() and 0xff) shl 24)90 return days.toLong() * 86_400_000L91 }9293 private fun decodeLicenseKey(key: String): ByteArray? {94 if (!key.startsWith("PPK-")) return null95 val encoded = key.removePrefix("PPK-").replace("-", "")96 return base58Decode(encoded)97 }9899 private fun base58Decode(input: String): ByteArray? {100 return try {101 var n = BigInteger.ZERO102 for (char in input) {103 val index = BASE58_ALPHABET.indexOf(char)104 if (index < 0) return null105 n = n.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(index.toLong()))106 }107 val bytes = n.toByteArray()108 if (bytes.isNotEmpty() && bytes[0] == 0.toByte()) bytes.copyOfRange(1, bytes.size) else bytes109 } catch (_: Exception) {110 null111 }112 }113114 private fun verifySignature(payload: ByteArray, signature: ByteArray): Boolean {115 return try {116 val publicKeyBytes = hexToBytes(PUBLIC_KEY_HEX)117 val verifier = Ed25519Signer()118 verifier.init(false, Ed25519PublicKeyParameters(publicKeyBytes, 0))119 verifier.update(payload, 0, payload.size)120 verifier.verifySignature(signature)121 } catch (_: Exception) {122 false123 }124 }125126 private fun hexToBytes(hex: String): ByteArray {127 val len = hex.length128 val data = ByteArray(len / 2)129 for (i in 0 until len step 2) {130 data[i / 2] = ((Character.digit(hex[i], 16) shl 4) +131 Character.digit(hex[i + 1], 16)).toByte()132 }133 return data134 }135}