Identities: let the user specify a subject CN

This commit is contained in:
dece 2022-02-04 19:09:14 +01:00
parent b6d855fdeb
commit 1d69c075a1
3 changed files with 39 additions and 15 deletions

View file

@ -4,8 +4,10 @@ import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Log
import androidx.room.*
import java.lang.IllegalArgumentException
import java.security.KeyPairGenerator
import java.security.KeyStore
import javax.security.auth.x500.X500Principal
object Identities {
@Entity
@ -58,11 +60,20 @@ object Identities {
Database.INSTANCE.identityDao().delete(*identities)
}
fun generateClientCert(alias: String) {
fun generateClientCert(alias: String, commonName: String) {
val algo = KeyProperties.KEY_ALGORITHM_RSA
val kpg = KeyPairGenerator.getInstance(algo, "AndroidKeyStore")
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_SHA256)
.build()
kpg.initialize(spec)

View file

@ -82,27 +82,39 @@ class IdentitiesFragment : Fragment(), IdentitiesAdapter.Listener, IdentityEditD
vm.saveIdentity(identity)
}
private fun openNewIdentityEditor() {
toast(requireContext(), R.string.generating_keypair)
vm.newIdentity.observe(viewLifecycleOwner) { identity ->
if (identity == null)
return@observe
vm.newIdentity.removeObservers(viewLifecycleOwner)
vm.newIdentity.value = null
IdentityDialog(requireContext(), identity, this).show()
}
vm.createNewIdentity()
/**
* Open the new identity wizard.
*
* There is a first dialog to ask the user about the desired subject common name,
* then the certificate is generated and the edition dialog is opened.
*/
private fun openIdentityWizard() {
InputDialog(requireContext(), getString(R.string.input_common_name))
.show(
onOk = { text ->
toast(requireContext(), R.string.generating_keypair)
vm.newIdentity.observe(viewLifecycleOwner) { identity ->
if (identity == null)
return@observe
vm.newIdentity.removeObservers(viewLifecycleOwner)
vm.newIdentity.value = null
IdentityEditDialog(requireContext(), identity, this).show()
}
vm.createNewIdentity(text)
},
onDismiss = {}
)
}
class IdentitiesViewModel : ViewModel() {
val identities: MutableLiveData<List<Identity>> by lazy { MutableLiveData<List<Identity>>() }
val newIdentity: MutableLiveData<Identity> by lazy { MutableLiveData<Identity>() }
fun createNewIdentity() {
fun createNewIdentity(commonName: String) {
viewModelScope.launch(Dispatchers.IO) {
val alias = "identity-${UUID.randomUUID()}"
Identities.generateClientCert(alias)
val newIdentityId = Identities.insert(alias)
Identities.generateClientCert(alias, commonName)
val newIdentityId = Identities.insert(alias, commonName)
newIdentity.postValue(Identities.get(newIdentityId))
}
.invokeOnCompletion { refreshIdentities() }

View file

@ -44,6 +44,7 @@
<string name="confirm">Confirm</string>
<string name="confirm_identity_delete">Are you sure you want to delete this identity? The client certificate cannot be retrieved afterwards.</string>
<string name="edit">Edit</string>
<string name="identity_usages">Active URL paths</string>
<string name="identity_usages">Active URL path</string>
<string name="input_common_name">Enter a name to use as the certificate\'s subject common name. This can be left empty.</string>
</resources>