From d97288d6a8d5d152deab68886bd66cfa055006d9 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sun, 22 Mar 2015 22:14:09 +0100 Subject: [PATCH 1/2] Also move `persistent=false` to Preferences in app settings --- src/main/res/xml/app_settings.xml | 45 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/main/res/xml/app_settings.xml b/src/main/res/xml/app_settings.xml index 96f08cc0..0e58d2b1 100644 --- a/src/main/res/xml/app_settings.xml +++ b/src/main/res/xml/app_settings.xml @@ -32,71 +32,82 @@ + android:title="@string/syncthing_options"> + android:title="@string/device_name" + android:persistent="false" /> + android:title="@string/listen_address" + android:persistent="false" /> + android:numeric="integer" + android:persistent="false" /> + android:numeric="integer" + android:persistent="false" /> + android:title="@string/global_announce_enabled" + android:persistent="false" /> + android:title="@string/local_announce_enabled" + android:persistent="false" /> + android:title="@string/upnp_enabled" + android:persistent="false" /> + android:title="@string/global_announce_server" + android:persistent="false" /> + android:title="@string/usage_reporting" + android:persistent="false" /> + android:title="@string/syncthing_gui"> + android:title="@string/gui_address" + android:persistent="false" /> + android:title="@string/gui_user" + android:persistent="false" /> + android:inputType="textPassword" + android:persistent="false" /> + android:enabled="false" + android:persistent="false" /> From c6e7ce6025566ba8b90e6cd989e932262f2ff966 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sun, 22 Mar 2015 23:02:15 +0100 Subject: [PATCH 2/2] Set username and random password on first start. --- .../test/syncthing/SyncthingServiceTest.java | 8 + .../fragments/SettingsFragment.java | 81 +++-- .../syncthing/SyncthingRunnable.java | 20 +- .../syncthing/SyncthingService.java | 23 ++ .../syncthingandroid/util/PRNGFixes.java | 338 ++++++++++++++++++ src/main/res/values/strings.xml | 9 + src/main/res/xml/app_settings.xml | 11 +- 7 files changed, 449 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/nutomic/syncthingandroid/util/PRNGFixes.java diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/SyncthingServiceTest.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/SyncthingServiceTest.java index 3a82f090..85193c01 100644 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/SyncthingServiceTest.java +++ b/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/SyncthingServiceTest.java @@ -2,6 +2,7 @@ package com.nutomic.syncthingandroid.test.syncthing; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.test.ServiceTestCase; import android.test.suitebuilder.annotation.MediumTest; @@ -109,4 +110,11 @@ public class SyncthingServiceTest extends ServiceTestCase { 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()); + } + } diff --git a/src/main/java/com/nutomic/syncthingandroid/fragments/SettingsFragment.java b/src/main/java/com/nutomic/syncthingandroid/fragments/SettingsFragment.java index 8ef63c64..20ba64ec 100644 --- a/src/main/java/com/nutomic/syncthingandroid/fragments/SettingsFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/fragments/SettingsFragment.java @@ -1,5 +1,6 @@ package com.nutomic.syncthingandroid.fragments; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.preference.CheckBoxPreference; @@ -27,11 +28,16 @@ public class SettingsFragment extends PreferenceFragment private static final String TAG = "SettingsFragment"; private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options"; - private static final String SYNCTHING_GUI_KEY = "syncthing_gui"; - private static final String DEVICE_NAME_KEY = "DeviceName"; + private static final String SYNCTHING_GUI_KEY = "syncthing_gui"; + private static final String DEVICE_NAME_KEY = "DeviceName"; private static final String USAGE_REPORT_ACCEPTED = "URAccepted"; - private static final String EXPORT_CONFIG = "export_config"; - private static final String IMPORT_CONFIG = "import_config"; + private static final String ADDRESS = "Address"; + 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"; @@ -72,18 +78,15 @@ public class SettingsFragment extends PreferenceFragment break; default: value = api.getValue(RestApi.TYPE_OPTIONS, pref.getKey()); - - } applyPreference(pref, value); } - for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) { - Preference pref = mGuiScreen.getPreference(i); - pref.setOnPreferenceChangeListener(SettingsFragment.this); - String value = api.getValue(RestApi.TYPE_GUI, pref.getKey()); - applyPreference(pref, value); - } + Preference address = mGuiScreen.findPreference(ADDRESS); + applyPreference(address, api.getValue(RestApi.TYPE_GUI, ADDRESS)); + + Preference tls = mGuiScreen.findPreference(USER_TLS); + applyPreference(tls, api.getValue(RestApi.TYPE_GUI, USER_TLS)); } } @@ -122,26 +125,33 @@ public class SettingsFragment extends PreferenceFragment findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING); mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI); 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 { appVersion.setSummary(getActivity().getPackageManager() .getPackageInfo(getActivity().getPackageName(), 0).versionName); } catch (PackageManager.NameNotFoundException e) { 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); mSyncOnlyCharging.setOnPreferenceChangeListener(this); mSyncOnlyWifi.setOnPreferenceChangeListener(this); screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this); screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this); + user.setOnPreferenceChangeListener(this); + password.setOnPreferenceChangeListener(this); // Force summary update and wifi/charging preferences enable/disable. onPreferenceChange(mAlwaysRunInBackground, mAlwaysRunInBackground.isChecked()); - Preference sttrace = findPreference("sttrace"); 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 @@ -171,7 +181,7 @@ public class SettingsFragment extends PreferenceFragment @Override public boolean onPreferenceChange(Preference preference, Object o) { // 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; if ((pref.getEditText().getInputType() & InputType.TYPE_CLASS_NUMBER) > 0) { try { @@ -193,7 +203,6 @@ public class SettingsFragment extends PreferenceFragment : R.string.always_run_in_background_disabled); mSyncOnlyCharging.setEnabled((Boolean) o); mSyncOnlyWifi.setEnabled((Boolean) o); - } else if (preference.getKey().equals(DEVICE_NAME_KEY)) { RestApi.Device old = mSyncthingService.getApi().getLocalDevice(); RestApi.Device updated = new RestApi.Device(); @@ -211,16 +220,34 @@ public class SettingsFragment extends PreferenceFragment preference.getKey().equals("GlobalAnnServers"); mSyncthingService.getApi().setValue(RestApi.TYPE_OPTIONS, preference.getKey(), o, isArray, getActivity()); - } else if (mGuiScreen.findPreference(preference.getKey()) != null) { + } else if (preference.getKey().equals(ADDRESS) || preference.getKey().equals(USER_TLS)) { mSyncthingService.getApi().setValue( RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity()); - } else if (preference.getKey().equals("sttrace")) { - // Avoid any code injection. - if (!((String) o).matches("[a-z,]*")) { - Log.w(TAG, "Only a-z and ',' are allowed in STTRACE options"); - return false; - } - ((SyncthingActivity) getActivity()).getApi().requireRestart(getActivity()); + } + + // Avoid any code injection. + int error = 0; + if (preference.getKey().equals(STTRACE)) { + if (((String) o).matches("[a-z, ]*")) + 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; diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingRunnable.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingRunnable.java index ba36f938..7877f331 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingRunnable.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingRunnable.java @@ -12,6 +12,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Map; /** * Runs the syncthing binary from command line, and prints its output to logcat. @@ -42,20 +43,25 @@ public class SyncthingRunnable implements Runnable { @Override public void run() { - SharedPreferences pm = PreferenceManager.getDefaultSharedPreferences(mContext); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); DataOutputStream dos = null; int ret = 1; Process process = null; try { // Loop to handle syncthing restarts (these always have an error code of 3). do { - process = Runtime.getRuntime().exec("sh"); + ProcessBuilder pb = new ProcessBuilder("sh"); + Map 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()); - // 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/". dos.writeBytes(mCommand + " -home " + mContext.getFilesDir() + "\n"); dos.writeBytes("exit\n"); diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java index c9323492..4f828009 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java @@ -12,6 +12,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.AsyncTask; +import android.os.Build; import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; @@ -24,6 +25,7 @@ import com.nutomic.syncthingandroid.activities.MainActivity; import com.nutomic.syncthingandroid.activities.SettingsActivity; import com.nutomic.syncthingandroid.util.ConfigXml; import com.nutomic.syncthingandroid.util.FolderObserver; +import com.nutomic.syncthingandroid.util.PRNGFixes; import java.io.File; import java.io.FileInputStream; @@ -32,6 +34,7 @@ import java.io.FilenameFilter; import java.io.IOException; import java.lang.ref.WeakReference; import java.nio.channels.FileChannel; +import java.security.SecureRandom; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; @@ -261,6 +264,26 @@ public class SyncthingService extends Service { */ @Override 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); registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); new StartupTask().execute(); diff --git a/src/main/java/com/nutomic/syncthingandroid/util/PRNGFixes.java b/src/main/java/com/nutomic/syncthingandroid/util/PRNGFixes.java new file mode 100644 index 00000000..d28f95e7 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/util/PRNGFixes.java @@ -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 Android Developers Blog Post + */ +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"); + } + } +} diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 142c13f0..079dd22b 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -274,6 +274,15 @@ Please report any problems you encounter via Github. Debug Options + + Only a-z and \',\' are allowed in STTRACE options + + + Characters : and \' are not allowed in username + + + Characters : and \' are not allowed in password + About diff --git a/src/main/res/xml/app_settings.xml b/src/main/res/xml/app_settings.xml index 0e58d2b1..af872291 100644 --- a/src/main/res/xml/app_settings.xml +++ b/src/main/res/xml/app_settings.xml @@ -93,15 +93,12 @@ android:persistent="false" /> + android:key="gui_user" + android:title="@string/gui_user" /> + android:key="gui_password" + android:title="@string/gui_password" />