diff --git a/README.md b/README.md
index 0b7ce41..6837fdb 100644
--- a/README.md
+++ b/README.md
@@ -2,38 +2,113 @@
Provides framework / prototype for end-to-end encryption with perfect forward secrecy in Java
-Protocol combines symmetric and asymmetric encryption algorithms [RSA, Elliptic curve Diffie–Hellman, AES] to implement end-to-end encryption, It works on top of https
+Protocol combines symmetric and asymmetric encryption algorithms [RSA, Elliptic Curve Diffie–Hellman, AES] to implement end-to-end encryption. It works on top of https protocol providing encryption in transport and storage
-protocol provides encryption in transport and storage
+## Getting Started
+Dependencies are managed using [Maven](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
+The framework uses Bouncy Castle as provider for encryption. The dependency has to be excluded from the *fat* jar because of signature verification problems.
+
+From the source: [tomee.apache](http://tomee.apache.org/bouncy-castle.html), bouncy castle is easily installed in two steps:
+1. Add the Bouncy Castle provider jar to the $JAVA_HOME/jre/lib/ext directory
+2. Create a Bouncy Castle provider entry in the $JAVA_HOME/jre/lib/security/java.security file
+
+The entry to java.security will look something like the following:
+
+> security.provider.N=org.bouncycastle.jce.provider.BouncyCastleProvider
+
+The framework is configured to work with a mysql database. It uses the mysql-connector-v8 which may or may not be compatible with older versions on MySQL. The following line in `DataBaseServiceImpl.java` can be changed for other database usage.
+> dataSource.setUrl("jdbc:mysql://" + config.getString("sql.host") + "/" + config.getString("sql.database"));
+
+There is a schema.sql file located in the SQL directory of the otr-server. A database should be created and initialized to use this schema.
+
+[Lithium Flow](https://github.com/lithiumtech/flow) is used for managing configuration files. The config file must be specified at runtime with a system property, and the library is included in the pom.xml.
+
+### Configuration
+There are three total configuration files, a common, server, and client. The server and client contain some required configurations for starting the application.
+
+#### Server
+The following items need to be configured before the server will work:
+1. `sql.host` - include the hostname, default port used is 4567
+2. `sql.user` - username of sql database
+3. `sql.password` - password of sql database
+4. `sql.database` - database name used when creating the sql schema mentioned above
+
+#### Client
+The following items to need to be configured before the client will work:
+1. `server.url` - default port number is 4567
+2. `user.keystore` - storage location for key information generated for a user
+
+The ECDH curve can also be changed in this configuration file.
+
+### Running
+After ensuring that all of the dependencies and configurations have been taken care of, build the application with `mvn package` from the top-level directory.
+
+Change into the server's directory and run the server with:
+> java -Dconfig=server.config -cp target/*-fat.jar com.jigar.otr.OTRServerMain
+
+Start another shell and change into the client's directory, run the client with:
+> java -Dconfig=client.config -cp target/*-fat.jar com.jigar.otr.OTRClientDemoMain
+
+Sample exchange on the same client.
+
+User1
+1. Register user1
+2. Login as user1
+3. Logout of the user (writes login information to disk)
+
+User2
+1. Register user2
+2. Login as user2
+3. Logout of the user (writes login information to disk)
+
+- Login as user1, and list users.
+- Send a message to user2 using the id identified by the list user's call, logout.
+- Login to user2, receive the message.
+
+## API
### Keys
+A set of keys are generated on the client-side before interaction with the server.
+
##### Identity Key
-During registration, client generates RSA 4096 bit key pair, public key gets sent to server during registration, private key remains at client
+During registration, client generates a 4096 bit RSA key pair, and sends the public key to server. The private key remains at client.
##### Pre-Keys
-During registration client generates large bulk of ECDH key pairs, and public portion of these keys gets sent to server during registration, all private keys remains at client
+During registration, client generates large bulk of ECDH key pairs (configurable), and the public portion of these keys is sent to server. All private keys remains at client to be used as keys for encrypting communication.
### Encryption
-It uses 256 bit AES encryption
+Current implementation uses 256 bit AES Encryption.
### Protocol
-##### registration
+#### Registration
+
+ - User provides `login`, `password`, `public identity key`, `set of public pre-keys` -> server returns user identification number back upon successful registration, client stores this information
+
+#### Login
- - User provides `login`, `password`, `public identity key`, `set of public pre-keys`, user gets user identification number back upon successful registration, which is stored
+ - User provides `login`, `password` and `signedLogin` (signed login with private identity key) -> server provides `userId` (user identification number) on a successful login.
-##### login
+#### Logout [TODO Encrypt Stored Information]
- - User provides `login`, `password` and `signedLogin` (signed login with private identity key), upon login server provides `userId` (user identification number) which client stores it
+ - User writes all key related information to the file "username-otr.json" in the directory defined by the "user.keystore" configuration variable. The user's information is then cleared from memory. The information is stored as plaintext.
+
+#### List Users
-##### send message
+ - User request a list of users from the server.
+
+ - Server returns a list of user's with including both their userId and userName.
+
+#### Send Message
- - Bob wants to send message to Alice, Both user needs to have registered in system already, Bob will request Alice's public identity key & Alice's one of the public pre-key from server server
+ - Bob wants to send message to Alice -- Both users need to be registered in system already
+
+ - Bob will request Alice's public identity key & one of her public pre-keys being stored on the server
- - Server will give public identity key and one of the public pre-key of Alice to Bob
+ - Server returns public identity key and one of the Alice's public pre-keys of to Bob
- - Server will remove supplied public pre-key, if there is only one last public pre-key left on server, server will keep it until client comes back and replenishes them
+ - Server will remove supplied public pre-key; if this is Alice's last public pre-key on server, the server will keep it until Alice comes back and replenishes them
- Bob generates ECDH key pair and using Alice's ECDH public pre-key derives secret
@@ -41,51 +116,54 @@ It uses 256 bit AES encryption
- Bob uses computed secret, salt and IV as input to AES (256 bit) to encrypt his message for Alice
- - Bob uses Alice's public identity key to encrypt Bob's ECDH public pre-key, salt and Alice's public ECDH pre-key
+ - Bob uses Alice's public identity key to encrypt Bob's ECDH public pre-key, salt, IV, and Alice's public ECDH pre-key
- Bob signs salt with his private key
- - Bob sends server Alice's userId, encrypted message, encrypted salt, signed salt, encrypted IV, encrypted Alice's public ECDH pre-key, encrypted Bob's public ECDH pre-key and signed salt
+ - Bob sends server Alice's userId, encrypted message, encrypted salt, signed salt, encrypted IV, Alice's public ECDH pre-key encrypted, Bob's public ECDH pre-key encrypted and signed salt
- - server simply stores all these information
+ - Server stores all of this information
-##### receive message
+#### Receive Message
- - Alice requests for her message to server by providing her userId, server validates if Alice is logged in
+ - Alice makes a request for her messages to server by providing her userId, server validates if Alice is logged in
- Server provides encrypted data which was submitted by Bob
- Alice uses her private key to decrypt Alice's ECDH public pre-key, Bob's ECDH public key, salt and IV
- - Alice verifies signedSalt with Bob's public key
+ - Alice verifies the signed salt with Bob's public key
- - Alice checks Bob's identity finger print and makes sure, Bob is really the one who Alice thinks by making sure his identity by going out of band
+ - Alice checks Bob's identity fingerprint to make sure Bob is really the who Alice thinks by making sure his identity by going out of band
- - Alice then uses her ECDH private key, Bob's ECDH public key to compute secret and uses AES to decrypt message
+ - Alice then uses her ECDH private key and Bob's ECDH public key to compute secret.
-##### replenishing pre-keys
+ - AES is used to decrypt message
+
+#### Replenishing Pre-Keys
+
+A user that has registered with the server has a public pre-key removed each time a user sends them a message. Because of this, the pre-keys must be replenished on the server's side.
- Client maintains N number of pre-keys on server and periodically replenishes them
### Data storage [TODO]
- - To store backup of Bob and Alice's chat
+ - Stores backup of Bob and Alice's chat
- - Alice and Bob both are asked if you want to store messages, if both agrees to continue then client proceeds with backup
+ - Alice and Bob both are asked if you want to store messages, if both agree to continue the client proceeds with backup
- - To backup Alice's client generates a secure random key and encrypts with Bob's public key & Alice's public key and sends it to server [2 keys]
+ - To backup, Alice's client generates a secure random key and encrypts with both Bob's public key & Alice's public key; it is then sent to server [2 keys]
- Bob's client does the same
- - Server once receives both the encrypted keys, generates a random salt and keeps on the record and provides that random salt to Bob and Alice on their next ping to server in encrypted form via their public identity keys
+ - Once the server receives both the encrypted keys, it generates a random salt and keeps this on record, providing the random salt to Bob and Alice on their next ping to server in encrypted form via their public identity keys
- - Bob and Alice gets the secret key for their chat storage through server and decrypts with their private key and combines them to derive secret key that only Alice and Bob knows and then sends data back to server
+ - Bob and Alice get the secret key for their chat storage through the server and decrypt it with their private keys, combining them to derive a secret key that only Alice and Bob know. They then send data back to server
- These keys must remain present to client at all times protected by a master password which remains in the mind of user for backup and retrieval purpose
-
### License
Copyright 2016 Jigar Joshi
diff --git a/otr-client/client.config b/otr-client/client.config
index 9e73ad2..e5630a8 100644
--- a/otr-client/client.config
+++ b/otr-client/client.config
@@ -4,11 +4,8 @@ app.name = otr-client
server.url = http://localhost:4567
keys.identity.keySize = 4096
-
-
keys.pre.count = 100
-
aes.keygen.algorithm = PBKDF2WithHmacSHA1
aes.key.iterations = 65536
aes.key.length = 256
@@ -17,8 +14,4 @@ aes.cipher.transformation = AES/CBC/PKCS5Padding
## org.bouncycastle.jce.ECNamedCurveTable.getNames
ecdh.curveName = prime192v1
-
-
-##test purposes
-user = jigar1
-target = 2
\ No newline at end of file
+user.keystore = users/
diff --git a/otr-client/pom.xml b/otr-client/pom.xml
index bc3f63c..a2eea36 100644
--- a/otr-client/pom.xml
+++ b/otr-client/pom.xml
@@ -23,6 +23,7 @@
org.bouncycastle
bcprov-jdk15on
1.54
+ provided
@@ -73,25 +74,35 @@
- maven-assembly-plugin
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+
+
+ org.bouncycastle:*:*:*
+
+
+
+
+
+ com.jigar.otr.OTRClientDemoMain
+ . ./lib/bcprov-jdk15on-1.54.jar
+
+
+
+ true
+ fat
+
package
- single
+ shade
-
-
- jar-with-dependencies
-
-
-
- com.jigar.otr.OTRServerMain
-
-
-
diff --git a/otr-client/src/main/java/com/jigar/otr/OTRClientDemoMain.java b/otr-client/src/main/java/com/jigar/otr/OTRClientDemoMain.java
index ab241ea..ed61587 100644
--- a/otr-client/src/main/java/com/jigar/otr/OTRClientDemoMain.java
+++ b/otr-client/src/main/java/com/jigar/otr/OTRClientDemoMain.java
@@ -38,29 +38,37 @@ public static void main(String[] args) throws Exception {
private static void apiTest() throws Exception {
OTRClient client = OTRClient.get(Main.config());
- String user = Main.config().getString("user");
- int target = Main.config().getInt("target");
+ // String user = Main.config().getString("user");
while (true) {
Scanner scanner = new Scanner(System.in);
log.info("1. register");
log.info("2. login");
log.info("3. sendMessage");
log.info("4. readMessage");
- log.info("5. exit");
+ log.info("5. list Users");
+ log.info("6. logout");
+ log.info("7. exit");
String command = scanner.nextLine();
+ String user = "";
switch (command) {
case "1":
+ log.info("enter user: ");
+ user = scanner.nextLine();
log.info("enter password: ");
String password = scanner.nextLine();
client.register(user, password);
break;
case "2":
+ log.info("enter user: ");
+ user = scanner.nextLine();
log.info("enter password: ");
password = scanner.nextLine();
client.login(user, password);
break;
case "3":
+ log.info("Enter target: ");
+ int target = Integer.valueOf(scanner.nextLine());
log.info("Enter message: ");
String message = scanner.nextLine();
client.sendMessage("From " + user + ", message = " + message, target);
@@ -69,6 +77,12 @@ private static void apiTest() throws Exception {
client.readMessages();
break;
case "5":
+ client.listUsers();
+ break;
+ case "6":
+ client.logout();
+ break;
+ case "7":
System.exit(0);
default:
log.error("invalid choice");
diff --git a/otr-client/src/main/java/com/jigar/otr/service/OTRClient.java b/otr-client/src/main/java/com/jigar/otr/service/OTRClient.java
index 9090027..0848d25 100644
--- a/otr-client/src/main/java/com/jigar/otr/service/OTRClient.java
+++ b/otr-client/src/main/java/com/jigar/otr/service/OTRClient.java
@@ -21,6 +21,8 @@
import com.jigar.otr.exception.OTRException;
import com.jigar.otr.service.impl.OTRRestClient;
+import java.util.Map;
+
/**
* Created by jigar.joshi on 11/21/16.
*/
@@ -31,6 +33,8 @@ public interface OTRClient {
void login(String login, String password) throws OTRException;
+ void logout() throws OTRException;
+
void refreshMessageKeys() throws OTRException;
void readMessages();
@@ -41,6 +45,8 @@ public interface OTRClient {
String getPrePublicKey(int userId) throws OTRException;
+ String listUsers() throws OTRException;
+
static OTRClient get(Config config) {
String clientType = config.getString("otr.client", "rest");
switch (clientType) {
diff --git a/otr-client/src/main/java/com/jigar/otr/service/Storer.java b/otr-client/src/main/java/com/jigar/otr/service/Storer.java
index 4ee109c..9d74463 100644
--- a/otr-client/src/main/java/com/jigar/otr/service/Storer.java
+++ b/otr-client/src/main/java/com/jigar/otr/service/Storer.java
@@ -42,7 +42,6 @@ enum NameSpace {
void remove(NameSpace namespace);
-
static Storer get(Config config) {
String storerType = config.getString("storer.type", "memory");
switch (storerType) {
diff --git a/otr-client/src/main/java/com/jigar/otr/service/StorerWrapper.java b/otr-client/src/main/java/com/jigar/otr/service/StorerWrapper.java
index 2a16b48..4355929 100644
--- a/otr-client/src/main/java/com/jigar/otr/service/StorerWrapper.java
+++ b/otr-client/src/main/java/com/jigar/otr/service/StorerWrapper.java
@@ -26,6 +26,7 @@
public interface StorerWrapper {
int getUserId();
+ String getUsername();
String getPrivateIdKey();
diff --git a/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorer.java b/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorer.java
index 288ab91..3f4016a 100644
--- a/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorer.java
+++ b/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorer.java
@@ -19,6 +19,7 @@
import java.util.HashMap;
import java.util.Map;
+import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.jigar.otr.service.Storer;
@@ -76,4 +77,19 @@ public void remove(NameSpace namespace, String key) {
public void remove(NameSpace namespace) {
map.remove(namespace);
}
+
+ @Override
+ public String toString()
+ {
+ return new GsonBuilder().disableHtmlEscaping().create().toJson(map);
+ }
+
+ public boolean set(Map> newUser){
+ map.clear();
+ for (NameSpace space: newUser.keySet())
+ {
+ map.put(space, newUser.get(space));
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorerWrapper.java b/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorerWrapper.java
index 4cbe8cd..0ad63cc 100644
--- a/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorerWrapper.java
+++ b/otr-client/src/main/java/com/jigar/otr/service/impl/InMemoryStorerWrapper.java
@@ -16,9 +16,12 @@
package com.jigar.otr.service.impl;
+import com.google.gson.JsonObject;
import com.jigar.otr.service.Storer;
import com.jigar.otr.service.StorerWrapper;
+import java.util.Map;
+
/**
* Created by jigar.joshi on 11/28/16.
*/
@@ -42,6 +45,11 @@ public int getUserId() {
return storer.get(Storer.NameSpace.USER, "user").get("userId").getAsInt();
}
+ @Override
+ public String getUsername() {
+ return storer.get(Storer.NameSpace.USER, "user").get("userName").getAsString();
+ }
+
@Override
public String getPrivateIdKey() {
return storer.get(Storer.NameSpace.ID_KEYS, Storer.ID_KEY).get("private").getAsString();
@@ -57,4 +65,11 @@ public String getPublicIdKey() {
public String getPrivatePreKey(String publicPreKey) {
return storer.get(Storer.NameSpace.PRE_KEYS, publicPreKey).get("private").getAsString();
}
+
+ public boolean loadUser(Map> newUser){
+ if (storer instanceof InMemoryStorer){
+ return ((InMemoryStorer) storer).set(newUser);
+ }
+ return false;
+ }
}
diff --git a/otr-client/src/main/java/com/jigar/otr/service/impl/OTRRestClient.java b/otr-client/src/main/java/com/jigar/otr/service/impl/OTRRestClient.java
index 9fbba5a..e58e6cd 100644
--- a/otr-client/src/main/java/com/jigar/otr/service/impl/OTRRestClient.java
+++ b/otr-client/src/main/java/com/jigar/otr/service/impl/OTRRestClient.java
@@ -15,17 +15,32 @@
*/
package com.jigar.otr.service.impl;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
import com.lithium.flow.config.Config;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpURLConnection;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
+import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
@@ -46,6 +61,8 @@
import com.jigar.otr.service.Storer;
import com.jigar.otr.service.StorerWrapper;
+import javax.naming.Name;
+
/**
* Created by jigar.joshi on 11/21/16.
*/
@@ -123,12 +140,18 @@ public void register(String login, String password) throws OTRException {
throw new OTRException("Failed to process registration on server, server responded with status code: "
+ response.getStatusCode());
}
- storeUser(response);
+ storeUser(response, login);
}
@Override
public void login(String login, String password) throws OTRException {
String signedLogin;
+ if(!loadUser(login))
+ {
+ // No users present on the current instance.
+ // You need a keypair to even login.
+ return;
+ }
try {
PrivateKey privateKey = rsa.getPrivateKeyFromString(storerWrapper.getPrivateIdKey());
signedLogin = rsa.sign(login, privateKey);
@@ -144,12 +167,65 @@ public void login(String login, String password) throws OTRException {
int statusCode = response.getStatusCode();
if (statusCode == HttpURLConnection.HTTP_OK) {
log.info("logged in");
- storeUser(response);
+ storeUser(response, login);
} else {
throw new OTRException("Failed to login, status code received : " + statusCode);
}
}
+ @Override
+ public void logout() throws OTRException
+ {
+ try
+ {
+ String username = storerWrapper.getUsername();
+ String keystore = config.getString("user.keystore", "./");
+ File dirs = new File(keystore);
+ if(!dirs.exists()){
+ dirs.mkdirs();
+ }
+
+ FileWriter fw = new FileWriter(keystore + username + "-otr.json");
+ fw.write(storer.toString());
+ fw.flush();
+ fw.close();
+
+ String json = new String(Files.readAllBytes(Paths.get(keystore + username + "-otr.json")));
+ Type mapType = new TypeToken