diff --git a/CHANGELOG.md b/CHANGELOG.md index cd9fe3848..7dc51477d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps. A breaking change will get clearly notified in this log. ## 0.21.2 +- Update challenge transaction helpers for SEP-0010 v3.0.0. ([#308](https://github.com/stellar/java-stellar-sdk/pull/308)) - Fix the decoding of `balanceId` in `org.stellar.sdk.ClaimClaimableBalanceOperation`. ([#310](https://github.com/stellar/java-stellar-sdk/pull/310)) ## 0.21.1 diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 11f182f27..1836b4ae7 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -19,7 +19,7 @@ public class Sep10Challenge { * @param signer The server's signing account. * @param network The Stellar network used by the server. * @param clientAccountId The stellar account belonging to the client. - * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used). + * @param domainName The fully qualified domain name of the service requiring authentication. * @param timebounds The lifetime of the challenge token. */ public static Transaction newChallenge( @@ -64,17 +64,23 @@ public static Transaction newChallenge( * that any signatures other than the servers on the transaction are valid. Use * one of the following functions to completely verify the transaction: * {@link Sep10Challenge#verifyChallengeTransactionSigners(String, String, Network, String, Set)} or - * {@link Sep10Challenge#verifyChallengeTransactionThreshold(String, String, Network, String, int, Set)} + * {@link Sep10Challenge#verifyChallengeTransactionThreshold(String, String, Network, String, int, Set)} or + * {@link Sep10Challenge#verifyChallengeTransactionSigners(String, String, Network, String[], Set)} or + * {@link Sep10Challenge#verifyChallengeTransactionThreshold(String, String, Network, String[], int, Set)} or * * @param challengeXdr SEP-0010 transaction challenge transaction in base64. * @param serverAccountId Account ID for server's account. * @param network The network to connect to for verifying and retrieving. - * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used). + * @param domainNames An array of home domains, one of which is expected to be included in the first Manage Data operation's string key. * @return {@link ChallengeTransaction}, the decoded transaction envelope and client account ID contained within. * @throws InvalidSep10ChallengeException If the SEP-0010 validation fails, the exception will be thrown. * @throws IOException If read XDR string fails, the exception will be thrown. */ - public static ChallengeTransaction readChallengeTransaction(String challengeXdr, String serverAccountId, Network network, String domainName) throws InvalidSep10ChallengeException, IOException { + public static ChallengeTransaction readChallengeTransaction(String challengeXdr, String serverAccountId, Network network, String[] domainNames) throws InvalidSep10ChallengeException, IOException { + if (domainNames == null || domainNames.length == 0) { + throw new IllegalArgumentException("At least one domain name must be included in domainNames."); + } + // decode the received input as a base64-urlencoded XDR representation of Stellar transaction envelope AbstractTransaction parsed = Transaction.fromEnvelopeXdr(challengeXdr, network); if (!(parsed instanceof Transaction)) { @@ -130,10 +136,25 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, throw new InvalidSep10ChallengeException("Operation should have a source account."); } + String matchedDomainName = null; + for (String homeDomain : domainNames) { + if ((homeDomain + " " + MANAGER_DATA_NAME_FLAG).equals(manageDataOperation.getName())) { + matchedDomainName = homeDomain; + break; + } + } + + if (matchedDomainName == null) { + throw new InvalidSep10ChallengeException("The transaction's operation key name does not include one of the expected home domains."); + } + if (StrKey.decodeVersionByte(clientAccountId) != StrKey.VersionByte.ACCOUNT_ID) { throw new InvalidSep10ChallengeException("clientAccountId: "+clientAccountId+" is not a valid account id"); } + if (manageDataOperation.getValue() == null) { + throw new InvalidSep10ChallengeException("The transaction's operation value should not be null."); + } // verify manage data value if (manageDataOperation.getValue().length != 64) { throw new InvalidSep10ChallengeException("Random nonce encoded as base64 should be 64 bytes long."); @@ -170,7 +191,32 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, throw new InvalidSep10ChallengeException(String.format("Transaction not signed by server: %s.", serverAccountId)); } - return new ChallengeTransaction(transaction, clientAccountId); + return new ChallengeTransaction(transaction, clientAccountId, matchedDomainName); + } + + /** + * Reads a SEP 10 challenge transaction and returns the decoded transaction envelope and client account ID contained within. + *

+ * It also verifies that transaction is signed by the server. + *

+ * It does not verify that the transaction has been signed by the client or + * that any signatures other than the servers on the transaction are valid. Use + * one of the following functions to completely verify the transaction: + * {@link Sep10Challenge#verifyChallengeTransactionSigners(String, String, Network, String, Set)} or + * {@link Sep10Challenge#verifyChallengeTransactionThreshold(String, String, Network, String, int, Set)} or + * {@link Sep10Challenge#verifyChallengeTransactionSigners(String, String, Network, String[], Set)} or + * {@link Sep10Challenge#verifyChallengeTransactionThreshold(String, String, Network, String[], int, Set)} or + * + * @param challengeXdr SEP-0010 transaction challenge transaction in base64. + * @param serverAccountId Account ID for server's account. + * @param network The network to connect to for verifying and retrieving. + * @param domainName The home domain that is expected to be included in the first Manage Data operation's string key. + * @return {@link ChallengeTransaction}, the decoded transaction envelope and client account ID contained within. + * @throws InvalidSep10ChallengeException If the SEP-0010 validation fails, the exception will be thrown. + * @throws IOException If read XDR string fails, the exception will be thrown. + */ + public static ChallengeTransaction readChallengeTransaction(String challengeXdr, String serverAccountId, Network network, String domainName) throws InvalidSep10ChallengeException, IOException { + return readChallengeTransaction(challengeXdr, serverAccountId, network, new String[]{domainName}); } /** @@ -185,19 +231,41 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, * @param challengeXdr SEP-0010 transaction challenge transaction in base64. * @param serverAccountId Account ID for server's account. * @param network The network to connect to for verifying and retrieving. - * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used). + * @param domainName The home domain that is expected to be included in the first Manage Data operation's string key. * @param signers The signers of client account. * @return a list of signers that were found is returned, excluding the server account ID. * @throws InvalidSep10ChallengeException If the SEP-0010 validation fails, the exception will be thrown. * @throws IOException If read XDR string fails, the exception will be thrown. */ public static Set verifyChallengeTransactionSigners(String challengeXdr, String serverAccountId, Network network, String domainName, Set signers) throws InvalidSep10ChallengeException, IOException { + return verifyChallengeTransactionSigners(challengeXdr, serverAccountId, network, new String[]{domainName}, signers); + } + + /** + * Verifies that for a SEP 10 challenge transaction + * all signatures on the transaction are accounted for. A transaction is + * verified if it is signed by the server account, and all other signatures + * match a signer that has been provided as an argument. 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 a list of + * signers that were found is returned, excluding the server account ID. + * + * @param challengeXdr SEP-0010 transaction challenge transaction in base64. + * @param serverAccountId Account ID for server's account. + * @param network The network to connect to for verifying and retrieving. + * @param domainNames An array of home domains, one of which is expected to be included in the first Manage Data operation's string key. + * @param signers The signers of client account. + * @return a list of signers that were found is returned, excluding the server account ID. + * @throws InvalidSep10ChallengeException If the SEP-0010 validation fails, the exception will be thrown. + * @throws IOException If read XDR string fails, the exception will be thrown. + */ + public static Set verifyChallengeTransactionSigners(String challengeXdr, String serverAccountId, Network network, String[] domainNames, Set signers) throws InvalidSep10ChallengeException, IOException { if (signers == null || signers.isEmpty()) { throw new InvalidSep10ChallengeException("No verifiable signers provided, at least one G... address must be provided."); } // Read the transaction which validates its structure. - ChallengeTransaction parsedChallengeTransaction = readChallengeTransaction(challengeXdr, serverAccountId, network, domainName); + ChallengeTransaction parsedChallengeTransaction = readChallengeTransaction(challengeXdr, serverAccountId, network, domainNames); Transaction transaction = parsedChallengeTransaction.getTransaction(); // Ensure the server account ID is an address and not a seed. @@ -275,14 +343,14 @@ public static Set verifyChallengeTransactionSigners(String challengeXdr, * @param challengeXdr SEP-0010 transaction challenge transaction in base64. * @param serverAccountId Account ID for server's account. * @param network The network to connect to for verifying and retrieving. - * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used). + * @param domainNames An array of home domains, one of which is expected to be included in the first Manage Data operation's string key. * @param threshold The threshold on the client account. * @param signers The signers of client account. * @return a list of signers that were found is returned, excluding the server account ID. * @throws InvalidSep10ChallengeException If the SEP-0010 validation fails, the exception will be thrown. * @throws IOException If read XDR string fails, the exception will be thrown. */ - public static Set verifyChallengeTransactionThreshold(String challengeXdr, String serverAccountId, Network network, String domainName, int threshold, Set signers) throws InvalidSep10ChallengeException, IOException { + public static Set verifyChallengeTransactionThreshold(String challengeXdr, String serverAccountId, Network network, String[] domainNames, int threshold, Set signers) throws InvalidSep10ChallengeException, IOException { if (signers == null || signers.isEmpty()) { throw new InvalidSep10ChallengeException("No verifiable signers provided, at least one G... address must be provided."); } @@ -292,7 +360,7 @@ public static Set verifyChallengeTransactionThreshold(String challengeXd weightsForSigner.put(signer.getKey(), signer.getWeight()); } - Set signersFound = verifyChallengeTransactionSigners(challengeXdr, serverAccountId, network, domainName, weightsForSigner.keySet()); + Set signersFound = verifyChallengeTransactionSigners(challengeXdr, serverAccountId, network, domainNames, weightsForSigner.keySet()); int sum = 0; for (String signer : signersFound) { @@ -309,6 +377,28 @@ public static Set verifyChallengeTransactionThreshold(String challengeXd return signersFound; } + /** + * Verifies that for a SEP-0010 challenge transaction + * all signatures on the transaction are accounted for and that the signatures + * meet a threshold on an account. A transaction is verified if it is signed by + * the server account, and all other signatures match a signer that has been + * provided as an argument, and those signatures meet a threshold on the + * account. + * + * @param challengeXdr SEP-0010 transaction challenge transaction in base64. + * @param serverAccountId Account ID for server's account. + * @param network The network to connect to for verifying and retrieving. + * @param domainName The home domain that is expected to be included in the first Manage Data operation's string key. + * @param threshold The threshold on the client account. + * @param signers The signers of client account. + * @return a list of signers that were found is returned, excluding the server account ID. + * @throws InvalidSep10ChallengeException If the SEP-0010 validation fails, the exception will be thrown. + * @throws IOException If read XDR string fails, the exception will be thrown. + */ + public static Set verifyChallengeTransactionThreshold(String challengeXdr, String serverAccountId, Network network, String domainName, int threshold, Set signers) throws InvalidSep10ChallengeException, IOException { + return verifyChallengeTransactionThreshold(challengeXdr, serverAccountId, network, new String[]{domainName}, threshold, signers); + } + private static Set verifyTransactionSignatures(Transaction transaction, Set signers) throws InvalidSep10ChallengeException { if (transaction.getSignatures().isEmpty()) { throw new InvalidSep10ChallengeException("Transaction has no signatures."); @@ -346,15 +436,17 @@ private static boolean verifyTransactionSignature(Transaction transaction, Strin } /** - * Used to store the results produced by {@link Sep10Challenge#readChallengeTransaction(String, String, Network, String)}. + * Used to store the results produced by {@link Sep10Challenge#readChallengeTransaction(String, String, Network, String[])}. */ public static class ChallengeTransaction { private final Transaction transaction; private final String clientAccountId; + private final String matchedHomeDomain; - public ChallengeTransaction(Transaction transaction, String clientAccountId) { + public ChallengeTransaction(Transaction transaction, String clientAccountId, String matchedHomeDomain) { this.transaction = transaction; this.clientAccountId = clientAccountId; + this.matchedHomeDomain = matchedHomeDomain; } public Transaction getTransaction() { @@ -365,6 +457,10 @@ public String getClientAccountId() { return clientAccountId; } + public String getMatchedHomeDomain() { + return matchedHomeDomain; + } + @Override public int hashCode() { return Objects.hashCode(this.transaction.hashHex(), this.clientAccountId); @@ -382,7 +478,8 @@ public boolean equals(Object object) { ChallengeTransaction other = (ChallengeTransaction) object; return Objects.equal(this.transaction.hashHex(), other.transaction.hashHex()) && - Objects.equal(this.clientAccountId, other.clientAccountId); + Objects.equal(this.clientAccountId, other.clientAccountId) && + Objects.equal(this.matchedHomeDomain, other.matchedHomeDomain); } } diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 15420bea8..b324944d2 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -3,7 +3,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; import org.junit.Test; -import org.stellar.sdk.xdr.BumpSequenceOp; import org.stellar.sdk.xdr.EnvelopeType; import org.stellar.sdk.xdr.TransactionEnvelope; @@ -102,7 +101,7 @@ public void testReadChallengeTransactionValidSignedByServer() throws InvalidSep1 ); Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, domainName); - assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction); + assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId(), domainName), challengeTransaction); } @Test @@ -154,7 +153,7 @@ public void testReadChallengeTransactionAcceptsBothV0AndV1() throws InvalidSep10 server.getAccountId(), Network.TESTNET, domainName ); - assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction); + assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId(), domainName), challengeTransaction); } } @@ -256,7 +255,7 @@ public void testReadChallengeTransactionValidSignedByServerAndClient() throws In transaction.sign(client); Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, domainName); - assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction); + assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId(), domainName), challengeTransaction); } @Test @@ -745,49 +744,41 @@ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws } @Test - public void testReadChallengeTransactionValidDoesNotVerifyHomeDomainWithHomeDomainSetToNull() throws IOException, InvalidSep10ChallengeException { + public void testReadChallengeTransactionInvalidDataValueIsNull() throws IOException { KeyPair server = KeyPair.random(); KeyPair client = KeyPair.random(); - Network network = Network.TESTNET; String domainName = "example.com"; - long now = System.currentTimeMillis() / 1000L; - long end = now + 300; - TimeBounds timeBounds = new TimeBounds(now, end); - - Transaction transaction = Sep10Challenge.newChallenge( - server, - network, - client.getAccountId(), - domainName, - timeBounds - ); - - Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, null); - assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction); - } - - @Test - public void testReadChallengeTransactionValidDoesNotVerifyHomeDomainWithHomeDomainSetToInvalidValue() throws IOException, InvalidSep10ChallengeException { - KeyPair server = KeyPair.random(); - KeyPair client = KeyPair.random(); Network network = Network.TESTNET; - String domainName = "example.com"; long now = System.currentTimeMillis() / 1000L; long end = now + 300; TimeBounds timeBounds = new TimeBounds(now, end); - Transaction transaction = Sep10Challenge.newChallenge( - server, - network, - client.getAccountId(), - domainName, - timeBounds + Account sourceAccount = new Account(server.getAccountId(), -1L); + ManageDataOperation manageDataOperation1 = new ManageDataOperation.Builder(domainName + " auth", null) + .setSourceAccount(client.getAccountId()) + .build(); + + Operation[] operations = new Operation[]{manageDataOperation1}; + Transaction transaction = new Transaction( + sourceAccount.getAccountId(), + 100 * operations.length, + sourceAccount.getIncrementedSequenceNumber(), + operations, + Memo.none(), + timeBounds, + network ); + transaction.sign(server); + String challenge = transaction.toEnvelopeXdrBase64(); - Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, "invalid.domain"); - assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction); + try { + Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName); + fail(); + } catch (InvalidSep10ChallengeException e) { + assertEquals("The transaction's operation value should not be null.", e.getMessage()); + } } @Test @@ -829,7 +820,7 @@ public void testReadChallengeTransactionValidAdditionalManageDataOpsWithSourceAc String challenge = transaction.toEnvelopeXdrBase64(); Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName); - assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction); + assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId(), domainName), challengeTransaction); } @Test @@ -968,6 +959,132 @@ public void testReadChallengeTransactionInvalidAdditionalOpsOfOtherTypes() throw } } + @Test + public void testReadChallengeTransactionValidMultipleDomainNames() throws IOException, InvalidSep10ChallengeException { + KeyPair server = KeyPair.random(); + KeyPair client = KeyPair.random(); + Network network = Network.TESTNET; + String domainName = "example.com"; + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + + Transaction transaction = null; + try { + transaction = Sep10Challenge.newChallenge( + server, + network, + client.getAccountId(), + domainName, + timeBounds + ); + } catch (InvalidSep10ChallengeException e) { + fail("Should not have thrown any exception."); + } + + Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, new String[]{"example3.com", "example2.com", "example.com"}); + assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId(), domainName), challengeTransaction); + } + + @Test + public void testReadChallengeTransactionInvalidDomainNamesMismatch() throws IOException { + KeyPair server = KeyPair.random(); + KeyPair client = KeyPair.random(); + Network network = Network.TESTNET; + String domainName = "example.com"; + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + + Transaction transaction = null; + try { + transaction = Sep10Challenge.newChallenge( + server, + network, + client.getAccountId(), + domainName, + timeBounds + ); + } catch (InvalidSep10ChallengeException e) { + fail("Should not have thrown any exception."); + } + + try { + Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, new String[]{"example2.com", "example1.com"}); + fail(); + } catch (InvalidSep10ChallengeException e) { + assertEquals("The transaction's operation key name does not include one of the expected home domains.", e.getMessage()); + } + } + + @Test + public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOException, InvalidSep10ChallengeException { + KeyPair server = KeyPair.random(); + KeyPair client = KeyPair.random(); + Network network = Network.TESTNET; + String domainName = "example.com"; + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + + Transaction transaction = null; + try { + transaction = Sep10Challenge.newChallenge( + server, + network, + client.getAccountId(), + domainName, + timeBounds + ); + } catch (InvalidSep10ChallengeException e) { + fail("Should not have thrown any exception."); + } + + try { + String[] domainNames = new String[]{}; + Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, domainNames); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("At least one domain name must be included in domainNames.", e.getMessage()); + } + } + + @Test + public void testReadChallengeTransactionInvalidDomainNamesNull() throws IOException, InvalidSep10ChallengeException { + KeyPair server = KeyPair.random(); + KeyPair client = KeyPair.random(); + Network network = Network.TESTNET; + String domainName = "example.com"; + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + + Transaction transaction = null; + try { + transaction = Sep10Challenge.newChallenge( + server, + network, + client.getAccountId(), + domainName, + timeBounds + ); + } catch (InvalidSep10ChallengeException e) { + fail("Should not have thrown any exception."); + } + + try { + String[] domainNames = null; + Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, domainNames); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("At least one domain name must be included in domainNames.", e.getMessage()); + } + } + @Test public void testVerifyChallengeTransactionThresholdInvalidNotSignedByServer() throws IOException { Network network = Network.TESTNET; @@ -1018,7 +1135,6 @@ public void testVerifyChallengeTransactionThresholdInvalidNotSignedByServer() th } catch (InvalidSep10ChallengeException e) { assertEquals(String.format("Transaction not signed by server: %s.", server.getAccountId()), e.getMessage()); } - } @Test @@ -1051,6 +1167,36 @@ public void testVerifyChallengeTransactionThresholdValidServerAndClientKeyMeetin assertEquals(new HashSet(Collections.singletonList(masterClient.getAccountId())), signersFound); } + @Test + public void testVerifyChallengeTransactionThresholdValidMultipleDomainNames() throws IOException, InvalidSep10ChallengeException { + Network network = Network.TESTNET; + KeyPair server = KeyPair.random(); + KeyPair masterClient = KeyPair.random(); + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + String domainName = "example.com"; + + Transaction transaction = Sep10Challenge.newChallenge( + server, + network, + masterClient.getAccountId(), + domainName, + timeBounds + ); + + transaction.sign(masterClient); + + Set signers = new HashSet(Collections.singletonList( + new Sep10Challenge.Signer(masterClient.getAccountId(), 255) + )); + + int threshold = 255; + Set signersFound = Sep10Challenge.verifyChallengeTransactionThreshold(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, new String[]{"example3.com", "example2.com", "example.com"}, threshold, signers); + assertEquals(new HashSet(Collections.singletonList(masterClient.getAccountId())), signersFound); + } + @Test public void testVerifyChallengeTransactionThresholdValidServerAndMultipleClientKeyMeetingThreshold() throws IOException, InvalidSep10ChallengeException { Network network = Network.TESTNET; @@ -1427,6 +1573,32 @@ public void testVerifyChallengeTransactionSignersValidServerAndClientMasterKey() assertEquals(signers, signersFound); } + @Test + public void testVerifyChallengeTransactionSignersValidMultipleDomainNames() throws InvalidSep10ChallengeException, IOException { + KeyPair server = KeyPair.random(); + KeyPair masterClient = KeyPair.random(); + Network network = Network.TESTNET; + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + String domainName = "example.com"; + + Transaction transaction = Sep10Challenge.newChallenge( + server, + network, + masterClient.getAccountId(), + domainName, + timeBounds + ); + + transaction.sign(masterClient); + + Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId())); + Set signersFound = Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, new String[]{"example3.com", "example2.com", "example.com"}, signers); + assertEquals(signers, signersFound); + } + @Test public void testVerifyChallengeTransactionSignersInvalidServerAndNoClient() throws InvalidSep10ChallengeException, IOException { KeyPair server = KeyPair.random(); @@ -1790,56 +1962,6 @@ public void testVerifyChallengeTransactionSignersInvalidNoSignersEmptySet() thro } } - @Test - public void testVerifyChallengeTransactionValidDoesNotVerifyHomeDomainHomeDomainSetToNull() throws IOException, InvalidSep10ChallengeException { - KeyPair server = KeyPair.random(); - KeyPair masterClient = KeyPair.random(); - Network network = Network.TESTNET; - String domainName = "example.com"; - - long now = System.currentTimeMillis() / 1000L; - long end = now + 300; - TimeBounds timeBounds = new TimeBounds(now, end); - - Transaction transaction = Sep10Challenge.newChallenge( - server, - network, - masterClient.getAccountId(), - domainName, - timeBounds - ); - transaction.sign(masterClient); - - Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId())); - Set signersFound = Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, null, signers); - assertEquals(signers, signersFound); - } - - @Test - public void testVerifyChallengeTransactionValidDoesNotVerifyHomeDomainWithHomeDomainSetToInvalidValue() throws IOException, InvalidSep10ChallengeException { - KeyPair server = KeyPair.random(); - KeyPair masterClient = KeyPair.random(); - Network network = Network.TESTNET; - String domainName = "example.com"; - - long now = System.currentTimeMillis() / 1000L; - long end = now + 300; - TimeBounds timeBounds = new TimeBounds(now, end); - - Transaction transaction = Sep10Challenge.newChallenge( - server, - network, - masterClient.getAccountId(), - domainName, - timeBounds - ); - transaction.sign(masterClient); - - Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId())); - Set signersFound = Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, "invalid.domain", signers); - assertEquals(signers, signersFound); - } - @Test public void testVerifyChallengeTransactionValidAdditionalManageDataOpsWithSourceAccountSetToServerAccount() throws IOException, InvalidSep10ChallengeException { KeyPair server = KeyPair.random();