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 180212c1ac
commit 58a2c4d9a0
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),