summaryrefslogtreecommitdiff
path: root/encryption.go
diff options
context:
space:
mode:
authorT <t@tjp.lol>2025-06-26 11:42:17 -0600
committerT <t@tjp.lol>2025-07-01 17:50:49 -0600
commit639ad6a02cbb4b713434671ec09f309aa5410921 (patch)
tree7dde9cce8136636d11f2f7c961072984cfc705e7 /encryption.go
Create authentic_kate: user authentication for go HTTP applications
Diffstat (limited to 'encryption.go')
-rw-r--r--encryption.go136
1 files changed, 136 insertions, 0 deletions
diff --git a/encryption.go b/encryption.go
new file mode 100644
index 0000000..a445668
--- /dev/null
+++ b/encryption.go
@@ -0,0 +1,136 @@
+package kate
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+
+ "golang.org/x/crypto/poly1305" //nolint:staticcheck
+ "golang.org/x/crypto/salsa20"
+)
+
+type encryption struct {
+ Key [naclKeyLen]byte
+}
+
+var ErrInvalidToken = errors.New("invalid token")
+
+func encryptionFromHexKey(key string) (encryption, error) {
+ e := encryption{}
+ l := hex.EncodedLen(naclKeyLen)
+ keybytes := []byte(key)
+ if l != len(keybytes) {
+ return e, fmt.Errorf("expected %d length encryption key, got %d", l, len(keybytes))
+ }
+ if _, err := hex.Decode(e.Key[:], keybytes); err != nil {
+ return e, fmt.Errorf("encryptionFromHexKey: %w", err)
+ }
+ return e, nil
+}
+
+func (e encryption) Encrypt(data []byte) string {
+ buf := make([]byte, naclNonceLen+len(data)+naclMacLen)
+ nonce := [naclNonceLen]byte{}
+
+ _, _ = rand.Read(nonce[:])
+
+ seal(buf[naclNonceLen:], e.Key, nonce, data)
+
+ copy(buf[:naclNonceLen], nonce[:])
+ return hex.EncodeToString(buf)
+}
+
+func (e encryption) Decrypt(token string) ([]byte, bool) {
+ tokenBytes := []byte(token)
+ ciphertext := make([]byte, hex.DecodedLen(len(tokenBytes)))
+ if _, err := hex.Decode(ciphertext, tokenBytes); err != nil {
+ return nil, false
+ }
+
+ nonce := ciphertext[:naclNonceLen]
+ noncebuf := [naclNonceLen]byte{}
+ copy(noncebuf[:], nonce)
+ ciphertext = ciphertext[naclNonceLen:]
+
+ cleartext := make([]byte, len(ciphertext)-naclMacLen)
+ if _, ok := open(cleartext, e.Key, noncebuf, ciphertext); !ok {
+ return nil, false
+ }
+ return cleartext, true
+}
+
+const (
+ naclKeyLen = 32
+ naclNonceLen = 24
+ naclMacLen = 16
+)
+
+//
+// Replicating the libsodium secretbox API below.
+//
+// This was a last resort. I really didn't want to introduce CGO,
+// and the only pure-go implementation I could find (libgodium)
+// is unmaintained and riddled with bugs.
+//
+// With solid go implementations of the core primitives already in
+// golang.org/x/crypto, it made the most sense to just wrap those.
+//
+
+func seal(dst []byte, key [naclKeyLen]byte, nonce [naclNonceLen]byte, plaintext []byte) []byte {
+ if len(dst) < len(plaintext)+naclMacLen {
+ dst = make([]byte, len(plaintext)+naclMacLen)
+ }
+
+ keystream := make([]byte, naclKeyLen+len(plaintext))
+ salsa20.XORKeyStream(keystream, keystream, nonce[:], &key)
+
+ var poly1305Key [naclKeyLen]byte
+ copy(poly1305Key[:], keystream[:naclKeyLen])
+
+ ciphertext := make([]byte, len(plaintext))
+ for i := range plaintext {
+ ciphertext[i] = plaintext[i] ^ keystream[naclKeyLen+i]
+ }
+
+ var mac [naclMacLen]byte
+ poly1305.Sum(&mac, ciphertext, &poly1305Key)
+
+ copy(dst[:naclMacLen], mac[:])
+ copy(dst[naclMacLen:], ciphertext)
+
+ return dst
+}
+
+func open(dst []byte, key [naclKeyLen]byte, nonce [naclNonceLen]byte, box []byte) ([]byte, bool) {
+ if len(box) < naclMacLen {
+ return nil, false
+ }
+
+ if len(dst) < len(box)-naclMacLen {
+ dst = make([]byte, len(box)-naclMacLen)
+ }
+
+ var receivedMAC [naclMacLen]byte
+ copy(receivedMAC[:], box[:naclMacLen])
+ ciphertext := box[naclMacLen:]
+
+ keystream := make([]byte, naclKeyLen+len(ciphertext))
+ salsa20.XORKeyStream(keystream, keystream, nonce[:], &key)
+
+ var poly1305Key [naclKeyLen]byte
+ copy(poly1305Key[:], keystream[:naclKeyLen])
+
+ var expectedMAC [naclMacLen]byte
+ poly1305.Sum(&expectedMAC, ciphertext, &poly1305Key)
+
+ if !poly1305.Verify(&receivedMAC, ciphertext, &poly1305Key) {
+ return nil, false
+ }
+
+ for i := range ciphertext {
+ dst[i] = ciphertext[i] ^ keystream[naclKeyLen+i]
+ }
+
+ return dst, true
+}