mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-22 21:15:30 +03:00
Initial support for client certificate auth
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
473becd623
commit
ea80b3c9d9
22 changed files with 546 additions and 100 deletions
|
@ -260,7 +260,7 @@ public class CallActivity extends AppCompatActivity {
|
||||||
if (!userEntity.getCurrent()) {
|
if (!userEntity.getCurrent()) {
|
||||||
userUtils.createOrUpdateUser(null,
|
userUtils.createOrUpdateUser(null,
|
||||||
null, null, null,
|
null, null, null,
|
||||||
null, true, null, userEntity.getId(), null)
|
null, true, null, userEntity.getId(), null, null)
|
||||||
.subscribe(new Observer<UserEntity>() {
|
.subscribe(new Observer<UserEntity>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Disposable d) {
|
public void onSubscribe(Disposable d) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ import com.nextcloud.talk.utils.ApiUtils;
|
||||||
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
|
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
|
|
||||||
|
@ -72,6 +73,9 @@ public class AccountVerificationController extends BaseController {
|
||||||
@Inject
|
@Inject
|
||||||
CookieManager cookieManager;
|
CookieManager cookieManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AppPreferences appPreferences;
|
||||||
|
|
||||||
@BindView(R.id.progress_text)
|
@BindView(R.id.progress_text)
|
||||||
TextView progressText;
|
TextView progressText;
|
||||||
|
|
||||||
|
@ -198,7 +202,8 @@ public class AccountVerificationController extends BaseController {
|
||||||
if (!TextUtils.isEmpty(displayName)) {
|
if (!TextUtils.isEmpty(displayName)) {
|
||||||
dbQueryDisposable = userUtils.createOrUpdateUser(username, token,
|
dbQueryDisposable = userUtils.createOrUpdateUser(username, token,
|
||||||
baseUrl, displayName, null, true,
|
baseUrl, displayName, null, true,
|
||||||
userProfileOverall.getOcs().getData().getUserId(), null, null)
|
userProfileOverall.getOcs().getData().getUserId(), null, null,
|
||||||
|
appPreferences.getTemporaryClientCertAlias())
|
||||||
.subscribeOn(Schedulers.newThread())
|
.subscribeOn(Schedulers.newThread())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(userEntity -> {
|
.subscribe(userEntity -> {
|
||||||
|
|
|
@ -20,10 +20,12 @@
|
||||||
|
|
||||||
package com.nextcloud.talk.controllers;
|
package com.nextcloud.talk.controllers;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.security.KeyChain;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -46,6 +48,7 @@ import com.nextcloud.talk.utils.ApiUtils;
|
||||||
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
|
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
@ -53,6 +56,7 @@ import javax.inject.Inject;
|
||||||
|
|
||||||
import autodagger.AutoInjector;
|
import autodagger.AutoInjector;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
import butterknife.OnClick;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
@ -72,6 +76,8 @@ public class ServerSelectionController extends BaseController {
|
||||||
ProgressBar progressBar;
|
ProgressBar progressBar;
|
||||||
@BindView(R.id.helper_text_view)
|
@BindView(R.id.helper_text_view)
|
||||||
TextView providersTextView;
|
TextView providersTextView;
|
||||||
|
@BindView(R.id.cert_text_view)
|
||||||
|
TextView certTextView;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
NcApi ncApi;
|
NcApi ncApi;
|
||||||
|
@ -79,6 +85,9 @@ public class ServerSelectionController extends BaseController {
|
||||||
@Inject
|
@Inject
|
||||||
UserUtils userUtils;
|
UserUtils userUtils;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AppPreferences appPreferences;
|
||||||
|
|
||||||
private Disposable statusQueryDisposable;
|
private Disposable statusQueryDisposable;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -86,6 +95,22 @@ public class ServerSelectionController extends BaseController {
|
||||||
return inflater.inflate(R.layout.controller_server_selection, container, false);
|
return inflater.inflate(R.layout.controller_server_selection, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("LongLogTag")
|
||||||
|
@OnClick(R.id.cert_text_view)
|
||||||
|
public void onCertClick() {
|
||||||
|
if (getActivity() != null) {
|
||||||
|
KeyChain.choosePrivateKeyAlias(getActivity(), alias -> {
|
||||||
|
if (alias != null) {
|
||||||
|
appPreferences.setTemporaryClientCertAlias(alias);
|
||||||
|
} else {
|
||||||
|
appPreferences.removeTemporaryClientCertAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCertTextView();
|
||||||
|
}, new String[]{"RSA", "EC"}, null, null, -1, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onViewBound(@NonNull View view) {
|
protected void onViewBound(@NonNull View view) {
|
||||||
super.onViewBound(view);
|
super.onViewBound(view);
|
||||||
|
@ -108,7 +133,7 @@ public class ServerSelectionController extends BaseController {
|
||||||
|
|
||||||
if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url)) && (TextUtils.isEmpty(getResources
|
if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url)) && (TextUtils.isEmpty(getResources
|
||||||
().getString(R.string.nc_import_account_type)))) {
|
().getString(R.string.nc_import_account_type)))) {
|
||||||
providersTextView.setVisibility(View.GONE);
|
providersTextView.setVisibility(View.INVISIBLE);
|
||||||
} else {
|
} else {
|
||||||
if ((TextUtils.isEmpty(getResources
|
if ((TextUtils.isEmpty(getResources
|
||||||
().getString(R.string.nc_import_account_type)) ||
|
().getString(R.string.nc_import_account_type)) ||
|
||||||
|
@ -151,7 +176,7 @@ public class ServerSelectionController extends BaseController {
|
||||||
.popChangeHandler(new HorizontalChangeHandler()));
|
.popChangeHandler(new HorizontalChangeHandler()));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
providersTextView.setVisibility(View.GONE);
|
providersTextView.setVisibility(View.INVISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,8 +229,9 @@ public class ServerSelectionController extends BaseController {
|
||||||
|
|
||||||
serverEntry.setEnabled(false);
|
serverEntry.setEnabled(false);
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
if (providersTextView.getVisibility() != View.GONE) {
|
if (providersTextView.getVisibility() != View.INVISIBLE) {
|
||||||
providersTextView.setVisibility(View.INVISIBLE);
|
providersTextView.setVisibility(View.INVISIBLE);
|
||||||
|
certTextView.setVisibility(View.INVISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.endsWith("/")) {
|
if (url.endsWith("/")) {
|
||||||
|
@ -279,8 +305,9 @@ public class ServerSelectionController extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.setVisibility(View.INVISIBLE);
|
progressBar.setVisibility(View.INVISIBLE);
|
||||||
if (providersTextView.getVisibility() != View.GONE) {
|
if (providersTextView.getVisibility() != View.INVISIBLE) {
|
||||||
providersTextView.setVisibility(View.VISIBLE);
|
providersTextView.setVisibility(View.VISIBLE);
|
||||||
|
certTextView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
toggleProceedButton(false);
|
toggleProceedButton(false);
|
||||||
|
|
||||||
|
@ -288,8 +315,9 @@ public class ServerSelectionController extends BaseController {
|
||||||
}
|
}
|
||||||
}, () -> {
|
}, () -> {
|
||||||
progressBar.setVisibility(View.INVISIBLE);
|
progressBar.setVisibility(View.INVISIBLE);
|
||||||
if (providersTextView.getVisibility() != View.GONE) {
|
if (providersTextView.getVisibility() != View.INVISIBLE) {
|
||||||
providersTextView.setVisibility(View.VISIBLE);
|
providersTextView.setVisibility(View.VISIBLE);
|
||||||
|
certTextView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
|
@ -315,6 +343,23 @@ public class ServerSelectionController extends BaseController {
|
||||||
}
|
}
|
||||||
ApplicationWideMessageHolder.getInstance().setMessageType(null);
|
ApplicationWideMessageHolder.getInstance().setMessageType(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCertTextView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCertTextView() {
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> {
|
||||||
|
if (!TextUtils.isEmpty(appPreferences.getTemporaryClientCertAlias())) {
|
||||||
|
certTextView.setText(R.string.nc_change_cert_auth);
|
||||||
|
} else {
|
||||||
|
certTextView.setText(R.string.nc_configure_cert_auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
textFieldBoxes.setError("", true);
|
||||||
|
toggleProceedButton(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,9 +24,11 @@ import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.security.KeyChain;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -64,9 +66,12 @@ import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener;
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@ -126,6 +131,9 @@ public class SettingsController extends BaseController {
|
||||||
@BindView(R.id.message_view)
|
@BindView(R.id.message_view)
|
||||||
MaterialPreferenceCategory messageView;
|
MaterialPreferenceCategory messageView;
|
||||||
|
|
||||||
|
@BindView(R.id.settings_client_cert)
|
||||||
|
MaterialStandardPreference certificateSetup;
|
||||||
|
|
||||||
@BindView(R.id.message_text)
|
@BindView(R.id.message_text)
|
||||||
TextView messageText;
|
TextView messageText;
|
||||||
|
|
||||||
|
@ -221,6 +229,45 @@ public class SettingsController extends BaseController {
|
||||||
SwitchAccountController()).pushChangeHandler(new VerticalChangeHandler())
|
SwitchAccountController()).pushChangeHandler(new VerticalChangeHandler())
|
||||||
.popChangeHandler(new VerticalChangeHandler()));
|
.popChangeHandler(new VerticalChangeHandler()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (userEntity.getClientCertificate() != null) {
|
||||||
|
certificateSetup.setTitle(R.string.nc_client_cert_change);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String host = null;
|
||||||
|
int port = -1;
|
||||||
|
|
||||||
|
URI uri;
|
||||||
|
try {
|
||||||
|
uri = new URI(userEntity.getBaseUrl());
|
||||||
|
host = uri.getHost();
|
||||||
|
port = uri.getPort();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
Log.e(TAG, "Failed to create uri");
|
||||||
|
}
|
||||||
|
|
||||||
|
String finalHost = host;
|
||||||
|
int finalPort = port;
|
||||||
|
certificateSetup.addPreferenceClickListener(v -> KeyChain.choosePrivateKeyAlias(Objects.requireNonNull(getActivity()), alias -> {
|
||||||
|
String finalAlias = alias;
|
||||||
|
getActivity().runOnUiThread(() -> {
|
||||||
|
if (finalAlias != null) {
|
||||||
|
certificateSetup.setTitle(R.string.nc_client_cert_change);
|
||||||
|
} else {
|
||||||
|
certificateSetup.setTitle(R.string.nc_client_cert_setup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (alias == null) {
|
||||||
|
alias = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userUtils.createOrUpdateUser(null, null, null, null, null, null, null, userEntity.getId(),
|
||||||
|
null, alias);
|
||||||
|
}, new String[]{"RSA", "EC"}, null, finalHost, finalPort, userEntity.getClientCertificate
|
||||||
|
()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -290,7 +337,7 @@ public class SettingsController extends BaseController {
|
||||||
dbQueryDisposable = userUtils.createOrUpdateUser(null,
|
dbQueryDisposable = userUtils.createOrUpdateUser(null,
|
||||||
null,
|
null,
|
||||||
null, displayName, null, true,
|
null, displayName, null, true,
|
||||||
userProfileOverall.getOcs().getData().getUserId(), userEntity.getId(), null)
|
userProfileOverall.getOcs().getData().getUserId(), userEntity.getId(), null, null)
|
||||||
.subscribeOn(Schedulers.newThread())
|
.subscribeOn(Schedulers.newThread())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(userEntityResult -> {
|
.subscribe(userEntityResult -> {
|
||||||
|
|
|
@ -100,7 +100,7 @@ public class SwitchAccountController extends BaseController {
|
||||||
UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity();
|
UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity();
|
||||||
userUtils.createOrUpdateUser(null,
|
userUtils.createOrUpdateUser(null,
|
||||||
null, null, null,
|
null, null, null,
|
||||||
null, true, null, userEntity.getId(), null)
|
null, true, null, userEntity.getId(), null, null)
|
||||||
.subscribe(new Observer<UserEntity>() {
|
.subscribe(new Observer<UserEntity>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Disposable d) {
|
public void onSubscribe(Disposable d) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
package com.nextcloud.talk.controllers;
|
package com.nextcloud.talk.controllers;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.net.http.SslCertificate;
|
import android.net.http.SslCertificate;
|
||||||
import android.net.http.SslError;
|
import android.net.http.SslError;
|
||||||
|
@ -29,7 +30,6 @@ import android.security.KeyChain;
|
||||||
import android.security.KeyChainException;
|
import android.security.KeyChainException;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -52,13 +52,13 @@ import com.nextcloud.talk.models.database.UserEntity;
|
||||||
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
|
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.MalformedURLException;
|
import java.net.CookieManager;
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
@ -86,13 +86,16 @@ public class WebViewLoginController extends BaseController {
|
||||||
@Inject
|
@Inject
|
||||||
UserUtils userUtils;
|
UserUtils userUtils;
|
||||||
@Inject
|
@Inject
|
||||||
|
AppPreferences appPreferences;
|
||||||
|
@Inject
|
||||||
ReactiveEntityStore<Persistable> dataStore;
|
ReactiveEntityStore<Persistable> dataStore;
|
||||||
@Inject
|
@Inject
|
||||||
MagicTrustManager magicTrustManager;
|
MagicTrustManager magicTrustManager;
|
||||||
@Inject
|
@Inject
|
||||||
EventBus eventBus;
|
EventBus eventBus;
|
||||||
@Inject
|
@Inject
|
||||||
java.net.CookieManager cookieManager;
|
CookieManager cookieManager;
|
||||||
|
|
||||||
|
|
||||||
@BindView(R.id.webview)
|
@BindView(R.id.webview)
|
||||||
WebView webView;
|
WebView webView;
|
||||||
|
@ -140,6 +143,7 @@ public class WebViewLoginController extends BaseController {
|
||||||
return inflater.inflate(R.layout.controller_web_view_login, container, false);
|
return inflater.inflate(R.layout.controller_web_view_login, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
@Override
|
@Override
|
||||||
protected void onViewBound(@NonNull View view) {
|
protected void onViewBound(@NonNull View view) {
|
||||||
super.onViewBound(view);
|
super.onViewBound(view);
|
||||||
|
@ -167,6 +171,7 @@ public class WebViewLoginController extends BaseController {
|
||||||
webView.clearCache(true);
|
webView.clearCache(true);
|
||||||
webView.clearFormData();
|
webView.clearFormData();
|
||||||
webView.clearHistory();
|
webView.clearHistory();
|
||||||
|
WebView.clearClientCertPreferences(null);
|
||||||
|
|
||||||
CookieSyncManager.createInstance(getActivity());
|
CookieSyncManager.createInstance(getActivity());
|
||||||
android.webkit.CookieManager.getInstance().removeAllCookies(null);
|
android.webkit.CookieManager.getInstance().removeAllCookies(null);
|
||||||
|
@ -218,32 +223,55 @@ public class WebViewLoginController extends BaseController {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
||||||
String host = null;
|
UserEntity userEntity;
|
||||||
|
|
||||||
try {
|
String alias = null;
|
||||||
URL url = new URL(webView.getUrl());
|
if (!isPasswordUpdate) {
|
||||||
host = url.getHost();
|
alias = appPreferences.getTemporaryClientCertAlias();
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
Log.d(TAG, "Failed to create url");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyChain.choosePrivateKeyAlias(getActivity(), alias -> {
|
if (TextUtils.isEmpty(alias) && (userEntity = userUtils.getCurrentUser()) != null) {
|
||||||
|
alias = userEntity.getClientCertificate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(alias)) {
|
||||||
|
String finalAlias = alias;
|
||||||
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
if (alias != null) {
|
PrivateKey privateKey = KeyChain.getPrivateKey(getActivity(), finalAlias);
|
||||||
PrivateKey privateKey = KeyChain.getPrivateKey(getActivity(), alias);
|
X509Certificate[] certificates = KeyChain.getCertificateChain(getActivity(), finalAlias);
|
||||||
X509Certificate[] certificates = KeyChain.getCertificateChain(getActivity(), alias);
|
if (privateKey != null && certificates != null) {
|
||||||
request.proceed(privateKey, certificates);
|
request.proceed(privateKey, certificates);
|
||||||
} else {
|
} else {
|
||||||
request.cancel();
|
request.cancel();
|
||||||
}
|
}
|
||||||
} catch (KeyChainException e) {
|
} catch (KeyChainException | InterruptedException e) {
|
||||||
Log.e(TAG, "Failed to get keys via keychain exception");
|
|
||||||
request.cancel();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "Failed to get keys due to interruption");
|
|
||||||
request.cancel();
|
request.cancel();
|
||||||
}
|
}
|
||||||
}, new String[]{"RSA"}, null, host, -1, null);
|
}).start();
|
||||||
|
} else {
|
||||||
|
KeyChain.choosePrivateKeyAlias(getActivity(), chosenAlias -> {
|
||||||
|
if (chosenAlias != null) {
|
||||||
|
appPreferences.setTemporaryClientCertAlias(chosenAlias);
|
||||||
|
new Thread(() -> {
|
||||||
|
PrivateKey privateKey = null;
|
||||||
|
try {
|
||||||
|
privateKey = KeyChain.getPrivateKey(getActivity(), chosenAlias);
|
||||||
|
X509Certificate[] certificates = KeyChain.getCertificateChain(getActivity(), chosenAlias);
|
||||||
|
if (privateKey != null && certificates != null) {
|
||||||
|
request.proceed(privateKey, certificates);
|
||||||
|
} else {
|
||||||
|
request.cancel();
|
||||||
|
}
|
||||||
|
} catch (KeyChainException | InterruptedException e) {
|
||||||
|
request.cancel();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
} else {
|
||||||
|
request.cancel();
|
||||||
|
}
|
||||||
|
}, new String[]{"RSA", "EC"}, null, request.getHost(), request.getPort(), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -339,7 +367,7 @@ public class WebViewLoginController extends BaseController {
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
userQueryDisposable = userUtils.createOrUpdateUser(null, null,
|
userQueryDisposable = userUtils.createOrUpdateUser(null, null,
|
||||||
null, null, null, true,
|
null, null, null, true,
|
||||||
null, currentUser.getId(), null).
|
null, currentUser.getId(), null, appPreferences.getTemporaryClientCertAlias()).
|
||||||
subscribe(userEntity -> {
|
subscribe(userEntity -> {
|
||||||
if (finalMessageType != null) {
|
if (finalMessageType != null) {
|
||||||
ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType);
|
ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType);
|
||||||
|
|
|
@ -25,10 +25,28 @@ import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.bluelinelabs.conductor.Controller;
|
import com.bluelinelabs.conductor.Controller;
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||||
|
import com.nextcloud.talk.controllers.AccountVerificationController;
|
||||||
|
import com.nextcloud.talk.controllers.ServerSelectionController;
|
||||||
|
import com.nextcloud.talk.controllers.WebViewLoginController;
|
||||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider;
|
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider;
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import autodagger.AutoInjector;
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public abstract class BaseController extends RefWatchingController {
|
public abstract class BaseController extends RefWatchingController {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AppPreferences appPreferences;
|
||||||
|
|
||||||
|
private List<String> temporaryClassNames = new ArrayList<>();
|
||||||
|
|
||||||
private static final String TAG = "BaseController";
|
private static final String TAG = "BaseController";
|
||||||
|
|
||||||
protected BaseController() {
|
protected BaseController() {
|
||||||
|
@ -38,6 +56,12 @@ public abstract class BaseController extends RefWatchingController {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onViewBound(@NonNull View view) {
|
||||||
|
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
|
||||||
|
super.onViewBound(view);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
|
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
|
||||||
// be accessed. In a production app, this would use Dagger instead.
|
// be accessed. In a production app, this would use Dagger instead.
|
||||||
protected ActionBar getActionBar() {
|
protected ActionBar getActionBar() {
|
||||||
|
@ -54,6 +78,14 @@ public abstract class BaseController extends RefWatchingController {
|
||||||
protected void onAttach(@NonNull View view) {
|
protected void onAttach(@NonNull View view) {
|
||||||
setTitle();
|
setTitle();
|
||||||
getActionBar().setDisplayHomeAsUpEnabled(false);
|
getActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
|
|
||||||
|
temporaryClassNames.add(ServerSelectionController.class.getName());
|
||||||
|
temporaryClassNames.add(AccountVerificationController.class.getName());
|
||||||
|
temporaryClassNames.add(WebViewLoginController.class.getName());
|
||||||
|
|
||||||
|
if (!temporaryClassNames.contains(getClass().getName())) {
|
||||||
|
appPreferences.removeTemporaryClientCertAlias();
|
||||||
|
}
|
||||||
super.onAttach(view);
|
super.onAttach(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -352,6 +352,7 @@ public class OperationsMenuController extends BaseController {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showResultImage(boolean everythingOK, boolean isGuestSupportError) {
|
private void showResultImage(boolean everythingOK, boolean isGuestSupportError) {
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class DatabaseModule {
|
||||||
return new SqlCipherDatabaseSource(context, Models.DEFAULT,
|
return new SqlCipherDatabaseSource(context, Models.DEFAULT,
|
||||||
context.getResources().getString(R.string.nc_app_name).toLowerCase()
|
context.getResources().getString(R.string.nc_app_name).toLowerCase()
|
||||||
.replace(" ", "_").trim() + ".sqlite",
|
.replace(" ", "_").trim() + ".sqlite",
|
||||||
context.getString(R.string.nc_talk_database_encryption_key), 3);
|
context.getString(R.string.nc_talk_database_encryption_key), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -30,7 +30,9 @@ import com.nextcloud.talk.BuildConfig;
|
||||||
import com.nextcloud.talk.api.NcApi;
|
import com.nextcloud.talk.api.NcApi;
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||||
import com.nextcloud.talk.utils.ApiUtils;
|
import com.nextcloud.talk.utils.ApiUtils;
|
||||||
|
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
|
import com.nextcloud.talk.utils.ssl.MagicKeyManager;
|
||||||
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
|
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
|
||||||
import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat;
|
import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat;
|
||||||
|
|
||||||
|
@ -38,9 +40,16 @@ import java.io.IOException;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
@ -110,8 +119,35 @@ public class RestModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicTrustManager magicTrustManager) {
|
MagicKeyManager provideKeyManager(AppPreferences appPreferences, UserUtils userUtils) {
|
||||||
return new SSLSocketFactoryCompat(magicTrustManager);
|
KeyStore keyStore = null;
|
||||||
|
try {
|
||||||
|
keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||||
|
keyStore.load(null);
|
||||||
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmf.init(keyStore, null);
|
||||||
|
X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
|
||||||
|
return new MagicKeyManager(origKm, userUtils, appPreferences);
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage());
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
Log.e(TAG, "CertificateException " + e.getLocalizedMessage());
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.e(TAG, "NoSuchAlgorithmException " + e.getLocalizedMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "IOException " + e.getLocalizedMessage());
|
||||||
|
} catch (UnrecoverableKeyException e) {
|
||||||
|
Log.e(TAG, "UnrecoverableKeyException " + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicKeyManager keyManager, MagicTrustManager
|
||||||
|
magicTrustManager) {
|
||||||
|
return new SSLSocketFactoryCompat(keyManager, magicTrustManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -145,10 +181,10 @@ public class RestModule {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
|
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
|
||||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
|
||||||
httpClient.addInterceptor(loggingInterceptor);
|
httpClient.addInterceptor(loggingInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trust own CA and all self-signed certs
|
||||||
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager);
|
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager);
|
||||||
httpClient.retryOnConnectionFailure(true);
|
httpClient.retryOnConnectionFailure(true);
|
||||||
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE));
|
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE));
|
||||||
|
|
|
@ -118,7 +118,7 @@ public class CapabilitiesJob extends Job {
|
||||||
userUtils.createOrUpdateUser(null, null,
|
userUtils.createOrUpdateUser(null, null,
|
||||||
null, null,
|
null, null,
|
||||||
null, null, null, internalUserEntity.getId(),
|
null, null, null, internalUserEntity.getId(),
|
||||||
LoganSquare.serialize(capabilitiesOverall.getOcs().getData().getCapabilities()))
|
LoganSquare.serialize(capabilitiesOverall.getOcs().getData().getCapabilities()), null)
|
||||||
.subscribeOn(Schedulers.newThread())
|
.subscribeOn(Schedulers.newThread())
|
||||||
.subscribe(new Observer<UserEntity>() {
|
.subscribe(new Observer<UserEntity>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -55,6 +55,8 @@ public interface User extends Parcelable, Persistable, Serializable {
|
||||||
|
|
||||||
String getCapabilities();
|
String getCapabilities();
|
||||||
|
|
||||||
|
String getClientCertificate();
|
||||||
|
|
||||||
boolean getCurrent();
|
boolean getCurrent();
|
||||||
|
|
||||||
boolean getScheduledForDeletion();
|
boolean getScheduledForDeletion();
|
||||||
|
|
|
@ -318,7 +318,7 @@ public class PushUtils {
|
||||||
null, null,
|
null, null,
|
||||||
userEntity.getDisplayName(),
|
userEntity.getDisplayName(),
|
||||||
LoganSquare.serialize(pushConfigurationState), null,
|
LoganSquare.serialize(pushConfigurationState), null,
|
||||||
null, userEntity.getId(), null)
|
null, userEntity.getId(), null, null)
|
||||||
.subscribe(new Observer<UserEntity>() {
|
.subscribe(new Observer<UserEntity>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Disposable d) {
|
public void onSubscribe(Disposable d) {
|
||||||
|
|
|
@ -161,7 +161,8 @@ public class UserUtils {
|
||||||
@Nullable Boolean currentUser,
|
@Nullable Boolean currentUser,
|
||||||
@Nullable String userId,
|
@Nullable String userId,
|
||||||
@Nullable Long internalId,
|
@Nullable Long internalId,
|
||||||
@Nullable String capabilities) {
|
@Nullable String capabilities,
|
||||||
|
@Nullable String certificateAlias) {
|
||||||
Result findUserQueryResult;
|
Result findUserQueryResult;
|
||||||
if (internalId == null) {
|
if (internalId == null) {
|
||||||
findUserQueryResult = dataStore.select(User.class).where(UserEntity.USERNAME.eq(username).
|
findUserQueryResult = dataStore.select(User.class).where(UserEntity.USERNAME.eq(username).
|
||||||
|
@ -194,6 +195,10 @@ public class UserUtils {
|
||||||
user.setCapabilities(capabilities);
|
user.setCapabilities(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(certificateAlias)) {
|
||||||
|
user.setClientCertificate(certificateAlias);
|
||||||
|
}
|
||||||
|
|
||||||
user.setCurrent(true);
|
user.setCurrent(true);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,6 +223,11 @@ public class UserUtils {
|
||||||
user.setCapabilities(capabilities);
|
user.setCapabilities(capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (certificateAlias != null && !certificateAlias.equals(user.getClientCertificate())) {
|
||||||
|
user.setClientCertificate(certificateAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
user.setCurrent(currentUser);
|
user.setCurrent(currentUser);
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,8 +115,19 @@ public interface AppPreferences {
|
||||||
void setPushToken(String pushToken);
|
void setPushToken(String pushToken);
|
||||||
|
|
||||||
@KeyByString("push_token")
|
@KeyByString("push_token")
|
||||||
|
@RemoveMethod
|
||||||
void removePushToken();
|
void removePushToken();
|
||||||
|
|
||||||
|
@KeyByString("tempClientCertAlias")
|
||||||
|
String getTemporaryClientCertAlias();
|
||||||
|
|
||||||
|
@KeyByString("tempClientCertAlias")
|
||||||
|
void setTemporaryClientCertAlias(String alias);
|
||||||
|
|
||||||
|
@KeyByString("tempClientCertAlias")
|
||||||
|
@RemoveMethod
|
||||||
|
void removeTemporaryClientCertAlias();
|
||||||
|
|
||||||
@KeyByString("pushToTalk_intro_shown")
|
@KeyByString("pushToTalk_intro_shown")
|
||||||
boolean getPushToTalkIntroShown();
|
boolean getPushToTalkIntroShown();
|
||||||
|
|
||||||
|
@ -124,9 +135,9 @@ public interface AppPreferences {
|
||||||
void setPushToTalkIntroShown(boolean shown);
|
void setPushToTalkIntroShown(boolean shown);
|
||||||
|
|
||||||
@KeyByString("pushToTalk_intro_shown")
|
@KeyByString("pushToTalk_intro_shown")
|
||||||
|
@RemoveMethod
|
||||||
void removePushToTalkIntroShown();
|
void removePushToTalkIntroShown();
|
||||||
|
|
||||||
|
|
||||||
@ClearMethod
|
@ClearMethod
|
||||||
void clear();
|
void clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Mario Danic
|
||||||
|
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.utils.ssl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.security.KeyChain;
|
||||||
|
import android.security.KeyChainException;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||||
|
import com.nextcloud.talk.models.database.UserEntity;
|
||||||
|
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
|
|
||||||
|
public class MagicKeyManager implements X509KeyManager {
|
||||||
|
private static final String TAG = "MagicKeyManager";
|
||||||
|
private final X509KeyManager keyManager;
|
||||||
|
|
||||||
|
private UserUtils userUtils;
|
||||||
|
private AppPreferences appPreferences;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public MagicKeyManager(X509KeyManager keyManager, UserUtils userUtils, AppPreferences appPreferences) {
|
||||||
|
this.keyManager = keyManager;
|
||||||
|
this.userUtils = userUtils;
|
||||||
|
this.appPreferences = appPreferences;
|
||||||
|
|
||||||
|
context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
|
||||||
|
String alias;
|
||||||
|
if (!TextUtils.isEmpty(alias = userUtils.getCurrentUser().getClientCertificate()) ||
|
||||||
|
!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())
|
||||||
|
&& new ArrayList<>(Arrays.asList(getClientAliases())).contains(alias)) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509Certificate[] getCertificatesForAlias(@Nullable String alias) {
|
||||||
|
if (alias != null) {
|
||||||
|
GetCertificatesForAliasRunnable getCertificatesForAliasRunnable = new GetCertificatesForAliasRunnable(alias);
|
||||||
|
Thread getCertificatesThread = new Thread(getCertificatesForAliasRunnable);
|
||||||
|
getCertificatesThread.start();
|
||||||
|
try {
|
||||||
|
getCertificatesThread.join();
|
||||||
|
return getCertificatesForAliasRunnable.getCertificates();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Failed to join the thread while getting certificates: " + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrivateKey getPrivateKeyForAlias(@Nullable String alias) {
|
||||||
|
if (alias != null) {
|
||||||
|
GetPrivateKeyForAliasRunnable getPrivateKeyForAliasRunnable = new GetPrivateKeyForAliasRunnable(alias);
|
||||||
|
Thread getPrivateKeyThread = new Thread(getPrivateKeyForAliasRunnable);
|
||||||
|
getPrivateKeyThread.start();
|
||||||
|
try {
|
||||||
|
getPrivateKeyThread.join();
|
||||||
|
return getPrivateKeyForAliasRunnable.getPrivateKey();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Failed to join the thread while getting private key: " + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getCertificateChain(String s) {
|
||||||
|
if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) {
|
||||||
|
return getCertificatesForAlias(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getClientAliases() {
|
||||||
|
Set<String> aliases = new HashSet<>();
|
||||||
|
String alias;
|
||||||
|
if (!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())) {
|
||||||
|
aliases.add(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UserEntity> userEntities = userUtils.getUsers();
|
||||||
|
for (int i = 0; i < userEntities.size(); i++) {
|
||||||
|
if (!TextUtils.isEmpty(alias = userEntities.get(i).getClientCertificate())) {
|
||||||
|
aliases.add(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases.toArray(new String[aliases.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getClientAliases(String s, Principal[] principals) {
|
||||||
|
return getClientAliases();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getServerAliases(String s, Principal[] principals) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivateKey(String s) {
|
||||||
|
if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) {
|
||||||
|
return getPrivateKeyForAlias(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GetCertificatesForAliasRunnable implements Runnable {
|
||||||
|
private volatile X509Certificate[] certificates;
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
public GetCertificatesForAliasRunnable(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
certificates = KeyChain.getCertificateChain(context, alias);
|
||||||
|
} catch (KeyChainException | InterruptedException e) {
|
||||||
|
Log.e(TAG, e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getCertificates() {
|
||||||
|
return certificates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GetPrivateKeyForAliasRunnable implements Runnable {
|
||||||
|
private volatile PrivateKey privateKey;
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
public GetPrivateKeyForAliasRunnable(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
privateKey = KeyChain.getPrivateKey(context, alias);
|
||||||
|
} catch (KeyChainException | InterruptedException e) {
|
||||||
|
Log.e(TAG, e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateKey getPrivateKey() {
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,12 +14,10 @@ import java.net.InetAddress
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.security.GeneralSecurityException
|
import java.security.GeneralSecurityException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.*
|
||||||
import javax.net.ssl.SSLSocket
|
|
||||||
import javax.net.ssl.SSLSocketFactory
|
|
||||||
import javax.net.ssl.X509TrustManager
|
|
||||||
|
|
||||||
class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory() {
|
class SSLSocketFactoryCompat(keyManager: KeyManager?,
|
||||||
|
trustManager: X509TrustManager) : SSLSocketFactory() {
|
||||||
|
|
||||||
private var delegate: SSLSocketFactory
|
private var delegate: SSLSocketFactory
|
||||||
|
|
||||||
|
@ -101,7 +99,10 @@ class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory(
|
||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
val sslContext = SSLContext.getInstance("TLS")
|
val sslContext = SSLContext.getInstance("TLS")
|
||||||
sslContext.init(null, arrayOf(trustManager), null)
|
sslContext.init(
|
||||||
|
if (keyManager != null) arrayOf(keyManager) else null,
|
||||||
|
arrayOf(trustManager),
|
||||||
|
null)
|
||||||
delegate = sslContext.socketFactory
|
delegate = sslContext.socketFactory
|
||||||
} catch (e: GeneralSecurityException) {
|
} catch (e: GeneralSecurityException) {
|
||||||
throw IllegalStateException() // system has no TLS
|
throw IllegalStateException() // system has no TLS
|
||||||
|
|
|
@ -91,4 +91,19 @@
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textColor="@color/nc_light_blue_color"/>
|
android:textColor="@color/nc_light_blue_color"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/cert_text_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/helper_text_view"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||||
|
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:lines="2"
|
||||||
|
android:text="@string/nc_configure_cert_auth"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@color/nc_light_blue_color"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -86,10 +86,17 @@
|
||||||
apc:mp_title="@string/nc_settings_reauthorize"/>
|
apc:mp_title="@string/nc_settings_reauthorize"/>
|
||||||
|
|
||||||
<com.yarolegovich.mp.MaterialStandardPreference
|
<com.yarolegovich.mp.MaterialStandardPreference
|
||||||
android:id="@+id/settings_remove_account"
|
android:id="@+id/settings_client_cert"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/settings_reauthorize"
|
android:layout_below="@id/settings_reauthorize"
|
||||||
|
apc:mp_title="@string/nc_client_cert_setup"/>
|
||||||
|
|
||||||
|
<com.yarolegovich.mp.MaterialStandardPreference
|
||||||
|
android:id="@+id/settings_remove_account"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/settings_client_cert"
|
||||||
apc:mp_title="@string/nc_settings_remove_account"/>
|
apc:mp_title="@string/nc_settings_remove_account"/>
|
||||||
|
|
||||||
<com.yarolegovich.mp.MaterialStandardPreference
|
<com.yarolegovich.mp.MaterialStandardPreference
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
<string name="nc_settings_use_credentials_key">proxy_credentials</string>
|
<string name="nc_settings_use_credentials_key">proxy_credentials</string>
|
||||||
<string name="nc_settings_switch_account">Switch between accounts</string>
|
<string name="nc_settings_switch_account">Switch between accounts</string>
|
||||||
<string name="nc_settings_reauthorize">Reauthorize</string>
|
<string name="nc_settings_reauthorize">Reauthorize</string>
|
||||||
|
<string name="nc_client_cert_setup">Set up client certificate</string>
|
||||||
|
<string name="nc_client_cert_change">Change client certificate</string>
|
||||||
<string name="nc_settings_remove_account">Remove account</string>
|
<string name="nc_settings_remove_account">Remove account</string>
|
||||||
<string name="nc_settings_add_account">Add a new account</string>
|
<string name="nc_settings_add_account">Add a new account</string>
|
||||||
<string name="nc_settings_wrong_account">Only current account can be reauthorized</string>
|
<string name="nc_settings_wrong_account">Only current account can be reauthorized</string>
|
||||||
|
@ -135,6 +137,8 @@
|
||||||
<string name="nc_push_to_talk">Push-to-talk</string>
|
<string name="nc_push_to_talk">Push-to-talk</string>
|
||||||
<string name="nc_push_to_talk_desc">With microphone disabled, click&hold to use Push-to-talk</string>
|
<string name="nc_push_to_talk_desc">With microphone disabled, click&hold to use Push-to-talk</string>
|
||||||
<string name="nc_store_short_desc">Have private video calls and chat using your own server.</string>
|
<string name="nc_store_short_desc">Have private video calls and chat using your own server.</string>
|
||||||
|
<string name="nc_configure_cert_auth">Select authentication certificate</string>
|
||||||
|
<string name="nc_change_cert_auth">Change authentication certificate</string>
|
||||||
<string name="nc_store_full_desc">Use Nextcloud Talk to have one-on-one or group audio or video calls, create or join web conferences and send chat messages. All communication is fully encrypted and mediated by your own server, providing the highest degree of privacy possible.
|
<string name="nc_store_full_desc">Use Nextcloud Talk to have one-on-one or group audio or video calls, create or join web conferences and send chat messages. All communication is fully encrypted and mediated by your own server, providing the highest degree of privacy possible.
|
||||||
|
|
||||||
Nextcloud Talk is easy to use and will always be completely free!
|
Nextcloud Talk is easy to use and will always be completely free!
|
||||||
|
|
Loading…
Reference in a new issue