diff --git a/src/nodash/core/NoAdapter.java b/src/nodash/core/NoAdapter.java index f718dde..86c8ca9 100644 --- a/src/nodash/core/NoAdapter.java +++ b/src/nodash/core/NoAdapter.java @@ -4,6 +4,7 @@ import java.security.PublicKey; import java.util.Collection; import nodash.exceptions.NoAdapterException; +import nodash.exceptions.NoSessionExpiredException; import nodash.exceptions.NoUserAlreadyOnlineException; import nodash.exceptions.NoUserNotValidException; import nodash.models.NoByteSet; @@ -19,11 +20,11 @@ public interface NoAdapter { public byte[][] exportHashes() throws NoAdapterException; public long hashCount() throws NoAdapterException; - + public void goOnline(byte[] hash) throws NoAdapterException, NoUserAlreadyOnlineException; - + public boolean isOnline(byte[] hash) throws NoAdapterException; - + public void goOffline(byte[] hash) throws NoAdapterException; public void addNoSession(NoSession session) throws NoAdapterException; @@ -32,12 +33,13 @@ public interface NoAdapter { public void shredNoSession(byte[] encryptedUuid) throws NoAdapterException; - public NoSession getNoSession(byte[] encryptedUuid) throws NoAdapterException; + public NoSession getNoSession(byte[] encryptedUuid) throws NoAdapterException, + NoSessionExpiredException; public Collection pollNoByteSets(PublicKey address) throws NoAdapterException; public void addNoByteSet(NoByteSet byteSet, PublicKey address) throws NoAdapterException; - + public void addNoByteSets(Collection byteSets, PublicKey address) throws NoAdapterException; } diff --git a/src/nodash/core/NoCore.java b/src/nodash/core/NoCore.java index 343b571..30e65ab 100644 --- a/src/nodash/core/NoCore.java +++ b/src/nodash/core/NoCore.java @@ -17,6 +17,8 @@ package nodash.core; +import org.apache.commons.codec.binary.Base64; + import nodash.exceptions.NoAdapterException; import nodash.exceptions.NoByteSetBadDecryptionException; import nodash.exceptions.NoDashFatalException; @@ -59,7 +61,7 @@ public final class NoCore { public byte[] login(byte[] data, char[] password) throws NoUserNotValidException, NoUserAlreadyOnlineException, NoSessionExpiredException { - NoSession session = new NoSession(adapter, data, password); + NoSession session = new NoSession(data, password); /* 1. Check that user is a valid user of the system based on their hash. */ try { @@ -129,6 +131,7 @@ public final class NoCore { byte[] userFile; try { userFile = save(cookie, password); + adapter.goOnline(user.createHash()); } catch (NoSessionExpiredException e) { throw new NoDashFatalException("Session expired despite just being created."); } catch (NoSessionConfirmedException e) { @@ -139,6 +142,10 @@ public final class NoCore { } catch (NoSessionAlreadyAwaitingConfirmationException e) { throw new NoDashFatalException( "Session already waiting confirmation despite just being created."); + } catch (NoAdapterException e) { + throw new NoDashFatalException("Could not go online.", e); + } catch (NoUserAlreadyOnlineException e) { + throw new NoDashFatalException("User with same hash is already online."); } return new NoRegister(cookie, userFile); @@ -196,29 +203,44 @@ public final class NoCore { } try { - adapter.goOffline(newHash); + 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); } + + if (!session.isNewUser()) { + try { + adapter.removeHash(oldHash); + } catch (NoAdapterException e) { + throw new NoDashFatalException("Could not remove old hash.", e); + } + } + } + + public void shred(byte[] cookie) throws NoSessionExpiredException { + NoSession session = getNoSession(cookie); try { adapter.shredNoSession(cookie); } catch (NoAdapterException e) { throw new NoDashFatalException("Could not shred session.", e); } - + try { - adapter.removeHash(oldHash); + adapter.addNoByteSets(session.getIncomingSafe(), session.getNoUserSafe().getRsaPublicKey()); } catch (NoAdapterException e) { - throw new NoDashFatalException("Could not remove old hash.", e); + throw new NoDashFatalException("Could not add bytesets back into pool."); } - } - - public void shred(byte[] cookie) { try { - adapter.shredNoSession(cookie); + adapter.goOffline(session.getNoUserSafe().createHash()); } catch (NoAdapterException e) { - throw new NoDashFatalException("Could not shred session.", e); + throw new NoDashFatalException("Could not go offline.", e); } } diff --git a/src/nodash/core/NoDefaultAdapter.java b/src/nodash/core/NoDefaultAdapter.java index cf90ca2..bab4cb7 100644 --- a/src/nodash/core/NoDefaultAdapter.java +++ b/src/nodash/core/NoDefaultAdapter.java @@ -44,19 +44,21 @@ public class NoDefaultAdapter implements NoAdapter { private static synchronized byte[] alterFile(byte[] hashToAdd, byte[] hashToRemove) throws IOException { - if (!(hashToAdd == null ^ hashToRemove == null) && Arrays.equals(hashToAdd, hashToRemove)) { - throw new IllegalArgumentException("Hashes to add and remove cannot be the same."); - } - File file = new File(HASH_FILE); + if (!file.exists()) { + Files.createFile(file.toPath()); + } byte[] originalFileBytes = Files.readAllBytes(file.toPath()); if (hashToAdd == null && hashToRemove == null) { return originalFileBytes; } + if (!(hashToAdd == null ^ hashToRemove == null) && Arrays.equals(hashToAdd, hashToRemove)) { + throw new IllegalArgumentException("Hashes to add and remove cannot be the same."); + } int hashes = originalFileBytes.length / 64; List newFile = new ArrayList(); - + for (int x = 0; x < hashes; x++) { byte[] hash = Arrays.copyOfRange(originalFileBytes, x * 64, x * 64 + 64); @@ -66,7 +68,7 @@ public class NoDefaultAdapter implements NoAdapter { } } - if (hashToAdd != null || Arrays.equals(hash, hashToAdd)) { + if (hashToAdd != null && Arrays.equals(hash, hashToAdd)) { hashToAdd = null; } } @@ -152,7 +154,7 @@ public class NoDefaultAdapter implements NoAdapter { public boolean containsNoSession(byte[] encryptedUuid) { String uuid; try { - uuid = Base64.encodeBase64String(NoUtil.decrypt(encryptedUuid)); + uuid = Base64.encodeBase64URLSafeString(NoUtil.decrypt(encryptedUuid)); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new NoDashFatalException("Could not decrypt given UUID.", e); } @@ -163,7 +165,6 @@ public class NoDefaultAdapter implements NoAdapter { @Override public void shredNoSession(byte[] encryptedUuid) { NoSession session = getNoSession(encryptedUuid); - addNoByteSets(session.getIncomingSafe(), session.getNoUserSafe().getRsaPublicKey()); sessions.remove(session.getUuid()); } @@ -172,7 +173,7 @@ public class NoDefaultAdapter implements NoAdapter { if (containsNoSession(encryptedUuid)) { String uuid; try { - uuid = Base64.encodeBase64String(NoUtil.decrypt(encryptedUuid)); + uuid = Base64.encodeBase64URLSafeString(NoUtil.decrypt(encryptedUuid)); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new NoDashFatalException("Could not decrypt given UUID.", e); } @@ -206,6 +207,14 @@ public class NoDefaultAdapter implements NoAdapter { @Override public void addNoByteSets(Collection addedByteSets, PublicKey address) { + if (addedByteSets == null) { + return; + } + + if (address == null) { + throw new NullPointerException("Address cannot be null."); + } + if (byteSets.containsKey(address)) { byteSets.get(address).addAll(addedByteSets); } else { @@ -215,7 +224,7 @@ public class NoDefaultAdapter implements NoAdapter { @Override public void goOnline(byte[] hash) throws NoUserAlreadyOnlineException { - String hashString = Base64.encodeBase64String(hash); + String hashString = Base64.encodeBase64URLSafeString(hash); if (online.contains(hashString)) { throw new NoUserAlreadyOnlineException(); } @@ -224,13 +233,13 @@ public class NoDefaultAdapter implements NoAdapter { @Override public boolean isOnline(byte[] hash) { - String hashString = Base64.encodeBase64String(hash); + String hashString = Base64.encodeBase64URLSafeString(hash); return online.contains(hashString); } @Override public void goOffline(byte[] hash) { - String hashString = Base64.encodeBase64String(hash); + String hashString = Base64.encodeBase64URLSafeString(hash); online.remove(hashString); } diff --git a/src/nodash/models/NoSession.java b/src/nodash/models/NoSession.java index 12adabf..9d0e91d 100644 --- a/src/nodash/models/NoSession.java +++ b/src/nodash/models/NoSession.java @@ -3,6 +3,7 @@ package nodash.models; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -32,13 +33,12 @@ public final class NoSession implements Serializable { private NoUser original; private NoState state; private final long expiry; - private boolean newUserSession; private Collection incoming; private NoUser current; private String uuid; - public NoSession() { + private NoSession() { this.state = NoState.IDLE; this.expiry = System.currentTimeMillis() + NoSession.SESSION_DURATION; this.uuid = UUID.randomUUID().toString(); @@ -46,18 +46,25 @@ public final class NoSession implements Serializable { public NoSession(NoUser newUser) { this(); + if (newUser == null) { + throw new NullPointerException("Session cannot be created with null user."); + } this.state = NoState.MODIFIED; this.original = null; this.current = newUser; - this.newUserSession = true; } - public NoSession(NoAdapter adapter, byte[] data, char[] password) throws NoUserNotValidException { + public NoSession(byte[] data, char[] password) throws NoUserNotValidException { this(); - this.newUserSession = false; this.state = NoState.IDLE; try { - this.original = NoUser.createUserFromFile(data, password); + 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); + NoUtil.wipeBytes(data); + NoUtil.wipeChars(password); + this.uuid = UUID.randomUUID().toString(); } catch (IOException e) { throw new NoUserNotValidException(); } catch (IllegalBlockSizeException e) { @@ -80,7 +87,7 @@ public final class NoSession implements Serializable { public NoState touchState() throws NoSessionConfirmedException, NoSessionExpiredException { check(); - if (this.newUserSession) { + if (this.original == null) { if (this.state != NoState.AWAITING_CONFIRMATION) { this.state = NoState.MODIFIED; } @@ -133,8 +140,6 @@ public final class NoSession implements Serializable { this.incoming = new ArrayList(); List actions = this.current.getNoActions(); this.incoming = null; - this.original = null; - this.current = null; /* 5.2.4: execute NoActions */ for (NoAction action : actions) { /* @@ -181,7 +186,7 @@ public final class NoSession implements Serializable { } public byte[] getOriginalHash() { - if (this.original != null) { + if (!isNewUser()) { return this.original.createHash(); } else { return null; @@ -200,4 +205,8 @@ 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 5b46468..1797f72 100644 --- a/src/nodash/models/NoUser.java +++ b/src/nodash/models/NoUser.java @@ -195,7 +195,21 @@ public class NoUser implements Serializable { } public String createHashString() { - return Base64.encodeBase64String(this.createHash()); + return Base64.encodeBase64URLSafeString(this.createHash()); } + @Override + public 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 5e7986b..a613e5a 100644 --- a/src/nodash/test/NoCoreTest.java +++ b/src/nodash/test/NoCoreTest.java @@ -9,6 +9,7 @@ import nodash.core.NoCore; import nodash.core.NoDefaultAdapter; import nodash.core.NoRegister; import nodash.exceptions.NoAdapterException; +import nodash.exceptions.NoDashFatalException; import nodash.exceptions.NoDashSessionBadUuidException; import nodash.exceptions.NoSessionAlreadyAwaitingConfirmationException; import nodash.exceptions.NoSessionConfirmedException; @@ -18,7 +19,9 @@ import nodash.exceptions.NoSessionNotChangedException; import nodash.exceptions.NoUserAlreadyOnlineException; import nodash.exceptions.NoUserNotValidException; import nodash.models.NoUser; +import nodash.models.NoSession.NoState; +import org.apache.commons.codec.binary.Base64; import org.junit.Test; public class NoCoreTest { @@ -36,7 +39,7 @@ public class NoCoreTest { NoRegister registration2 = core.register(user2, "password".toCharArray()); assertFalse(Arrays.equals(registration1.cookie, registration2.cookie)); - assertFalse(Arrays.equals(registration2.data, registration2.data)); + assertFalse(Arrays.equals(registration1.data, registration2.data)); NoUser user3 = new NoUser(); try { @@ -64,7 +67,8 @@ public class NoCoreTest { @Test public void testSaveAndConfirm() throws NoSessionExpiredException, NoSessionConfirmedException, NoSessionNotAwaitingConfirmationException, NoUserNotValidException, - NoDashSessionBadUuidException, NoUserAlreadyOnlineException, NoSessionNotChangedException, NoSessionAlreadyAwaitingConfirmationException, NoAdapterException { + NoDashSessionBadUuidException, NoUserAlreadyOnlineException, NoSessionNotChangedException, + NoSessionAlreadyAwaitingConfirmationException, NoAdapterException { NoAdapter adapter = new NoDefaultAdapter(); NoCore core = new NoCore(adapter); @@ -74,7 +78,7 @@ public class NoCoreTest { core.confirm(registration.cookie, "password".toCharArray(), newUserFile); byte[] newUserHash = newUser.createHash(); adapter.checkHash(newUserHash); - + NoUser newUserBadPass = new NoUser(); registration = core.register(newUserBadPass, "password".toCharArray()); byte[] newUserBadPassFile = Arrays.copyOf(registration.data, registration.data.length); @@ -82,27 +86,36 @@ public class NoCoreTest { core.confirm(registration.cookie, "badpassword".toCharArray(), newUserBadPassFile); fail("Confirmed with a bad password without throwing an exception."); } catch (NoUserNotValidException e) { - // Do nothing, true + // Do nothing, true + } + + byte[] badCookie = Arrays.copyOf(registration.cookie, registration.cookie.length); + badCookie[0] = (byte) (badCookie[0] == 'A' ? 'B' : 'A'); + try { + core.confirm(badCookie, "password".toCharArray(), newUserBadPassFile); + fail("Confirmed on bad cookie without throwing fatal exception."); + } catch (NoSessionExpiredException e) { + // Do nothing, correct } try { - core.confirm(new byte[] {'b', 'a', 'd', 'c', 'o', 'o', 'k', 'i', 'e'}, "password".toCharArray(), - newUserBadPassFile); + core.confirm(new byte[] {'b', 'a', 'd', 'c', 'o', 'o', 'k', 'i', 'e'}, + "password".toCharArray(), newUserBadPassFile); fail("Confirmed on bad cookie without throwing exception."); - } catch (NoSessionExpiredException e) { + } catch (NoDashFatalException e) { // Do nothing, true } - + NoUser oldUser = new NoUser(); registration = core.register(oldUser, "password".toCharArray()); byte[] oldUserFile = Arrays.copyOf(registration.data, registration.data.length); core.confirm(registration.cookie, "password".toCharArray(), oldUserFile); - + oldUserFile = Arrays.copyOf(registration.data, registration.data.length); byte[] oldUserCookie = core.login(oldUserFile, "password".toCharArray()); assertNotNull(adapter.getNoSession(oldUserCookie)); oldUser.createFile("password".toCharArray()); // Touch the randomizer - + NoUser oldUserRevisited = core.getNoUser(oldUserCookie); byte[] currentHash = oldUserRevisited.createHash(); oldUserRevisited.createFile("password".toCharArray()); @@ -121,18 +134,79 @@ public class NoCoreTest { @Test - public void testGetUser() { - fail("Not yet implemented"); + public void testGetUser() throws NoSessionExpiredException, NoSessionConfirmedException, + NoSessionNotAwaitingConfirmationException, NoUserNotValidException, + NoUserAlreadyOnlineException { + NoCore core = new NoCore(new NoDefaultAdapter()); + NoUser user = new NoUser(); + NoRegister registration = core.register(user, "password".toCharArray()); + byte[] file = Arrays.copyOf(registration.data, registration.data.length); + core.confirm(registration.cookie, "password".toCharArray(), file); + + file = Arrays.copyOf(registration.data, registration.data.length); + byte[] cookie = core.login(file, "password".toCharArray()); + byte[] badCookie = Arrays.copyOf(cookie, cookie.length); + badCookie[0] = (byte) (badCookie[0] == 'A' ? 'B' : 'A'); + NoUser user2 = core.getNoUser(cookie); + assertNotNull(user2); + assertEquals(user, user2); + + try { + core.getNoUser(badCookie); + fail("Did not fail when given a bad cookie."); + } catch (NoSessionExpiredException e) { + // Correct, do nothing. + } } @Test - public void testGetSessionState() { - fail("Not yet implemented"); + public void testGetSessionState() throws NoSessionExpiredException, NoSessionConfirmedException, + NoSessionNotAwaitingConfirmationException, NoUserNotValidException, + NoUserAlreadyOnlineException, NoSessionNotChangedException, NoSessionAlreadyAwaitingConfirmationException { + NoCore core = new NoCore(new NoDefaultAdapter()); + NoUser user = new NoUser(); + NoRegister registration = core.register(user, "password".toCharArray()); + assertEquals(core.getSessionState(registration.cookie), NoState.AWAITING_CONFIRMATION); + byte[] file = Arrays.copyOf(registration.data, registration.data.length); + core.confirm(registration.cookie, "password".toCharArray(), file); + + file = Arrays.copyOf(registration.data, registration.data.length); + byte[] cookie = core.login(file, "password".toCharArray()); + assertEquals(core.getSessionState(cookie), NoState.IDLE); + user = core.getNoUser(cookie); + user.createFile("password".toCharArray()); // touch randomizer + assertEquals(core.getSessionState(cookie), NoState.MODIFIED); + + file = core.save(cookie, "password".toCharArray()); + assertEquals(core.getSessionState(cookie), NoState.AWAITING_CONFIRMATION); + + core.confirm(cookie, "password".toCharArray(), file); + try { + core.getSessionState(cookie); + fail("Didn't fail on getting session after confirm."); + } catch (NoSessionExpiredException e) { + // Do nothing, correct + } } @Test - public void testShred() { - fail("Not yet implemented"); + public void testShred() throws NoAdapterException, NoSessionConfirmedException, NoSessionExpiredException { + NoAdapter adapter = new NoDefaultAdapter(); + NoCore core = new NoCore(adapter); + NoUser user = new NoUser(); + NoRegister registration = core.register(user, "password".toCharArray()); + assertTrue(adapter.isOnline(user.createHash())); + assertEquals(core.getSessionState(registration.cookie), NoState.AWAITING_CONFIRMATION); + + core.shred(registration.cookie); + + assertFalse(adapter.isOnline(user.createHash())); + try { + core.getNoUser(registration.cookie); + fail("Returned a user object after shredding."); + } catch (NoSessionExpiredException e) { + // Do nothing, correct + } } } diff --git a/src/nodash/test/NoDashBasicTests.java b/src/nodash/test/NoDashBasicTests.java new file mode 100644 index 0000000..98a28d1 --- /dev/null +++ b/src/nodash/test/NoDashBasicTests.java @@ -0,0 +1,11 @@ +package nodash.test; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({NoCoreTest.class, NoSessionTest.class, NoUserTest.class, NoUtilTest.class}) +public class NoDashBasicTests { + +} diff --git a/src/nodash/test/NoSessionTest.java b/src/nodash/test/NoSessionTest.java index 078bd9b..84fb63a 100644 --- a/src/nodash/test/NoSessionTest.java +++ b/src/nodash/test/NoSessionTest.java @@ -1,65 +1,71 @@ package nodash.test; import static org.junit.Assert.*; + +import java.util.Arrays; + import nodash.exceptions.NoSessionConfirmedException; import nodash.exceptions.NoSessionExpiredException; +import nodash.exceptions.NoUserNotValidException; import nodash.models.NoSession; +import nodash.models.NoSession.NoState; +import nodash.models.NoUser; import org.junit.Test; public class NoSessionTest { @Test - public void testNoSession() throws NoSessionConfirmedException, NoSessionExpiredException { - NoSession session = new NoSession(); + public void testNoSessionNoUser() throws NoSessionConfirmedException, NoSessionExpiredException { + NoUser user = new NoUser(); + NoSession session = new NoSession(user); + assertNotNull(session.getNoUser()); assertNotNull(session.getUuid()); - assertNull(session.getNoUser()); + assertNotNull(session.getEncryptedUuid()); + assertNull(session.getIncoming()); + assertNull(session.getOriginalHash()); + assertEquals(session.getNoUser(), user); + assertEquals(session.getNoState(), NoState.MODIFIED); + try { + new NoSession(null); + fail("Did not throw NullPointerException when given a null user."); + } catch (NullPointerException e) { + // Do nothing, correct + } } - @Test - public void testNoSessionNoUser() { - fail("Not yet implemented"); - } - - @Test - public void testNoSessionByteArrayCharArray() { - fail("Not yet implemented"); - } - - @Test - public void testCheck() { - fail("Not yet implemented"); - } - - @Test - public void testTouchState() { - fail("Not yet implemented"); - } - - @Test - public void testInitiateSaveAttempt() { - fail("Not yet implemented"); - } - - @Test - public void testConfirmSave() { - fail("Not yet implemented"); - } - - @Test - public void testDecryptUuid() { - fail("Not yet implemented"); - } - - @Test - public void testConsume() { - fail("Not yet implemented"); - } - - @Test - public void testClose() { - fail("Not yet implemented"); + public void testNoSessionByteArrayCharArray() throws NoUserNotValidException, + NoSessionExpiredException, NoSessionConfirmedException { + NoUser user = new NoUser(); + final byte[] userFile1 = user.createFile("password".toCharArray()); + byte[] userFile2 = Arrays.copyOf(userFile1, userFile1.length); + char[] userPassword = "password".toCharArray(); + NoSession session = new NoSession(userFile2, userPassword); + assertFalse(Arrays.equals(userFile1, userFile2)); + assertFalse(Arrays.equals("password".toCharArray(), userPassword)); + assertNotNull(session.getNoUser()); + assertNotNull(session.getOriginalHash()); + assertNotNull(session.getUuid()); + assertNull(session.getIncoming()); + assertEquals(session.getNoUser(), user); + assertEquals(session.getNoState(), NoState.IDLE); + + byte[] badUserFile = Arrays.copyOf(userFile1, userFile1.length); + badUserFile[0] = (byte) (badUserFile[0] == 'A' ? 'B' : 'A'); + try { + new NoSession(badUserFile, "password".toCharArray()); + fail("Did not throw NoUserNotValidException when given bad file."); + } catch (NoUserNotValidException e) { + // Do nothing, correct + } + + try { + new NoSession(Arrays.copyOf(userFile2, userFile2.length), "badpassword".toCharArray()); + fail("Did not throw NoUserNotValidException when given bad password."); + } catch (NoUserNotValidException e) { + // Do nothing, correct + } } } diff --git a/src/nodash/test/NoUserTest.java b/src/nodash/test/NoUserTest.java index b71b29a..78d91e3 100644 --- a/src/nodash/test/NoUserTest.java +++ b/src/nodash/test/NoUserTest.java @@ -59,7 +59,7 @@ public class NoUserTest { byte[] hash = user.createHash(); String hashString = user.createHashString(); - assertEquals(Base64.encodeBase64String(hash), hashString); + assertEquals(Base64.encodeBase64URLSafeString(hash), hashString); } @Test