tFirst commit - safe-go - Unnamed repository; edit this file 'description' to name the repository.
(HTM) git clone git://git.z3bra.org/safe-go.git
(DIR) Log
(DIR) Files
(DIR) Refs
---
(DIR) commit e42736b586fc5a5eef31959a7800af18e9e44b6b
(HTM) Author: Willy Goiffon <contact@z3bra.org>
Date: Mon, 12 Sep 2022 14:42:07 +0100
First commit
Diffstat:
A config.mk | 5 +++++
A go.mod | 10 ++++++++++
A go.sum | 12 ++++++++++++
A mkfile | 24 ++++++++++++++++++++++++
A safe.1 | 106 ++++++++++++++++++++++++++++++
A safe.go | 185 ++++++++++++++++++++++++++++++
6 files changed, 342 insertions(+), 0 deletions(-)
---
(DIR) diff --git a/config.mk b/config.mk
t@@ -0,0 +1,5 @@
+GO = go
+GOOS = `{uname -s | tr A-Z a-z}
+
+PREFIX = /usr/local
+MANDIR = ${PREFIX}/man
(DIR) diff --git a/go.mod b/go.mod
t@@ -0,0 +1,10 @@
+module z3bra.org/safe
+
+go 1.19
+
+require (
+ github.com/netfoundry/secretstream v0.1.2
+ golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
+)
+
+require golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
(DIR) diff --git a/go.sum b/go.sum
t@@ -0,0 +1,12 @@
+github.com/netfoundry/secretstream v0.1.2 h1:NgqrYytDnjKbOfWI29TT0SJM+RwB3yf9MIkJVJaU+J0=
+github.com/netfoundry/secretstream v0.1.2/go.mod h1:uasYkYSp0MmNSlKOWJ2sVzxPms8e58TS4ENq4yro86k=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
(DIR) diff --git a/mkfile b/mkfile
t@@ -0,0 +1,24 @@
+<config.mk
+
+all:V: safe
+
+%: %.go
+ $GO build -o $stem $stem.go
+
+clean:V:
+ rm -f safe
+
+install:V: safe
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp safe ${DESTDIR}${PREFIX}/bin/safe
+ chmod 755 ${DESTDIR}${PREFIX}/bin/safe
+ mkdir -p ${DESTDIR}${MANDIR}/man1
+ cp safe.1 ${DESTDIR}${MANDIR}/man1/safe.1
+ chmod 644 ${DESTDIR}${MANDIR}/man1/safe.1
+ mkdir -p ${DESTDIR}${MANDIR}/man5
+ cp safe.5 ${DESTDIR}${MANDIR}/man5/safe.5
+ chmod 644 ${DESTDIR}${MANDIR}/man5/safe.5
+
+uninstall:V:
+ rm ${DESTDIR}${PREFIX}/bin/safe
+ rm ${DESTDIR}${MANDIR}/man1/safe.1
(DIR) diff --git a/safe.1 b/safe.1
t@@ -0,0 +1,106 @@
+.Dd 2019-02-20
+.Dt SAFE 1
+.Os POSIX.1-2017
+.Sh NAME
+.Nm safe
+.Nd digital safe for your secrets
+.Sh SYNOPSIS
+.Nm
+.Op Fl bhr
+.Op Fl p Ar prompt
+.Op Fl s Ar safe
+.Op Oo Fl af Oc Ar secret
+.Sh DESCRIPTION
+.Nm
+stores secrets (files) encrypted on your disk, and lets you retrieve them,
+given that you have the right password.
+.El
+.Bl -tag -width Ds
+.It Ar secret
+Decrypt file
+.Ar secret
+from your safe to stdout.
+.It Fl a Ar secret
+Encrypt stdin to your safe as
+.Ar secret .
+Use
+.Fl f
+to overwrite an existing secret.
+.It Fl b
+Batch mode. Reads master password from stdin.
+.It Fl f
+Force writing to
+.Ar secret
+if it exists.
+Implies
+.Fl a .
+.It Fl h
+Print a quick usage text.
+.It Fl k
+Prompt user for password using an external program (see: SAFE_ASKPASS).
+.It Fl p Ar prompt
+Prompt user for password using text
+.Ar prompt .
+(default: "password:")
+.It Fl r
+Remember the password. The variable
+.Ev SAFE_SOCK
+must be set and point to the UNIX-domain socket bound by a running agent
+(see AGENT).
+.It Fl s Ar safe
+Set the path to your safe as
+.Ar safe .
+(default: .secrets)
+.Sh AGENT
+When the agent is started,
+.Nm
+can retrieve the key from it rather than prompting you for a password.
+.Nm
+will try to read the key from the agent whenever the
+.Ev SAFE_SOCK
+variable is set in the environment.
+.Pp
+When the agent is first started, you can push the key to it using the
+.Fl p
+flag.
+.Sh MASTER PASSWORD
+When you add your first secret to the safe, a
+.Ar master
+entry will be created automatically. This entry stores your master
+password, and is used to check that you typed the master password
+correctly on the next calls.
+.Pp
+Do not delete this entry as it could lead to a corrupted safe.
+.Sh EXAMPLES
+Store a secret in your safe
+.Bd -literal
+ $ safe -a secret/file < kitten.gif
+.Ed
+.Pp
+List all secrets in $SAFE_DIR (choose your weapon)
+.Bd -literal
+ $ tree --noreport $SAFE_DIR
+ $ find $SAFE_DIR -type f
+ $ ls -R $SAFE_DIR
+ $ tar -C $SAFE_DIR -v -f /dev/null -c . | cut -d / -f 2-
+.Ed
+.Pp
+Retrieve a secret from your safe
+.Bd -literal
+ $ safe secret/file > kitten.gif
+ password:
+.Ed
+.Sh ENVIRONMENT
+.Bl -tag -width "SAFE_SOCK"
+.It Ev SAFE_DIR
+Defines the location of your safe (default: .secrets)
+.It Ev SAFE_SOCK
+Path to the UNIX-domain socket used to communicate with the agent.
+.It Ev SAFE_ASKPASS
+If no TTY is available, the program specified by this variable will be
+used to read the master password (default: ssh-askpass)
+.Sh SEE ALSO
+.Xr safe-agent 1 ,
+.Xr safe-store 5
+.Sh AUTHORS
+.An Willy Goiffon Aq Mt dev@z3bra.org
(DIR) diff --git a/safe.go b/safe.go
t@@ -0,0 +1,185 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+
+ "golang.org/x/crypto/argon2"
+ "github.com/netfoundry/secretstream"
+)
+
+type Safe struct {
+ key []byte
+ salt []byte
+}
+
+const (
+ BUFSIZ = 8192
+
+ // default key derivation values in libsodium
+ argon2id_time_cost = 2
+ argon2id_memory_cost = 67108864 / 1024
+ argon2id_threads = 1
+ argon2id_salt_len = 16
+ xchacha20poly1305_key_len = 32
+ xchacha20poly1305_iv_len = 24
+)
+
+func usage() {
+ fmt.Printf("usage: %s [-hr] [-s safe] [[-af] entry]\n", os.Args[0])
+ os.Exit(2)
+}
+
+func readsalt(secret string, safe *Safe) {
+ f, err := os.Open(secret)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+
+ safe.salt = make([]byte, argon2id_salt_len)
+ s := io.NewSectionReader(f, 0, argon2id_salt_len)
+
+ _, err = s.Read(safe.salt)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func deriv(pw []byte, s *Safe) {
+ s.key = make([]byte, xchacha20poly1305_key_len)
+ s.key = argon2.IDKey(pw, s.salt,
+ argon2id_time_cost,
+ argon2id_memory_cost,
+ argon2id_threads,
+ xchacha20poly1305_key_len)
+}
+
+func encrypt(secret string, safe *Safe) {
+ var tag byte
+ buf := make([]byte, BUFSIZ)
+
+ enc, nonce, err := secretstream.NewEncryptor(safe.key)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ f, err := os.OpenFile(secret, os.O_CREATE|os.O_WRONLY, 0600)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+
+ f.Write(safe.salt)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ f.Write(nonce)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ loop := 1
+ tag = secretstream.TagMessage
+ for loop > 0 {
+ n, err := os.Stdin.Read(buf)
+ if err == io.EOF || n < len(buf) {
+ tag = secretstream.TagFinal
+ loop = 0
+ } else if err != nil {
+ log.Fatal(err)
+ }
+ cipher, err := enc.Push(buf[:n], tag)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ f.Write(cipher)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
+func decrypt(secret string, safe *Safe) {
+ buf := make([]byte, BUFSIZ + secretstream.StreamABytes)
+ header := make([]byte, secretstream.StreamHeaderBytes)
+
+ f, err := os.Open(secret)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+
+ f.Seek(argon2id_salt_len, os.SEEK_SET)
+ _, err = f.Read(header)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ dec, err := secretstream.NewDecryptor(safe.key, header)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ loop := 1
+ for loop > 0 {
+ n, err := f.Read(buf)
+ if err != nil && err != io.EOF {
+ log.Fatal(err)
+ }
+
+ plain, tag, err := dec.Pull(buf[:n])
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if tag == secretstream.TagFinal {
+ loop = 0
+ }
+ os.Stdout.Write(plain)
+ }
+}
+
+func main() {
+ var safe Safe
+
+ var store string
+ var aflag, fflag, rflag bool
+
+ flag.StringVar(&store, "s", ".secrets", "Set safe store location")
+ flag.BoolVar(&aflag, "a", false, "Add a secret to the safe")
+ flag.BoolVar(&fflag, "f", false, "Force writing secret (implies -a)")
+ flag.BoolVar(&rflag, "r", false, "Save password in a running agent")
+ flag.Usage = usage
+ flag.Parse()
+
+ args := flag.Args()
+ if len(args) < 1 {
+ usage()
+ }
+
+ err := os.Chdir(store)
+ if err != nil {
+ log.Fatal(err)
+
+ }
+
+ //fmt.Printf("password:")
+ //password, _ := bufio.NewReader(os.Stdin).ReadString('\n')
+
+ // read salt from master entry
+ readsalt("master", &safe)
+
+ deriv([]byte("azerty"), &safe)
+
+ if !aflag {
+ decrypt(args[0], &safe)
+ } else {
+ encrypt(args[0], &safe)
+ }
+}