changeset 18:00d30c67b56d

Put all the library stuff into multipass/file.
author Paul Fisher <paul@pfish.zone>
date Sun, 01 Nov 2015 12:16:51 -0500
parents 342f63116bfd
children 58fe867c9ecf
files auth/auth.go auth/auth_test.go cmds/multipass-add/add.go file/auth.go file/auth_test.go file/file.go file/file_test.go
diffstat 7 files changed, 355 insertions(+), 361 deletions(-) [+]
line wrap: on
line diff
--- a/auth/auth.go	Fri Oct 30 00:18:13 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-// Package auth contains data structures for authenticating users.
-
-package auth
-
-import (
-	"crypto/rand"
-	"encoding/base64"
-	"errors"
-	"math/big"
-	"strconv"
-	"strings"
-
-	"golang.org/x/crypto/bcrypt"
-)
-
-const (
-	// default cost stolen from python bcrypt
-	bcryptCost = 12
-	// we only generate passwords from lowercases for non-ambiguity
-	lowercases = "abcdefghijklmnopqrstuvwxyz"
-	template   = "????-????-????-????"
-)
-
-var (
-	lowercaseLen *big.Int = big.NewInt(int64(len(lowercases)))
-	maxID                 = big.NewInt(0)
-)
-
-var (
-	WrongLengthError     error = errors.New("multipass/auth: password entry must have 3 fields")
-	BadIDError                 = errors.New("multipass/auth: ID field invalid")
-	Base64Error                = errors.New("multipass/auth: can't decode base64 data")
-	LongDescriptionError       = errors.New("multipass/auth: description must be less than 255 bytes")
-)
-
-func init() {
-	one := big.NewInt(1)
-	maxID.Lsh(one, 64)
-}
-
-// Entry represents a single entry in the a multipass file.
-type Entry struct {
-	id          uint64
-	hash        string
-	description string
-}
-
-// EntryFromShadow creates a new entry from a line in a multipass shadow file.
-// The line should not end in a newline.
-func EntryFromShadow(shadow string) (*Entry, error) {
-	segments := strings.Split(shadow, ":")
-	if len(segments) != 3 {
-		return nil, WrongLengthError
-	}
-	entry := new(Entry)
-	id, err := strconv.ParseUint(segments[0], 10, 64)
-	if err != nil {
-		return nil, BadIDError
-	}
-	entry.id = id
-	entry.hash = segments[1]
-	description, err := base64.StdEncoding.DecodeString(segments[2])
-	if err != nil {
-		return nil, Base64Error
-	}
-	entry.description = string(description)
-	return entry, nil
-}
-
-// NewEntry creates an Entry for the given description.
-// It returns the Entry itself and a generated password.
-func NewEntry(description string) (entry *Entry, password string, err error) {
-	if len(description) > 255 {
-		return nil, "", LongDescriptionError
-	}
-	passBytes := genPassword()
-	password = string(passBytes)
-	hashBytes, err := bcrypt.GenerateFromPassword(passBytes, bcryptCost)
-	if err != nil {
-		// This is very unexpected.
-		return nil, "", err
-	}
-	e := new(Entry)
-	e.id = newID()
-	e.hash = string(hashBytes)
-	e.description = description
-	return e, password, nil
-}
-
-// ID is a unique 64-bit integer which identifies the entry.
-func (e *Entry) ID() uint64 {
-	return e.id
-}
-
-// Description is the user's description of their password.
-func (e *Entry) Description() string {
-	return e.description
-}
-
-// Authenticate tests whether the password is correct.
-func (e *Entry) Authenticate(password string) bool {
-	err := bcrypt.CompareHashAndPassword([]byte(e.hash), []byte(password))
-	return err == nil
-}
-
-// Encode encodes this Entry to a bytestring for writing to a multipass shadow file.
-func (e *Entry) Encode() string {
-	segments := []string{
-		strconv.FormatUint(e.id, 10),
-		e.hash,
-		base64.StdEncoding.EncodeToString([]byte(e.description)),
-	}
-	return strings.Join(segments, ":")
-}
-
-func genPassword() []byte {
-	password := []byte(template)
-	for i, chr := range password {
-		if chr == '?' {
-			password[i] = randChr()
-		}
-	}
-	return password
-}
-
-func randChr() byte {
-	bigIdx, err := rand.Int(rand.Reader, lowercaseLen)
-	if err != nil {
-		panic("multipass/auth: can't get a random number")
-	}
-	idx := bigIdx.Int64()
-	return byte(lowercases[idx])
-}
-
-func newID() uint64 {
-	bigID, err := rand.Int(rand.Reader, maxID)
-	if err != nil {
-		panic("multipass/auth: can't get a random number")
-	}
-	return bigID.Uint64()
-}
--- a/auth/auth_test.go	Fri Oct 30 00:18:13 2015 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-package auth
-
-import (
-	"regexp"
-	"testing"
-)
-
-var passPattern *regexp.Regexp = regexp.MustCompile(`^(?:[a-z]{4}-){3}[a-z]{4}$`)
-
-const basicShadow = "9999:$2a$12$tcv2MrtXgibAJHsSwVfHiOevXBFmiGy0HTNoOB8QzIhEh46iWS1uC:YW55dGhpbmcgbW9yZSB0aGFuIDUgcmVwcyBpcyBjYXJkaW8="
-const anotherShadow = "1:$2a$12$lINQdYWHOcLKoqhNOr3mNOpZSAu5JOBS2F7T/VDfYn2rvv6qUJehG:"
-
-func TestEntryFromShadow(t *testing.T) {
-	cases := []struct {
-		shadow      string
-		wantErr     bool
-		username    string
-		id          uint64
-		hash        string
-		description string
-		rest        []string
-	}{
-		{
-			shadow: "1234:$2a$12$apFtWGXKtWBavVy5eo.22Ohs43GudT5IYTqyQkIBX9LpS7YtvKBpa:",
-			id:     1234,
-			hash:   "$2a$12$apFtWGXKtWBavVy5eo.22Ohs43GudT5IYTqyQkIBX9LpS7YtvKBpa",
-		},
-		{
-			shadow:      basicShadow,
-			id:          9999,
-			hash:        "$2a$12$tcv2MrtXgibAJHsSwVfHiOevXBFmiGy0HTNoOB8QzIhEh46iWS1uC",
-			description: "anything more than 5 reps is cardio",
-		},
-		{
-			shadow: anotherShadow,
-			id:     1,
-			hash:   "$2a$12$lINQdYWHOcLKoqhNOr3mNOpZSAu5JOBS2F7T/VDfYn2rvv6qUJehG",
-		},
-		{
-			shadow:  "one:bogushash:",
-			wantErr: true,
-		},
-		{
-			shadow:  "-1:bogushash:",
-			wantErr: true,
-		},
-		{
-			shadow:  "0:tooshort",
-			wantErr: true,
-		},
-		{
-			shadow:  "0:bogushash:invalid base64",
-			wantErr: true,
-		},
-		{
-			shadow:  "1:bogushash::more things",
-			wantErr: true,
-		},
-	}
-	for _, c := range cases {
-		entry, err := EntryFromShadow(c.shadow)
-		if c.wantErr {
-			if err == nil {
-				t.Errorf("EntryFromShadow(%q) == _, nil; want non-nil err", c.shadow)
-			}
-			continue
-		}
-		if err != nil {
-			t.Errorf("EntryFromShadow(%q) == _, %q; want nil err", c.shadow, err)
-		}
-		if c.id != entry.id {
-			t.Errorf("EntryFromShadow(%q).id = %q; want %q", c.shadow, entry.id, c.id)
-		}
-		if c.hash != string(entry.hash) {
-			t.Errorf("EntryFromShadow(%q).password = %q; want %q", c.shadow, entry.hash, c.hash)
-		}
-		if c.description != entry.description {
-			t.Errorf("EntryFromShadow(%q).description = %q; want %q", c.shadow, entry.description, c.description)
-		}
-	}
-}
-
-func TestNewEntry(t *testing.T) {
-	cases := []struct {
-		description string
-		wantErr     bool
-	}{
-		{"one", false},
-		{"the other", false},
-		{string(make([]byte, 1000)), true},
-	}
-	for _, c := range cases {
-		entry, password, err := NewEntry(c.description)
-		if c.wantErr {
-			if err == nil {
-				t.Errorf("NewEntry(%q) = _, _, nil; want non-nil err", c.description)
-			}
-			continue
-		}
-		if err != nil {
-			t.Errorf("NewEntry(%q) = _, _, %q; want nil err", c.description, err)
-		}
-		if entry.id == 0 {
-			// This test has a 1/(2**64) chance of failing! :o
-			t.Errorf("NewEntry(_).id == 0, want nonzero")
-		}
-		if c.description != entry.description {
-			t.Errorf("NewEntry(%q).description = %q, want %q",
-				c.description, entry.description, c.description)
-		}
-		if !passPattern.MatchString(password) {
-			t.Errorf("NewEntry(_) = _, %q, _; wanted to match xxxx-xxxx-xxxx-xxxx", password)
-		}
-		if !entry.Authenticate(password) {
-			t.Errorf("NewEntry(%q).Authenticate(%q) failed",
-				c.description, password)
-		}
-	}
-}
-
-func TestGenPassword(t *testing.T) {
-	p := genPassword()
-	if !passPattern.MatchString(string(p)) {
-		t.Errorf("genPassword() = %q; wanted to match xxxx-xxxx-xxxx-xxxx", p)
-	}
-}
-
-func TestAuthenticate(t *testing.T) {
-	entry, password, err := NewEntry("")
-	if err != nil {
-		t.Errorf("Error building entry")
-	}
-	type testcase struct {
-		password string
-		want     bool
-	}
-
-	cases := []testcase{
-		{password, true},
-		{"not the password", false},
-	}
-	for _, c := range cases {
-		got := entry.Authenticate(c.password)
-		if got != c.want {
-			t.Errorf("entry.Authenticate(%q) == %v, want %v",
-				c.password, got, c.want)
-		}
-	}
-
-	entry, err = EntryFromShadow(basicShadow)
-	if err != nil {
-		t.Errorf("Error loading valid shadow")
-	}
-
-	cases = []testcase{
-		{"nocardio", true},
-		{"not the password", false},
-	}
-	for _, c := range cases {
-		got := entry.Authenticate(c.password)
-		if got != c.want {
-			t.Errorf("entry.Authenticate(%q) == %v, want %v",
-				c.password, got, c.want)
-		}
-	}
-}
-
-func TestEncode(t *testing.T) {
-	// Crafted entry
-	shadowed, err := EntryFromShadow(basicShadow)
-	if err != nil {
-		t.Errorf("Error loading valid shadow")
-	}
-	anotherShadowed, err := EntryFromShadow(anotherShadow)
-	if err != nil {
-		t.Errorf("Error loading valid shadow")
-	}
-	cases := []struct {
-		entry *Entry
-		want  string
-	}{
-		{
-			&Entry{
-				id:          6775,
-				hash:        "bogushash",
-				description: "something",
-			},
-			"6775:bogushash:c29tZXRoaW5n",
-		},
-		{shadowed, basicShadow},
-		{anotherShadowed, anotherShadow},
-	}
-	for _, c := range cases {
-		got := string(c.entry.Encode())
-		if got != c.want {
-			t.Errorf("entry.Encode() = %q, want %q", got, c.want)
-		}
-	}
-}
--- a/cmds/multipass-add/add.go	Fri Oct 30 00:18:13 2015 -0400
+++ b/cmds/multipass-add/add.go	Sun Nov 01 12:16:51 2015 -0500
@@ -7,7 +7,6 @@
 	"fmt"
 	"os"
 
-	"pfish.zone/go/multipass/auth"
 	"pfish.zone/go/multipass/file"
 )
 
@@ -25,7 +24,7 @@
 		os.Exit(1)
 	}
 	text = text[:len(text)-1]
-	entry, password, err := auth.NewEntry(text)
+	entry, password, err := file.NewEntry(text)
 	if err != nil {
 		fmt.Println(err.Error())
 		os.Exit(1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/file/auth.go	Sun Nov 01 12:16:51 2015 -0500
@@ -0,0 +1,139 @@
+package file
+
+import (
+	"crypto/rand"
+	"encoding/base64"
+	"errors"
+	"math/big"
+	"strconv"
+	"strings"
+
+	"golang.org/x/crypto/bcrypt"
+)
+
+const (
+	// default cost stolen from python bcrypt
+	bcryptCost = 12
+	// we only generate passwords from lowercases for non-ambiguity
+	lowercases = "abcdefghijklmnopqrstuvwxyz"
+	template   = "????-????-????-????"
+)
+
+var (
+	lowercaseLen *big.Int = big.NewInt(int64(len(lowercases)))
+	maxID                 = big.NewInt(0)
+)
+
+var (
+	WrongLengthError     error = errors.New("multipass/auth: password entry must have 3 fields")
+	BadIDError                 = errors.New("multipass/auth: ID field invalid")
+	Base64Error                = errors.New("multipass/auth: can't decode base64 data")
+	LongDescriptionError       = errors.New("multipass/auth: description must be less than 255 bytes")
+)
+
+func init() {
+	one := big.NewInt(1)
+	maxID.Lsh(one, 64)
+}
+
+// Entry represents a single entry in the a multipass file.
+type Entry struct {
+	id          uint64
+	hash        string
+	description string
+}
+
+// EntryFromShadow creates a new entry from a line in a multipass shadow file.
+// The line should not end in a newline.
+func EntryFromShadow(shadow string) (*Entry, error) {
+	segments := strings.Split(shadow, ":")
+	if len(segments) != 3 {
+		return nil, WrongLengthError
+	}
+	entry := new(Entry)
+	id, err := strconv.ParseUint(segments[0], 10, 64)
+	if err != nil {
+		return nil, BadIDError
+	}
+	entry.id = id
+	entry.hash = segments[1]
+	description, err := base64.StdEncoding.DecodeString(segments[2])
+	if err != nil {
+		return nil, Base64Error
+	}
+	entry.description = string(description)
+	return entry, nil
+}
+
+// NewEntry creates an Entry for the given description.
+// It returns the Entry itself and a generated password.
+func NewEntry(description string) (entry *Entry, password string, err error) {
+	if len(description) > 255 {
+		return nil, "", LongDescriptionError
+	}
+	passBytes := genPassword()
+	password = string(passBytes)
+	hashBytes, err := bcrypt.GenerateFromPassword(passBytes, bcryptCost)
+	if err != nil {
+		// This is very unexpected.
+		return nil, "", err
+	}
+	e := new(Entry)
+	e.id = newID()
+	e.hash = string(hashBytes)
+	e.description = description
+	return e, password, nil
+}
+
+// ID is a unique 64-bit integer which identifies the entry.
+func (e *Entry) ID() uint64 {
+	return e.id
+}
+
+// Description is the user's description of their password.
+func (e *Entry) Description() string {
+	return e.description
+}
+
+// Authenticate tests whether the password is correct.
+func (e *Entry) Authenticate(password string) bool {
+	err := bcrypt.CompareHashAndPassword([]byte(e.hash), []byte(password))
+	return err == nil
+}
+
+// Encode encodes this Entry to a bytestring for writing to a multipass shadow file.
+func (e *Entry) Encode() string {
+	segments := []string{
+		strconv.FormatUint(e.id, 10),
+		e.hash,
+		base64.StdEncoding.EncodeToString([]byte(e.description)),
+	}
+	return strings.Join(segments, ":")
+}
+
+func genPassword() []byte {
+	password := []byte(template)
+	for i, chr := range password {
+		if chr == '?' {
+			password[i] = randChr()
+		}
+	}
+	return password
+}
+
+func randChr() byte {
+	bigIdx, err := rand.Int(rand.Reader, lowercaseLen)
+	if err != nil {
+		panic("multipass/auth: can't get a random number")
+	}
+	idx := bigIdx.Int64()
+	return byte(lowercases[idx])
+}
+
+func newID() uint64 {
+	bigID, err := rand.Int(rand.Reader, maxID)
+	if err != nil {
+		panic("multipass/auth: can't get a random number")
+	}
+	return bigID.Uint64()
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/file/auth_test.go	Sun Nov 01 12:16:51 2015 -0500
@@ -0,0 +1,199 @@
+package file
+
+import (
+	"regexp"
+	"testing"
+)
+
+var passPattern *regexp.Regexp = regexp.MustCompile(`^(?:[a-z]{4}-){3}[a-z]{4}$`)
+
+const basicShadow = "9999:$2a$12$tcv2MrtXgibAJHsSwVfHiOevXBFmiGy0HTNoOB8QzIhEh46iWS1uC:YW55dGhpbmcgbW9yZSB0aGFuIDUgcmVwcyBpcyBjYXJkaW8="
+const anotherShadow = "1:$2a$12$lINQdYWHOcLKoqhNOr3mNOpZSAu5JOBS2F7T/VDfYn2rvv6qUJehG:"
+
+func TestEntryFromShadow(t *testing.T) {
+	cases := []struct {
+		shadow      string
+		wantErr     bool
+		username    string
+		id          uint64
+		hash        string
+		description string
+		rest        []string
+	}{
+		{
+			shadow: "1234:$2a$12$apFtWGXKtWBavVy5eo.22Ohs43GudT5IYTqyQkIBX9LpS7YtvKBpa:",
+			id:     1234,
+			hash:   "$2a$12$apFtWGXKtWBavVy5eo.22Ohs43GudT5IYTqyQkIBX9LpS7YtvKBpa",
+		},
+		{
+			shadow:      basicShadow,
+			id:          9999,
+			hash:        "$2a$12$tcv2MrtXgibAJHsSwVfHiOevXBFmiGy0HTNoOB8QzIhEh46iWS1uC",
+			description: "anything more than 5 reps is cardio",
+		},
+		{
+			shadow: anotherShadow,
+			id:     1,
+			hash:   "$2a$12$lINQdYWHOcLKoqhNOr3mNOpZSAu5JOBS2F7T/VDfYn2rvv6qUJehG",
+		},
+		{
+			shadow:  "one:bogushash:",
+			wantErr: true,
+		},
+		{
+			shadow:  "-1:bogushash:",
+			wantErr: true,
+		},
+		{
+			shadow:  "0:tooshort",
+			wantErr: true,
+		},
+		{
+			shadow:  "0:bogushash:invalid base64",
+			wantErr: true,
+		},
+		{
+			shadow:  "1:bogushash::more things",
+			wantErr: true,
+		},
+	}
+	for _, c := range cases {
+		entry, err := EntryFromShadow(c.shadow)
+		if c.wantErr {
+			if err == nil {
+				t.Errorf("EntryFromShadow(%q) == _, nil; want non-nil err", c.shadow)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("EntryFromShadow(%q) == _, %q; want nil err", c.shadow, err)
+		}
+		if c.id != entry.id {
+			t.Errorf("EntryFromShadow(%q).id = %q; want %q", c.shadow, entry.id, c.id)
+		}
+		if c.hash != string(entry.hash) {
+			t.Errorf("EntryFromShadow(%q).password = %q; want %q", c.shadow, entry.hash, c.hash)
+		}
+		if c.description != entry.description {
+			t.Errorf("EntryFromShadow(%q).description = %q; want %q", c.shadow, entry.description, c.description)
+		}
+	}
+}
+
+func TestNewEntry(t *testing.T) {
+	cases := []struct {
+		description string
+		wantErr     bool
+	}{
+		{"one", false},
+		{"the other", false},
+		{string(make([]byte, 1000)), true},
+	}
+	for _, c := range cases {
+		entry, password, err := NewEntry(c.description)
+		if c.wantErr {
+			if err == nil {
+				t.Errorf("NewEntry(%q) = _, _, nil; want non-nil err", c.description)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("NewEntry(%q) = _, _, %q; want nil err", c.description, err)
+		}
+		if entry.id == 0 {
+			// This test has a 1/(2**64) chance of failing! :o
+			t.Errorf("NewEntry(_).id == 0, want nonzero")
+		}
+		if c.description != entry.description {
+			t.Errorf("NewEntry(%q).description = %q, want %q",
+				c.description, entry.description, c.description)
+		}
+		if !passPattern.MatchString(password) {
+			t.Errorf("NewEntry(_) = _, %q, _; wanted to match xxxx-xxxx-xxxx-xxxx", password)
+		}
+		if !entry.Authenticate(password) {
+			t.Errorf("NewEntry(%q).Authenticate(%q) failed",
+				c.description, password)
+		}
+	}
+}
+
+func TestGenPassword(t *testing.T) {
+	p := genPassword()
+	if !passPattern.MatchString(string(p)) {
+		t.Errorf("genPassword() = %q; wanted to match xxxx-xxxx-xxxx-xxxx", p)
+	}
+}
+
+func TestAuthenticate(t *testing.T) {
+	entry, password, err := NewEntry("")
+	if err != nil {
+		t.Errorf("Error building entry")
+	}
+	type testcase struct {
+		password string
+		want     bool
+	}
+
+	cases := []testcase{
+		{password, true},
+		{"not the password", false},
+	}
+	for _, c := range cases {
+		got := entry.Authenticate(c.password)
+		if got != c.want {
+			t.Errorf("entry.Authenticate(%q) == %v, want %v",
+				c.password, got, c.want)
+		}
+	}
+
+	entry, err = EntryFromShadow(basicShadow)
+	if err != nil {
+		t.Errorf("Error loading valid shadow")
+	}
+
+	cases = []testcase{
+		{"nocardio", true},
+		{"not the password", false},
+	}
+	for _, c := range cases {
+		got := entry.Authenticate(c.password)
+		if got != c.want {
+			t.Errorf("entry.Authenticate(%q) == %v, want %v",
+				c.password, got, c.want)
+		}
+	}
+}
+
+func TestEncode(t *testing.T) {
+	// Crafted entry
+	shadowed, err := EntryFromShadow(basicShadow)
+	if err != nil {
+		t.Errorf("Error loading valid shadow")
+	}
+	anotherShadowed, err := EntryFromShadow(anotherShadow)
+	if err != nil {
+		t.Errorf("Error loading valid shadow")
+	}
+	cases := []struct {
+		entry *Entry
+		want  string
+	}{
+		{
+			&Entry{
+				id:          6775,
+				hash:        "bogushash",
+				description: "something",
+			},
+			"6775:bogushash:c29tZXRoaW5n",
+		},
+		{shadowed, basicShadow},
+		{anotherShadowed, anotherShadow},
+	}
+	for _, c := range cases {
+		got := string(c.entry.Encode())
+		if got != c.want {
+			t.Errorf("entry.Encode() = %q, want %q", got, c.want)
+		}
+	}
+}
--- a/file/file.go	Fri Oct 30 00:18:13 2015 -0400
+++ b/file/file.go	Sun Nov 01 12:16:51 2015 -0500
@@ -2,7 +2,7 @@
 //
 // A password file contains multiple passwords for a single user.
 // It starts with a banner that indicates the version of the file,
-// then has entries in the format specified by auth.Entry.
+// then has entries in the format specified by Entry.
 
 package file
 
@@ -14,7 +14,6 @@
 	"time"
 
 	"golang.org/x/sys/unix"
-	"pfish.zone/go/multipass/auth"
 )
 
 const (
@@ -73,7 +72,7 @@
 	defer file.Close()
 
 	for scanner.Scan() {
-		entry, err := auth.EntryFromShadow(scanner.Text())
+		entry, err := EntryFromShadow(scanner.Text())
 		// Skip invalid lines.
 		if err != nil {
 			continue
@@ -85,7 +84,7 @@
 	return false, nil
 }
 
-func (f *ShadowFile) Add(entry *auth.Entry) error {
+func (f *ShadowFile) Add(entry *Entry) error {
 	handle, err := f.openWrite()
 	if err != nil {
 		return err
@@ -107,17 +106,17 @@
 	return handle.finalize()
 }
 
-func (f *ShadowFile) AllEntries() ([]*auth.Entry, error) {
+func (f *ShadowFile) AllEntries() ([]*Entry, error) {
 	file, scanner, err := f.open()
 	if err != nil {
 		return nil, err
 	}
 	defer file.Close()
 
-	var entries []*auth.Entry
+	var entries []*Entry
 
 	for scanner.Scan() {
-		entry, err := auth.EntryFromShadow(scanner.Text())
+		entry, err := EntryFromShadow(scanner.Text())
 		// Skip invalid lines.
 		if err != nil {
 			continue
@@ -242,11 +241,11 @@
 	return h.scanner != nil && h.scanner.Scan()
 }
 
-func (h *writeHandle) entry() (*auth.Entry, error) {
-	return auth.EntryFromShadow(h.scanner.Text())
+func (h *writeHandle) entry() (*Entry, error) {
+	return EntryFromShadow(h.scanner.Text())
 }
 
-func (h *writeHandle) write(entry *auth.Entry) error {
+func (h *writeHandle) write(entry *Entry) error {
 	if _, err := h.writer.WriteString(entry.Encode()); err != nil {
 		return err
 	}
--- a/file/file_test.go	Fri Oct 30 00:18:13 2015 -0400
+++ b/file/file_test.go	Sun Nov 01 12:16:51 2015 -0500
@@ -7,8 +7,6 @@
 	"path"
 	"testing"
 	"time"
-
-	"pfish.zone/go/multipass/auth"
 )
 
 var tempdir string
@@ -21,17 +19,17 @@
 )
 
 var (
-	aSpooky   *auth.Entry
-	aWhatever *auth.Entry
+	aSpooky   *Entry
+	aWhatever *Entry
 )
 
 func init() {
-	s, err := auth.EntryFromShadow(shadowSpooky)
+	s, err := EntryFromShadow(shadowSpooky)
 	if err != nil {
 		panic(err.Error())
 	}
 	aSpooky = s
-	w, err := auth.EntryFromShadow(shadowWhatever)
+	w, err := EntryFromShadow(shadowWhatever)
 	if err != nil {
 		panic(err.Error())
 	}
@@ -188,15 +186,15 @@
 	}
 }
 
-func TestAuthenticate(t *testing.T) {
+func TestAuthenticateFile(t *testing.T) {
 	f := mktest(t, "auth")
 	defer f.cleanup()
 	s := New(f.filepath())
-	eA, passA, err := auth.NewEntry("a")
+	eA, passA, err := NewEntry("a")
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
-	eB, passB, err := auth.NewEntry("b")
+	eB, passB, err := NewEntry("b")
 	if err != nil {
 		t.Fatalf(err.Error())
 	}