Provide base64 keys in addition to hex encoded. (#1734)

* Provide base64 keys in addition to hex encoded.

Accept these at unseal/rekey time.

Also fix a bug where backup would not be honored when doing a rekey with
no operation currently ongoing.
This commit is contained in:
Jeff Mitchell 2016-08-15 16:01:15 -04:00 committed by GitHub
parent 159255b5a6
commit 62c69f8e19
15 changed files with 236 additions and 117 deletions

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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))

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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))
}
}
}

View File

@ -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)
}

View File

@ -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,
))
}

View File

@ -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

View File

@ -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 {

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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,
},
}