feat: harden secret unlock and deletion
This commit is contained in:
parent
63c2c1314f
commit
4bb9e84c35
|
|
@ -293,6 +293,15 @@ export function createPluginAPI(pluginId) {
|
||||||
return App.PluginSecretsWrite(pluginId, record || {});
|
return App.PluginSecretsWrite(pluginId, record || {});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
delete: function(secretId) {
|
||||||
|
assertActive('secrets.delete(' + secretId + ')');
|
||||||
|
if (!secretId) {
|
||||||
|
throw new Error('secrets.delete requires a secret id');
|
||||||
|
}
|
||||||
|
return callBackendErrorString(pluginId, 'secrets.delete(' + secretId + ')', function() {
|
||||||
|
return App.PluginSecretsDelete(pluginId, secretId);
|
||||||
|
});
|
||||||
|
},
|
||||||
copyLink: function(secretId) {
|
copyLink: function(secretId) {
|
||||||
assertActive('secrets.copyLink(' + secretId + ')');
|
assertActive('secrets.copyLink(' + secretId + ')');
|
||||||
if (!secretId) {
|
if (!secretId) {
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,20 @@ export function OpenVaultPathExternal(arg1:string,arg2:string):Promise<string>;
|
||||||
|
|
||||||
export function OpenWorkbenchResource(arg1:string,arg2:Record<string, any>):Promise<workbench.OpenResourceResult|string>;
|
export function OpenWorkbenchResource(arg1:string,arg2:Record<string, any>):Promise<workbench.OpenResourceResult|string>;
|
||||||
|
|
||||||
|
export function PluginSecretsCopyLink(arg1:string,arg2:string):Promise<string|string>;
|
||||||
|
|
||||||
|
export function PluginSecretsDelete(arg1:string,arg2:string):Promise<string>;
|
||||||
|
|
||||||
|
export function PluginSecretsList(arg1:string):Promise<Array<Record<string, any>>|string>;
|
||||||
|
|
||||||
|
export function PluginSecretsRead(arg1:string,arg2:string):Promise<Record<string, any>|string>;
|
||||||
|
|
||||||
|
export function PluginSecretsStatus(arg1:string):Promise<Record<string, any>|string>;
|
||||||
|
|
||||||
|
export function PluginSecretsUnlock(arg1:string,arg2:string):Promise<string>;
|
||||||
|
|
||||||
|
export function PluginSecretsWrite(arg1:string,arg2:Record<string, any>):Promise<Record<string, any>|string>;
|
||||||
|
|
||||||
export function PluginSyncConfigure(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
export function PluginSyncConfigure(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
||||||
|
|
||||||
export function PluginSyncDisconnect(arg1:string):Promise<string>;
|
export function PluginSyncDisconnect(arg1:string):Promise<string>;
|
||||||
|
|
@ -94,18 +108,6 @@ export function PluginSyncStatus(arg1:string):Promise<api.SyncStatusDTO|string>;
|
||||||
|
|
||||||
export function PluginSyncTestConnection(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
export function PluginSyncTestConnection(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
||||||
|
|
||||||
export function PluginSecretsCopyLink(arg1:string,arg2:string):Promise<string|string>;
|
|
||||||
|
|
||||||
export function PluginSecretsList(arg1:string):Promise<Array<Record<string, any>>|string>;
|
|
||||||
|
|
||||||
export function PluginSecretsRead(arg1:string,arg2:string):Promise<Record<string, any>|string>;
|
|
||||||
|
|
||||||
export function PluginSecretsStatus(arg1:string):Promise<Record<string, any>|string>;
|
|
||||||
|
|
||||||
export function PluginSecretsUnlock(arg1:string,arg2:string):Promise<string>;
|
|
||||||
|
|
||||||
export function PluginSecretsWrite(arg1:string,arg2:Record<string, any>):Promise<Record<string, any>|string>;
|
|
||||||
|
|
||||||
export function PublishPluginEvent(arg1:string,arg2:string,arg3:Record<string, any>):Promise<string>;
|
export function PublishPluginEvent(arg1:string,arg2:string,arg3:Record<string, any>):Promise<string>;
|
||||||
|
|
||||||
export function ReadPluginDataJSON(arg1:string,arg2:string):Promise<Record<string, any>>;
|
export function ReadPluginDataJSON(arg1:string,arg2:string):Promise<Record<string, any>>;
|
||||||
|
|
@ -160,6 +162,6 @@ export function WritePluginSetting(arg1:string,arg2:string,arg3:any):Promise<str
|
||||||
|
|
||||||
export function WritePluginSettings(arg1:string,arg2:Record<string, any>):Promise<string>;
|
export function WritePluginSettings(arg1:string,arg2:Record<string, any>):Promise<string>;
|
||||||
|
|
||||||
export function WriteVaultTextFile(arg1:string,arg2:string,arg3:string,arg4:files.WriteOptions):Promise<string>;
|
|
||||||
|
|
||||||
export function WriteVaultFileBytes(arg1:string,arg2:string,arg3:string,arg4:files.WriteOptions):Promise<string>;
|
export function WriteVaultFileBytes(arg1:string,arg2:string,arg3:string,arg4:files.WriteOptions):Promise<string>;
|
||||||
|
|
||||||
|
export function WriteVaultTextFile(arg1:string,arg2:string,arg3:string,arg4:files.WriteOptions):Promise<string>;
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,34 @@ export function OpenWorkbenchResource(arg1, arg2) {
|
||||||
return window['go']['api']['App']['OpenWorkbenchResource'](arg1, arg2);
|
return window['go']['api']['App']['OpenWorkbenchResource'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsCopyLink(arg1, arg2) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsCopyLink'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsDelete(arg1, arg2) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsDelete'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsList(arg1) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsList'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsRead(arg1, arg2) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsRead'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsStatus(arg1) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsStatus'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsUnlock(arg1, arg2) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsUnlock'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginSecretsWrite(arg1, arg2) {
|
||||||
|
return window['go']['api']['App']['PluginSecretsWrite'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
export function PluginSyncConfigure(arg1, arg2, arg3, arg4) {
|
export function PluginSyncConfigure(arg1, arg2, arg3, arg4) {
|
||||||
return window['go']['api']['App']['PluginSyncConfigure'](arg1, arg2, arg3, arg4);
|
return window['go']['api']['App']['PluginSyncConfigure'](arg1, arg2, arg3, arg4);
|
||||||
}
|
}
|
||||||
|
|
@ -174,30 +202,6 @@ export function PluginSyncTestConnection(arg1, arg2, arg3, arg4) {
|
||||||
return window['go']['api']['App']['PluginSyncTestConnection'](arg1, arg2, arg3, arg4);
|
return window['go']['api']['App']['PluginSyncTestConnection'](arg1, arg2, arg3, arg4);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PluginSecretsCopyLink(arg1, arg2) {
|
|
||||||
return window['go']['api']['App']['PluginSecretsCopyLink'](arg1, arg2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PluginSecretsList(arg1) {
|
|
||||||
return window['go']['api']['App']['PluginSecretsList'](arg1);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PluginSecretsRead(arg1, arg2) {
|
|
||||||
return window['go']['api']['App']['PluginSecretsRead'](arg1, arg2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PluginSecretsStatus(arg1) {
|
|
||||||
return window['go']['api']['App']['PluginSecretsStatus'](arg1);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PluginSecretsUnlock(arg1, arg2) {
|
|
||||||
return window['go']['api']['App']['PluginSecretsUnlock'](arg1, arg2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PluginSecretsWrite(arg1, arg2) {
|
|
||||||
return window['go']['api']['App']['PluginSecretsWrite'](arg1, arg2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PublishPluginEvent(arg1, arg2, arg3) {
|
export function PublishPluginEvent(arg1, arg2, arg3) {
|
||||||
return window['go']['api']['App']['PublishPluginEvent'](arg1, arg2, arg3);
|
return window['go']['api']['App']['PublishPluginEvent'](arg1, arg2, arg3);
|
||||||
}
|
}
|
||||||
|
|
@ -306,10 +310,10 @@ export function WritePluginSettings(arg1, arg2) {
|
||||||
return window['go']['api']['App']['WritePluginSettings'](arg1, arg2);
|
return window['go']['api']['App']['WritePluginSettings'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WriteVaultTextFile(arg1, arg2, arg3, arg4) {
|
|
||||||
return window['go']['api']['App']['WriteVaultTextFile'](arg1, arg2, arg3, arg4);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WriteVaultFileBytes(arg1, arg2, arg3, arg4) {
|
export function WriteVaultFileBytes(arg1, arg2, arg3, arg4) {
|
||||||
return window['go']['api']['App']['WriteVaultFileBytes'](arg1, arg2, arg3, arg4);
|
return window['go']['api']['App']['WriteVaultFileBytes'](arg1, arg2, arg3, arg4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function WriteVaultTextFile(arg1, arg2, arg3, arg4) {
|
||||||
|
return window['go']['api']['App']['WriteVaultTextFile'](arg1, arg2, arg3, arg4);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1322,7 +1322,12 @@ func (a *App) PluginSecretsStatus(pluginID string) (map[string]interface{}, stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err.Error()
|
return nil, err.Error()
|
||||||
}
|
}
|
||||||
|
initialized, err := session.Initialized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.Error()
|
||||||
|
}
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
|
"initialized": initialized,
|
||||||
"unlocked": session.Unlocked(),
|
"unlocked": session.Unlocked(),
|
||||||
}, ""
|
}, ""
|
||||||
}
|
}
|
||||||
|
|
@ -1397,6 +1402,20 @@ func (a *App) PluginSecretsWrite(pluginID string, rawRecord map[string]interface
|
||||||
return secretRecordMap(written, false), ""
|
return secretRecordMap(written, false), ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) PluginSecretsDelete(pluginID, secretID string) string {
|
||||||
|
if err := a.requirePluginSecretsAccess(pluginID, true); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
store, err := a.requireUnlockedSecretStore()
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
if err := store.Delete(secretID); err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) PluginSecretsCopyLink(pluginID, secretID string) (string, string) {
|
func (a *App) PluginSecretsCopyLink(pluginID, secretID string) (string, string) {
|
||||||
if err := a.requirePluginSecretsAccess(pluginID, false); err != nil {
|
if err := a.requirePluginSecretsAccess(pluginID, false); err != nil {
|
||||||
return "", err.Error()
|
return "", err.Error()
|
||||||
|
|
|
||||||
|
|
@ -1628,6 +1628,9 @@ func TestPluginSecretsRequirePermissionsAndUnlock(t *testing.T) {
|
||||||
if status["unlocked"] == true {
|
if status["unlocked"] == true {
|
||||||
t.Fatalf("new secret session should be locked: %+v", status)
|
t.Fatalf("new secret session should be locked: %+v", status)
|
||||||
}
|
}
|
||||||
|
if status["initialized"] == true {
|
||||||
|
t.Fatalf("new secret session should not be initialized: %+v", status)
|
||||||
|
}
|
||||||
|
|
||||||
if errStr := app.PluginSecretsUnlock("no.storage", "master password"); !strings.Contains(errStr, "secrets.read") {
|
if errStr := app.PluginSecretsUnlock("no.storage", "master password"); !strings.Contains(errStr, "secrets.read") {
|
||||||
t.Fatalf("PluginSecretsUnlock err = %q, want secrets.read permission error", errStr)
|
t.Fatalf("PluginSecretsUnlock err = %q, want secrets.read permission error", errStr)
|
||||||
|
|
@ -1636,6 +1639,10 @@ func TestPluginSecretsRequirePermissionsAndUnlock(t *testing.T) {
|
||||||
t.Fatalf("PluginSecretsList before unlock err = %q, want locked", errStr)
|
t.Fatalf("PluginSecretsList before unlock err = %q, want locked", errStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errStr := app.PluginSecretsUnlock("secrets.plugin", "123123"); !strings.Contains(errStr, "at least 8 characters") {
|
||||||
|
t.Fatalf("weak PluginSecretsUnlock err = %q, want minimum length error", errStr)
|
||||||
|
}
|
||||||
|
|
||||||
if errStr := app.PluginSecretsUnlock("secrets.plugin", "master password"); errStr != "" {
|
if errStr := app.PluginSecretsUnlock("secrets.plugin", "master password"); errStr != "" {
|
||||||
t.Fatalf("PluginSecretsUnlock: %s", errStr)
|
t.Fatalf("PluginSecretsUnlock: %s", errStr)
|
||||||
}
|
}
|
||||||
|
|
@ -1646,6 +1653,9 @@ func TestPluginSecretsRequirePermissionsAndUnlock(t *testing.T) {
|
||||||
if status["unlocked"] != true {
|
if status["unlocked"] != true {
|
||||||
t.Fatalf("secret session not unlocked: %+v", status)
|
t.Fatalf("secret session not unlocked: %+v", status)
|
||||||
}
|
}
|
||||||
|
if status["initialized"] != true {
|
||||||
|
t.Fatalf("secret session not initialized: %+v", status)
|
||||||
|
}
|
||||||
|
|
||||||
writeResult, errStr := app.PluginSecretsWrite("secrets.plugin", map[string]interface{}{
|
writeResult, errStr := app.PluginSecretsWrite("secrets.plugin", map[string]interface{}{
|
||||||
"id": "client-a.database",
|
"id": "client-a.database",
|
||||||
|
|
@ -1694,6 +1704,17 @@ func TestPluginSecretsRequirePermissionsAndUnlock(t *testing.T) {
|
||||||
if link != "[Client A Database](verstak-secret://client-a.database)" {
|
if link != "[Client A Database](verstak-secret://client-a.database)" {
|
||||||
t.Fatalf("link = %q", link)
|
t.Fatalf("link = %q", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errStr := app.PluginSecretsDelete("secrets.plugin", "client-a.database"); errStr != "" {
|
||||||
|
t.Fatalf("PluginSecretsDelete: %s", errStr)
|
||||||
|
}
|
||||||
|
list, errStr = app.PluginSecretsList("secrets.plugin")
|
||||||
|
if errStr != "" {
|
||||||
|
t.Fatalf("PluginSecretsList after delete: %s", errStr)
|
||||||
|
}
|
||||||
|
if len(list) != 0 {
|
||||||
|
t.Fatalf("PluginSecretsList after delete = %+v, want empty", list)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPluginSecretsRejectWrongMasterPasswordAcrossSessions(t *testing.T) {
|
func TestPluginSecretsRejectWrongMasterPasswordAcrossSessions(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ const (
|
||||||
masterPBKDF2Iterations = 200000
|
masterPBKDF2Iterations = 200000
|
||||||
masterVerifierPlaintext = "verstak-secret-store:v1"
|
masterVerifierPlaintext = "verstak-secret-store:v1"
|
||||||
masterMetadataVersion = 1
|
masterMetadataVersion = 1
|
||||||
|
minMasterPasswordLength = 8
|
||||||
recordsDirName = "records"
|
recordsDirName = "records"
|
||||||
masterMetadataFileName = "metadata.json"
|
masterMetadataFileName = "metadata.json"
|
||||||
ScopeGlobal = "global"
|
ScopeGlobal = "global"
|
||||||
|
|
@ -184,6 +185,18 @@ func (s *Store) ReadRecord(id string) (SecretRecord, error) {
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) Delete(id string) error {
|
||||||
|
if err := validateID(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if err := os.Remove(s.pathForID(id)); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("delete secret %q: %w", id, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) ListRecords() ([]SecretRecord, error) {
|
func (s *Store) ListRecords() ([]SecretRecord, error) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
entries, err := os.ReadDir(s.root)
|
entries, err := os.ReadDir(s.root)
|
||||||
|
|
@ -350,6 +363,14 @@ func (s *VaultSession) Store() (*Store, error) {
|
||||||
return s.store, nil
|
return s.store, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *VaultSession) Initialized() (bool, error) {
|
||||||
|
metadata, err := readMasterMetadata(s.root)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return metadata != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *VaultSession) Unlock(masterPassword string) (*Store, error) {
|
func (s *VaultSession) Unlock(masterPassword string) (*Store, error) {
|
||||||
if strings.TrimSpace(masterPassword) == "" {
|
if strings.TrimSpace(masterPassword) == "" {
|
||||||
return nil, fmt.Errorf("master password is empty")
|
return nil, fmt.Errorf("master password is empty")
|
||||||
|
|
@ -366,6 +387,9 @@ func (s *VaultSession) Unlock(masterPassword string) (*Store, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
|
if err := validateInitialMasterPassword(masterPassword); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
created, key, err := createMasterMetadata(masterPassword)
|
created, key, err := createMasterMetadata(masterPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -393,6 +417,13 @@ func (s *VaultSession) Unlock(masterPassword string) (*Store, error) {
|
||||||
return store, nil
|
return store, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateInitialMasterPassword(masterPassword string) error {
|
||||||
|
if len([]rune(masterPassword)) < minMasterPasswordLength {
|
||||||
|
return fmt.Errorf("master password must be at least %d characters", minMasterPasswordLength)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func readMasterMetadata(root string) (*masterMetadata, error) {
|
func readMasterMetadata(root string) (*masterMetadata, error) {
|
||||||
data, err := os.ReadFile(filepath.Join(root, masterMetadataFileName))
|
data, err := os.ReadFile(filepath.Join(root, masterMetadataFileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,30 @@ func TestStoreRejectsUnsafeIDs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoreDeletesSecretRecord(t *testing.T) {
|
||||||
|
store, err := NewStore(t.TempDir(), testKey(0x11))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewStore: %v", err)
|
||||||
|
}
|
||||||
|
if err := store.Write("server.password", "s3cr3t-value"); err != nil {
|
||||||
|
t.Fatalf("Write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.Delete("server.password"); err != nil {
|
||||||
|
t.Fatalf("Delete: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := store.Read("server.password"); err == nil {
|
||||||
|
t.Fatal("Read after Delete succeeded")
|
||||||
|
}
|
||||||
|
list, err := store.ListRecords()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListRecords: %v", err)
|
||||||
|
}
|
||||||
|
if len(list) != 0 {
|
||||||
|
t.Fatalf("ListRecords after Delete = %+v, want empty", list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStoreListsScopedRecordsWithoutPlaintextOnDisk(t *testing.T) {
|
func TestStoreListsScopedRecordsWithoutPlaintextOnDisk(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
store, err := NewStore(root, testKey(0x11))
|
store, err := NewStore(root, testKey(0x11))
|
||||||
|
|
@ -224,3 +248,30 @@ func TestVaultSessionUnlocksWithMasterPasswordOnce(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVaultSessionReportsInitializationAndRejectsWeakInitialPassword(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
session := NewVaultSession(root)
|
||||||
|
|
||||||
|
initialized, err := session.Initialized()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Initialized: %v", err)
|
||||||
|
}
|
||||||
|
if initialized {
|
||||||
|
t.Fatal("new secret session is initialized")
|
||||||
|
}
|
||||||
|
if _, err := session.Unlock("123123"); err == nil || !strings.Contains(err.Error(), "at least 8 characters") {
|
||||||
|
t.Fatalf("weak initial Unlock err = %v, want minimum length error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := session.Unlock("strong password"); err != nil {
|
||||||
|
t.Fatalf("Unlock strong initial password: %v", err)
|
||||||
|
}
|
||||||
|
initialized, err = session.Initialized()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Initialized after unlock: %v", err)
|
||||||
|
}
|
||||||
|
if !initialized {
|
||||||
|
t.Fatal("secret session was not initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue