LicenseRepository.kt

4.6 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 clearLicense() = prefs.edit().remove(KEY_LICENSE).apply()5657    fun isProCelebrated(): Boolean = prefs.getBoolean(KEY_PRO_CELEBRATED, false)5859    fun markProCelebrated() = prefs.edit().putBoolean(KEY_PRO_CELEBRATED, true).apply()6061    fun isProActive(): Boolean = getLicenseKey()?.let(::validateLicense) == true6263    fun hasFeature(feature: ProFeature): Boolean = isProActive()6465    fun validateLicense(licenseKey: String): Boolean {66        return try {67            val decoded = decodeLicenseKey(licenseKey) ?: return false68            if (decoded.size != TOTAL_SIZE) return false69            val payload = decoded.copyOfRange(0, PAYLOAD_SIZE)70            val signature = decoded.copyOfRange(PAYLOAD_SIZE, TOTAL_SIZE)71            if (payload[0] != PRODUCT_TAG) return false72            if (payload[1] != PLAN_TAG) return false73            if (payload[2] != VERSION_TAG) return false74            verifySignature(payload, signature)75        } catch (_: Exception) {76            false77        }78    }7980    private fun decodeLicenseKey(key: String): ByteArray? {81        if (!key.startsWith("PPK-")) return null82        val encoded = key.removePrefix("PPK-").replace("-", "")83        return base58Decode(encoded)84    }8586    private fun base58Decode(input: String): ByteArray? {87        return try {88            var n = BigInteger.ZERO89            for (char in input) {90                val index = BASE58_ALPHABET.indexOf(char)91                if (index < 0) return null92                n = n.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(index.toLong()))93            }94            val bytes = n.toByteArray()95            if (bytes.isNotEmpty() && bytes[0] == 0.toByte()) bytes.copyOfRange(1, bytes.size) else bytes96        } catch (_: Exception) {97            null98        }99    }100101    private fun verifySignature(payload: ByteArray, signature: ByteArray): Boolean {102        return try {103            val publicKeyBytes = hexToBytes(PUBLIC_KEY_HEX)104            val keyFactory = KeyFactory.getInstance("Ed25519")105            val derPrefix = byteArrayOf(106                0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65,107                0x70, 0x03, 0x21, 0x00,108            )109            val encoded = derPrefix + publicKeyBytes110            val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(encoded))111            val sig = Signature.getInstance("Ed25519")112            sig.initVerify(publicKey)113            sig.update(payload)114            sig.verify(signature)115        } catch (_: Exception) {116            false117        }118    }119120    private fun hexToBytes(hex: String): ByteArray {121        val len = hex.length122        val data = ByteArray(len / 2)123        for (i in 0 until len step 2) {124            data[i / 2] = ((Character.digit(hex[i], 16) shl 4) +125                Character.digit(hex[i + 1], 16)).toByte()126        }127        return data128    }129}