//go:build !gui
// +build !gui

package main

import (
	"bufio"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/sha512"
	"crypto/x509"
	"encoding/asn1"
	"encoding/base64"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"flag"
	"fmt"
	"io"
	"math/big"
	"os"
	"regexp"
	"strings"

	"github.com/awnumar/memguard"
	"github.com/go-piv/piv-go/v2/piv"
)

type ecSignature struct{ R, S *big.Int }

const (
	AlgorithmECCP256 = "ECCP256"
	AlgorithmECCP384 = "ECCP384"
	AlgorithmED25519 = "ED25519"
)

const (
	Ed25519SignatureSize = 64
	Ed25519PublicKeySize = 32
	Ed25519CombinedSize  = Ed25519SignatureSize + Ed25519PublicKeySize
	minRSABits           = 2048
)

var supportedAlgorithms = map[string]bool{
	AlgorithmECCP256: true,
	AlgorithmECCP384: true,
	AlgorithmED25519: true,
}

var curveToAlgorithm = map[elliptic.Curve]string{
	elliptic.P256(): AlgorithmECCP256,
	elliptic.P384(): AlgorithmECCP384,
}

var curveToHash = map[elliptic.Curve]crypto.Hash{
	elliptic.P256(): crypto.SHA256,
	elliptic.P384(): crypto.SHA384,
}

var supportedRSASizes = map[int]string{
	2048: "RSA2048",
	3072: "RSA3072",
	4096: "RSA4096",
}

var yubiKeyIndex int

type signatureInfo struct {
	Algorithm       string
	OriginalMessage []byte
	PublicKey       []byte
}

func main() {
	defer memguard.Purge()

	if len(os.Args) == 1 {
		printCLIHelp(os.Stdout)
		return
	}
	if err := runCLI(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, "Error:", err)
		os.Exit(1)
	}
}

func runCLI(args []string) error {
	if len(args) == 0 {
		printCLIHelp(os.Stdout)
		return nil
	}

	switch args[0] {
	case "help", "-h", "--help":
		printCLIHelp(os.Stdout)
		return nil
	case "version", "-v", "--version":
		fmt.Println("yubicrypt v0.2.0")
		return nil
	case "encrypt", "enc":
		return runEncryptCLI(args[1:])
	case "decrypt", "dec":
		return runDecryptCLI(args[1:])
	case "sign":
		return runSignCLI(args[1:])
	case "verify":
		return runVerifyCLI(args[1:])
	case "pad":
		return runPadCLI(args[1:])
	case "unpad":
		return runUnpadCLI(args[1:])
	case "cards":
		return runCardsCLI()
	default:
		return fmt.Errorf("unknown command %q; run `yubicrypt help`", args[0])
	}
}

func printCLIHelp(w io.Writer) {
	_, _ = io.WriteString(w, `Usage:
  yubicrypt <command> [options]

Commands:
  encrypt   Encrypt with an RSA public certificate/key
  decrypt   Decrypt with YubiKey PIV slot 9d
  sign      Sign with YubiKey PIV slot 9c
  verify    Verify an embedded yubicrypt signature
  pad       ISO/IEC 7816-4 pad and Base64 encode
  unpad     Base64 decode and remove padding
  cards     List visible YubiKeys
  version   Print version

Common IO options:
  -i, --input FILE     input file, or - for stdin (default -)
  -o, --output FILE    output file, or - for stdout (default -)
      --text TEXT      use TEXT instead of reading input
      --quiet          suppress status lines on stderr

PIN options for decrypt/sign:
      --pin-stdin      read the PIV PIN from the first stdin line
      --pin-file FILE  read the PIV PIN from the first line of FILE
      --pin PIN        use PIN directly (less safe: shell history/process list)
      --card-index N   YubiKey index from yubicrypt cards (default 0)

Examples:
  yubicrypt encrypt --key alice.crt -i msg.txt -o msg.yc
  printf '%s\n' "$PIV_PIN" | yubicrypt decrypt --pin-stdin -i msg.yc -o msg.txt
  printf '%s\n' "$PIV_PIN" | yubicrypt sign --pin-stdin -i msg.txt -o msg.sig.txt
  yubicrypt verify -i msg.sig.txt --public-key
`)
}

func newFlagSet(name string) *flag.FlagSet {
	fs := flag.NewFlagSet(name, flag.ContinueOnError)
	fs.SetOutput(os.Stderr)
	return fs
}

func addIOFlags(fs *flag.FlagSet, input, output, text *string, quiet *bool) {
	fs.StringVar(input, "input", "-", "input file, or - for stdin")
	fs.StringVar(input, "i", "-", "input file, or - for stdin")
	fs.StringVar(output, "output", "-", "output file, or - for stdout")
	fs.StringVar(output, "o", "-", "output file, or - for stdout")
	fs.StringVar(text, "text", "", "literal input text")
	fs.BoolVar(quiet, "quiet", false, "suppress status lines on stderr")
}

func addPINFlags(fs *flag.FlagSet, pin, pinFile *string, pinStdin *bool) {
	fs.StringVar(pin, "pin", "", "PIV PIN (less safe: shell history/process list)")
	fs.StringVar(pinFile, "pin-file", "", "file containing the PIV PIN on the first line")
	fs.BoolVar(pinStdin, "pin-stdin", false, "read the PIV PIN from the first stdin line")
	fs.IntVar(&yubiKeyIndex, "card-index", 0, "YubiKey index from `yubicrypt cards`")
}

func runEncryptCLI(args []string) error {
	var input, output, text, key string
	var quiet bool
	fs := newFlagSet("encrypt")
	addIOFlags(fs, &input, &output, &text, &quiet)
	fs.StringVar(&key, "key", "", "RSA public certificate/key file")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if key == "" {
		return fmt.Errorf("encrypt requires --key")
	}

	stdin := bufio.NewReader(os.Stdin)
	data, err := readInput(input, text, stdin)
	if err != nil {
		return fmt.Errorf("read input: %w", err)
	}
	result, err := encryptData(data, key)
	if err != nil {
		return err
	}
	if err := writeOutput(output, []byte(result)); err != nil {
		return fmt.Errorf("write output: %w", err)
	}
	logStatus(quiet, "encrypted %d bytes", len(data))
	return nil
}

func runDecryptCLI(args []string) error {
	var input, output, text, pin, pinFile string
	var quiet, pinStdin bool
	fs := newFlagSet("decrypt")
	addIOFlags(fs, &input, &output, &text, &quiet)
	addPINFlags(fs, &pin, &pinFile, &pinStdin)
	if err := fs.Parse(args); err != nil {
		return err
	}

	stdin := bufio.NewReader(os.Stdin)
	pivPIN, err := readPIN(pin, pinFile, pinStdin, stdin)
	if err != nil {
		return err
	}
	data, err := readInput(input, text, stdin)
	if err != nil {
		return fmt.Errorf("read input: %w", err)
	}
	result, err := decryptData(data, pivPIN)
	if err != nil {
		return err
	}
	if err := writeOutput(output, result); err != nil {
		return fmt.Errorf("write output: %w", err)
	}
	logStatus(quiet, "decrypted %d bytes", len(result))
	return nil
}

func runSignCLI(args []string) error {
	var input, output, text, pin, pinFile string
	var quiet, pinStdin, force bool
	fs := newFlagSet("sign")
	addIOFlags(fs, &input, &output, &text, &quiet)
	addPINFlags(fs, &pin, &pinFile, &pinStdin)
	fs.BoolVar(&force, "force", false, "allow signing input that already contains a yubicrypt signature")
	if err := fs.Parse(args); err != nil {
		return err
	}

	stdin := bufio.NewReader(os.Stdin)
	pivPIN, err := readPIN(pin, pinFile, pinStdin, stdin)
	if err != nil {
		return err
	}
	data, err := readInput(input, text, stdin)
	if err != nil {
		return fmt.Errorf("read input: %w", err)
	}
	if !force && containsSignatureBlock(data) {
		return fmt.Errorf("input already contains a yubicrypt signature; use --force to sign anyway")
	}
	result, err := signData(data, pivPIN)
	if err != nil {
		return err
	}
	if err := writeOutput(output, []byte(result)); err != nil {
		return fmt.Errorf("write output: %w", err)
	}
	logStatus(quiet, "signed %d bytes", len(data))
	return nil
}

func runVerifyCLI(args []string) error {
	var input, output, text, messageOutput string
	var quiet, publicKeyOnly bool
	fs := newFlagSet("verify")
	fs.StringVar(&input, "input", "-", "input file, or - for stdin")
	fs.StringVar(&input, "i", "-", "input file, or - for stdin")
	fs.StringVar(&text, "text", "", "literal input text")
	fs.BoolVar(&quiet, "quiet", false, "suppress success output")
	fs.BoolVar(&publicKeyOnly, "public-key", false, "print embedded public key hex only")
	fs.StringVar(&messageOutput, "message-output", "", "write verified original message to file")
	fs.StringVar(&output, "output", "", "alias for --message-output")
	fs.StringVar(&output, "o", "", "alias for --message-output")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if output != "" && messageOutput == "" {
		messageOutput = output
	}

	stdin := bufio.NewReader(os.Stdin)
	data, err := readInput(input, text, stdin)
	if err != nil {
		return fmt.Errorf("read input: %w", err)
	}
	info, err := verifySignedData(data)
	if err != nil {
		return err
	}
	if messageOutput != "" {
		if err := writeOutput(messageOutput, info.OriginalMessage); err != nil {
			return fmt.Errorf("write verified message: %w", err)
		}
	}
	if publicKeyOnly {
		fmt.Fprintln(os.Stdout, hex.EncodeToString(info.PublicKey))
		return nil
	}
	if !quiet {
		fmt.Fprintf(os.Stdout, "OK %s signature valid; message=%d bytes; public_key=%s\n",
			info.Algorithm, len(info.OriginalMessage), hex.EncodeToString(info.PublicKey))
	}
	return nil
}

func runPadCLI(args []string) error {
	var input, output, text string
	var quiet bool
	fs := newFlagSet("pad")
	addIOFlags(fs, &input, &output, &text, &quiet)
	if err := fs.Parse(args); err != nil {
		return err
	}

	stdin := bufio.NewReader(os.Stdin)
	data, err := readInput(input, text, stdin)
	if err != nil {
		return fmt.Errorf("read input: %w", err)
	}
	padded := securePadMessage(data)
	encoded := formatBase64RFC(base64.StdEncoding.EncodeToString(padded))
	if err := writeOutput(output, []byte(encoded)); err != nil {
		return fmt.Errorf("write output: %w", err)
	}
	logStatus(quiet, "padded %d -> %d bytes", len(data), len(padded))
	return nil
}

func runUnpadCLI(args []string) error {
	var input, output, text string
	var quiet bool
	fs := newFlagSet("unpad")
	addIOFlags(fs, &input, &output, &text, &quiet)
	if err := fs.Parse(args); err != nil {
		return err
	}

	stdin := bufio.NewReader(os.Stdin)
	data, err := readInput(input, text, stdin)
	if err != nil {
		return fmt.Errorf("read input: %w", err)
	}
	decoded, err := decodeBase64Clean(data)
	if err != nil {
		return err
	}
	defer memguard.WipeBytes(decoded)
	result, err := secureUnpadMessage(decoded)
	if err != nil {
		return err
	}
	if err := writeOutput(output, result); err != nil {
		return fmt.Errorf("write output: %w", err)
	}
	logStatus(quiet, "unpadded %d bytes", len(result))
	return nil
}

func runCardsCLI() error {
	cards, err := piv.Cards()
	if err != nil {
		return fmt.Errorf("list cards: %w", err)
	}
	if len(cards) == 0 {
		return fmt.Errorf("no smart card found")
	}
	for i, card := range cards {
		fmt.Fprintf(os.Stdout, "%d\t%s\n", i, card)
	}
	return nil
}

func readInput(path, text string, stdin *bufio.Reader) ([]byte, error) {
	if text != "" {
		return []byte(text), nil
	}
	if path == "" || path == "-" {
		return io.ReadAll(stdin)
	}
	return os.ReadFile(path)
}

func writeOutput(path string, data []byte) error {
	if path == "" || path == "-" {
		_, err := os.Stdout.Write(data)
		return err
	}
	return os.WriteFile(path, data, 0600)
}

func firstLine(data []byte) string {
	line := string(data)
	if i := strings.IndexAny(line, "\r\n"); i >= 0 {
		line = line[:i]
	}
	return strings.TrimSpace(line)
}

func readPIN(pin, pinFile string, pinStdin bool, stdin *bufio.Reader) (string, error) {
	switch {
	case pin != "":
		return pin, nil
	case pinFile != "":
		data, err := os.ReadFile(pinFile)
		if err != nil {
			return "", fmt.Errorf("read PIN file: %w", err)
		}
		defer memguard.WipeBytes(data)
		p := firstLine(data)
		if p == "" {
			return "", fmt.Errorf("PIN file is empty")
		}
		return p, nil
	case pinStdin:
		line, err := stdin.ReadString('\n')
		if err != nil && err != io.EOF {
			return "", fmt.Errorf("read PIN from stdin: %w", err)
		}
		p := strings.TrimRight(line, "\r\n")
		if p == "" {
			return "", fmt.Errorf("stdin PIN is empty")
		}
		return p, nil
	default:
		return "", fmt.Errorf("PIV PIN required; use --pin-stdin, --pin-file, or --pin")
	}
}

func logStatus(quiet bool, format string, args ...any) {
	if quiet {
		return
	}
	fmt.Fprintf(os.Stderr, format+"\n", args...)
}

func containsSignatureBlock(data []byte) bool {
	s := string(data)
	for algo := range supportedAlgorithms {
		if strings.Contains(s, "-----BEGIN "+algo+" SIGNATURE-----") {
			return true
		}
	}
	return false
}

func signData(data []byte, pin string) (string, error) {
	pinGuard := memguard.NewBufferFromBytes([]byte(pin))
	defer pinGuard.Destroy()

	normalizedData := normalizeToRFCCompliantCRLF(data)
	sig, algo, err := signDataInternal(pinGuard.Bytes(), normalizedData)
	if err != nil {
		return "", fmt.Errorf("signing failed: %w", err)
	}

	sep := "\r\n"
	if len(normalizedData) > 0 {
		last := string(normalizedData[len(normalizedData)-1:])
		if last == "\n" && !(len(normalizedData) >= 2 && string(normalizedData[len(normalizedData)-2:]) == "\r\n") {
			sep = "\n"
		}
	}

	return string(normalizedData) + sep +
		"-----BEGIN " + algo + " SIGNATURE-----" + sep +
		formatSignatureRFC(sig) +
		"-----END " + algo + " SIGNATURE-----" + sep, nil
}

func signDataInternal(pin, data []byte) (string, string, error) {
	yk, err := openYubiKey(yubiKeyIndex)
	if err != nil {
		return "", "", err
	}
	defer yk.Close()

	cert, err := yk.Certificate(piv.SlotSignature)
	if err != nil {
		return "", "", fmt.Errorf("failed to get certificate from signature slot: %w", err)
	}

	if ed25519PubKey, ok := cert.PublicKey.(ed25519.PublicKey); ok {
		hash := sha256.Sum256(data)
		return signEd25519Data(string(pin), hash[:], ed25519PubKey, yk)
	}

	pubKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
	if !ok {
		return "", "", fmt.Errorf("public key is not ECDSA or Ed25519")
	}

	algorithm, exists := curveToAlgorithm[pubKey.Curve]
	if !exists {
		return "", "", fmt.Errorf("unsupported curve: %v", pubKey.Curve)
	}

	hashFunc := curveToHash[pubKey.Curve]
	var digest []byte
	switch hashFunc {
	case crypto.SHA256:
		h := sha256.New()
		h.Write(data)
		digest = h.Sum(nil)
	case crypto.SHA384:
		h := sha512.New384()
		h.Write(data)
		digest = h.Sum(nil)
	default:
		return "", "", fmt.Errorf("unsupported hash algorithm for curve")
	}

	auth := piv.KeyAuth{PIN: string(pin)}
	priv, err := yk.PrivateKey(piv.SlotSignature, cert.PublicKey, auth)
	if err != nil {
		return "", "", fmt.Errorf("failed to get private key: %w", err)
	}

	signer, ok := priv.(crypto.Signer)
	if !ok {
		return "", "", fmt.Errorf("key does not implement crypto.Signer")
	}

	asn1sig, err := signer.Sign(rand.Reader, digest, nil)
	if err != nil {
		return "", "", fmt.Errorf("signing failed: %w", err)
	}

	var sig ecSignature
	if _, err := asn1.Unmarshal(asn1sig, &sig); err != nil {
		return "", "", fmt.Errorf("ASN.1 unmarshal failed: %w", err)
	}

	curveSize := (pubKey.Curve.Params().BitSize + 7) / 8
	var raw []byte
	raw = append(raw, safePad(pubKey.X.Bytes(), curveSize)...)
	raw = append(raw, safePad(pubKey.Y.Bytes(), curveSize)...)
	raw = append(raw, safePad(sig.R.Bytes(), curveSize)...)
	raw = append(raw, safePad(sig.S.Bytes(), curveSize)...)

	return hex.EncodeToString(raw), algorithm, nil
}

func signEd25519Data(pin string, hash []byte, pubKey ed25519.PublicKey, yk *piv.YubiKey) (string, string, error) {
	auth := piv.KeyAuth{PIN: pin}
	priv, err := yk.PrivateKey(piv.SlotSignature, pubKey, auth)
	if err != nil {
		return "", "", fmt.Errorf("failed to get private key: %w", err)
	}

	signer, ok := priv.(crypto.Signer)
	if !ok {
		return "", "", fmt.Errorf("key does not implement crypto.Signer")
	}

	signature, err := signer.Sign(rand.Reader, hash, crypto.Hash(0))
	if err != nil {
		return "", "", fmt.Errorf("Ed25519 signing failed: %w", err)
	}

	combined := append(pubKey, signature...)
	return hex.EncodeToString(combined), AlgorithmED25519, nil
}

func verifySignedData(data []byte) (*signatureInfo, error) {
	s := string(normalizeToRFCCompliantCRLF(data))
	var algorithm string
	beginIndex := -1
	beginMarker := ""

	for algo := range supportedAlgorithms {
		marker := "-----BEGIN " + algo + " SIGNATURE-----"
		if idx := strings.Index(s, marker); idx >= 0 {
			algorithm = algo
			beginIndex = idx
			beginMarker = marker
			break
		}
	}
	if algorithm == "" {
		return nil, fmt.Errorf("no supported signature block found")
	}

	endMarker := "-----END " + algorithm + " SIGNATURE-----"
	hexStart := beginIndex + len(beginMarker)
	endRel := strings.Index(s[hexStart:], endMarker)
	if endRel < 0 {
		return nil, fmt.Errorf("invalid signature block format")
	}

	original := []byte(s[:beginIndex])
	original = bytesTrimOneLineEnding(original)
	hexPart := s[hexStart : hexStart+endRel]
	hexPart = regexp.MustCompile(`[\r\n\s\t]+`).ReplaceAllString(hexPart, "")

	combined, err := hex.DecodeString(hexPart)
	if err != nil {
		return nil, fmt.Errorf("hex decode failed: %w", err)
	}

	var verificationErr error
	switch algorithm {
	case AlgorithmED25519:
		hash := sha256.Sum256(original)
		verificationErr = verifyEd25519(hash[:], combined)
	case AlgorithmECCP256, AlgorithmECCP384:
		verificationErr = verifyECDSA(original, combined, algorithm)
	default:
		verificationErr = fmt.Errorf("unsupported algorithm: %s", algorithm)
	}
	if verificationErr != nil {
		return nil, verificationErr
	}

	publicKey, err := extractPublicKeyFromSignature(combined, algorithm)
	if err != nil {
		return nil, err
	}
	return &signatureInfo{
		Algorithm:       algorithm,
		OriginalMessage: original,
		PublicKey:       publicKey,
	}, nil
}

func extractPublicKeyFromSignature(combined []byte, algorithm string) ([]byte, error) {
	switch algorithm {
	case AlgorithmED25519:
		if len(combined) != Ed25519CombinedSize {
			return nil, fmt.Errorf("invalid Ed25519 signature block")
		}
		return combined[:Ed25519PublicKeySize], nil
	case AlgorithmECCP256, AlgorithmECCP384:
		var curve elliptic.Curve
		switch algorithm {
		case AlgorithmECCP256:
			curve = elliptic.P256()
		case AlgorithmECCP384:
			curve = elliptic.P384()
		}
		curveSize := (curve.Params().BitSize + 7) / 8
		expectedBytes := 4 * curveSize
		if len(combined) != expectedBytes {
			return nil, fmt.Errorf("invalid signature block size: expected %d, got %d", expectedBytes, len(combined))
		}
		return combined[:2*curveSize], nil
	default:
		return nil, fmt.Errorf("unsupported algorithm: %s", algorithm)
	}
}

func verifyEd25519(dataHash, combined []byte) error {
	if len(combined) != Ed25519CombinedSize {
		return fmt.Errorf("invalid Ed25519 signature block")
	}
	publicKey := combined[:Ed25519PublicKeySize]
	signature := combined[Ed25519PublicKeySize:]
	if !ed25519.Verify(ed25519.PublicKey(publicKey), dataHash, signature) {
		return fmt.Errorf("Ed25519 signature verification failed")
	}
	return nil
}

func verifyECDSA(data, combined []byte, algorithm string) error {
	var curve elliptic.Curve
	var hashFunc crypto.Hash
	switch algorithm {
	case AlgorithmECCP256:
		curve = elliptic.P256()
		hashFunc = crypto.SHA256
	case AlgorithmECCP384:
		curve = elliptic.P384()
		hashFunc = crypto.SHA384
	default:
		return fmt.Errorf("unsupported ECDSA algorithm: %s", algorithm)
	}

	curveSize := (curve.Params().BitSize + 7) / 8
	expectedBytes := 4 * curveSize
	if len(combined) != expectedBytes {
		return fmt.Errorf("invalid signature block size: expected %d, got %d", expectedBytes, len(combined))
	}

	x := new(big.Int).SetBytes(safePad(combined[0:curveSize], curveSize))
	y := new(big.Int).SetBytes(safePad(combined[curveSize:2*curveSize], curveSize))
	r := new(big.Int).SetBytes(safePad(combined[2*curveSize:3*curveSize], curveSize))
	s := new(big.Int).SetBytes(safePad(combined[3*curveSize:], curveSize))

	if !curve.IsOnCurve(x, y) {
		return fmt.Errorf("public key point (X,Y) is not on the curve %s", curve.Params().Name)
	}

	pub := &ecdsa.PublicKey{Curve: curve, X: x, Y: y}
	var digest []byte
	switch hashFunc {
	case crypto.SHA256:
		h := sha256.New()
		h.Write(data)
		digest = h.Sum(nil)
	case crypto.SHA384:
		h := sha512.New384()
		h.Write(data)
		digest = h.Sum(nil)
	}

	if !ecdsa.Verify(pub, digest, r, s) {
		return fmt.Errorf("signature verification failed")
	}
	return nil
}

func encryptData(data []byte, pubKeyFile string) (string, error) {
	pubKey, err := loadRSAPublicKey(pubKeyFile)
	if err != nil {
		return "", fmt.Errorf("failed to load public key: %w", err)
	}

	aesKeyGuard := memguard.NewBuffer(32)
	defer aesKeyGuard.Destroy()
	if _, err := rand.Read(aesKeyGuard.Bytes()); err != nil {
		return "", fmt.Errorf("failed to generate AES key: %w", err)
	}

	encryptedKey, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, aesKeyGuard.Bytes())
	if err != nil {
		return "", fmt.Errorf("RSA encryption failed: %w", err)
	}
	defer memguard.WipeBytes(encryptedKey)

	encryptedData, err := encryptAES(data, aesKeyGuard.Bytes())
	if err != nil {
		return "", fmt.Errorf("AES encryption failed: %w", err)
	}
	defer memguard.WipeBytes(encryptedData)

	combined := append(encryptedKey, encryptedData...)
	defer memguard.WipeBytes(combined)

	return formatBase64RFC(base64.StdEncoding.EncodeToString(combined)), nil
}

func decryptData(data []byte, pin string) ([]byte, error) {
	pinGuard := memguard.NewBufferFromBytes([]byte(pin))
	defer pinGuard.Destroy()

	combined, err := decodeBase64Clean(data)
	if err != nil {
		return nil, err
	}
	defer memguard.WipeBytes(combined)

	yk, err := openYubiKey(yubiKeyIndex)
	if err != nil {
		return nil, fmt.Errorf("failed to open YubiKey: %w", err)
	}
	defer yk.Close()

	cert, err := yk.Certificate(piv.SlotKeyManagement)
	if err != nil {
		return nil, fmt.Errorf("failed to get certificate from slot 9d: %w", err)
	}

	rsaPubKey, ok := cert.PublicKey.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("certificate does not contain RSA public key")
	}
	if err := checkRSASecurity(rsaPubKey, "on YubiKey"); err != nil {
		return nil, err
	}

	keySize := rsaPubKey.Size()
	if len(combined) < keySize {
		return nil, fmt.Errorf("ciphertext too short")
	}

	encryptedKey := combined[:keySize]
	encryptedData := combined[keySize:]

	auth := piv.KeyAuth{PIN: string(pinGuard.Bytes())}
	priv, err := yk.PrivateKey(piv.SlotKeyManagement, cert.PublicKey, auth)
	if err != nil {
		return nil, fmt.Errorf("failed to get private key: %w", err)
	}

	decrypter, ok := priv.(crypto.Decrypter)
	if !ok {
		return nil, fmt.Errorf("private key does not support decryption")
	}

	decryptedPayload, err := decrypter.Decrypt(rand.Reader, encryptedKey, nil)
	if err != nil {
		return nil, fmt.Errorf("RSA decryption failed: %w", err)
	}
	defer memguard.WipeBytes(decryptedPayload)

	if len(decryptedPayload) != 32 {
		return nil, fmt.Errorf("invalid AES key size")
	}

	decryptedData, err := decryptAES(encryptedData, decryptedPayload)
	if err != nil {
		return nil, fmt.Errorf("AES decryption failed: %w", err)
	}
	return decryptedData, nil
}

func checkRSASecurity(pubKey *rsa.PublicKey, context string) error {
	keySize := pubKey.N.BitLen()
	if keySize < minRSABits {
		return fmt.Errorf("insecure %d-bit RSA key %s - minimum is %d-bit", keySize, context, minRSABits)
	}
	if _, supported := supportedRSASizes[keySize]; !supported {
		fmt.Fprintf(os.Stderr, "WARNING: %d-bit RSA key %s - supported sizes are 2048, 3072, 4096 bits\n", keySize, context)
	}
	if keySize == 1024 {
		fmt.Fprintf(os.Stderr, "CRITICAL WARNING: 1024-bit RSA keys %s are insecure and should not be used!\n", context)
	}
	return nil
}

func loadRSAPublicKey(filename string) (*rsa.PublicKey, error) {
	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("failed to read public key file: %w", err)
	}
	defer memguard.WipeBytes(data)

	block, _ := pem.Decode(data)
	if block == nil {
		return nil, fmt.Errorf("no data found in file")
	}

	switch block.Type {
	case "CERTIFICATE":
		cert, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("failed to parse certificate: %w", err)
		}
		pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
		if !ok {
			return nil, fmt.Errorf("certificate does not contain RSA public key")
		}
		if err := checkRSASecurity(pubKey, "in certificate "+filename); err != nil {
			return nil, err
		}
		return pubKey, nil
	case "PUBLIC KEY":
		pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("failed to parse public key: %w", err)
		}
		pubKey, ok := pubInterface.(*rsa.PublicKey)
		if !ok {
			return nil, fmt.Errorf("not an RSA public key")
		}
		if err := checkRSASecurity(pubKey, "in file "+filename); err != nil {
			return nil, err
		}
		return pubKey, nil
	case "RSA PUBLIC KEY":
		pubKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("failed to parse RSA public key: %w", err)
		}
		if err := checkRSASecurity(pubKey, "in file "+filename); err != nil {
			return nil, err
		}
		return pubKey, nil
	default:
		return nil, fmt.Errorf("unsupported type: %s, expected CERTIFICATE, PUBLIC KEY or RSA PUBLIC KEY", block.Type)
	}
}

func encryptAES(data, 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 := rand.Read(nonce); err != nil {
		return nil, err
	}
	return gcm.Seal(nonce, nonce, data, nil), nil
}

func decryptAES(data, 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(data) < nonceSize {
		return nil, fmt.Errorf("ciphertext too short")
	}
	nonce, ciphertext := data[:nonceSize], data[nonceSize:]
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, err
	}
	return plaintext, nil
}

func openYubiKey(index int) (*piv.YubiKey, error) {
	cards, err := piv.Cards()
	if err != nil {
		return nil, fmt.Errorf("failed to list cards: %w", err)
	}
	if len(cards) == 0 {
		return nil, fmt.Errorf("no smart card found")
	}

	count := 0
	for _, card := range cards {
		if strings.Contains(strings.ToLower(card), "yubikey") {
			if count == index {
				return piv.Open(card)
			}
			count++
		}
	}
	return nil, fmt.Errorf("no YubiKey found at index %d", index)
}

func normalizeToRFCCompliantCRLF(data []byte) []byte {
	s := string(data)
	s = strings.ReplaceAll(s, "\r\n", "\n")
	s = strings.ReplaceAll(s, "\r", "\n")
	s = strings.ReplaceAll(s, "\n", "\r\n")
	return []byte(s)
}

func formatSignatureRFC(sig string) string {
	var result strings.Builder
	for i := 0; i < len(sig); i += 64 {
		end := i + 64
		if end > len(sig) {
			end = len(sig)
		}
		result.WriteString(sig[i:end])
		result.WriteString("\r\n")
	}
	return result.String()
}

func formatBase64RFC(data string) string {
	var result strings.Builder
	for i := 0; i < len(data); i += 76 {
		end := i + 76
		if end > len(data) {
			end = len(data)
		}
		result.WriteString(data[i:end])
		result.WriteString("\r\n")
	}
	return result.String()
}

func securePadMessage(data []byte) []byte {
	const blockSize = 4096
	paddingNeeded := blockSize - (len(data) % blockSize)
	if paddingNeeded == blockSize {
		return data
	}
	paddedData := make([]byte, len(data)+paddingNeeded)
	copy(paddedData, data)
	paddedData[len(data)] = 0x80
	return paddedData
}

func secureUnpadMessage(data []byte) ([]byte, error) {
	if len(data) == 0 {
		return nil, errors.New("cannot unpad empty data")
	}
	if len(data)%4096 != 0 {
		return nil, errors.New("invalid block size for unpadding")
	}

	lastIndex := -1
	for i := len(data) - 1; i >= 0; i-- {
		if data[i] == 0x80 {
			lastIndex = i
			break
		}
		if data[i] != 0x00 {
			return nil, errors.New("invalid padding format: unexpected non-zero byte")
		}
	}
	if lastIndex == -1 {
		return nil, errors.New("no padding marker found")
	}
	return data[:lastIndex], nil
}

func safePad(b []byte, size int) []byte {
	if len(b) > size {
		return b[len(b)-size:]
	}
	return append(make([]byte, size-len(b)), b...)
}

func bytesTrimOneLineEnding(data []byte) []byte {
	if len(data) >= 2 && data[len(data)-2] == '\r' && data[len(data)-1] == '\n' {
		return data[:len(data)-2]
	}
	if len(data) >= 1 && data[len(data)-1] == '\n' {
		return data[:len(data)-1]
	}
	return data
}

func decodeBase64Clean(data []byte) ([]byte, error) {
	s := string(data)
	s = strings.ReplaceAll(s, "\r", "")
	s = strings.ReplaceAll(s, "\n", "")
	s = strings.ReplaceAll(s, " ", "")
	decoded, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return nil, fmt.Errorf("base64 decode failed: %w", err)
	}
	return decoded, nil
}
