diff --git a/api/sys_init.go b/api/sys_init.go index 37c2bcc8c..d307f732b 100644 --- a/api/sys_init.go +++ b/api/sys_init.go @@ -45,7 +45,9 @@ type InitStatusResponse struct { } type InitResponse struct { - Keys []string `json:"keys"` - RecoveryKeys []string `json:"recovery_keys"` - RootToken string `json:"root_token"` + Keys []string `json:"keys"` + KeysB64 []string `json:"keys_base64"` + RecoveryKeys []string `json:"recovery_keys"` + RecoveryKeysB64 []string `json:"recovery_keys_base64"` + RootToken string `json:"root_token"` } diff --git a/api/sys_rekey.go b/api/sys_rekey.go index 4fbfbb9fc..e6d039e27 100644 --- a/api/sys_rekey.go +++ b/api/sys_rekey.go @@ -190,11 +190,13 @@ type RekeyUpdateResponse struct { Nonce string Complete bool Keys []string + KeysB64 []string `json:"keys_base64"` PGPFingerprints []string `json:"pgp_fingerprints"` Backup bool } type RekeyRetrieveResponse struct { - Nonce string - Keys map[string][]string + Nonce string + Keys map[string][]string + KeysB64 map[string][]string `json:"keys_base64"` } diff --git a/command/init.go b/command/init.go index 918e0587d..179cfc181 100644 --- a/command/init.go +++ b/command/init.go @@ -192,10 +192,20 @@ func (c *InitCommand) runInit(check bool, initRequest *api.InitRequest) int { } for i, key := range resp.Keys { - c.Ui.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, key)) + if resp.KeysB64 != nil && len(resp.KeysB64) == len(resp.Keys) { + c.Ui.Output(fmt.Sprintf("Unseal Key %d (hex) : %s", i+1, key)) + c.Ui.Output(fmt.Sprintf("Unseal Key %d (base64): %s", i+1, resp.KeysB64[i])) + } else { + c.Ui.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, key)) + } } for i, key := range resp.RecoveryKeys { - c.Ui.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, key)) + if resp.RecoveryKeysB64 != nil && len(resp.RecoveryKeysB64) == len(resp.RecoveryKeys) { + c.Ui.Output(fmt.Sprintf("Recovery Key %d (hex) : %s", i+1, key)) + c.Ui.Output(fmt.Sprintf("Recovery Key %d (base64): %s", i+1, resp.RecoveryKeysB64[i])) + } else { + c.Ui.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, key)) + } } c.Ui.Output(fmt.Sprintf("Initial Root Token: %s", resp.RootToken)) diff --git a/command/init_test.go b/command/init_test.go index 89f9663e4..7c70a3902 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -244,5 +244,5 @@ func TestInit_PGP(t *testing.T) { rootToken := matches[0][1] - parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), rootToken, false, nil, core) + parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), rootToken, false, nil, nil, core) } diff --git a/command/pgp_test.go b/command/pgp_test.go index 3e08b50a6..d67997c42 100644 --- a/command/pgp_test.go +++ b/command/pgp_test.go @@ -66,7 +66,9 @@ func parseDecryptAndTestUnsealKeys(t *testing.T, input, rootToken string, fingerprints bool, backupKeys map[string][]string, + backupKeysB64 map[string][]string, core *vault.Core) { + decoder := base64.StdEncoding priv1Bytes, err := decoder.DecodeString(pgpkeys.TestPrivKey1) if err != nil { @@ -87,89 +89,106 @@ func parseDecryptAndTestUnsealKeys(t *testing.T, priv3Bytes, } - var re *regexp.Regexp - if fingerprints { - re, err = regexp.Compile("\\s*Key\\s+\\d+\\s+fingerprint:\\s+([0-9a-fA-F]+);\\s+value:\\s+(.*)") - } else { - re, err = regexp.Compile("\\s*Key\\s+\\d+:\\s+(.*)") - } - if err != nil { - t.Fatalf("Error compiling regex: %s", err) - } - matches := re.FindAllStringSubmatch(input, -1) - if len(matches) != 4 { - t.Fatalf("Unexpected number of keys returned, got %d, matches was \n\n%#v\n\n, input was \n\n%s\n\n", len(matches), matches, input) - } - - encodedKeys := []string{} - matchedFingerprints := []string{} - for _, tuple := range matches { + testFunc := func(b64 bool, bkeys map[string][]string) { + var re *regexp.Regexp if fingerprints { - if len(tuple) != 3 { - t.Fatalf("Key not found: %#v", tuple) + if b64 { + re, err = regexp.Compile("\\s*Key\\s+\\d+\\s+fingerprint:\\s+([0-9a-fA-F]+);\\s+value\\s+\\(base64\\):\\s+(.*)") + } else { + re, err = regexp.Compile("\\s*Key\\s+\\d+\\s+fingerprint:\\s+([0-9a-fA-F]+);\\s+value\\s+\\(hex\\)\\s+:\\s+(.*)") } - matchedFingerprints = append(matchedFingerprints, tuple[1]) - encodedKeys = append(encodedKeys, tuple[2]) } else { - if len(tuple) != 2 { - t.Fatalf("Key not found: %#v", tuple) + if b64 { + re, err = regexp.Compile("\\s*Key\\s+\\d+\\s\\(base64\\):\\s+(.*)") + } else { + re, err = regexp.Compile("\\s*Key\\s+\\d+\\s\\(hex\\)\\s+:\\s+(.*)") + } + } + if err != nil { + t.Fatalf("Error compiling regex: %s", err) + } + matches := re.FindAllStringSubmatch(input, -1) + if len(matches) != 4 { + t.Fatalf("Unexpected number of keys returned, got %d, matches was \n\n%#v\n\n, input was \n\n%s\n\n", len(matches), matches, input) + } + + encodedKeys := []string{} + matchedFingerprints := []string{} + for _, tuple := range matches { + if fingerprints { + if len(tuple) != 3 { + t.Fatalf("Key not found: %#v", tuple) + } + matchedFingerprints = append(matchedFingerprints, tuple[1]) + encodedKeys = append(encodedKeys, tuple[2]) + } else { + if len(tuple) != 2 { + t.Fatalf("Key not found: %#v", tuple) + } + encodedKeys = append(encodedKeys, tuple[1]) + } + } + + if bkeys != nil && len(matchedFingerprints) != 0 { + testMap := map[string][]string{} + for i, v := range matchedFingerprints { + testMap[v] = append(testMap[v], encodedKeys[i]) + sort.Strings(testMap[v]) + } + if !reflect.DeepEqual(testMap, bkeys) { + t.Fatalf("test map and backup map do not match, test map is\n%#v\nbackup map is\n%#v", testMap, bkeys) + } + } + + unsealKeys := []string{} + ptBuf := bytes.NewBuffer(nil) + for i, privKeyBytes := range privBytes { + if i > 2 { + break + } + ptBuf.Reset() + entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(privKeyBytes))) + if err != nil { + t.Fatalf("Error parsing private key %d: %s", i, err) + } + var keyBytes []byte + if b64 { + keyBytes, err = base64.StdEncoding.DecodeString(encodedKeys[i]) + } else { + keyBytes, err = hex.DecodeString(encodedKeys[i]) + } + if err != nil { + t.Fatalf("Error decoding key %d: %s", i, err) + } + entityList := &openpgp.EntityList{entity} + md, err := openpgp.ReadMessage(bytes.NewBuffer(keyBytes), entityList, nil, nil) + if err != nil { + t.Fatalf("Error decrypting with key %d (%s): %s", i, encodedKeys[i], err) + } + ptBuf.ReadFrom(md.UnverifiedBody) + unsealKeys = append(unsealKeys, ptBuf.String()) + } + + err = core.Seal(rootToken) + if err != nil { + t.Fatalf("Error sealing vault with provided root token: %s", err) + } + + for i, unsealKey := range unsealKeys { + unsealBytes, err := hex.DecodeString(unsealKey) + if err != nil { + t.Fatalf("Error hex decoding unseal key %s: %s", unsealKey, err) + } + unsealed, err := core.Unseal(unsealBytes) + if err != nil { + t.Fatalf("Error using unseal key %s: %s", unsealKey, err) + } + if i >= 2 && !unsealed { + t.Fatalf("Error: Provided two unseal keys but core is not unsealed") } - encodedKeys = append(encodedKeys, tuple[1]) - } - } - - if backupKeys != nil && len(matchedFingerprints) != 0 { - testMap := map[string][]string{} - for i, v := range matchedFingerprints { - testMap[v] = append(testMap[v], encodedKeys[i]) - sort.Strings(testMap[v]) - } - if !reflect.DeepEqual(testMap, backupKeys) { - t.Fatalf("test map and backup map do not match, test map is\n%#v\nbackup map is\n%#v", testMap, backupKeys) - } - } - - unsealKeys := []string{} - ptBuf := bytes.NewBuffer(nil) - for i, privKeyBytes := range privBytes { - if i > 2 { - break - } - ptBuf.Reset() - entity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(privKeyBytes))) - if err != nil { - t.Fatalf("Error parsing private key %d: %s", i, err) - } - keyBytes, err := hex.DecodeString(encodedKeys[i]) - if err != nil { - t.Fatalf("Error hex-decoding key %d: %s", i, err) - } - entityList := &openpgp.EntityList{entity} - md, err := openpgp.ReadMessage(bytes.NewBuffer(keyBytes), entityList, nil, nil) - if err != nil { - t.Fatalf("Error decrypting with key %d (%s): %s", i, encodedKeys[i], err) - } - ptBuf.ReadFrom(md.UnverifiedBody) - unsealKeys = append(unsealKeys, ptBuf.String()) - } - - err = core.Seal(rootToken) - if err != nil { - t.Fatalf("Error sealing vault with provided root token: %s", err) - } - - for i, unsealKey := range unsealKeys { - unsealBytes, err := hex.DecodeString(unsealKey) - if err != nil { - t.Fatalf("Error hex decoding unseal key %s: %s", unsealKey, err) - } - unsealed, err := core.Unseal(unsealBytes) - if err != nil { - t.Fatalf("Error using unseal key %s: %s", unsealKey, err) - } - if i >= 2 && !unsealed { - t.Fatalf("Error: Provided two unseal keys but core is not unsealed") } } + testFunc(false, backupKeys) + testFunc(true, backupKeysB64) } diff --git a/command/rekey.go b/command/rekey.go index 3a64d2ac6..989744f9e 100644 --- a/command/rekey.go +++ b/command/rekey.go @@ -93,12 +93,14 @@ func (c *RekeyCommand) Run(args []string) int { SecretShares: shares, SecretThreshold: threshold, PGPKeys: pgpKeys, + Backup: backup, }) } else { rekeyStatus, err = client.Sys().RekeyInit(&api.RekeyInitRequest{ SecretShares: shares, SecretThreshold: threshold, PGPKeys: pgpKeys, + Backup: backup, }) } if err != nil { @@ -158,11 +160,25 @@ func (c *RekeyCommand) Run(args []string) int { // Space between the key prompt, if any, and the output c.Ui.Output("\n") // Provide the keys + var haveB64 bool + if result.KeysB64 != nil && len(result.KeysB64) == len(result.Keys) { + haveB64 = true + } for i, key := range result.Keys { if len(result.PGPFingerprints) > 0 { - c.Ui.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, result.PGPFingerprints[i], key)) + if haveB64 { + c.Ui.Output(fmt.Sprintf("Key %d fingerprint: %s; value (hex) : %s", i+1, result.PGPFingerprints[i], key)) + c.Ui.Output(fmt.Sprintf("Key %d fingerprint: %s; value (base64): %s", i+1, result.PGPFingerprints[i], result.KeysB64[i])) + } else { + c.Ui.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, result.PGPFingerprints[i], key)) + } } else { - c.Ui.Output(fmt.Sprintf("Key %d: %s", i+1, key)) + if haveB64 { + c.Ui.Output(fmt.Sprintf("Key %d (hex) : %s", i+1, key)) + c.Ui.Output(fmt.Sprintf("Key %d (base64): %s", i+1, result.KeysB64[i])) + } else { + c.Ui.Output(fmt.Sprintf("Key %d: %s", i+1, key)) + } } } diff --git a/command/rekey_test.go b/command/rekey_test.go index 0da22fd2b..a3402f782 100644 --- a/command/rekey_test.go +++ b/command/rekey_test.go @@ -227,7 +227,8 @@ func TestRekey_init_pgp(t *testing.T) { } type backupStruct struct { - Keys map[string][]string + Keys map[string][]string + KeysB64 map[string][]string } backupVals := &backupStruct{} @@ -247,6 +248,7 @@ func TestRekey_init_pgp(t *testing.T) { } backupVals.Keys = resp.Data["keys"].(map[string][]string) + backupVals.KeysB64 = resp.Data["keys_base64"].(map[string][]string) // Now delete and try again; the values should be inaccessible req = logical.TestRequest(t, logical.DeleteOperation, "rekey/backup") @@ -269,7 +271,8 @@ func TestRekey_init_pgp(t *testing.T) { // Sort, because it'll be tested with DeepEqual later for k, _ := range backupVals.Keys { sort.Strings(backupVals.Keys[k]) + sort.Strings(backupVals.KeysB64[k]) } - parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), token, true, backupVals.Keys, core) + parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), token, true, backupVals.Keys, backupVals.KeysB64, core) } diff --git a/command/server.go b/command/server.go index 0531b4f39..855ae8e30 100644 --- a/command/server.go +++ b/command/server.go @@ -1,6 +1,7 @@ package command import ( + "encoding/base64" "encoding/hex" "fmt" "log" @@ -489,8 +490,9 @@ func (c *ServerCommand) Run(args []string) int { " "+export+" VAULT_ADDR="+quote+"http://"+config.Listeners[0].Config["address"]+quote+"\n\n"+ "The unseal key and root token are reproduced below in case you\n"+ "want to seal/unseal the Vault or play with authentication.\n\n"+ - "Unseal Key: %s\nRoot Token: %s\n", + "Unseal Key (hex) : %s\nUnseal Key (base64): %s\nRoot Token: %s\n", hex.EncodeToString(init.SecretShares[0]), + base64.StdEncoding.EncodeToString(init.SecretShares[0]), init.RootToken, )) } diff --git a/http/sys_generate_root.go b/http/sys_generate_root.go index caaf095f7..ef97feebd 100644 --- a/http/sys_generate_root.go +++ b/http/sys_generate_root.go @@ -1,6 +1,7 @@ package http import ( + "encoding/base64" "encoding/hex" "errors" "fmt" @@ -123,13 +124,20 @@ func handleSysGenerateRootUpdate(core *vault.Core) http.Handler { return } - // Decode the key, which is hex encoded + // Decode the key, which is base64 or hex encoded + min, max := core.BarrierKeyLength() key, err := hex.DecodeString(req.Key) - if err != nil { - respondError( - w, http.StatusBadRequest, - errors.New("'key' must be a valid hex-string")) - return + // We check min and max here to ensure that a string that is base64 + // encoded but also valid hex will not be valid and we instead base64 + // decode it + if err != nil || len(key) < min || len(key) > max { + key, err = base64.StdEncoding.DecodeString(req.Key) + if err != nil { + respondError( + w, http.StatusBadRequest, + errors.New("'key' must be a valid hex or base64 string")) + return + } } // Use the key to make progress on root generation diff --git a/http/sys_init.go b/http/sys_init.go index 6d99719bb..78faabfae 100644 --- a/http/sys_init.go +++ b/http/sys_init.go @@ -1,6 +1,7 @@ package http import ( + "encoding/base64" "encoding/hex" "fmt" "net/http" @@ -93,19 +94,24 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) // Encode the keys keys := make([]string, 0, len(result.SecretShares)) + keysB64 := make([]string, 0, len(result.SecretShares)) for _, k := range result.SecretShares { keys = append(keys, hex.EncodeToString(k)) + keysB64 = append(keysB64, base64.StdEncoding.EncodeToString(k)) } resp := &InitResponse{ Keys: keys, + KeysB64: keysB64, RootToken: result.RootToken, } if len(result.RecoveryShares) > 0 { resp.RecoveryKeys = make([]string, 0, len(result.RecoveryShares)) + resp.RecoveryKeysB64 = make([]string, 0, len(result.RecoveryShares)) for _, k := range result.RecoveryShares { resp.RecoveryKeys = append(resp.RecoveryKeys, hex.EncodeToString(k)) + resp.RecoveryKeysB64 = append(resp.RecoveryKeysB64, base64.StdEncoding.EncodeToString(k)) } } @@ -125,9 +131,11 @@ type InitRequest struct { } type InitResponse struct { - Keys []string `json:"keys"` - RecoveryKeys []string `json:"recovery_keys,omitempty"` - RootToken string `json:"root_token"` + Keys []string `json:"keys"` + KeysB64 []string `json:"keys_base64"` + RecoveryKeys []string `json:"recovery_keys,omitempty"` + RecoveryKeysB64 []string `json:"recovery_keys_base64,omitempty"` + RootToken string `json:"root_token"` } type InitStatusResponse struct { diff --git a/http/sys_rekey.go b/http/sys_rekey.go index d813e1b5d..a49ed4079 100644 --- a/http/sys_rekey.go +++ b/http/sys_rekey.go @@ -1,6 +1,7 @@ package http import ( + "encoding/base64" "encoding/hex" "errors" "fmt" @@ -146,13 +147,20 @@ func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler { return } - // Decode the key, which is hex encoded + // Decode the key, which is base64 or hex encoded + min, max := core.BarrierKeyLength() key, err := hex.DecodeString(req.Key) - if err != nil { - respondError( - w, http.StatusBadRequest, - errors.New("'key' must be a valid hex-string")) - return + // We check min and max here to ensure that a string that is base64 + // encoded but also valid hex will not be valid and we instead base64 + // decode it + if err != nil || len(key) < min || len(key) > max { + key, err = base64.StdEncoding.DecodeString(req.Key) + if err != nil { + respondError( + w, http.StatusBadRequest, + errors.New("'key' must be a valid hex or base64 string")) + return + } } // Use the key to make progress on rekey @@ -167,16 +175,18 @@ func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler { if result != nil { resp.Complete = true resp.Nonce = req.Nonce + resp.Backup = result.Backup + resp.PGPFingerprints = result.PGPFingerprints // Encode the keys keys := make([]string, 0, len(result.SecretShares)) + keysB64 := make([]string, 0, len(result.SecretShares)) for _, k := range result.SecretShares { keys = append(keys, hex.EncodeToString(k)) + keysB64 = append(keysB64, base64.StdEncoding.EncodeToString(k)) } resp.Keys = keys - - resp.Backup = result.Backup - resp.PGPFingerprints = result.PGPFingerprints + resp.KeysB64 = keysB64 } respondOk(w, resp) }) @@ -210,6 +220,7 @@ type RekeyUpdateResponse struct { Nonce string `json:"nonce"` Complete bool `json:"complete"` Keys []string `json:"keys"` + KeysB64 []string `json:"keys_base64"` PGPFingerprints []string `json:"pgp_fingerprints"` Backup bool `json:"backup"` } diff --git a/http/sys_rekey_test.go b/http/sys_rekey_test.go index ef5fb7343..4811bd241 100644 --- a/http/sys_rekey_test.go +++ b/http/sys_rekey_test.go @@ -180,8 +180,13 @@ func TestSysRekey_Update(t *testing.T) { if len(keys) != 5 { t.Fatalf("bad: %#v", keys) } + keysB64 := actual["keys_base64"].([]interface{}) + if len(keysB64) != 5 { + t.Fatalf("bad: %#v", keysB64) + } delete(actual, "keys") + delete(actual, "keys_base64") if !reflect.DeepEqual(actual, expected) { t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } diff --git a/http/sys_seal.go b/http/sys_seal.go index 9a75f3844..066c88c27 100644 --- a/http/sys_seal.go +++ b/http/sys_seal.go @@ -1,6 +1,7 @@ package http import ( + "encoding/base64" "encoding/hex" "errors" "fmt" @@ -97,13 +98,20 @@ func handleSysUnseal(core *vault.Core) http.Handler { } core.ResetUnsealProcess() } else { - // Decode the key, which is hex encoded + // Decode the key, which is base64 or hex encoded + min, max := core.BarrierKeyLength() key, err := hex.DecodeString(req.Key) - if err != nil { - respondError( - w, http.StatusBadRequest, - errors.New("'key' must be a valid hex-string")) - return + // We check min and max here to ensure that a string that is base64 + // encoded but also valid hex will not be valid and we instead base64 + // decode it + if err != nil || len(key) < min || len(key) > max { + key, err = base64.StdEncoding.DecodeString(req.Key) + if err != nil { + respondError( + w, http.StatusBadRequest, + errors.New("'key' must be a valid hex or base64 string")) + return + } } // Attempt the unseal diff --git a/vault/core.go b/vault/core.go index 244046697..b41f129f6 100644 --- a/vault/core.go +++ b/vault/core.go @@ -1490,3 +1490,9 @@ func (c *Core) SealAccess() *SealAccess { func (c *Core) Logger() *log.Logger { return c.logger } + +func (c *Core) BarrierKeyLength() (min, max int) { + min, max = c.barrier.KeyLength() + max += shamir.ShareOverhead + return +} diff --git a/vault/logical_system.go b/vault/logical_system.go index 228fe175d..5638ce2b0 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -1,6 +1,8 @@ package vault import ( + "encoding/base64" + "encoding/hex" "fmt" "strings" "sync" @@ -607,11 +609,28 @@ func (b *SystemBackend) handleRekeyRetrieve( return logical.ErrorResponse("no backed-up keys found"), nil } + keysB64 := map[string][]string{} + for k, v := range backup.Keys { + for _, j := range v { + currB64Keys := keysB64[k] + if currB64Keys == nil { + currB64Keys = []string{} + } + key, err := hex.DecodeString(j) + if err != nil { + return nil, fmt.Errorf("error decoding hex-encoded backup key: %v", err) + } + currB64Keys = append(currB64Keys, base64.StdEncoding.EncodeToString(key)) + keysB64[k] = currB64Keys + } + } + // Format the status resp := &logical.Response{ Data: map[string]interface{}{ - "nonce": backup.Nonce, - "keys": backup.Keys, + "nonce": backup.Nonce, + "keys": backup.Keys, + "keys_base64": keysB64, }, }