diff options
author | Parménides GV <parmegv@sdf.org> | 2013-04-03 19:34:40 +0200 |
---|---|---|
committer | Parménides GV <parmegv@sdf.org> | 2013-04-03 19:34:40 +0200 |
commit | 3e9a68fcc6c16be69abfa27d5fd3a2cbfc620bb0 (patch) | |
tree | eee38b81b77877de0524112b0636eb8ec3d50850 | |
parent | 7af9519591ea481718b4f903b97463250cc5f116 (diff) |
Fixed bug #2146 => A calculation is now fine. Next step: fix M1
calculation, since right now (using tests) response() method is not
doing OK.
Added new SRPSession modifying response() method from JBoss SRP
implementation.
Added hosts-for-android-emulator. Use with the following commands to be
able to test on api.lvh.me:
adb shell mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system
adb push ~/workspace/leap_android/hosts-for-android-emulator
/system/etc/hosts
-rw-r--r-- | hosts-for-android-emulator | 9 | ||||
-rw-r--r-- | src/se/leap/leapclient/LeapHttpClient.java | 8 | ||||
-rw-r--r-- | src/se/leap/leapclient/LeapSRPSession.java | 193 | ||||
-rw-r--r-- | src/se/leap/leapclient/ProviderAPI.java | 99 |
4 files changed, 249 insertions, 60 deletions
diff --git a/hosts-for-android-emulator b/hosts-for-android-emulator new file mode 100644 index 00000000..b10103ef --- /dev/null +++ b/hosts-for-android-emulator @@ -0,0 +1,9 @@ +127.0.0.1 localhost +10.0.2.2 api.lvh.me + +# The following lines are desirable for IPv6 capable hosts +::1 ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters diff --git a/src/se/leap/leapclient/LeapHttpClient.java b/src/se/leap/leapclient/LeapHttpClient.java index 9e1a541b..fd6db745 100644 --- a/src/se/leap/leapclient/LeapHttpClient.java +++ b/src/se/leap/leapclient/LeapHttpClient.java @@ -15,6 +15,8 @@ import android.content.Context; public class LeapHttpClient extends DefaultHttpClient { final Context context; + + private static LeapHttpClient client; public LeapHttpClient(Context context) { this.context = context; @@ -55,4 +57,10 @@ public class LeapHttpClient extends DefaultHttpClient { throw new AssertionError(e); } } + + public static LeapHttpClient getInstance(Context context) { + if(client == null) + client = new LeapHttpClient(context); + return client; + } } diff --git a/src/se/leap/leapclient/LeapSRPSession.java b/src/se/leap/leapclient/LeapSRPSession.java new file mode 100644 index 00000000..1d1f0c9d --- /dev/null +++ b/src/se/leap/leapclient/LeapSRPSession.java @@ -0,0 +1,193 @@ +package se.leap.leapclient; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import org.jboss.security.Util; +import org.jboss.security.srp.SRPClientSession; +import org.jboss.security.srp.SRPParameters; +import org.jboss.security.srp.SRPPermission; + +public class LeapSRPSession { + + private SRPParameters params; + private BigInteger N; + private BigInteger g; + private BigInteger x; + private BigInteger v; + private byte[] s; + private BigInteger a; + private BigInteger A; + private byte[] K; + /** The M1 = H(H(N) xor H(g) | H(U) | s | A | B | K) hash */ + private MessageDigest clientHash; + /** The M2 = H(A | M | K) hash */ + private MessageDigest serverHash; + + private static int A_LEN; + + /** Creates a new SRP server session object from the username, password + verifier, + @param username, the user ID + @param password, the user clear text password + @param params, the SRP parameters for the session + */ + public LeapSRPSession(String username, char[] password, SRPParameters params) + { + this(username, password, params, null); + } + + /** Creates a new SRP server session object from the username, password + verifier, + @param username, the user ID + @param password, the user clear text password + @param params, the SRP parameters for the session + @param abytes, the random exponent used in the A public key. This must be + 8 bytes in length. + */ + public LeapSRPSession(String username, char[] password, SRPParameters params, + byte[] abytes) + { + try + { + // Initialize the secure random number and message digests + Util.init(); + } + catch(NoSuchAlgorithmException e) + { + } + this.params = params; + this.g = new BigInteger(1, params.g); + this.N = new BigInteger(1, params.N); + if( abytes != null ) + { + A_LEN = 8*abytes.length; + /* TODO Why did they put this condition? + if( 8*abytes.length != A_LEN ) + throw new IllegalArgumentException("The abytes param must be " + +(A_LEN/8)+" in length, abytes.length="+abytes.length); + */ + this.a = new BigInteger(abytes); + } + + // Calculate x = H(s | H(U | ':' | password)) + byte[] xb = Util.calculatePasswordHash(username, password, params.s); + this.x = new BigInteger(1, xb); + this.v = g.modPow(x, N); // g^x % N + + serverHash = Util.newDigest(); + clientHash = Util.newDigest(); + // H(N) + byte[] hn = Util.newDigest().digest(params.N); + // H(g) + byte[] hg = Util.newDigest().digest(params.g); + // clientHash = H(N) xor H(g) + byte[] hxg = Util.xor(hn, hg, 20); + clientHash.update(hxg); + // clientHash = H(N) xor H(g) | H(U) + clientHash.update(Util.newDigest().digest(username.getBytes())); + // clientHash = H(N) xor H(g) | H(U) | s + clientHash.update(params.s); + K = null; + } + + /** + * @returns The exponential residue (parameter A) to be sent to the server. + */ + public byte[] exponential() + { + byte[] Abytes = null; + if(A == null) + { + /* If the random component of A has not been specified use a random + number */ + if( a == null ) + { + BigInteger one = BigInteger.ONE; + do + { + a = new BigInteger(A_LEN, Util.getPRNG()); + } while(a.compareTo(one) <= 0); + } + A = g.modPow(a, N); + Abytes = Util.trim(A.toByteArray()); + // clientHash = H(N) xor H(g) | H(U) | A + clientHash.update(Abytes); + // serverHash = A + serverHash.update(Abytes); + } + return Abytes; + } + + public byte[] response(byte[] Bbytes) throws NoSuchAlgorithmException { + // clientHash = H(N) xor H(g) | H(U) | s | A | B + clientHash.update(Bbytes); + + /* + var B = new BigInteger(ephemeral, 16); + var Bstr = ephemeral; + // u = H(A,B) + var u = new BigInteger(SHA256(hex2a(Astr + Bstr)), 16); + // x = H(s, H(I:p)) + var x = this.calcX(salt); + //S = (B - kg^x) ^ (a + ux) + var kgx = k.multiply(g.modPow(x, N)); + var aux = a.add(u.multiply(x)); + S = B.subtract(kgx).modPow(aux, N); + K = SHA256(hex2a(S.toString(16))); + */ + byte[] hA = Util.newDigest().digest(A.toByteArray()); + MessageDigest u_digest = Util.newDigest(); + u_digest.update(A.toByteArray()); + u_digest.update(Bbytes); + clientHash.update(u_digest.digest()); + byte[] ub = new BigInteger(clientHash.digest()).toByteArray(); + // Calculate S = (B - g^x) ^ (a + u * x) % N + BigInteger B = new BigInteger(1, Bbytes); + if( B.compareTo(v) < 0 ) + B = B.add(N); + BigInteger u = new BigInteger(1, ub); + BigInteger B_v = B.subtract(v); + BigInteger a_ux = a.add(u.multiply(x)); + BigInteger S = B_v.modPow(a_ux, N); + // K = SessionHash(S) + MessageDigest sessionDigest = MessageDigest.getInstance(params.hashAlgorithm); + K = sessionDigest.digest(S.toByteArray()); + // clientHash = H(N) xor H(g) | H(U) | A | B | K + clientHash.update(K); + byte[] M1 = clientHash.digest(); + return M1; + } + + + /** + * @param M2 The server's response to the client's challenge + * @returns True if and only if the server's response was correct. + */ + public boolean verify(byte[] M2) + { + // M2 = H(A | M1 | K) + byte[] myM2 = serverHash.digest(); + boolean valid = Arrays.equals(M2, myM2); + return valid; + } + + /** Returns the negotiated session K, K = SHA_Interleave(S) + @return the private session K byte[] + @throws SecurityException - if the current thread does not have an + getSessionKey SRPPermission. + */ + public byte[] getSessionKey() throws SecurityException + { + SecurityManager sm = System.getSecurityManager(); + if( sm != null ) + { + SRPPermission p = new SRPPermission("getSessionKey"); + sm.checkPermission(p); + } + return K; + } + +} diff --git a/src/se/leap/leapclient/ProviderAPI.java b/src/se/leap/leapclient/ProviderAPI.java index b8d6a765..ea1410ad 100644 --- a/src/se/leap/leapclient/ProviderAPI.java +++ b/src/se/leap/leapclient/ProviderAPI.java @@ -3,7 +3,6 @@ package se.leap.leapclient; import java.io.IOException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.List; import java.util.Scanner; @@ -13,15 +12,16 @@ import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.protocol.ClientContext; import org.apache.http.cookie.Cookie; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.jboss.security.srp.SRPClientSession; +import org.jboss.security.srp.SRPParameters; import org.json.JSONException; import org.json.JSONObject; -import com.jordanzimmerman.SRPClientSession; -import com.jordanzimmerman.SRPConstants; -import com.jordanzimmerman.SRPFactory; - import se.leap.leapclient.ProviderListContent.ProviderItem; import android.app.IntentService; @@ -121,22 +121,31 @@ public class ProviderAPI extends IntentService { String username = (String) task.get(ConfigHelper.username_key); String password = (String) task.get(ConfigHelper.password_key); String authentication_server = (String) task.get(ConfigHelper.srp_server_url_key); + + String salt = "abcd"; - SRPConstants constants = new SRPConstants(new BigInteger(ConfigHelper.NG_1024, 16), ConfigHelper.g); - SRPFactory factory = SRPFactory.getInstance(constants); - SRPClientSession session = factory.newClientSession(password.getBytes()); - session.setSalt_s(new BigInteger("1")); - byte[] A = session.getPublicKey_A().toString(16).getBytes(); + SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + //SRPClientSession client = new SRPClientSession(username, password.toCharArray(), params); + LeapSRPSession client = new LeapSRPSession(username, password.toCharArray(), params); + byte[] A = client.exponential(); try { - JSONObject saltAndB = sendAToSRPServer(authentication_server, username, getHexString(A)); - String B = saltAndB.getString("B"); - String salt = saltAndB.getString("salt"); - session.setSalt_s(new BigInteger(salt, 16)); - session.setServerPublicKey_B(new BigInteger(B, 16)); - byte[] M1 = session.getEvidenceValue_M1().toString(16).getBytes(); - byte[] M2 = sendM1ToSRPServer(authentication_server, username, M1); - session.validateServerEvidenceValue_M2(new BigInteger(getHexString(M2), 16)); - return true; + JSONObject saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(A).toString(16)); + if(saltAndB.length() > 0) { + byte[] B = saltAndB.getString("B").getBytes(); + salt = saltAndB.getString("salt"); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + //client = new SRPClientSession(username, password.toCharArray(), params); + client = new LeapSRPSession(username, password.toCharArray(), params); + A = client.exponential(); + saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(A).toString(16)); + String Bhex = saltAndB.getString("B"); + byte[] M1 = client.response(new BigInteger(Bhex, 16).toByteArray()); + byte[] M2 = sendM1ToSRPServer(authentication_server, username, M1); + if( client.verify(M2) == false ) + throw new SecurityException("Failed to validate server reply"); + return true; + } + else return false; } catch (ClientProtocolException e1) { // TODO Auto-generated catch block e1.printStackTrace(); @@ -149,25 +158,15 @@ public class ProviderAPI extends IntentService { // TODO Auto-generated catch block e1.printStackTrace(); return false; + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; } } - - public static String getHexString(byte[] b) { - String result = ""; - StringBuffer sb = new StringBuffer(); - for (byte tmp : b) { - sb.append(Integer.toHexString((int) (tmp & 0xff))); - } - return sb.toString(); -// for (int i=0; i < b.length; i++) { -// result += -// Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 ); -// } -// return result; - } private JSONObject sendAToSRPServer(String server_url, String username, String clientA) throws ClientProtocolException, IOException, JSONException { - DefaultHttpClient client = new LeapHttpClient(getApplicationContext()); + DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); String parameter_chain = "A" + "=" + clientA + "&" + "login" + "=" + username; HttpPost post = new HttpPost(server_url + "/sessions.json" + "?" + parameter_chain); @@ -185,31 +184,14 @@ public class ProviderAPI extends IntentService { return json_response; } - private byte[] sendAToSRPServer(String server_url, String username, byte[] clientA) throws ClientProtocolException, IOException, JSONException { - DefaultHttpClient client = new LeapHttpClient(getApplicationContext()); - String parameter_chain = "A" + "=" + getHexString(clientA) + "&" + "login" + "=" + username; - HttpPost post = new HttpPost(server_url + "/sessions.json" + "?" + parameter_chain); - - HttpResponse getResponse = client.execute(post); - HttpEntity responseEntity = getResponse.getEntity(); - String plain_response = new Scanner(responseEntity.getContent()).useDelimiter("\\A").next(); - JSONObject json_response = new JSONObject(plain_response); - if(!json_response.isNull("errors") || json_response.has("errors")) { - return new byte[0]; - } - List<Cookie> cookies = client.getCookieStore().getCookies(); - if(!cookies.isEmpty()) { - String session_id = cookies.get(0).getValue(); - } - return json_response.getString("B").getBytes(); - } - private byte[] sendM1ToSRPServer(String server_url, String username, byte[] m1) throws ClientProtocolException, IOException, JSONException { - DefaultHttpClient client = new LeapHttpClient(getApplicationContext()); - String parameter_chain = "client_auth" + "=" + getHexString(m1); + DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); + String parameter_chain = "client_auth" + "=" + new BigInteger(m1).toString(16); HttpPut put = new HttpPut(server_url + "/sessions/" + username +".json" + "?" + parameter_chain); + HttpContext localContext = new BasicHttpContext(); + localContext.setAttribute(ClientContext.COOKIE_STORE, client.getCookieStore()); - HttpResponse getResponse = client.execute(put); + HttpResponse getResponse = client.execute(put, localContext); HttpEntity responseEntity = getResponse.getEntity(); String plain_response = new Scanner(responseEntity.getContent()).useDelimiter("\\A").next(); JSONObject json_response = new JSONObject(plain_response); @@ -217,9 +199,6 @@ public class ProviderAPI extends IntentService { return new byte[0]; } - List<Cookie> cookies = client.getCookieStore().getCookies(); - String session_id = cookies.get(0).getValue(); - return json_response.getString("M2").getBytes(); } @@ -231,7 +210,7 @@ public class ProviderAPI extends IntentService { String json_file_content = ""; - DefaultHttpClient client = new LeapHttpClient(getApplicationContext()); + DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); HttpGet get = new HttpGet(string_url); // Execute the GET call and obtain the response HttpResponse getResponse = client.execute(get); |