diff --git a/lib/gson-2.5.jar b/lib/gson-2.5.jar new file mode 100644 index 0000000..5c35c5d Binary files /dev/null and b/lib/gson-2.5.jar differ diff --git a/src/nodash/core/NoCore.java b/src/nodash/core/NoCore.java index 4861cd2..3ce29a5 100644 --- a/src/nodash/core/NoCore.java +++ b/src/nodash/core/NoCore.java @@ -285,25 +285,28 @@ public final class NoCore { byte[] oldHash = session.getOriginalHash(); byte[] newHash = session.getNoUserSafe().createHash(); - session.confirmSave(adapter, data, password); - try { - adapter.insertHash(newHash); - } catch (NoAdapterException e) { - throw new NoDashFatalException("Could not insert confirmed hash.", e); - } + session.confirmSave(adapter, data, password); - try { - adapter.shredNoSession(cookie); - } catch (NoAdapterException e) { - throw new NoDashFatalException("Could not shred session.", e); - } + try { + adapter.insertHash(newHash); + } catch (NoAdapterException e) { + throw new NoDashFatalException("Could not insert confirmed hash.", e); + } + } finally { + try { + adapter.shredNoSession(cookie); + } catch (NoAdapterException e) { + throw new NoDashFatalException("Could not shred session.", e); + } - try { - adapter.goOffline(session.getNoUserSafe().createHash()); - } catch (NoAdapterException e) { - throw new NoDashFatalException("Could not go offline.", e); + try { + adapter.goOffline(session.getNoUserSafe().createHash()); + } catch (NoAdapterException e) { + throw new NoDashFatalException("Could not go offline.", e); + } } + if (!session.isNewUser()) { try { diff --git a/src/nodash/core/NoUtil.java b/src/nodash/core/NoUtil.java index 3dbb649..0153528 100644 --- a/src/nodash/core/NoUtil.java +++ b/src/nodash/core/NoUtil.java @@ -17,6 +17,7 @@ package nodash.core; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -37,6 +38,7 @@ import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import nodash.exceptions.NoDashFatalException; +import nodash.models.NoUser; public final class NoUtil { public static final SecretKey SECRET_KEY = setupSecretKey(); @@ -51,25 +53,45 @@ public final class NoUtil { public static final int RSA_STRENGTH = setupRsaStrength(); public static final int AES_STRENGTH = 256; public static final byte BLANK_BYTE = 'A'; + public static final Class NO_USER_CLASS = setupNoUserClass(); + + private static Class setupNoUserClass() { + String secretEnv = System.getenv("NODASH_USER_CLASS"); + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(secretEnv); + return clazz; + } catch (ClassNotFoundException e) { + throw new RuntimeException("Can't find NODASH_USER_CLASS"); + } + } private static SecretKey setupSecretKey() { String secretEnv = System.getenv("NODASH_SECRET"); if (secretEnv == null) { throw new RuntimeException("Can't find NODASH_SECRET."); } else { - byte[] encoded= Base64.decodeBase64(secretEnv); + byte[] encoded = Base64.decodeBase64(secretEnv); return new SecretKeySpec(encoded, 0, encoded.length, NoUtil.CIPHER_KEY_SPEC); } } - + private static int setupRsaStrength() { String secretEnv = System.getenv("NODASH_RSA_STRENGTH"); if (secretEnv == null) { return 4096; } else { - return Integer.parseInt(secretEnv); + return Integer.parseInt(secretEnv); } } + + public static byte[] toBytes(String string) { + return string.getBytes(Charset.forName("UTF-8")); + } + + public static String fromBytes(byte[] bytes) { + return new String(bytes, Charset.forName("UTF-8")); + } public static char[] bytesToChars(byte[] array) { char[] result = new char[array.length]; @@ -126,8 +148,8 @@ public final class NoUtil { } } - public static byte[] decrypt(byte[] data, char[] password) throws IllegalBlockSizeException, - BadPaddingException { + public static byte[] decrypt(byte[] data, char[] password) + throws IllegalBlockSizeException, BadPaddingException { byte[] passwordByte = NoUtil.getPbeKeyFromPassword(password); byte[] response = NoUtil.decrypt(NoUtil.decrypt(data), passwordByte); NoUtil.wipeBytes(passwordByte); @@ -170,8 +192,8 @@ public final class NoUtil { return NoUtil.encrypt(data, SECRET_KEY.getEncoded()); } - public static byte[] decrypt(byte[] data, byte[] key) throws IllegalBlockSizeException, - BadPaddingException { + public static byte[] decrypt(byte[] data, byte[] key) + throws IllegalBlockSizeException, BadPaddingException { Cipher cipher; try { cipher = Cipher.getInstance(NoUtil.CIPHER_TYPE); @@ -202,7 +224,8 @@ public final class NoUtil { throw new NoDashFatalException("Value for CIPHER_RSA_TYPE is not valid (no such algorithm).", e); } catch (NoSuchPaddingException e) { - throw new NoDashFatalException("Value for CIPHER_RSA_TYPE is not valid (no such padding).", e); + throw new NoDashFatalException("Value for CIPHER_RSA_TYPE is not valid (no such padding).", + e); } try { cipher.init(Cipher.ENCRYPT_MODE, publicKey); @@ -216,8 +239,8 @@ public final class NoUtil { } } - public static byte[] decryptRsa(byte[] data, PrivateKey privateKey) throws InvalidKeyException, - IllegalBlockSizeException, BadPaddingException { + public static byte[] decryptRsa(byte[] data, PrivateKey privateKey) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher; try { cipher = Cipher.getInstance(NoUtil.CIPHER_RSA_TYPE); @@ -225,7 +248,8 @@ public final class NoUtil { throw new NoDashFatalException("Value for CIPHER_RSA_TYPE is not valid (no such algorithm).", e); } catch (NoSuchPaddingException e) { - throw new NoDashFatalException("Value for CIPHER_RSA_TYPE is not valid (no such padding).", e); + throw new NoDashFatalException("Value for CIPHER_RSA_TYPE is not valid (no such padding).", + e); } cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(data); diff --git a/src/nodash/models/NoHash.java b/src/nodash/models/NoHash.java new file mode 100644 index 0000000..e0866c4 --- /dev/null +++ b/src/nodash/models/NoHash.java @@ -0,0 +1,9 @@ +package nodash.models; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface NoHash { + +} diff --git a/src/nodash/models/NoSession.java b/src/nodash/models/NoSession.java index 6260f6a..cbdbf2a 100644 --- a/src/nodash/models/NoSession.java +++ b/src/nodash/models/NoSession.java @@ -1,6 +1,5 @@ package nodash.models; -import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -13,6 +12,8 @@ import javax.crypto.IllegalBlockSizeException; import org.apache.commons.codec.binary.Base64; +import com.google.gson.Gson; + import nodash.core.NoAdapter; import nodash.core.NoUtil; import nodash.exceptions.NoByteSetBadDecryptionException; @@ -60,19 +61,16 @@ public final class NoSession implements Serializable { try { byte[] originalData = Arrays.copyOf(data, data.length); char[] originalPassword = Arrays.copyOf(password, password.length); - this.original = NoUser.createUserFromFile(originalData, originalPassword); - this.current = NoUser.createUserFromFile(data, password); + this.original = + NoUser.createUserFromFile(originalData, originalPassword, NoUtil.NO_USER_CLASS); + this.current = NoUser.createUserFromFile(data, password, NoUtil.NO_USER_CLASS); NoUtil.wipeBytes(data); NoUtil.wipeChars(password); this.uuid = UUID.randomUUID().toString(); - } catch (IOException e) { - throw new NoUserNotValidException(e); } catch (IllegalBlockSizeException e) { throw new NoUserNotValidException(); } catch (BadPaddingException e) { throw new NoUserNotValidException(); - } catch (ClassNotFoundException e) { - throw new NoUserNotValidException(); } } @@ -103,8 +101,8 @@ public final class NoSession implements Serializable { return this.state; } - public byte[] initiateSaveAttempt(char[] password) throws NoSessionConfirmedException, - NoSessionExpiredException { + public byte[] initiateSaveAttempt(char[] password) + throws NoSessionConfirmedException, NoSessionExpiredException { touchState(); this.state = NoState.AWAITING_CONFIRMATION; byte[] file = this.current.createFile(password); @@ -112,8 +110,9 @@ public final class NoSession implements Serializable { return file; } - public void confirmSave(NoAdapter adapter, byte[] confirmData, char[] password) throws NoSessionConfirmedException, - NoSessionExpiredException, NoSessionNotAwaitingConfirmationException, NoUserNotValidException { + public void confirmSave(NoAdapter adapter, byte[] confirmData, char[] password) + throws NoSessionConfirmedException, NoSessionExpiredException, + NoSessionNotAwaitingConfirmationException, NoUserNotValidException { check(); if (this.state != NoState.AWAITING_CONFIRMATION) { throw new NoSessionNotAwaitingConfirmationException(); @@ -121,30 +120,27 @@ public final class NoSession implements Serializable { NoUser confirmed; try { - confirmed = NoUser.createUserFromFile(confirmData, password); - } catch (IOException e) { - throw new NoUserNotValidException(e); + confirmed = NoUser.createUserFromFile(confirmData, password, NoUtil.NO_USER_CLASS); } catch (IllegalBlockSizeException e) { throw new NoUserNotValidException(); } catch (BadPaddingException e) { throw new NoUserNotValidException(); - } catch (ClassNotFoundException e) { - throw new NoUserNotValidException(e); } NoUtil.wipeBytes(confirmData); NoUtil.wipeChars(password); - if (confirmed.createHashString().equals(this.current.createHashString())) { + + if (confirmed.createHashString().equals(current.createHashString())) { this.state = NoState.CONFIRMED; /* 5.2.3: clear influences as they will not need to be re-applied */ this.incoming = new ArrayList(); - List actions = this.current.getNoActions(); + List actions = current.getNoActions(); this.incoming = null; /* 5.2.4: execute NoActions */ for (NoAction action : actions) { /* - * It is assumed that actions are not long-running tasks It is also assumed that actions - * have the information they need without the user objects + * It is assumed that actions are not long-running tasks + * It is also assumed that actions have the information they need without the user objects */ action.execute(adapter); action.purge(); @@ -163,20 +159,21 @@ public final class NoSession implements Serializable { check(); return this.current; } - + public NoUser getNoUserSafe() { return this.current; } - - public Collection getIncoming() throws NoSessionConfirmedException, NoSessionExpiredException { + + public Collection getIncoming() + throws NoSessionConfirmedException, NoSessionExpiredException { check(); return this.incoming; } - + public List getIncomingSafe() { return this.incoming; } - + public String getUuid() { return this.uuid; } @@ -192,12 +189,13 @@ public final class NoSession implements Serializable { return null; } } - + public void setIncoming(List incoming) { this.incoming = incoming; } - public void consume(NoByteSet byteSet) throws NoByteSetBadDecryptionException, NoSessionConfirmedException, NoSessionExpiredException { + public void consume(NoByteSet byteSet) throws NoByteSetBadDecryptionException, + NoSessionConfirmedException, NoSessionExpiredException { check(); this.current.consume(byteSet); } @@ -205,7 +203,7 @@ public final class NoSession implements Serializable { public void close() { this.state = NoState.CLOSED; } - + public boolean isNewUser() { return this.original == null; } diff --git a/src/nodash/models/NoUser.java b/src/nodash/models/NoUser.java index 6f3afa0..36b2d92 100644 --- a/src/nodash/models/NoUser.java +++ b/src/nodash/models/NoUser.java @@ -18,13 +18,14 @@ package nodash.models; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.math.BigInteger; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -34,6 +35,8 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import javax.crypto.BadPaddingException; @@ -43,6 +46,9 @@ import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; +import com.google.gson.Gson; + +import sun.security.rsa.RSAPrivateCrtKeyImpl; import sun.security.rsa.RSAPublicKeyImpl; import nodash.core.NoUtil; import nodash.exceptions.NoByteSetBadDecryptionException; @@ -50,13 +56,17 @@ import nodash.exceptions.NoDashFatalException; public class NoUser implements Serializable { private static final long serialVersionUID = 7132405837081692211L; - private PublicKey publicKey; - private PrivateKey privateKey; - @SuppressWarnings("unused") + @NoHash + private RSAPublicKeyImpl publicKey; + @NoHash + private RSAPrivateCrtKeyImpl privateKey; + @NoHash private String randomized; + @NoHash private int influences; - @SuppressWarnings("unused") + + @NoHash private int actions; private List outgoing = new ArrayList(); @@ -79,11 +89,11 @@ public class NoUser implements Serializable { } KeyPair keyPair = kpg.generateKeyPair(); - this.publicKey = keyPair.getPublic(); - this.privateKey = keyPair.getPrivate(); + this.publicKey = (RSAPublicKeyImpl) keyPair.getPublic(); + this.privateKey = (RSAPrivateCrtKeyImpl) keyPair.getPrivate(); this.influences = 0; this.actions = 0; - this.touchRandomizer(); + touchRandomizer(); } private void touchRandomizer() { @@ -93,45 +103,73 @@ public class NoUser implements Serializable { } catch (NoSuchAlgorithmException e) { throw new NoDashFatalException("Value for SECURERANDOM_ALGORITHM not valid.", e); } - this.randomized = new String(randomBytes); + randomized = new String(randomBytes); } public final byte[] createFile(char[] password) { - List temp = this.outgoing; - try { - this.touchRandomizer(); - this.outgoing = new ArrayList(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(this); - byte[] encrypted = NoUtil.encrypt(baos.toByteArray(), password); - oos.close(); - baos.close(); - return encrypted; - } catch (IOException e) { - throw new NoDashFatalException( - "IO Exception encountered while generating encrypted user file byte stream.", e); - } finally { - this.outgoing = temp; - } + List tempActions = outgoing; + int tempActionCount = actions; + + touchRandomizer(); + outgoing = new ArrayList(); + actions = 0; + + Gson gson = new Gson(); + byte[] json = NoUtil.toBytes(gson.toJson(this)); + byte[] encrypted = NoUtil.encrypt(json, password); + + actions = tempActionCount; + outgoing = tempActions; + return encrypted; } public final byte[] createHash() { - List temp = this.outgoing; try { - this.outgoing = new ArrayList(); + List items = new ArrayList(); + Comparator fieldComp = new Comparator() { + @Override + public int compare(Field o1, Field o2) { + return o1.getName().compareTo(o2.getName()); + } + }; + + Class userClass = getClass(); + + while (userClass != null) { + Field[] noHashFields = userClass.getDeclaredFields(); + + Arrays.sort(noHashFields, fieldComp); + + for (Field field : noHashFields) { + if (field.isAnnotationPresent(NoHash.class)) { + field.setAccessible(true); + items.add(field.get(this)); + } + } + + if (userClass == NoUser.class) { + userClass = null; + } else { + userClass = (Class) userClass.getSuperclass(); + } + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(this); - byte[] userBytes = baos.toByteArray(); - return NoUtil.getHashFromByteArray(userBytes); + oos.writeObject(items); + byte[] itemBytes = baos.toByteArray(); + + return NoUtil.getHashFromByteArray(itemBytes); } catch (IOException e) { throw new NoDashFatalException("IO Exception encountered while generating user hash.", e); - } finally { - this.outgoing = temp; + } catch (IllegalArgumentException e) { + throw new NoDashFatalException("IllegalArgument Exception encountered while generating user hash.", e); + } catch (IllegalAccessException e) { + throw new NoDashFatalException("IllegalAccess Exception encountered while generating user hash.", e); } } - + public final void consume(NoByteSet byteSet) throws NoByteSetBadDecryptionException { try { SecretKey secretKey = new SecretKeySpec(decryptRsa(byteSet.key), NoUtil.CIPHER_KEY_SPEC); @@ -182,38 +220,38 @@ public class NoUser implements Serializable { return influences; } - private final byte[] decryptRsa(byte[] data) throws InvalidKeyException, - IllegalBlockSizeException, BadPaddingException { + private final byte[] decryptRsa(byte[] data) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException { return NoUtil.decryptRsa(data, this.privateKey); } - public static NoUser createUserFromFile(byte[] data, char[] password) - throws IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException { + public static NoUser createUserFromFile(byte[] data, char[] password, Class clazz) + throws IllegalBlockSizeException, BadPaddingException { byte[] decrypted = NoUtil.decrypt(data, password); - ByteArrayInputStream bais = new ByteArrayInputStream(decrypted); - ObjectInputStream ois = new ObjectInputStream(bais); - NoUser noUser = (NoUser) ois.readObject(); - ois.close(); - bais.close(); + + Gson gson = new Gson(); + String json = NoUtil.fromBytes(decrypted); + NoUser noUser = gson.fromJson(json, clazz); + return noUser; } public String createHashString() { - return Base64.encodeBase64URLSafeString(this.createHash()); + return Base64.encodeBase64URLSafeString(createHash()); } @Override - public boolean equals(Object otherUser) { + public final boolean equals(Object otherUser) { if (otherUser == null) { return false; } - + if (!otherUser.getClass().equals(getClass())) { return false; } - + return this.privateKey.equals(((NoUser) otherUser).privateKey); } - - + + } diff --git a/src/nodash/test/NoCoreTest.java b/src/nodash/test/NoCoreTest.java index 5bcc37c..65a099e 100644 --- a/src/nodash/test/NoCoreTest.java +++ b/src/nodash/test/NoCoreTest.java @@ -19,6 +19,8 @@ package nodash.test; import static org.junit.Assert.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Arrays; import nodash.core.NoAdapter; @@ -84,7 +86,8 @@ public class NoCoreTest { public void testSaveAndConfirm() throws NoSessionExpiredException, NoSessionConfirmedException, NoSessionNotAwaitingConfirmationException, NoUserNotValidException, NoDashSessionBadUuidException, NoUserAlreadyOnlineException, NoSessionNotChangedException, - NoSessionAlreadyAwaitingConfirmationException, NoAdapterException { + NoSessionAlreadyAwaitingConfirmationException, NoAdapterException, NoSuchMethodException, + SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { NoAdapter adapter = new NoDefaultAdapter(); NoCore core = new NoCore(adapter); @@ -135,6 +138,11 @@ public class NoCoreTest { NoUser oldUserRevisited = core.getNoUser(oldUserCookie); byte[] currentHash = oldUserRevisited.createHash(); oldUserRevisited.createFile("password".toCharArray()); + + Method touchRandomizer = NoUser.class.getDeclaredMethod("touchRandomizer"); + touchRandomizer.setAccessible(true); + touchRandomizer.invoke(adapter.getNoSession(oldUserCookie).getNoUser()); + byte[] oldCreatedFile = core.save(oldUserCookie, "new-password".toCharArray()); byte[] oldUserHash = oldUserRevisited.createHash(); core.confirm(oldUserCookie, "new-password".toCharArray(), oldCreatedFile); diff --git a/src/nodash/test/NoUserTest.java b/src/nodash/test/NoUserTest.java index 35bbb85..32b38b2 100644 --- a/src/nodash/test/NoUserTest.java +++ b/src/nodash/test/NoUserTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; +import nodash.core.NoUtil; import nodash.models.NoUser; import org.apache.commons.codec.binary.Base64; @@ -80,8 +81,8 @@ public class NoUserTest { } @Test - public void testCreateUserFromFile() throws IllegalBlockSizeException, BadPaddingException, - ClassNotFoundException, IOException { + public void testCreateUserFromFile() + throws IllegalBlockSizeException, BadPaddingException, ClassNotFoundException, IOException { NoUser user = new NoUser(); final byte[] originalFile = user.createFile("password".toCharArray()); byte[] file = Arrays.copyOf(originalFile, originalFile.length); @@ -90,34 +91,34 @@ public class NoUserTest { user = null; try { - user = NoUser.createUserFromFile(file, "wrongpassword".toCharArray()); + user = NoUser.createUserFromFile(file, "wrongpassword".toCharArray(), NoUtil.NO_USER_CLASS); fail("Should have thrown an error when given wrong password."); } catch (BadPaddingException e) { // Do nothing, correct } file = Arrays.copyOf(originalFile, originalFile.length); - user = NoUser.createUserFromFile(file, "password".toCharArray()); + user = NoUser.createUserFromFile(file, "password".toCharArray(), NoUtil.NO_USER_CLASS); assertTrue(Arrays.equals(hash, user.createHash())); assertEquals(hashString, user.createHashString()); file = Arrays.copyOf(originalFile, originalFile.length); try { - NoUser.createUserFromFile(file, null); + NoUser.createUserFromFile(file, null, NoUtil.NO_USER_CLASS); fail("Should have thrown a NullPointerException."); } catch (NullPointerException e) { // Do nothing, correct } try { - NoUser.createUserFromFile(null, "password".toCharArray()); + NoUser.createUserFromFile(null, "password".toCharArray(), NoUtil.NO_USER_CLASS); fail("Should have thrown a IllegalArgumentException."); } catch (IllegalArgumentException e) { // Do nothing, correct } try { - NoUser.createUserFromFile(null, null); + NoUser.createUserFromFile(null, null, NoUtil.NO_USER_CLASS); fail("Should have thrown a IllegalArgumentException."); } catch (NullPointerException e) { // Do nothing, correct diff --git a/src/nodash/test/functional/implementations/TestNoUser.java b/src/nodash/test/functional/implementations/TestNoUser.java index 504e7c4..bb70850 100644 --- a/src/nodash/test/functional/implementations/TestNoUser.java +++ b/src/nodash/test/functional/implementations/TestNoUser.java @@ -10,6 +10,7 @@ public class TestNoUser extends NoUser { * */ private static final long serialVersionUID = 1L; + private String username; private int money; private List receipts;