68eaee0b9f
Security: - Encrypt Gitea tokens at rest (AES-256-GCM with MH_SECRET) - Secure cookie flag when behind HTTPS (X-Forwarded-Proto) - Password complexity (min 8 chars) - TOTP: defer persist until verified (totp_pending column) - Audit log table + logging on login/rename/password change Features: - Rename files/folders (double-click in tree, /api/files/rename) - beforeunload warning for unsaved changes - Mobile hamburger menu - PWA icons (192px, 512px) - Max file size enforcement (10MB) - Shared file read access (cross-user with permission check) Polish: - Toast notifications replace all alert() calls - Keyboard shortcut help overlay (Ctrl+/) - File rename via double-click in FileTree
95 lines
2.2 KiB
Go
95 lines
2.2 KiB
Go
package crypto
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/argon2"
|
|
)
|
|
|
|
// DeriveKey derives a 256-bit key from password and salt using Argon2id.
|
|
func DeriveKey(password []byte, salt []byte) []byte {
|
|
return argon2.IDKey(password, salt, 3, 64*1024, 4, 32)
|
|
}
|
|
|
|
// KeyFromSecret derives a 256-bit AES key from a string secret (for token encryption).
|
|
func KeyFromSecret(secret string) []byte {
|
|
h := sha256.Sum256([]byte(secret))
|
|
return h[:]
|
|
}
|
|
|
|
// EncryptString encrypts a string and returns base64-encoded ciphertext.
|
|
func EncryptString(plaintext string, key []byte) (string, error) {
|
|
ct, err := Encrypt([]byte(plaintext), key)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(ct), nil
|
|
}
|
|
|
|
// DecryptString decrypts a base64-encoded ciphertext to a string.
|
|
func DecryptString(encoded string, key []byte) (string, error) {
|
|
ct, err := base64.StdEncoding.DecodeString(encoded)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pt, err := Decrypt(ct, key)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(pt), nil
|
|
}
|
|
|
|
// GenerateSalt returns a random 16-byte salt.
|
|
func GenerateSalt() ([]byte, error) {
|
|
salt := make([]byte, 16)
|
|
_, err := io.ReadFull(rand.Reader, salt)
|
|
return salt, err
|
|
}
|
|
|
|
// Encrypt encrypts plaintext with AES-256-GCM using the given key.
|
|
func Encrypt(plaintext, key []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
|
}
|
|
|
|
// Decrypt decrypts AES-256-GCM ciphertext using the given key.
|
|
func Decrypt(ciphertext, key []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonceSize := gcm.NonceSize()
|
|
if len(ciphertext) < nonceSize {
|
|
return nil, errors.New("ciphertext too short")
|
|
}
|
|
|
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
return gcm.Open(nil, nonce, ciphertext, nil)
|
|
}
|