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:
parent
3d3ad051a8
commit
5f5542cb91
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"+
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue