...
This commit is contained in:
		
							
								
								
									
										488
									
								
								pkg/servers/webdavserver/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								pkg/servers/webdavserver/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,488 @@ | ||||
| package webdavserver | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/rsa" | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"crypto/x509/pkix" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/pem" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"math/big" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/net/webdav" | ||||
| ) | ||||
|  | ||||
| // Config holds the configuration for the WebDAV server | ||||
| type Config struct { | ||||
| 	Host                string | ||||
| 	Port                int | ||||
| 	BasePath            string | ||||
| 	FileSystem          string | ||||
| 	ReadTimeout         time.Duration | ||||
| 	WriteTimeout        time.Duration | ||||
| 	DebugMode           bool | ||||
| 	UseAuth             bool | ||||
| 	Username            string | ||||
| 	Password            string | ||||
| 	UseHTTPS            bool | ||||
| 	CertFile            string | ||||
| 	KeyFile             string | ||||
| 	AutoGenerateCerts   bool | ||||
| 	CertValidityDays    int | ||||
| 	CertOrganization    string | ||||
| } | ||||
|  | ||||
| // Server represents the WebDAV server | ||||
| type Server struct { | ||||
| 	config     Config | ||||
| 	httpServer *http.Server | ||||
| 	handler    *webdav.Handler | ||||
| 	debugLog   func(format string, v ...interface{}) | ||||
| } | ||||
|  | ||||
| // responseWrapper wraps http.ResponseWriter to capture the status code | ||||
| type responseWrapper struct { | ||||
| 	http.ResponseWriter | ||||
| 	statusCode int | ||||
| } | ||||
|  | ||||
| // WriteHeader captures the status code and passes it to the wrapped ResponseWriter | ||||
| func (rw *responseWrapper) WriteHeader(code int) { | ||||
| 	rw.statusCode = code | ||||
| 	rw.ResponseWriter.WriteHeader(code) | ||||
| } | ||||
|  | ||||
| // Write captures a 200 status code if WriteHeader hasn't been called yet | ||||
| func (rw *responseWrapper) Write(b []byte) (int, error) { | ||||
| 	if rw.statusCode == 0 { | ||||
| 		rw.statusCode = http.StatusOK | ||||
| 	} | ||||
| 	return rw.ResponseWriter.Write(b) | ||||
| } | ||||
|  | ||||
| // NewServer creates a new WebDAV server | ||||
| func NewServer(config Config) (*Server, error) { | ||||
| 	log.Printf("Creating new WebDAV server with config: host=%s, port=%d, basePath=%s, fileSystem=%s, debug=%v, auth=%v, https=%v", | ||||
| 		config.Host, config.Port, config.BasePath, config.FileSystem, config.DebugMode, config.UseAuth, config.UseHTTPS) | ||||
|  | ||||
| 	// Ensure the file system directory exists | ||||
| 	if err := os.MkdirAll(config.FileSystem, 0755); err != nil { | ||||
| 		log.Printf("ERROR: Failed to create file system directory %s: %v", config.FileSystem, err) | ||||
| 		return nil, fmt.Errorf("failed to create file system directory: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	// Log the file system path | ||||
| 	log.Printf("Using file system path: %s", config.FileSystem) | ||||
| 	 | ||||
| 	// Create debug logger function | ||||
| 	debugLog := func(format string, v ...interface{}) { | ||||
| 		if config.DebugMode { | ||||
| 			log.Printf("[WebDAV DEBUG] "+format, v...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Create WebDAV handler | ||||
| 	handler := &webdav.Handler{ | ||||
| 		FileSystem: webdav.Dir(config.FileSystem), | ||||
| 		LockSystem: webdav.NewMemLS(), | ||||
| 		Logger: func(r *http.Request, err error) { | ||||
| 			if err != nil { | ||||
| 				log.Printf("WebDAV error: %s %s - %v", r.Method, r.URL.Path, err) | ||||
| 			} else { | ||||
| 				log.Printf("WebDAV: %s %s", r.Method, r.URL.Path) | ||||
| 			} | ||||
| 			 | ||||
| 			// Additional debug logging | ||||
| 			if config.DebugMode { | ||||
| 				log.Printf("[WebDAV DEBUG] Request Headers: %v", r.Header) | ||||
| 				log.Printf("[WebDAV DEBUG] Request RemoteAddr: %s", r.RemoteAddr) | ||||
| 				log.Printf("[WebDAV DEBUG] Request UserAgent: %s", r.UserAgent()) | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	// Create HTTP server | ||||
| 	httpServer := &http.Server{ | ||||
| 		Addr:         fmt.Sprintf("%s:%d", config.Host, config.Port), | ||||
| 		ReadTimeout:  config.ReadTimeout, | ||||
| 		WriteTimeout: config.WriteTimeout, | ||||
| 	} | ||||
|  | ||||
| 	return &Server{ | ||||
| 		config:     config, | ||||
| 		httpServer: httpServer, | ||||
| 		handler:    handler, | ||||
| 		debugLog:   debugLog, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Start starts the WebDAV server | ||||
| func (s *Server) Start() error { | ||||
| 	log.Printf("Starting WebDAV server at %s with file system %s", s.httpServer.Addr, s.config.FileSystem) | ||||
|  | ||||
| 	// Create a mux to handle the WebDAV requests | ||||
| 	mux := http.NewServeMux() | ||||
|  | ||||
| 	// Register the WebDAV handler at the base path | ||||
| 	mux.HandleFunc(s.config.BasePath, func(w http.ResponseWriter, r *http.Request) { | ||||
| 		// Enhanced debug logging | ||||
| 		s.debugLog("Received request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) | ||||
| 		s.debugLog("Request Protocol: %s", r.Proto) | ||||
| 		s.debugLog("User-Agent: %s", r.UserAgent()) | ||||
| 		 | ||||
| 		// Log all request headers | ||||
| 		for name, values := range r.Header { | ||||
| 			s.debugLog("Header: %s = %s", name, values) | ||||
| 		} | ||||
| 		 | ||||
| 		// Log request depth (important for WebDAV) | ||||
| 		s.debugLog("Depth header: %s", r.Header.Get("Depth")) | ||||
| 		 | ||||
| 		// Add CORS headers | ||||
| 		w.Header().Set("Access-Control-Allow-Origin", "*") | ||||
| 		w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE") | ||||
| 		w.Header().Set("Access-Control-Allow-Headers", "Depth, Authorization, Content-Type, X-Requested-With") | ||||
| 		w.Header().Set("Access-Control-Max-Age", "86400") | ||||
|  | ||||
| 		// Handle OPTIONS requests for CORS and WebDAV discovery | ||||
| 		if r.Method == "OPTIONS" { | ||||
| 			// Add WebDAV specific headers for OPTIONS responses | ||||
| 			w.Header().Set("DAV", "1, 2") | ||||
| 			w.Header().Set("MS-Author-Via", "DAV") | ||||
| 			w.Header().Set("Allow", "OPTIONS, GET, HEAD, POST, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE") | ||||
| 			 | ||||
| 			// Check if this is a macOS WebDAV client | ||||
| 			isMacOSClient := strings.Contains(r.UserAgent(), "WebDAVFS") ||  | ||||
| 				strings.Contains(r.UserAgent(), "WebDAVLib") ||  | ||||
| 				strings.Contains(r.UserAgent(), "Darwin") | ||||
| 			 | ||||
| 			if isMacOSClient { | ||||
| 				s.debugLog("Detected macOS WebDAV client OPTIONS request, adding macOS-specific headers") | ||||
| 				// These headers help macOS Finder with WebDAV compatibility | ||||
| 				w.Header().Set("X-Dav-Server", "HeroLauncher WebDAV Server") | ||||
| 			} | ||||
| 			 | ||||
| 			w.WriteHeader(http.StatusOK) | ||||
| 			return | ||||
| 		} | ||||
| 		 | ||||
| 		// Handle authentication if enabled | ||||
| 		if s.config.UseAuth { | ||||
| 			s.debugLog("Authentication required for request") | ||||
| 			auth := r.Header.Get("Authorization") | ||||
| 			 | ||||
| 			// Check if this is a macOS WebDAV client | ||||
| 			isMacOSClient := strings.Contains(r.UserAgent(), "WebDAVFS") ||  | ||||
| 				strings.Contains(r.UserAgent(), "WebDAVLib") ||  | ||||
| 				strings.Contains(r.UserAgent(), "Darwin") | ||||
| 			 | ||||
| 			// Special handling for OPTIONS requests from macOS clients | ||||
| 			if r.Method == "OPTIONS" && isMacOSClient { | ||||
| 				s.debugLog("Detected macOS WebDAV client OPTIONS request, allowing without auth") | ||||
| 				// macOS sends OPTIONS without auth first, we need to let this through | ||||
| 				// but still send the auth challenge | ||||
| 				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"") | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			if auth == "" { | ||||
| 				s.debugLog("No Authorization header provided for non-OPTIONS request") | ||||
| 				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"") | ||||
| 				http.Error(w, "Unauthorized", http.StatusUnauthorized) | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			// Parse the authentication header | ||||
| 			if !strings.HasPrefix(auth, "Basic ") { | ||||
| 				s.debugLog("Invalid Authorization header format: %s", auth) | ||||
| 				http.Error(w, "Invalid authorization header", http.StatusBadRequest) | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			payload, err := base64.StdEncoding.DecodeString(auth[6:]) | ||||
| 			if err != nil { | ||||
| 				s.debugLog("Failed to decode Authorization header: %v, raw header: %s", err, auth) | ||||
| 				http.Error(w, "Invalid authorization header", http.StatusBadRequest) | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			pair := strings.SplitN(string(payload), ":", 2) | ||||
| 			if len(pair) != 2 { | ||||
| 				s.debugLog("Invalid credential format: could not split into username:password") | ||||
| 				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"") | ||||
| 				http.Error(w, "Unauthorized", http.StatusUnauthorized) | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			// Log username for debugging (don't log password) | ||||
| 			s.debugLog("Received credentials for user: %s", pair[0]) | ||||
| 			 | ||||
| 			if pair[0] != s.config.Username || pair[1] != s.config.Password { | ||||
| 				s.debugLog("Invalid credentials provided, expected user: %s", s.config.Username) | ||||
| 				w.Header().Set("WWW-Authenticate", "Basic realm=\"WebDAV Server\"") | ||||
| 				http.Error(w, "Unauthorized", http.StatusUnauthorized) | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			s.debugLog("Authentication successful for user: %s", pair[0]) | ||||
| 		} | ||||
|  | ||||
| 		// Log request body for WebDAV methods | ||||
| 		if r.Method == "PROPFIND" || r.Method == "PROPPATCH" || r.Method == "REPORT" || r.Method == "PUT" { | ||||
| 			if r.Body != nil { | ||||
| 				bodyBytes, err := io.ReadAll(r.Body) | ||||
| 				if err == nil { | ||||
| 					s.debugLog("Request body: %s", string(bodyBytes)) | ||||
| 					// Create a new reader with the same content | ||||
| 					r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Add macOS-specific headers for better compatibility | ||||
| 		isMacOSClient := strings.Contains(r.UserAgent(), "WebDAVFS") ||  | ||||
| 			strings.Contains(r.UserAgent(), "WebDAVLib") ||  | ||||
| 			strings.Contains(r.UserAgent(), "Darwin") | ||||
| 		 | ||||
| 		if isMacOSClient { | ||||
| 			s.debugLog("Adding macOS-specific headers for better compatibility") | ||||
| 			// These headers help macOS Finder with WebDAV compatibility | ||||
| 			w.Header().Set("MS-Author-Via", "DAV") | ||||
| 			w.Header().Set("X-Dav-Server", "HeroLauncher WebDAV Server") | ||||
| 			w.Header().Set("DAV", "1, 2") | ||||
| 			 | ||||
| 			// Special handling for PROPFIND requests from macOS | ||||
| 			if r.Method == "PROPFIND" { | ||||
| 				s.debugLog("Handling macOS PROPFIND request with special compatibility") | ||||
| 				// Make sure Content-Type is set correctly for PROPFIND responses | ||||
| 				w.Header().Set("Content-Type", "text/xml; charset=utf-8") | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Create a response wrapper to capture the response | ||||
| 		responseWrapper := &responseWrapper{ResponseWriter: w} | ||||
|  | ||||
| 		// Handle WebDAV requests | ||||
| 		s.debugLog("Handling WebDAV request: %s %s", r.Method, r.URL.Path) | ||||
| 		s.handler.ServeHTTP(responseWrapper, r) | ||||
|  | ||||
| 		// Log response details | ||||
| 		s.debugLog("Response status: %d", responseWrapper.statusCode) | ||||
| 		s.debugLog("Response content type: %s", w.Header().Get("Content-Type")) | ||||
| 		 | ||||
| 		// Log detailed information for debugging connection issues | ||||
| 		if responseWrapper.statusCode >= 400 { | ||||
| 			s.debugLog("ERROR: WebDAV request failed with status %d", responseWrapper.statusCode) | ||||
| 			s.debugLog("Request method: %s, path: %s", r.Method, r.URL.Path) | ||||
| 			s.debugLog("Response headers: %v", w.Header()) | ||||
| 		} else { | ||||
| 			s.debugLog("WebDAV request succeeded with status %d", responseWrapper.statusCode) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Set the mux as the HTTP server handler | ||||
| 	s.httpServer.Handler = mux | ||||
|  | ||||
| 	// Start the server with HTTPS if configured | ||||
| 	var err error | ||||
| 	if s.config.UseHTTPS { | ||||
| 		// Check if certificate files exist or need to be generated | ||||
| 		if (s.config.CertFile == "" || s.config.KeyFile == "") && !s.config.AutoGenerateCerts { | ||||
| 			log.Printf("ERROR: HTTPS enabled but certificate or key file not provided and auto-generation is disabled") | ||||
| 			return fmt.Errorf("HTTPS enabled but certificate or key file not provided and auto-generation is disabled") | ||||
| 		} | ||||
| 		 | ||||
| 		// Auto-generate certificates if needed | ||||
| 		if (s.config.CertFile == "" || s.config.KeyFile == "" ||  | ||||
| 			!fileExists(s.config.CertFile) || !fileExists(s.config.KeyFile)) &&  | ||||
| 			s.config.AutoGenerateCerts { | ||||
| 			 | ||||
| 			s.debugLog("Certificate files not found, auto-generating...") | ||||
| 			 | ||||
| 			// Get base directory from the file system path | ||||
| 			baseDir := filepath.Dir(s.config.FileSystem) | ||||
| 			 | ||||
| 			// Create certificates directory if it doesn't exist | ||||
| 			certsDir := filepath.Join(baseDir, "certificates") | ||||
| 			if err := os.MkdirAll(certsDir, 0755); err != nil { | ||||
| 				log.Printf("ERROR: Failed to create certificates directory: %v", err) | ||||
| 				return fmt.Errorf("failed to create certificates directory: %w", err) | ||||
| 			} | ||||
| 			 | ||||
| 			// Set default certificate paths if not provided | ||||
| 			if s.config.CertFile == "" { | ||||
| 				s.config.CertFile = filepath.Join(certsDir, "webdav.crt") | ||||
| 			} | ||||
| 			if s.config.KeyFile == "" { | ||||
| 				s.config.KeyFile = filepath.Join(certsDir, "webdav.key") | ||||
| 			} | ||||
| 			 | ||||
| 			// Generate certificates | ||||
| 			if err := generateCertificate( | ||||
| 				s.config.CertFile,  | ||||
| 				s.config.KeyFile,  | ||||
| 				s.config.CertOrganization,  | ||||
| 				s.config.CertValidityDays, | ||||
| 				s.debugLog, | ||||
| 			); err != nil { | ||||
| 				log.Printf("ERROR: Failed to generate certificates: %v", err) | ||||
| 				return fmt.Errorf("failed to generate certificates: %w", err) | ||||
| 			} | ||||
| 			 | ||||
| 			log.Printf("Successfully generated self-signed certificates at %s and %s",  | ||||
| 				s.config.CertFile, s.config.KeyFile) | ||||
| 		} | ||||
| 		 | ||||
| 		// Verify certificate files exist | ||||
| 		if !fileExists(s.config.CertFile) || !fileExists(s.config.KeyFile) { | ||||
| 			log.Printf("ERROR: Certificate files not found at %s and/or %s",  | ||||
| 				s.config.CertFile, s.config.KeyFile) | ||||
| 			return fmt.Errorf("certificate files not found") | ||||
| 		} | ||||
| 		 | ||||
| 		// Configure TLS | ||||
| 		tlsConfig := &tls.Config{ | ||||
| 			MinVersion: tls.VersionTLS12, | ||||
| 		} | ||||
| 		s.httpServer.TLSConfig = tlsConfig | ||||
| 		 | ||||
| 		log.Printf("Starting WebDAV server with HTTPS on %s using certificates: %s, %s",  | ||||
| 			s.httpServer.Addr, s.config.CertFile, s.config.KeyFile) | ||||
| 		err = s.httpServer.ListenAndServeTLS(s.config.CertFile, s.config.KeyFile) | ||||
| 	} else { | ||||
| 		log.Printf("Starting WebDAV server with HTTP on %s", s.httpServer.Addr) | ||||
| 		err = s.httpServer.ListenAndServe() | ||||
| 	} | ||||
| 	 | ||||
| 	if err != nil && err != http.ErrServerClosed { | ||||
| 		log.Printf("ERROR: WebDAV server failed to start: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Stop stops the WebDAV server | ||||
| func (s *Server) Stop() error { | ||||
| 	log.Printf("Stopping WebDAV server at %s", s.httpServer.Addr) | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||
| 	defer cancel() | ||||
| 	err := s.httpServer.Shutdown(ctx) | ||||
| 	if err != nil { | ||||
| 		log.Printf("ERROR: Failed to stop WebDAV server: %v", err) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // DefaultConfig returns the default configuration for the WebDAV server | ||||
| func DefaultConfig() Config { | ||||
| 	// Use system temp directory as default base path | ||||
| 	defaultBasePath := filepath.Join(os.TempDir(), "heroagent") | ||||
| 	 | ||||
| 	return Config{ | ||||
| 		Host:              "0.0.0.0", | ||||
| 		Port:              9999, | ||||
| 		BasePath:          "/", | ||||
| 		FileSystem:        defaultBasePath, | ||||
| 		ReadTimeout:       30 * time.Second, | ||||
| 		WriteTimeout:      30 * time.Second, | ||||
| 		DebugMode:         false, | ||||
| 		UseAuth:           false, | ||||
| 		Username:          "admin", | ||||
| 		Password:          "1234", | ||||
| 		UseHTTPS:          false, | ||||
| 		CertFile:          "", | ||||
| 		KeyFile:           "", | ||||
| 		AutoGenerateCerts: true, | ||||
| 		CertValidityDays:  365, | ||||
| 		CertOrganization:  "HeroLauncher WebDAV Server", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // fileExists checks if a file exists and is not a directory | ||||
| func fileExists(filename string) bool { | ||||
| 	info, err := os.Stat(filename) | ||||
| 	if os.IsNotExist(err) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return err == nil && !info.IsDir() | ||||
| } | ||||
|  | ||||
| // generateCertificate creates a self-signed TLS certificate and key | ||||
| func generateCertificate(certFile, keyFile, organization string, validityDays int, debugLog func(format string, args ...interface{})) error { | ||||
| 	debugLog("Generating self-signed certificate: certFile=%s, keyFile=%s, organization=%s, validityDays=%d",  | ||||
| 		certFile, keyFile, organization, validityDays) | ||||
| 	 | ||||
| 	// Generate private key | ||||
| 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to generate private key: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	// Prepare certificate template | ||||
| 	notBefore := time.Now() | ||||
| 	notAfter := notBefore.Add(time.Duration(validityDays) * 24 * time.Hour) | ||||
| 	 | ||||
| 	serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to generate serial number: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	template := x509.Certificate{ | ||||
| 		SerialNumber: serialNumber, | ||||
| 		Subject: pkix.Name{ | ||||
| 			Organization: []string{organization}, | ||||
| 			CommonName:   "localhost", | ||||
| 		}, | ||||
| 		NotBefore:             notBefore, | ||||
| 		NotAfter:              notAfter, | ||||
| 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||||
| 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||||
| 		BasicConstraintsValid: true, | ||||
| 		IPAddresses:           []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, | ||||
| 		DNSNames:              []string{"localhost"}, | ||||
| 	} | ||||
| 	 | ||||
| 	// Create certificate | ||||
| 	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create certificate: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	// Write certificate to file | ||||
| 	certOut, err := os.Create(certFile) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to open %s for writing: %w", certFile, err) | ||||
| 	} | ||||
| 	defer certOut.Close() | ||||
| 	 | ||||
| 	if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { | ||||
| 		return fmt.Errorf("failed to write certificate to file: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	// Write private key to file | ||||
| 	keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to open %s for writing: %w", keyFile, err) | ||||
| 	} | ||||
| 	defer keyOut.Close() | ||||
| 	 | ||||
| 	privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} | ||||
| 	if err := pem.Encode(keyOut, privateKeyPEM); err != nil { | ||||
| 		return fmt.Errorf("failed to write private key to file: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	debugLog("Successfully generated self-signed certificate valid for %d days", validityDays) | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user