diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5783cd59..1c0d75c71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
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.20.0
+* Update challenge transaction helpers for SEP-0010 v2.1.0. ([#300](https://github.com/stellar/java-stellar-sdk/pull/300)).
+ - Remove verification of domain name.
+ - Allow additional manage data operations that have the source account set as the server key.
+
## 0.19.0
### Add
diff --git a/src/main/java/org/stellar/sdk/Sep10Challenge.java b/src/main/java/org/stellar/sdk/Sep10Challenge.java
index 7ccbbb851..11f182f27 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.
+ * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used).
* @param timebounds The lifetime of the challenge token.
*/
public static Transaction newChallenge(
@@ -69,7 +69,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 domainName The fully qualified domain name of the service requiring authentication.
+ * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used).
* @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.
@@ -112,10 +112,12 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr,
throw new InvalidSep10ChallengeException("Transaction is not within range of the specified timebounds.");
}
- // verify that transaction contains a single Manage Data operation and its source account is not null
- if (transaction.getOperations().length != 1) {
- throw new InvalidSep10ChallengeException("Transaction requires a single ManageData operation.");
+ if (transaction.getOperations().length < 1) {
+ throw new InvalidSep10ChallengeException("Transaction requires at least one ManageData operation.");
}
+
+ // verify that the first operation in the transaction is a Manage Data operation
+ // and its source account is not null
Operation operation = transaction.getOperations()[0];
if (!(operation instanceof ManageDataOperation)) {
throw new InvalidSep10ChallengeException("Operation type should be ManageData.");
@@ -128,10 +130,6 @@ 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())) {
- throw new InvalidSep10ChallengeException("The transaction's operation key name does not include the expected home domain.");
- }
-
if (StrKey.decodeVersionByte(clientAccountId) != StrKey.VersionByte.ACCOUNT_ID) {
throw new InvalidSep10ChallengeException("clientAccountId: "+clientAccountId+" is not a valid account id");
}
@@ -153,6 +151,21 @@ public static ChallengeTransaction readChallengeTransaction(String challengeXdr,
throw new InvalidSep10ChallengeException("Random nonce before encoding as base64 should be 48 bytes long.");
}
+ // verify subsequent operations are manage data ops with source account set to server account
+ for (int i = 1; i < transaction.getOperations().length; i++) {
+ Operation op = transaction.getOperations()[i];
+ if (!(op instanceof ManageDataOperation)) {
+ throw new InvalidSep10ChallengeException("Operation type should be ManageData.");
+ }
+ ManageDataOperation manageDataOp = (ManageDataOperation) op;
+ if (manageDataOp.getSourceAccount() == null) {
+ throw new InvalidSep10ChallengeException("Operation should have a source account.");
+ }
+ if (!manageDataOp.getSourceAccount().equals(serverAccountId)) {
+ throw new InvalidSep10ChallengeException("Subsequent operations are unrecognized.");
+ }
+ }
+
if (!verifyTransactionSignature(transaction, serverAccountId)) {
throw new InvalidSep10ChallengeException(String.format("Transaction not signed by server: %s.", serverAccountId));
}
@@ -172,7 +185,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 fully qualified domain name of the service requiring authentication.
+ * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used).
* @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.
@@ -262,7 +275,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 domainName The fully qualified domain name of the service requiring authentication.
+ * @param domainName The fully qualified domain name of the service requiring authentication (The domainName field is reserved for future use and not used).
* @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.
diff --git a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java
index 02ca66685..15420bea8 100644
--- a/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java
+++ b/src/test/java/org/stellar/sdk/Sep10ChallengeTest.java
@@ -3,6 +3,7 @@
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;
@@ -537,9 +538,45 @@ public void testReadChallengeTransactionInvalidTimeBoundsTooLate() throws Invali
}
@Test
- public void testReadChallengeTransactionInvalidTooManyOperations() throws IOException {
+ public void testReadChallengeTransactionInvalidOperationWrongType() throws IOException {
KeyPair server = KeyPair.random();
KeyPair client = 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";
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ SetOptionsOperation setOptionsOperation = new SetOptionsOperation.Builder()
+ .setSourceAccount(client.getAccountId())
+ .build();
+
+ Operation[] operations = new Operation[]{setOptionsOperation};
+ 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("Operation type should be ManageData.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testReadChallengeTransactionInvalidOperationNoSourceAccount() throws IOException {
+ KeyPair server = KeyPair.random();
String domainName = "example.com";
Network network = Network.TESTNET;
@@ -556,14 +593,9 @@ public void testReadChallengeTransactionInvalidTooManyOperations() throws IOExce
Account sourceAccount = new Account(server.getAccountId(), -1L);
ManageDataOperation manageDataOperation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
- .setSourceAccount(client.getAccountId())
- .build();
-
- ManageDataOperation manageDataOperation2 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
- .setSourceAccount(client.getAccountId())
.build();
- Operation[] operations = new Operation[]{manageDataOperation1, manageDataOperation2};
+ Operation[] operations = new Operation[]{manageDataOperation1};
Transaction transaction = new Transaction(
sourceAccount.getAccountId(),
100 * operations.length,
@@ -580,27 +612,34 @@ public void testReadChallengeTransactionInvalidTooManyOperations() throws IOExce
Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("Transaction requires a single ManageData operation.", e.getMessage());
+ assertEquals("Operation should have a source account.", e.getMessage());
}
}
@Test
- public void testReadChallengeTransactionInvalidOperationWrongType() throws IOException {
+ public void testReadChallengeTransactionInvalidDataValueWrongEncodedLength() 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);
- String domainName = "example.com";
+
+ byte[] nonce = new byte[32];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
Account sourceAccount = new Account(server.getAccountId(), -1L);
- SetOptionsOperation setOptionsOperation = new SetOptionsOperation.Builder()
+ ManageDataOperation manageDataOperation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
.setSourceAccount(client.getAccountId())
.build();
- Operation[] operations = new Operation[]{setOptionsOperation};
+ Operation[] operations = new Operation[]{manageDataOperation1};
Transaction transaction = new Transaction(
sourceAccount.getAccountId(),
100 * operations.length,
@@ -617,13 +656,14 @@ public void testReadChallengeTransactionInvalidOperationWrongType() throws IOExc
Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("Operation type should be ManageData.", e.getMessage());
+ assertEquals("Random nonce encoded as base64 should be 64 bytes long.", e.getMessage());
}
}
@Test
- public void testReadChallengeTransactionInvalidOperationNoSourceAccount() throws IOException {
+ public void testReadChallengeTransactionInvalidDataValueCorruptBase64() throws IOException {
KeyPair server = KeyPair.random();
+ KeyPair client = KeyPair.random();
String domainName = "example.com";
Network network = Network.TESTNET;
@@ -632,14 +672,10 @@ public void testReadChallengeTransactionInvalidOperationNoSourceAccount() throws
long end = now + 300;
TimeBounds timeBounds = new TimeBounds(now, end);
- byte[] nonce = new byte[48];
- SecureRandom random = new SecureRandom();
- random.nextBytes(nonce);
- BaseEncoding base64Encoding = BaseEncoding.base64();
- byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
-
+ byte[] encodedNonce = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?AAAAAAAAAAAAAAAAAAAAAAAAAA".getBytes("UTF-8");
Account sourceAccount = new Account(server.getAccountId(), -1L);
ManageDataOperation manageDataOperation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(client.getAccountId())
.build();
Operation[] operations = new Operation[]{manageDataOperation1};
@@ -659,12 +695,13 @@ public void testReadChallengeTransactionInvalidOperationNoSourceAccount() throws
Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("Operation should have a source account.", e.getMessage());
+ assertEquals("Failed to decode random nonce provided in ManageData operation.", e.getMessage());
+ assertEquals("com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: ?", e.getCause().getMessage());
}
}
@Test
- public void testReadChallengeTransactionInvalidDataValueWrongEncodedLength() throws IOException {
+ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws IOException {
KeyPair server = KeyPair.random();
KeyPair client = KeyPair.random();
String domainName = "example.com";
@@ -675,7 +712,7 @@ public void testReadChallengeTransactionInvalidDataValueWrongEncodedLength() thr
long end = now + 300;
TimeBounds timeBounds = new TimeBounds(now, end);
- byte[] nonce = new byte[32];
+ byte[] nonce = new byte[47];
SecureRandom random = new SecureRandom();
random.nextBytes(nonce);
BaseEncoding base64Encoding = BaseEncoding.base64();
@@ -703,29 +740,124 @@ public void testReadChallengeTransactionInvalidDataValueWrongEncodedLength() thr
Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("Random nonce encoded as base64 should be 64 bytes long.", e.getMessage());
+ assertEquals("Random nonce before encoding as base64 should be 48 bytes long.", e.getMessage());
}
}
@Test
- public void testReadChallengeTransactionInvalidDataValueCorruptBase64() throws IOException {
+ public void testReadChallengeTransactionValidDoesNotVerifyHomeDomainWithHomeDomainSetToNull() 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
+ );
+
+ 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);
- byte[] encodedNonce = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?AAAAAAAAAAAAAAAAAAAAAAAAAA".getBytes("UTF-8");
+ Transaction transaction = Sep10Challenge.newChallenge(
+ server,
+ network,
+ client.getAccountId(),
+ domainName,
+ timeBounds
+ );
+
+ Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, "invalid.domain");
+ assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction);
+ }
+
+ @Test
+ public void testReadChallengeTransactionValidAdditionalManageDataOpsWithSourceAccountSetToServerAccount() throws IOException, InvalidSep10ChallengeException {
+ 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);
+
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
Account sourceAccount = new Account(server.getAccountId(), -1L);
- ManageDataOperation manageDataOperation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
.setSourceAccount(client.getAccountId())
.build();
+ ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes())
+ .setSourceAccount(server.getAccountId())
+ .build();
+ Operation[] operations = new Operation[]{operation1, operation2};
+ Transaction transaction = new Transaction(
+ sourceAccount.getAccountId(),
+ 100 * operations.length,
+ sourceAccount.getIncrementedSequenceNumber(),
+ operations,
+ Memo.none(),
+ timeBounds,
+ network
+ );
+ transaction.sign(server);
+ String challenge = transaction.toEnvelopeXdrBase64();
- Operation[] operations = new Operation[]{manageDataOperation1};
+ Sep10Challenge.ChallengeTransaction challengeTransaction = Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
+ assertEquals(new Sep10Challenge.ChallengeTransaction(transaction, client.getAccountId()), challengeTransaction);
+ }
+
+ @Test
+ public void testReadChallengeTransactionInvalidAdditionalManageDataOpsWithoutSourceAccountSetToServerAccount() 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);
+
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(client.getAccountId())
+ .build();
+ ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes())
+ .setSourceAccount(client.getAccountId())
+ .build();
+ Operation[] operations = new Operation[]{operation1, operation2};
Transaction transaction = new Transaction(
sourceAccount.getAccountId(),
100 * operations.length,
@@ -742,13 +874,12 @@ public void testReadChallengeTransactionInvalidDataValueCorruptBase64() throws I
Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("Failed to decode random nonce provided in ManageData operation.", e.getMessage());
- assertEquals("com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: ?", e.getCause().getMessage());
+ assertEquals("Subsequent operations are unrecognized.", e.getMessage());
}
}
@Test
- public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws IOException {
+ public void testReadChallengeTransactionInvalidAdditionalManageDataOpsWithSourceAccountSetToNull() throws IOException {
KeyPair server = KeyPair.random();
KeyPair client = KeyPair.random();
String domainName = "example.com";
@@ -759,18 +890,18 @@ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws
long end = now + 300;
TimeBounds timeBounds = new TimeBounds(now, end);
- byte[] nonce = new byte[47];
+ byte[] nonce = new byte[48];
SecureRandom random = new SecureRandom();
random.nextBytes(nonce);
BaseEncoding base64Encoding = BaseEncoding.base64();
byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
Account sourceAccount = new Account(server.getAccountId(), -1L);
- ManageDataOperation manageDataOperation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
.setSourceAccount(client.getAccountId())
.build();
-
- Operation[] operations = new Operation[]{manageDataOperation1};
+ ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes()).build();
+ Operation[] operations = new Operation[]{operation1, operation2};
Transaction transaction = new Transaction(
sourceAccount.getAccountId(),
100 * operations.length,
@@ -787,40 +918,53 @@ public void testReadChallengeTransactionInvalidDataValueWrongByteLength() throws
Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("Random nonce before encoding as base64 should be 48 bytes long.", e.getMessage());
+ assertEquals("Operation should have a source account.", e.getMessage());
}
}
@Test
- public void testReadChallengeTransactionInvalidDomainNameMismatch() throws IOException {
+ public void testReadChallengeTransactionInvalidAdditionalOpsOfOtherTypes() throws IOException {
KeyPair server = KeyPair.random();
KeyPair client = KeyPair.random();
- Network network = Network.TESTNET;
String domainName = "example.com";
- String mismatchDomainName = "mismatch_example.com";
+
+ Network network = Network.TESTNET;
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.");
- }
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(client.getAccountId())
+ .build();
+ BumpSequenceOperation operation2 = new BumpSequenceOperation.Builder(0L)
+ .setSourceAccount(server.getAccountId())
+ .build();
+ Operation[] operations = new Operation[]{operation1, operation2};
+ 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(transaction.toEnvelopeXdrBase64(), server.getAccountId(), Network.TESTNET, mismatchDomainName);
+ Sep10Challenge.readChallengeTransaction(challenge, server.getAccountId(), Network.TESTNET, domainName);
fail();
} catch (InvalidSep10ChallengeException e) {
- assertEquals("The transaction's operation key name does not include the expected home domain.", e.getMessage());
+ assertEquals("Operation type should be ManageData.", e.getMessage());
}
}
@@ -1645,4 +1789,236 @@ public void testVerifyChallengeTransactionSignersInvalidNoSignersEmptySet() thro
assertEquals("No verifiable signers provided, at least one G... address must be provided.", e.getMessage());
}
}
+
+ @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();
+ KeyPair masterClient = 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);
+
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(masterClient.getAccountId())
+ .build();
+ ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes())
+ .setSourceAccount(server.getAccountId())
+ .build();
+ Operation[] operations = new Operation[]{operation1, operation2};
+ Transaction transaction = new Transaction(
+ sourceAccount.getAccountId(),
+ 100 * operations.length,
+ sourceAccount.getIncrementedSequenceNumber(),
+ operations,
+ Memo.none(),
+ timeBounds,
+ network
+ );
+ transaction.sign(server);
+ transaction.sign(masterClient);
+
+ Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId()));
+ Set signersFound = Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, domainName, signers);
+ assertEquals(signers, signersFound);
+ }
+
+ @Test
+ public void testVerifyChallengeTransactionInvalidAdditionalManageDataOpsWithoutSourceAccountSetToServerAccount() throws IOException {
+ KeyPair server = KeyPair.random();
+ KeyPair masterClient = 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);
+
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(masterClient.getAccountId())
+ .build();
+ ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes())
+ .setSourceAccount(masterClient.getAccountId())
+ .build();
+ Operation[] operations = new Operation[]{operation1, operation2};
+ Transaction transaction = new Transaction(
+ sourceAccount.getAccountId(),
+ 100 * operations.length,
+ sourceAccount.getIncrementedSequenceNumber(),
+ operations,
+ Memo.none(),
+ timeBounds,
+ network
+ );
+ transaction.sign(server);
+ transaction.sign(masterClient);
+
+ Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId()));
+ try {
+ Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, domainName, signers);
+ fail();
+ } catch (InvalidSep10ChallengeException e) {
+ assertEquals("Subsequent operations are unrecognized.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testVerifyChallengeTransactionInvalidAdditionalManageDataOpsWithSourceAccountSetToNull() throws IOException {
+ KeyPair server = KeyPair.random();
+ KeyPair masterClient = 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);
+
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(masterClient.getAccountId())
+ .build();
+ ManageDataOperation operation2 = new ManageDataOperation.Builder("key", "value".getBytes()).build();
+ Operation[] operations = new Operation[]{operation1, operation2};
+ Transaction transaction = new Transaction(
+ sourceAccount.getAccountId(),
+ 100 * operations.length,
+ sourceAccount.getIncrementedSequenceNumber(),
+ operations,
+ Memo.none(),
+ timeBounds,
+ network
+ );
+ transaction.sign(server);
+ transaction.sign(masterClient);
+
+ Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId()));
+ try {
+ Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, domainName, signers);
+ fail();
+ } catch (InvalidSep10ChallengeException e) {
+ assertEquals("Operation should have a source account.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testVerifyChallengeTransactionInvalidAdditionalOpsOfOtherTypes() throws IOException {
+ KeyPair server = KeyPair.random();
+ KeyPair masterClient = 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);
+
+ byte[] nonce = new byte[48];
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(nonce);
+ BaseEncoding base64Encoding = BaseEncoding.base64();
+ byte[] encodedNonce = base64Encoding.encode(nonce).getBytes();
+
+ Account sourceAccount = new Account(server.getAccountId(), -1L);
+ ManageDataOperation operation1 = new ManageDataOperation.Builder(domainName + " auth", encodedNonce)
+ .setSourceAccount(masterClient.getAccountId())
+ .build();
+ BumpSequenceOperation operation2 = new BumpSequenceOperation.Builder(0L)
+ .setSourceAccount(server.getAccountId())
+ .build();
+ Operation[] operations = new Operation[]{operation1, operation2};
+ Transaction transaction = new Transaction(
+ sourceAccount.getAccountId(),
+ 100 * operations.length,
+ sourceAccount.getIncrementedSequenceNumber(),
+ operations,
+ Memo.none(),
+ timeBounds,
+ network
+ );
+ transaction.sign(server);
+ transaction.sign(masterClient);
+
+ Set signers = new HashSet(Collections.singletonList(masterClient.getAccountId()));
+ try {
+ Sep10Challenge.verifyChallengeTransactionSigners(transaction.toEnvelopeXdrBase64(), server.getAccountId(), network, domainName, signers);
+ fail();
+ } catch (InvalidSep10ChallengeException e) {
+ assertEquals("Operation type should be ManageData.", e.getMessage());
+ }
+ }
}