vault_test.go raw

   1  package domain
   2  
   3  import (
   4  	"errors"
   5  	"strings"
   6  	"testing"
   7  )
   8  
   9  // testRounds is the work factor the vault tests use: the cost of PBKDF2 is not the property
  10  // under test, and one test asserts the shipped factor still round-trips.
  11  const testRounds = 1000
  12  
  13  // phrase is a valid twelve-word phrase with a correct checksum.
  14  const phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
  15  
  16  // TestSealAndOpen: the phrase survives the round trip, and the record is not the phrase.
  17  func TestSealAndOpen(t *testing.T) {
  18  	sealed, err := seal("correct horse", phrase, testRounds)
  19  
  20  	if err != nil {
  21  		t.Fatal(err)
  22  	}
  23  
  24  	if strings.Contains(string(sealed), "abandon") {
  25  		t.Fatal("the record contains the phrase")
  26  	}
  27  
  28  	opened, err := open("correct horse", sealed, testRounds)
  29  
  30  	if err != nil {
  31  		t.Fatal(err)
  32  	}
  33  
  34  	if opened != phrase {
  35  		t.Fatalf("opened %q", opened)
  36  	}
  37  }
  38  
  39  // TestTheShippedWorkFactorRoundTrips: Seal and Open agree on the shipped factor.
  40  func TestTheShippedWorkFactorRoundTrips(t *testing.T) {
  41  	sealed, err := Seal("correct horse", phrase)
  42  
  43  	if err != nil {
  44  		t.Fatal(err)
  45  	}
  46  
  47  	opened, err := Open("correct horse", sealed)
  48  
  49  	if err != nil {
  50  		t.Fatal(err)
  51  	}
  52  
  53  	if opened != phrase {
  54  		t.Fatalf("opened %q", opened)
  55  	}
  56  }
  57  
  58  // TestWrongPassphraseIsRefused: a wallet says the passphrase is wrong rather than opening a
  59  // different seed.
  60  func TestWrongPassphraseIsRefused(t *testing.T) {
  61  	sealed, err := seal("one", phrase, testRounds)
  62  
  63  	if err != nil {
  64  		t.Fatal(err)
  65  	}
  66  
  67  	if _, err := open("two", sealed, testRounds); !errors.Is(err, ErrVault) {
  68  		t.Fatalf("wrong passphrase opened the record: %v", err)
  69  	}
  70  }
  71  
  72  // TestTamperedRecordIsRefused: GCM authenticates, so an edited byte is an error.
  73  func TestTamperedRecordIsRefused(t *testing.T) {
  74  	sealed, err := seal("pass", phrase, testRounds)
  75  
  76  	if err != nil {
  77  		t.Fatal(err)
  78  	}
  79  
  80  	for _, index := range []int{0, 1, vaultHeaderLen, len(sealed) - 1} {
  81  		edited := append([]byte{}, sealed...)
  82  		edited[index] ^= 0xff
  83  
  84  		if _, err := open("pass", edited, testRounds); err == nil {
  85  			t.Fatalf("a record edited at byte %d opened", index)
  86  		}
  87  	}
  88  }
  89  
  90  // TestShortRecordIsRefused: a truncated record is a damaged record, not an empty wallet.
  91  func TestShortRecordIsRefused(t *testing.T) {
  92  	if _, err := open("pass", []byte{1, 2, 3}, testRounds); err == nil {
  93  		t.Fatal("a three-byte record opened")
  94  	}
  95  
  96  	if _, err := open("pass", nil, testRounds); err == nil {
  97  		t.Fatal("an empty record opened")
  98  	}
  99  }
 100  
 101  // TestUnknownVersionIsRefused: a record this build does not understand is refused, not guessed
 102  // at.
 103  func TestUnknownVersionIsRefused(t *testing.T) {
 104  	sealed, err := seal("pass", phrase, testRounds)
 105  
 106  	if err != nil {
 107  		t.Fatal(err)
 108  	}
 109  
 110  	sealed[0] = 99
 111  
 112  	if _, err := open("pass", sealed, testRounds); err == nil {
 113  		t.Fatal("a record with an unknown version opened")
 114  	}
 115  }
 116  
 117  // TestAnEmptyPassphraseIsRefused: sealing under nothing would be sealing under a constant.
 118  func TestAnEmptyPassphraseIsRefused(t *testing.T) {
 119  	if _, err := Seal("", phrase); !errors.Is(err, ErrNoPassphrase) {
 120  		t.Fatalf("sealed under an empty passphrase: %v", err)
 121  	}
 122  
 123  	if _, err := Open("", []byte{1, 2, 3}); !errors.Is(err, ErrNoPassphrase) {
 124  		t.Fatalf("opened under an empty passphrase: %v", err)
 125  	}
 126  }
 127  
 128  // TestSealingIsNotDeterministic: a fresh salt and nonce per record, so two wallets sealed from
 129  // one phrase do not look alike.
 130  func TestSealingIsNotDeterministic(t *testing.T) {
 131  	first, err := seal("pass", phrase, testRounds)
 132  
 133  	if err != nil {
 134  		t.Fatal(err)
 135  	}
 136  
 137  	second, err := seal("pass", phrase, testRounds)
 138  
 139  	if err != nil {
 140  		t.Fatal(err)
 141  	}
 142  
 143  	if string(first) == string(second) {
 144  		t.Fatal("two seals of one phrase are identical")
 145  	}
 146  }
 147  
 148  // TestAnEmptyPhraseIsRefused: sealing nothing produces a wallet that cannot restore anything.
 149  func TestAnEmptyPhraseIsRefused(t *testing.T) {
 150  	if _, err := Seal("pass", ""); err == nil {
 151  		t.Fatal("an empty phrase was sealed")
 152  	}
 153  }
 154