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

View file

@ -82,27 +82,39 @@ class IdentitiesFragment : Fragment(), IdentitiesAdapter.Listener, IdentityEditD
vm.saveIdentity(identity) vm.saveIdentity(identity)
} }
private fun openNewIdentityEditor() { /**
* 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) toast(requireContext(), R.string.generating_keypair)
vm.newIdentity.observe(viewLifecycleOwner) { identity -> vm.newIdentity.observe(viewLifecycleOwner) { identity ->
if (identity == null) if (identity == null)
return@observe return@observe
vm.newIdentity.removeObservers(viewLifecycleOwner) vm.newIdentity.removeObservers(viewLifecycleOwner)
vm.newIdentity.value = null vm.newIdentity.value = null
IdentityDialog(requireContext(), identity, this).show() IdentityEditDialog(requireContext(), identity, this).show()
} }
vm.createNewIdentity() vm.createNewIdentity(text)
},
onDismiss = {}
)
} }
class IdentitiesViewModel : ViewModel() { class IdentitiesViewModel : ViewModel() {
val identities: MutableLiveData<List<Identity>> by lazy { MutableLiveData<List<Identity>>() } val identities: MutableLiveData<List<Identity>> by lazy { MutableLiveData<List<Identity>>() }
val newIdentity: MutableLiveData<Identity> by lazy { MutableLiveData<Identity>() } val newIdentity: MutableLiveData<Identity> by lazy { MutableLiveData<Identity>() }
fun createNewIdentity() { fun createNewIdentity(commonName: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val alias = "identity-${UUID.randomUUID()}" val alias = "identity-${UUID.randomUUID()}"
Identities.generateClientCert(alias) Identities.generateClientCert(alias, commonName)
val newIdentityId = Identities.insert(alias) val newIdentityId = Identities.insert(alias, commonName)
newIdentity.postValue(Identities.get(newIdentityId)) newIdentity.postValue(Identities.get(newIdentityId))
} }
.invokeOnCompletion { refreshIdentities() } .invokeOnCompletion { refreshIdentities() }

View file

@ -44,6 +44,7 @@
<string name="confirm">Confirm</string> <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="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="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> </resources>