You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
3.6 KiB
102 lines
3.6 KiB
package dev.lowrespalmtree.comet
|
|
|
|
import android.security.keystore.KeyGenParameterSpec
|
|
import android.security.keystore.KeyProperties
|
|
import android.util.Log
|
|
import androidx.room.*
|
|
import java.security.KeyPairGenerator
|
|
import java.security.KeyStore
|
|
import javax.security.auth.x500.X500Principal
|
|
|
|
object Identities {
|
|
private const val PROVIDER = "AndroidKeyStore"
|
|
private const val TAG = "Identities"
|
|
|
|
val keyStore: KeyStore by lazy { KeyStore.getInstance(PROVIDER).apply { load(null) } }
|
|
|
|
@Entity
|
|
data class Identity(
|
|
/** ID. */
|
|
@PrimaryKey(autoGenerate = true) val id: Int,
|
|
/** Key to retrieve certificate from the keystore. */
|
|
val key: String,
|
|
/** Label for this identity. */
|
|
var name: String?,
|
|
/** URL paths configured to use this identity. */
|
|
var urls: UrlList
|
|
)
|
|
|
|
@Dao
|
|
interface IdentityDao {
|
|
@Insert
|
|
suspend fun insert(identity: Identity): Long
|
|
|
|
@Query("SELECT * FROM Identity WHERE :id = id")
|
|
suspend fun get(id: Long): Identity?
|
|
|
|
@Query("SELECT * FROM Identity ORDER BY id")
|
|
suspend fun getAll(): List<Identity>
|
|
|
|
@Update
|
|
suspend fun update(vararg identities: Identity)
|
|
|
|
@Delete
|
|
suspend fun delete(vararg identities: Identity)
|
|
}
|
|
|
|
suspend fun insert(key: String, name: String? = null): Long =
|
|
Database.INSTANCE.identityDao().insert(Identity(0, key, name, arrayListOf()))
|
|
|
|
suspend fun get(id: Long): Identity? =
|
|
Database.INSTANCE.identityDao().get(id)
|
|
|
|
suspend fun getAll(): List<Identity> =
|
|
Database.INSTANCE.identityDao().getAll()
|
|
|
|
suspend fun getForUrl(url: String): Identity? =
|
|
Database.INSTANCE.identityDao().getAll()
|
|
.find { it.urls.any { usedUrl -> url.startsWith(usedUrl) } }
|
|
|
|
suspend fun update(vararg identities: Identity) =
|
|
Database.INSTANCE.identityDao().update(*identities)
|
|
|
|
suspend fun delete(vararg identities: Identity) {
|
|
for (identity in identities) {
|
|
if (identity.key.isNotEmpty())
|
|
deleteClientCert(identity.key)
|
|
}
|
|
Database.INSTANCE.identityDao().delete(*identities)
|
|
}
|
|
|
|
fun generateClientCert(alias: String, commonName: String) {
|
|
val algo = KeyProperties.KEY_ALGORITHM_RSA
|
|
val kpg = KeyPairGenerator.getInstance(algo, PROVIDER)
|
|
val purposes = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
|
val spec = KeyGenParameterSpec.Builder(alias, purposes)
|
|
.apply {
|
|
if (commonName.isNotEmpty()) {
|
|
try {
|
|
setCertificateSubject(X500Principal("CN=$commonName"))
|
|
} catch (e: IllegalArgumentException) {
|
|
Log.e(TAG, "generateClientCert: bad common name: ${e.message}")
|
|
}
|
|
}
|
|
}
|
|
.setDigests(KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
|
|
.build()
|
|
kpg.initialize(spec)
|
|
kpg.generateKeyPair()
|
|
Log.i(TAG, "generateClientCert: key pair with alias \"$alias\" has been generated")
|
|
}
|
|
|
|
private fun deleteClientCert(alias: String) {
|
|
if (keyStore.containsAlias(alias)) {
|
|
keyStore.deleteEntry(alias)
|
|
Log.i(TAG, "deleteClientCert: deleted entry with alias \"$alias\"")
|
|
} else {
|
|
Log.i(TAG, "deleteClientCert: no such alias \"$alias\"")
|
|
}
|
|
}
|
|
} |