change workstation

This commit is contained in:
Suyono 2023-07-20 08:32:38 +10:00
parent 3dba047793
commit bd976b1533
4 changed files with 304 additions and 12 deletions

8
go.mod
View File

@ -2,12 +2,14 @@ module gitea.suyono.dev/suyono/simple-privacy-tool
go 1.20
require github.com/spf13/cobra v1.7.0
require (
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/crypto v0.11.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
)

69
privacy/chacha.go Normal file
View File

@ -0,0 +1,69 @@
package privacy
import (
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"golang.org/x/crypto/chacha20poly1305"
"io"
)
type chacha20 struct {
aead cipher.AEAD
privacy *Privacy
block []byte
blank bool
nonce []byte
}
func newChaCha20(key []byte, p *Privacy) (c *chacha20, err error) {
c = &chacha20{
privacy: p,
}
c.aead, err = chacha20poly1305.NewX(key)
if err != nil {
return nil, err
}
c.nonce = make([]byte, c.aead.NonceSize())
c.block = make([]byte, 0, c.aead.NonceSize()+int(p.GetSegmentSize())+c.aead.Overhead())
c.blank = true
return
}
func (c *chacha20) write(wc io.WriteCloser, inwr []byte) error {
var err error
if c.blank {
_, err = rand.Read(c.nonce)
if err != nil {
return err
}
if len(inwr) > 0 {
c.blank = false
}
}
if len(c.block)+len(inwr) < int(c.privacy.GetSegmentSize()) {
start := len(c.block)
c.block = c.block[:start+len(inwr)]
copy(c.block[start:], inwr)
return nil
} else if len(c.block)+len(inwr) == int(c.privacy.GetSegmentSize()) {
start := len(c.block)
c.block = c.block[:start+len(inwr)]
copy(c.block[start:], inwr)
lenSlot := make([]byte, 4)
blockLen := uint32(len(c.block) + c.aead.Overhead())
binary.BigEndian.PutUint32(lenSlot, blockLen)
//TODO: fix and resume from here, it was a stopping point
//result := c.aead.Seal(c.block[:0], c.nonce, c.block, lenSlot)
c.aead.Seal(c.block[:0], c.nonce, c.block, lenSlot)
}
return nil
}

View File

@ -1,26 +1,223 @@
package privacy
import (
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"errors"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/term"
"io"
"os"
)
func GenerateKey(password string) ([]byte, error) {
salt := make([]byte, 16)
_, err := rand.Read(salt[1:])
if err != nil {
return nil, err
}
type CipherMethodType byte
key := argon2.IDKey([]byte(password), salt, 1, 16*1024, 4, 32)
const (
segmentSizeBytesLen int = 4
return key, nil
Uninitialised CipherMethodType = 0
XChaCha20Simple CipherMethodType = 1
DefaultCipherMethod CipherMethodType = XChaCha20Simple
)
var (
ErrInvalidSaltLen = errors.New("invalid salt length")
ErrUninitialisedMethod = errors.New("cipher method type uninitialised")
ErrInvalidCipherMethod = errors.New("invalid cipher method type")
ErrCannotReadMagicBytes = errors.New("cannot read magic bytes")
ErrInvalidReadFlow = errors.New("func ReadMagic should be called before calling Read")
ErrInvalidKeyState = errors.New("func GenerateKey should be called first")
ErrInvalidSegmentLength = errors.New("segment length is too long")
)
type Reader struct {
*Privacy
reader io.Reader
buf []byte
bufSlice []byte
spillOver []byte
}
func ReadPasswordFromTerminal() (string, error) {
type WriteCloser struct {
*Privacy
writeCloser io.WriteCloser
}
type Privacy struct {
salt []byte
segmentSize uint32
cmType CipherMethodType
aead cipher.AEAD
}
func newPrivacy() *Privacy {
return &Privacy{
segmentSize: 64 * 1024 * 1024,
cmType: Uninitialised,
}
}
func NewPrivacyReader(reader io.Reader) *Reader {
return &Reader{
Privacy: newPrivacy(),
reader: reader,
}
}
func NewPrivacyWriteCloser(wc io.WriteCloser, cmType CipherMethodType) *WriteCloser {
privacy := newPrivacy()
privacy.cmType = cmType
return &WriteCloser{
Privacy: privacy,
writeCloser: wc,
}
}
func (p *Privacy) SetSalt(salt []byte) error {
if len(salt) != 16 {
return ErrInvalidSaltLen
}
if len(p.salt) != 16 {
p.salt = make([]byte, 16)
}
copy(p.salt, salt)
return nil
}
func (p *Privacy) GetSegmentSize() uint32 {
return p.segmentSize
}
func (p *Privacy) SetSegmentSize(size uint32) {
p.segmentSize = size
}
func (p *Privacy) NewSalt() error {
if len(p.salt) != 16 {
p.salt = make([]byte, 16)
}
if p.cmType == Uninitialised {
return ErrUninitialisedMethod
}
p.salt[0] = byte(p.cmType)
_, err := rand.Read(p.salt[1:])
if err != nil {
return err
}
return nil
}
func (p *Privacy) GenerateKey(passphrase string) error {
var (
key []byte
err error
)
if p.cmType == Uninitialised {
return ErrUninitialisedMethod
}
key = argon2.IDKey([]byte(passphrase), p.salt, 1, 16*1024, 4, 32)
switch p.cmType {
case XChaCha20Simple:
p.aead, err = chacha20poly1305.NewX(key)
default:
return ErrInvalidCipherMethod
}
if err != nil {
return err
}
return nil
}
func (r *Reader) ReadMagic() (err error) {
if r.cmType == Uninitialised {
magic := make([]byte, 16)
_, err = r.reader.Read(magic[:1])
if err != nil {
return
}
switch CipherMethodType(magic[0]) {
case XChaCha20Simple:
r.cmType = XChaCha20Simple
_, err = r.reader.Read(magic[1:])
if err != nil {
return
}
err = r.SetSalt(magic)
if err != nil {
return
}
default:
return ErrInvalidCipherMethod
}
}
return nil
}
func (r *Reader) Read(b []byte) (n int, err error) {
var (
segmentLen uint32
segmentLenBytes []byte
//nonce []byte
//ciphertext []byte
//plaintext []byte
)
if r.cmType == Uninitialised {
return 0, ErrInvalidReadFlow
}
if r.aead == nil {
return 0, ErrInvalidKeyState
}
if cap(r.buf) != int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize() {
r.buf = make([]byte, int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize())
}
if cap(r.spillOver) != int(r.segmentSize) {
r.spillOver = make([]byte, int(r.segmentSize))
}
if len(r.bufSlice) == 0 {
//TODO: nothing in the buffer, fill it up
segmentLenBytes = make([]byte, segmentSizeBytesLen)
n, err = r.reader.Read(segmentLenBytes)
if err != nil {
return
}
segmentLen = binary.LittleEndian.Uint32(segmentLenBytes)
if segmentLen > r.segmentSize {
return 0, ErrInvalidSegmentLength
}
n, err = r.reader.Read(r.buf)
if err != nil {
return
}
}
//TODO: fix me, placeholder!
return 0, nil
}
func ReadPassphraseFromTerminal() (string, error) {
var inputFd int = int(os.Stdin.Fd())
if !term.IsTerminal(inputFd) {
return "", errors.New("not a terminal")

24
privacy/privacy_test.go Normal file
View File

@ -0,0 +1,24 @@
package privacy
import "testing"
func TestTrial(t *testing.T) {
x := make([]byte, 20)
for i := range x {
x[i] = byte(i)
}
t.Log("len x:", len(x))
t.Log("cap x:", cap(x))
t.Log("x:", x)
y := x[5:13]
t.Log("len y:", len(y))
t.Log("cap y:", cap(y))
t.Log("y:", y)
z := y[10:15]
t.Log("len z:", len(z))
t.Log("cap z:", cap(z))
t.Log("z:", z)
}