215 lines
6.8 KiB
Kotlin
215 lines
6.8 KiB
Kotlin
package fan.ovo.hwsign
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.Context
|
|
import android.os.Build
|
|
import android.security.keystore.KeyGenParameterSpec
|
|
import android.security.keystore.KeyProperties
|
|
import android.security.keystore.KeyInfo
|
|
import android.util.Log
|
|
import androidx.core.content.edit
|
|
import androidx.security.crypto.EncryptedSharedPreferences
|
|
import androidx.security.crypto.MasterKey
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.withContext
|
|
import java.security.KeyFactory
|
|
import java.security.KeyPair
|
|
import java.security.KeyPairGenerator
|
|
import java.security.KeyStore
|
|
import java.security.PrivateKey
|
|
import java.security.Signature
|
|
import java.security.spec.ECGenParameterSpec
|
|
import java.util.Base64
|
|
|
|
/**
|
|
* Manages cryptographic keys for hardware-backed authentication
|
|
*/
|
|
class KeyManager(private val context: Context) {
|
|
|
|
// Constants
|
|
private val keyAlias = "hw_sign_hardware_key"
|
|
private val keyAccelId = "accel_key_id"
|
|
|
|
// Cache for acceleration keys
|
|
private var accelerationKeyPair: KeyPair? = null
|
|
|
|
private val keystore by lazy {
|
|
KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
|
|
}
|
|
|
|
private val sharedPreferences by lazy {
|
|
EncryptedSharedPreferences.create(
|
|
context,
|
|
"auth_prefs",
|
|
MasterKey.Builder(context)
|
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
.build(),
|
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Check the security level of the hardware key
|
|
*/
|
|
@SuppressLint("SwitchIntDef")
|
|
fun getKeySecurityLevel(): String {
|
|
return try {
|
|
val privateKey = keystore.getKey(keyAlias, null) as PrivateKey
|
|
val keyFactory = KeyFactory.getInstance(privateKey.algorithm, "AndroidKeyStore")
|
|
val keyInfo = keyFactory.getKeySpec(privateKey, KeyInfo::class.java) as KeyInfo
|
|
|
|
// Acceptable value: StrongBox, TEE, SecureHardware.
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
when (keyInfo.securityLevel) {
|
|
KeyProperties.SECURITY_LEVEL_STRONGBOX -> "StrongBox"
|
|
KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT -> "TEE"
|
|
KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE -> "UnknownSecure"
|
|
KeyProperties.SECURITY_LEVEL_SOFTWARE -> "Software"
|
|
KeyProperties.SECURITY_LEVEL_UNKNOWN -> "Unknown"
|
|
else -> "Unknown"
|
|
}
|
|
} else {
|
|
if (keyInfo.isInsideSecureHardware) {
|
|
"SecureHardware"
|
|
} else {
|
|
"Insecure"
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
"Unknown"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a hardware-backed key pair using AndroidKeyStore
|
|
* Attempts to use StrongBox if available, falls back to TEE
|
|
*/
|
|
suspend fun generateHardwareKey(): KeyPair = withContext(Dispatchers.IO) {
|
|
// Delete any existing key
|
|
if (keystore.containsAlias(keyAlias)) {
|
|
keystore.deleteEntry(keyAlias)
|
|
}
|
|
|
|
val keyPairGenerator = KeyPairGenerator.getInstance(
|
|
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"
|
|
)
|
|
|
|
val builder = KeyGenParameterSpec.Builder(
|
|
keyAlias, KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
|
).apply {
|
|
setDigests(KeyProperties.DIGEST_SHA256)
|
|
setUserAuthenticationRequired(false)
|
|
setUnlockedDeviceRequired(true)
|
|
}
|
|
|
|
var keyPair: KeyPair
|
|
// First try StrongBox if available (API 28+)
|
|
try {
|
|
builder.setIsStrongBoxBacked(true)
|
|
keyPairGenerator.initialize(builder.build())
|
|
keyPair = keyPairGenerator.generateKeyPair()
|
|
} catch (e: Exception) {
|
|
// Initialize with TEE fallback
|
|
builder.setIsStrongBoxBacked(false)
|
|
keyPairGenerator.initialize(builder.build())
|
|
keyPair = keyPairGenerator.generateKeyPair()
|
|
}
|
|
|
|
val keyLevel = getKeySecurityLevel()
|
|
Log.i(null, "successfully generated key pair in $keyLevel")
|
|
keyPair
|
|
}
|
|
|
|
/**
|
|
* Retrieve the existing hardware key pair from the Android KeyStore
|
|
*/
|
|
private fun getHardwareKeyPair(): KeyPair? = try {
|
|
if (keystore.containsAlias(keyAlias)) {
|
|
val privateKey = keystore.getKey(keyAlias, null) as PrivateKey
|
|
val publicKey = keystore.getCertificate(keyAlias).publicKey
|
|
KeyPair(publicKey, privateKey)
|
|
} else {
|
|
null
|
|
}
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
null
|
|
}
|
|
|
|
/**
|
|
* Store and retrieve acceleration key ID
|
|
*/
|
|
fun storeAccelKeyId(keyId: String) {
|
|
sharedPreferences.edit { putString(keyAccelId, keyId) }
|
|
}
|
|
|
|
fun getAccelKeyId(): String? =
|
|
sharedPreferences.getString(keyAccelId, null)
|
|
|
|
fun clearAccelKeyId() {
|
|
sharedPreferences.edit { remove(keyAccelId) }
|
|
}
|
|
|
|
/**
|
|
* Sign data using the hardware key
|
|
*/
|
|
suspend fun signWithHardwareKey(data: String): String = withContext(Dispatchers.IO) {
|
|
val keyPair = getHardwareKeyPair() ?: throw SecurityException("Hardware key not found")
|
|
|
|
val signature = Signature.getInstance("SHA256withECDSA").apply {
|
|
initSign(keyPair.private)
|
|
update(data.toByteArray())
|
|
}
|
|
|
|
Base64.getEncoder().encodeToString(signature.sign())
|
|
}
|
|
|
|
/**
|
|
* Generate an acceleration key pair in memory
|
|
*/
|
|
fun generateAccelerationKey(): KeyPair {
|
|
val keyPair = KeyPairGenerator.getInstance("EC").apply {
|
|
initialize(ECGenParameterSpec("secp256r1"))
|
|
}.generateKeyPair()
|
|
|
|
accelerationKeyPair = keyPair
|
|
return keyPair
|
|
}
|
|
|
|
/**
|
|
* Get the cached acceleration key pair or generate a new one
|
|
*/
|
|
fun getOrCreateAccelerationKey(): KeyPair {
|
|
return accelerationKeyPair ?: generateAccelerationKey()
|
|
}
|
|
|
|
/**
|
|
* Sign data with a specific key pair
|
|
*/
|
|
fun signWithKey(keyPair: KeyPair, data: String): String {
|
|
val signature = Signature.getInstance("SHA256withECDSA").apply {
|
|
initSign(keyPair.private)
|
|
update(data.toByteArray())
|
|
}
|
|
return Base64.getEncoder().encodeToString(signature.sign())
|
|
}
|
|
|
|
/**
|
|
* Clear stored keys and identifiers
|
|
*/
|
|
fun clearKeys() {
|
|
accelerationKeyPair = null
|
|
clearAccelKeyId()
|
|
|
|
try {
|
|
if (keystore.containsAlias(keyAlias)) {
|
|
keystore.deleteEntry(keyAlias)
|
|
}
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
}
|
|
}
|