From 8084465e13ee600c733b709059dfbaa753603674 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Fri, 4 Aug 2017 23:23:51 +0200 Subject: Rename file to b64file --- b64file/file.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++ b64file/file_test.go | 31 +++++++++++++++++ b64file/names.go | 72 ++++++++++++++++++++++++++++++++++++++ b64file/names_test.go | 65 ++++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 b64file/file.go create mode 100644 b64file/file_test.go create mode 100644 b64file/names.go create mode 100644 b64file/names_test.go (limited to 'b64file') diff --git a/b64file/file.go b/b64file/file.go new file mode 100644 index 0000000..ebca8fe --- /dev/null +++ b/b64file/file.go @@ -0,0 +1,96 @@ +// Package file implements signify file format +package b64file + +import ( + "bufio" + "bytes" + "encoding" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "strings" +) + +const ( + ModeSec os.FileMode = 0600 + ModePub os.FileMode = 0644 + ModeSig os.FileMode = 0644 + untrusted = "untrusted comment: " +) + +// Original Error: "invalid comment in %s; must start with 'untrusted comment: '" +var ErrUntrusted = errors.New("comment must start with 'untrusted comment: '") + +func DecodeFile(fname string, u encoding.BinaryUnmarshaler) (string, []byte, error) { + fd, err := os.Open(fname) + if err != nil { + return "", nil, err + } + defer fd.Close() + return Decode(fd, u) +} + +func DecodeString(data string, u encoding.BinaryUnmarshaler) (string, []byte, error) { + r := strings.NewReader(data) + return Decode(r, u) +} + +func DecodeBytes(data []byte, u encoding.BinaryUnmarshaler) (string, []byte, error) { + r := bytes.NewReader(data) + return Decode(r, u) +} + +func Decode(r io.Reader, u encoding.BinaryUnmarshaler) (string, []byte, error) { + buf := bufio.NewReader(r) + + comment, err := buf.ReadString('\n') + if err != nil { + return "", nil, err + } + if !strings.HasPrefix(comment, untrusted) { + return "", nil, ErrUntrusted + } + comment = strings.TrimSpace(comment[len(untrusted):]) + + raw, err := buf.ReadString('\n') + if err != nil { + return "", nil, err + } + b, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return "", nil, err + } + if err := u.UnmarshalBinary(b); err != nil { + return "", nil, err + } + + message, err := ioutil.ReadAll(buf) + if err != nil { + return "", nil, err + } + + return comment, message, nil +} + +func EncodeFile(fname string, perm os.FileMode, u encoding.BinaryMarshaler, comment string, msg []byte) error { + fd, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm) + if err != nil { + return err + } + defer fd.Close() + return Encode(fd, u, comment, msg) +} + +func Encode(w io.Writer, u encoding.BinaryMarshaler, comment string, msg []byte) error { + raw, err := u.MarshalBinary() + if err != nil { + return err + } + fmt.Fprintf(w, "%s%s\n", untrusted, comment) + fmt.Fprintf(w, "%s\n", base64.StdEncoding.EncodeToString(raw)) + w.Write(msg) + return nil +} diff --git a/b64file/file_test.go b/b64file/file_test.go new file mode 100644 index 0000000..ac95c38 --- /dev/null +++ b/b64file/file_test.go @@ -0,0 +1,31 @@ +package b64file + +import ( + "bytes" + "testing" +) + +type moc struct{} + +func (moc) MarshalBinary() ([]byte, error) { return nil, nil } +func (moc) UnmarshalBinary(_ []byte) error { return nil } + +func TestSig(t *testing.T) { + comment := "untrusted comment: comment" + message := []byte{'t', 'e', 's', 't'} + buf := new(bytes.Buffer) + err := Encode(buf, moc{}, comment, message) + if err != nil { + t.Error(err) + } + comment2, message2, err := Decode(buf, moc{}) + if err != nil { + t.Error(err) + } + if comment != comment2 { + t.Errorf("got %v, want %v", comment2, comment) + } + if !bytes.Equal(message, message2) { + t.Errorf("got %v, want %v", message2, message) + } +} diff --git a/b64file/names.go b/b64file/names.go new file mode 100644 index 0000000..83d73d2 --- /dev/null +++ b/b64file/names.go @@ -0,0 +1,72 @@ +package b64file + +import ( + "errors" + "os" + "path" + "path/filepath" + "strings" +) + +var ErrNames = errors.New("please use naming scheme of keyname.pub and keyname.sec") + +const ( + extPub = ".pub" + extSec = ".sec" + extSig = ".sig" + verifyWith = "verify with " +) + +func splitNameExt(fname string) (string, string) { + _, file := path.Split(fname) + ext := filepath.Ext(file) + return file[:len(file)-len(ext)], ext +} + +func ValidateNames(pubFile, secFile string) error { + pubName, pubExt := splitNameExt(pubFile) + secName, secExt := splitNameExt(secFile) + if pubExt != extPub || secExt != extSec || pubName != secName { + return ErrNames + } + return nil +} + +func PubName(secFile string) string { + ext := filepath.Ext(secFile) + return filepath.Base(secFile[:len(ext)-1] + extPub) +} + +func SigName(msgFile string) string { + return msgFile + extSig +} + +func PubFile(comment string) string { + if strings.HasPrefix(comment, verifyWith) { + file := comment[len(verifyWith):] + if strings.HasSuffix(file, extPub) { + return FindFile(file) + } + } + return "" +} + +func VerifyWith(secFile string) string { + return verifyWith + PubName(secFile) +} + +var safePath = []string{ + "/etc/signify", + "$HOME/.signify", +} + +// FindFile locates keys in safe path. Falls back to current dir. +func FindFile(fname string) string { + for _, v := range safePath { + p := path.Join(os.Expand(v, os.Getenv), fname) + if _, err := os.Stat(p); err == nil { + return p + } + } + return fname +} diff --git a/b64file/names_test.go b/b64file/names_test.go new file mode 100644 index 0000000..0acabed --- /dev/null +++ b/b64file/names_test.go @@ -0,0 +1,65 @@ +package b64file + +import "testing" + +func TestCheckNames(t *testing.T) { + testCases := []struct { + pub, sec string + err error + }{ + {"key.pub", "key.sec", nil}, + {"testdata/key.pub", "key.sec", nil}, + {"key.pub", "testdata/key.sec", nil}, + {"foo.pub", "bar.sec", ErrNames}, + {"key.foo", "key.bar", ErrNames}, + } + for _, tc := range testCases { + t.Run(tc.pub+"+"+tc.sec, func(t *testing.T) { + err := ValidateNames(tc.pub, tc.sec) + if err != tc.err { + t.Errorf("got %v, want %v", err, tc.err) + } + }) + } +} + +func TestVerify(t *testing.T) { + testCases := []struct { + comment string + file string + }{ + {"verify with key.pub", "key.pub"}, + {"verify with s p a c e s.pub", "s p a c e s.pub"}, + {"verify with key.sec", ""}, + {"whatever", ""}, + } + for _, tc := range testCases { + t.Run(tc.comment, func(t *testing.T) { + file := PubFile(tc.comment) + if file != tc.file { + t.Errorf("got %v, want %v", file, tc.file) + } + }) + } +} + +func TestSplit(t *testing.T) { + testCases := []struct { + fname, name, ext string + }{ + {"testkey.pub", "testkey", ".pub"}, + {"testkey", "testkey", ""}, + {".pub", "", ".pub"}, + {".testkey.pub", ".testkey", ".pub"}, + {"", "", ""}, + {"path/key.pub", "key", ".pub"}, + } + for _, tc := range testCases { + t.Run(tc.fname, func(t *testing.T) { + name, ext := splitNameExt(tc.fname) + if name != tc.name || ext != tc.ext { + t.Errorf("got %q %q, want %q %q", name, tc.name, ext, tc.ext) + } + }) + } +} -- cgit v1.2.3