crypto.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package crypto
  2. import (
  3. "crypto/rand"
  4. "crypto/rsa"
  5. "crypto/sha256"
  6. "crypto/tls"
  7. "crypto/x509"
  8. "crypto/x509/pkix"
  9. "encoding/hex"
  10. "encoding/json"
  11. "encoding/pem"
  12. "fmt"
  13. "math/big"
  14. "os"
  15. "time"
  16. "TengTuToolbox/localsend/logging"
  17. )
  18. // StoredSecurityContext holds PEM-encoded cert/key for config/server
  19. // and a fingerprint for discovery. Matches Dart model.
  20. type StoredSecurityContext struct {
  21. PrivateKey string `json:"privateKey"`
  22. Certificate string `json:"certificate"`
  23. CertificateHash string `json:"certificateHash"`
  24. }
  25. // ClientCertificateFunc returns a tls.Config.GetClientCertificate callback that
  26. // always presents the stored certificate. LocalSend apps (protocol v2.2) ask
  27. // every HTTPS client for a certificate and drop the handshake without one, but
  28. // they advertise a CA list our self-signed certificate can never satisfy, so
  29. // tls.Config.Certificates would silently send nothing.
  30. func (c *StoredSecurityContext) ClientCertificateFunc() (func(*tls.CertificateRequestInfo) (*tls.Certificate, error), error) {
  31. cert, err := tls.X509KeyPair([]byte(c.Certificate), []byte(c.PrivateKey))
  32. if err != nil {
  33. return nil, err
  34. }
  35. return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
  36. return &cert, nil
  37. }, nil
  38. }
  39. // GenerateKeys generates a new RSA key pair.
  40. func generateKeys() (*rsa.PrivateKey, error) {
  41. return rsa.GenerateKey(rand.Reader, 2048)
  42. }
  43. // encodePrivateKeyToPem encodes an RSA private key to PEM format (PKCS#1).
  44. func encodePrivateKeyToPem(privKey *rsa.PrivateKey) string {
  45. privBytes := x509.MarshalPKCS1PrivateKey(privKey)
  46. privPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})
  47. return string(privPem)
  48. }
  49. // generateSelfSignedCertificate creates a self-signed X.509 certificate DER bytes.
  50. func generateSelfSignedCertificate(privKey *rsa.PrivateKey, alias string) ([]byte, error) {
  51. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  52. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  53. if err != nil {
  54. return nil, fmt.Errorf("failed to generate serial number: %w", err)
  55. }
  56. template := x509.Certificate{
  57. SerialNumber: serialNumber,
  58. Subject: pkix.Name{
  59. Organization: []string{"LocalSend"},
  60. CommonName: alias,
  61. },
  62. NotBefore: time.Now(),
  63. NotAfter: time.Now().AddDate(10, 0, 0),
  64. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  65. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
  66. BasicConstraintsValid: true,
  67. }
  68. certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
  69. if err != nil {
  70. return nil, err
  71. }
  72. return certBytes, nil
  73. }
  74. // encodeCertificateToPem encodes certificate DER bytes to PEM format.
  75. func encodeCertificateToPem(certBytes []byte) string {
  76. certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
  77. return string(certPem)
  78. }
  79. // calculateCertificateHash calculates the SHA-256 hash of the certificate (DER format).
  80. func calculateCertificateHash(certBytes []byte) string {
  81. hash := sha256.Sum256(certBytes)
  82. return hex.EncodeToString(hash[:])
  83. }
  84. // GenerateSecurityContext creates a new security context with keys and a self-signed certificate.
  85. func GenerateSecurityContext(alias string, logger *logging.Logger) (*StoredSecurityContext, error) {
  86. privKey, err := generateKeys()
  87. if err != nil {
  88. return nil, fmt.Errorf("failed to generate RSA keys: %w", err)
  89. }
  90. certBytes, err := generateSelfSignedCertificate(privKey, alias)
  91. if err != nil {
  92. return nil, fmt.Errorf("failed to generate certificate: %w", err)
  93. }
  94. certHash := calculateCertificateHash(certBytes)
  95. ctx := &StoredSecurityContext{
  96. PrivateKey: encodePrivateKeyToPem(privKey),
  97. Certificate: encodeCertificateToPem(certBytes),
  98. CertificateHash: certHash,
  99. }
  100. if logger != nil {
  101. logger.Infow("Generated new Security Context", "fingerprint", ctx.CertificateHash)
  102. }
  103. return ctx, nil
  104. }
  105. // SaveSecurityContext saves the context as JSON to the specified path.
  106. func SaveSecurityContext(ctx *StoredSecurityContext, path string, logger *logging.Logger) error {
  107. file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  108. if err != nil {
  109. return fmt.Errorf("failed to create security context file '%s': %w", path, err)
  110. }
  111. defer file.Close()
  112. encoder := json.NewEncoder(file)
  113. encoder.SetIndent("", " ")
  114. if err := encoder.Encode(ctx); err != nil {
  115. return fmt.Errorf("failed to encode security context to '%s': %w", path, err)
  116. }
  117. if logger != nil {
  118. logger.Infow("Saved security context", "path", path)
  119. }
  120. return nil
  121. }
  122. // LoadSecurityContext loads the context from JSON from the specified path.
  123. func LoadSecurityContext(path string, logger *logging.Logger) (*StoredSecurityContext, error) {
  124. file, err := os.Open(path)
  125. if err != nil {
  126. if os.IsNotExist(err) {
  127. return nil, err
  128. }
  129. return nil, fmt.Errorf("failed to open security context file '%s': %w", path, err)
  130. }
  131. defer file.Close()
  132. var ctx StoredSecurityContext
  133. if err := json.NewDecoder(file).Decode(&ctx); err != nil {
  134. return nil, fmt.Errorf("failed to decode security context from '%s': %w", path, err)
  135. }
  136. if logger != nil {
  137. logger.Debugw("Loaded security context", "path", path, "fingerprint", ctx.CertificateHash)
  138. }
  139. return &ctx, nil
  140. }