From 5e82f16273b7ce97e50866cba207bc52f498e9c7 Mon Sep 17 00:00:00 2001 From: Jake Urban <10968980+JakeUrban@users.noreply.github.com> Date: Wed, 16 Sep 2020 12:52:30 -0700 Subject: [PATCH] {txnbuild, webauth}: support the SEP-10 V2 (#2967) resolves #2968 Makes the same changes made to the JS SDK. Co-authored-by: Howard Tinghao Chen --- exp/services/webauth/README.md | 6 +- exp/services/webauth/cmd/serve.go | 14 ++ .../webauth/internal/serve/challenge.go | 30 ++++- .../webauth/internal/serve/challenge_test.go | 89 ++++++++++++- exp/services/webauth/internal/serve/serve.go | 70 +++++++++- exp/services/webauth/internal/serve/token.go | 29 +++-- .../webauth/internal/serve/token_test.go | 123 ++++++++++++++++-- txnbuild/CHANGELOG.md | 11 ++ txnbuild/transaction.go | 29 +++-- .../transaction_challenge_example_test.go | 6 +- txnbuild/transaction_test.go | 79 +++++------ 11 files changed, 401 insertions(+), 85 deletions(-) diff --git a/exp/services/webauth/README.md b/exp/services/webauth/README.md index bc7c76605b..fea0c94bb2 100644 --- a/exp/services/webauth/README.md +++ b/exp/services/webauth/README.md @@ -1,6 +1,6 @@ # webauth -This is a [SEP-10] Web Authentication implementation based on SEP-10 v1.3.0 +This is a [SEP-10] Web Authentication implementation based on SEP-10 v2.0.0 that requires a user to prove they possess a signing key(s) that meets the high threshold for an account, i.e. they have the ability to perform any high threshold operation on the given account. If an account does not exist it may @@ -42,6 +42,7 @@ Usage: Flags: --allow-accounts-that-do-not-exist Allow accounts that do not exist (ALLOW_ACCOUNTS_THAT_DO_NOT_EXIST) + --auth-home-domain string Home domain(s) of the service(s) requiring SEP-10 authentication comma separated (first domain is the default domain) (AUTH_HOME_DOMAIN) --challenge-expires-in int The time period in seconds after which the challenge transaction expires (CHALLENGE_EXPIRES_IN) (default 300) --horizon-url string Horizon URL used for looking up account details (HORIZON_URL) (default "https://horizon-testnet.stellar.org/") --jwk string JSON Web Key (JWK) used for signing JWTs (if the key is an asymmetric key that has separate public and private key, the JWK must contain the private key) (JWK) @@ -50,6 +51,7 @@ Flags: --network-passphrase string Network passphrase of the Stellar network transactions should be signed for (NETWORK_PASSPHRASE) (default "Test SDF Network ; September 2015") --port int Port to listen and serve on (PORT) (default 8000) --signing-key string Stellar signing key(s) used for signing transactions comma separated (first key is used for signing, others used for verifying challenges) (SIGNING_KEY) + --stellar-toml-domain string Domain where stellar.toml is served. The private key counterpart of the SIGNING_KEY specified in the stellar.toml file has to be provided via signing-key (STELLAR_TOML_DOMAIN) ``` -[SEP-10]: https://github.com/stellar/stellar-protocol/blob/2be91ce8d8032ca9b2f368800d06b9fba346a147/ecosystem/sep-0010.md +[SEP-10]: https://github.com/stellar/stellar-protocol/blob/28c636b4ef5074ca0c3d46bbe9bf0f3f38095233/ecosystem/sep-0010.md diff --git a/exp/services/webauth/cmd/serve.go b/exp/services/webauth/cmd/serve.go index 9499429f59..d454eb940f 100644 --- a/exp/services/webauth/cmd/serve.go +++ b/exp/services/webauth/cmd/serve.go @@ -51,6 +51,20 @@ func (c *ServeCommand) Command() *cobra.Command { ConfigKey: &opts.SigningKeys, Required: true, }, + { + Name: "stellar-toml-domain", + Usage: "Domain where stellar.toml is served. The private key counterpart of the SIGNING_KEY specified in the stellar.toml file has to be provided via signing-key", + OptType: types.String, + ConfigKey: &opts.StellarTOMLDomain, + Required: true, + }, + { + Name: "auth-home-domain", + Usage: "Home domain(s) of the service(s) requiring SEP-10 authentication comma separated (first domain is the default domain)", + OptType: types.String, + ConfigKey: &opts.AuthHomeDomains, + Required: true, + }, { Name: "challenge-expires-in", Usage: "The time period in seconds after which the challenge transaction expires", diff --git a/exp/services/webauth/internal/serve/challenge.go b/exp/services/webauth/internal/serve/challenge.go index 54a64fc7c0..a10bb7553e 100644 --- a/exp/services/webauth/internal/serve/challenge.go +++ b/exp/services/webauth/internal/serve/challenge.go @@ -2,6 +2,7 @@ package serve import ( "net/http" + "strings" "time" "github.com/stellar/go/keypair" @@ -15,10 +16,10 @@ import ( // requests for a new challenge transaction. type challengeHandler struct { Logger *supportlog.Entry - ServerName string NetworkPassphrase string SigningKey *keypair.Full ChallengeExpiresIn time.Duration + HomeDomains []string } type challengeResponse struct { @@ -28,17 +29,37 @@ type challengeResponse struct { func (h challengeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + queryValues := r.URL.Query() - account := r.URL.Query().Get("account") + account := queryValues.Get("account") if !strkey.IsValidEd25519PublicKey(account) { badRequest.Render(w) return } + homeDomain := queryValues.Get("home_domain") + if homeDomain != "" { + // In some cases the full stop (period) character is used at the end of a FQDN. + homeDomain = strings.TrimSuffix(homeDomain, ".") + matched := false + for _, supportedDomain := range h.HomeDomains { + if homeDomain == supportedDomain { + matched = true + break + } + } + if !matched { + badRequest.Render(w) + return + } + } else { + homeDomain = h.HomeDomains[0] + } + tx, err := txnbuild.BuildChallengeTx( h.SigningKey.Seed(), account, - h.ServerName, + homeDomain, h.NetworkPassphrase, h.ChallengeExpiresIn, ) @@ -58,7 +79,8 @@ func (h challengeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { l := h.Logger.Ctx(ctx). WithField("tx", hash). WithField("account", account). - WithField("serversigner", h.SigningKey.Address()) + WithField("serversigner", h.SigningKey.Address()). + WithField("homedomain", homeDomain) l.Info("Generated challenge transaction for account.") diff --git a/exp/services/webauth/internal/serve/challenge_test.go b/exp/services/webauth/internal/serve/challenge_test.go index c8f1b3d279..3aea8f1b6b 100644 --- a/exp/services/webauth/internal/serve/challenge_test.go +++ b/exp/services/webauth/internal/serve/challenge_test.go @@ -2,6 +2,7 @@ package serve import ( "encoding/json" + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -22,10 +23,10 @@ func TestChallenge(t *testing.T) { h := challengeHandler{ Logger: supportlog.DefaultLogger, - ServerName: "testserver", NetworkPassphrase: network.TestNetworkPassphrase, SigningKey: serverKey, ChallengeExpiresIn: time.Minute, + HomeDomains: []string{"testdomain"}, } r := httptest.NewRequest("GET", "/?account="+account.Address(), nil) @@ -56,7 +57,7 @@ func TestChallenge(t *testing.T) { opSourceAccount := tx.Operations()[0].SourceAccount.ToAccountId() assert.Equal(t, account.Address(), opSourceAccount.Address()) assert.Equal(t, xdr.OperationTypeManageData, tx.Operations()[0].Body.Type) - assert.Regexp(t, "^testserver auth", tx.Operations()[0].Body.ManageDataOp.DataName) + assert.Regexp(t, "^testdomain auth", tx.Operations()[0].Body.ManageDataOp.DataName) hash, err := network.HashTransactionInEnvelope(tx, res.NetworkPassphrase) require.NoError(t, err) @@ -65,8 +66,60 @@ func TestChallenge(t *testing.T) { assert.Equal(t, network.TestNetworkPassphrase, res.NetworkPassphrase) } -func TestChallengeNoAccount(t *testing.T) { - h := challengeHandler{} +func TestChallenge_anotherHomeDomain(t *testing.T) { + serverKey := keypair.MustRandom() + account := keypair.MustRandom() + anotherDomain := "anotherdomain" + + h := challengeHandler{ + Logger: supportlog.DefaultLogger, + NetworkPassphrase: network.TestNetworkPassphrase, + SigningKey: serverKey, + ChallengeExpiresIn: time.Minute, + HomeDomains: []string{"testdomain", anotherDomain}, + } + + r := httptest.NewRequest("GET", fmt.Sprintf("/?account=%s&home_domain=%s", account.Address(), anotherDomain), nil) + w := httptest.NewRecorder() + h.ServeHTTP(w, r) + resp := w.Result() + + require.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type")) + + res := struct { + Transaction string `json:"transaction"` + NetworkPassphrase string `json:"network_passphrase"` + }{} + err := json.NewDecoder(resp.Body).Decode(&res) + require.NoError(t, err) + + var tx xdr.TransactionEnvelope + err = xdr.SafeUnmarshalBase64(res.Transaction, &tx) + require.NoError(t, err) + + assert.Len(t, tx.Signatures(), 1) + sourceAccount := tx.SourceAccount().ToAccountId() + assert.Equal(t, serverKey.Address(), sourceAccount.Address()) + assert.Equal(t, tx.SeqNum(), int64(0)) + assert.Equal(t, time.Unix(int64(tx.TimeBounds().MaxTime), 0).Sub(time.Unix(int64(tx.TimeBounds().MinTime), 0)), time.Minute) + assert.Len(t, tx.Operations(), 1) + opSourceAccount := tx.Operations()[0].SourceAccount.ToAccountId() + assert.Equal(t, account.Address(), opSourceAccount.Address()) + assert.Equal(t, xdr.OperationTypeManageData, tx.Operations()[0].Body.Type) + assert.Regexp(t, "^anotherdomain auth", tx.Operations()[0].Body.ManageDataOp.DataName) + + hash, err := network.HashTransactionInEnvelope(tx, res.NetworkPassphrase) + require.NoError(t, err) + assert.NoError(t, serverKey.FromAddress().Verify(hash[:], tx.V0.Signatures[0].Signature)) + + assert.Equal(t, network.TestNetworkPassphrase, res.NetworkPassphrase) +} + +func TestChallenge_noAccount(t *testing.T) { + h := challengeHandler{ + SigningKey: keypair.MustRandom(), + } r := httptest.NewRequest("GET", "/", nil) w := httptest.NewRecorder() @@ -81,8 +134,10 @@ func TestChallengeNoAccount(t *testing.T) { assert.JSONEq(t, `{"error":"The request was invalid in some way."}`, string(body)) } -func TestChallengeInvalidAccount(t *testing.T) { - h := challengeHandler{} +func TestChallenge_invalidAccount(t *testing.T) { + h := challengeHandler{ + SigningKey: keypair.MustRandom(), + } r := httptest.NewRequest("GET", "/?account=GREATACCOUNT", nil) w := httptest.NewRecorder() @@ -96,3 +151,25 @@ func TestChallengeInvalidAccount(t *testing.T) { require.NoError(t, err) assert.JSONEq(t, `{"error":"The request was invalid in some way."}`, string(body)) } + +func TestChallenge_invalidHomeDomain(t *testing.T) { + account := keypair.MustRandom() + anotherDomain := "anotherdomain" + + h := challengeHandler{ + SigningKey: keypair.MustRandom(), + HomeDomains: []string{"testdomain"}, + } + + r := httptest.NewRequest("GET", fmt.Sprintf("/?account=%s&home_domain=%s", account.Address(), anotherDomain), nil) + w := httptest.NewRecorder() + h.ServeHTTP(w, r) + resp := w.Result() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type")) + + body, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + assert.JSONEq(t, `{"error":"The request was invalid in some way."}`, string(body)) +} diff --git a/exp/services/webauth/internal/serve/serve.go b/exp/services/webauth/internal/serve/serve.go index 0727d2eca6..546ad6e0d5 100644 --- a/exp/services/webauth/internal/serve/serve.go +++ b/exp/services/webauth/internal/serve/serve.go @@ -3,10 +3,12 @@ package serve import ( "encoding/json" "fmt" + "io" "net/http" "strings" "time" + "github.com/BurntSushi/toml" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/support/errors" @@ -16,12 +18,16 @@ import ( "gopkg.in/square/go-jose.v2" ) +const stellarTomlMaxSize = 100 * 1024 + type Options struct { Logger *supportlog.Entry HorizonURL string Port int NetworkPassphrase string SigningKeys string + StellarTOMLDomain string + AuthHomeDomains string ChallengeExpiresIn time.Duration JWK string JWTIssuer string @@ -48,18 +54,41 @@ func Serve(opts Options) { } func handler(opts Options) (http.Handler, error) { - signingKeys := []*keypair.Full{} - signingAddresses := []*keypair.FromAddress{} - for i, signingKeyStr := range strings.Split(opts.SigningKeys, ",") { + var signingKeyFull *keypair.Full + signingKeyStrs := strings.Split(opts.SigningKeys, ",") + signingAddresses := make([]*keypair.FromAddress, 0, len(signingKeyStrs)) + + for i, signingKeyStr := range signingKeyStrs { signingKey, err := keypair.ParseFull(signingKeyStr) if err != nil { return nil, errors.Wrap(err, "parsing signing key seed") } - signingKeys = append(signingKeys, signingKey) + + // Only the first key is used for signing. The rest is for verifying challenge transactions, if any. + if i == 0 { + var signingKeyPub string + signingKeyPub, err = getStellarTOMLSigningKey(opts.StellarTOMLDomain) + if err != nil { + opts.Logger.Errorf("Error reading SIGNING_KEY from domain %s: %v", opts.StellarTOMLDomain, err) + } + + if err == nil && signingKey.Address() != signingKeyPub { + opts.Logger.Error("The configured signing key does not match the private key counterpart of the SIGNING_KEY in the stellar.toml file.") + } + + signingKeyFull = signingKey + } signingAddresses = append(signingAddresses, signingKey.FromAddress()) opts.Logger.Info("Signing key ", i, ": ", signingKey.Address()) } + homeDomains := strings.Split(opts.AuthHomeDomains, ",") + trimmedHomeDomains := make([]string, 0, len(homeDomains)) + for _, homeDomain := range homeDomains { + // In some cases the full stop (period) character is used at the end of a FQDN. + trimmedHomeDomains = append(trimmedHomeDomains, strings.TrimSuffix(homeDomain, ".")) + } + jwk := jose.JSONWebKey{} err := json.Unmarshal([]byte(opts.JWK), &jwk) if err != nil { @@ -88,8 +117,9 @@ func handler(opts Options) (http.Handler, error) { mux.Get("/", challengeHandler{ Logger: opts.Logger, NetworkPassphrase: opts.NetworkPassphrase, - SigningKey: signingKeys[0], + SigningKey: signingKeyFull, ChallengeExpiresIn: opts.ChallengeExpiresIn, + HomeDomains: trimmedHomeDomains, }.ServeHTTP) mux.Post("/", tokenHandler{ Logger: opts.Logger, @@ -100,7 +130,37 @@ func handler(opts Options) (http.Handler, error) { JWTIssuer: opts.JWTIssuer, JWTExpiresIn: opts.JWTExpiresIn, AllowAccountsThatDoNotExist: opts.AllowAccountsThatDoNotExist, + HomeDomains: trimmedHomeDomains, }.ServeHTTP) return mux, nil } + +func getStellarTOMLSigningKey(domain string) (string, error) { + var signingKeyTOML struct { + SigningKey string `toml:"SIGNING_KEY"` + } + + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + + domain = strings.TrimRight(domain, "./") + resp, err := httpClient.Get(fmt.Sprintf("https://%s/.well-known/stellar.toml", domain)) + if err != nil { + return "", errors.Wrap(err, "sending http request") + } + defer resp.Body.Close() + + if resp.StatusCode/100 != 2 { + return "", errors.New("http request failed with non-200 status code") + } + + safeResBody := io.LimitReader(resp.Body, stellarTomlMaxSize) + _, err = toml.DecodeReader(safeResBody, &signingKeyTOML) + if err != nil { + return "", errors.Wrap(err, "decoding signing key") + } + + return signingKeyTOML.SigningKey, nil +} diff --git a/exp/services/webauth/internal/serve/token.go b/exp/services/webauth/internal/serve/token.go index dbec20c2b1..b698fa3578 100644 --- a/exp/services/webauth/internal/serve/token.go +++ b/exp/services/webauth/internal/serve/token.go @@ -24,6 +24,7 @@ type tokenHandler struct { JWTIssuer string JWTExpiresIn time.Duration AllowAccountsThatDoNotExist bool + HomeDomains []string } type tokenRequest struct { @@ -45,13 +46,22 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - var tx *txnbuild.Transaction - var clientAccountID string - var signingAddress *keypair.FromAddress + var ( + tx *txnbuild.Transaction + clientAccountID string + signingAddress *keypair.FromAddress + homeDomain string + ) for _, s := range h.SigningAddresses { - tx, clientAccountID, err = txnbuild.ReadChallengeTx(req.Transaction, s.Address(), h.NetworkPassphrase) - if err == nil { - signingAddress = s + for _, domain := range h.HomeDomains { + tx, clientAccountID, err = txnbuild.ReadChallengeTx(req.Transaction, s.Address(), h.NetworkPassphrase, domain) + if err == nil { + signingAddress = s + homeDomain = domain + break + } + } + if signingAddress != nil { break } } @@ -70,7 +80,8 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { l := h.Logger.Ctx(ctx). WithField("tx", hash). WithField("account", clientAccountID). - WithField("serversigner", signingAddress.Address()) + WithField("serversigner", signingAddress.Address()). + WithField("homedomain", homeDomain) l.Info("Start verifying challenge transaction.") @@ -93,7 +104,7 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if clientAccountExists { requiredThreshold := txnbuild.Threshold(clientAccount.Thresholds.HighThreshold) clientSignerSummary := clientAccount.SignerSummary() - signersVerified, err = txnbuild.VerifyChallengeTxThreshold(req.Transaction, signingAddress.Address(), h.NetworkPassphrase, requiredThreshold, clientSignerSummary) + signersVerified, err = txnbuild.VerifyChallengeTxThreshold(req.Transaction, signingAddress.Address(), h.NetworkPassphrase, homeDomain, requiredThreshold, clientSignerSummary) if err != nil { l. WithField("signersCount", len(clientSignerSummary)). @@ -109,7 +120,7 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { unauthorized.Render(w) return } - signersVerified, err = txnbuild.VerifyChallengeTxSigners(req.Transaction, signingAddress.Address(), h.NetworkPassphrase, clientAccountID) + signersVerified, err = txnbuild.VerifyChallengeTxSigners(req.Transaction, signingAddress.Address(), h.NetworkPassphrase, homeDomain, clientAccountID) if err != nil { l.Infof("Failed to verify with account master key as signer.") unauthorized.Render(w) diff --git a/exp/services/webauth/internal/serve/token_test.go b/exp/services/webauth/internal/serve/token_test.go index c2a672727e..fbb6209432 100644 --- a/exp/services/webauth/internal/serve/token_test.go +++ b/exp/services/webauth/internal/serve/token_test.go @@ -37,10 +37,11 @@ func TestToken_formInputSuccess(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -83,6 +84,7 @@ func TestToken_formInputSuccess(t *testing.T) { JWK: jwk, JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, + HomeDomains: []string{homeDomain}, } body := url.Values{} @@ -135,10 +137,11 @@ func TestToken_jsonInputSuccess(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -181,6 +184,7 @@ func TestToken_jsonInputSuccess(t *testing.T) { JWK: jwk, JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, + HomeDomains: []string{homeDomain}, } body := struct { @@ -275,6 +279,7 @@ func TestToken_jsonInputValidRotatingServerSigners(t *testing.T) { nil, ) + homeDomain := "example.com" h := tokenHandler{ Logger: supportlog.DefaultLogger, HorizonClient: horizonClient, @@ -283,6 +288,7 @@ func TestToken_jsonInputValidRotatingServerSigners(t *testing.T) { JWK: jwk, JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, + HomeDomains: []string{homeDomain}, } for i, serverKey := range serverKeys { @@ -291,7 +297,7 @@ func TestToken_jsonInputValidRotatingServerSigners(t *testing.T) { tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -373,10 +379,11 @@ func TestToken_jsonInputValidMultipleSigners(t *testing.T) { accountSigner2 := keypair.MustRandom() t.Logf("Client account signer 2: %s", accountSigner2.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -423,6 +430,7 @@ func TestToken_jsonInputValidMultipleSigners(t *testing.T) { JWK: jwk, JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, + HomeDomains: []string{homeDomain}, } body := struct { @@ -468,6 +476,89 @@ func TestToken_jsonInputValidMultipleSigners(t *testing.T) { assert.Equal(t, exp.Sub(iat), time.Minute) } +func TestToken_jsonInputHomeDomainNotSupported(t *testing.T) { + serverKey := keypair.MustRandom() + t.Logf("Server signing key: %s", serverKey.Address()) + + jwtPrivateKey, err := jwtkey.GenerateKey() + require.NoError(t, err) + jwk := jose.JSONWebKey{Key: jwtPrivateKey, Algorithm: string(jose.ES256)} + + account := keypair.MustRandom() + t.Logf("Client account: %s", account.Address()) + + homeDomain := "example.com" + tx, err := txnbuild.BuildChallengeTx( + serverKey.Seed(), + account.Address(), + homeDomain, + network.TestNetworkPassphrase, + time.Minute, + ) + require.NoError(t, err) + + chTx, err := tx.Base64() + require.NoError(t, err) + t.Logf("Tx: %s", chTx) + + tx, err = tx.Sign(network.TestNetworkPassphrase, account) + require.NoError(t, err) + txSigned, err := tx.Base64() + require.NoError(t, err) + t.Logf("Signed: %s", txSigned) + + horizonClient := &horizonclient.MockClient{} + horizonClient. + On("AccountDetail", horizonclient.AccountRequest{AccountID: account.Address()}). + Return( + horizon.Account{ + Thresholds: horizon.AccountThresholds{ + LowThreshold: 1, + MedThreshold: 10, + HighThreshold: 100, + }, + Signers: []horizon.Signer{ + { + Key: account.Address(), + Weight: 100, + }, + }}, + nil, + ) + + h := tokenHandler{ + Logger: supportlog.DefaultLogger, + HorizonClient: horizonClient, + NetworkPassphrase: network.TestNetworkPassphrase, + SigningAddresses: []*keypair.FromAddress{serverKey.FromAddress()}, + JWK: jwk, + JWTIssuer: "https://example.com", + JWTExpiresIn: time.Minute, + HomeDomains: []string{"another.example.com"}, + } + + body := struct { + Transaction string `json:"transaction"` + }{ + Transaction: txSigned, + } + bodyBytes, err := json.Marshal(body) + require.NoError(t, err) + r := httptest.NewRequest("POST", "/", bytes.NewReader(bodyBytes)) + r.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + h.ServeHTTP(w, r) + resp := w.Result() + + require.Equal(t, 400, resp.StatusCode) + assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type")) + + respBodyBytes, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + assert.JSONEq(t, `{"error":"The request was invalid in some way."}`, string(respBodyBytes)) +} + func TestToken_jsonInputNotEnoughWeight(t *testing.T) { serverKey := keypair.MustRandom() t.Logf("Server signing key: %s", serverKey.Address()) @@ -479,10 +570,11 @@ func TestToken_jsonInputNotEnoughWeight(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -525,6 +617,7 @@ func TestToken_jsonInputNotEnoughWeight(t *testing.T) { JWK: jwk, JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, + HomeDomains: []string{homeDomain}, } body := struct { @@ -560,10 +653,11 @@ func TestToken_jsonInputUnrecognizedSigner(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -606,6 +700,7 @@ func TestToken_jsonInputUnrecognizedSigner(t *testing.T) { JWK: jwk, JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, + HomeDomains: []string{homeDomain}, } body := struct { @@ -641,10 +736,11 @@ func TestToken_jsonInputAccountNotExistSuccess(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -683,6 +779,7 @@ func TestToken_jsonInputAccountNotExistSuccess(t *testing.T) { JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, AllowAccountsThatDoNotExist: true, + HomeDomains: []string{homeDomain}, } body := struct { @@ -743,10 +840,11 @@ func TestToken_jsonInputAccountNotExistFail(t *testing.T) { otherSigner := keypair.MustRandom() t.Logf("Other signer: %s", otherSigner.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -785,6 +883,7 @@ func TestToken_jsonInputAccountNotExistFail(t *testing.T) { JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, AllowAccountsThatDoNotExist: true, + HomeDomains: []string{homeDomain}, } body := struct { @@ -820,10 +919,11 @@ func TestToken_jsonInputAccountNotExistNotAllowed(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -862,6 +962,7 @@ func TestToken_jsonInputAccountNotExistNotAllowed(t *testing.T) { JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, AllowAccountsThatDoNotExist: false, + HomeDomains: []string{homeDomain}, } body := struct { @@ -899,10 +1000,11 @@ func TestToken_jsonInputUnrecognizedServerSigner(t *testing.T) { account := keypair.MustRandom() t.Logf("Client account: %s", account.Address()) + homeDomain := "example.com" tx, err := txnbuild.BuildChallengeTx( serverKey1.Seed(), account.Address(), - "testserver", + homeDomain, network.TestNetworkPassphrase, time.Minute, ) @@ -941,6 +1043,7 @@ func TestToken_jsonInputUnrecognizedServerSigner(t *testing.T) { JWTIssuer: "https://example.com", JWTExpiresIn: time.Minute, AllowAccountsThatDoNotExist: false, + HomeDomains: []string{homeDomain}, } body := struct { diff --git a/txnbuild/CHANGELOG.md b/txnbuild/CHANGELOG.md index 5ad9969bca..64d07469bd 100644 --- a/txnbuild/CHANGELOG.md +++ b/txnbuild/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [v4.0.0](https://github.com/stellar/go/releases/tag/horizonclient-v4.0.0) - 2020-08-31 + +### Breaking changes + +* Replace `BuildChallengeTx()`'s `anchorName string` parameter with `homeDomain string` +* Add `homeDomain string` parameter to `ReadChallengeTx()`, `VerifyChallengeTxThreshold()`, and `VerifyChallengeTxSigners()` + +SEP-10 now requires clients to verify the `SIGNING_KEY` included in the TOML file of the service requiring authentication is used to sign the challenge and that the challenge's Manage Data operation key includes the requested service's home domain. These checks ensure the challenge cannot be used in a relay attack. + +The breaking changes described above support the added SEP-10 2.0 requirements for both servers and clients. + ## [v3.1.0](https://github.com/stellar/go/releases/tag/horizonclient-v3.1.0) - 2020-05-14 * Fix bug which occurs when parsing xdr offers with prices that require more than 7 decimals of precision ([#2588](https://github.com/stellar/go/pull/2588)) diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index cbb5469ca0..d7c758fb42 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -744,7 +744,7 @@ func NewFeeBumpTransaction(params FeeBumpTransactionParams) (*FeeBumpTransaction // BuildChallengeTx is a factory method that creates a valid SEP 10 challenge, for use in web authentication. // "timebound" is the time duration the transaction should be valid for, and must be greater than 1s (300s is recommended). // More details on SEP 10: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md -func BuildChallengeTx(serverSignerSecret, clientAccountID, anchorName, network string, timebound time.Duration) (*Transaction, error) { +func BuildChallengeTx(serverSignerSecret, clientAccountID, homeDomain, network string, timebound time.Duration) (*Transaction, error) { if timebound < time.Second { return nil, errors.New("provided timebound must be at least 1s (300s is recommended)") } @@ -792,7 +792,7 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, anchorName, network s Operations: []Operation{ &ManageData{ SourceAccount: &ca, - Name: anchorName + " auth", + Name: homeDomain + " auth", Value: []byte(randomNonceToString), }, }, @@ -827,14 +827,22 @@ func generateRandomNonce(n int) ([]byte, error) { // ReadChallengeTx reads a SEP 10 challenge transaction and returns the decoded // transaction and client account ID contained within. // -// It also verifies that transaction is signed by the server. +// Before calling this function, retrieve the SIGNING_KEY included in the TOML file +// hosted on the service's homeDomain and ensure it matches the serverAccountID you +// intend to pass. +// +// This function verifies that the home domain passed is included in the Manage Data +// operation key and that the serverAccountID signed the challenge. If the +// serverAccountID also matches the SIGNING_KEY included in the TOML file hosted the +// service's homeDomain passed, malicious web services will not be able to use the +// challenge transaction POSTed back to the authentication endpoint. // // 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: // - VerifyChallengeTxThreshold // - VerifyChallengeTxSigners -func ReadChallengeTx(challengeTx, serverAccountID, network string) (tx *Transaction, clientAccountID string, err error) { +func ReadChallengeTx(challengeTx, serverAccountID, network, homeDomain string) (tx *Transaction, clientAccountID string, err error) { parsed, err := TransactionFromXDR(challengeTx) if err != nil { return tx, clientAccountID, errors.Wrap(err, "could not parse challenge") @@ -891,6 +899,11 @@ func ReadChallengeTx(challengeTx, serverAccountID, network string) (tx *Transact return tx, clientAccountID, err } + // verify manage data key + if op.Name != homeDomain+" auth" { + return tx, clientAccountID, errors.New("manage data operation key does not match homeDomain passed") + } + // verify manage data value nonceB64 := string(op.Value) if len(nonceB64) != 64 { @@ -928,13 +941,13 @@ func ReadChallengeTx(challengeTx, serverAccountID, network string) (tx *Transact // - One or more signatures in the transaction are not identifiable as the // server account or one of the signers provided in the arguments. // - The signatures are all valid but do not meet the threshold. -func VerifyChallengeTxThreshold(challengeTx, serverAccountID, network string, threshold Threshold, signerSummary SignerSummary) (signersFound []string, err error) { +func VerifyChallengeTxThreshold(challengeTx, serverAccountID, network, homeDomain string, threshold Threshold, signerSummary SignerSummary) (signersFound []string, err error) { signers := make([]string, 0, len(signerSummary)) for s := range signerSummary { signers = append(signers, s) } - signersFound, err = VerifyChallengeTxSigners(challengeTx, serverAccountID, network, signers...) + signersFound, err = VerifyChallengeTxSigners(challengeTx, serverAccountID, network, homeDomain, signers...) if err != nil { return nil, err } @@ -967,9 +980,9 @@ func VerifyChallengeTxThreshold(challengeTx, serverAccountID, network string, th // - No client signatures are found on the transaction. // - One or more signatures in the transaction are not identifiable as the // server account or one of the signers provided in the arguments. -func VerifyChallengeTxSigners(challengeTx, serverAccountID, network string, signers ...string) ([]string, error) { +func VerifyChallengeTxSigners(challengeTx, serverAccountID, network, homeDomain string, signers ...string) ([]string, error) { // Read the transaction which validates its structure. - tx, _, err := ReadChallengeTx(challengeTx, serverAccountID, network) + tx, _, err := ReadChallengeTx(challengeTx, serverAccountID, network, homeDomain) if err != nil { return nil, err } diff --git a/txnbuild/transaction_challenge_example_test.go b/txnbuild/transaction_challenge_example_test.go index ca637e165b..1438d09b72 100644 --- a/txnbuild/transaction_challenge_example_test.go +++ b/txnbuild/transaction_challenge_example_test.go @@ -52,7 +52,7 @@ func ExampleVerifyChallengeTxThreshold() { // Client reads and signs challenge transaction var signedChallengeTx string { - tx, txClientAccountID, err := txnbuild.ReadChallengeTx(challengeTx, serverAccount.Address(), network.TestNetworkPassphrase) + tx, txClientAccountID, err := txnbuild.ReadChallengeTx(challengeTx, serverAccount.Address(), network.TestNetworkPassphrase, "test") if err != nil { fmt.Println("Error:", err) return @@ -75,7 +75,7 @@ func ExampleVerifyChallengeTxThreshold() { // Server verifies signed challenge transaction { - _, txClientAccountID, err := txnbuild.ReadChallengeTx(challengeTx, serverAccount.Address(), network.TestNetworkPassphrase) + _, txClientAccountID, err := txnbuild.ReadChallengeTx(challengeTx, serverAccount.Address(), network.TestNetworkPassphrase, "test") if err != nil { fmt.Println("Error:", err) return @@ -102,7 +102,7 @@ func ExampleVerifyChallengeTxThreshold() { threshold := txnbuild.Threshold(horizonClientAccount.Thresholds.MedThreshold) // Server verifies threshold is met - signers, err := txnbuild.VerifyChallengeTxThreshold(signedChallengeTx, serverAccount.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signers, err := txnbuild.VerifyChallengeTxThreshold(signedChallengeTx, serverAccount.Address(), network.TestNetworkPassphrase, "test", threshold, signerSummary) if err != nil { fmt.Println("Error:", err) return diff --git a/txnbuild/transaction_test.go b/txnbuild/transaction_test.go index 97ae961dac..f8ed12a025 100644 --- a/txnbuild/transaction_test.go +++ b/txnbuild/transaction_test.go @@ -1528,7 +1528,7 @@ func TestReadChallengeTx_validSignedByServerAndClient(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, clientKP.Address(), readClientAccountID) assert.NoError(t, err) @@ -1559,7 +1559,7 @@ func TestReadChallengeTx_validSignedByServer(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, clientKP.Address(), readClientAccountID) assert.NoError(t, err) @@ -1588,7 +1588,7 @@ func TestReadChallengeTx_invalidNotSignedByServer(t *testing.T) { tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, clientKP.Address(), readClientAccountID) assert.EqualError(t, err, "transaction not signed by "+serverKP.Address()) @@ -1620,7 +1620,7 @@ func TestReadChallengeTx_invalidCorrupted(t *testing.T) { tx64, err := tx.Base64() require.NoError(t, err) tx64 = strings.ReplaceAll(tx64, "A", "B") - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Nil(t, readTx) assert.Equal(t, "", readClientAccountID) assert.EqualError( @@ -1656,7 +1656,7 @@ func TestReadChallengeTx_invalidServerAccountIDMismatch(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, "", readClientAccountID) assert.EqualError(t, err, "transaction source account is not equal to server's account") @@ -1687,7 +1687,7 @@ func TestReadChallengeTx_invalidSeqNoNotZero(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, "", readClientAccountID) assert.EqualError(t, err, "transaction sequence number must be 0") @@ -1718,7 +1718,7 @@ func TestReadChallengeTx_invalidTimeboundsInfinite(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, "", readClientAccountID) assert.EqualError(t, err, "transaction requires non-infinite timebounds") @@ -1749,7 +1749,7 @@ func TestReadChallengeTx_invalidTimeboundsOutsideRange(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, "", readClientAccountID) assert.Error(t, err) @@ -1781,7 +1781,7 @@ func TestReadChallengeTx_invalidTooManyOperations(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - _, _, err = ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + _, _, err = ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.EqualError(t, err, "transaction requires a single manage_data operation") } @@ -1809,7 +1809,7 @@ func TestReadChallengeTx_invalidOperationWrongType(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, "", readClientAccountID) assert.EqualError(t, err, "operation type should be manage_data") @@ -1837,7 +1837,7 @@ func TestReadChallengeTx_invalidOperationNoSourceAccount(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - _, _, err = ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + _, _, err = ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.EqualError(t, err, "operation should have a source account") } @@ -1866,7 +1866,7 @@ func TestReadChallengeTx_invalidDataValueWrongEncodedLength(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, clientKP.Address(), readClientAccountID) assert.EqualError(t, err, "random nonce encoded as base64 should be 64 bytes long") @@ -1897,7 +1897,7 @@ func TestReadChallengeTx_invalidDataValueCorruptBase64(t *testing.T) { assert.NoError(t, err) tx64, err := tx.Base64() require.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, clientKP.Address(), readClientAccountID) assert.EqualError(t, err, "failed to decode random nonce provided in manage_data operation: illegal base64 data at input byte 37") @@ -1929,7 +1929,7 @@ func TestReadChallengeTx_invalidDataValueWrongByteLength(t *testing.T) { tx64, err := tx.Base64() assert.NoError(t, err) - readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase) + readTx, readClientAccountID, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.Equal(t, tx, readTx) assert.Equal(t, clientKP.Address(), readClientAccountID) assert.EqualError(t, err, "random nonce before encoding as base64 should be 48 bytes long") @@ -1982,6 +1982,7 @@ func TestReadChallengeTx_acceptsV0AndV1Transactions(t *testing.T) { challenge, kp0.Address(), network.TestNetworkPassphrase, + "SDF", ) assert.NoError(t, err) @@ -2024,6 +2025,7 @@ func TestReadChallengeTx_forbidsFeeBumpTransactions(t *testing.T) { challenge, kp0.Address(), network.TestNetworkPassphrase, + "SDF", ) assert.EqualError(t, err, "challenge cannot be a fee bump transaction") } @@ -2057,6 +2059,7 @@ func TestReadChallengeTx_forbidsMuxedAccounts(t *testing.T) { challenge, kp0.Address(), network.TestNetworkPassphrase, + "SDF", ) errorMessage := "only valid Ed25519 accounts are allowed in challenge transactions" assert.Contains(t, err.Error(), errorMessage) @@ -2090,7 +2093,7 @@ func TestVerifyChallengeTxThreshold_invalidServer(t *testing.T) { signerSummary := SignerSummary{ clientKP.Address(): 1, } - signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.Empty(t, signersFound) assert.EqualError(t, err, "transaction not signed by "+serverKP.Address()) } @@ -2126,7 +2129,7 @@ func TestVerifyChallengeTxThreshold_validServerAndClientKeyMeetingThreshold(t *t clientKP.Address(), } - signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.ElementsMatch(t, wantSigners, signersFound) assert.NoError(t, err) } @@ -2165,7 +2168,7 @@ func TestVerifyChallengeTxThreshold_validServerAndMultipleClientKeyMeetingThresh clientKP2.Address(), } - signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.ElementsMatch(t, wantSigners, signersFound) assert.NoError(t, err) } @@ -2206,7 +2209,7 @@ func TestVerifyChallengeTxThreshold_validServerAndMultipleClientKeyMeetingThresh ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.ElementsMatch(t, wantSigners, signersFound) assert.NoError(t, err) } @@ -2253,7 +2256,7 @@ func TestVerifyChallengeTxThreshold_validServerAndMultipleClientKeyMeetingThresh ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.ElementsMatch(t, wantSigners, signersFound) assert.NoError(t, err) } @@ -2290,7 +2293,7 @@ func TestVerifyChallengeTxThreshold_invalidServerAndMultipleClientKeyNotMeetingT ) assert.NoError(t, err) - _, err = VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + _, err = VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.EqualError(t, err, "signers with weight 3 do not meet threshold 10") } @@ -2325,7 +2328,7 @@ func TestVerifyChallengeTxThreshold_invalidClientKeyUnrecognized(t *testing.T) { ) assert.NoError(t, err) - _, err = VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + _, err = VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.EqualError(t, err, "transaction has unrecognized signatures") } @@ -2357,7 +2360,7 @@ func TestVerifyChallengeTxThreshold_invalidNoSigners(t *testing.T) { ) assert.NoError(t, err) - _, err = VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + _, err = VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.EqualError(t, err, "no verifiable signers provided, at least one G... address must be provided") } @@ -2395,7 +2398,7 @@ func TestVerifyChallengeTxThreshold_weightsAddToMoreThan8Bits(t *testing.T) { clientKP2.Address(), } - signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, threshold, signerSummary) + signersFound, err := VerifyChallengeTxThreshold(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", threshold, signerSummary) assert.ElementsMatch(t, wantSigners, signersFound) assert.NoError(t, err) } @@ -2423,7 +2426,7 @@ func TestVerifyChallengeTxSigners_invalidServer(t *testing.T) { ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address()) assert.Empty(t, signersFound) assert.EqualError(t, err, "transaction not signed by "+serverKP.Address()) } @@ -2451,7 +2454,7 @@ func TestVerifyChallengeTxSigners_validServerAndClientMasterKey(t *testing.T) { ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address()) assert.Equal(t, []string{clientKP.Address()}, signersFound) assert.NoError(t, err) } @@ -2479,7 +2482,7 @@ func TestVerifyChallengeTxSigners_invalidServerAndNoClient(t *testing.T) { ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address()) assert.Empty(t, signersFound) assert.EqualError(t, err, "transaction not signed by "+clientKP.Address()) } @@ -2508,7 +2511,7 @@ func TestVerifyChallengeTxSigners_invalidServerAndUnrecognizedClient(t *testing. ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address()) assert.Empty(t, signersFound) assert.EqualError(t, err, "transaction not signed by "+clientKP.Address()) } @@ -2537,7 +2540,7 @@ func TestVerifyChallengeTxSigners_validServerAndMultipleClientSigners(t *testing ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address(), clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address(), clientKP2.Address()) assert.Equal(t, []string{clientKP.Address(), clientKP2.Address()}, signersFound) assert.NoError(t, err) } @@ -2566,7 +2569,7 @@ func TestVerifyChallengeTxSigners_validServerAndMultipleClientSignersReverseOrde ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address(), clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address(), clientKP2.Address()) assert.Equal(t, []string{clientKP.Address(), clientKP2.Address()}, signersFound) assert.NoError(t, err) } @@ -2595,7 +2598,7 @@ func TestVerifyChallengeTxSigners_validServerAndClientSignersNotMasterKey(t *tes ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP2.Address()) assert.Equal(t, []string{clientKP2.Address()}, signersFound) assert.NoError(t, err) } @@ -2624,7 +2627,7 @@ func TestVerifyChallengeTxSigners_validServerAndClientSignersIgnoresServerSigner ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, serverKP.Address(), clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", serverKP.Address(), clientKP2.Address()) assert.Equal(t, []string{clientKP2.Address()}, signersFound) assert.NoError(t, err) } @@ -2653,7 +2656,7 @@ func TestVerifyChallengeTxSigners_invalidServerNoClientSignersIgnoresServerSigne ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, serverKP.Address(), clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", serverKP.Address(), clientKP2.Address()) assert.Empty(t, signersFound) assert.EqualError(t, err, "transaction not signed by "+clientKP2.Address()) } @@ -2682,7 +2685,7 @@ func TestVerifyChallengeTxSigners_validServerAndClientSignersIgnoresDuplicateSig ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP2.Address(), clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP2.Address(), clientKP2.Address()) assert.Equal(t, []string{clientKP2.Address()}, signersFound) assert.NoError(t, err) } @@ -2714,7 +2717,7 @@ func TestVerifyChallengeTxSigners_validIgnorePreauthTxHashAndXHash(t *testing.T) ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP2.Address(), preauthTxHash, xHash, unknownSignerType) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP2.Address(), preauthTxHash, xHash, unknownSignerType) assert.Equal(t, []string{clientKP2.Address()}, signersFound) assert.NoError(t, err) } @@ -2743,7 +2746,7 @@ func TestVerifyChallengeTxSigners_invalidServerAndClientSignersIgnoresDuplicateS ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP.Address(), clientKP.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP.Address(), clientKP.Address()) assert.Empty(t, signersFound) assert.EqualError(t, err, "transaction not signed by "+clientKP.Address()) } @@ -2772,7 +2775,7 @@ func TestVerifyChallengeTxSigners_invalidServerAndClientSignersFailsDuplicateSig ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP2.Address()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP2.Address()) assert.Equal(t, []string{clientKP2.Address()}, signersFound) assert.EqualError(t, err, "transaction has unrecognized signatures") } @@ -2801,7 +2804,7 @@ func TestVerifyChallengeTxSigners_invalidServerAndClientSignersFailsSignerSeed(t ) assert.NoError(t, err) - signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, clientKP2.Seed()) + signersFound, err := VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver", clientKP2.Seed()) assert.Empty(t, signersFound) assert.EqualError(t, err, "no verifiable signers provided, at least one G... address must be provided") } @@ -2829,7 +2832,7 @@ func TestVerifyChallengeTxSigners_invalidNoSigners(t *testing.T) { ) assert.NoError(t, err) - _, err = VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase) + _, err = VerifyChallengeTxSigners(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testserver") assert.EqualError(t, err, "no verifiable signers provided, at least one G... address must be provided") }