LicenseRepository.kt

5.0 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 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}