Identities: let the user specify a subject CN
This commit is contained in:
parent
b6d855fdeb
commit
1d69c075a1
|
@ -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)
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
|
@ -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>
|
Reference in a new issue