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){
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + (long) seconds;
public long getFutureTime(int 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]
@ -23,8 +34,8 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true);
session.addPublic(newK, getFutureTime(61));
Session session = new Session(privateK, publicK, true, 5);
session.addPublic(newK, getFutureTime(610));
Assert.IsTrue(Enumerable.SequenceEqual(newK, session.getLatestPublicKey()));
}
@ -33,10 +44,10 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true);
session.addPublic(newK, getFutureTime(61));
Session session = new Session(privateK, publicK, true, 5);
session.addPublic(newK, getFutureTime(615));
try{
session.addPublic(newK, getFutureTime(61));
session.addPublic(newK, getFutureTime(615));
}
catch(DuplicatePublicKey){return;}
Assert.Fail();
@ -47,7 +58,7 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = {3, 5};
Session session = new Session(privateK, publicK, true);
Session session = new Session(privateK, publicK, true, 5);
try{
session.addPublic(newK, getFutureTime(61));
}
@ -62,7 +73,7 @@ namespace sessionTests
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
byte[] privateK = PublicKeyBox.GenerateKeyPair().PrivateKey;
byte[] newK = PublicKeyBox.GenerateKeyPair().PublicKey;
Session session = new Session(privateK, publicK, true);
Session session = new Session(privateK, publicK, true, 5);
try{
session.addPublic(newK, getFutureTime(-1));
}
@ -77,11 +88,11 @@ namespace sessionTests
{
byte[] publicK = PublicKeyBox.GenerateKeyPair().PublicKey;
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};
try{
new Session(invalid, publicK, true);
new Session(invalid, publicK, true, 5);
}
catch(InvalidKeyLength){
goto secondAssert;
@ -89,7 +100,7 @@ namespace sessionTests
Assert.Fail();
secondAssert:
try{
new Session(privateK, invalid, true);
new Session(privateK, invalid, true, 5);
}
catch(InvalidKeyLength){
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{
internal class Session{
public class Session{
// Create List of tuples(time, byte[])
// Where the tuple contains a time stamp for expiry and a ed25519 key
@ -17,16 +17,29 @@ namespace chestcrypto{
private byte[] ourMasterPrivateKey;
private byte[] theirMasterPublicKey;
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){
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){
foreach( (int, byte[]) k in theirPublicKeys){
foreach((int, byte[]) k in theirPublicKeys){
if (Enumerable.SequenceEqual(k.Item2, key)){
return true;
}
@ -34,27 +47,88 @@ namespace chestcrypto{
return false;
}
public Session(byte[] masterPrivate, byte[] masterPublic, bool strictMode){
validateKey(masterPrivate);
validateKey(masterPublic);
private bool privateKeyExists(byte[] key){
foreach((int, byte[]) k in ourPrivateKeys){
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;
theirMasterPublicKey = masterPublic;
this.strictMode = strictMode;
this.messageDelay = messageDelay;
ourPrivateKeys = new List<(long, byte[])>();
theirPublicKeys = new List<(long, byte[])>();
}
public void setMinimumKeyExpireSeconds(int newSeconds){minimumKeyExpireSeconds = newSeconds;}
public void setMessageDelay(long newDelay){
messageDelay = newDelay;
}
public void addPublic(byte[] publicKey, long timestamp){
validateKey(publicKey);
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();}
if (timestamp < DateTimeOffset.UtcNow.ToUnixTimeSeconds() + minimumKeyExpireSeconds){
throw new ArgumentOutOfRangeException();
}
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);
}
}
}