added session tests

This commit is contained in:
Kevin Froman 2020-05-30 03:06:08 -05:00
parent fd47ade07b
commit 3cb4da4004
No known key found for this signature in database
GPG Key ID: BF1CE85F428B8CE3
8 changed files with 326 additions and 24 deletions

View File

@ -0,0 +1,75 @@
using NUnit.Framework;
using System;
using System.Linq;
using System.Threading;
using chestcrypto.session;
using chestcrypto.exceptions;
using Sodium;
namespace sessionPrivateTestsCleaning
{
public class Tests
{
[SetUp]
public void Setup()
{
}
public long getFutureTime(int seconds){return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + (long) seconds;}
[Test]
public void TestSessionCleanPrivate(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PrivateKey;
Session session = new Session(privateK, publicK, true, 5);
session.setMinimumKeyExpireSeconds(1);
session.setMessageDelay((long) 1);
session.addPrivate(newK, getFutureTime(2));
bool atLeastOneLoop = false;
while(true){
try{
if (Enumerable.SequenceEqual(session.getLatestPrivateKey(), newK)){
Thread.Sleep(25); // ms
atLeastOneLoop = true; // key should not be deleted instantly
continue;
}
}
catch(System.ArgumentOutOfRangeException){
break;
}
session.cleanPrivate();
}
Assert.IsTrue(atLeastOneLoop);
}
[Test]
public void TestSessionCleanPublic(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true, 5);
session.setMinimumKeyExpireSeconds(1);
session.setMessageDelay((long) 1);
session.addPublic(newK, getFutureTime(2));
bool atLeastOneLoop = false;
while(true){
try{
if (Enumerable.SequenceEqual(session.getLatestPublicKey(), newK)){
Thread.Sleep(25); // ms
atLeastOneLoop = true; // key should not be deleted instantly
continue;
}
}
catch(System.ArgumentOutOfRangeException){
break;
}
session.cleanPublic();
}
Assert.IsTrue(atLeastOneLoop);
}
}
}

View File

@ -0,0 +1,35 @@
using NUnit.Framework;
using System;
using System.Linq;
using System.Threading;
using chestcrypto.session;
using chestcrypto.exceptions;
using Sodium;
namespace sessionTestEncrypt
{
public class Tests
{
[SetUp]
public void Setup()
{
}
public long getFutureTime(int seconds){return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + (long) seconds;}
[Test]
public void TestEncrypt(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte message = ""
Session session = new Session(privateK, publicK, true, 5);
SessionCrypto sessionCrypto = new SessionCrypto(session);
session.setMinimumKeyExpireSeconds(1);
session.setMessageDelay((long) 1);
session.addPublic(newK, getFutureTime(9));
sessionCrypto.encrypt()
}
}
}

View File

@ -0,0 +1,55 @@
using NUnit.Framework;
using System;
using System.Linq;
using chestcrypto.session;
using chestcrypto.exceptions;
using Sodium;
namespace sessionPrivateTests
{
public class Tests
{
[SetUp]
public void Setup()
{
}
public long getFutureTime(int seconds){return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + (long) seconds;}
[Test]
public void TestSessionAddValidPrivate(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PrivateKey;
Session session = new Session(privateK, publicK, true, 5);
session.addPrivate(newK, getFutureTime(670));
Assert.IsTrue(Enumerable.SequenceEqual(newK, session.getLatestPrivateKey()));
}
[Test]
public void TestSessionAddInvalidPrivateTime(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PrivateKey;
Session session = new Session(privateK, publicK, true, 5);
try{
session.addPrivate(newK, getFutureTime(1));
}
catch(System.ArgumentOutOfRangeException){return;}
Assert.Fail();
}
[Test]
public void TestSessionAddInvalidPrivate(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = {5, 3, 2, 1};
Session session = new Session(privateK, publicK, true, 5);
try{
session.addPrivate(newK, getFutureTime(7010));
}
catch(InvalidKeyLength){return;}
Assert.Fail();
}
}
}

View File

@ -14,8 +14,19 @@ namespace sessionTests
{ {
} }
public long getFutureTime(int seconds){ public long getFutureTime(int seconds){return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + (long) seconds;}
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + (long) seconds;
[Test]
public void TestSessionGetLatestPublic(){
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true, 5);
for (int i = 0; i < 5; i++){
session.addPublic(PublicKeyBox.GenerateKeyPair().PublicKey, getFutureTime(630));
}
session.addPublic(newK, getFutureTime(650));
Assert.IsTrue(Enumerable.SequenceEqual(newK, session.getLatestPublicKey()));
} }
[Test] [Test]
@ -23,8 +34,8 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey; byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true); Session session = new Session(privateK, publicK, true, 5);
session.addPublic(newK, getFutureTime(61)); session.addPublic(newK, getFutureTime(610));
Assert.IsTrue(Enumerable.SequenceEqual(newK, session.getLatestPublicKey())); Assert.IsTrue(Enumerable.SequenceEqual(newK, session.getLatestPublicKey()));
} }
@ -33,10 +44,10 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey; byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true); Session session = new Session(privateK, publicK, true, 5);
session.addPublic(newK, getFutureTime(61)); session.addPublic(newK, getFutureTime(615));
try{ try{
session.addPublic(newK, getFutureTime(61)); session.addPublic(newK, getFutureTime(615));
} }
catch(DuplicatePublicKey){return;} catch(DuplicatePublicKey){return;}
Assert.Fail(); Assert.Fail();
@ -47,7 +58,7 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey; byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = {3, 5}; byte[] newK = {3, 5};
Session session = new Session(privateK, publicK, true); Session session = new Session(privateK, publicK, true, 5);
try{ try{
session.addPublic(newK, getFutureTime(61)); session.addPublic(newK, getFutureTime(61));
} }
@ -62,7 +73,7 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey; byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true); Session session = new Session(privateK, publicK, true, 5);
try{ try{
session.addPublic(newK, getFutureTime(-1)); session.addPublic(newK, getFutureTime(-1));
} }
@ -77,11 +88,11 @@ namespace sessionTests
{ {
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey; byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey; byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
Session session = new Session(privateK, publicK, true); Session session = new Session(privateK, publicK, true, 5);
byte[] invalid = {0, 0, 0}; byte[] invalid = {0, 0, 0};
try{ try{
new Session(invalid, publicK, true); new Session(invalid, publicK, true, 5);
} }
catch(InvalidKeyLength){ catch(InvalidKeyLength){
goto secondAssert; goto secondAssert;
@ -89,7 +100,7 @@ namespace sessionTests
Assert.Fail(); Assert.Fail();
secondAssert: secondAssert:
try{ try{
new Session(privateK, invalid, true); new Session(privateK, invalid, true, 5);
} }
catch(InvalidKeyLength){ catch(InvalidKeyLength){
return; return;

View File

@ -0,0 +1,18 @@
using Sodium;
using chestcrypto.session;
using chestcrypto;
namespace chestcrypto.session.crypto{
internal class SessionEncrypt{
public static byte[] Encrypt(Session activeSession, byte[] message){
byte[] publicKey = activeSession.getLatestPublicKey();
byte[] privateKey = activeSession.getLatestPrivateKey();
return Curve25519.encrypt(privateKey, publicKey, message);
}
}
}

View File

@ -18,6 +18,40 @@ namespace chestcrypto{
{ {
} }
} }
public class DuplicatePrivateKey : Exception
{
public DuplicatePrivateKey()
{
}
public DuplicatePrivateKey(string message)
: base(message)
{
}
public DuplicatePrivateKey(string message, Exception inner)
: base(message, inner)
{
}
}
public class NoSessionKeyAvailable : Exception
{
public NoSessionKeyAvailable()
{
}
public NoSessionKeyAvailable(string message)
: base(message)
{
}
public NoSessionKeyAvailable(string message, Exception inner)
: base(message, inner)
{
}
}
} }
} }

View File

@ -7,7 +7,7 @@ namespace chestcrypto{
namespace session{ namespace session{
internal class Session{ public class Session{
// Create List of tuples(time, byte[]) // Create List of tuples(time, byte[])
// Where the tuple contains a time stamp for expiry and a ed25519 key // Where the tuple contains a time stamp for expiry and a ed25519 key
@ -17,16 +17,29 @@ namespace chestcrypto{
private byte[] ourMasterPrivateKey; private byte[] ourMasterPrivateKey;
private byte[] theirMasterPublicKey; private byte[] theirMasterPublicKey;
private bool strictMode; private bool strictMode;
private const int minimumKeyExpireSeconds = 60;
private void validateKey(byte[] key){ private long messageDelay = 25;
private int minimumKeyExpireSeconds = 600;
private void validateKeyLength(byte[] key){
if (key.Length != 32){ if (key.Length != 32){
throw new InvalidKeyLength(); throw new InvalidKeyLength();
} }
} }
private long getEpoch(){
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
private void validateTimestamp(long ts){
if (ts < getEpoch() + minimumKeyExpireSeconds){
throw new ArgumentOutOfRangeException();
}
}
private bool publicKeyExists(byte[] key){ private bool publicKeyExists(byte[] key){
foreach( (int, byte[]) k in theirPublicKeys){ foreach((int, byte[]) k in theirPublicKeys){
if (Enumerable.SequenceEqual(k.Item2, key)){ if (Enumerable.SequenceEqual(k.Item2, key)){
return true; return true;
} }
@ -34,27 +47,88 @@ namespace chestcrypto{
return false; return false;
} }
public Session(byte[] masterPrivate, byte[] masterPublic, bool strictMode){ private bool privateKeyExists(byte[] key){
validateKey(masterPrivate); foreach((int, byte[]) k in ourPrivateKeys){
validateKey(masterPublic); if (Enumerable.SequenceEqual(k.Item2, key)){
return true;
}
}
return false;
}
public Session(byte[] masterPrivate, byte[] masterPublic, bool strictMode, long messageDelay){
validateKeyLength(masterPrivate);
validateKeyLength(masterPublic);
ourMasterPrivateKey = masterPrivate; ourMasterPrivateKey = masterPrivate;
theirMasterPublicKey = masterPublic; theirMasterPublicKey = masterPublic;
this.strictMode = strictMode; this.strictMode = strictMode;
this.messageDelay = messageDelay;
ourPrivateKeys = new List<(long, byte[])>(); ourPrivateKeys = new List<(long, byte[])>();
theirPublicKeys = new List<(long, byte[])>(); theirPublicKeys = new List<(long, byte[])>();
} }
public void addPublic(byte[] publicKey, long timestamp){ public void setMinimumKeyExpireSeconds(int newSeconds){minimumKeyExpireSeconds = newSeconds;}
validateKey(publicKey); public void setMessageDelay(long newDelay){
if (publicKeyExists(publicKey)){throw new DuplicatePublicKey();} messageDelay = newDelay;
if (timestamp < DateTimeOffset.UtcNow.ToUnixTimeSeconds() + minimumKeyExpireSeconds){
throw new ArgumentOutOfRangeException();
} }
public void addPublic(byte[] publicKey, long timestamp){
timestamp -= messageDelay; // Subtract some time from the specified timestamp because we don't want to use it close to expiry
validateKeyLength(publicKey);
validateTimestamp(timestamp);
if (publicKeyExists(publicKey)){throw new DuplicatePublicKey();}
theirPublicKeys.Add((timestamp, publicKey)); theirPublicKeys.Add((timestamp, publicKey));
} }
public byte[] getLatestPublicKey(){return theirPublicKeys[theirPublicKeys.Count - 1].Item2;} public byte[] getLatestPublicKey(){
if (theirPublicKeys.Count == 0 && strictMode)
throw new NoSessionKeyAvailable();
var key = theirPublicKeys[theirPublicKeys.Count - 1];
validateTimestamp(key.Item1);
return key.Item2;
}
public byte[] getLatestPrivateKey(){
if (ourPrivateKeys.Count == 0 && strictMode)
throw new NoSessionKeyAvailable();
var key = ourPrivateKeys[ourPrivateKeys.Count -1];
validateTimestamp(key.Item1);
return key.Item2;
}
public void addPrivate(byte[] privateKey, long timestamp){
validateKeyLength(privateKey);
validateTimestamp(timestamp);
if (privateKeyExists(privateKey)){throw new DuplicatePrivateKey();}
ourPrivateKeys.Add((timestamp, privateKey));
}
public void cleanPublic(){
long epoch = getEpoch();
bool expired((long, byte[]) k){
if (k.Item1 > epoch){
return true;
}
return false;
}
theirPublicKeys.RemoveAll(expired); // remove all keys who are truthy with expired()
}
public void cleanPrivate(){
// Can't use predicate approach because we want to zero out private keys
List<int> remove = new List<int>();
for (int i = 0; i < ourPrivateKeys.Count; i++){
if (ourPrivateKeys[i].Item1 > getEpoch()){
remove.Add(i);
// We manually clear memory to reduce attack surface a tiny bit (GC may take too long)
Array.Clear(ourPrivateKeys[i].Item2, 0, ourPrivateKeys[i].Item2.Length);
}
}
foreach(int i in remove){
ourPrivateKeys.RemoveAt((int) i);
}
}
} }