Scert/backend/internal/certificate/lifecycle_test.go

104 lines
4.0 KiB
Go

package certificate
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)
func TestNormalizeDomainsSupportsSANAndWildcard(t *testing.T) {
got, err := NormalizeDomains([]string{"Example.COM.", "*.example.com", "example.com"})
if err != nil {
t.Fatalf("规范化域名失败:%v", err)
}
if len(got) != 2 || got[0] != "*.example.com" || got[1] != "example.com" {
t.Fatalf("规范化结果错误:%v", got)
}
}
func TestFileStoreSaveBacksUpAndKeepsKeyRestricted(t *testing.T) {
root := t.TempDir()
store := FileStore{Root: filepath.Join(root, "ssl"), BackupRoot: filepath.Join(root, "backups"), Now: func() time.Time { return time.Date(2026, 8, 26, 10, 0, 0, 0, time.UTC) }}
resource := testResource(t, "example.com")
first, err := store.Save(context.Background(), "example.com", resource)
if err != nil {
t.Fatalf("第一次保存失败:%v", err)
}
if first.BackupDirectory != "" {
t.Fatal("第一次保存不应生成备份")
}
resource.Certificate = append([]byte(nil), resource.Certificate...)
second, err := store.Save(context.Background(), "example.com", resource)
if err != nil {
t.Fatalf("第二次保存失败:%v", err)
}
if second.BackupDirectory == "" {
t.Fatal("替换已有证书时应生成备份")
}
if _, err := os.Stat(second.FullChainPath); err != nil {
t.Fatalf("完整证书链不存在:%v", err)
}
if info, err := os.Stat(second.PrivateKeyPath); err != nil {
t.Fatalf("私钥不存在:%v", err)
} else if runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 {
t.Fatalf("私钥权限错误:%o", info.Mode().Perm())
}
}
func TestValidateResourceRejectsMismatchedKey(t *testing.T) {
resource := testResource(t, "example.com")
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
resource.PrivateKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
if _, err := ValidateResource(resource); err == nil {
t.Fatal("证书与私钥不匹配时应失败")
}
}
func TestValidateResourceForDomainsSupportsWildcard(t *testing.T) {
resource := testResourceForWildcard(t, "*.example.com")
if _, err := ValidateResourceForDomains(resource, []string{"*.example.com"}); err != nil {
t.Fatalf("泛域名证书覆盖校验失败:%v", err)
}
}
func testResource(t *testing.T, name string) Resource {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
now := time.Now().UTC()
template := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: name}, DNSNames: []string{name}, NotBefore: now.Add(-time.Minute), NotAfter: now.Add(90 * 24 * time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
t.Fatal(err)
}
return Resource{Domains: []string{name}, Certificate: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), PrivateKey: pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})}
}
func testResourceForWildcard(t *testing.T, name string) Resource {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
now := time.Now().UTC()
template := &x509.Certificate{SerialNumber: big.NewInt(3), Subject: pkix.Name{CommonName: name}, DNSNames: []string{name}, NotBefore: now.Add(-time.Minute), NotAfter: now.Add(90 * 24 * time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
t.Fatal(err)
}
return Resource{Domains: []string{name}, Certificate: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), PrivateKey: pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})}
}