Skip to content

Commit

Permalink
[ANCHOR-612] Fix Sep10 validate wildcard domain failure (#1271)
Browse files Browse the repository at this point in the history
### Description

Decode the challenge XDR and extract the home domain

Verify the domain matches a configured home domain or wildcard

Call the SDK’s parse challenge method with the extracted home domain

### Testing

- `./gradlew test`
  • Loading branch information
JiahuiWho authored Feb 29, 2024
1 parent 9b56c9d commit a8ebfa2
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/sub_essential_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ jobs:
RUN_EVENT_PROCESSING_SERVER: true
RUN_WALLET_SERVER: true
SEP1_TOML_VALUE: /home/runner/java-stellar-anchor-sdk/service-runner/src/main/resources/config/stellar.host.docker.internal.toml
SEP10_HOME_DOMAIN: host.docker.internal:8080
SEP10_WEB_AUTH_DOMAIN: host.docker.internal:8080
SEP10_HOME_DOMAINS: host.docker.internal:8080,*.stellar.org
run: |
cp /home/runner/java-stellar-anchor-sdk/service-runner/build/libs/anchor-platform-runner-*.jar /home/runner/anchor-platform-runner.jar
java -jar /home/runner/anchor-platform-runner.jar -t &
Expand Down
72 changes: 59 additions & 13 deletions core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,13 @@ void validateAccountFormat(ChallengeRequest request) throws SepException {

void validateChallengeRequest(
ValidationRequest request, AccountResponse account, String clientDomain)
throws InvalidSep10ChallengeException, IOException {
throws InvalidSep10ChallengeException, IOException, SepValidationException {
// fetch the signers from the transaction
Set<Sep10Challenge.Signer> signers = fetchSigners(account);
// the signatures must be greater than the medium threshold of the account.
int threshold = account.getThresholds().getMedThreshold();
Network network = new Network(appConfig.getStellarNetworkPassphrase());
String homeDomain = extractHomeDomainFromChallengeXdr(request.getTransaction(), network);

infoF(
"Verifying challenge threshold. server_account={}, client_domain={}, threshold={}, signers={}",
Expand All @@ -299,8 +301,8 @@ void validateChallengeRequest(
.verifyChallengeTransactionThreshold(
request.getTransaction(),
serverAccountId,
new Network(appConfig.getStellarNetworkPassphrase()),
sep10Config.getHomeDomains().toArray(new String[0]),
network,
homeDomain,
sep10Config.getWebAuthDomain(),
threshold,
signers);
Expand All @@ -316,7 +318,7 @@ Set<Sep10Challenge.Signer> fetchSigners(AccountResponse account) {

AccountResponse fetchAccount(
ValidationRequest request, ChallengeTransaction challenge, String clientDomain)
throws InvalidSep10ChallengeException, IOException {
throws InvalidSep10ChallengeException, IOException, SepValidationException {
// Check the client's account
AccountResponse account;
try {
Expand Down Expand Up @@ -348,13 +350,16 @@ AccountResponse fetchAccount(
"There is more than one client signer on challenge transaction for an account that doesn't exist");
}

Network network = new Network(appConfig.getStellarNetworkPassphrase());
String homeDomain = extractHomeDomainFromChallengeXdr(request.getTransaction(), network);

debug("Calling Sep10Challenge.verifyChallengeTransactionSigners");
Sep10ChallengeWrapper.instance()
.verifyChallengeTransactionSigners(
request.getTransaction(),
serverAccountId,
new Network(appConfig.getStellarNetworkPassphrase()),
sep10Config.getHomeDomains().toArray(new String[0]),
network,
homeDomain,
sep10Config.getWebAuthDomain(),
signers);

Expand Down Expand Up @@ -392,6 +397,8 @@ ChallengeTransaction parseChallenge(ValidationRequest request)
}

String transaction = request.getTransaction();
Network network = new Network(appConfig.getStellarNetworkPassphrase());
String homeDomain = extractHomeDomainFromChallengeXdr(transaction, network);

debug("Parse challenge string.");
ChallengeTransaction challenge =
Expand All @@ -400,7 +407,7 @@ ChallengeTransaction parseChallenge(ValidationRequest request)
transaction,
serverAccountId,
new Network(appConfig.getStellarNetworkPassphrase()),
sep10Config.getHomeDomains().toArray(new String[0]),
homeDomain,
sep10Config.getWebAuthDomain());

debugF(
Expand Down Expand Up @@ -430,6 +437,45 @@ String generateSep10Jwt(ChallengeTransaction challenge, String clientDomain, Str
debug("jwtToken:", sep10Jwt);
return jwtService.encode(sep10Jwt);
}

/**
* Extracts the home domain from a Stellar SEP-10 challenge transaction represented in XDR format.
*
* @param challengeXdr SEP-10 transaction challenge transaction in base64.
* @param network The network to connect to for verifying and retrieving.
* @return The extracted home domain from the Manage Data operation within the SEP-10 challenge
* transaction.
* @throws IOException If read XDR string fails, the exception will be thrown.
* @throws SepValidationException If the transaction is not a valid SEP-10 challenge transaction.
*/
String extractHomeDomainFromChallengeXdr(String challengeXdr, Network network)
throws IOException, SepValidationException {
AbstractTransaction parsed =
Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), challengeXdr, network);
if (!(parsed instanceof Transaction)) {
throw new SepValidationException("Transaction cannot be a fee bump transaction");
}

Transaction transaction = (Transaction) parsed;
if (transaction.getOperations().length < 1) {
throw new SepValidationException("Transaction requires at least one ManageData operation.");
}

// verify that the first operation in the transaction is a Manage Data operation
Operation operation = transaction.getOperations()[0];
if (!(operation instanceof ManageDataOperation)) {
throw new SepValidationException("Operation type should be ManageData.");
}

ManageDataOperation manageDataOperation = (ManageDataOperation) operation;
String homeDomain = manageDataOperation.getName().split(" ")[0];
if (!Sep10Helper.isDomainNameMatch(sep10Config.getHomeDomains(), homeDomain)) {
throw new SepValidationException(
"The transaction's operation key name does not include one of the expected home domains.");
}

return homeDomain;
}
}

class Sep10ChallengeWrapper {
Expand Down Expand Up @@ -466,35 +512,35 @@ public synchronized ChallengeTransaction readChallengeTransaction(
String challengeXdr,
String serverAccountId,
Network network,
String[] domainNames,
String domainName,
String webAuthDomain)
throws InvalidSep10ChallengeException, IOException {
return Sep10Challenge.readChallengeTransaction(
challengeXdr, serverAccountId, network, domainNames, webAuthDomain);
challengeXdr, serverAccountId, network, domainName, webAuthDomain);
}

public synchronized void verifyChallengeTransactionSigners(
String challengeXdr,
String serverAccountId,
Network network,
String[] domainNames,
String domainName,
String webAuthDomain,
Set<String> signers)
throws InvalidSep10ChallengeException, IOException {
Sep10Challenge.verifyChallengeTransactionSigners(
challengeXdr, serverAccountId, network, domainNames, webAuthDomain, signers);
challengeXdr, serverAccountId, network, domainName, webAuthDomain, signers);
}

public synchronized void verifyChallengeTransactionThreshold(
String challengeXdr,
String serverAccountId,
Network network,
String[] domainNames,
String domainName,
String webAuthDomain,
int threshold,
Set<Sep10Challenge.Signer> signers)
throws InvalidSep10ChallengeException, IOException {
Sep10Challenge.verifyChallengeTransactionThreshold(
challengeXdr, serverAccountId, network, domainNames, webAuthDomain, threshold, signers);
challengeXdr, serverAccountId, network, domainName, webAuthDomain, threshold, signers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,13 @@ internal class Sep10ServiceTest {
assertThrows<SepValidationException> { sep10Service.validateChallenge(vr) }
}

@Test
fun `Test validate challenge with bad home domain failure`() {
val vr = ValidationRequest()
vr.transaction = createTestChallenge("", "abc.badPattern.stellar.org", false)
assertThrows<SepValidationException> { sep10Service.validateChallenge(vr) }
}

@Test
fun `Test request to create challenge with bad home domain failure`() {
val cr =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import java.io.IOException
import java.util.*
import kotlin.test.assertFailsWith
import org.junit.jupiter.api.Test
import org.stellar.anchor.api.exception.SepException
import org.stellar.anchor.api.exception.SepNotAuthorizedException
import org.stellar.anchor.api.sep.sep10.ValidationRequest
import org.stellar.anchor.client.Sep10Client
Expand All @@ -15,6 +16,7 @@ import org.stellar.anchor.platform.*
class Sep10Tests : AbstractIntegrationTests(TestConfig()) {
lateinit var sep10Client: Sep10Client
lateinit var sep10ClientMultiSig: Sep10Client
lateinit var webAuthDomain: String

init {
if (!::sep10Client.isInitialized) {
Expand All @@ -39,6 +41,7 @@ class Sep10Tests : AbstractIntegrationTests(TestConfig()) {
),
)
}
webAuthDomain = toml.getString("WEB_AUTH_ENDPOINT").split("/")[2]
}

@Test
Expand All @@ -52,12 +55,25 @@ class Sep10Tests : AbstractIntegrationTests(TestConfig()) {

@Test
fun testAuth() {
sep10Client.auth()
sep10Client.auth(webAuthDomain)
}

@Test
fun testAuthWithWildcardDomain() {
sep10Client.auth("test.stellar.org")
}

@Test
fun testAuthWithWildcardDomainFail() {
assertFailsWith(
exceptionClass = SepException::class,
block = { sep10Client.auth("bad.domain.org") },
)
}

@Test
fun testMultiSig() {
sep10ClientMultiSig.auth()
sep10ClientMultiSig.auth(webAuthDomain)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@ class Sep10Client(
signingSeed: String
) : this(endpoint, serverAccount, walletAccount, arrayOf(signingSeed))

fun auth(): String {
fun auth(homeDomain: String): String {
// Call to get challenge
val challenge = challenge()
val challenge = challenge(homeDomain)
// Sign challenge
val txn = sign(challenge, signingKeys, serverAccount)
val txn = sign(challenge, signingKeys, serverAccount, homeDomain)
// Get token from challenge
return validate(ValidationRequest.of(txn))!!.token
}

fun challenge(): ChallengeResponse {
val url = String.format("%s?account=%s", this.endpoint, walletAccount)
fun challenge(homeDomain: String? = ""): ChallengeResponse {
val url =
String.format("%s?account=%s&home_domain=%s", this.endpoint, walletAccount, homeDomain)
val responseBody = httpGet(url)
return gson.fromJson(responseBody, ChallengeResponse::class.java)
}
Expand All @@ -47,7 +48,8 @@ class Sep10Client(
private fun sign(
challengeResponse: ChallengeResponse,
signingKeys: Array<String>,
serverAccount: String
serverAccount: String,
homeDomain: String,
): String {
val url = URL(endpoint)
val webAuthDomain = url.authority
Expand All @@ -56,7 +58,7 @@ class Sep10Client(
challengeResponse.transaction,
serverAccount,
Network(challengeResponse.networkPassphrase),
webAuthDomain, // TODO: home domain may be different than WEB_AUTH_DOMAIN
homeDomain,
webAuthDomain
)
for (signingKey in signingKeys) {
Expand Down
2 changes: 2 additions & 0 deletions service-runner/src/main/resources/profiles/default/config.env
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ sep1.toml.type=file
sep1.toml.value=/config/stellar.localhost.toml
sep6.enabled=true
sep10.enabled=true
sep10.web_auth_domain=localhost:8080
sep10.home_domains=localhost:8080,*.stellar.org
sep12.enabled=true
sep31.enabled=true
sep38.enabled=true
Expand Down

0 comments on commit a8ebfa2

Please sign in to comment.