Return status for rekey/root generation at init time. This mitigates a

(very unlikely) potential timing attack between init-ing and fetching
status.

Fixes #1054
This commit is contained in:
Jeff Mitchell 2016-02-12 14:24:36 -05:00
parent 3d3ad051a8
commit 5f5542cb91
8 changed files with 103 additions and 58 deletions

View File

@ -13,7 +13,7 @@ func (c *Sys) GenerateRootStatus() (*GenerateRootStatusResponse, error) {
return &result, err
}
func (c *Sys) GenerateRootInit(otp, pgpKey string) error {
func (c *Sys) GenerateRootInit(otp, pgpKey string) (*GenerateRootStatusResponse, error) {
body := map[string]interface{}{
"otp": otp,
"pgp_key": pgpKey,
@ -21,14 +21,18 @@ func (c *Sys) GenerateRootInit(otp, pgpKey string) error {
r := c.c.NewRequest("PUT", "/v1/sys/generate-root/attempt")
if err := r.SetJSONBody(body); err != nil {
return err
return nil, err
}
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
if err != nil {
return nil, err
}
return err
defer resp.Body.Close()
var result GenerateRootStatusResponse
err = resp.DecodeJSON(&result)
return &result, err
}
func (c *Sys) GenerateRootCancel() error {

View File

@ -13,17 +13,21 @@ func (c *Sys) RekeyStatus() (*RekeyStatusResponse, error) {
return &result, err
}
func (c *Sys) RekeyInit(config *RekeyInitRequest) error {
func (c *Sys) RekeyInit(config *RekeyInitRequest) (*RekeyStatusResponse, error) {
r := c.c.NewRequest("PUT", "/v1/sys/rekey/init")
if err := r.SetJSONBody(config); err != nil {
return err
return nil, err
}
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
if err != nil {
return nil, err
}
return err
defer resp.Body.Close()
var result RekeyStatusResponse
err = resp.DecodeJSON(&result)
return &result, err
}
func (c *Sys) RekeyCancel() error {

View File

@ -140,16 +140,11 @@ func (c *GenerateRootCommand) Run(args []string) int {
// Start the root generation process if not started
if !rootGenerationStatus.Started {
err = client.Sys().GenerateRootInit(otp, pgpKey)
rootGenerationStatus, err = client.Sys().GenerateRootInit(otp, pgpKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 1
}
rootGenerationStatus, err = client.Sys().GenerateRootStatus()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
return 1
}
c.Nonce = rootGenerationStatus.Nonce
}
@ -229,14 +224,15 @@ func (c *GenerateRootCommand) decode(encodedVal, otp string) int {
// initGenerateRoot is used to start the generation process
func (c *GenerateRootCommand) initGenerateRoot(client *api.Client, otp string, pgpKey string) int {
// Start the rekey
err := client.Sys().GenerateRootInit(otp, pgpKey)
status, err := client.Sys().GenerateRootInit(otp, pgpKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 1
}
// Provide the current status
return c.rootGenerationStatus(client)
c.dumpStatus(status)
return 0
}
// cancelGenerateRoot is used to abort the generation process

View File

@ -78,7 +78,7 @@ func (c *RekeyCommand) Run(args []string) int {
// Start the rekey process if not started
if !rekeyStatus.Started {
err := client.Sys().RekeyInit(&api.RekeyInitRequest{
rekeyStatus, err = client.Sys().RekeyInit(&api.RekeyInitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
@ -87,11 +87,6 @@ func (c *RekeyCommand) Run(args []string) int {
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
return 1
}
rekeyStatus, err = client.Sys().RekeyStatus()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading rekey status: %s", err))
return 1
}
c.Nonce = rekeyStatus.Nonce
}
@ -182,7 +177,7 @@ func (c *RekeyCommand) initRekey(client *api.Client,
pgpKeys pgpkeys.PubKeyFilesFlag,
backup bool) int {
// Start the rekey
err := client.Sys().RekeyInit(&api.RekeyInitRequest{
status, err := client.Sys().RekeyInit(&api.RekeyInitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
@ -214,7 +209,7 @@ be deleted at a later time with 'vault rekey -delete'.
}
// Provide the current status
return c.rekeyStatus(client)
return c.dumpRekeyStatus(status)
}
// cancelRekey is used to abort the rekey process
@ -237,6 +232,10 @@ func (c *RekeyCommand) rekeyStatus(client *api.Client) int {
return 1
}
return c.dumpRekeyStatus(status)
}
func (c *RekeyCommand) dumpRekeyStatus(status *api.RekeyStatusResponse) int {
// Dump the status
statString := fmt.Sprintf(
"Nonce: %s\n"+

View File

@ -86,7 +86,8 @@ func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r
respondError(w, http.StatusBadRequest, err)
return
}
respondOk(w, nil)
handleSysGenerateRootAttemptGet(core, w, r)
}
func handleSysGenerateRootAttemptDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) {

View File

@ -56,9 +56,7 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"otp": otp,
})
testResponseStatus(t, resp, 204)
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt")
testResponseStatus(t, resp, 200)
var actual map[string]interface{}
expected := map[string]interface{}{
@ -75,6 +73,24 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
}
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt")
actual = map[string]interface{}{}
expected = map[string]interface{}{
"started": true,
"progress": float64(0),
"required": float64(1),
"complete": false,
"encoded_root_token": "",
"pgp_fingerprint": "",
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
expected["nonce"] = actual["nonce"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
}
}
func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
@ -86,7 +102,7 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"pgp_key": pgpkeys.TestPubKey1,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt")
@ -123,6 +139,23 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
"otp": otp,
})
var actual map[string]interface{}
expected := map[string]interface{}{
"started": true,
"progress": float64(0),
"required": float64(1),
"complete": false,
"encoded_root_token": "",
"pgp_fingerprint": "",
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
expected["nonce"] = actual["nonce"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
}
initialNonce := expected["nonce"].(string)
resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt")
testResponseStatus(t, resp, 204)
@ -131,8 +164,8 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
t.Fatalf("err: %s", err)
}
var actual map[string]interface{}
expected := map[string]interface{}{
actual = map[string]interface{}{}
expected = map[string]interface{}{
"started": false,
"progress": float64(0),
"required": float64(1),
@ -146,6 +179,10 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
}
if expected["nonce"].(string) == initialNonce {
t.Fatalf("Same nonce detected across two invocations")
}
}
func TestSysGenerateRoot_badKey(t *testing.T) {
@ -181,7 +218,7 @@ func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"otp": otp,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt")
testResponseStatus(t, resp, 204)
@ -190,7 +227,7 @@ func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
"pgp_key": pgpkeys.TestPubKey1,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
}
func TestSysGenerateRoot_Update_OTP(t *testing.T) {
@ -208,13 +245,6 @@ func TestSysGenerateRoot_Update_OTP(t *testing.T) {
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"otp": otp,
})
testResponseStatus(t, resp, 204)
// We need to get the nonce first before we update
resp, err = http.Get(addr + "/v1/sys/generate-root/attempt")
if err != nil {
t.Fatalf("err: %s", err)
}
var rootGenerationStatus map[string]interface{}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &rootGenerationStatus)
@ -287,7 +317,7 @@ func TestSysGenerateRoot_Update_PGP(t *testing.T) {
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"pgp_key": pgpkeys.TestPubKey1,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
// We need to get the nonce first before we update
resp, err := http.Get(addr + "/v1/sys/generate-root/attempt")

View File

@ -100,7 +100,8 @@ func handleSysRekeyInitPut(core *vault.Core, w http.ResponseWriter, r *http.Requ
respondError(w, http.StatusBadRequest, err)
return
}
respondOk(w, nil)
handleSysRekeyInitGet(core, w, r)
}
func handleSysRekeyInitDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) {

View File

@ -48,9 +48,7 @@ func TestSysRekeyInit_Setup(t *testing.T) {
"secret_shares": 5,
"secret_threshold": 3,
})
testResponseStatus(t, resp, 204)
resp = testHttpGet(t, token, addr+"/v1/sys/rekey/init")
testResponseStatus(t, resp, 200)
var actual map[string]interface{}
expected := map[string]interface{}{
@ -68,6 +66,25 @@ func TestSysRekeyInit_Setup(t *testing.T) {
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
}
resp = testHttpGet(t, token, addr+"/v1/sys/rekey/init")
actual = map[string]interface{}{}
expected = map[string]interface{}{
"started": true,
"t": float64(3),
"n": float64(5),
"progress": float64(0),
"required": float64(1),
"pgp_fingerprints": interface{}(nil),
"backup": false,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
expected["nonce"] = actual["nonce"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
}
}
func TestSysRekeyInit_Cancel(t *testing.T) {
@ -80,7 +97,7 @@ func TestSysRekeyInit_Cancel(t *testing.T) {
"secret_shares": 5,
"secret_threshold": 3,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
resp = testHttpDelete(t, token, addr+"/v1/sys/rekey/init")
testResponseStatus(t, resp, 204)
@ -130,13 +147,6 @@ func TestSysRekey_Update(t *testing.T) {
"secret_shares": 5,
"secret_threshold": 3,
})
testResponseStatus(t, resp, 204)
// We need to get the nonce first before we update
resp, err := http.Get(addr + "/v1/sys/rekey/init")
if err != nil {
t.Fatalf("err: %s", err)
}
var rekeyStatus map[string]interface{}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &rekeyStatus)
@ -177,7 +187,7 @@ func TestSysRekey_ReInitUpdate(t *testing.T) {
"secret_shares": 5,
"secret_threshold": 3,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
resp = testHttpDelete(t, token, addr+"/v1/sys/rekey/init")
testResponseStatus(t, resp, 204)
@ -186,7 +196,7 @@ func TestSysRekey_ReInitUpdate(t *testing.T) {
"secret_shares": 5,
"secret_threshold": 3,
})
testResponseStatus(t, resp, 204)
testResponseStatus(t, resp, 200)
resp = testHttpPut(t, token, addr+"/v1/sys/rekey/update", map[string]interface{}{
"key": hex.EncodeToString(master),