tFirst commit - safe-go - Unnamed repository; edit this file 'description' to name the repository.
  HTML git clone git://git.z3bra.org/safe-go.git
   DIR Log
   DIR Files
   DIR Refs
       ---
   DIR commit e42736b586fc5a5eef31959a7800af18e9e44b6b
  HTML 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)
       +        }
       +}