From 0720193d9b545b741c0be6a285b564af6139f13a Mon Sep 17 00:00:00 2001 From: Jai Date: Sun, 9 Feb 2020 17:47:18 +0100 Subject: [PATCH] Fixed TimeBounds.cs error message. Fixed WebAuthentication VerifyTransactionThreshold method. Added a lot of the tests of WebAuthentication (Still missing some) --- .../WebAuthenticationTest.cs | 496 ++++++++++++++++++ stellar-dotnet-sdk/TimeBounds.cs | 6 +- stellar-dotnet-sdk/WebAuthentication.cs | 4 +- 3 files changed, 501 insertions(+), 5 deletions(-) diff --git a/stellar-dotnet-sdk-test/WebAuthenticationTest.cs b/stellar-dotnet-sdk-test/WebAuthenticationTest.cs index c539a4d2d..0233b0464 100644 --- a/stellar-dotnet-sdk-test/WebAuthenticationTest.cs +++ b/stellar-dotnet-sdk-test/WebAuthenticationTest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using stellar_dotnet_sdk; @@ -320,5 +322,499 @@ private void CheckOperation(Transaction tx, string clientAccountId) Assert.AreEqual(48, bytes.Length); } + + [TestMethod] + public void TestReadChallengeTransactionValidSignedByServerAndClient() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + transaction.Sign(clientKeypair); + + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + + Assert.AreEqual(clientKeypair.AccountId, readTransactionID); + } + + [TestMethod] + public void TestReadChallengeTransactionValidSignedByServer() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + + Assert.AreEqual(clientKeypair.AccountId, readTransactionID); + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidNotSignedByServer() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by server")); + } + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidServerAccountIDMismatch() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(KeyPair.Random().Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction source must be serverAccountId")); + } + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidSequenceNoNotZero() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, 1234); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction sequence number must be 0")); + } + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidTooManyOperations() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction must contain one operation")); + } + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidOperationWrongType() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var operation = new BumpSequenceOperation.Builder(100).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction operation must be of type ManageDataOperation")); + } + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidOperationNoSourceAccount() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction operation must have source account")); + } + } + + [TestMethod] + public void TestReadChallengeTransactionInvalidDataValueWrongEncodedLength() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?AAAAAAAAAAAAAAAAAAAAAAAAAA"); + var base64Data = plainTextBytes; + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + + + try + { + var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction operation data must be base64 encoded")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdInvalidServer() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(clientKeypair); + + var threshold = 1; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 1 } + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()); + } + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by server")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdValidServerAndClientKeyMeetingThreshold() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + transaction.Sign(clientKeypair); + + var threshold = 1; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 1 } + }; + + var wantSigners = new string[1] + { + clientKeypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTxThresholdValidServerAndMultipleClientKeyMeetingThreshold() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + var client2Keypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + transaction.Sign(clientKeypair); + transaction.Sign(client2Keypair); + + var threshold = 3; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 1 }, + { client2Keypair.Address, 2 } + }; + + var wantSigners = new string[2] + { + clientKeypair.Address, + client2Keypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdValidServerAndMultipleClientKeyMeetingThresholdSomeUnused() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + var client2Keypair = KeyPair.Random(); + var client3Keypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + transaction.Sign(clientKeypair); + transaction.Sign(client2Keypair); + + var threshold = 3; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 1 }, + { client2Keypair.Address, 2 }, + { client3Keypair.Address, 2 } + }; + + var wantSigners = new string[2] + { + clientKeypair.Address, + client2Keypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdInvalidServerAndMultipleClientKeyNotMeetingThreshold() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + var client2Keypair = KeyPair.Random(); + var client3Keypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + transaction.Sign(clientKeypair); + transaction.Sign(client2Keypair); + + var threshold = 10; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 1 }, + { client2Keypair.Address, 2 }, + { client3Keypair.Address, 2 } + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + } + + catch(Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Signers with weight 3 do not meet threshold 10")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdInvalidClientKeyUnrecognized() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + var client2Keypair = KeyPair.Random(); + var client3Keypair = KeyPair.Random(); + + var txSource = new Account(serverKeypair.Address, -1); + var opSource = new Account(clientKeypair.Address, 0); + + var plainTextBytes = Encoding.UTF8.GetBytes(new string(' ', 48)); + var base64Data = Encoding.ASCII.GetBytes(Convert.ToBase64String(plainTextBytes)); + + var operation = new ManageDataOperation.Builder("testserver auth", base64Data).SetSourceAccount(opSource.KeyPair).Build(); + var transaction = new Transaction.Builder(txSource).AddOperation(operation).AddTimeBounds(new TimeBounds(DateTimeOffset.Now, DateTimeOffset.Now.AddSeconds(1000))).Build(); + + transaction.Sign(serverKeypair); + transaction.Sign(clientKeypair); + transaction.Sign(client2Keypair); + transaction.Sign(client3Keypair); + + var threshold = 3; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 1 }, + { client2Keypair.Address, 2 }, + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction has unrecognized signatures")); + } + } } } \ No newline at end of file diff --git a/stellar-dotnet-sdk/TimeBounds.cs b/stellar-dotnet-sdk/TimeBounds.cs index 2b299bb0c..b994a6390 100644 --- a/stellar-dotnet-sdk/TimeBounds.cs +++ b/stellar-dotnet-sdk/TimeBounds.cs @@ -19,7 +19,7 @@ public class TimeBounds public TimeBounds(ulong minTime, ulong maxTime) { if (maxTime != 0 && minTime >= maxTime) - throw new ArgumentException("minTime must be >= maxTime"); + throw new ArgumentException("minTime must be < maxTime"); _minTime = minTime; _maxTime = maxTime; @@ -32,7 +32,7 @@ public TimeBounds(long minTime, long maxTime) if (maxTime < 0) throw new ArgumentException("maxTime must be >= 0"); if (maxTime != 0 && minTime >= maxTime) - throw new ArgumentException("minTime must be >= maxTime"); + throw new ArgumentException("minTime must be < maxTime"); _minTime = (ulong) minTime; _maxTime = (ulong) maxTime; } @@ -45,7 +45,7 @@ public TimeBounds(long minTime, long maxTime) public TimeBounds(DateTimeOffset? minTime = null, DateTimeOffset? maxTime = null) { if (maxTime != null && minTime >= maxTime) - throw new ArgumentException("minTime must be >= maxTime"); + throw new ArgumentException("minTime must be < maxTime"); var minEpoch = minTime?.ToUnixTimeSeconds() ?? 0; var maxEpoch = maxTime?.ToUnixTimeSeconds() ?? 0; diff --git a/stellar-dotnet-sdk/WebAuthentication.cs b/stellar-dotnet-sdk/WebAuthentication.cs index 4ef23c7fc..67aeb704c 100644 --- a/stellar-dotnet-sdk/WebAuthentication.cs +++ b/stellar-dotnet-sdk/WebAuthentication.cs @@ -183,9 +183,9 @@ public static class WebAuthentication var serverKeypair = KeyPair.FromAccountId(serverAccountId); var clientSigners = signers.Where(signer => signer != serverKeypair.Address).ToList(); - var allSigners = clientSigners.Select(signer => signer.Clone()).ToList(); + var allSigners = clientSigners.Select(signer => signer.Clone() as string).ToList(); allSigners.Add(serverKeypair.Address); - var allSignersFound = VerifyTransactionSignatures(transaction, signers, network); + var allSignersFound = VerifyTransactionSignatures(transaction, allSigners, network); var serverSigner = allSignersFound.FirstOrDefault(signer => signer == serverKeypair.Address); if (serverSigner is null)