150 lines
3.8 KiB
Go
150 lines
3.8 KiB
Go
package secret
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/base64"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestAValueSurvivesTheRoundTrip(t *testing.T) {
|
||
|
|
b := mustBox(t)
|
||
|
|
for _, in := range []string{"1//0abc-refresh", "kodeord med æøå", strings.Repeat("x", 5000)} {
|
||
|
|
sealed, err := b.Seal(in)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if strings.Contains(sealed, in) {
|
||
|
|
t.Fatal("the sealed form still contains the value")
|
||
|
|
}
|
||
|
|
out, err := b.Unseal(sealed)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if out != in {
|
||
|
|
t.Errorf("got %q, want %q", out, in)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Two seals of the same value must not look alike, or the column tells anyone
|
||
|
|
// reading it which accounts share a password.
|
||
|
|
func TestSealingTwiceGivesDifferentText(t *testing.T) {
|
||
|
|
b := mustBox(t)
|
||
|
|
first, _ := b.Seal("same")
|
||
|
|
second, _ := b.Seal("same")
|
||
|
|
if first == second {
|
||
|
|
t.Error("the same value sealed to the same text twice")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The column holds values written before there was a key. Reading must not fail
|
||
|
|
// on them, and sealing must not wrap an already-sealed value a second time.
|
||
|
|
func TestPlainTextIsReadAsItself(t *testing.T) {
|
||
|
|
b := mustBox(t)
|
||
|
|
out, err := b.Unseal("1//0abc-written-before-there-was-a-key")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if out != "1//0abc-written-before-there-was-a-key" {
|
||
|
|
t.Errorf("an unsealed value came back as %q", out)
|
||
|
|
}
|
||
|
|
|
||
|
|
sealed, _ := b.Seal("x")
|
||
|
|
again, _ := b.Seal(sealed)
|
||
|
|
if again != sealed {
|
||
|
|
t.Error("an already-sealed value was sealed a second time")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNothingIsStillNothing(t *testing.T) {
|
||
|
|
b := mustBox(t)
|
||
|
|
if out, _ := b.Seal(""); out != "" {
|
||
|
|
t.Errorf("an empty value sealed to %q", out)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The message somebody gets after restoring a database without its key file. It
|
||
|
|
// has to point at the key, because every other symptom points at the mailbox.
|
||
|
|
func TestTheWrongKeySaysItIsTheWrongKey(t *testing.T) {
|
||
|
|
sealed, err := mustBox(t).Seal("hemmelig")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
_, err = mustBox(t).Unseal(sealed)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("another key opened it")
|
||
|
|
}
|
||
|
|
if !strings.Contains(err.Error(), "different key") {
|
||
|
|
t.Errorf("the error does not name the cause: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTheKeyFileIsMadeOnceAndKeptPrivate(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
if _, err := Open("", dir); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
path := filepath.Join(dir, KeyFile)
|
||
|
|
info, err := os.Stat(path)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if perm := info.Mode().Perm(); perm != 0o600 {
|
||
|
|
t.Errorf("the key file is %04o, not 0600", perm)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Opening again must reuse it. A second key here would quietly make every
|
||
|
|
// stored token unreadable, and it would look like the mailboxes disconnecting
|
||
|
|
// themselves overnight.
|
||
|
|
first, _ := os.ReadFile(path)
|
||
|
|
if _, err := Open("", dir); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
second, _ := os.ReadFile(path)
|
||
|
|
if string(first) != string(second) {
|
||
|
|
t.Error("opening a second time replaced the key")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAKeyOthersCanReadIsRefused(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
if _, err := Open("", dir); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
path := filepath.Join(dir, KeyFile)
|
||
|
|
if err := os.Chmod(path, 0o644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if _, err := Open("", dir); err == nil {
|
||
|
|
t.Error("a world-readable key was accepted")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The environment wins, so a host can keep the key off its disks altogether.
|
||
|
|
func TestTheEnvironmentBeatsTheFile(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
key := base64.StdEncoding.EncodeToString([]byte(strings.Repeat("k", 32)))
|
||
|
|
if _, err := Open(key, dir); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if _, err := os.Stat(filepath.Join(dir, KeyFile)); !os.IsNotExist(err) {
|
||
|
|
t.Error("a key file was written even though the environment carried one")
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := Open("too-short", dir); err == nil {
|
||
|
|
t.Error("a key that is not 32 bytes was accepted")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func mustBox(t *testing.T) *Box {
|
||
|
|
t.Helper()
|
||
|
|
b, err := Open("", t.TempDir())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
return b
|
||
|
|
}
|