From 9f39413adb5cae9687ef2b6f36948f7368b315d7 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 24 Aug 2016 11:26:14 -0700 Subject: [PATCH] Properly restore delegations. This change addresses several points: 1. The integration tests didn't verify that delegations could be used for decryption following a restore. The integration tests now verify this. 2. There was no functionality for clearing persisted delegations if needed. The vault admin can now do this via the command line tool. 3. Restoring active delegations wasn't storing the key with the delegation. Keys are now serialised properly. 4. [Minor] The MSP package now reports the name of the offending user when it can't find a user name in the database. --- client/client.go | 17 ++++++ cmd/ro/main.go | 36 ++++++++---- core/core.go | 34 +++++++++++ core/core_test.go | 22 +++++-- cryptor/cryptor.go | 15 ++++- keycache/keycache.go | 38 +++++++++++++ msp/msp.go | 3 +- persist/file.go | 9 +++ persist/file_test.go | 98 +++++++++++++++++++++++++++++++ persist/null.go | 4 ++ persist/null_test.go | 6 +- persist/persist.go | 2 + redoctober.go | 37 ++++++------ redoctober_test.go | 133 ++++++++++++++++++++++++++++++++++++++----- 14 files changed, 405 insertions(+), 49 deletions(-) diff --git a/client/client.go b/client/client.go index 19564ee..414ea29 100644 --- a/client/client.go +++ b/client/client.go @@ -404,3 +404,20 @@ func (c *RemoteServer) Restore(req core.DelegateRequest) (*core.ResponseData, er return unmarshalResponseData(respBytes) } + +// ResetPersisted issues a persisted delegation reset request, +// clearing out any persisted delegations. This must be done by an +// admin user. +func (c *RemoteServer) ResetPersisted(req core.PurgeRequest) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("reset-persisted", reqBytes) + if err != nil { + return nil, err + } + + return unmarshalResponseData(respBytes) +} diff --git a/cmd/ro/main.go b/cmd/ro/main.go index 57ebbd8..1218382 100644 --- a/cmd/ro/main.go +++ b/cmd/ro/main.go @@ -35,17 +35,18 @@ type command struct { var roServer *client.RemoteServer var commandSet = map[string]command{ - "create": command{Run: runCreate, Desc: "create the disk vault and admin account"}, - "create-user": command{Run: runCreateUser, Desc: "create a user account"}, - "summary": command{Run: runSummary, Desc: "list the user and delegation summary"}, - "delegate": command{Run: runDelegate, Desc: "do decryption delegation"}, - "encrypt": command{Run: runEncrypt, Desc: "encrypt a file"}, - "decrypt": command{Run: runDecrypt, Desc: "decrypt a file"}, - "re-encrypt": command{Run: runReEncrypt, Desc: "re-encrypt a file"}, - "order": command{Run: runOrder, Desc: "place an order for delegations"}, - "owners": command{Run: runOwner, Desc: "show owners list"}, - "status": command{Run: runStatus, Desc: "show Red October persistent delegation state"}, - "restore": command{Run: runRestore, Desc: "perform a restore delegation"}, + "create": command{Run: runCreate, Desc: "create the disk vault and admin account"}, + "create-user": command{Run: runCreateUser, Desc: "create a user account"}, + "summary": command{Run: runSummary, Desc: "list the user and delegation summary"}, + "delegate": command{Run: runDelegate, Desc: "do decryption delegation"}, + "encrypt": command{Run: runEncrypt, Desc: "encrypt a file"}, + "decrypt": command{Run: runDecrypt, Desc: "decrypt a file"}, + "re-encrypt": command{Run: runReEncrypt, Desc: "re-encrypt a file"}, + "order": command{Run: runOrder, Desc: "place an order for delegations"}, + "owners": command{Run: runOwner, Desc: "show owners list"}, + "status": command{Run: runStatus, Desc: "show Red October persistent delegation state"}, + "restore": command{Run: runRestore, Desc: "perform a restore delegation"}, + "reset-persisted": command{Run: runResetPersisted, Desc: "reset the persisted delegations"}, } func registerFlags() { @@ -313,6 +314,19 @@ func runStatus() { fmt.Println(resp) } +func runResetPersisted() { + req := core.PurgeRequest{ + Name: user, + Password: pswd, + } + + resp, err := roServer.ResetPersisted(req) + processError(err) + + fmt.Println(resp.Status) + fmt.Println(resp) +} + func main() { flag.Usage = func() { fmt.Println("Usage: ro [options] subcommand") diff --git a/core/core.go b/core/core.go index c0baa8d..ac23c63 100644 --- a/core/core.go +++ b/core/core.go @@ -995,3 +995,37 @@ func Restore(jsonIn []byte) (out []byte, err error) { return jsonResponse(out) } + +// ResetPersisted clears the persisted user data from the server. This +// request requires an admin. +func ResetPersisted(jsonIn []byte) (out []byte, err error) { + var req PurgeRequest + + defer func() { + if err != nil { + log.Printf("core.resetpersisted failed: user=%s %v", req.Name, err) + } else { + log.Printf("core.resetpersisted success: user=%s", req.Name) + } + }() + + if err = json.Unmarshal(jsonIn, &req); err != nil { + return jsonStatusError(err) + } + + if err := validateUser(req.Name, req.Password, true); err != nil { + return jsonStatusError(err) + } + + st, err := crypt.ResetPersisted() + if err != nil { + return jsonStatusError(err) + } + + resp := &StatusData{Status: st.State} + if out, err = json.Marshal(resp); err != nil { + return jsonStatusError(err) + } + + return jsonResponse(out) +} diff --git a/core/core_test.go b/core/core_test.go index f861f81..8fa8e33 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -1306,10 +1306,11 @@ func afterRestartRestore(t *testing.T) { } func restoreDelegateRestore(t *testing.T) { - delegateJsonBad := []byte("{\"Name\":\"Alice\",\"Password\":\"Hi\",\"Time\":\"5m\",\"Uses\":1}") - delegateJson1 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":1}") - delegateJson2 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":1}") - delegateJson3 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":1}") + adminUser := []byte("{\"Name\":\"Admin\",\"Password\":\"Admin\"}") + delegateJsonBad := []byte("{\"Name\":\"Alice\",\"Password\":\"Hi\",\"Time\":\"5m\",\"Uses\":3}") + delegateJson1 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":3}") + delegateJson2 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":3}") + delegateJson3 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":3}") var s ResponseData respJson, err := Restore(delegateJsonBad) @@ -1382,6 +1383,19 @@ func restoreDelegateRestore(t *testing.T) { } else if _, ok = summary.Live["Bob"]; !ok { t.Fatalf("Bob should be present in the active delegations.") } + + respJson, err = ResetPersisted(adminUser) + if err != nil { + t.Fatalf("Resetting persisted delegations failed: %s", err) + } + + err = json.Unmarshal(respJson, &s) + if err != nil { + t.Fatalf("Error resetting persisted delegations: %s", err) + } else if s.Status != "ok" { + t.Fatalf("Error resetting persisted delegations: %s", s.Status) + } + restoreState(t, persist.Active) } func TestRestore(t *testing.T) { diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 4d9b945..ea8890e 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -777,7 +777,13 @@ func (c *Cryptor) Restore(name, password string, uses int, slot, durationString return err } - c.cache = keycache.NewFrom(uk) + rcache := keycache.NewFrom(uk) + err = rcache.Restore() + if err != nil { + return err + } + + c.cache = rcache c.persist.Persist() c.persist.Cache().Flush() return nil @@ -787,3 +793,10 @@ func (c *Cryptor) Restore(name, password string, uses int, slot, durationString func (c *Cryptor) Status() *persist.Status { return c.persist.Status() } + +// ResetPersisted clears any persisted delegations and returns the +// vault to an active delegation state if configured. +func (c *Cryptor) ResetPersisted() (*persist.Status, error) { + err := c.persist.Purge() + return c.persist.Status(), err +} diff --git a/keycache/keycache.go b/keycache/keycache.go index 9268964..00443e8 100644 --- a/keycache/keycache.go +++ b/keycache/keycache.go @@ -11,6 +11,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha1" + "crypto/x509" "errors" "fmt" "log" @@ -44,6 +45,7 @@ type ActiveUser struct { AltNames map[string]string Admin bool Type string + Key []byte rsaKey rsa.PrivateKey eccKey *ecdsa.PrivateKey @@ -225,8 +227,16 @@ func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, pass switch record.Type { case passvault.RSARecord: current.rsaKey, err = record.GetKeyRSA(password) + if err != nil { + return + } + current.Key = x509.MarshalPKCS1PrivateKey(¤t.rsaKey) case passvault.ECCRecord: current.eccKey, err = record.GetKeyECC(password) + if err != nil { + return + } + current.Key, err = x509.MarshalECPrivateKey(current.eccKey) default: err = errors.New("Unknown record type") } @@ -370,3 +380,31 @@ func (cache *Cache) DelegateStatus(name string, labels, admins []string) (admins } return } + +// Restore unmarshals the private key stored in the delegator to the +// appropriate private structure. +func (cache *Cache) Restore() (err error) { + for index, uk := range cache.UserKeys { + if len(uk.Key) == 0 { + return errors.New("keycache: no private key in active user") + } + + rsaPriv, err := x509.ParsePKCS1PrivateKey(uk.Key) + if err == nil { + uk.rsaKey = *rsaPriv + cache.UserKeys[index] = uk + continue + } + + ecPriv, err := x509.ParseECPrivateKey(uk.Key) + if err == nil { + uk.eccKey = ecPriv + cache.UserKeys[index] = uk + continue + } + + return err + } + + return nil +} diff --git a/msp/msp.go b/msp/msp.go index 75a7782..3e11621 100644 --- a/msp/msp.go +++ b/msp/msp.go @@ -4,6 +4,7 @@ import ( "container/heap" "crypto/rand" "errors" + "fmt" "strings" ) @@ -191,7 +192,7 @@ func (m MSP) DistributeShares(sec []byte, db UserDatabase) (map[string][][]byte, case Name: name := cond.string if !db.ValidUser(name) { - return nil, errors.New("Unknown user in predicate.") + return nil, fmt.Errorf("Unknown user '%s' in predicate.", name) } out[name] = append(out[name], share) diff --git a/persist/file.go b/persist/file.go index b03a018..3b7144a 100644 --- a/persist/file.go +++ b/persist/file.go @@ -127,3 +127,12 @@ func (f *File) Status() *Status { Summary: f.cache.GetSummary(), } } + +func (f *File) Purge() error { + f.state = Active + f.blob = nil + if err := os.Remove(f.config.Location); err != nil { + return err + } + return nil +} diff --git a/persist/file_test.go b/persist/file_test.go index 44f6963..05c03fe 100644 --- a/persist/file_test.go +++ b/persist/file_test.go @@ -8,6 +8,11 @@ import ( "github.com/cloudflare/redoctober/config" ) +func fexists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + func TestFileConfig(t *testing.T) { cfg := &config.Delegations{ Persist: false, @@ -165,3 +170,96 @@ func TestNewFilePersists(t *testing.T) { t.Fatalf("fresh store should be persisting") } } + +func TestActivePurge(t *testing.T) { + sf, err := tempName() + if err != nil { + t.Fatal(err) + } + defer os.Remove(sf) + + cfg := &config.Delegations{ + Persist: true, + Mechanism: FileMechanism, + Policy: "alice & bob", + Users: []string{"alice", "bob"}, + Location: sf, + } + + f, err := New(cfg) + if err != nil { + t.Fatal(err) + } + + _, ok := f.(*File) + if !ok { + t.Fatalf("persist: expected to get a *File but have %T", f) + } + + const expected = "test data" + if err = f.Store([]byte(expected)); err != nil { + t.Fatal(err) + } + + if !fexists(sf) { + t.Fatalf("persist: file store wasn't written to disk") + } + + err = f.Purge() + if err != nil { + t.Fatalf("%s", err) + } + + if fexists(sf) { + t.Fatalf("persist: store should have been removed during purge") + } +} + +func TestInactivePurge(t *testing.T) { + sf, err := tempName() + if err != nil { + t.Fatal(err) + } + defer os.Remove(sf) + + cfg := &config.Delegations{ + Persist: true, + Mechanism: FileMechanism, + Policy: "alice & bob", + Users: []string{"alice", "bob"}, + Location: sf, + } + + const expected = "test data" + err = ioutil.WriteFile(sf, []byte(expected), 0644) + if err != nil { + t.Fatalf("%s", err) + } + + f, err := New(cfg) + if err != nil { + t.Fatal(err) + } + + file, ok := f.(*File) + if !ok { + t.Fatalf("persist: expected to get a *File but have %T", f) + } + + if err = f.Store([]byte(expected)); err != nil { + t.Fatal(err) + } + + err = f.Purge() + if err != nil { + t.Fatalf("%s", err) + } + + if fexists(sf) { + t.Fatalf("persist: store should have been removed during purge") + } + + if file.Status().State != Active { + t.Fatalf("fresh store should be persisting") + } +} diff --git a/persist/null.go b/persist/null.go index 8d9d4fd..3f261ec 100644 --- a/persist/null.go +++ b/persist/null.go @@ -57,3 +57,7 @@ func (n *Null) Cache() *keycache.Cache { cache := keycache.NewCache() return &cache } + +func (n *Null) Purge() error { + return nil +} diff --git a/persist/null_test.go b/persist/null_test.go index af972cc..60bf9e3 100644 --- a/persist/null_test.go +++ b/persist/null_test.go @@ -55,6 +55,10 @@ func TestNewNull(t *testing.T) { } if cache := store.Cache(); len(cache.UserKeys) != 0 { - t.Fatal("persist: Null Cache should return an empty cache") + t.Fatal("persist: Null Cache() should return an empty cache") + } + + if store.Purge() != nil { + t.Fatal("persist: Null Purge() shouldn't return an error") } } diff --git a/persist/persist.go b/persist/persist.go index 8eaf5f8..b0a91c1 100644 --- a/persist/persist.go +++ b/persist/persist.go @@ -53,6 +53,8 @@ type Store interface { // This is not the main keycache. This is the keycache for // users that can decrypt the store. Cache() *keycache.Cache + // Purge clears the persisted keys. + Purge() error } // FileMechanism indicates that the persistence mechanism is a file. diff --git a/redoctober.go b/redoctober.go index 27a2f7c..1d0f7e3 100644 --- a/redoctober.go +++ b/redoctober.go @@ -29,24 +29,25 @@ import ( // List of URLs to register and their related functions var functions = map[string]func([]byte) ([]byte, error){ - "/create": core.Create, - "/create-user": core.CreateUser, - "/summary": core.Summary, - "/purge": core.Purge, - "/delegate": core.Delegate, - "/password": core.Password, - "/encrypt": core.Encrypt, - "/re-encrypt": core.ReEncrypt, - "/decrypt": core.Decrypt, - "/owners": core.Owners, - "/modify": core.Modify, - "/export": core.Export, - "/order": core.Order, - "/orderout": core.OrdersOutstanding, - "/orderinfo": core.OrderInfo, - "/ordercancel": core.OrderCancel, - "/restore": core.Restore, - "/status": core.Status, + "/create": core.Create, + "/create-user": core.CreateUser, + "/summary": core.Summary, + "/purge": core.Purge, + "/delegate": core.Delegate, + "/password": core.Password, + "/encrypt": core.Encrypt, + "/re-encrypt": core.ReEncrypt, + "/decrypt": core.Decrypt, + "/owners": core.Owners, + "/modify": core.Modify, + "/export": core.Export, + "/order": core.Order, + "/orderout": core.OrdersOutstanding, + "/orderinfo": core.OrderInfo, + "/ordercancel": core.OrderCancel, + "/restore": core.Restore, + "/reset-persisted": core.ResetPersisted, + "/status": core.Status, } type userRequest struct { diff --git a/redoctober_test.go b/redoctober_test.go index 05e8aa6..46199b0 100644 --- a/redoctober_test.go +++ b/redoctober_test.go @@ -9,6 +9,7 @@ package main import ( "bytes" "crypto/tls" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -19,6 +20,7 @@ import ( "testing" "time" + "github.com/cloudflare/redoctober/client" "github.com/cloudflare/redoctober/config" "github.com/cloudflare/redoctober/core" "github.com/cloudflare/redoctober/persist" @@ -59,7 +61,8 @@ var ( Uses: 1, } - encryptInput = &core.EncryptRequest{ + encryptMessage = "Why is a raven like a writing desk?\n" + encryptInput = &core.EncryptRequest{ Minimum: 2, Name: createVaultInput.Name, Password: createVaultInput.Password, @@ -636,6 +639,22 @@ func TestPurge(t *testing.T) { // These need to write files to disk in order to test recovering delegations. // //////////////////////////////////////////////////////////////////////////////// +var ( + restore *client.RemoteServer + + // restoreSecret is the encrypted data that should be decryptable + // both before and after the restart. + restoreSecret []byte + + restoreEncryptInput = &core.EncryptRequest{ + Minimum: 2, + Name: createVaultInput.Name, + Password: createVaultInput.Password, + Owners: []string{createUserInput3.Name, createUserInput2.Name}, + Data: []byte(base64.StdEncoding.EncodeToString([]byte(encryptMessage))), + } +) + func restoreSetup(t *testing.T, configPath, vaultPath string) (cmd *exec.Cmd) { const maxAttempts = 5 @@ -650,7 +669,6 @@ func restoreSetup(t *testing.T, configPath, vaultPath string) (cmd *exec.Cmd) { } cmd = exec.Command(binaryPath, "-vaultpath", vaultPath, "-f", configPath) - if err := cmd.Start(); err != nil { t.Fatalf("Error running redoctober command, %v", err) } @@ -705,6 +723,9 @@ func TestRestore(t *testing.T) { // The server has restarted --- verify that the persisted // delegations are available. afterRestartRestore(t, cfgPath, vaultPath) + + // Verify that we can reset the persisted delegations. + afterRestartPurge(t, cfgPath, vaultPath) } func prepareSetup(t *testing.T, pstore, cfgPath string) { @@ -736,6 +757,16 @@ func prepareSetup(t *testing.T, pstore, cfgPath string) { if err != nil { t.Fatalf("failed to write config file: %s", err) } + + // We'll have two uses: one to permit the pre-restart decryption, + // one to permit the post-restart decryption, and one to verify + // the purge functionality. + delegateInput1.Uses = 3 + delegateInput2.Uses = 3 + + // Allow someone to decrypt. + delegateInput1.Users = []string{createVaultInput.Name} + delegateInput2.Users = []string{createVaultInput.Name} } func restoreCheckStatus(t *testing.T, expected string) { @@ -816,17 +847,23 @@ func beforeRestartRestore(t *testing.T, cfgPath, vaultPath string) { cmd := restoreSetup(t, cfgPath, vaultPath) defer teardown(t, cmd) + srv, err := client.NewRemoteServer("localhost:8080", "testdata/server.crt") + if err != nil { + t.Fatalf("failed to set up client: %s", err) + } + // Create a vault/admin user and 2 normal users so there is data to work with. - if _, _, err := post("create", createVaultInput); err != nil { + if _, _, err = post("create", createVaultInput); err != nil { t.Fatalf("failed to create the vault: %s", err) } - if _, _, err := post("create-user", createUserInput1); err != nil { + + if _, err = srv.CreateUser(*createUserInput1); err != nil { t.Fatalf("couldn't create user %s: %s", createUserInput1.Name, err) } - if _, _, err := post("create-user", createUserInput2); err != nil { + if _, err = srv.CreateUser(*createUserInput2); err != nil { t.Fatalf("couldn't create user %s: %s", createUserInput2.Name, err) } - if _, _, err := post("create-user", createUserInput3); err != nil { + if _, err = srv.CreateUser(*createUserInput3); err != nil { t.Fatalf("couldn't create user %s: %s", createUserInput3.Name, err) } @@ -835,29 +872,55 @@ func beforeRestartRestore(t *testing.T, cfgPath, vaultPath string) { restoreCheckStatus(t, persist.Active) // Delegate two users. - if _, _, err := post("delegate", delegateInput2); err != nil { + if _, err = srv.Delegate(*delegateInput2); err != nil { t.Fatalf("failed to delegate for %s: %s", delegateInput2.Name, err) } - if _, _, err := post("delegate", delegateInput3); err != nil { + if _, err = srv.Delegate(*delegateInput3); err != nil { t.Fatalf("failed to delegate for %s: %s", delegateInput3.Name, err) } restoreCheckLiveCount(t, 2) restoreCheckLiveUsers(t, []string{delegateInput2.Name, delegateInput3.Name}, []string{createUserInput1.Name, createVaultInput.Name}) + + // Encrypt a message, and make sure it can be decrypted. + resp, err := srv.Encrypt(*restoreEncryptInput) + restoreSecret = resp.Response + + decryptInput := &core.DecryptRequest{ + Name: createVaultInput.Name, + Password: createVaultInput.Password, + Data: restoreSecret[:], + } + + decrypted, err := srv.DecryptIntoData(*decryptInput) + if err != nil { + t.Fatalf("failed to decrypt message: %s", err) + } + + decryptedMessage, err := base64.StdEncoding.DecodeString(string(decrypted)) + if string(decryptedMessage) != encryptMessage { + t.Fatalf("decryption produced the wrong message: want '%s' but have '%s'", + encryptMessage, decryptedMessage) + } } func afterRestartRestore(t *testing.T, cfgPath, vaultPath string) { cmd := restoreSetup(t, cfgPath, vaultPath) defer teardown(t, cmd) + srv, err := client.NewRemoteServer("localhost:8080", "testdata/server.crt") + if err != nil { + t.Fatalf("failed to set up client: %s", err) + } + // An existing vault with a persisted delegation store should // be inactive. restoreCheckStatus(t, persist.Inactive) // Delegate a user who wasn't in the persisted delegation set. - if _, _, err := post("delegate", delegateInput1); err != nil { - t.Fatalf("Error delegating with user 1, %v", err) + if _, err := srv.Delegate(*delegateInput1); err != nil { + t.Fatalf("error delegating with user 1, %v", err) } restoreCheckLiveCount(t, 1) @@ -865,7 +928,7 @@ func afterRestartRestore(t *testing.T, cfgPath, vaultPath string) { []string{createUserInput2.Name, delegateInput3.Name, createVaultInput.Name}) // Begin the restoration by delegating for a single user. - if _, _, err := post("restore", delegateInput1); err != nil { + if _, err := srv.Restore(*delegateInput1); err != nil { t.Fatalf("restoration by user %s failed: %s", delegateInput1.Name, err) } @@ -877,12 +940,56 @@ func afterRestartRestore(t *testing.T, cfgPath, vaultPath string) { // Delegate the second user, which should lead to restoring // the delegations. - if _, _, err := post("restore", delegateInput4); err != nil { - t.Fatalf("restoration by user %s failed: %s", delegateInput4.Name, err) + if _, err := srv.Restore(*delegateInput4); err != nil { + t.Fatalf("restoration by user %s failed: %s", delegateInput1.Name, err) } restoreCheckStatus(t, persist.Active) restoreCheckLiveCount(t, 2) restoreCheckLiveUsers(t, []string{delegateInput2.Name, delegateInput3.Name}, []string{createUserInput1.Name, createVaultInput.Name}) + + decryptInput := &core.DecryptRequest{ + Name: createVaultInput.Name, + Password: createVaultInput.Password, + Data: restoreSecret[:], + } + + decrypted, err := srv.DecryptIntoData(*decryptInput) + if err != nil { + t.Fatalf("failed to decrypt message: %s", err) + } + + decryptedMessage, err := base64.StdEncoding.DecodeString(string(decrypted)) + if string(decryptedMessage) != encryptMessage { + t.Fatalf("decryption produced the wrong message: want '%s' but have '%s'", + encryptMessage, decryptedMessage) + } +} + +func afterRestartPurge(t *testing.T, cfgPath, vaultPath string) { + cmd := restoreSetup(t, cfgPath, vaultPath) + defer teardown(t, cmd) + + srv, err := client.NewRemoteServer("localhost:8080", "testdata/server.crt") + if err != nil { + t.Fatalf("failed to set up client: %s", err) + } + + // An existing vault with a persisted delegation store should + // be inactive. + restoreCheckStatus(t, persist.Inactive) + + resp, err := srv.ResetPersisted(core.PurgeRequest{Name: createVaultInput.Name, Password: createVaultInput.Password}) + if err != nil { + t.Fatalf("failed to reset persisted delegations: %s", err) + } + + if resp.Status != "ok" { + t.Fatalf("failed to reset persisted delegations: %s", resp.Status) + } + + // An existing vault whose persistence store has been reset + // should be active. + restoreCheckStatus(t, persist.Active) }