michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: package org.mozilla.gecko.background.healthreport; michael@0: michael@0: import java.io.UnsupportedEncodingException; michael@0: import java.security.MessageDigest; michael@0: import java.security.NoSuchAlgorithmException; michael@0: import java.util.Arrays; michael@0: import java.util.LinkedList; michael@0: michael@0: import org.mozilla.apache.commons.codec.binary.Base64; michael@0: import org.mozilla.gecko.background.healthreport.EnvironmentV1.EnvironmentAppender; michael@0: import org.mozilla.gecko.background.healthreport.EnvironmentV1.HashAppender; michael@0: import org.mozilla.gecko.background.helpers.FakeProfileTestCase; michael@0: import org.mozilla.gecko.sync.Utils; michael@0: michael@0: /** michael@0: * Tests the HashAppender functionality. Note that these tests must be run on an Android michael@0: * device because the SHA-1 native library needs to be loaded. michael@0: */ michael@0: public class TestEnvironmentV1HashAppender extends FakeProfileTestCase { michael@0: // input and expected values via: http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c michael@0: private final static String[] INPUTS = new String[] { michael@0: "abc", michael@0: "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", michael@0: "" // To be filled in below. michael@0: }; michael@0: static { michael@0: final String baseStr = "01234567"; michael@0: final int repetitions = 80; michael@0: final StringBuilder builder = new StringBuilder(baseStr.length() * repetitions); michael@0: for (int i = 0; i < 80; ++i) { michael@0: builder.append(baseStr); michael@0: } michael@0: INPUTS[2] = builder.toString(); michael@0: } michael@0: michael@0: private final static String[] EXPECTEDS = new String[] { michael@0: "a9993e364706816aba3e25717850c26c9cd0d89d", michael@0: "84983e441c3bd26ebaae4aa1f95129e5e54670f1", michael@0: "dea356a2cddd90c7a7ecedc5ebb563934f460452" michael@0: }; michael@0: static { michael@0: for (int i = 0; i < EXPECTEDS.length; ++i) { michael@0: EXPECTEDS[i] = new Base64(-1, null, false).encodeAsString(Utils.hex2Byte(EXPECTEDS[i])); michael@0: } michael@0: } michael@0: michael@0: public void testSHA1Hashing() throws Exception { michael@0: for (int i = 0; i < INPUTS.length; ++i) { michael@0: final String input = INPUTS[i]; michael@0: final String expected = EXPECTEDS[i]; michael@0: michael@0: final HashAppender appender = new HashAppender(); michael@0: addStringToAppenderInParts(appender, input); michael@0: final String result = appender.toString(); michael@0: michael@0: assertEquals(expected, result); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Tests to ensure output is the same as the former MessageDigest implementation (bug 959652). michael@0: */ michael@0: public void testAgainstMessageDigestImpl() throws Exception { michael@0: // List.add doesn't allow add(null) so we make a LinkedList here. michael@0: final LinkedList inputs = new LinkedList(Arrays.asList(INPUTS)); michael@0: inputs.add(null); michael@0: michael@0: for (final String input : inputs) { michael@0: final HashAppender hAppender = new HashAppender(); michael@0: final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender(); michael@0: michael@0: hAppender.append(input); michael@0: mdAppender.append(input); michael@0: michael@0: final String hResult = hAppender.toString(); michael@0: final String mdResult = mdAppender.toString(); michael@0: assertEquals(mdResult, hResult); michael@0: } michael@0: } michael@0: michael@0: public void testIntegersAgainstMessageDigestImpl() throws Exception { michael@0: final int[] INPUTS = {Integer.MIN_VALUE, -1337, -42, 0, 42, 1337, Integer.MAX_VALUE}; michael@0: for (final int input : INPUTS) { michael@0: final HashAppender hAppender = new HashAppender(); michael@0: final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender(); michael@0: michael@0: hAppender.append(input); michael@0: mdAppender.append(input); michael@0: michael@0: final String hResult = hAppender.toString(); michael@0: final String mdResult = mdAppender.toString(); michael@0: assertEquals(mdResult, hResult); michael@0: } michael@0: } michael@0: michael@0: private void addStringToAppenderInParts(final EnvironmentAppender appender, final String input) { michael@0: int substrInd = 0; michael@0: int substrLength = 1; michael@0: while (substrInd < input.length()) { michael@0: final int endInd = Math.min(substrInd + substrLength, input.length()); michael@0: michael@0: appender.append(input.substring(substrInd, endInd)); michael@0: michael@0: substrInd = endInd; michael@0: ++substrLength; michael@0: } michael@0: } michael@0: michael@0: // --- COPY-PASTA'D CODE, FOR TESTING PURPOSES. --- michael@0: public static class MessageDigestHashAppender extends EnvironmentAppender { michael@0: final MessageDigest hasher; michael@0: michael@0: public MessageDigestHashAppender() throws NoSuchAlgorithmException { michael@0: // Note to the security-minded reader: we deliberately use SHA-1 here, not michael@0: // a stronger hash. These identifiers don't strictly need a cryptographic michael@0: // hash function, because there is negligible value in attacking the hash. michael@0: // We use SHA-1 because it's *shorter* -- the exact same reason that Git michael@0: // chose SHA-1. michael@0: hasher = MessageDigest.getInstance("SHA-1"); michael@0: } michael@0: michael@0: @Override michael@0: public void append(String s) { michael@0: try { michael@0: hasher.update(((s == null) ? "null" : s).getBytes("UTF-8")); michael@0: } catch (UnsupportedEncodingException e) { michael@0: // This can never occur. Thanks, Java. michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void append(int profileCreation) { michael@0: append(Integer.toString(profileCreation, 10)); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: // We *could* use ASCII85… but the savings would be negated by the michael@0: // inclusion of JSON-unsafe characters like double-quote. michael@0: return new Base64(-1, null, false).encodeAsString(hasher.digest()); michael@0: } michael@0: } michael@0: }