Select Git revision
CmixStopNetworkFollower.swift
password_test.go 12.41 KiB
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
//go:build js && wasm
package storage
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"testing"
"gitlab.com/elixxir/wasm-utils/storage"
"gitlab.com/xx_network/crypto/csprng"
)
// Tests that running getOrInit twice returns the same internal password both
// times.
func Test_getOrInit(t *testing.T) {
externalPassword := "myPassword"
internalPassword, err := getOrInit(externalPassword)
if err != nil {
t.Errorf("%+v", err)
}
loadedInternalPassword, err := getOrInit(externalPassword)
if err != nil {
t.Errorf("%+v", err)
}
if !bytes.Equal(internalPassword, loadedInternalPassword) {
t.Errorf("Internal password from storage does not match original."+
"\nexpected: %+v\nreceived: %+v",
internalPassword, loadedInternalPassword)
}
}
// Tests that changeExternalPassword correctly changes the password and updates
// the encryption.
// func Test_changeExternalPassword(t *testing.T) {
// oldExternalPassword := "myPassword"
// newExternalPassword := "hunter2"
// oldInternalPassword, err := getOrInit(oldExternalPassword)
// if err != nil {
// t.Errorf("%+v", err)
// }
// err = changeExternalPassword(oldExternalPassword, newExternalPassword)
// if err != nil {
// t.Errorf("%+v", err)
// }
// newInternalPassword, err := getOrInit(newExternalPassword)
// if err != nil {
// t.Errorf("%+v", err)
// }
// if !bytes.Equal(oldInternalPassword, newInternalPassword) {
// t.Errorf("Internal password was not changed in storage. Old and new "+
// "should be different.\nold: %+v\nnew: %+v",
// oldInternalPassword, newInternalPassword)
// }
// _, err = getOrInit(oldExternalPassword)
// expectedErr := strings.Split(decryptWithPasswordErr, "%")[0]
// if err == nil || !strings.Contains(err.Error(), expectedErr) {
// t.Errorf("Unexpected error when trying to get internal password with "+
// "old external password.\nexpected: %s\nreceived: %+v", expectedErr, err)
// }
// }
// Tests that verifyPassword returns true for a valid password and false for an
// invalid password
func Test_verifyPassword(t *testing.T) {
storage.GetLocalStorage().Clear()
externalPassword := "myPassword"
if _, err := getOrInit(externalPassword); err != nil {
t.Errorf("%+v", err)
}
if !verifyPassword(externalPassword) {
t.Errorf("Password %q is incorrect.", externalPassword)
}
if verifyPassword("wrong password") {
t.Error("Incorrect password found to be correct.")
}
}
// Tests that the internal password returned by initInternalPassword matches
// the encrypted one saved to local storage.
func Test_initInternalPassword(t *testing.T) {
externalPassword := "myPassword"
ls := storage.GetLocalStorage()
rng := csprng.NewSystemRNG()
internalPassword, err := initInternalPassword(
externalPassword, ls, rng, defaultParams())
if err != nil {
t.Errorf("%+v", err)
}
// Attempt to retrieve encrypted internal password from storage
encryptedInternalPassword, err := ls.Get(passwordKey)
if err != nil {
t.Errorf(
"Failed to load encrypted internal password from storage: %+v", err)
}
// Attempt to retrieve salt from storage
salt, err := ls.Get(saltKey)
if err != nil {
t.Errorf("Failed to load salt from storage: %+v", err)
}
// Attempt to decrypt
key := deriveKey(externalPassword, salt, defaultParams())
decryptedInternalPassword, err :=
decryptPassword(encryptedInternalPassword, key)
if err != nil {
t.Errorf("Failed to load decrpyt internal password: %+v", err)
}
if !bytes.Equal(internalPassword, decryptedInternalPassword) {
t.Errorf("Decrypted internal password from storage does not match "+
"original.\nexpected: %+v\nreceived: %+v",
internalPassword, decryptedInternalPassword)
}
}
// Tests that initInternalPassword returns an error when the RNG returns an
// error when read.
func Test_initInternalPassword_CsprngReadError(t *testing.T) {
externalPassword := "myPassword"
ls := storage.GetLocalStorage()
b := bytes.NewBuffer([]byte{})
expectedErr := strings.Split(readInternalPasswordErr, "%")[0]
_, err := initInternalPassword(externalPassword, ls, b, defaultParams())
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error when RNG returns a read error."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Tests that initInternalPassword returns an error when the RNG does not
// return enough bytes.
// func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
// externalPassword := "myPassword"
// ls := storage.GetLocalStorage()
// b := bytes.NewBuffer(make([]byte, internalPasswordLen/2))
// expectedErr := fmt.Sprintf(
// internalPasswordNumBytesErr, internalPasswordLen, internalPasswordLen/2)
// _, err := initInternalPassword(externalPassword, ls, b, defaultParams())
// if err == nil || !strings.Contains(err.Error(), expectedErr) {
// t.Errorf("Unexpected error when RNG does not return enough bytes."+
// "\nexpected: %s\nreceived: %+v", expectedErr, err)
// }
// }
// Tests that getInternalPassword returns the internal password that is saved
// to local storage by initInternalPassword.
func Test_getInternalPassword(t *testing.T) {
externalPassword := "myPassword"
ls := storage.GetLocalStorage()
rng := csprng.NewSystemRNG()
internalPassword, err := initInternalPassword(
externalPassword, ls, rng, defaultParams())
if err != nil {
t.Errorf("%+v", err)
}
loadedInternalPassword, err := getInternalPassword(externalPassword, ls)
if err != nil {
t.Errorf("%+v", err)
}
if !bytes.Equal(internalPassword, loadedInternalPassword) {
t.Errorf("Internal password from storage does not match original."+
"\nexpected: %+v\nreceived: %+v",
internalPassword, loadedInternalPassword)
}
}
// Tests that getInternalPassword returns an error when the password cannot be
// loaded from local storage.
func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
externalPassword := "myPassword"
ls := storage.GetLocalStorage()
ls.Clear()
expectedErr := strings.Split(getPasswordStorageErr, "%")[0]
_, err := getInternalPassword(externalPassword, ls)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error when password cannot be loaded from storage."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Tests that getInternalPassword returns an error when the salt cannot be
// loaded from local storage.
func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
externalPassword := "myPassword"
ls := storage.GetLocalStorage()
ls.Clear()
if err := ls.Set(passwordKey, []byte("password")); err != nil {
t.Fatalf("Failed to set %q: %+v", passwordKey, err)
}
expectedErr := strings.Split(getSaltStorageErr, "%")[0]
_, err := getInternalPassword(externalPassword, ls)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error when salt cannot be loaded from storage."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Tests that getInternalPassword returns an error when the password cannot be
// decrypted.
func Test_getInternalPassword_DecryptPasswordError(t *testing.T) {
externalPassword := "myPassword"
ls := storage.GetLocalStorage()
ls.Clear()
if err := ls.Set(saltKey, []byte("salt")); err != nil {
t.Errorf("failed to set %q: %+v", saltKey, err)
}
if err := ls.Set(passwordKey, []byte("password")); err != nil {
t.Errorf("failed to set %q: %+v", passwordKey, err)
}
if err := ls.Set(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`)); err != nil {
t.Errorf("failed to set %q: %+v", argonParamsKey, err)
}
expectedErr := strings.Split(decryptPasswordErr, "%")[0]
_, err := getInternalPassword(externalPassword, ls)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error when the password is decrypted."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Smoke test of encryptPassword and decryptPassword.
func Test_encryptPassword_decryptPassword(t *testing.T) {
plaintext := []byte("Hello, World!")
password := []byte("test_password")
ciphertext := encryptPassword(plaintext, password, rand.Reader)
decrypted, err := decryptPassword(ciphertext, password)
if err != nil {
t.Errorf("%+v", err)
}
for i := range plaintext {
if plaintext[i] != decrypted[i] {
t.Errorf("%b != %b", plaintext[i], decrypted[i])
}
}
}
// Tests that decryptPassword does not panic when given too little data.
func Test_decryptPassword_ShortData(t *testing.T) {
// Anything under 24 should cause an error.
ciphertext := []byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
_, err := decryptPassword(ciphertext, []byte("dummyPassword"))
expectedErr := fmt.Sprintf(readNonceLenErr, 24)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error on short decryption."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
// Empty string shouldn't panic should cause an error.
ciphertext = []byte{}
_, err = decryptPassword(ciphertext, []byte("dummyPassword"))
expectedErr = fmt.Sprintf(readNonceLenErr, 0)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error on short decryption."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Tests that deriveKey returns a key of the correct length and that it is the
// same for the same set of password and salt. Also checks that keys with the
// same salt or passwords do not collide.
func Test_deriveKey(t *testing.T) {
p := testParams()
salts := make([][]byte, 6)
passwords := make([]string, len(salts))
keys := make(map[string]bool, len(salts)*len(passwords))
for i := range salts {
prng := csprng.NewSystemRNG()
salt, _ := makeSalt(prng)
salts[i] = salt
password := make([]byte, 16)
_, _ = prng.Read(password)
passwords[i] = base64.StdEncoding.EncodeToString(password)[:16]
}
for _, salt := range salts {
for _, password := range passwords {
key := deriveKey(password, salt, p)
// Check that the length of the key is correct
if len(key) != keyLen {
t.Errorf("Incorrect key length.\nexpected: %d\nreceived: %d",
keyLen, len(key))
}
// Check that the same key is generated when the same password and salt
// are used
key2 := deriveKey(password, salt, p)
if !bytes.Equal(key, key2) {
t.Errorf("Keys with same password and salt do not match."+
"\nexpected: %v\nreceived: %v", key, key2)
}
if keys[string(key)] {
t.Errorf("Key already exists.")
}
keys[string(key)] = true
}
}
}
// Tests that multiple calls to makeSalt results in unique salts of the
// specified length.
func Test_makeSalt(t *testing.T) {
salts := make(map[string]bool, 50)
for i := 0; i < 50; i++ {
salt, err := makeSalt(csprng.NewSystemRNG())
if err != nil {
t.Errorf("MakeSalt returned an error: %+v", err)
}
if len(salt) != saltLen {
t.Errorf("Incorrect salt length.\nexpected: %d\nreceived: %d",
saltLen, len(salt))
}
if salts[string(salt)] {
t.Errorf("Salt already exists (%d).", i)
}
salts[string(salt)] = true
}
}
// Tests that makeSalt returns an error when the RNG returns an error when read.
func Test_makeSalt_ReadError(t *testing.T) {
b := bytes.NewBuffer([]byte{})
expectedErr := strings.Split(readSaltErr, "%")[0]
_, err := makeSalt(b)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error when RNG returns a read error."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Tests that makeSalt returns an error when the RNG does not return enough
// bytes.
func Test_makeSalt_ReadNumBytesError(t *testing.T) {
b := bytes.NewBuffer(make([]byte, saltLen/2))
expectedErr := fmt.Sprintf(saltNumBytesErr, saltLen, saltLen/2)
_, err := makeSalt(b)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("Unexpected error when RNG does not return enough bytes."+
"\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// testParams returns params used in testing that are quick.
func testParams() argonParams {
return argonParams{
Time: 1,
Memory: 1,
Threads: 1,
}
}