LocalSend-compatible file-transfer TUI for headless Arch/Omarchy servers. - Pure-stdlib implementation of the LocalSend v2 protocol (discovery, HTTPS with matching cert fingerprint, send/receive, PIN). - Bubble Tea TUI: Devices, Transfers, Manage (received-file housekeeping) and Settings, theme-aware on Omarchy. - Dual-mode install.sh: curl-pipe download or build-from-clone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.2 KiB
Go
68 lines
2.2 KiB
Go
// Package security generates and handles the self-signed TLS identity used for
|
|
// LocalSend's encrypted (HTTPS) mode.
|
|
//
|
|
// The LocalSend fingerprint is the SHA-256 of the certificate's DER bytes,
|
|
// encoded as uppercase hex (verified against the official client's stored
|
|
// certificateHash). Peers do not validate the certificate chain; they pin this
|
|
// fingerprint, which is advertised in the discovery announce.
|
|
package security
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Identity is a self-signed certificate, its key, and its LocalSend fingerprint.
|
|
type Identity struct {
|
|
CertPEM string
|
|
KeyPEM string
|
|
Fingerprint string
|
|
}
|
|
|
|
// Fingerprint returns the LocalSend fingerprint of a DER-encoded certificate:
|
|
// uppercase hex of its SHA-256.
|
|
func Fingerprint(der []byte) string {
|
|
sum := sha256.Sum256(der)
|
|
return strings.ToUpper(hex.EncodeToString(sum[:]))
|
|
}
|
|
|
|
// Generate creates a fresh RSA-2048 self-signed certificate matching the shape
|
|
// the official LocalSend client uses (CN "LocalSend User", ~10-year validity).
|
|
func Generate() (Identity, error) {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return Identity{}, err
|
|
}
|
|
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
if err != nil {
|
|
return Identity{}, err
|
|
}
|
|
tmpl := x509.Certificate{
|
|
SerialNumber: serial,
|
|
Subject: pkix.Name{CommonName: "LocalSend User"},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
BasicConstraintsValid: true,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key)
|
|
if err != nil {
|
|
return Identity{}, err
|
|
}
|
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
|
return Identity{
|
|
CertPEM: string(certPEM),
|
|
KeyPEM: string(keyPEM),
|
|
Fingerprint: Fingerprint(der),
|
|
}, nil
|
|
}
|