Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 3 | |
michael@0 | 4 | package org.mozilla.gecko.background.healthreport; |
michael@0 | 5 | |
michael@0 | 6 | import java.io.UnsupportedEncodingException; |
michael@0 | 7 | import java.security.MessageDigest; |
michael@0 | 8 | import java.security.NoSuchAlgorithmException; |
michael@0 | 9 | import java.util.Arrays; |
michael@0 | 10 | import java.util.LinkedList; |
michael@0 | 11 | |
michael@0 | 12 | import org.mozilla.apache.commons.codec.binary.Base64; |
michael@0 | 13 | import org.mozilla.gecko.background.healthreport.EnvironmentV1.EnvironmentAppender; |
michael@0 | 14 | import org.mozilla.gecko.background.healthreport.EnvironmentV1.HashAppender; |
michael@0 | 15 | import org.mozilla.gecko.background.helpers.FakeProfileTestCase; |
michael@0 | 16 | import org.mozilla.gecko.sync.Utils; |
michael@0 | 17 | |
michael@0 | 18 | /** |
michael@0 | 19 | * Tests the HashAppender functionality. Note that these tests must be run on an Android |
michael@0 | 20 | * device because the SHA-1 native library needs to be loaded. |
michael@0 | 21 | */ |
michael@0 | 22 | public class TestEnvironmentV1HashAppender extends FakeProfileTestCase { |
michael@0 | 23 | // input and expected values via: http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c |
michael@0 | 24 | private final static String[] INPUTS = new String[] { |
michael@0 | 25 | "abc", |
michael@0 | 26 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", |
michael@0 | 27 | "" // To be filled in below. |
michael@0 | 28 | }; |
michael@0 | 29 | static { |
michael@0 | 30 | final String baseStr = "01234567"; |
michael@0 | 31 | final int repetitions = 80; |
michael@0 | 32 | final StringBuilder builder = new StringBuilder(baseStr.length() * repetitions); |
michael@0 | 33 | for (int i = 0; i < 80; ++i) { |
michael@0 | 34 | builder.append(baseStr); |
michael@0 | 35 | } |
michael@0 | 36 | INPUTS[2] = builder.toString(); |
michael@0 | 37 | } |
michael@0 | 38 | |
michael@0 | 39 | private final static String[] EXPECTEDS = new String[] { |
michael@0 | 40 | "a9993e364706816aba3e25717850c26c9cd0d89d", |
michael@0 | 41 | "84983e441c3bd26ebaae4aa1f95129e5e54670f1", |
michael@0 | 42 | "dea356a2cddd90c7a7ecedc5ebb563934f460452" |
michael@0 | 43 | }; |
michael@0 | 44 | static { |
michael@0 | 45 | for (int i = 0; i < EXPECTEDS.length; ++i) { |
michael@0 | 46 | EXPECTEDS[i] = new Base64(-1, null, false).encodeAsString(Utils.hex2Byte(EXPECTEDS[i])); |
michael@0 | 47 | } |
michael@0 | 48 | } |
michael@0 | 49 | |
michael@0 | 50 | public void testSHA1Hashing() throws Exception { |
michael@0 | 51 | for (int i = 0; i < INPUTS.length; ++i) { |
michael@0 | 52 | final String input = INPUTS[i]; |
michael@0 | 53 | final String expected = EXPECTEDS[i]; |
michael@0 | 54 | |
michael@0 | 55 | final HashAppender appender = new HashAppender(); |
michael@0 | 56 | addStringToAppenderInParts(appender, input); |
michael@0 | 57 | final String result = appender.toString(); |
michael@0 | 58 | |
michael@0 | 59 | assertEquals(expected, result); |
michael@0 | 60 | } |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | /** |
michael@0 | 64 | * Tests to ensure output is the same as the former MessageDigest implementation (bug 959652). |
michael@0 | 65 | */ |
michael@0 | 66 | public void testAgainstMessageDigestImpl() throws Exception { |
michael@0 | 67 | // List.add doesn't allow add(null) so we make a LinkedList here. |
michael@0 | 68 | final LinkedList<String> inputs = new LinkedList<String>(Arrays.asList(INPUTS)); |
michael@0 | 69 | inputs.add(null); |
michael@0 | 70 | |
michael@0 | 71 | for (final String input : inputs) { |
michael@0 | 72 | final HashAppender hAppender = new HashAppender(); |
michael@0 | 73 | final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender(); |
michael@0 | 74 | |
michael@0 | 75 | hAppender.append(input); |
michael@0 | 76 | mdAppender.append(input); |
michael@0 | 77 | |
michael@0 | 78 | final String hResult = hAppender.toString(); |
michael@0 | 79 | final String mdResult = mdAppender.toString(); |
michael@0 | 80 | assertEquals(mdResult, hResult); |
michael@0 | 81 | } |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | public void testIntegersAgainstMessageDigestImpl() throws Exception { |
michael@0 | 85 | final int[] INPUTS = {Integer.MIN_VALUE, -1337, -42, 0, 42, 1337, Integer.MAX_VALUE}; |
michael@0 | 86 | for (final int input : INPUTS) { |
michael@0 | 87 | final HashAppender hAppender = new HashAppender(); |
michael@0 | 88 | final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender(); |
michael@0 | 89 | |
michael@0 | 90 | hAppender.append(input); |
michael@0 | 91 | mdAppender.append(input); |
michael@0 | 92 | |
michael@0 | 93 | final String hResult = hAppender.toString(); |
michael@0 | 94 | final String mdResult = mdAppender.toString(); |
michael@0 | 95 | assertEquals(mdResult, hResult); |
michael@0 | 96 | } |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | private void addStringToAppenderInParts(final EnvironmentAppender appender, final String input) { |
michael@0 | 100 | int substrInd = 0; |
michael@0 | 101 | int substrLength = 1; |
michael@0 | 102 | while (substrInd < input.length()) { |
michael@0 | 103 | final int endInd = Math.min(substrInd + substrLength, input.length()); |
michael@0 | 104 | |
michael@0 | 105 | appender.append(input.substring(substrInd, endInd)); |
michael@0 | 106 | |
michael@0 | 107 | substrInd = endInd; |
michael@0 | 108 | ++substrLength; |
michael@0 | 109 | } |
michael@0 | 110 | } |
michael@0 | 111 | |
michael@0 | 112 | // --- COPY-PASTA'D CODE, FOR TESTING PURPOSES. --- |
michael@0 | 113 | public static class MessageDigestHashAppender extends EnvironmentAppender { |
michael@0 | 114 | final MessageDigest hasher; |
michael@0 | 115 | |
michael@0 | 116 | public MessageDigestHashAppender() throws NoSuchAlgorithmException { |
michael@0 | 117 | // Note to the security-minded reader: we deliberately use SHA-1 here, not |
michael@0 | 118 | // a stronger hash. These identifiers don't strictly need a cryptographic |
michael@0 | 119 | // hash function, because there is negligible value in attacking the hash. |
michael@0 | 120 | // We use SHA-1 because it's *shorter* -- the exact same reason that Git |
michael@0 | 121 | // chose SHA-1. |
michael@0 | 122 | hasher = MessageDigest.getInstance("SHA-1"); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | @Override |
michael@0 | 126 | public void append(String s) { |
michael@0 | 127 | try { |
michael@0 | 128 | hasher.update(((s == null) ? "null" : s).getBytes("UTF-8")); |
michael@0 | 129 | } catch (UnsupportedEncodingException e) { |
michael@0 | 130 | // This can never occur. Thanks, Java. |
michael@0 | 131 | } |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | @Override |
michael@0 | 135 | public void append(int profileCreation) { |
michael@0 | 136 | append(Integer.toString(profileCreation, 10)); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | @Override |
michael@0 | 140 | public String toString() { |
michael@0 | 141 | // We *could* use ASCII85… but the savings would be negated by the |
michael@0 | 142 | // inclusion of JSON-unsafe characters like double-quote. |
michael@0 | 143 | return new Base64(-1, null, false).encodeAsString(hasher.digest()); |
michael@0 | 144 | } |
michael@0 | 145 | } |
michael@0 | 146 | } |