summaryrefslogtreecommitdiff
path: root/main/src/ui/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt
blob: 8430d78801e47365d216039eb3687902e9f5c805 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*
 * Copyright (c) 2012-2018 Arne Schwabe
 * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
 */

package de.blinkt.openvpn.fragments

import android.annotation.TargetApi
import android.app.Activity
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.security.KeyChain
import android.security.KeyChainException
import android.security.keystore.KeyInfo
import android.text.TextUtils
import android.view.View
import android.widget.AdapterView
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import de.blinkt.openvpn.R
import de.blinkt.openvpn.VpnProfile
import de.blinkt.openvpn.api.ExternalCertificateProvider
import de.blinkt.openvpn.core.ExtAuthHelper
import de.blinkt.openvpn.core.X509Utils
import java.security.KeyFactory
import java.security.PrivateKey

import java.security.cert.X509Certificate

internal abstract class KeyChainSettingsFragment : Settings_Fragment(), View.OnClickListener, Handler.Callback {


    private lateinit var mAliasCertificate: TextView
    private lateinit var mAliasName: TextView
    private var mHandler: Handler? = null
    private lateinit var mExtAliasName: TextView
    private lateinit var mExtAuthSpinner: Spinner

    private val isInHardwareKeystore: Boolean
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Throws(KeyChainException::class, InterruptedException::class)
        get() {
            val key: PrivateKey = KeyChain.getPrivateKey(requireActivity().applicationContext, mProfile.mAlias) ?: return false

            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                val keyFactory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
                val keyInfo = keyFactory.getKeySpec(key, KeyInfo::class.java)
                return keyInfo.isInsideSecureHardware()

            } else {
                val algorithm = key.algorithm
                return KeyChain.isBoundKeyAlgorithm(algorithm)
            }
        }


    private fun setKeyStoreAlias() {
        if (mProfile.mAlias == null) {
            mAliasName.setText(R.string.client_no_certificate)
            mAliasCertificate.text = ""
        } else {
            mAliasCertificate.text = "Loading certificate from Keystore..."
            mAliasName.text = mProfile.mAlias
            setCertificate(false)
        }
    }

    private fun setExtAlias() {
        if (mProfile.mAlias == null) {
            mExtAliasName.setText(R.string.extauth_not_configured)
            mAliasCertificate.text = ""
        } else {
            mAliasCertificate.text = "Querying certificate from external provider..."
            mExtAliasName.text = ""
            setCertificate(true)
        }
    }

    private fun fetchExtCertificateMetaData() {
        object : Thread() {
            override fun run() {
                try {
                    val b = ExtAuthHelper.getCertificateMetaData(context!!, mProfile.mExternalAuthenticator, mProfile.mAlias)
                    mProfile.mAlias = b.getString(ExtAuthHelper.EXTRA_ALIAS)
                    requireActivity().runOnUiThread { setAlias() }
                } catch (e: KeyChainException) {
                    e.printStackTrace()
                }

            }
        }.start()
    }


    protected fun setCertificate(external: Boolean) {
        object : Thread() {
            override fun run() {
                var certstr = ""
                var metadata: Bundle? = null
                try {
                    var cert: X509Certificate? = null

                    if (external) {
                        if (!TextUtils.isEmpty(mProfile.mExternalAuthenticator) && !TextUtils.isEmpty(mProfile.mAlias)) {
                            cert = ExtAuthHelper.getCertificateChain(context!!, mProfile.mExternalAuthenticator, mProfile.mAlias)!![0]
                            metadata = ExtAuthHelper.getCertificateMetaData(context!!, mProfile.mExternalAuthenticator, mProfile.mAlias)
                        } else {
                            cert = null
                            certstr = getString(R.string.extauth_not_configured)
                        }
                    } else {
                        val certChain = KeyChain.getCertificateChain(requireActivity().applicationContext, mProfile.mAlias)
                        if (certChain != null) {
                            cert = certChain[0]
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                                run {
                                    if (isInHardwareKeystore)
                                        certstr += getString(R.string.hwkeychain)
                                }
                            }
                        }
                    }
                    if (cert != null) {
                        certstr += X509Utils.getCertificateValidityString(cert, resources)
                        certstr += ", "
                        certstr += X509Utils.getCertificateFriendlyName(cert)
                    }

                } catch (e: Exception) {
                    certstr = "Could not get certificate from Keystore: " + e.localizedMessage
                }

                val certStringCopy = certstr
                val finalMetadata = metadata
                requireActivity().runOnUiThread {
                    mAliasCertificate.text = certStringCopy
                    if (finalMetadata != null)
                        mExtAliasName.text = finalMetadata.getString(ExtAuthHelper.EXTRA_DESCRIPTION)

                }

            }
        }.start()
    }

    protected fun initKeychainViews(v: View) {
        v.findViewById<View>(R.id.select_keystore_button).setOnClickListener(this)
        v.findViewById<View>(R.id.configure_extauth_button)?.setOnClickListener(this)
        v.findViewById<View>(R.id.install_keystore_button).setOnClickListener(this)
        mAliasCertificate = v.findViewById(R.id.alias_certificate)
        mExtAuthSpinner = v.findViewById(R.id.extauth_spinner)
        mExtAuthSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
                val selectedProvider = parent.getItemAtPosition(position) as ExtAuthHelper.ExternalAuthProvider
                if (selectedProvider.packageName != mProfile.mExternalAuthenticator) {
                    mProfile.mAlias = ""
                }
            }

            override fun onNothingSelected(parent: AdapterView<*>) {

            }
        }
        mExtAliasName = v.findViewById(R.id.extauth_detail)
        mAliasName = v.findViewById(R.id.aliasname)
        if (mHandler == null) {
            mHandler = Handler(this)
        }
        ExtAuthHelper.setExternalAuthProviderSpinnerList(mExtAuthSpinner, mProfile.mExternalAuthenticator)

        v.findViewById<View>(R.id.install_keystore_button).setOnClickListener {
            startActivity(KeyChain.createInstallIntent())
        };
    }

    override fun onClick(v: View) {
        if (v === v.findViewById<View>(R.id.select_keystore_button)) {
            showCertDialog()
        } else if (v === v.findViewById<View>(R.id.configure_extauth_button)) {
            startExternalAuthConfig()
        }
    }

    private fun startExternalAuthConfig() {
        val eAuth = mExtAuthSpinner.selectedItem as ExtAuthHelper.ExternalAuthProvider
        mProfile.mExternalAuthenticator = eAuth.packageName
        if (!eAuth.configurable) {
            fetchExtCertificateMetaData()
            return
        }
        val extauth = Intent(ExtAuthHelper.ACTION_CERT_CONFIGURATION)
        extauth.setPackage(eAuth.packageName)
        extauth.putExtra(ExtAuthHelper.EXTRA_ALIAS, mProfile.mAlias)
        startActivityForResult(extauth, UPDATEE_EXT_ALIAS)
    }

    override fun savePreferences() {

    }

    override fun onStart() {
        super.onStart()
        loadPreferences()
    }

    fun showCertDialog() {
        try {
            KeyChain.choosePrivateKeyAlias(requireActivity(),
                    { alias ->
                        // Credential alias selected.  Remember the alias selection for future use.
                        mProfile.mAlias = alias
                        mHandler!!.sendEmptyMessage(UPDATE_ALIAS)
                    },
                    arrayOf("RSA", "EC"), null, // issuer, null for any
                    mProfile.mServerName, // host name of server requesting the cert, null if unavailable
                    -1, // port of server requesting the cert, -1 if unavailable
                    mProfile.mAlias)// List of acceptable key types. null for any
            // alias to preselect, null if unavailable
        } catch (anf: ActivityNotFoundException) {
            val ab = AlertDialog.Builder(activity)
            ab.setTitle(R.string.broken_image_cert_title)
            ab.setMessage(R.string.broken_image_cert)
            ab.setPositiveButton(android.R.string.ok, null)
            ab.show()
        }

    }

    protected open fun loadPreferences() {
        setAlias()

    }

    private fun setAlias() {
        if (mProfile.mAuthenticationType == VpnProfile.TYPE_EXTERNAL_APP)
            setExtAlias()
        else
            setKeyStoreAlias()
    }

    override fun handleMessage(msg: Message): Boolean {
        setAlias()
        return true
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (data != null && requestCode == UPDATEE_EXT_ALIAS && resultCode == Activity.RESULT_OK) {
            mProfile.mAlias = data.getStringExtra(ExtAuthHelper.EXTRA_ALIAS)
            mExtAliasName.text = data.getStringExtra(ExtAuthHelper.EXTRA_DESCRIPTION)
        }
    }

    companion object {
        private val UPDATE_ALIAS = 20
        private val UPDATEE_EXT_ALIAS = 210
    }
}