161 lines
4.0 KiB
Go
161 lines
4.0 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"crypto/rand"
|
||
"encoding/base64"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"strings"
|
||
|
||
_ "filippo.io/age"
|
||
"filippo.io/age/armor"
|
||
)
|
||
|
||
//var cert *x509.Certificate
|
||
//var key *rsa.PrivateKey
|
||
//
|
||
//func encryptBytes(data []byte) []byte {
|
||
// if cert == nil || key == nil {
|
||
// loadCerts()
|
||
// }
|
||
//
|
||
// encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, cert.PublicKey.(*rsa.PublicKey), data)
|
||
// if err != nil {
|
||
// fmt.Println("Error encrypting data,", err)
|
||
// os.Exit(1)
|
||
// }
|
||
// return encrypted
|
||
//}
|
||
//
|
||
//func decryptBytes(data []byte) []byte {
|
||
// if cert == nil || key == nil {
|
||
// loadCerts()
|
||
// }
|
||
//
|
||
// decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, key, data)
|
||
// if err != nil {
|
||
// fmt.Println("Error decrypting data,", err)
|
||
// os.Exit(1)
|
||
// }
|
||
// return decrypted
|
||
//}
|
||
//
|
||
//func loadCerts() {
|
||
// var err error
|
||
// certBytes, err := os.ReadFile(config.GetAsString("Crypto.cert_path"))
|
||
// keyBytes, err := os.ReadFile(config.GetAsString("Crypto.key_path"))
|
||
// if err != nil {
|
||
// fmt.Println("Error reading cert or key,", err)
|
||
// os.Exit(1)
|
||
// }
|
||
//
|
||
// cert, err = x509.ParseCertificate(certBytes)
|
||
// if err != nil {
|
||
// fmt.Println("Error parsing certificate,", err)
|
||
// os.Exit(1)
|
||
// }
|
||
// key, err = x509.ParsePKCS1PrivateKey(keyBytes)
|
||
// if err != nil {
|
||
// fmt.Println("Error parsing private key,", err)
|
||
// }
|
||
//}
|
||
|
||
// GenerateKey returns a base64-encoded 32-byte random key suitable to use as the
|
||
// symmetric passphrase for age scrypt mode. Store this securely (never in Git).
|
||
func GenerateKey() (string, error) {
|
||
k := make([]byte, 32)
|
||
if _, err := rand.Read(k); err != nil {
|
||
return "", err
|
||
}
|
||
out := make([]byte, base64.StdEncoding.EncodedLen(len(k)))
|
||
base64.StdEncoding.Encode(out, k)
|
||
return string(out), nil
|
||
}
|
||
|
||
// LoadKeyFromFile reads a key file that contains either a raw base64 string or
|
||
// "AGE_SYM_KEY=<base64>" (handy for dotenv). Whitespace is trimmed.
|
||
func LoadKeyFromFile(path string) (string, error) {
|
||
b, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
s := strings.TrimSpace(string(b))
|
||
if i := strings.Index(s, "AGE_SYM_KEY="); i >= 0 {
|
||
s = strings.TrimSpace(strings.TrimPrefix(s, "AGE_SYM_KEY="))
|
||
}
|
||
if s == "" {
|
||
return "", errors.New("empty symmetric key")
|
||
}
|
||
// Quick sanity check that it’s base64 and ~32 bytes after decode.
|
||
if _, err := base64.StdEncoding.DecodeString(s); err != nil {
|
||
return "", fmt.Errorf("invalid base64 key: %w", err)
|
||
}
|
||
return s, nil
|
||
}
|
||
|
||
// Encrypt streams plaintext from r to w using a symmetric passphrase.
|
||
// If armorOut is true, output is ASCII-armored (BEGIN AGE ENCRYPTED FILE).
|
||
func Encrypt(r io.Reader, w io.Writer, passphrase string, armorOut bool) error {
|
||
passphrase = strings.TrimSpace(passphrase)
|
||
if passphrase == "" {
|
||
return errors.New("missing passphrase")
|
||
}
|
||
|
||
var out io.WriteCloser
|
||
var err error
|
||
|
||
if armorOut {
|
||
aw := armor.NewWriter(w)
|
||
defer aw.Close()
|
||
//out, err = age.Encrypt(aw, age.NewScryptRecipient(passphrase))
|
||
} else {
|
||
//out, err = age.Encrypt(w, age.NewScryptRecipient(passphrase))
|
||
}
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
_, copyErr := io.Copy(out, bufio.NewReader(r))
|
||
closeErr := out.Close()
|
||
if copyErr != nil {
|
||
return copyErr
|
||
}
|
||
return closeErr
|
||
}
|
||
|
||
// Decrypt streams ciphertext from r to w using the same symmetric passphrase.
|
||
// It auto-detects armored vs binary ciphertext.
|
||
func Decrypt(r io.Reader, w io.Writer, passphrase string) error {
|
||
passphrase = strings.TrimSpace(passphrase)
|
||
if passphrase == "" {
|
||
return errors.New("missing passphrase")
|
||
}
|
||
|
||
br := bufio.NewReader(r)
|
||
peek, _ := br.Peek(32)
|
||
//var in io.Reader = br
|
||
if strings.HasPrefix(string(peek), "-----BEGIN AGE ENCRYPTED FILE-----") {
|
||
// in = armor.NewReader(br)
|
||
}
|
||
|
||
//dr, err := age.Decrypt(in, age.NewScryptIdentity(passphrase))
|
||
//if err != nil {
|
||
// return err
|
||
//}
|
||
//_, err = io.Copy(w, bufio.NewWriter(wrap0600(w)))
|
||
//return err
|
||
return nil
|
||
}
|
||
|
||
// wrap0600 ensures that when w is an *os.File newly created by caller,
|
||
// its perms are 0600. If it’s not an *os.File, it’s returned unchanged.
|
||
func wrap0600(w io.Writer) io.Writer {
|
||
if f, ok := w.(*os.File); ok {
|
||
_ = f.Chmod(0600)
|
||
}
|
||
return w
|
||
}
|