package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "encoding/pem" "errors" "fmt" "math/big" "net" "os" "path/filepath" "time" ) func certificateFingerprintDER(der []byte) string { sum := sha256.Sum256(der) return hex.EncodeToString(sum[:]) } func certificateFingerprint(certFile string) (string, error) { raw, err := os.ReadFile(certFile) if err != nil { return "", err } block, _ := pem.Decode(raw) if block == nil || block.Type != "CERTIFICATE" { return "", fmt.Errorf("%s does not contain a PEM certificate", certFile) } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return "", fmt.Errorf("parse certificate: %w", err) } return certificateFingerprintDER(cert.Raw), nil } func atomicWrite(path string, data []byte, mode os.FileMode) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0750); err != nil { return err } tmp, err := os.CreateTemp(dir, ".rustproxy-agent-*") if err != nil { return err } tmpName := tmp.Name() defer os.Remove(tmpName) if err := tmp.Chmod(mode); err != nil { tmp.Close() return err } if _, err := tmp.Write(data); err != nil { tmp.Close() return err } if err := tmp.Sync(); err != nil { tmp.Close() return err } if err := tmp.Close(); err != nil { return err } return os.Rename(tmpName, path) } func ensureSelfSignedCertificate(certFile, keyFile, hostname string) (string, bool, error) { certInfo, certErr := os.Stat(certFile) keyInfo, keyErr := os.Stat(keyFile) if certErr == nil && keyErr == nil { if certInfo.IsDir() || keyInfo.IsDir() { return "", false, errors.New("TLS certificate/key path points to a directory") } if _, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil { return "", false, fmt.Errorf("load TLS certificate/key: %w", err) } fp, err := certificateFingerprint(certFile) return fp, false, err } if (certErr == nil) != (keyErr == nil) { return "", false, errors.New("only one TLS certificate/key file exists; refusing to replace the other") } if certErr != nil && !os.IsNotExist(certErr) { return "", false, certErr } if keyErr != nil && !os.IsNotExist(keyErr) { return "", false, keyErr } privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return "", false, fmt.Errorf("generate TLS key: %w", err) } serialLimit := new(big.Int).Lsh(big.NewInt(1), 128) serial, err := rand.Int(rand.Reader, serialLimit) if err != nil { return "", false, fmt.Errorf("generate TLS serial: %w", err) } now := time.Now().UTC() template := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: hostname, Organization: []string{"RustProxy Docker Agent"}}, NotBefore: now.Add(-5 * time.Minute), NotAfter: now.AddDate(10, 0, 0), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } if hostname != "" { if ip := net.ParseIP(hostname); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, hostname) } } der, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey) if err != nil { return "", false, fmt.Errorf("create TLS certificate: %w", err) } keyDER, err := x509.MarshalPKCS8PrivateKey(privateKey) if err != nil { return "", false, fmt.Errorf("marshal TLS key: %w", err) } if err := atomicWrite(keyFile, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0600); err != nil { return "", false, fmt.Errorf("write TLS key: %w", err) } if err := atomicWrite(certFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0644); err != nil { _ = os.Remove(keyFile) return "", false, fmt.Errorf("write TLS certificate: %w", err) } return certificateFingerprintDER(der), true, nil }