mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-12-23 19:31:30 +00:00
Set username and random password on first start.
This commit is contained in:
parent
d97288d6a8
commit
c6e7ce6025
7 changed files with 449 additions and 41 deletions
|
@ -2,6 +2,7 @@ package com.nutomic.syncthingandroid.test.syncthing;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.test.ServiceTestCase;
|
import android.test.ServiceTestCase;
|
||||||
import android.test.suitebuilder.annotation.MediumTest;
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
|
@ -109,4 +110,11 @@ public class SyncthingServiceTest extends ServiceTestCase<SyncthingService> {
|
||||||
assertTrue(publicKey.exists());
|
assertTrue(publicKey.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPassword() {
|
||||||
|
startService(new Intent(getContext(), SyncthingService.class));
|
||||||
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
|
assertNotNull(sp.getString("gui_user", null));
|
||||||
|
assertEquals(20, sp.getString("gui_password", null).length());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.nutomic.syncthingandroid.fragments;
|
package com.nutomic.syncthingandroid.fragments;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
import android.preference.CheckBoxPreference;
|
||||||
|
@ -27,11 +28,16 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
private static final String TAG = "SettingsFragment";
|
private static final String TAG = "SettingsFragment";
|
||||||
|
|
||||||
private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options";
|
private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options";
|
||||||
private static final String SYNCTHING_GUI_KEY = "syncthing_gui";
|
private static final String SYNCTHING_GUI_KEY = "syncthing_gui";
|
||||||
private static final String DEVICE_NAME_KEY = "DeviceName";
|
private static final String DEVICE_NAME_KEY = "DeviceName";
|
||||||
private static final String USAGE_REPORT_ACCEPTED = "URAccepted";
|
private static final String USAGE_REPORT_ACCEPTED = "URAccepted";
|
||||||
private static final String EXPORT_CONFIG = "export_config";
|
private static final String ADDRESS = "Address";
|
||||||
private static final String IMPORT_CONFIG = "import_config";
|
private static final String GUI_USER = "gui_user";
|
||||||
|
private static final String GUI_PASSWORD = "gui_password";
|
||||||
|
private static final String USER_TLS = "UseTLS";
|
||||||
|
private static final String EXPORT_CONFIG = "export_config";
|
||||||
|
private static final String IMPORT_CONFIG = "import_config";
|
||||||
|
private static final String STTRACE = "sttrace";
|
||||||
|
|
||||||
private static final String SYNCTHING_VERSION_KEY = "syncthing_version";
|
private static final String SYNCTHING_VERSION_KEY = "syncthing_version";
|
||||||
|
|
||||||
|
@ -72,18 +78,15 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
value = api.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
|
value = api.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
applyPreference(pref, value);
|
applyPreference(pref, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
|
Preference address = mGuiScreen.findPreference(ADDRESS);
|
||||||
Preference pref = mGuiScreen.getPreference(i);
|
applyPreference(address, api.getValue(RestApi.TYPE_GUI, ADDRESS));
|
||||||
pref.setOnPreferenceChangeListener(SettingsFragment.this);
|
|
||||||
String value = api.getValue(RestApi.TYPE_GUI, pref.getKey());
|
Preference tls = mGuiScreen.findPreference(USER_TLS);
|
||||||
applyPreference(pref, value);
|
applyPreference(tls, api.getValue(RestApi.TYPE_GUI, USER_TLS));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,26 +125,33 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING);
|
findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING);
|
||||||
mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI);
|
mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI);
|
||||||
Preference appVersion = screen.findPreference(APP_VERSION_KEY);
|
Preference appVersion = screen.findPreference(APP_VERSION_KEY);
|
||||||
|
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
|
||||||
|
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
|
||||||
|
Preference user = screen.findPreference(GUI_USER);
|
||||||
|
Preference password = screen.findPreference(GUI_PASSWORD);
|
||||||
|
Preference sttrace = findPreference(STTRACE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appVersion.setSummary(getActivity().getPackageManager()
|
appVersion.setSummary(getActivity().getPackageManager()
|
||||||
.getPackageInfo(getActivity().getPackageName(), 0).versionName);
|
.getPackageInfo(getActivity().getPackageName(), 0).versionName);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
Log.d(TAG, "Failed to get app version name");
|
Log.d(TAG, "Failed to get app version name");
|
||||||
}
|
}
|
||||||
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
|
|
||||||
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
|
|
||||||
|
|
||||||
mAlwaysRunInBackground.setOnPreferenceChangeListener(this);
|
mAlwaysRunInBackground.setOnPreferenceChangeListener(this);
|
||||||
mSyncOnlyCharging.setOnPreferenceChangeListener(this);
|
mSyncOnlyCharging.setOnPreferenceChangeListener(this);
|
||||||
mSyncOnlyWifi.setOnPreferenceChangeListener(this);
|
mSyncOnlyWifi.setOnPreferenceChangeListener(this);
|
||||||
screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this);
|
screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||||
screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this);
|
screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||||
|
user.setOnPreferenceChangeListener(this);
|
||||||
|
password.setOnPreferenceChangeListener(this);
|
||||||
// Force summary update and wifi/charging preferences enable/disable.
|
// Force summary update and wifi/charging preferences enable/disable.
|
||||||
onPreferenceChange(mAlwaysRunInBackground, mAlwaysRunInBackground.isChecked());
|
onPreferenceChange(mAlwaysRunInBackground, mAlwaysRunInBackground.isChecked());
|
||||||
Preference sttrace = findPreference("sttrace");
|
|
||||||
sttrace.setOnPreferenceChangeListener(this);
|
sttrace.setOnPreferenceChangeListener(this);
|
||||||
sttrace.setSummary(PreferenceManager
|
|
||||||
.getDefaultSharedPreferences(getActivity()).getString("sttrace", ""));
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
|
user.setSummary(sp.getString("gui_user", ""));
|
||||||
|
sttrace.setSummary(sp.getString("sttrace", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -171,7 +181,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||||
// Convert new value to integer if input type is number.
|
// Convert new value to integer if input type is number.
|
||||||
if (preference instanceof EditTextPreference) {
|
if (preference instanceof EditTextPreference && !preference.getKey().equals(GUI_PASSWORD)) {
|
||||||
EditTextPreference pref = (EditTextPreference) preference;
|
EditTextPreference pref = (EditTextPreference) preference;
|
||||||
if ((pref.getEditText().getInputType() & InputType.TYPE_CLASS_NUMBER) > 0) {
|
if ((pref.getEditText().getInputType() & InputType.TYPE_CLASS_NUMBER) > 0) {
|
||||||
try {
|
try {
|
||||||
|
@ -193,7 +203,6 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
: R.string.always_run_in_background_disabled);
|
: R.string.always_run_in_background_disabled);
|
||||||
mSyncOnlyCharging.setEnabled((Boolean) o);
|
mSyncOnlyCharging.setEnabled((Boolean) o);
|
||||||
mSyncOnlyWifi.setEnabled((Boolean) o);
|
mSyncOnlyWifi.setEnabled((Boolean) o);
|
||||||
|
|
||||||
} else if (preference.getKey().equals(DEVICE_NAME_KEY)) {
|
} else if (preference.getKey().equals(DEVICE_NAME_KEY)) {
|
||||||
RestApi.Device old = mSyncthingService.getApi().getLocalDevice();
|
RestApi.Device old = mSyncthingService.getApi().getLocalDevice();
|
||||||
RestApi.Device updated = new RestApi.Device();
|
RestApi.Device updated = new RestApi.Device();
|
||||||
|
@ -211,16 +220,34 @@ public class SettingsFragment extends PreferenceFragment
|
||||||
preference.getKey().equals("GlobalAnnServers");
|
preference.getKey().equals("GlobalAnnServers");
|
||||||
mSyncthingService.getApi().setValue(RestApi.TYPE_OPTIONS, preference.getKey(), o,
|
mSyncthingService.getApi().setValue(RestApi.TYPE_OPTIONS, preference.getKey(), o,
|
||||||
isArray, getActivity());
|
isArray, getActivity());
|
||||||
} else if (mGuiScreen.findPreference(preference.getKey()) != null) {
|
} else if (preference.getKey().equals(ADDRESS) || preference.getKey().equals(USER_TLS)) {
|
||||||
mSyncthingService.getApi().setValue(
|
mSyncthingService.getApi().setValue(
|
||||||
RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
|
RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
|
||||||
} else if (preference.getKey().equals("sttrace")) {
|
}
|
||||||
// Avoid any code injection.
|
|
||||||
if (!((String) o).matches("[a-z,]*")) {
|
// Avoid any code injection.
|
||||||
Log.w(TAG, "Only a-z and ',' are allowed in STTRACE options");
|
int error = 0;
|
||||||
return false;
|
if (preference.getKey().equals(STTRACE)) {
|
||||||
}
|
if (((String) o).matches("[a-z, ]*"))
|
||||||
((SyncthingActivity) getActivity()).getApi().requireRestart(getActivity());
|
mSyncthingService.getApi().requireRestart(getActivity());
|
||||||
|
else
|
||||||
|
error = R.string.toast_invalid_sttrace;
|
||||||
|
} else if (preference.getKey().equals(GUI_USER)) {
|
||||||
|
String s = (String) o;
|
||||||
|
if (!s.contains(":") && !s.contains("'"))
|
||||||
|
mSyncthingService.getApi().requireRestart(getActivity());
|
||||||
|
else
|
||||||
|
error = R.string.toast_invalid_username;
|
||||||
|
} else if (preference.getKey().equals(GUI_PASSWORD)) {
|
||||||
|
String s = (String) o;
|
||||||
|
if (!s.contains(":") && !s.contains("'"))
|
||||||
|
mSyncthingService.getApi().requireRestart(getActivity());
|
||||||
|
else
|
||||||
|
error = R.string.toast_invalid_password;
|
||||||
|
}
|
||||||
|
if (error != 0) {
|
||||||
|
Toast.makeText(getActivity(), error, Toast.LENGTH_SHORT).show();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the syncthing binary from command line, and prints its output to logcat.
|
* Runs the syncthing binary from command line, and prints its output to logcat.
|
||||||
|
@ -42,20 +43,25 @@ public class SyncthingRunnable implements Runnable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
SharedPreferences pm = PreferenceManager.getDefaultSharedPreferences(mContext);
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||||
DataOutputStream dos = null;
|
DataOutputStream dos = null;
|
||||||
int ret = 1;
|
int ret = 1;
|
||||||
Process process = null;
|
Process process = null;
|
||||||
try {
|
try {
|
||||||
// Loop to handle syncthing restarts (these always have an error code of 3).
|
// Loop to handle syncthing restarts (these always have an error code of 3).
|
||||||
do {
|
do {
|
||||||
process = Runtime.getRuntime().exec("sh");
|
ProcessBuilder pb = new ProcessBuilder("sh");
|
||||||
|
Map<String, String> env = pb.environment();
|
||||||
|
// Set home directory to data folder for web GUI folder picker.
|
||||||
|
env.put("HOME", Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||||
|
env.put("STTRACE", sp.getString("sttrace", ""));
|
||||||
|
env.put("STNORESTART", "1");
|
||||||
|
env.put("STNOUPGRADE", "1");
|
||||||
|
env.put("STGUIAUTH", sp.getString("gui_user", "") + ":" +
|
||||||
|
sp.getString("gui_password", ""));
|
||||||
|
process = pb.start();
|
||||||
|
|
||||||
dos = new DataOutputStream(process.getOutputStream());
|
dos = new DataOutputStream(process.getOutputStream());
|
||||||
// Set home directory to data folder for syncthing to use.
|
|
||||||
dos.writeBytes("HOME=" + Environment.getExternalStorageDirectory() + " ");
|
|
||||||
dos.writeBytes("STTRACE=" + pm.getString("sttrace", "") + " ");
|
|
||||||
dos.writeBytes("STNORESTART=1 ");
|
|
||||||
dos.writeBytes("STNOUPGRADE=1 ");
|
|
||||||
// Call syncthing with -home (as it would otherwise use "~/.config/syncthing/".
|
// Call syncthing with -home (as it would otherwise use "~/.config/syncthing/".
|
||||||
dos.writeBytes(mCommand + " -home " + mContext.getFilesDir() + "\n");
|
dos.writeBytes(mCommand + " -home " + mContext.getFilesDir() + "\n");
|
||||||
dos.writeBytes("exit\n");
|
dos.writeBytes("exit\n");
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
@ -24,6 +25,7 @@ import com.nutomic.syncthingandroid.activities.MainActivity;
|
||||||
import com.nutomic.syncthingandroid.activities.SettingsActivity;
|
import com.nutomic.syncthingandroid.activities.SettingsActivity;
|
||||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
import com.nutomic.syncthingandroid.util.FolderObserver;
|
import com.nutomic.syncthingandroid.util.FolderObserver;
|
||||||
|
import com.nutomic.syncthingandroid.util.PRNGFixes;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -32,6 +34,7 @@ import java.io.FilenameFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -261,6 +264,26 @@ public class SyncthingService extends Service {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
PRNGFixes.apply();
|
||||||
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
|
// Make sure this is also done for existing installs. We can replace this check with
|
||||||
|
// {@link #isFirstStart()} after a while.
|
||||||
|
if (!sp.getBoolean("default_user_pw_set", false)) {
|
||||||
|
sp.edit().putBoolean("default_user_pw_set", true).commit();
|
||||||
|
char[] chars =
|
||||||
|
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
for (int i = 0; i < 20; i++)
|
||||||
|
sb.append(chars[random.nextInt(chars.length)]);
|
||||||
|
|
||||||
|
String user = Build.MODEL.replaceAll("[^a-zA-Z0-9 ]", "");
|
||||||
|
Log.i(TAG, "Generated GUI username and password (username is " + user + ")");
|
||||||
|
sp.edit().putString("gui_user", user)
|
||||||
|
.putString("gui_password", sb.toString()).commit();
|
||||||
|
}
|
||||||
|
|
||||||
mDeviceStateHolder = new DeviceStateHolder(SyncthingService.this);
|
mDeviceStateHolder = new DeviceStateHolder(SyncthingService.this);
|
||||||
registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||||
new StartupTask().execute();
|
new StartupTask().execute();
|
||||||
|
|
338
src/main/java/com/nutomic/syncthingandroid/util/PRNGFixes.java
Normal file
338
src/main/java/com/nutomic/syncthingandroid/util/PRNGFixes.java
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
package com.nutomic.syncthingandroid.util;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty. In no event will Google be held liable for any damages
|
||||||
|
* arising from the use of this software.
|
||||||
|
*
|
||||||
|
* Permission is granted to anyone to use this software for any purpose,
|
||||||
|
* including commercial applications, and to alter it and redistribute it
|
||||||
|
* freely, as long as the origin is not misrepresented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.SecureRandomSpi;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes for the output of the default PRNG having low entropy.
|
||||||
|
*
|
||||||
|
* The fixes need to be applied via {@link #apply()} before any use of Java
|
||||||
|
* Cryptography Architecture primitives. A good place to invoke them is in the
|
||||||
|
* application's {@code onCreate}.
|
||||||
|
*
|
||||||
|
* @see <a href="http://android-developers.blogspot.jp/2013/08/some-securerandom-thoughts.html">Android Developers Blog Post</a>
|
||||||
|
*/
|
||||||
|
public final class PRNGFixes {
|
||||||
|
|
||||||
|
private static final int VERSION_CODE_JELLY_BEAN = 16;
|
||||||
|
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
||||||
|
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
|
||||||
|
getBuildFingerprintAndDeviceSerial();
|
||||||
|
|
||||||
|
/** Hidden constructor to prevent instantiation. */
|
||||||
|
private PRNGFixes() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies all fixes.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if a fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
public static void apply() {
|
||||||
|
applyOpenSSLFix();
|
||||||
|
installLinuxPRNGSecureRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
||||||
|
* fix is not needed.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
private static void applyOpenSSLFix() throws SecurityException {
|
||||||
|
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|
||||||
|
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
|
||||||
|
// No need to apply the fix
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Mix in the device- and invocation-specific seed.
|
||||||
|
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
.getMethod("RAND_seed", byte[].class)
|
||||||
|
.invoke(null, generateSeed());
|
||||||
|
|
||||||
|
// Mix output of Linux PRNG into OpenSSL's PRNG
|
||||||
|
int bytesRead = (Integer) Class.forName(
|
||||||
|
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
.getMethod("RAND_load_file", String.class, long.class)
|
||||||
|
.invoke(null, "/dev/urandom", 1024);
|
||||||
|
if (bytesRead != 1024) {
|
||||||
|
throw new IOException(
|
||||||
|
"Unexpected number of bytes read from Linux PRNG: "
|
||||||
|
+ bytesRead);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
|
||||||
|
* default. Does nothing if the implementation is already the default or if
|
||||||
|
* there is not need to install the implementation.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
private static void installLinuxPRNGSecureRandom()
|
||||||
|
throws SecurityException {
|
||||||
|
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
||||||
|
// No need to apply the fix
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install a Linux PRNG-based SecureRandom implementation as the
|
||||||
|
// default, if not yet installed.
|
||||||
|
Provider[] secureRandomProviders =
|
||||||
|
Security.getProviders("SecureRandom.SHA1PRNG");
|
||||||
|
if ((secureRandomProviders == null)
|
||||||
|
|| (secureRandomProviders.length < 1)
|
||||||
|
|| (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
secureRandomProviders[0].getClass()))) {
|
||||||
|
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that new SecureRandom() and
|
||||||
|
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
||||||
|
// by the Linux PRNG-based SecureRandom implementation.
|
||||||
|
SecureRandom rng1 = new SecureRandom();
|
||||||
|
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
rng1.getProvider().getClass())) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"new SecureRandom() backed by wrong Provider: "
|
||||||
|
+ rng1.getProvider().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureRandom rng2;
|
||||||
|
try {
|
||||||
|
rng2 = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new SecurityException("SHA1PRNG not available", e);
|
||||||
|
}
|
||||||
|
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
rng2.getProvider().getClass())) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
|
||||||
|
+ " Provider: " + rng2.getProvider().getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code Provider} of {@code SecureRandom} engines which pass through
|
||||||
|
* all requests to the Linux PRNG.
|
||||||
|
*/
|
||||||
|
private static class LinuxPRNGSecureRandomProvider extends Provider {
|
||||||
|
|
||||||
|
public LinuxPRNGSecureRandomProvider() {
|
||||||
|
super("LinuxPRNG",
|
||||||
|
1.0,
|
||||||
|
"A Linux-specific random number provider that uses"
|
||||||
|
+ " /dev/urandom");
|
||||||
|
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
||||||
|
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
||||||
|
// prevent them from getting the default implementation whose output
|
||||||
|
// may have low entropy.
|
||||||
|
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
|
||||||
|
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
|
||||||
|
* ({@code /dev/urandom}).
|
||||||
|
*/
|
||||||
|
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
||||||
|
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
||||||
|
* this class seed themselves by mixing in the current time, PID, UID,
|
||||||
|
* build fingerprint, and hardware serial number (where available) into
|
||||||
|
* Linux PRNG.
|
||||||
|
*
|
||||||
|
* Concurrency: Read requests to the underlying Linux PRNG are
|
||||||
|
* serialized (on sLock) to ensure that multiple threads do not get
|
||||||
|
* duplicated PRNG output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final File URANDOM_FILE = new File("/dev/urandom");
|
||||||
|
|
||||||
|
private static final Object sLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input stream for reading from Linux PRNG or {@code null} if not yet
|
||||||
|
* opened.
|
||||||
|
*
|
||||||
|
* @GuardedBy("sLock")
|
||||||
|
*/
|
||||||
|
private static DataInputStream sUrandomIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output stream for writing to Linux PRNG or {@code null} if not yet
|
||||||
|
* opened.
|
||||||
|
*
|
||||||
|
* @GuardedBy("sLock")
|
||||||
|
*/
|
||||||
|
private static OutputStream sUrandomOut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this engine instance has been seeded. This is needed because
|
||||||
|
* each instance needs to seed itself if the client does not explicitly
|
||||||
|
* seed it.
|
||||||
|
*/
|
||||||
|
private boolean mSeeded;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineSetSeed(byte[] bytes) {
|
||||||
|
try {
|
||||||
|
OutputStream out;
|
||||||
|
synchronized (sLock) {
|
||||||
|
out = getUrandomOutputStream();
|
||||||
|
}
|
||||||
|
out.write(bytes);
|
||||||
|
out.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// On a small fraction of devices /dev/urandom is not writable.
|
||||||
|
// Log and ignore.
|
||||||
|
Log.w(PRNGFixes.class.getSimpleName(),
|
||||||
|
"Failed to mix seed into " + URANDOM_FILE);
|
||||||
|
} finally {
|
||||||
|
mSeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineNextBytes(byte[] bytes) {
|
||||||
|
if (!mSeeded) {
|
||||||
|
// Mix in the device- and invocation-specific seed.
|
||||||
|
engineSetSeed(generateSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DataInputStream in;
|
||||||
|
synchronized (sLock) {
|
||||||
|
in = getUrandomInputStream();
|
||||||
|
}
|
||||||
|
synchronized (in) {
|
||||||
|
in.readFully(bytes);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Failed to read from " + URANDOM_FILE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] engineGenerateSeed(int size) {
|
||||||
|
byte[] seed = new byte[size];
|
||||||
|
engineNextBytes(seed);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataInputStream getUrandomInputStream() {
|
||||||
|
synchronized (sLock) {
|
||||||
|
if (sUrandomIn == null) {
|
||||||
|
// NOTE: Consider inserting a BufferedInputStream between
|
||||||
|
// DataInputStream and FileInputStream if you need higher
|
||||||
|
// PRNG output performance and can live with future PRNG
|
||||||
|
// output being pulled into this process prematurely.
|
||||||
|
try {
|
||||||
|
sUrandomIn = new DataInputStream(
|
||||||
|
new FileInputStream(URANDOM_FILE));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException("Failed to open "
|
||||||
|
+ URANDOM_FILE + " for reading", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sUrandomIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getUrandomOutputStream() throws IOException {
|
||||||
|
synchronized (sLock) {
|
||||||
|
if (sUrandomOut == null) {
|
||||||
|
sUrandomOut = new FileOutputStream(URANDOM_FILE);
|
||||||
|
}
|
||||||
|
return sUrandomOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a device- and invocation-specific seed to be mixed into the
|
||||||
|
* Linux PRNG.
|
||||||
|
*/
|
||||||
|
private static byte[] generateSeed() {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream seedBufferOut =
|
||||||
|
new DataOutputStream(seedBuffer);
|
||||||
|
seedBufferOut.writeLong(System.currentTimeMillis());
|
||||||
|
seedBufferOut.writeLong(System.nanoTime());
|
||||||
|
seedBufferOut.writeInt(Process.myPid());
|
||||||
|
seedBufferOut.writeInt(Process.myUid());
|
||||||
|
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
|
||||||
|
seedBufferOut.close();
|
||||||
|
return seedBuffer.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException("Failed to generate seed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the hardware serial number of this device.
|
||||||
|
*
|
||||||
|
* @return serial number or {@code null} if not available.
|
||||||
|
*/
|
||||||
|
private static String getDeviceSerialNumber() {
|
||||||
|
// We're using the Reflection API because Build.SERIAL is only available
|
||||||
|
// since API Level 9 (Gingerbread, Android 2.3).
|
||||||
|
try {
|
||||||
|
return (String) Build.class.getField("SERIAL").get(null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBuildFingerprintAndDeviceSerial() {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String fingerprint = Build.FINGERPRINT;
|
||||||
|
if (fingerprint != null) {
|
||||||
|
result.append(fingerprint);
|
||||||
|
}
|
||||||
|
String serial = getDeviceSerialNumber();
|
||||||
|
if (serial != null) {
|
||||||
|
result.append(serial);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return result.toString().getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 encoding not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -274,6 +274,15 @@ Please report any problems you encounter via Github.</string>
|
||||||
<!-- Title for the preference to set STTRACE parameters -->
|
<!-- Title for the preference to set STTRACE parameters -->
|
||||||
<string name="sttrace_title">Debug Options</string>
|
<string name="sttrace_title">Debug Options</string>
|
||||||
|
|
||||||
|
<!-- Toast after entering invalid STTRACE params -->
|
||||||
|
<string name="toast_invalid_sttrace">Only a-z and \',\' are allowed in STTRACE options</string>
|
||||||
|
|
||||||
|
<!-- Toast after entering invalid username -->
|
||||||
|
<string name="toast_invalid_username">Characters : and \' are not allowed in username</string>
|
||||||
|
|
||||||
|
<!-- Toast after entering invalid password -->
|
||||||
|
<string name="toast_invalid_password">Characters : and \' are not allowed in password</string>
|
||||||
|
|
||||||
<string name="category_about">About</string>
|
<string name="category_about">About</string>
|
||||||
|
|
||||||
<!-- Settings item that opens issue tracker -->
|
<!-- Settings item that opens issue tracker -->
|
||||||
|
|
|
@ -93,15 +93,12 @@
|
||||||
android:persistent="false" />
|
android:persistent="false" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:key="User"
|
android:key="gui_user"
|
||||||
android:title="@string/gui_user"
|
android:title="@string/gui_user" />
|
||||||
android:persistent="false" />
|
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:key="Password"
|
android:key="gui_password"
|
||||||
android:title="@string/gui_password"
|
android:title="@string/gui_password" />
|
||||||
android:inputType="textPassword"
|
|
||||||
android:persistent="false" />
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="UseTLS"
|
android:key="UseTLS"
|
||||||
|
|
Loading…
Reference in a new issue