package org.emergent.android.weave.client; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; import java.security.Key; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.crypto.spec.SecretKeySpec; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class UserWeave { public enum CollectionNode { STORAGE_BOOKMARKS("bookmarks"), STORAGE_PASSWORDS("passwords"), ; public final String engineName; public final String nodePath; CollectionNode(String engineName) { this.engineName = engineName; this.nodePath = "/storage/" + this.engineName; } } public enum HashNode { INFO_COLLECTIONS(false, "/info/collections"), META_GLOBAL(false, "/storage/meta/global"), ; public final boolean userServer; public final String nodePath; HashNode(boolean userServer, String path) { this.userServer = userServer; this.nodePath = path; } } protected static URI buildSyncUriFromSubpath(URI clusterUri, String userId, QueryParams params, String pathSection) { String subpath = pathSection; if (params != null) subpath = subpath + params.toQueryString(); WeaveUtil.checkNull(clusterUri); WeaveUtil.UriBuilder builder = WeaveUtil.buildUpon(clusterUri); builder.appendEncodedPath(WeaveConstants.WEAVE_API_VERSION + "/" + userId); while (subpath.startsWith("/")) { subpath = subpath.substring(1); } builder.appendEncodedPath(subpath); return builder.build(); } protected static URI buildSyncUriFromSubpath(URI clusterUri, String userId, String subpath) { return buildSyncUriFromSubpath(clusterUri, userId, null, subpath); } protected static URI buildUserUriFromSubpath(URI authUri, String userId, String subpath) { WeaveUtil.checkNull(authUri); WeaveUtil.UriBuilder builder = WeaveUtil.buildUpon(authUri); builder.appendEncodedPath("user/" + WeaveConstants.WEAVE_API_VERSION + "/" + userId); while (subpath.startsWith("/")) { subpath = subpath.substring(1); } builder.appendEncodedPath(subpath); return builder.build(); } private final WeaveTransport m_transport; private final URI m_authUri; @SuppressWarnings("unused") private final String m_userId; private final String m_password; private final String m_legalUsername; private final AtomicReference<URI> m_clusterUri; UserWeave(WeaveTransport transport, URI authUri, String userId, String password) { this(transport, authUri, userId, password, null); } protected UserWeave(WeaveTransport transport, URI authUri, String userId, String password, URI clusterUri) { m_authUri = authUri; m_userId = userId; m_legalUsername = WeaveCryptoUtil.getInstance() .legalizeUsername(userId); m_password = password; m_transport = transport; m_clusterUri = new AtomicReference<URI>(clusterUri); } public void authenticate() throws WeaveException { JSONObject jsonObj = getNode(HashNode.INFO_COLLECTIONS).getValue(); jsonObj.has("foo"); } public void authenticateSecret(char[] secret) throws WeaveException { authenticate(); } public URI buildSyncUriFromSubpath(String subpath) throws WeaveException { return buildSyncUriFromSubpath(getClusterUri(), getLegalUsername(), subpath); } public URI buildUserUriFromSubpath(String subpath) { return buildUserUriFromSubpath(m_authUri, getLegalUsername(), subpath); } public boolean checkUsernameAvailable() throws WeaveException { try { String nodePath = "/"; String nodeStrVal = getUserNode(nodePath).getBody(); return Integer.parseInt(nodeStrVal) == 0; } catch (NumberFormatException e) { throw new WeaveException(e); } } protected BulkKeyCouplet getBulkKeyPair(byte[] syncKey) throws GeneralSecurityException, WeaveException { try { byte[] keyBytes = WeaveCryptoUtil.deriveSyncKey(syncKey, getLegalUsername()); Key bulkKey = new SecretKeySpec(keyBytes, "AES"); byte[] hmkeyBytes = WeaveCryptoUtil.deriveSyncHmacKey(syncKey, keyBytes, getLegalUsername()); Key hmbulkKey = new SecretKeySpec(hmkeyBytes, "AES"); JSONObject ckwbojsonobj = getCryptoKeys(); WeaveBasicObject.WeaveEncryptedObject weo = new WeaveBasicObject.WeaveEncryptedObject( ckwbojsonobj); JSONObject ckencPayload = weo.decryptObject(bulkKey, hmbulkKey); JSONArray jsonArray = ckencPayload.getJSONArray("default"); String bkey2str = jsonArray.getString(0); String bhmac2str = jsonArray.getString(1); byte[] bkey2bytes = Base64.decode(bkey2str); Key bulkKey2 = new SecretKeySpec(bkey2bytes, "AES"); byte[] bhmac2bytes = Base64.decode(bhmac2str); Key bulkHmacKey2 = new SecretKeySpec(bhmac2bytes, "AES"); return new BulkKeyCouplet(bulkKey2, bulkHmacKey2); } catch (JSONException e) { throw new WeaveException(e); } } public final URI getClusterUri() throws WeaveException { return getClusterUri(true); } public final URI getClusterUri(boolean useCache) throws WeaveException { URI cached = null; if (useCache && ((cached = m_clusterUri.get()) != null)) return cached; URI retval = getClusterUriSafe(); m_clusterUri.compareAndSet(cached, retval); return retval; } private URI getClusterUriSafe() { URI retval = m_authUri; try { URI unsafeResult = getClusterUriUnsafe(); if (unsafeResult != null) retval = unsafeResult; } catch (Exception ignored) { // Dbg.v(e); } return retval; } private URI getClusterUriUnsafe() throws WeaveException { try { String nodePath = "/node/weave"; String nodeWeaveVal = getUserNode(nodePath).getBody(); return new URI(nodeWeaveVal); } catch (URISyntaxException e) { throw new WeaveException(e); } } protected JSONObject getCryptoKeys() throws WeaveException { try { URI nodeUri = buildSyncUriFromSubpath("/storage/crypto/keys"); WeaveBasicObject nodeObj = new WeaveBasicObject(nodeUri, new JSONObject(getNode(nodeUri).getBody())); return nodeObj.getPayload(); } catch (JSONException e) { throw new WeaveException(e); } } public String getLegalUsername() { return m_legalUsername; } public QueryResult<JSONObject> getNode(HashNode node) throws WeaveException { try { URI nodeUri = node.userServer ? buildUserUriFromSubpath(node.nodePath) : buildSyncUriFromSubpath(node.nodePath); WeaveResponse result = getNode(nodeUri); return new QueryResult<JSONObject>(result, new JSONObject( result.getBody())); } catch (JSONException e) { throw new WeaveException(e); } } protected final WeaveResponse getNode(URI nodeUri) throws WeaveException { try { return m_transport.execGetMethod(getLegalUsername(), m_password, nodeUri); } catch (IOException e) { throw new WeaveException(e); } } protected RSAPublicKey getPublicKey() throws WeaveException { try { URI nodeUri = buildSyncUriFromSubpath("/storage/keys/pubkey"); WeaveBasicObject nodeObj = new WeaveBasicObject(nodeUri, new JSONObject(getNode(nodeUri).getBody())); JSONObject payloadObj = nodeObj.getPayload(); String pubKey = payloadObj.getString("keyData"); return WeaveCryptoUtil.getInstance().readCertificatePubKey(pubKey); } catch (GeneralSecurityException e) { throw new WeaveException(e); } catch (JSONException e) { throw new WeaveException(e); } } protected final WeaveResponse getUserNode(String path) throws WeaveException { URI nodeUri = buildUserUriFromSubpath(path); return getNode(nodeUri); } public QueryResult<List<WeaveBasicObject>> getWboCollection(URI uri) throws WeaveException { try { WeaveResponse response = getNode(uri); QueryResult<List<WeaveBasicObject>> result = new QueryResult<List<WeaveBasicObject>>( response); JSONArray jsonPassArray = new JSONArray(response.getBody()); List<WeaveBasicObject> records = new ArrayList<WeaveBasicObject>(); for (int ii = 0; ii < jsonPassArray.length(); ii++) { JSONObject jsonObj = jsonPassArray.getJSONObject(ii); WeaveBasicObject wbo = new WeaveBasicObject(uri, jsonObj); records.add(wbo); } result.setValue(records); return result; } catch (JSONException e) { throw new WeaveException(e); } } public final URI setClusterUri(URI clusterUri) { return m_clusterUri.getAndSet(clusterUri); } public void shutdown() { m_transport.shutdown(); } }