added session tests
This commit is contained in:
parent
fd47ade07b
commit
3cb4da4004
75
tests/session/testSessionClean.cs
Normal file
75
tests/session/testSessionClean.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
35
tests/session/testSessionEncrypt.cs
Normal file
35
tests/session/testSessionEncrypt.cs
Normal 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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
55
tests/session/testSessionPrivate.cs
Normal file
55
tests/session/testSessionPrivate.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
18
treasurechest/chestcrypto/session/crypto/encrypt.cs
Normal file
18
treasurechest/chestcrypto/session/crypto/encrypt.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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 addPublic(byte[] publicKey, long timestamp){
|
||||
validateKey(publicKey);
|
||||
if (publicKeyExists(publicKey)){throw new DuplicatePublicKey();}
|
||||
if (timestamp < DateTimeOffset.UtcNow.ToUnixTimeSeconds() + minimumKeyExpireSeconds){
|
||||
throw new ArgumentOutOfRangeException();
|
||||
public void setMinimumKeyExpireSeconds(int newSeconds){minimumKeyExpireSeconds = newSeconds;}
|
||||
public void setMessageDelay(long newDelay){
|
||||
messageDelay = newDelay;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user