diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3ca05ce --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/AYM1607/ccclip + +go 1.21 + +require ( + golang.org/x/crypto v0.14.0 + golang.org/x/term v0.13.0 +) + +require golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..16e3f28 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= diff --git a/pkg/crypto/encryption.go b/pkg/crypto/encryption.go new file mode 100644 index 0000000..1e56807 --- /dev/null +++ b/pkg/crypto/encryption.go @@ -0,0 +1,35 @@ +package crypto + +import ( + "crypto/rand" + + "golang.org/x/crypto/chacha20poly1305" +) + +func Encrypt(key, msg []byte) []byte { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + panic(err) + } + + nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead()) + if _, err := rand.Read(nonce); err != nil { + panic(err) + } + + return aead.Seal(nonce, nonce, msg, nil) +} + +func Decrypt(key, encryptedMsg []byte) []byte { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + panic(err) + } + + nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():] + msg, err := aead.Open(nil, nonce, ciphertext, nil) + if err != nil { + panic(err) + } + return msg +} diff --git a/pkg/crypto/keys.go b/pkg/crypto/keys.go new file mode 100644 index 0000000..50d244a --- /dev/null +++ b/pkg/crypto/keys.go @@ -0,0 +1,95 @@ +package crypto + +import ( + "crypto/ecdh" + "crypto/rand" + "encoding/base64" + "fmt" + "os" + + "golang.org/x/crypto/blake2b" +) + +type Direction int + +const ( + KeySize uint = 32 + + SendDirection Direction = iota + ReceiveDirection +) + +func NewPrivateKey() *ecdh.PrivateKey { + c := ecdh.X25519() + k, err := c.GenerateKey(rand.Reader) + if err != nil { + panic(fmt.Sprintf("could not generate private key: %s", err.Error())) + } + return k +} + +func NewSharedKey(local *ecdh.PrivateKey, remote *ecdh.PublicKey, direction Direction) []byte { + // Calculating a shared secret. + secret, err := local.ECDH(remote) + if err != nil { + panic(err) + } + + // Take into account public keys for key derivation. + // See raw_shared_secret @ https://monocypher.org/manual/x25519#DESCRIPTION + xof, err := blake2b.NewXOF(32, nil) + if err != nil { + panic(err) + } + xof.Write(secret) + if direction == SendDirection { + xof.Write(local.PublicKey().Bytes()) + xof.Write(remote.Bytes()) + } else { + xof.Write(remote.Bytes()) + xof.Write(local.PublicKey().Bytes()) + } + + key := make([]byte, 32) + _, err = xof.Read(key) + if err != nil { + panic(err) + } + + return key +} + +func PrivateKeyFromBytes(keyBytes []byte) *ecdh.PrivateKey { + key, err := ecdh.X25519().NewPrivateKey(keyBytes) + if err != nil { + panic(err) + } + return key +} + +func PublicKeyFromBytes(keyBytes []byte) *ecdh.PublicKey { + key, err := ecdh.X25519().NewPublicKey(keyBytes) + if err != nil { + panic(err) + } + return key +} + +func LoadPrivateKey(fp string) *ecdh.PrivateKey { + return PrivateKeyFromBytes(loadKey(fp)) +} + +func LoadPublicKey(fp string) *ecdh.PublicKey { + return PublicKeyFromBytes(loadKey(fp)) +} + +func loadKey(fn string) []byte { + b64Key, err := os.ReadFile(fn) + if err != nil { + panic(err) + } + + keyBytes := make([]byte, KeySize) + base64.StdEncoding.Decode(keyBytes, b64Key) + return keyBytes +} diff --git a/pkg/input/password.go b/pkg/input/password.go new file mode 100644 index 0000000..7ec3050 --- /dev/null +++ b/pkg/input/password.go @@ -0,0 +1,17 @@ +package input + +import ( + "fmt" + "os" + + "golang.org/x/term" +) + +// ReadPassword reads a single line of text from the terminal withouth echoing it out. +func ReadPassword() string { + raw, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + panic(fmt.Sprintf("could not reat password from the terminal: %s", err.Error())) + } + return string(raw) +}