LicenseRepository.kt

5.3 KB · raw

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}