Files
ihasvpn/internal/server/tls.go
T
jcoffey-dev 02e7993c87 Rename the project to ihasvpn
WGX shares its name with several other WireGuard tools, so the project
becomes ihasvpn, alongside ihasmail.

- Module github.com/Coffey-Labs/ihasvpn, command cmd/ihasvpn, image
  ghcr.io/coffey-labs/ihasvpn.
- Environment variables move from WGX_* to IHASVPN_*. The default database
  is ihasvpn.db, the nftables table is `ihasvpn`, metrics are ihasvpn_*, and
  the session cookie and theme key are renamed, so existing sessions end.
- The mark is the ihasmail cat peeking over the edge of a shield, drawn as
  a vector. docs/brand/generate.py builds the mark, mono mark, wordmarks,
  social card, favicons and app icons from that one drawing.
- The console takes ihasmail's palette: the ihasmail.org teal-navy for dark,
  its contrast-checked light tiers with the site's light accent, received
  traffic in the cat's orange and sent in teal. The wordmark weight and
  font stack follow ihasmail.org.
- Detail values wrap at spaces before breaking inside an address, so an
  IPv6 tunnel address no longer splits mid-number.
- The README history note about the earlier WGX installer is gone with the
  name it explained. Screenshots retaken.
2026-09-12 23:48:36 -07:00

81 lines
2.5 KiB
Go

package server
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"path/filepath"
"time"
)
// loadCertificate returns the configured certificate, generating and
// persisting a self-signed one when asked to.
func (s *Server) loadCertificate() (tls.Certificate, error) {
if s.cfg.TLSCert != "" {
cert, err := tls.LoadX509KeyPair(s.cfg.TLSCert, s.cfg.TLSKey)
if err != nil {
return tls.Certificate{}, fmt.Errorf("load TLS certificate: %w", err)
}
return cert, nil
}
certPath := filepath.Join(s.cfg.DataDir, "tls.crt")
keyPath := filepath.Join(s.cfg.DataDir, "tls.key")
if cert, err := tls.LoadX509KeyPair(certPath, keyPath); err == nil {
if leaf, err := x509.ParseCertificate(cert.Certificate[0]); err == nil && time.Now().Before(leaf.NotAfter.Add(-30*24*time.Hour)) {
return cert, nil
}
}
s.log.Info("generating a self-signed TLS certificate", "path", certPath)
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return tls.Certificate{}, err
}
host := s.eng.Settings().EndpointHost
tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: "ihasvpn", Organization: []string{"ihasvpn"}},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(3 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
}
if host != "" {
if ip := net.ParseIP(host); ip != nil {
tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
} else {
tmpl.DNSNames = append(tmpl.DNSNames, host)
}
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
if err != nil {
return tls.Certificate{}, err
}
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
return tls.Certificate{}, err
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
if err := os.WriteFile(certPath, certPEM, 0o644); err != nil {
return tls.Certificate{}, err
}
if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil {
return tls.Certificate{}, err
}
return tls.X509KeyPair(certPEM, keyPEM)
}