1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/tests/background/junit3/src/healthreport/TestEnvironmentV1HashAppender.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,146 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +package org.mozilla.gecko.background.healthreport; 1.8 + 1.9 +import java.io.UnsupportedEncodingException; 1.10 +import java.security.MessageDigest; 1.11 +import java.security.NoSuchAlgorithmException; 1.12 +import java.util.Arrays; 1.13 +import java.util.LinkedList; 1.14 + 1.15 +import org.mozilla.apache.commons.codec.binary.Base64; 1.16 +import org.mozilla.gecko.background.healthreport.EnvironmentV1.EnvironmentAppender; 1.17 +import org.mozilla.gecko.background.healthreport.EnvironmentV1.HashAppender; 1.18 +import org.mozilla.gecko.background.helpers.FakeProfileTestCase; 1.19 +import org.mozilla.gecko.sync.Utils; 1.20 + 1.21 +/** 1.22 + * Tests the HashAppender functionality. Note that these tests must be run on an Android 1.23 + * device because the SHA-1 native library needs to be loaded. 1.24 + */ 1.25 +public class TestEnvironmentV1HashAppender extends FakeProfileTestCase { 1.26 + // input and expected values via: http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c 1.27 + private final static String[] INPUTS = new String[] { 1.28 + "abc", 1.29 + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 1.30 + "" // To be filled in below. 1.31 + }; 1.32 + static { 1.33 + final String baseStr = "01234567"; 1.34 + final int repetitions = 80; 1.35 + final StringBuilder builder = new StringBuilder(baseStr.length() * repetitions); 1.36 + for (int i = 0; i < 80; ++i) { 1.37 + builder.append(baseStr); 1.38 + } 1.39 + INPUTS[2] = builder.toString(); 1.40 + } 1.41 + 1.42 + private final static String[] EXPECTEDS = new String[] { 1.43 + "a9993e364706816aba3e25717850c26c9cd0d89d", 1.44 + "84983e441c3bd26ebaae4aa1f95129e5e54670f1", 1.45 + "dea356a2cddd90c7a7ecedc5ebb563934f460452" 1.46 + }; 1.47 + static { 1.48 + for (int i = 0; i < EXPECTEDS.length; ++i) { 1.49 + EXPECTEDS[i] = new Base64(-1, null, false).encodeAsString(Utils.hex2Byte(EXPECTEDS[i])); 1.50 + } 1.51 + } 1.52 + 1.53 + public void testSHA1Hashing() throws Exception { 1.54 + for (int i = 0; i < INPUTS.length; ++i) { 1.55 + final String input = INPUTS[i]; 1.56 + final String expected = EXPECTEDS[i]; 1.57 + 1.58 + final HashAppender appender = new HashAppender(); 1.59 + addStringToAppenderInParts(appender, input); 1.60 + final String result = appender.toString(); 1.61 + 1.62 + assertEquals(expected, result); 1.63 + } 1.64 + } 1.65 + 1.66 + /** 1.67 + * Tests to ensure output is the same as the former MessageDigest implementation (bug 959652). 1.68 + */ 1.69 + public void testAgainstMessageDigestImpl() throws Exception { 1.70 + // List.add doesn't allow add(null) so we make a LinkedList here. 1.71 + final LinkedList<String> inputs = new LinkedList<String>(Arrays.asList(INPUTS)); 1.72 + inputs.add(null); 1.73 + 1.74 + for (final String input : inputs) { 1.75 + final HashAppender hAppender = new HashAppender(); 1.76 + final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender(); 1.77 + 1.78 + hAppender.append(input); 1.79 + mdAppender.append(input); 1.80 + 1.81 + final String hResult = hAppender.toString(); 1.82 + final String mdResult = mdAppender.toString(); 1.83 + assertEquals(mdResult, hResult); 1.84 + } 1.85 + } 1.86 + 1.87 + public void testIntegersAgainstMessageDigestImpl() throws Exception { 1.88 + final int[] INPUTS = {Integer.MIN_VALUE, -1337, -42, 0, 42, 1337, Integer.MAX_VALUE}; 1.89 + for (final int input : INPUTS) { 1.90 + final HashAppender hAppender = new HashAppender(); 1.91 + final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender(); 1.92 + 1.93 + hAppender.append(input); 1.94 + mdAppender.append(input); 1.95 + 1.96 + final String hResult = hAppender.toString(); 1.97 + final String mdResult = mdAppender.toString(); 1.98 + assertEquals(mdResult, hResult); 1.99 + } 1.100 + } 1.101 + 1.102 + private void addStringToAppenderInParts(final EnvironmentAppender appender, final String input) { 1.103 + int substrInd = 0; 1.104 + int substrLength = 1; 1.105 + while (substrInd < input.length()) { 1.106 + final int endInd = Math.min(substrInd + substrLength, input.length()); 1.107 + 1.108 + appender.append(input.substring(substrInd, endInd)); 1.109 + 1.110 + substrInd = endInd; 1.111 + ++substrLength; 1.112 + } 1.113 + } 1.114 + 1.115 + // --- COPY-PASTA'D CODE, FOR TESTING PURPOSES. --- 1.116 + public static class MessageDigestHashAppender extends EnvironmentAppender { 1.117 + final MessageDigest hasher; 1.118 + 1.119 + public MessageDigestHashAppender() throws NoSuchAlgorithmException { 1.120 + // Note to the security-minded reader: we deliberately use SHA-1 here, not 1.121 + // a stronger hash. These identifiers don't strictly need a cryptographic 1.122 + // hash function, because there is negligible value in attacking the hash. 1.123 + // We use SHA-1 because it's *shorter* -- the exact same reason that Git 1.124 + // chose SHA-1. 1.125 + hasher = MessageDigest.getInstance("SHA-1"); 1.126 + } 1.127 + 1.128 + @Override 1.129 + public void append(String s) { 1.130 + try { 1.131 + hasher.update(((s == null) ? "null" : s).getBytes("UTF-8")); 1.132 + } catch (UnsupportedEncodingException e) { 1.133 + // This can never occur. Thanks, Java. 1.134 + } 1.135 + } 1.136 + 1.137 + @Override 1.138 + public void append(int profileCreation) { 1.139 + append(Integer.toString(profileCreation, 10)); 1.140 + } 1.141 + 1.142 + @Override 1.143 + public String toString() { 1.144 + // We *could* use ASCII85… but the savings would be negated by the 1.145 + // inclusion of JSON-unsafe characters like double-quote. 1.146 + return new Base64(-1, null, false).encodeAsString(hasher.digest()); 1.147 + } 1.148 + } 1.149 +}