tls-psk: first steps
This commit is contained in:
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Tobias Schülke
|
||||||
|
*
|
||||||
|
* 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 org.sufficientlysecure.keychain.network;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.net.PskKeyManager;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(api = VERSION_CODES.LOLLIPOP)
|
||||||
|
public class KeyTransferClientInteractor {
|
||||||
|
private static final int SHOW_CONNECTION_DETAILS = 1;
|
||||||
|
private static final int CONNECTION_ESTABLISHED = 2;
|
||||||
|
public static final int CONNECTION_LOST = 3;
|
||||||
|
|
||||||
|
|
||||||
|
private Thread socketThread;
|
||||||
|
private KeyTransferClientCallback callback;
|
||||||
|
private Handler handler;
|
||||||
|
private SSLServerSocket serverSocket;
|
||||||
|
|
||||||
|
|
||||||
|
public void connectToServer(final String connectionDetails, KeyTransferClientCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
handler = new Handler(Looper.getMainLooper());
|
||||||
|
socketThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
serverSocket = null;
|
||||||
|
Socket socket = null;
|
||||||
|
BufferedReader bufferedReader = null;
|
||||||
|
try {
|
||||||
|
int port = 1336;
|
||||||
|
|
||||||
|
PKM pskKeyManager = new PKM();
|
||||||
|
SSLContext sslContext = null;
|
||||||
|
sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(new KeyManager[] { pskKeyManager }, new TrustManager[0], null);
|
||||||
|
socket = sslContext.getSocketFactory().createSocket(InetAddress.getByName(connectionDetails), port);
|
||||||
|
|
||||||
|
invokeListener(CONNECTION_ESTABLISHED, socket.getInetAddress().toString());
|
||||||
|
|
||||||
|
socket.setSoTimeout(500);
|
||||||
|
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
|
||||||
|
while (!isInterrupted() && socket.isConnected()) {
|
||||||
|
try {
|
||||||
|
String line = bufferedReader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "got line: " + line);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "disconnected");
|
||||||
|
invokeListener(CONNECTION_LOST, null);
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
|
||||||
|
Log.e(Constants.TAG, "error!", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (bufferedReader != null) {
|
||||||
|
bufferedReader.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (serverSocket != null) {
|
||||||
|
serverSocket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socketThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeConnection() {
|
||||||
|
if (socketThread != null) {
|
||||||
|
socketThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
socketThread = null;
|
||||||
|
callback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeListener(final int method, final String arg) {
|
||||||
|
if (handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable runnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case CONNECTION_ESTABLISHED:
|
||||||
|
callback.onConnectionEstablished(arg);
|
||||||
|
break;
|
||||||
|
case CONNECTION_LOST:
|
||||||
|
callback.onConnectionLost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handler.post(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface KeyTransferClientCallback {
|
||||||
|
void onConnectionEstablished(String otherName);
|
||||||
|
void onConnectionLost();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PKM extends PskKeyManager implements KeyManager {
|
||||||
|
@Override
|
||||||
|
public SecretKey getKey(String identityHint, String identity, Socket socket) {
|
||||||
|
return new SecretKeySpec("swag".getBytes(), "AES");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey getKey(String identityHint, String identity, SSLEngine engine) {
|
||||||
|
return new SecretKeySpec("swag".getBytes(), "AES");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Tobias Schülke
|
||||||
|
*
|
||||||
|
* 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 org.sufficientlysecure.keychain.network;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.net.PskKeyManager;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(api = VERSION_CODES.LOLLIPOP)
|
||||||
|
public class KeyTransferServerInteractor {
|
||||||
|
private static final int SHOW_CONNECTION_DETAILS = 1;
|
||||||
|
private static final int CONNECTION_ESTABLISHED = 2;
|
||||||
|
public static final int CONNECTION_LOST = 3;
|
||||||
|
|
||||||
|
|
||||||
|
private Thread socketThread;
|
||||||
|
private KeyTransferServerCallback callback;
|
||||||
|
private Handler handler;
|
||||||
|
private SSLServerSocket serverSocket;
|
||||||
|
|
||||||
|
public void startServer(KeyTransferServerCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
handler = new Handler(Looper.getMainLooper());
|
||||||
|
socketThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
serverSocket = null;
|
||||||
|
Socket socket = null;
|
||||||
|
BufferedReader bufferedReader = null;
|
||||||
|
try {
|
||||||
|
int port = 1336;
|
||||||
|
|
||||||
|
PKM pskKeyManager = new PKM();
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(new KeyManager[] { pskKeyManager }, new TrustManager[0], null);
|
||||||
|
serverSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(port);
|
||||||
|
|
||||||
|
String qrCodeData = getIPAddress(true) + ":" + port + ":" + "swag";
|
||||||
|
invokeListener(SHOW_CONNECTION_DETAILS, qrCodeData);
|
||||||
|
|
||||||
|
socket = serverSocket.accept();
|
||||||
|
invokeListener(CONNECTION_ESTABLISHED, socket.getInetAddress().toString());
|
||||||
|
|
||||||
|
socket.setSoTimeout(500);
|
||||||
|
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
|
||||||
|
while (!isInterrupted() && socket.isConnected()) {
|
||||||
|
try {
|
||||||
|
String line = bufferedReader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "got line: " + line);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "disconnected");
|
||||||
|
invokeListener(CONNECTION_LOST, null);
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
|
||||||
|
Log.e(Constants.TAG, "error!", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (bufferedReader != null) {
|
||||||
|
bufferedReader.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (serverSocket != null) {
|
||||||
|
serverSocket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socketThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopServer() {
|
||||||
|
if (socketThread != null) {
|
||||||
|
socketThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverSocket != null) {
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socketThread = null;
|
||||||
|
serverSocket = null;
|
||||||
|
callback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeListener(final int method, final String arg) {
|
||||||
|
if (handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable runnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
switch (method) {
|
||||||
|
case SHOW_CONNECTION_DETAILS:
|
||||||
|
callback.onServerStarted(arg);
|
||||||
|
break;
|
||||||
|
case CONNECTION_ESTABLISHED:
|
||||||
|
callback.onConnectionEstablished(arg);
|
||||||
|
break;
|
||||||
|
case CONNECTION_LOST:
|
||||||
|
callback.onConnectionLost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handler.post(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface KeyTransferServerCallback {
|
||||||
|
void onServerStarted(String qrCodeData);
|
||||||
|
void onConnectionEstablished(String otherName);
|
||||||
|
void onConnectionLost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* from: http://stackoverflow.com/a/13007325
|
||||||
|
* <p>
|
||||||
|
* Get IP address from first non-localhost interface
|
||||||
|
*
|
||||||
|
* @param useIPv4 true=return ipv4, false=return ipv6
|
||||||
|
* @return address or empty string
|
||||||
|
*/
|
||||||
|
private static String getIPAddress(boolean useIPv4) {
|
||||||
|
try {
|
||||||
|
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.
|
||||||
|
getNetworkInterfaces());
|
||||||
|
for (NetworkInterface intf : interfaces) {
|
||||||
|
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
|
||||||
|
for (InetAddress addr : addrs) {
|
||||||
|
if (!addr.isLoopbackAddress()) {
|
||||||
|
String sAddr = addr.getHostAddress();
|
||||||
|
boolean isIPv4 = sAddr.indexOf(':') < 0;
|
||||||
|
|
||||||
|
if (useIPv4) {
|
||||||
|
if (isIPv4)
|
||||||
|
return sAddr;
|
||||||
|
} else {
|
||||||
|
if (!isIPv4) {
|
||||||
|
int delim = sAddr.indexOf('%'); // drop ip6 zone suffix
|
||||||
|
return delim < 0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).
|
||||||
|
toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
} // for now eat exceptions
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PKM extends PskKeyManager implements KeyManager {
|
||||||
|
@Override
|
||||||
|
public SecretKey getKey(String identityHint, String identity, Socket socket) {
|
||||||
|
return new SecretKeySpec("swag".getBytes(), "AES");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey getKey(String identityHint, String identity, SSLEngine engine) {
|
||||||
|
return new SecretKeySpec("swag".getBytes(), "AES");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
|
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.transfer.view.TransferFragment;
|
||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
@@ -50,8 +51,9 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
|
|||||||
static final int ID_ENCRYPT_DECRYPT = 2;
|
static final int ID_ENCRYPT_DECRYPT = 2;
|
||||||
static final int ID_APPS = 3;
|
static final int ID_APPS = 3;
|
||||||
static final int ID_BACKUP = 4;
|
static final int ID_BACKUP = 4;
|
||||||
static final int ID_SETTINGS = 5;
|
static final int ID_TRANSFER = 5;
|
||||||
static final int ID_HELP = 6;
|
static final int ID_SETTINGS = 6;
|
||||||
|
static final int ID_HELP = 7;
|
||||||
|
|
||||||
// both of these are used for instrumentation testing only
|
// both of these are used for instrumentation testing only
|
||||||
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
|
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
|
||||||
@@ -82,6 +84,8 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
|
|||||||
.withIdentifier(ID_APPS).withSelectable(false),
|
.withIdentifier(ID_APPS).withSelectable(false),
|
||||||
new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
|
new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
|
||||||
.withIdentifier(ID_BACKUP).withSelectable(false),
|
.withIdentifier(ID_BACKUP).withSelectable(false),
|
||||||
|
new PrimaryDrawerItem().withName(R.string.nav_transfer).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
|
||||||
|
.withIdentifier(ID_TRANSFER).withSelectable(false),
|
||||||
new DividerDrawerItem(),
|
new DividerDrawerItem(),
|
||||||
new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
|
new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
|
||||||
new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
|
new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
|
||||||
@@ -105,6 +109,9 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
|
|||||||
case ID_BACKUP:
|
case ID_BACKUP:
|
||||||
onBackupSelected();
|
onBackupSelected();
|
||||||
break;
|
break;
|
||||||
|
case ID_TRANSFER:
|
||||||
|
onTransferSelected();
|
||||||
|
break;
|
||||||
case ID_SETTINGS:
|
case ID_SETTINGS:
|
||||||
intent = new Intent(MainActivity.this, SettingsActivity.class);
|
intent = new Intent(MainActivity.this, SettingsActivity.class);
|
||||||
break;
|
break;
|
||||||
@@ -207,6 +214,13 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
|
|||||||
setFragment(frag, true);
|
setFragment(frag, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onTransferSelected() {
|
||||||
|
mToolbar.setTitle(R.string.nav_transfer);
|
||||||
|
mDrawer.setSelection(ID_TRANSFER, false);
|
||||||
|
Fragment frag = new TransferFragment();
|
||||||
|
setFragment(frag, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
// add the values which need to be saved from the drawer to the bundle
|
// add the values which need to be saved from the drawer to the bundle
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Vincent Breitmoser <v.breitmoser@mugenguild.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 org.sufficientlysecure.keychain.ui.transfer.presenter;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.network.KeyTransferClientInteractor;
|
||||||
|
import org.sufficientlysecure.keychain.network.KeyTransferClientInteractor.KeyTransferClientCallback;
|
||||||
|
import org.sufficientlysecure.keychain.network.KeyTransferServerInteractor;
|
||||||
|
import org.sufficientlysecure.keychain.network.KeyTransferServerInteractor.KeyTransferServerCallback;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(api = VERSION_CODES.LOLLIPOP)
|
||||||
|
public class TransferPresenter implements KeyTransferServerCallback, KeyTransferClientCallback {
|
||||||
|
private final Context context;
|
||||||
|
private final TransferMvpView view;
|
||||||
|
|
||||||
|
private KeyTransferServerInteractor keyTransferServerInteractor;
|
||||||
|
private KeyTransferClientInteractor keyTransferClientInteractor;
|
||||||
|
|
||||||
|
public TransferPresenter(Context context, TransferMvpView view) {
|
||||||
|
this.context = context;
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDestroy() {
|
||||||
|
clearConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClickScan() {
|
||||||
|
clearConnections();
|
||||||
|
|
||||||
|
keyTransferClientInteractor = new KeyTransferClientInteractor();
|
||||||
|
keyTransferClientInteractor.connectToServer("10.100.40.126", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearConnections() {
|
||||||
|
if (keyTransferServerInteractor != null) {
|
||||||
|
keyTransferServerInteractor.stopServer();
|
||||||
|
keyTransferServerInteractor = null;
|
||||||
|
}
|
||||||
|
if (keyTransferClientInteractor != null) {
|
||||||
|
keyTransferClientInteractor.closeConnection();
|
||||||
|
keyTransferClientInteractor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServer() {
|
||||||
|
keyTransferServerInteractor = new KeyTransferServerInteractor();
|
||||||
|
keyTransferServerInteractor.startServer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServerStarted(String qrCodeData) {
|
||||||
|
Bitmap qrCodeBitmap = QrCodeUtils.getQRCodeBitmap(Uri.parse("pgp+transfer:" + qrCodeData), 0);
|
||||||
|
view.setQrImage(qrCodeBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionEstablished(String otherName) {
|
||||||
|
view.showConnectionEstablished(otherName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionLost() {
|
||||||
|
view.showWaitingForConnection();
|
||||||
|
startServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface TransferMvpView {
|
||||||
|
void showConnectionEstablished(String hostname);
|
||||||
|
void showWaitingForConnection();
|
||||||
|
|
||||||
|
void setQrImage(Bitmap qrCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* 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 org.sufficientlysecure.keychain.ui.transfer.view;
|
||||||
|
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter;
|
||||||
|
import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter.TransferMvpView;
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(api = VERSION_CODES.LOLLIPOP)
|
||||||
|
public class TransferFragment extends Fragment implements TransferMvpView {
|
||||||
|
public static final int VIEW_WAITING = 0;
|
||||||
|
public static final int VIEW_CONNECTED = 1;
|
||||||
|
|
||||||
|
|
||||||
|
private ImageView vQrCodeImage;
|
||||||
|
private View vScanButton;
|
||||||
|
private TransferPresenter presenter;
|
||||||
|
private ViewAnimator vTransferAnimator;
|
||||||
|
private TextView vConnectionStatusText;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.transfer_fragment, container, false);
|
||||||
|
|
||||||
|
vTransferAnimator = (ViewAnimator) view.findViewById(R.id.transfer_animator);
|
||||||
|
|
||||||
|
vConnectionStatusText = (TextView) view.findViewById(R.id.connection_status);
|
||||||
|
|
||||||
|
vQrCodeImage = (ImageView) view.findViewById(R.id.qr_code_image);
|
||||||
|
vScanButton = view.findViewById(R.id.button_scan);
|
||||||
|
vScanButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (presenter != null) {
|
||||||
|
presenter.onClickScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
presenter = new TransferPresenter(getContext(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
presenter.startServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
presenter.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showWaitingForConnection() {
|
||||||
|
vTransferAnimator.setDisplayedChild(VIEW_WAITING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showConnectionEstablished(String hostname) {
|
||||||
|
vTransferAnimator.setDisplayedChild(VIEW_CONNECTED);
|
||||||
|
vConnectionStatusText.setText("Connected to: " + hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setQrImage(final Bitmap qrCode) {
|
||||||
|
vQrCodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||||
|
new OnGlobalLayoutListener() {
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
// create actual bitmap in display dimensions
|
||||||
|
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||||
|
vQrCodeImage.getWidth(), vQrCodeImage.getWidth(), false);
|
||||||
|
vQrCodeImage.setImageBitmap(scaled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
vQrCodeImage.requestLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
84
OpenKeychain/src/main/res/layout/transfer_fragment.xml
Normal file
84
OpenKeychain/src/main/res/layout/transfer_fragment.xml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:custom="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/transfer_animator"
|
||||||
|
android:inAnimation="@anim/fade_in_delayed"
|
||||||
|
android:outAnimation="@anim/fade_out"
|
||||||
|
custom:initialView="01">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="250dp"
|
||||||
|
android:layout_height="250dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:id="@+id/qr_code_image"
|
||||||
|
tools:src="@drawable/ic_qrcode_white_24dp"
|
||||||
|
tools:tint="@color/md_black_1000"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:id="@+id/button_scan"
|
||||||
|
android:text="Scan"
|
||||||
|
android:drawableLeft="@drawable/ic_qrcode_white_24dp"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:text="Scan with either device to establish a secure connection for device setup."
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:id="@+id/connection_status"
|
||||||
|
android:text="Connected to "
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:text="Available Keys:"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -848,6 +848,7 @@
|
|||||||
<string name="drawer_close">"Close navigation drawer"</string>
|
<string name="drawer_close">"Close navigation drawer"</string>
|
||||||
<string name="my_keys">"My Keys"</string>
|
<string name="my_keys">"My Keys"</string>
|
||||||
<string name="nav_backup">"Backup/Restore"</string>
|
<string name="nav_backup">"Backup/Restore"</string>
|
||||||
|
<string name="nav_transfer">"Transfer"</string>
|
||||||
|
|
||||||
<!-- hints -->
|
<!-- hints -->
|
||||||
<string name="encrypt_content_edit_text_hint">"Type text"</string>
|
<string name="encrypt_content_edit_text_hint">"Type text"</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user