wip: debugging issue

This commit is contained in:
Suyono 2023-07-23 22:00:52 +10:00
parent 7b29d8f6cc
commit 6c4b48873b
10 changed files with 373 additions and 46 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/.idea/
/simple-privacy-tool
/simple-privacy-tool
/spt

View File

@ -1,2 +1,38 @@
# simple-privacy-tool
Simple Privacy Tool is a simple tool to encrypt and decrypt files. It uses the symmetric algorithm XChaCha-Poly1305 and
Argon2id for the key derivation function.
Since this tool uses a symmetric algorithm, the level of privacy hinges solely on the password's strength. So, make sure
to choose your password carefully.
### Build
```shell
go build
```
### Usage
#### Encrypt
Encrypting `plainfile` to `cryptedfile`
```shell
simple-privacy-tool encrypt plainfile cryptedfile
```
#### Decrypt
Decrypting `cryptedfile` back to `plainfile`
```shell
simple-privacy-tool decrypt cryptedfile plainfile
```
#### Using STDIN/STDOUT
`simple-privacy-tool` can operate on `STDIN` or `STDOUT`. Just replace the file path with `-`
```shell
tar -zcf - dir | simple-privacy-tool encrypt - - | another-command
```
Special usage, just omit both file paths to use `STDIN` and `STDOUT`.
```shell
tar -zcf - dir | simple-privacy-tool encrypt | another-command
```

113
cmd/spt/decrypt.go Normal file
View File

@ -0,0 +1,113 @@
package spt
import (
"fmt"
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
"io"
"os"
)
type decryptApp struct {
cApp
r *privacy.Reader
}
type stdoutWrapper struct {
file *os.File
}
func newDecryptApp(a *cApp) *decryptApp {
return &decryptApp{
cApp: *a,
}
}
func (d *decryptApp) GetPassphrase() (err error) {
var (
passphrase string
)
if passphrase, err = d.term.ReadPassword("input passphrase: "); err != nil {
return
}
d.passphrase = passphrase
return
}
func (d *decryptApp) ProcessFiles() (err error) {
d.r = privacy.NewPrivacyReader(d.srcFile)
if err = d.r.ReadMagic(); err != nil {
return
}
if err = d.r.GenerateKey(d.passphrase); err != nil {
return
}
if d.dstPath == "-" || d.srcPath == "-" {
w := stdoutWrapper{
file: d.dstFile,
}
if _, err = io.Copy(w, d.r); err != nil {
return
}
} else {
if _, err = io.Copy(d.dstFile, d.r); err != nil {
return
}
}
if err = d.dstFile.Close(); err != nil {
return
}
if err = d.srcFile.Close(); err != nil {
return
}
return
}
func (sw stdoutWrapper) ReadFrom(reader io.Reader) (n int64, err error) {
var (
nr int
rErr error
)
buf := make([]byte, 32768)
for {
if nr, err = reader.Read(buf); err != nil {
break
}
n += int64(nr)
if _, err = sw.file.Write(buf[:nr]); err != nil {
return n, fmt.Errorf("readfrom internal write: %w", err)
}
}
rErr = err
if nr > 0 {
n += int64(nr)
if nr > 32768 {
return n, fmt.Errorf("last piece length %d: %w", nr, err)
}
if _, err = sw.file.Write(buf[:nr]); err != nil {
return n, fmt.Errorf("readfrom internal write: %w", err)
}
}
if rErr == io.EOF || rErr == nil {
err = nil
} else {
err = rErr
}
return
}
func (sw stdoutWrapper) Write(b []byte) (n int, err error) {
return sw.file.Write(b)
}

65
cmd/spt/encrypt.go Normal file
View File

@ -0,0 +1,65 @@
package spt
import (
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
"io"
)
type encryptApp struct {
cApp
wc *privacy.WriteCloser
}
func newEncryptApp(a *cApp) *encryptApp {
return &encryptApp{
cApp: *a,
}
}
func (e *encryptApp) GetPassphrase() (err error) {
var (
passphrase string
verify string
)
if passphrase, err = e.term.ReadPassword("input passphrase: "); err != nil {
return
}
if verify, err = e.term.ReadPassword("verify - input passphrase: "); err != nil {
return
}
if passphrase != verify {
return ErrPassphraseMismatch
}
e.passphrase = passphrase
return nil
}
func (e *encryptApp) ProcessFiles() (err error) {
e.wc = privacy.NewPrivacyWriteCloser(e.dstFile, privacy.DefaultCipherMethod) //TODO: need to handle when custom keygen accepted
if err = e.wc.NewSalt(); err != nil {
return
}
if err = e.wc.GenerateKey(e.passphrase); err != nil {
return
}
if _, err = io.Copy(e.wc, e.srcFile); err != nil {
return
}
if err = e.wc.Close(); err != nil {
return
}
if err = e.srcFile.Close(); err != nil {
return
}
return
}

View File

@ -1,10 +1,25 @@
package spt
import (
"errors"
tw "gitea.suyono.dev/suyono/terminal_wrapper"
"github.com/spf13/cobra"
"os"
)
type cApp struct {
term *tw.Terminal
srcPath string
dstPath string
srcFile *os.File
dstFile *os.File
passphrase string
}
var (
//ErrFatalError = errors.New("fatal error occurred")
ErrPassphraseMismatch = errors.New("mismatch passphrase")
rootCmd = &cobra.Command{
Use: "spt",
Short: "a simple tool to encrypt and decrypt file",
@ -12,12 +27,14 @@ var (
encryptCmd = &cobra.Command{
Use: "encrypt",
Args: validatePositionalArgs,
RunE: encrypt,
}
decryptCmd = &cobra.Command{
Use: "decrypt",
RunE: decrypt,
Args: validatePositionalArgs,
}
)
@ -29,24 +46,117 @@ func init() {
rootCmd.AddCommand(encryptCmd, decryptCmd)
}
func encrypt(cmd *cobra.Command, args []string) error {
//TODO: implementation
//// this is the sample of reading password
//fmt.Print("input passphrase: ")
//passwd, err := privacy.ReadPassword()
//if err != nil {
// return err
//}
//fmt.Println()
//
//fmt.Printf("password: %s\n", passwd)
//// end of sample
func validatePositionalArgs(cmd *cobra.Command, args []string) error {
if len(args) != 0 && len(args) != 2 {
//TODO: improve the error message
return errors.New("invalid arguments")
}
return nil
}
func decrypt(cmd *cobra.Command, args []string) error {
//TODO: implementation
func encrypt(cmd *cobra.Command, args []string) (err error) {
var (
terminal *tw.Terminal
app *cApp
eApp *encryptApp
)
if terminal, err = tw.MakeTerminal(os.Stderr); err != nil {
return
}
defer func() {
existingErr := err
if err = terminal.Restore(); err == nil && existingErr != nil {
err = existingErr
}
}()
app = &cApp{
term: terminal,
}
if err = app.ProcessArgs(args); err != nil {
return
}
eApp = newEncryptApp(app)
if err = eApp.GetPassphrase(); err != nil {
return
}
//TODO: process additional flags
if err = eApp.ProcessFiles(); err != nil {
return
}
return nil
}
func decrypt(cmd *cobra.Command, args []string) (err error) {
var (
terminal *tw.Terminal
app *cApp
dApp *decryptApp
)
if terminal, err = tw.MakeTerminal(os.Stderr); err != nil {
return
}
defer func() {
existingErr := err
if err = terminal.Restore(); err == nil && existingErr != nil {
err = existingErr
}
}()
app = &cApp{
term: terminal,
}
if err = app.ProcessArgs(args); err != nil {
return
}
dApp = newDecryptApp(app)
if err = dApp.GetPassphrase(); err != nil {
return
}
//TODO: process additional flags
if err = dApp.ProcessFiles(); err != nil {
return
}
return nil
}
func (a *cApp) ProcessArgs(args []string) (err error) {
if len(args) == 0 {
a.srcFile = os.Stdin
a.dstFile = os.Stdout
} else {
a.srcPath = args[0]
a.dstPath = args[1]
if a.srcPath == "-" {
a.srcFile = os.Stdin
} else {
if a.srcFile, err = os.Open(a.srcPath); err != nil {
return
}
}
if a.dstPath == "-" {
a.dstFile = os.Stdout
} else {
if a.dstFile, err = os.OpenFile(a.dstPath, os.O_CREATE|os.O_WRONLY, 0640); err != nil { //TODO: allow user to define the destination file permission
return
}
}
}
return
}

3
go.mod
View File

@ -3,13 +3,14 @@ module gitea.suyono.dev/suyono/simple-privacy-tool
go 1.20
require (
gitea.suyono.dev/suyono/terminal_wrapper v0.0.0-20230722101024-a3e50949f40f
github.com/spf13/cobra v1.7.0
golang.org/x/crypto v0.11.0
golang.org/x/term v0.10.0
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
)

2
go.sum
View File

@ -1,3 +1,5 @@
gitea.suyono.dev/suyono/terminal_wrapper v0.0.0-20230722101024-a3e50949f40f h1:yPac52dWHSutQH99FUhqz9rt2FHKaHr9srdJuDI9pkg=
gitea.suyono.dev/suyono/terminal_wrapper v0.0.0-20230722101024-a3e50949f40f/go.mod h1:I8qBJ8oaj+RwbLggXWNRrvRCDD+/bnRoQne0V4xsBNU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=

View File

@ -9,5 +9,6 @@ func main() {
err := spt.Execute()
if err != nil {
fmt.Printf("error: %v\n", err)
//panic(err)
}
}

View File

@ -6,11 +6,8 @@ import (
"encoding/binary"
"encoding/json"
"errors"
"io"
"os"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/term"
"io"
)
type CipherMethodType byte
@ -369,17 +366,3 @@ func (r *Reader) Read(b []byte) (n int, err error) {
return copied, nil
}
func ReadPassphraseFromTerminal() (string, error) {
var inputFd = int(os.Stdin.Fd())
if !term.IsTerminal(inputFd) {
return "", errors.New("not a terminal")
}
passwd, err := term.ReadPassword(inputFd)
if err != nil {
return "", err
}
return string(passwd), nil
}

View File

@ -91,14 +91,25 @@ func TestReadWriteClose(t *testing.T) {
ur := mr.New(mr.NewSource(1))
bb := make([]byte, 1048)
var bar int
var (
//bar int
n int
wl int
rl int
)
for i := 0; i < 63; i++ {
bar = 1000 + ur.Intn(49)
ur.Read(bb[:bar])
sha.Write(bb[:bar])
if _, err = writer.Write(bb[:bar]); err != nil {
//bar = 1000 + ur.Intn(49)
//ur.Read(bb[:bar])
//sha.Write(bb[:bar])
//if n, err = writer.Write(bb[:bar]); err != nil {
// t.Fatal("unexpected: Write failed", err)
//}
ur.Read(bb)
sha.Write(bb)
if n, err = writer.Write(bb); err != nil {
t.Fatal("unexpected: Write failed", err)
}
wl += n
}
if err = writer.Close(); err != nil {
@ -120,25 +131,29 @@ func TestReadWriteClose(t *testing.T) {
sha.Reset()
err = nil
n = 0
for err == nil {
if _, err = reader.Read(bb); err != nil {
if n, err = reader.Read(bb); err != nil {
if err == io.EOF {
continue
} else {
t.Fatal("unexpected: Read failed", err)
}
}
rl += n
sha.Write(bb)
}
readHash := sha.Sum(nil)
t.Log("read hash:", hex.EncodeToString(readHash))
//for i := range writeHash {
// if readHash[i] != writeHash[i] {
// t.Fatal("unexpected: mismatch hash")
// }
//}
t.Log("wl", wl)
t.Log("rl", rl)
for i := range writeHash {
if readHash[i] != writeHash[i] {
t.Fatal("unexpected: mismatch hash")
}
}
}
func TestTrial(t *testing.T) {