diff options
author | T <t@tjp.lol> | 2025-06-26 11:42:17 -0600 |
---|---|---|
committer | T <t@tjp.lol> | 2025-07-01 17:50:49 -0600 |
commit | 639ad6a02cbb4b713434671ec09f309aa5410921 (patch) | |
tree | 7dde9cce8136636d11f2f7c961072984cfc705e7 /encryption.go |
Create authentic_kate: user authentication for go HTTP applications
Diffstat (limited to 'encryption.go')
-rw-r--r-- | encryption.go | 136 |
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 +} |