aboutsummaryrefslogtreecommitdiff
path: root/b64file
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2017-08-04 23:23:51 +0200
committerDimitri Sokolyuk <demon@dim13.org>2017-08-04 23:23:51 +0200
commit8084465e13ee600c733b709059dfbaa753603674 (patch)
tree40c070baf9befbc42208d301933859493bb6cbaf /b64file
parentb1d650c6542a8573ecfb204a037df12731272822 (diff)
Rename file to b64file
Diffstat (limited to 'b64file')
-rw-r--r--b64file/file.go96
-rw-r--r--b64file/file_test.go31
-rw-r--r--b64file/names.go72
-rw-r--r--b64file/names_test.go65
4 files changed, 264 insertions, 0 deletions
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)
+ }
+ })
+ }
+}