PRNG fixes were a nop ever since we switched to 4.4+
This commit is contained in:
		
							parent
							
								
									521bb7846c
								
							
						
					
					
						commit
						f23311ab3f
					
				|  | @ -1,327 +0,0 @@ | |||
| package eu.siacs.conversations.utils; | ||||
| 
 | ||||
| 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}. | ||||
|  */ | ||||
| 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"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	 Daniel Gultsch
						Daniel Gultsch