From c32e663907369995130c8ee97936e6363febf4d4 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Tue, 10 Nov 2020 21:41:24 +0800 Subject: [PATCH 1/6] feat: add support for SEP-10 v3.0.0. --- .../java/org/stellar/sdk/Sep10Challenge.java | 115 +++++++++++-- .../org/stellar/sdk/Sep10ChallengeTest.java | 160 +++++++++++++++++- 2 files changed, 258 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 7ccbbb851..a3433d210 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -64,17 +64,19 @@ 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. + * @param domainNames One of the home domains 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 { + public static ChallengeTransaction readChallengeTransaction(String challengeXdr, String serverAccountId, Network network, String[] domainNames) throws InvalidSep10ChallengeException, IOException { // decode the received input as a base64-urlencoded XDR representation of Stellar transaction envelope AbstractTransaction parsed = Transaction.fromEnvelopeXdr(challengeXdr, network); if (!(parsed instanceof Transaction)) { @@ -128,7 +130,15 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, throw new InvalidSep10ChallengeException("Operation should have a source account."); } - if (!String.format("%s %s", domainName, MANAGER_DATA_NAME_FLAG).equals(manageDataOperation.getName())) { + 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 the expected home domain."); } @@ -136,6 +146,9 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, 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."); @@ -157,7 +170,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 domains 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}); } /** @@ -172,19 +210,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. + * @param domainName One of the home domains 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 One of the home domains 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[] 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. @@ -262,14 +322,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. + * @param domainNames One of the home domains 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 { + 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."); } @@ -279,7 +339,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) { @@ -296,6 +356,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."); @@ -333,15 +415,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() { @@ -352,6 +436,10 @@ public String getClientAccountId() { return clientAccountId; } + public String getMatchedHomeDomain() { + return matchedHomeDomain; + } + @Override public int hashCode() { return Objects.hashCode(this.transaction.hashHex(), this.clientAccountId); @@ -369,7 +457,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 02ca66685..29fa70471 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -101,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 @@ -153,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); } } @@ -255,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 @@ -791,6 +791,44 @@ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws } } + @Test + public void testReadChallengeTransactionInvalidDataValueIsNull() throws IOException { + KeyPair server = KeyPair.random(); + KeyPair client = KeyPair.random(); + String domainName = "example.com"; + + Network network = Network.TESTNET; + + long now = System.currentTimeMillis() / 1000L; + long end = now + 300; + TimeBounds timeBounds = new TimeBounds(now, end); + + 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(); + + 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 public void testReadChallengeTransactionInvalidDomainNameMismatch() throws IOException { KeyPair server = KeyPair.random(); @@ -824,6 +862,65 @@ public void testReadChallengeTransactionInvalidDomainNameMismatch() throws IOExc } } + @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 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 { + Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, new String[]{}); + } catch (InvalidSep10ChallengeException e) { + assertEquals("The transaction's operation key name does not include the expected home domain.", e.getMessage()); + } + } + @Test public void testVerifyChallengeTransactionThresholdInvalidNotSignedByServer() throws IOException { Network network = Network.TESTNET; @@ -874,7 +971,6 @@ public void testVerifyChallengeTransactionThresholdInvalidNotSignedByServer() th } catch (InvalidSep10ChallengeException e) { assertEquals(String.format("Transaction not signed by server: %s.", server.getAccountId()), e.getMessage()); } - } @Test @@ -907,6 +1003,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; @@ -1283,6 +1409,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(); From 8b8fa49f17e2e8acfa868e8433a35fe4007df1f5 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Tue, 10 Nov 2020 22:08:07 +0800 Subject: [PATCH 2/6] add more tests --- CHANGELOG.md | 3 ++ .../java/org/stellar/sdk/Sep10Challenge.java | 4 +-- .../org/stellar/sdk/Sep10ChallengeTest.java | 34 ++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8474e302f..2fa4520e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ 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)). + ## 0.21.1 - Fix NullPointerException in `org.stellar.sdk.responses.operations.RevokeSponsorshipOperationResponse` accessor methods. diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index b0c77fe5d..0a435ae37 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -133,7 +133,7 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, } String matchedDomainName = null; - for (String homeDomain: domainNames) { + for (String homeDomain : domainNames) { if ((homeDomain + " " + MANAGER_DATA_NAME_FLAG).equals(manageDataOperation.getName())) { matchedDomainName = homeDomain; break; @@ -227,7 +227,7 @@ 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 One of the home domains that is expected to be included in the first Manage Data operation's string key. + * @param domainName The home domains 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. diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index ff21770c5..5cf6bed9a 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -988,7 +988,39 @@ public void testReadChallengeTransactionValidMultipleDomainNames() throws IOExce } @Test - public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOException, InvalidSep10ChallengeException { + 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 the expected home domain.", e.getMessage()); + } + } + + @Test + public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOException { KeyPair server = KeyPair.random(); KeyPair client = KeyPair.random(); Network network = Network.TESTNET; From b20d850a0ba2ceacfa7dde2b274d8fd8ddb955c0 Mon Sep 17 00:00:00 2001 From: Jun Luo <4catcode@gmail.com> Date: Fri, 13 Nov 2020 09:58:34 +0800 Subject: [PATCH 3/6] fix the error message in SEP-10 Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com> --- src/main/java/org/stellar/sdk/Sep10Challenge.java | 2 +- src/test/java/org/stellar/sdk/Sep10ChallengeTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 0a435ae37..89a0f0b8b 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -141,7 +141,7 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr, } if (matchedDomainName == null) { - throw new InvalidSep10ChallengeException("The transaction's operation key name does not include the expected home domain."); + 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) { diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 5cf6bed9a..536946254 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -1015,7 +1015,7 @@ public void testReadChallengeTransactionInvalidDomainNamesMismatch() throws IOEx 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 the expected home domain.", e.getMessage()); + assertEquals("The transaction's operation key name does not include one of the expected home domains.", e.getMessage()); } } @@ -1047,7 +1047,7 @@ public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOExcep Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, new String[]{}); fail(); } catch (InvalidSep10ChallengeException e) { - assertEquals("The transaction's operation key name does not include the expected home domain.", e.getMessage()); + assertEquals("The transaction's operation key name does not include one of the expected home domains.", e.getMessage()); } } From 5d52a5f69ffdf0d216f58250778cc6375c539eb4 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Tue, 17 Nov 2020 09:11:21 +0800 Subject: [PATCH 4/6] fix the docs in SEP-10 Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com> --- src/main/java/org/stellar/sdk/Sep10Challenge.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 89a0f0b8b..356f73ac7 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -71,7 +71,7 @@ public static Transaction newChallenge( * @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 One of the home domains that is expected to be included in the first Manage Data operation's string key. + * @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. @@ -206,7 +206,7 @@ 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 home domains that is expected to be included in the first Manage Data operation's string key. + * @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. @@ -227,7 +227,7 @@ 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 home domains that is expected to be included in the first Manage Data operation's string key. + * @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. @@ -249,7 +249,7 @@ 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 domainNames One of the home domains that is expected to be included in the first Manage Data operation's string key. + * @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. @@ -339,7 +339,7 @@ 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 domainNames One of the home domains that is expected to be included in the first Manage Data operation's string key. + * @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. From 54f5d206552b9e04853e83343fe8b0240cc9c4ea Mon Sep 17 00:00:00 2001 From: Jun Luo <4catcode@gmail.com> Date: Thu, 10 Dec 2020 09:36:13 +0800 Subject: [PATCH 5/6] Update CHANGELOG.md Co-authored-by: Leigh McCulloch --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12cfd222..7dc51477d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,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 +## 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)) From c8bcc32a598cd917fc15d013b19e1a3dd100eeee Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Thu, 10 Dec 2020 10:44:07 +0800 Subject: [PATCH 6/6] Add verification of the domainNames provided by the user. --- .../java/org/stellar/sdk/Sep10Challenge.java | 4 ++ .../org/stellar/sdk/Sep10ChallengeTest.java | 40 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java index 356f73ac7..1836b4ae7 100644 --- a/src/main/java/org/stellar/sdk/Sep10Challenge.java +++ b/src/main/java/org/stellar/sdk/Sep10Challenge.java @@ -77,6 +77,10 @@ public static Transaction newChallenge( * @throws IOException If read XDR string fails, the exception will be thrown. */ 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)) { diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java index 536946254..b324944d2 100644 --- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java +++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java @@ -1020,7 +1020,7 @@ public void testReadChallengeTransactionInvalidDomainNamesMismatch() throws IOEx } @Test - public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOException { + public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOException, InvalidSep10ChallengeException { KeyPair server = KeyPair.random(); KeyPair client = KeyPair.random(); Network network = Network.TESTNET; @@ -1044,10 +1044,44 @@ public void testReadChallengeTransactionInvalidDomainNamesEmpty() throws IOExcep } try { - Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, new String[]{}); + 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) { - assertEquals("The transaction's operation key name does not include one of the expected home domains.", e.getMessage()); + 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()); } }