Skip to content

Commit

Permalink
exp/services/recoverysigner: add support for configuring and using mu…
Browse files Browse the repository at this point in the history
…ltiple signing keys (#2627)

### What
Add support for configuring and using multiple signing keys where the
first signing key in the list is the default used for signing and the
others can be selected according to SEP-30 v0.3.0.

### Why
The reference implementation adheres to the API changes defined in
SEP-30 v0.3.0 that supported multiple signing keys per registered
account for the purpose of rotating signing keys, however the reference
implementation does not actually support multiple signing keys being
configured at one time.

### Note
Much of this code is possibly going to be rewritten for #2343 where
global keys will be supplemented with unique keys for each account that
get stored in the database.

### Known limitations
There is some code duplicity in this PR that I don't think is worth
optimizing on until unique keys are implemented as trying to abstract
commonalities may make @howardtw's job harder if the abstraction
doesn't quite align with the logic he inserts. I think we should revisit any
duplicate code once the unique key logic is added.
  • Loading branch information
leighmcculloch authored May 28, 2020
1 parent bd8a616 commit 000a7c5
Show file tree
Hide file tree
Showing 15 changed files with 711 additions and 297 deletions.
4 changes: 2 additions & 2 deletions exp/services/recoverysigner/cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ func (c *ServeCommand) Command() *cobra.Command {
},
{
Name: "signing-key",
Usage: "Stellar signing key used for signing transactions (will be deprecated with per-account keys in the future)",
Usage: "Stellar signing key(s) used for signing transactions comma separated (first key is preferred signer) (will be deprecated with per-account keys in the future)",
OptType: types.String,
ConfigKey: &opts.SigningKey,
ConfigKey: &opts.SigningKeys,
Required: true,
},
{
Expand Down
18 changes: 11 additions & 7 deletions exp/services/recoverysigner/internal/serve/account_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
)

type accountDeleteHandler struct {
Logger *supportlog.Entry
SigningAddress *keypair.FromAddress
AccountStore account.Store
Logger *supportlog.Entry
SigningAddresses []*keypair.FromAddress
AccountStore account.Store
}

type accountDeleteRequest struct {
Expand Down Expand Up @@ -53,12 +53,16 @@ func (h accountDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
return
}

signers := []accountResponseSigner{}
for _, signingAddress := range h.SigningAddresses {
signers = append(signers, accountResponseSigner{
Key: signingAddress.Address(),
})
}
resp := accountResponse{
Address: acc.Address,
Signer: h.SigningAddress.Address(),
Signers: []accountResponseSigner{
{Key: h.SigningAddress.Address()},
},
Signer: h.SigningAddresses[0].Address(),
Signers: signers,
}

// Authorized if authenticated as the account.
Expand Down
79 changes: 58 additions & 21 deletions exp/services/recoverysigner/internal/serve/account_delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ func TestAccountDelete_authenticatedNotAuthorized(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -76,9 +79,12 @@ func TestAccountDelete_notAuthenticated(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -119,9 +125,12 @@ func TestAccountDelete_notFound(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -168,9 +177,12 @@ func TestAccountDelete_authenticatedByIdentityAddress(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -206,6 +218,10 @@ func TestAccountDelete_authenticatedByIdentityAddress(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down Expand Up @@ -237,9 +253,12 @@ func TestAccountDelete_authenticatedByAccountAddress(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -274,6 +293,10 @@ func TestAccountDelete_authenticatedByAccountAddress(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down Expand Up @@ -305,9 +328,12 @@ func TestAccountDelete_authenticatedByPhoneNumber(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -343,6 +369,10 @@ func TestAccountDelete_authenticatedByPhoneNumber(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down Expand Up @@ -374,9 +404,12 @@ func TestAccountDelete_authenticatedByEmail(t *testing.T) {
},
})
h := accountDeleteHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -412,6 +445,10 @@ func TestAccountDelete_authenticatedByEmail(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down
18 changes: 11 additions & 7 deletions exp/services/recoverysigner/internal/serve/account_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
)

type accountGetHandler struct {
Logger *supportlog.Entry
SigningAddress *keypair.FromAddress
AccountStore account.Store
Logger *supportlog.Entry
SigningAddresses []*keypair.FromAddress
AccountStore account.Store
}

type accountGetRequest struct {
Expand Down Expand Up @@ -53,12 +53,16 @@ func (h accountGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

signers := []accountResponseSigner{}
for _, signingAddress := range h.SigningAddresses {
signers = append(signers, accountResponseSigner{
Key: signingAddress.Address(),
})
}
resp := accountResponse{
Address: acc.Address,
Signer: h.SigningAddress.Address(),
Signers: []accountResponseSigner{
{Key: h.SigningAddress.Address()},
},
Signer: h.SigningAddresses[0].Address(),
Signers: signers,
}

// Authorized if authenticated as the account.
Expand Down
79 changes: 58 additions & 21 deletions exp/services/recoverysigner/internal/serve/account_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ func TestAccountGet_authenticatedNotAuthorized(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -76,9 +79,12 @@ func TestAccountGet_notAuthenticated(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -119,9 +125,12 @@ func TestAccountGet_notFound(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -168,9 +177,12 @@ func TestAccountGet_authenticatedByIdentityAddress(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -206,6 +218,10 @@ func TestAccountGet_authenticatedByIdentityAddress(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down Expand Up @@ -233,9 +249,12 @@ func TestAccountGet_authenticatedByAccountAddress(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -270,6 +289,10 @@ func TestAccountGet_authenticatedByAccountAddress(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down Expand Up @@ -297,9 +320,12 @@ func TestAccountGet_authenticatedByPhoneNumber(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -335,6 +361,10 @@ func TestAccountGet_authenticatedByPhoneNumber(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down Expand Up @@ -362,9 +392,12 @@ func TestAccountGet_authenticatedByEmail(t *testing.T) {
},
})
h := accountGetHandler{
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddress: keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
Logger: supportlog.DefaultLogger,
AccountStore: s,
SigningAddresses: []*keypair.FromAddress{
keypair.MustParseAddress("GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE"),
keypair.MustParseAddress("GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS"),
},
}

ctx := context.Background()
Expand Down Expand Up @@ -400,6 +433,10 @@ func TestAccountGet_authenticatedByEmail(t *testing.T) {
{
"key": "GCAPXRXSU7P6D353YGXMP6ROJIC744HO5OZCIWTXZQK2X757YU5KCHUE",
"added_at": "0001-01-01T00:00:00Z"
},
{
"key": "GAPE22DOMALCH42VOR4S3HN6KIZZ643G7D3GNTYF4YOWWXP6UVRAF5JS",
"added_at": "0001-01-01T00:00:00Z"
}
]
}`
Expand Down
Loading

0 comments on commit 000a7c5

Please sign in to comment.