Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | package org.mozilla.gecko.sync.jpake; |
michael@0 | 6 | |
michael@0 | 7 | import java.io.UnsupportedEncodingException; |
michael@0 | 8 | import java.math.BigInteger; |
michael@0 | 9 | import java.security.GeneralSecurityException; |
michael@0 | 10 | import java.security.InvalidKeyException; |
michael@0 | 11 | import java.security.MessageDigest; |
michael@0 | 12 | import java.security.NoSuchAlgorithmException; |
michael@0 | 13 | |
michael@0 | 14 | import javax.crypto.Mac; |
michael@0 | 15 | import javax.crypto.spec.SecretKeySpec; |
michael@0 | 16 | |
michael@0 | 17 | import org.mozilla.gecko.background.common.log.Logger; |
michael@0 | 18 | import org.mozilla.gecko.sync.crypto.HKDF; |
michael@0 | 19 | import org.mozilla.gecko.sync.crypto.KeyBundle; |
michael@0 | 20 | |
michael@0 | 21 | public class JPakeCrypto { |
michael@0 | 22 | private static final String LOG_TAG = "JPakeCrypto"; |
michael@0 | 23 | |
michael@0 | 24 | /* |
michael@0 | 25 | * Primes P and Q, and generator G - from original Mozilla J-PAKE |
michael@0 | 26 | * implementation. |
michael@0 | 27 | */ |
michael@0 | 28 | public static final BigInteger P = new BigInteger( |
michael@0 | 29 | "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + |
michael@0 | 30 | "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" + |
michael@0 | 31 | "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" + |
michael@0 | 32 | "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" + |
michael@0 | 33 | "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" + |
michael@0 | 34 | "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" + |
michael@0 | 35 | "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" + |
michael@0 | 36 | "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" + |
michael@0 | 37 | "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" + |
michael@0 | 38 | "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" + |
michael@0 | 39 | "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" + |
michael@0 | 40 | "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", |
michael@0 | 41 | 16); |
michael@0 | 42 | |
michael@0 | 43 | public static final BigInteger Q = new BigInteger( |
michael@0 | 44 | "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", |
michael@0 | 45 | 16); |
michael@0 | 46 | |
michael@0 | 47 | public static final BigInteger G = new BigInteger( |
michael@0 | 48 | "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" + |
michael@0 | 49 | "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" + |
michael@0 | 50 | "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" + |
michael@0 | 51 | "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" + |
michael@0 | 52 | "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" + |
michael@0 | 53 | "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" + |
michael@0 | 54 | "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" + |
michael@0 | 55 | "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" + |
michael@0 | 56 | "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" + |
michael@0 | 57 | "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" + |
michael@0 | 58 | "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" + |
michael@0 | 59 | "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", |
michael@0 | 60 | 16); |
michael@0 | 61 | |
michael@0 | 62 | /** |
michael@0 | 63 | * |
michael@0 | 64 | * Round 1 of J-PAKE protocol. |
michael@0 | 65 | * Generate x1, x2, and ZKP for other party. |
michael@0 | 66 | */ |
michael@0 | 67 | public static void round1(JPakeParty jp, JPakeNumGenerator gen) throws NoSuchAlgorithmException, UnsupportedEncodingException { |
michael@0 | 68 | // Randomly select x1 from [0,q), x2 from [1,q). |
michael@0 | 69 | BigInteger x1 = gen.generateFromRange(Q); // [0, q) |
michael@0 | 70 | BigInteger x2 = jp.x2 = BigInteger.ONE.add(gen.generateFromRange(Q |
michael@0 | 71 | .subtract(BigInteger.ONE))); // [1, q) |
michael@0 | 72 | |
michael@0 | 73 | BigInteger gx1 = G.modPow(x1, P); |
michael@0 | 74 | BigInteger gx2 = G.modPow(x2, P); |
michael@0 | 75 | |
michael@0 | 76 | jp.gx1 = gx1; |
michael@0 | 77 | jp.gx2 = gx2; |
michael@0 | 78 | |
michael@0 | 79 | // Generate and store zero knowledge proofs. |
michael@0 | 80 | jp.zkp1 = createZkp(G, x1, gx1, jp.signerId, gen); |
michael@0 | 81 | jp.zkp2 = createZkp(G, x2, gx2, jp.signerId, gen); |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | /** |
michael@0 | 85 | * Round 2 of J-PAKE protocol. |
michael@0 | 86 | * Generate A and ZKP for A. |
michael@0 | 87 | * Verify ZKP from other party. Does not check for replay ZKP. |
michael@0 | 88 | */ |
michael@0 | 89 | public static void round2(BigInteger secretValue, JPakeParty jp, JPakeNumGenerator gen) |
michael@0 | 90 | throws IncorrectZkpException, NoSuchAlgorithmException, |
michael@0 | 91 | Gx3OrGx4IsZeroOrOneException, UnsupportedEncodingException { |
michael@0 | 92 | |
michael@0 | 93 | Logger.debug(LOG_TAG, "round2 started."); |
michael@0 | 94 | |
michael@0 | 95 | // checkZkp does some additional checks, but we can throw a more informative exception here. |
michael@0 | 96 | if (BigInteger.ZERO.compareTo(jp.gx3) == 0 || BigInteger.ONE.compareTo(jp.gx3) == 0 || |
michael@0 | 97 | BigInteger.ZERO.compareTo(jp.gx4) == 0 || BigInteger.ONE.compareTo(jp.gx4) == 0) { |
michael@0 | 98 | throw new Gx3OrGx4IsZeroOrOneException(); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | // Check ZKP. |
michael@0 | 102 | checkZkp(G, jp.gx3, jp.zkp3); |
michael@0 | 103 | checkZkp(G, jp.gx4, jp.zkp4); |
michael@0 | 104 | |
michael@0 | 105 | // Compute a = g^[(x1+x3+x4)*(x2*secret)]. |
michael@0 | 106 | BigInteger y1 = jp.gx3.multiply(jp.gx4).mod(P).multiply(jp.gx1).mod(P); |
michael@0 | 107 | BigInteger y2 = jp.x2.multiply(secretValue).mod(P); |
michael@0 | 108 | |
michael@0 | 109 | BigInteger a = y1.modPow(y2, P); |
michael@0 | 110 | jp.thisZkpA = createZkp(y1, y2, a, jp.signerId, gen); |
michael@0 | 111 | jp.thisA = a; |
michael@0 | 112 | |
michael@0 | 113 | Logger.debug(LOG_TAG, "round2 finished."); |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | /** |
michael@0 | 117 | * Final round of J-PAKE protocol. |
michael@0 | 118 | */ |
michael@0 | 119 | public static KeyBundle finalRound(BigInteger secretValue, JPakeParty jp) |
michael@0 | 120 | throws IncorrectZkpException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { |
michael@0 | 121 | Logger.debug(LOG_TAG, "Final round started."); |
michael@0 | 122 | BigInteger gb = jp.gx1.multiply(jp.gx2).mod(P).multiply(jp.gx3) |
michael@0 | 123 | .mod(P); |
michael@0 | 124 | checkZkp(gb, jp.otherA, jp.otherZkpA); |
michael@0 | 125 | |
michael@0 | 126 | // Calculate shared key g^(x1+x3)*x2*x4*secret, which is equivalent to |
michael@0 | 127 | // (B/g^(x2*x4*s))^x2 = (B*(g^x4)^x2^s^-1)^2. |
michael@0 | 128 | BigInteger k = jp.gx4.modPow(jp.x2.multiply(secretValue).negate().mod(Q), P).multiply(jp.otherA) |
michael@0 | 129 | .modPow(jp.x2, P); |
michael@0 | 130 | |
michael@0 | 131 | byte[] enc = new byte[32]; |
michael@0 | 132 | byte[] hmac = new byte[32]; |
michael@0 | 133 | generateKeyAndHmac(k, enc, hmac); |
michael@0 | 134 | |
michael@0 | 135 | Logger.debug(LOG_TAG, "Final round finished; returning key."); |
michael@0 | 136 | return new KeyBundle(enc, hmac); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | // TODO Replace this function with the one in the crypto library |
michael@0 | 140 | private static byte[] HMACSHA256(byte[] data, byte[] key) { |
michael@0 | 141 | byte[] result = null; |
michael@0 | 142 | try { |
michael@0 | 143 | Mac hmacSha256; |
michael@0 | 144 | hmacSha256 = Mac.getInstance("HmacSHA256"); |
michael@0 | 145 | SecretKeySpec secret_key = new SecretKeySpec(key, "HmacSHA256"); |
michael@0 | 146 | hmacSha256.init(secret_key); |
michael@0 | 147 | result = hmacSha256.doFinal(data); |
michael@0 | 148 | } catch (GeneralSecurityException e) { |
michael@0 | 149 | Logger.error(LOG_TAG, "Got exception calculating HMAC.", e); |
michael@0 | 150 | } |
michael@0 | 151 | return result; |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | /* Helper Methods */ |
michael@0 | 155 | |
michael@0 | 156 | /* |
michael@0 | 157 | * Generate the ZKP b = r - x*h, and g^r, where h = hash(g, g^r, g^x, id). (We |
michael@0 | 158 | * pass in gx to save on an exponentiation of g^x) |
michael@0 | 159 | */ |
michael@0 | 160 | private static Zkp createZkp(BigInteger g, BigInteger x, BigInteger gx, |
michael@0 | 161 | String id, JPakeNumGenerator gen) throws NoSuchAlgorithmException, UnsupportedEncodingException { |
michael@0 | 162 | // Generate random r for exponent. |
michael@0 | 163 | BigInteger r = gen.generateFromRange(Q); |
michael@0 | 164 | |
michael@0 | 165 | // Calculate g^r for ZKP. |
michael@0 | 166 | BigInteger gr = g.modPow(r, P); |
michael@0 | 167 | |
michael@0 | 168 | // Calculate the ZKP b value = (r-x*h) % q. |
michael@0 | 169 | BigInteger h = computeBHash(g, gr, gx, id); |
michael@0 | 170 | Logger.debug(LOG_TAG, "myhash: " + h.toString(16)); |
michael@0 | 171 | |
michael@0 | 172 | // ZKP value = b = r-x*h |
michael@0 | 173 | BigInteger b = r.subtract(x.multiply(h)).mod(Q); |
michael@0 | 174 | |
michael@0 | 175 | return new Zkp(gr, b, id); |
michael@0 | 176 | } |
michael@0 | 177 | |
michael@0 | 178 | /* |
michael@0 | 179 | * Verify ZKP. |
michael@0 | 180 | */ |
michael@0 | 181 | private static void checkZkp(BigInteger g, BigInteger gx, Zkp zkp) |
michael@0 | 182 | throws IncorrectZkpException, NoSuchAlgorithmException, UnsupportedEncodingException { |
michael@0 | 183 | |
michael@0 | 184 | BigInteger h = computeBHash(g, zkp.gr, gx, zkp.id); |
michael@0 | 185 | |
michael@0 | 186 | // Check parameters of zkp, and compare to computed hash. These shouldn't |
michael@0 | 187 | // fail. |
michael@0 | 188 | if (gx.compareTo(BigInteger.ONE) < 1) { // g^x > 1. |
michael@0 | 189 | Logger.error(LOG_TAG, "g^x > 1 fails."); |
michael@0 | 190 | throw new IncorrectZkpException(); |
michael@0 | 191 | } |
michael@0 | 192 | if (gx.compareTo(P.subtract(BigInteger.ONE)) > -1) { // g^x < p-1 |
michael@0 | 193 | Logger.error(LOG_TAG, "g^x < p-1 fails."); |
michael@0 | 194 | throw new IncorrectZkpException(); |
michael@0 | 195 | } |
michael@0 | 196 | if (gx.modPow(Q, P).compareTo(BigInteger.ONE) != 0) { |
michael@0 | 197 | Logger.error(LOG_TAG, "g^x^q % p = 1 fails."); |
michael@0 | 198 | throw new IncorrectZkpException(); |
michael@0 | 199 | } |
michael@0 | 200 | if (zkp.gr.compareTo(g.modPow(zkp.b, P).multiply(gx.modPow(h, P)).mod(P)) != 0) { |
michael@0 | 201 | // b = r-h*x ==> g^r = g^b*g^x^(h) |
michael@0 | 202 | Logger.debug(LOG_TAG, "gb*g(xh) = " + g.modPow(zkp.b, P).multiply(gx.modPow(h, P)).mod(P).toString(16)); |
michael@0 | 203 | Logger.debug(LOG_TAG, "gr = " + zkp.gr.toString(16)); |
michael@0 | 204 | Logger.debug(LOG_TAG, "b = " + zkp.b.toString(16)); |
michael@0 | 205 | Logger.debug(LOG_TAG, "g^b = " + g.modPow(zkp.b, P).toString(16)); |
michael@0 | 206 | Logger.debug(LOG_TAG, "g^(xh) = " + gx.modPow(h, P).toString(16)); |
michael@0 | 207 | Logger.debug(LOG_TAG, "gx = " + gx.toString(16)); |
michael@0 | 208 | Logger.debug(LOG_TAG, "h = " + h.toString(16)); |
michael@0 | 209 | Logger.error(LOG_TAG, "zkp calculation incorrect."); |
michael@0 | 210 | throw new IncorrectZkpException(); |
michael@0 | 211 | } |
michael@0 | 212 | Logger.debug(LOG_TAG, "*** ZKP SUCCESS ***"); |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | /* |
michael@0 | 216 | * Use SHA-256 to compute a BigInteger hash of g, gr, gx values with |
michael@0 | 217 | * mySignerId to prevent replay. Does not make a twos-complement BigInteger |
michael@0 | 218 | * form hash. |
michael@0 | 219 | */ |
michael@0 | 220 | private static BigInteger computeBHash(BigInteger g, BigInteger gr, BigInteger gx, |
michael@0 | 221 | String id) throws NoSuchAlgorithmException, UnsupportedEncodingException { |
michael@0 | 222 | MessageDigest sha = MessageDigest.getInstance("SHA-256"); |
michael@0 | 223 | sha.reset(); |
michael@0 | 224 | |
michael@0 | 225 | /* |
michael@0 | 226 | * Note: you should ensure the items in H(...) have clear boundaries. It |
michael@0 | 227 | * is simple if the other party knows sizes of g, gr, gx and signerID and |
michael@0 | 228 | * hence the boundary is unambiguous. If not, you'd better prepend each |
michael@0 | 229 | * item with its byte length, but I've omitted that here. |
michael@0 | 230 | */ |
michael@0 | 231 | |
michael@0 | 232 | hashByteArrayWithLength(sha, BigIntegerHelper.BigIntegerToByteArrayWithoutSign(g)); |
michael@0 | 233 | hashByteArrayWithLength(sha, BigIntegerHelper.BigIntegerToByteArrayWithoutSign(gr)); |
michael@0 | 234 | hashByteArrayWithLength(sha, BigIntegerHelper.BigIntegerToByteArrayWithoutSign(gx)); |
michael@0 | 235 | hashByteArrayWithLength(sha, id.getBytes("UTF-8")); |
michael@0 | 236 | |
michael@0 | 237 | byte[] hash = sha.digest(); |
michael@0 | 238 | |
michael@0 | 239 | return BigIntegerHelper.ByteArrayToBigIntegerWithoutSign(hash); |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | /* |
michael@0 | 243 | * Update a hash with a byte array's length and the byte array. |
michael@0 | 244 | */ |
michael@0 | 245 | private static void hashByteArrayWithLength(MessageDigest sha, byte[] data) { |
michael@0 | 246 | int length = data.length; |
michael@0 | 247 | byte[] b = new byte[] { (byte) (length >>> 8), (byte) (length & 0xff) }; |
michael@0 | 248 | sha.update(b); |
michael@0 | 249 | sha.update(data); |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | /* |
michael@0 | 253 | * Helper function to generate encryption key and HMAC from a byte array. |
michael@0 | 254 | */ |
michael@0 | 255 | public static void generateKeyAndHmac(BigInteger k, byte[] encOut, byte[] hmacOut) throws NoSuchAlgorithmException, InvalidKeyException { |
michael@0 | 256 | // Generate HMAC and Encryption keys from synckey. |
michael@0 | 257 | byte[] zerokey = new byte[32]; |
michael@0 | 258 | byte[] prk = HMACSHA256(BigIntegerHelper.BigIntegerToByteArrayWithoutSign(k), zerokey); |
michael@0 | 259 | |
michael@0 | 260 | byte[] okm = HKDF.hkdfExpand(prk, HKDF.HMAC_INPUT, 32 * 2); |
michael@0 | 261 | System.arraycopy(okm, 0, encOut, 0, 32); |
michael@0 | 262 | System.arraycopy(okm, 32, hmacOut, 0, 32); |
michael@0 | 263 | } |
michael@0 | 264 | } |