diff options
| -rw-r--r-- | yubicrypt.go | 996 |
1 files changed, 0 insertions, 996 deletions
diff --git a/yubicrypt.go b/yubicrypt.go deleted file mode 100644 index b187ad0..0000000 --- a/yubicrypt.go +++ /dev/null @@ -1,996 +0,0 @@ -package main
-
-import (
- "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"
- "fmt"
- "math/big"
- "os"
- "path/filepath"
- "strings"
-
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/app"
- "fyne.io/fyne/v2/container"
- "fyne.io/fyne/v2/dialog"
- "fyne.io/fyne/v2/layout"
- "fyne.io/fyne/v2/theme"
- "fyne.io/fyne/v2/widget"
-
- "github.com/awnumar/memguard"
- "github.com/go-piv/piv-go/v2/piv"
-)
-
-// ecSignature represents an ECDSA signature with R and S components
-type ecSignature struct{ R, S *big.Int }
-
-// Supported algorithms
-const (
- AlgorithmECCP256 = "ECCP256"
- AlgorithmECCP384 = "ECCP384"
- AlgorithmED25519 = "ED25519"
-)
-
-var supportedAlgorithms = map[string]bool{
- AlgorithmECCP256: true,
- AlgorithmECCP384: true,
- AlgorithmED25519: true,
-}
-
-// Curve to algorithm mapping
-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,
-}
-
-// For Ed25519 - fixed sizes
-const (
- Ed25519SignatureSize = 64
- Ed25519PublicKeySize = 32
- Ed25519CombinedSize = Ed25519SignatureSize + Ed25519PublicKeySize // 96 bytes
-)
-
-const (
- minRSABits = 2048 // Minimum RSA key size accepted
-)
-
-// Supported RSA key sizes
-var supportedRSASizes = map[int]string{
- 2048: "RSA2048",
- 3072: "RSA3072",
- 4096: "RSA4096",
-}
-
-// GUI Structure
-type GUI struct {
- app fyne.App
- window fyne.Window
- themeToggle *widget.Button
- textArea *widget.Entry
- pinEntry *widget.Entry
- statusLabel *widget.Label
- publicKeyPath string
- currentTheme string
- encryptionUsed bool // Flag to track if encryption was already used in this session
-}
-
-func main() {
- defer memguard.Purge()
-
- gui := &GUI{
- app: app.NewWithID("oc2mx.net.yubicrypt"),
- currentTheme: "dark",
- encryptionUsed: false,
- }
-
- gui.window = gui.app.NewWindow("yubicrypt")
- gui.window.Resize(fyne.NewSize(800, 600))
-
- gui.createUI()
- gui.applyTheme()
-
- gui.window.SetContent(gui.createMainUI())
- gui.window.ShowAndRun()
-}
-
-func (g *GUI) createUI() {
- monospace := &fyne.TextStyle{Monospace: true}
-
- g.textArea = widget.NewMultiLineEntry()
- g.textArea.Wrapping = fyne.TextWrapOff
- g.textArea.TextStyle = *monospace
- g.textArea.SetPlaceHolder("Enter text to encrypt, sign, or paste encrypted content here...")
-
- g.pinEntry = widget.NewPasswordEntry()
- g.pinEntry.SetPlaceHolder("Enter PIN (max 8 chars)")
- g.pinEntry.Validator = func(s string) error {
- if len(s) > 8 {
- return fmt.Errorf("PIN must be max 8 characters")
- }
- return nil
- }
-
- g.statusLabel = widget.NewLabel("Ready")
- g.statusLabel.Wrapping = fyne.TextWrapWord
-
- // Theme Toggle Button
- g.themeToggle = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), g.toggleTheme)
-}
-
-func (g *GUI) createMainUI() fyne.CanvasObject {
- // Operation Buttons
- signBtn := widget.NewButtonWithIcon("Sign", theme.ConfirmIcon(), g.onSign)
- verifyBtn := widget.NewButtonWithIcon("Verify", theme.VisibilityIcon(), g.onVerify)
- padBtn := widget.NewButtonWithIcon("Pad", theme.ContentAddIcon(), g.onPad)
- unpadBtn := widget.NewButtonWithIcon("Unpad", theme.ContentRemoveIcon(), g.onUnpad)
- encryptBtn := widget.NewButtonWithIcon("Encrypt", theme.MailComposeIcon(), g.onEncrypt)
- decryptBtn := widget.NewButtonWithIcon("Decrypt", theme.MailForwardIcon(), g.onDecrypt)
-
- // Button Container
- buttonContainer := container.NewHBox(
- layout.NewSpacer(),
- signBtn,
- verifyBtn,
- padBtn,
- unpadBtn,
- encryptBtn,
- decryptBtn,
- layout.NewSpacer(),
- )
-
- clearBtn := widget.NewButtonWithIcon("Clear", theme.DeleteIcon(), g.onClear)
- pinContainer := container.NewHBox(
- widget.NewLabel("PIN:"),
- g.pinEntry,
- clearBtn,
- )
-
- mainContainer := container.NewBorder(
- container.NewVBox(
- container.NewHBox(
- layout.NewSpacer(),
- g.themeToggle,
- ),
- buttonContainer,
- widget.NewSeparator(),
- ),
- container.NewVBox(
- widget.NewSeparator(),
- pinContainer,
- g.statusLabel,
- ),
- nil,
- nil,
- container.NewScroll(g.textArea),
- )
-
- return mainContainer
-}
-
-func (g *GUI) toggleTheme() {
- if g.currentTheme == "dark" {
- g.app.Settings().SetTheme(theme.LightTheme())
- g.currentTheme = "light"
- g.themeToggle.SetIcon(theme.ViewRefreshIcon())
- } else {
- g.app.Settings().SetTheme(theme.DarkTheme())
- g.currentTheme = "dark"
- g.themeToggle.SetIcon(theme.ViewRefreshIcon())
- }
-}
-
-func (g *GUI) applyTheme() {
- if g.currentTheme == "dark" {
- g.app.Settings().SetTheme(theme.DarkTheme())
- } else {
- g.app.Settings().SetTheme(theme.LightTheme())
- }
-}
-
-func (g *GUI) onSign() {
- if g.pinEntry.Text == "" {
- g.statusLabel.SetText("Error: PIN required for signing")
- return
- }
-
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Error: No text to sign")
- return
- }
-
- result, err := g.signData([]byte(input), g.pinEntry.Text)
- if err != nil {
- g.statusLabel.SetText("Signing failed: " + err.Error())
- return
- }
-
- g.textArea.SetText(result)
- g.statusLabel.SetText("✓ Message signed successfully")
-}
-
-func (g *GUI) onVerify() {
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Error: No text to verify")
- return
- }
-
- err := g.verifyData([]byte(input))
- if err != nil {
- g.statusLabel.SetText("Verification failed: " + err.Error())
- return
- }
-
- g.statusLabel.SetText("✓ Signature is valid")
-}
-
-func (g *GUI) onEncrypt() {
- // If encryption was already used in this session, require new certificate
- if g.encryptionUsed {
- g.statusLabel.SetText("Please select a new certificate for encryption.")
- g.publicKeyPath = "" // Reset public key path to force new selection
- g.choosePublicKey()
- return
- }
-
- // If certificate already selected, encrypt immediately
- if g.publicKeyPath != "" {
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Error: No text to encrypt")
- return
- }
-
- result, err := g.encryptData([]byte(input), g.publicKeyPath)
- if err != nil {
- g.statusLabel.SetText("Encryption failed: " + err.Error())
- return
- }
-
- g.encryptionUsed = true
- g.textArea.SetText(result)
- g.statusLabel.SetText("✓ Encrypted with: " + filepath.Base(g.publicKeyPath))
- return
- }
-
- // No certificate selected yet, open file dialog
- g.choosePublicKey()
-}
-
-func (g *GUI) choosePublicKey() {
- dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
- if err != nil {
- g.statusLabel.SetText("Error selecting file: " + err.Error())
- return
- }
- if reader == nil {
- return // Dialog cancelled
- }
- defer reader.Close()
-
- path := reader.URI().Path()
- if filepath.Ext(path) != ".pem" {
- g.statusLabel.SetText("Error: Please select a .pem file")
- return
- }
-
- g.publicKeyPath = path
- g.encryptionUsed = false // Reset encryption flag when new certificate is selected
- g.statusLabel.SetText("Selected public key: " + filepath.Base(path) + " - Encrypting...")
-
- // AUTOMATIC ENCRYPTION AFTER CERTIFICATE SELECTION - LIKE GNUPG/AGE
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Selected: " + filepath.Base(path) + " - No text to encrypt")
- return
- }
-
- result, err := g.encryptData([]byte(input), g.publicKeyPath)
- if err != nil {
- g.statusLabel.SetText("Encryption failed: " + err.Error())
- return
- }
-
- // Mark encryption as used for this session
- g.encryptionUsed = true
- g.textArea.SetText(result)
- g.statusLabel.SetText("✓ Encrypted with: " + filepath.Base(path))
- }, g.window)
-}
-
-func (g *GUI) onDecrypt() {
- if g.pinEntry.Text == "" {
- g.statusLabel.SetText("Error: PIN required for decryption")
- return
- }
-
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Error: No text to decrypt")
- return
- }
-
- result, err := g.decryptData([]byte(input), g.pinEntry.Text)
- if err != nil {
- g.statusLabel.SetText("Decryption failed: " + err.Error())
- return
- }
-
- g.textArea.SetText(string(result))
- g.statusLabel.SetText("✓ Message decrypted successfully")
-}
-
-func (g *GUI) onClear() {
- g.textArea.SetText("")
- g.publicKeyPath = ""
- g.encryptionUsed = false // Reset encryption state on clear
-
- clipboard := g.app.Clipboard()
- if clipboard != nil {
- clipboard.SetContent("")
- }
-
- g.statusLabel.SetText("Cleared text area, clipboard and reset encryption state")
-}
-
-func (g *GUI) signData(data []byte, pin string) (string, error) {
- pinGuard := memguard.NewBufferFromBytes([]byte(pin))
- defer pinGuard.Destroy()
-
- sig, curveType, err := g.signDataInternal(pinGuard.Bytes(), data)
- if err != nil {
- return "", fmt.Errorf("signing failed: %v", err)
- }
-
- return string(data) + "\r\n-----BEGIN " + curveType + " SIGNATURE-----\r\n" +
- formatSignature(sig) + "-----END " + curveType + " SIGNATURE-----\r\n", nil
-}
-
-func (g *GUI) signDataInternal(pin, data []byte) (string, string, error) {
- yk, err := openYubiKey(0)
- 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: %v", err)
- }
-
- // Check for Ed25519 support
- if ed25519PubKey, ok := cert.PublicKey.(ed25519.PublicKey); ok {
- return g.signDataEd25519(pin, data, ed25519PubKey, yk)
- }
-
- // ECDSA support
- pubKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
- if !ok {
- return "", "", fmt.Errorf("public key is not ECDSA or Ed25519")
- }
-
- // Determine curve type
- algorithm, exists := curveToAlgorithm[pubKey.Curve]
- if !exists {
- return "", "", fmt.Errorf("unsupported curve: %v", pubKey.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: %v", err)
- }
-
- signer, ok := priv.(crypto.Signer)
- if !ok {
- return "", "", fmt.Errorf("key does not implement crypto.Signer")
- }
-
- // Use appropriate hash for the curve
- hashFunc := curveToHash[pubKey.Curve]
- var digest []byte
-
- switch hashFunc {
- case crypto.SHA256:
- h := sha256.Sum256(data)
- digest = h[:]
- case crypto.SHA384:
- h := sha512.Sum384(data)
- digest = h[:]
- default:
- return "", "", fmt.Errorf("unsupported hash algorithm for curve")
- }
-
- asn1sig, err := signer.Sign(rand.Reader, digest, hashFunc)
- if err != nil {
- return "", "", fmt.Errorf("ECDSA signing failed: %v", err)
- }
-
- var sig ecSignature
- if _, err := asn1.Unmarshal(asn1sig, &sig); err != nil {
- return "", "", fmt.Errorf("ASN.1 unmarshal failed: %v", err)
- }
-
- // Calculate appropriate padding based on curve
- curveSize := (pubKey.Curve.Params().BitSize + 7) / 8
- pad := func(b []byte) []byte {
- if len(b) > curveSize {
- b = b[len(b)-curveSize:]
- }
- return append(make([]byte, curveSize-len(b)), b...)
- }
-
- var raw []byte
- raw = append(raw, pad(pubKey.X.Bytes())...)
- raw = append(raw, pad(pubKey.Y.Bytes())...)
- raw = append(raw, pad(sig.R.Bytes())...)
- raw = append(raw, pad(sig.S.Bytes())...)
-
- return hex.EncodeToString(raw), algorithm, nil
-}
-
-func (g *GUI) signDataEd25519(pin, data []byte, pubKey ed25519.PublicKey, yk *piv.YubiKey) (string, string, error) {
- auth := piv.KeyAuth{PIN: string(pin)}
- priv, err := yk.PrivateKey(piv.SlotSignature, pubKey, auth)
- if err != nil {
- return "", "", fmt.Errorf("failed to get private key: %v", err)
- }
-
- signer, ok := priv.(crypto.Signer)
- if !ok {
- return "", "", fmt.Errorf("key does not implement crypto.Signer")
- }
-
- signature, err := signer.Sign(rand.Reader, data, crypto.Hash(0))
- if err != nil {
- return "", "", fmt.Errorf("Ed25519 signing failed: %v", err)
- }
-
- combined := append(signature, pubKey...)
- return hex.EncodeToString(combined), AlgorithmED25519, nil
-}
-
-func (g *GUI) verifyData(data []byte) error {
- s := string(data)
-
- // Search for all supported algorithms
- var algorithm string
- var beg, end string
-
- for algo := range supportedAlgorithms {
- begTest := fmt.Sprintf("\r\n-----BEGIN %s SIGNATURE-----\r\n", algo)
- endTest := fmt.Sprintf("-----END %s SIGNATURE-----\r\n", algo)
-
- if strings.Contains(s, begTest) && strings.Contains(s, endTest) {
- algorithm = algo
- beg = begTest
- end = endTest
- break
- }
- }
-
- if algorithm == "" {
- return fmt.Errorf("no supported signature block found")
- }
-
- i := strings.Index(s, beg)
- j := strings.Index(s, end)
-
- if i == -1 || j == -1 || j < i {
- return fmt.Errorf("invalid signature block")
- }
-
- original := []byte(s[:i])
- hexPart := s[i+len(beg):j]
- hexPart = strings.ReplaceAll(hexPart, "\r\n", "")
- hexPart = strings.ReplaceAll(hexPart, " ", "")
-
- combined, err := hex.DecodeString(hexPart)
- if err != nil {
- return fmt.Errorf("hex decode failed: %v", err)
- }
-
- switch algorithm {
- case AlgorithmED25519:
- return g.verifyEd25519(original, combined)
- case AlgorithmECCP256, AlgorithmECCP384:
- return g.verifyECDSA(original, combined, algorithm)
- default:
- return fmt.Errorf("unsupported algorithm: %s", algorithm)
- }
-}
-
-func (g *GUI) verifyEd25519(data, combined []byte) error {
- if len(combined) != Ed25519CombinedSize {
- return fmt.Errorf("invalid Ed25519 signature block")
- }
-
- signature := combined[:Ed25519SignatureSize]
- publicKey := combined[Ed25519SignatureSize:]
-
- if !ed25519.Verify(publicKey, data, signature) {
- return fmt.Errorf("Ed25519 signature verification failed")
- }
-
- return nil
-}
-
-func (g *GUI) verifyECDSA(data, combined []byte, algorithm string) error {
- var curve elliptic.Curve
- switch algorithm {
- case AlgorithmECCP256:
- curve = elliptic.P256()
- case AlgorithmECCP384:
- curve = elliptic.P384()
- default:
- return fmt.Errorf("unsupported ECDSA algorithm: %s", algorithm)
- }
-
- curveSize := (curve.Params().BitSize + 7) / 8
- expectedBytes := curveSize * 4
-
- if len(combined) != expectedBytes {
- return fmt.Errorf("invalid signature block size")
- }
-
- x := new(big.Int).SetBytes(combined[0:curveSize])
- y := new(big.Int).SetBytes(combined[curveSize:curveSize*2])
- r := new(big.Int).SetBytes(combined[curveSize*2:curveSize*3])
- sVal := new(big.Int).SetBytes(combined[curveSize*3:curveSize*4])
-
- pub := &ecdsa.PublicKey{Curve: curve, X: x, Y: y}
-
- hashFunc := curveToHash[curve]
- var digest []byte
-
- switch hashFunc {
- case crypto.SHA256:
- h := sha256.Sum256(data)
- digest = h[:]
- case crypto.SHA384:
- h := sha512.Sum384(data)
- digest = h[:]
- default:
- return fmt.Errorf("unsupported hash algorithm")
- }
-
- if !ecdsa.Verify(pub, digest, r, sVal) {
- return fmt.Errorf("signature is not valid")
- }
-
- return nil
-}
-
-func (g *GUI) encryptData(data []byte, pubKeyFile string) (string, error) {
- pubKey, err := loadRSAPublicKey(pubKeyFile)
- if err != nil {
- return "", fmt.Errorf("failed to load public key: %v", err)
- }
-
- // Generate AES key - will be automatically cleaned up by memguard
- aesKeyGuard := memguard.NewBuffer(32)
- defer aesKeyGuard.Destroy() // Secure cleanup after use
- if _, err := rand.Read(aesKeyGuard.Bytes()); err != nil {
- return "", fmt.Errorf("failed to generate AES key: %v", err)
- }
-
- // Encrypt AES key with RSA public key
- encryptedKey, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, aesKeyGuard.Bytes())
- if err != nil {
- return "", fmt.Errorf("RSA encryption failed: %v", err)
- }
- defer memguard.WipeBytes(encryptedKey)
-
- // Encrypt data with AES key
- encryptedData, err := encryptAES(data, aesKeyGuard.Bytes())
- if err != nil {
- return "", fmt.Errorf("AES encryption failed: %v", err)
- }
- defer memguard.WipeBytes(encryptedData)
-
- // Combine encrypted key and data
- combined := append(encryptedKey, encryptedData...)
- defer memguard.WipeBytes(combined)
-
- base64Str := base64.StdEncoding.EncodeToString(combined)
- return formatBase64(base64Str), nil
-}
-
-func (g *GUI) decryptData(data []byte, pin string) ([]byte, error) {
- pinGuard := memguard.NewBufferFromBytes([]byte(pin))
- defer pinGuard.Destroy()
-
- s := string(data)
- s = strings.ReplaceAll(s, "\r\n", "")
- s = strings.ReplaceAll(s, " ", "")
-
- combined, err := base64.StdEncoding.DecodeString(s)
- if err != nil {
- return nil, fmt.Errorf("base64 decode failed: %v", err)
- }
- defer memguard.WipeBytes(combined)
-
- yk, err := openYubiKey(0)
- if err != nil {
- return nil, fmt.Errorf("failed to open YubiKey: %v", err)
- }
- defer yk.Close()
-
- cert, err := yk.Certificate(piv.SlotKeyManagement)
- if err != nil {
- return nil, fmt.Errorf("failed to get certificate from slot 9d: %v", 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:]
- defer memguard.WipeBytes(encryptedKey)
-
- auth := piv.KeyAuth{PIN: pin}
- priv, err := yk.PrivateKey(piv.SlotKeyManagement, cert.PublicKey, auth)
- if err != nil {
- return nil, fmt.Errorf("failed to get private key: %v", 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: %v", 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: %v", err)
- }
-
- return decryptedData, nil
-}
-
-func normalizeCRLF(data []byte) []byte {
- s := string(data)
- s = strings.ReplaceAll(s, "\r\n", "\n")
- s = strings.ReplaceAll(s, "\r", "\n")
- return []byte(strings.ReplaceAll(s, "\n", "\r\n"))
-}
-
-func formatSignature(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 formatBase64(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) (string, error) {
- const blockSize = 4096
- const minSize = 4096
- const lineLength = 76
-
- if len(data) == 0 {
- return "", errors.New("empty data cannot be padded")
- }
-
- // Preserve original text with line breaks intact
- originalText := string(data)
- currentSize := len(originalText)
-
- // Determine target size
- var targetSize int
- if currentSize < minSize {
- targetSize = minSize
- } else {
- targetSize = ((currentSize/blockSize) + 1) * blockSize
- }
-
- // Length information - IMPORTANT: length of ORIGINAL text
- lengthInfo := fmt.Sprintf("===LENGTH:%d===", len(originalText))
- totalMarkerLength := len("===PADDING===") + len(lengthInfo)
- paddingNeeded := targetSize - currentSize - totalMarkerLength
-
- if paddingNeeded < 0 {
- targetSize += blockSize
- paddingNeeded = targetSize - currentSize - totalMarkerLength
- }
-
- // Generate random padding
- randomBytes := make([]byte, (paddingNeeded+1)/2)
- if _, err := rand.Read(randomBytes); err != nil {
- return "", fmt.Errorf("error generating random padding: %v", err)
- }
-
- randomHex := hex.EncodeToString(randomBytes)
- if len(randomHex) > paddingNeeded {
- randomHex = randomHex[:paddingNeeded]
- }
-
- // Append padding at the END (after signature) and format it properly
- paddingContent := "===PADDING===" + randomHex + lengthInfo
- formattedPadding := formatTo76Chars(paddingContent)
-
- // Combine original text with formatted padding
- paddedContent := originalText + formattedPadding
-
- return paddedContent, nil
-}
-
-func secureUnpadMessage(data string) ([]byte, error) {
- if data == "" {
- return []byte{}, nil
- }
-
- // Search for padding marker in the original formatted text
- paddingIndex := strings.Index(data, "===PADDING===")
- if paddingIndex == -1 {
- // No padding found, return original text with all formatting
- return []byte(data), nil
- }
-
- // Return everything before the padding marker (preserves signature structure)
- return []byte(data[:paddingIndex]), nil
-}
-
-// Helper function to format only the padding part to 76 characters per line with CRLF
-func formatTo76Chars(text string) string {
- const lineLength = 76
- var result strings.Builder
-
- // Remove existing line breaks first to avoid double formatting
- cleanText := strings.ReplaceAll(text, "\r\n", "")
- cleanText = strings.ReplaceAll(cleanText, "\n", "")
-
- for i := 0; i < len(cleanText); i += lineLength {
- end := i + lineLength
- if end > len(cleanText) {
- end = len(cleanText)
- }
- result.WriteString(cleanText[i:end])
- result.WriteString("\r\n")
- }
- return result.String()
-}
-
-func (g *GUI) onPad() {
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Error: No text to pad")
- return
- }
-
- result, err := securePadMessage([]byte(input))
- if err != nil {
- g.statusLabel.SetText("Padding failed: " + err.Error())
- return
- }
-
- g.textArea.SetText(result)
-
- // Show padding statistics
- originalLen := len(input)
- paddedLen := len(result)
- lines := strings.Count(result, "\r\n") + 1
- g.statusLabel.SetText(fmt.Sprintf("✓ Padded: %d → %d bytes (%d lines)",
- originalLen, paddedLen, lines))
-}
-
-func (g *GUI) onUnpad() {
- input := g.textArea.Text
- if input == "" {
- g.statusLabel.SetText("Error: No text to unpad")
- return
- }
-
- result, err := secureUnpadMessage(input)
- if err != nil {
- g.statusLabel.SetText("Unpadding failed: " + err.Error())
- return
- }
-
- g.textArea.SetText(string(result))
- g.statusLabel.SetText("✓ Message unpadded successfully")
-}
-
-// checkRSASecurity checks RSA key length and issues warnings/errors
-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)
- }
-
- // Check if key size is supported
- 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: %v", err)
- }
- defer memguard.WipeBytes(data)
-
- block, _ := pem.Decode(data)
- if block == nil {
- return nil, fmt.Errorf("no PEM 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: %v", err)
- }
- pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
- if !ok {
- return nil, fmt.Errorf("certificate does not contain RSA public key")
- }
- // Security check for certificate
- 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: %v", err)
- }
- pubKey, ok := pubInterface.(*rsa.PublicKey)
- if !ok {
- return nil, fmt.Errorf("not an RSA public key")
- }
- // Security check for 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: %v", err)
- }
- // Security check for RSA public key
- if err := checkRSASecurity(pubKey, "in file "+filename); err != nil {
- return nil, err
- }
- return pubKey, nil
-
- default:
- return nil, fmt.Errorf("unsupported PEM 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
- }
-
- ciphertext := gcm.Seal(nonce, nonce, data, nil)
- return ciphertext, 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: %v", 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)
-}
|
