mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Changed IsCA to false, removed KeyUsageCertSign, and reduced validity from 10 years to 2 years. A compromised key can no longer sign additional certificates trusted by TOFU-pinning clients.
298 lines
8.1 KiB
Go
298 lines
8.1 KiB
Go
package auth_test
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/owncord/server/auth"
|
|
"github.com/owncord/server/config"
|
|
)
|
|
|
|
func TestGenerateSelfSignedCreatesFiles(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certFile := filepath.Join(tmpDir, "cert.pem")
|
|
keyFile := filepath.Join(tmpDir, "key.pem")
|
|
|
|
if err := auth.GenerateSelfSigned(certFile, keyFile); err != nil {
|
|
t.Fatalf("GenerateSelfSigned() error: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(certFile); os.IsNotExist(err) {
|
|
t.Error("cert.pem not created")
|
|
}
|
|
if _, err := os.Stat(keyFile); os.IsNotExist(err) {
|
|
t.Error("key.pem not created")
|
|
}
|
|
}
|
|
|
|
func TestGenerateSelfSignedProducesValidCert(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certFile := filepath.Join(tmpDir, "cert.pem")
|
|
keyFile := filepath.Join(tmpDir, "key.pem")
|
|
|
|
if err := auth.GenerateSelfSigned(certFile, keyFile); err != nil {
|
|
t.Fatalf("GenerateSelfSigned() error: %v", err)
|
|
}
|
|
|
|
// Load the generated cert/key pair.
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
t.Fatalf("tls.LoadX509KeyPair error: %v", err)
|
|
}
|
|
|
|
// Parse the leaf certificate.
|
|
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
t.Fatalf("x509.ParseCertificate error: %v", err)
|
|
}
|
|
|
|
// Verify validity period is ~2 years (not the old 10y).
|
|
minExpiry := time.Now().Add(1 * 365 * 24 * time.Hour)
|
|
maxExpiry := time.Now().Add(3 * 365 * 24 * time.Hour)
|
|
if leaf.NotAfter.Before(minExpiry) {
|
|
t.Errorf("cert expires %v, expected at least 1 year from now (%v)", leaf.NotAfter, minExpiry)
|
|
}
|
|
if leaf.NotAfter.After(maxExpiry) {
|
|
t.Errorf("cert expires %v, expected at most 3 years from now (%v)", leaf.NotAfter, maxExpiry)
|
|
}
|
|
|
|
// BUG-138: Leaf cert must NOT be a CA — prevents signing other certs on key compromise.
|
|
if leaf.IsCA {
|
|
t.Error("expected IsCA = false for self-signed leaf cert")
|
|
}
|
|
if leaf.KeyUsage&x509.KeyUsageCertSign != 0 {
|
|
t.Error("leaf cert should not have KeyUsageCertSign")
|
|
}
|
|
}
|
|
|
|
func TestGenerateSelfSignedInvalidCertPath(t *testing.T) {
|
|
err := auth.GenerateSelfSigned("/nonexistent/dir/cert.pem", "/nonexistent/dir/key.pem")
|
|
if err == nil {
|
|
t.Error("GenerateSelfSigned() should error for invalid cert path")
|
|
}
|
|
}
|
|
|
|
func TestGenerateSelfSignedInvalidKeyPath(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certFile := filepath.Join(tmpDir, "cert.pem")
|
|
|
|
// Key path in non-existent dir.
|
|
err := auth.GenerateSelfSigned(certFile, "/nonexistent/dir/key.pem")
|
|
if err == nil {
|
|
t.Error("GenerateSelfSigned() should error for invalid key path")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateSelfSigned(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certFile := filepath.Join(tmpDir, "cert.pem")
|
|
keyFile := filepath.Join(tmpDir, "key.pem")
|
|
|
|
cfg := config.TLSConfig{
|
|
Mode: "self_signed",
|
|
CertFile: certFile,
|
|
KeyFile: keyFile,
|
|
}
|
|
|
|
result, err := auth.LoadOrGenerate(cfg)
|
|
if err != nil {
|
|
t.Fatalf("LoadOrGenerate() error: %v", err)
|
|
}
|
|
if result.TLSConfig == nil {
|
|
t.Fatal("LoadOrGenerate() returned nil TLSConfig")
|
|
}
|
|
if len(result.TLSConfig.Certificates) == 0 {
|
|
t.Error("LoadOrGenerate() returned TLSConfig with no certificates")
|
|
}
|
|
if result.HTTPHandler != nil {
|
|
t.Error("self_signed mode should not set HTTPHandler")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateLoadsExistingCert(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certFile := filepath.Join(tmpDir, "cert.pem")
|
|
keyFile := filepath.Join(tmpDir, "key.pem")
|
|
|
|
// Generate a cert first.
|
|
if err := auth.GenerateSelfSigned(certFile, keyFile); err != nil {
|
|
t.Fatalf("GenerateSelfSigned() error: %v", err)
|
|
}
|
|
|
|
cfg := config.TLSConfig{
|
|
Mode: "self_signed",
|
|
CertFile: certFile,
|
|
KeyFile: keyFile,
|
|
}
|
|
|
|
// Load the existing cert (should not regenerate).
|
|
result, err := auth.LoadOrGenerate(cfg)
|
|
if err != nil {
|
|
t.Fatalf("LoadOrGenerate() error: %v", err)
|
|
}
|
|
if len(result.TLSConfig.Certificates) == 0 {
|
|
t.Error("LoadOrGenerate() returned no certificates")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateModeOff(t *testing.T) {
|
|
cfg := config.TLSConfig{Mode: "off"}
|
|
|
|
result, err := auth.LoadOrGenerate(cfg)
|
|
if err != nil {
|
|
t.Fatalf("LoadOrGenerate(mode=off) error: %v", err)
|
|
}
|
|
if result.TLSConfig != nil {
|
|
t.Error("LoadOrGenerate(mode=off) should return nil TLSConfig")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateModeManualMissingFiles(t *testing.T) {
|
|
cfg := config.TLSConfig{
|
|
Mode: "manual",
|
|
CertFile: "/nonexistent/cert.pem",
|
|
KeyFile: "/nonexistent/key.pem",
|
|
}
|
|
|
|
_, err := auth.LoadOrGenerate(cfg)
|
|
if err == nil {
|
|
t.Error("LoadOrGenerate(mode=manual) should error when cert/key don't exist")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateModeManualValidFiles(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certFile := filepath.Join(tmpDir, "cert.pem")
|
|
keyFile := filepath.Join(tmpDir, "key.pem")
|
|
|
|
// Pre-generate cert files.
|
|
if err := auth.GenerateSelfSigned(certFile, keyFile); err != nil {
|
|
t.Fatalf("GenerateSelfSigned() error: %v", err)
|
|
}
|
|
|
|
cfg := config.TLSConfig{
|
|
Mode: "manual",
|
|
CertFile: certFile,
|
|
KeyFile: keyFile,
|
|
}
|
|
|
|
result, err := auth.LoadOrGenerate(cfg)
|
|
if err != nil {
|
|
t.Fatalf("LoadOrGenerate(mode=manual) error: %v", err)
|
|
}
|
|
if len(result.TLSConfig.Certificates) == 0 {
|
|
t.Error("LoadOrGenerate(mode=manual) returned no certificates")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateUnknownMode(t *testing.T) {
|
|
cfg := config.TLSConfig{Mode: "unknown_mode"}
|
|
|
|
_, err := auth.LoadOrGenerate(cfg)
|
|
if err == nil {
|
|
t.Error("LoadOrGenerate() should error for unknown TLS mode")
|
|
}
|
|
}
|
|
|
|
// ── ACME mode tests ───────────────────────────────────────────────────────
|
|
|
|
func TestLoadOrGenerateACME_MissingDomain(t *testing.T) {
|
|
cfg := config.TLSConfig{Mode: "acme", Domain: ""}
|
|
|
|
_, err := auth.LoadOrGenerate(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for ACME mode without domain")
|
|
}
|
|
if !strings.Contains(err.Error(), "domain") {
|
|
t.Errorf("error should mention domain, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateACME_IPAddress(t *testing.T) {
|
|
cfg := config.TLSConfig{Mode: "acme", Domain: "192.168.1.1"}
|
|
|
|
_, err := auth.LoadOrGenerate(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for ACME mode with IP address")
|
|
}
|
|
if !strings.Contains(err.Error(), "IP address") {
|
|
t.Errorf("error should mention IP address, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateACME_WildcardDomain(t *testing.T) {
|
|
cfg := config.TLSConfig{Mode: "acme", Domain: "*.example.com"}
|
|
|
|
_, err := auth.LoadOrGenerate(cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error for ACME mode with wildcard domain")
|
|
}
|
|
if !strings.Contains(err.Error(), "wildcard") {
|
|
t.Errorf("error should mention wildcard, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateACME_ValidDomain(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
cacheDir := filepath.Join(tmpDir, "acme_certs")
|
|
|
|
cfg := config.TLSConfig{
|
|
Mode: "acme",
|
|
Domain: "chat.example.com",
|
|
AcmeCacheDir: cacheDir,
|
|
}
|
|
|
|
result, err := auth.LoadOrGenerate(cfg)
|
|
if err != nil {
|
|
t.Fatalf("LoadOrGenerate(acme) error: %v", err)
|
|
}
|
|
if result.TLSConfig == nil {
|
|
t.Fatal("ACME mode should return non-nil TLSConfig")
|
|
}
|
|
if result.TLSConfig.GetCertificate == nil {
|
|
t.Error("ACME TLSConfig should have GetCertificate set")
|
|
}
|
|
if result.HTTPHandler == nil {
|
|
t.Error("ACME mode should return non-nil HTTPHandler")
|
|
}
|
|
|
|
// Verify cache directory was created.
|
|
if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
|
|
t.Error("ACME cache directory was not created")
|
|
}
|
|
}
|
|
|
|
func TestLoadOrGenerateACME_HTTPRedirect(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
cfg := config.TLSConfig{
|
|
Mode: "acme",
|
|
Domain: "chat.example.com",
|
|
AcmeCacheDir: filepath.Join(tmpDir, "acme_certs"),
|
|
}
|
|
|
|
result, err := auth.LoadOrGenerate(cfg)
|
|
if err != nil {
|
|
t.Fatalf("LoadOrGenerate(acme) error: %v", err)
|
|
}
|
|
|
|
// Non-challenge requests should redirect to HTTPS.
|
|
req := httptest.NewRequest(http.MethodGet, "http://chat.example.com/some/path", nil)
|
|
rec := httptest.NewRecorder()
|
|
result.HTTPHandler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusMovedPermanently {
|
|
t.Errorf("expected 301 redirect, got %d", rec.Code)
|
|
}
|
|
loc := rec.Header().Get("Location")
|
|
if !strings.HasPrefix(loc, "https://chat.example.com/") {
|
|
t.Errorf("redirect should point to HTTPS, got: %s", loc)
|
|
}
|
|
}
|