From 92f9c66f90d45065334cdd2621060679ebe9ea26 Mon Sep 17 00:00:00 2001 From: Francesco Ceccon Date: Thu, 6 Feb 2020 21:23:06 +0000 Subject: [PATCH 1/4] Update web authentication to SEP 10 v1.3.0 --- stellar-dotnet-sdk/WebAuthentication.cs | 150 +++++++++++++++++++++--- 1 file changed, 135 insertions(+), 15 deletions(-) diff --git a/stellar-dotnet-sdk/WebAuthentication.cs b/stellar-dotnet-sdk/WebAuthentication.cs index eed592092..4ef23c7fc 100644 --- a/stellar-dotnet-sdk/WebAuthentication.cs +++ b/stellar-dotnet-sdk/WebAuthentication.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text; namespace stellar_dotnet_sdk @@ -71,7 +73,7 @@ public static class WebAuthentication } /// - /// Verify that a transaction is a valid Stellar Web Authentication transaction. + /// Read a SEP 10 challenge transaction and return the client account id. /// /// Performs the following checks: /// @@ -80,15 +82,15 @@ public static class WebAuthentication /// 3. Transaction has one operation only, of type ManageDataOperation /// 4. The ManageDataOperation name and value are correct /// 5. Transaction time bounds are still valid - /// 6. Transaction is signed by server and client + /// 6. Transaction is signed by server /// /// The challenge transaction /// The server account id /// The network the transaction was submitted to, defaults to Network.Current /// Current time, defaults to DateTimeOffset.Now - /// True if the transaction is valid + /// The client account id /// - public static bool VerifyChallengeTransaction(Transaction transaction, string serverAccountId, + public static string ReadChallengeTransaction(Transaction transaction, string serverAccountId, Network network = null, DateTimeOffset? now = null) { network = network ?? Network.Current; @@ -105,11 +107,18 @@ public static class WebAuthentication var operation = transaction.Operations[0] as ManageDataOperation; if (operation is null) - throw new InvalidWebAuthenticationException("Challenge transaction operation must be of type ManageDataOperation"); + throw new InvalidWebAuthenticationException( + "Challenge transaction operation must be of type ManageDataOperation"); + + if (operation.SourceAccount is null) + throw new InvalidWebAuthenticationException("Challenge transaction operation must have source account"); + + var clientAccountId = operation.SourceAccount.Address; var stringValue = Encoding.UTF8.GetString(operation.Value); if (stringValue.Length != 64) - throw new InvalidWebAuthenticationException("Challenge transaction operation data must be 64 bytes long"); + throw new InvalidWebAuthenticationException( + "Challenge transaction operation data must be 64 bytes long"); try { @@ -118,28 +127,112 @@ public static class WebAuthentication } catch (System.FormatException) { - throw new InvalidWebAuthenticationException("Challenge transaction operation data must be base64 encoded"); + throw new InvalidWebAuthenticationException( + "Challenge transaction operation data must be base64 encoded"); } if (!ValidateSignedBy(transaction, serverAccountId, network)) throw new InvalidWebAuthenticationException("Challenge transaction not signed by server"); - if (!ValidateSignedBy(transaction, operation.SourceAccount.AccountId, network)) - throw new InvalidWebAuthenticationException("Challenge transaction not signed by client"); - if (!ValidateTimeBounds(transaction.TimeBounds, now ?? DateTimeOffset.Now)) throw new InvalidWebAuthenticationException("Challenge transaction expired"); + return clientAccountId; + } + + public static ICollection VerifyChallengeTransactionThreshold(Transaction transaction, + string serverAccountId, + int threshold, Dictionary signerSummary, Network network = null, DateTimeOffset? now = null) + { + var signersFound = + VerifyChallengeTransactionSigners(transaction, serverAccountId, signerSummary.Keys.ToArray(), network, + now); + var weight = signersFound.Sum(signer => signerSummary[signer]); + if (weight < threshold) + throw new InvalidWebAuthenticationException( + $"Signers with weight {weight} do not meet threshold {threshold}"); + return signersFound; + } + + /// + /// Verify that all signers of a SEP 10 transaction are accounted for. + /// + /// A transaction is verified if it signed by the server account, and all other signatures match the provided + /// signers. Additional signers can be provided that do not have a signature, but all signatures must be + /// matched to a signer for verification to succeed. If verification succeeds, the list of signers that were + /// found is returned, excluding the server account id. + /// + /// The challenge transaction + /// The server account id + /// + /// The network the transaction was submitted to, defaults to Network.Current + /// Current time, defaults to DateTimeOffset.Now + /// + /// + public static string[] VerifyChallengeTransactionSigners(Transaction transaction, string serverAccountId, + ICollection signers, Network network = null, DateTimeOffset? now = null) + { + if (!signers.Any()) + throw new ArgumentException($"{nameof(signers)} must be non-empty"); + + network = network ?? Network.Current; + + ReadChallengeTransaction(transaction, serverAccountId, network, now); + + // Remove server signer if present + var serverKeypair = KeyPair.FromAccountId(serverAccountId); + var clientSigners = signers.Where(signer => signer != serverKeypair.Address).ToList(); + + var allSigners = clientSigners.Select(signer => signer.Clone()).ToList(); + allSigners.Add(serverKeypair.Address); + var allSignersFound = VerifyTransactionSignatures(transaction, signers, network); + + var serverSigner = allSignersFound.FirstOrDefault(signer => signer == serverKeypair.Address); + if (serverSigner is null) + throw new InvalidWebAuthenticationException("Challenge transaction not signed by server"); + + if (allSignersFound.Count != transaction.Signatures.Count) + throw new InvalidWebAuthenticationException("Challenge transaction has unrecognized signatures"); + + return allSignersFound.Where(signer => signer != serverSigner).ToArray(); + } + + /// + /// Verify that a transaction is a valid Stellar Web Authentication transaction. + /// + /// Performs the following checks: + /// + /// 1. Transaction sequence number is 0 + /// 2. Transaction source account is + /// 3. Transaction has one operation only, of type ManageDataOperation + /// 4. The ManageDataOperation name and value are correct + /// 5. Transaction time bounds are still valid + /// 6. Transaction is signed by server and client + /// + /// The challenge transaction + /// The server account id + /// The network the transaction was submitted to, defaults to Network.Current + /// Current time, defaults to DateTimeOffset.Now + /// True if the transaction is valid + /// + [Obsolete("Use VerifyChallengeTransactionThreshold and VerifyChallengeTransactionSigners")] + public static bool VerifyChallengeTransaction(Transaction transaction, string serverAccountId, + Network network = null, DateTimeOffset? now = null) + { + network = network ?? Network.Current; + + var clientAccountId = ReadChallengeTransaction(transaction, serverAccountId, network, now); + + if (!ValidateSignedBy(transaction, clientAccountId, network)) + throw new InvalidWebAuthenticationException("Challenge transaction not signed by client"); + return true; } private static bool ValidateSignedBy(Transaction transaction, string accountId, Network network) { - var transactionHash = transaction.Hash(network); - var keypair = KeyPair.FromAccountId(accountId); - - var signature = transaction.Signatures.Find(sig => keypair.Verify(transactionHash, sig.Signature)); - return signature != null; + var signaturesUsed = VerifyTransactionSignatures(transaction, new[] {accountId}, network); + return signaturesUsed.Count == 1; } private static bool ValidateTimeBounds(TimeBounds timeBounds, DateTimeOffset now) @@ -149,5 +242,32 @@ private static bool ValidateTimeBounds(TimeBounds timeBounds, DateTimeOffset now var unixNow = now.ToUnixTimeSeconds(); return timeBounds.MinTime <= unixNow && unixNow <= timeBounds.MaxTime; } + + private static ICollection VerifyTransactionSignatures(Transaction transaction, + IEnumerable signers, Network network) + { + var txHash = transaction.Hash(network); + var signaturesUsed = new Dictionary(); + + foreach (var signer in signers) + { + var keypair = KeyPair.FromAccountId(signer); + foreach (var signature in transaction.Signatures) + { + if (signaturesUsed.ContainsKey(signature)) + continue; + + if (!signature.Hint.InnerValue.SequenceEqual(keypair.SignatureHint.InnerValue)) + continue; + + if (keypair.Verify(txHash, signature.Signature)) + { + signaturesUsed[signature] = keypair.Address; + } + } + } + + return signaturesUsed.Values.ToArray(); + } } } \ No newline at end of file From 0720193d9b545b741c0be6a285b564af6139f13a Mon Sep 17 00:00:00 2001 From: Jai Date: Sun, 9 Feb 2020 17:47:18 +0100 Subject: [PATCH 2/4] 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) From e8b6a150881b0cbbd98e1e662a7ace8e92d764ca Mon Sep 17 00:00:00 2001 From: Jai Date: Sun, 9 Feb 2020 21:53:01 +0100 Subject: [PATCH 3/4] Added rest of WebAuthentication tests. Added error check in ValidateSigners. --- .../WebAuthenticationTest.cs | 581 ++++++++++++++++++ stellar-dotnet-sdk/WebAuthentication.cs | 5 +- 2 files changed, 585 insertions(+), 1 deletion(-) diff --git a/stellar-dotnet-sdk-test/WebAuthenticationTest.cs b/stellar-dotnet-sdk-test/WebAuthenticationTest.cs index 0233b0464..2824e0a3e 100644 --- a/stellar-dotnet-sdk-test/WebAuthenticationTest.cs +++ b/stellar-dotnet-sdk-test/WebAuthenticationTest.cs @@ -816,5 +816,586 @@ public void TestVerifyChallengeTransactionThresholdInvalidClientKeyUnrecognized( Assert.IsTrue(exception.Message.Contains("Challenge transaction has unrecognized signatures")); } } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdInvalidNoSigners() + { + 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() + { + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("signers must be non-empty")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionThresholdWeightsAddToMoreThan8Bits() + { + 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 = 1; + var signerSummary = new Dictionary() + { + { clientKeypair.Address, 255 }, + { client2Keypair.Address, 1 }, + }; + + 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 TestVerifyChallengeTransactionSignersInvalidServer() + { + 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, 255 }, + }; + + var wantSigners = new string[1] + { + clientKeypair.Address, + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by server")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersValidServerAndClientMasterKey() + { + 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 signers = new string[1] + { + clientKeypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()); + + Assert.AreEqual(clientKeypair.Address, signersFound[0]); + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersInvalidServerAndNoClient() + { + 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 signers = new string[1] + { + clientKeypair.Address + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersInvalidServerAndUnrecognizedClient() + { + Network.Use(Network.Test()); + + var serverKeypair = KeyPair.Random(); + var clientKeypair = KeyPair.Random(); + var unrecognizedKeypair = 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(unrecognizedKeypair); + + var signers = new string[1] + { + clientKeypair.Address + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersValidServerAndMultipleClientSigners() + { + 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 signers = new string[2] + { + clientKeypair.Address, + client2Keypair.Address + }; + + var wantSigners = new string[2] + { + clientKeypair.Address, + client2Keypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersValidServerAndMultipleClientSignersReverseOrder() + { + 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(client2Keypair); + transaction.Sign(clientKeypair); + + var signers = new string[2] + { + clientKeypair.Address, + client2Keypair.Address + }; + + var wantSigners = new string[2] + { + clientKeypair.Address, + client2Keypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersValidServerAndClientSignersNotMasterKey() + { + 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(client2Keypair); + + var signers = new string[1] + { + client2Keypair.Address + }; + + var wantSigners = new string[1] + { + client2Keypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersValidServerAndClientSignersIgnoresServerSigner() + { + 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(client2Keypair); + + var signers = new string[2] + { + serverKeypair.Address, + client2Keypair.Address + }; + + var wantSigners = new string[1] + { + client2Keypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersInvalidServerNoClientSignersIgnoresServerSigner() + { + 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); + + var signers = new string[2] + { + serverKeypair.Address, + client2Keypair.Address + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersValidServerAndClientSignersIgnoresDuplicateSigner() + { + 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 signers = new string[2] + { + clientKeypair.Address, + clientKeypair.Address + }; + + var wantSigners = new string[1] + { + clientKeypair.Address + }; + + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + + for (int i = 0; i < wantSigners.Length; i++) + { + Assert.AreEqual(signersFound[i], wantSigners[i]); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersInvalidServerAndClientSignersIgnoresDuplicateSignerInError() + { + 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(client2Keypair); + + var signers = new string[2] + { + clientKeypair.Address, + clientKeypair.Address + }; + + var wantSigners = new string[1] + { + client2Keypair.Address + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersInvalidServerAndClientSignersFailsDuplicateSignatures() + { + 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); + transaction.Sign(clientKeypair); + + var signers = new string[1] + { + clientKeypair.Address + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("Challenge transaction has unrecognized signatures")); + } + } + + [TestMethod] + public void TestVerifyChallengeTransactionSignersInvalidNoSigners() + { + 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 signers = new string[0] + { + }; + + try + { + var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); + } + + catch (Exception exception) + { + Assert.IsTrue(exception.Message.Contains("signers must be non-empty")); + } + } } } \ No newline at end of file diff --git a/stellar-dotnet-sdk/WebAuthentication.cs b/stellar-dotnet-sdk/WebAuthentication.cs index 67aeb704c..47ed0f2c6 100644 --- a/stellar-dotnet-sdk/WebAuthentication.cs +++ b/stellar-dotnet-sdk/WebAuthentication.cs @@ -191,9 +191,12 @@ public static class WebAuthentication if (serverSigner is null) throw new InvalidWebAuthenticationException("Challenge transaction not signed by server"); + if (allSignersFound.Count == 1) + throw new InvalidWebAuthenticationException("Challenge transaction not signed by client"); + if (allSignersFound.Count != transaction.Signatures.Count) throw new InvalidWebAuthenticationException("Challenge transaction has unrecognized signatures"); - + return allSignersFound.Where(signer => signer != serverSigner).ToArray(); } From d5a88fab713ba770e9aa5750b86ef5ee3c7f22df Mon Sep 17 00:00:00 2001 From: Francesco Ceccon Date: Sun, 9 Feb 2020 21:30:10 +0000 Subject: [PATCH 4/4] Fix CodeFactor issue --- stellar-dotnet-sdk-test/WebAuthenticationTest.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/stellar-dotnet-sdk-test/WebAuthenticationTest.cs b/stellar-dotnet-sdk-test/WebAuthenticationTest.cs index 2824e0a3e..b0ab198ca 100644 --- a/stellar-dotnet-sdk-test/WebAuthenticationTest.cs +++ b/stellar-dotnet-sdk-test/WebAuthenticationTest.cs @@ -393,7 +393,7 @@ public void TestReadChallengeTransactionInvalidNotSignedByServer() try { var readTransactionID = WebAuthentication.ReadChallengeTransaction(transaction, serverKeypair.AccountId, Network.Test()); - + } catch (Exception exception) { @@ -768,7 +768,6 @@ public void TestVerifyChallengeTransactionThresholdInvalidServerAndMultipleClien { 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")); @@ -810,7 +809,6 @@ public void TestVerifyChallengeTransactionThresholdInvalidClientKeyUnrecognized( { 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")); @@ -850,7 +848,6 @@ public void TestVerifyChallengeTransactionThresholdInvalidNoSigners() { var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("signers must be non-empty")); @@ -935,7 +932,6 @@ public void TestVerifyChallengeTransactionSignersInvalidServer() { var signersFound = WebAuthentication.VerifyChallengeTransactionThreshold(transaction, serverKeypair.AccountId, threshold, signerSummary, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by server")); @@ -968,7 +964,7 @@ public void TestVerifyChallengeTransactionSignersValidServerAndClientMasterKey() }; var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()); - + Assert.AreEqual(clientKeypair.Address, signersFound[0]); } @@ -1000,7 +996,6 @@ public void TestVerifyChallengeTransactionSignersInvalidServerAndNoClient() { var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); @@ -1037,7 +1032,6 @@ public void TestVerifyChallengeTransactionSignersInvalidServerAndUnrecognizedCli { var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); @@ -1237,7 +1231,6 @@ public void TestVerifyChallengeTransactionSignersInvalidServerNoClientSignersIgn { var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); @@ -1319,7 +1312,6 @@ public void TestVerifyChallengeTransactionSignersInvalidServerAndClientSignersIg { var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("Challenge transaction not signed by client")); @@ -1356,7 +1348,6 @@ public void TestVerifyChallengeTransactionSignersInvalidServerAndClientSignersFa { var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("Challenge transaction has unrecognized signatures")); @@ -1384,14 +1375,13 @@ public void TestVerifyChallengeTransactionSignersInvalidNoSigners() transaction.Sign(clientKeypair); var signers = new string[0] - { + { }; try { var signersFound = WebAuthentication.VerifyChallengeTransactionSigners(transaction, serverKeypair.AccountId, signers, Network.Test()).ToList(); } - catch (Exception exception) { Assert.IsTrue(exception.Message.Contains("signers must be non-empty"));