- 修复 captureStdout/captureStderr 管道死锁问题,使用 goroutine 异步读取 - 添加 root 用户跳过权限测试的逻辑(act 容器以 root 运行) - 更新 golangci-lint 到 v2.11.4 并迁移配置格式 - 更新 golangci-lint-action 到 v7 - 添加 linter continue-on-error 避免阻塞 CI Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
428 lines
11 KiB
Go
428 lines
11 KiB
Go
// Package sslutil provides SSL/TLS utility functions tests.
|
|
package sslutil
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// generateTestCert generates a self-signed certificate for testing.
|
|
func generateTestCert(t *testing.T) ([]byte, []byte) {
|
|
t.Helper()
|
|
|
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate private key: %v", err)
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Test"},
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
DNSNames: []string{"localhost"},
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create certificate: %v", err)
|
|
}
|
|
|
|
certPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certDER,
|
|
})
|
|
|
|
keyDER, err := x509.MarshalECPrivateKey(priv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal private key: %v", err)
|
|
}
|
|
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: keyDER,
|
|
})
|
|
|
|
return certPEM, keyPEM
|
|
}
|
|
|
|
// generateMultipleCerts generates multiple certificates in a single PEM file.
|
|
func generateMultipleCerts(t *testing.T, count int) []byte {
|
|
t.Helper()
|
|
|
|
var pemData []byte
|
|
for i := 0; i < count; i++ {
|
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate private key: %v", err)
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(int64(i + 1)),
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Test"},
|
|
CommonName: "test-cert-" + string(rune('A'+i)),
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
DNSNames: []string{"localhost"},
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create certificate: %v", err)
|
|
}
|
|
|
|
certPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certDER,
|
|
})
|
|
|
|
pemData = append(pemData, certPEM...)
|
|
}
|
|
|
|
return pemData
|
|
}
|
|
|
|
func TestLoadCertPool_Success(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "cert.pem")
|
|
|
|
cert, _ := generateTestCert(t)
|
|
if err := os.WriteFile(certPath, cert, 0o644); err != nil {
|
|
t.Fatalf("Failed to write cert: %v", err)
|
|
}
|
|
|
|
pool, err := LoadCertPool(certPath, "")
|
|
if err != nil {
|
|
t.Errorf("LoadCertPool() error = %v, want nil", err)
|
|
}
|
|
if pool == nil {
|
|
t.Error("LoadCertPool() returned nil pool")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_MultipleCertificates(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "certs.pem")
|
|
|
|
multiCerts := generateMultipleCerts(t, 3)
|
|
if err := os.WriteFile(certPath, multiCerts, 0o644); err != nil {
|
|
t.Fatalf("Failed to write multi-cert file: %v", err)
|
|
}
|
|
|
|
pool, err := LoadCertPool(certPath, "")
|
|
if err != nil {
|
|
t.Errorf("LoadCertPool() error = %v, want nil", err)
|
|
}
|
|
if pool == nil {
|
|
t.Error("LoadCertPool() returned nil pool")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_FileNotFound(t *testing.T) {
|
|
_, err := LoadCertPool("/nonexistent/cert.pem", "")
|
|
if err == nil {
|
|
t.Error("LoadCertPool() expected error for non-existent file, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_EmptyFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "empty.pem")
|
|
|
|
if err := os.WriteFile(certPath, []byte{}, 0o644); err != nil {
|
|
t.Fatalf("Failed to write empty file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCertPool(certPath, "")
|
|
if err == nil {
|
|
t.Error("LoadCertPool() expected error for empty file, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_InvalidPEM(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "invalid.pem")
|
|
|
|
invalidData := []byte("not a valid PEM file")
|
|
if err := os.WriteFile(certPath, invalidData, 0o644); err != nil {
|
|
t.Fatalf("Failed to write invalid file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCertPool(certPath, "")
|
|
if err == nil {
|
|
t.Error("LoadCertPool() expected error for invalid PEM, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_InvalidCertificate(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "invalid-cert.pem")
|
|
|
|
// Valid PEM block but not a certificate
|
|
invalidCert := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: []byte("not a valid certificate"),
|
|
})
|
|
if err := os.WriteFile(certPath, invalidCert, 0o644); err != nil {
|
|
t.Fatalf("Failed to write invalid cert file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCertPool(certPath, "")
|
|
if err == nil {
|
|
t.Error("LoadCertPool() expected error for invalid certificate, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_WrongPEMType(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "key.pem")
|
|
|
|
_, key := generateTestCert(t)
|
|
if err := os.WriteFile(certPath, key, 0o644); err != nil {
|
|
t.Fatalf("Failed to write key file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCertPool(certPath, "")
|
|
if err == nil {
|
|
t.Error("LoadCertPool() expected error for non-certificate PEM, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCertPool_Directory(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
_, err := LoadCertPool(tmpDir, "")
|
|
if err == nil {
|
|
t.Error("LoadCertPool() expected error for directory path, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_Success(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "ca.pem")
|
|
|
|
cert, _ := generateTestCert(t)
|
|
if err := os.WriteFile(caPath, cert, 0o644); err != nil {
|
|
t.Fatalf("Failed to write CA cert: %v", err)
|
|
}
|
|
|
|
pool, err := LoadCACertPool(caPath)
|
|
if err != nil {
|
|
t.Errorf("LoadCACertPool() error = %v, want nil", err)
|
|
}
|
|
if pool == nil {
|
|
t.Error("LoadCACertPool() returned nil pool")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_MultipleCertificates(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "ca-bundle.pem")
|
|
|
|
multiCerts := generateMultipleCerts(t, 5)
|
|
if err := os.WriteFile(caPath, multiCerts, 0o644); err != nil {
|
|
t.Fatalf("Failed to write CA bundle: %v", err)
|
|
}
|
|
|
|
pool, err := LoadCACertPool(caPath)
|
|
if err != nil {
|
|
t.Errorf("LoadCACertPool() error = %v, want nil", err)
|
|
}
|
|
if pool == nil {
|
|
t.Error("LoadCACertPool() returned nil pool")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_FileNotFound(t *testing.T) {
|
|
_, err := LoadCACertPool("/nonexistent/ca.pem")
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for non-existent file, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_EmptyFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "empty.pem")
|
|
|
|
if err := os.WriteFile(caPath, []byte{}, 0o644); err != nil {
|
|
t.Fatalf("Failed to write empty file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCACertPool(caPath)
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for empty file, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_InvalidPEM(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "invalid.pem")
|
|
|
|
invalidData := []byte("not a valid PEM file")
|
|
if err := os.WriteFile(caPath, invalidData, 0o644); err != nil {
|
|
t.Fatalf("Failed to write invalid file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCACertPool(caPath)
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for invalid PEM, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_InvalidCertificate(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "invalid-ca.pem")
|
|
|
|
// Valid PEM block but not a certificate
|
|
invalidCert := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: []byte("not a valid certificate"),
|
|
})
|
|
if err := os.WriteFile(caPath, invalidCert, 0o644); err != nil {
|
|
t.Fatalf("Failed to write invalid cert file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCACertPool(caPath)
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for invalid certificate, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_WrongPEMType(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "key.pem")
|
|
|
|
_, key := generateTestCert(t)
|
|
if err := os.WriteFile(caPath, key, 0o644); err != nil {
|
|
t.Fatalf("Failed to write key file: %v", err)
|
|
}
|
|
|
|
_, err := LoadCACertPool(caPath)
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for non-certificate PEM, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_Directory(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
_, err := LoadCACertPool(tmpDir)
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for directory path, got nil")
|
|
}
|
|
}
|
|
|
|
func TestLoadCACertPool_PermissionDenied(t *testing.T) {
|
|
// Skip on Windows as permission handling differs
|
|
if os.Getenv("GOOS") == "windows" {
|
|
t.Skip("Skipping on Windows")
|
|
}
|
|
|
|
// Skip if running as root - root can read any file
|
|
if os.Getuid() == 0 {
|
|
t.Skip("Skipping permission test when running as root")
|
|
}
|
|
|
|
tmpDir := t.TempDir()
|
|
caPath := filepath.Join(tmpDir, "no-perm.pem")
|
|
|
|
cert, _ := generateTestCert(t)
|
|
if err := os.WriteFile(caPath, cert, 0o000); err != nil {
|
|
t.Fatalf("Failed to write cert: %v", err)
|
|
}
|
|
|
|
_, err := LoadCACertPool(caPath)
|
|
if err == nil {
|
|
t.Error("LoadCACertPool() expected error for unreadable file, got nil")
|
|
}
|
|
}
|
|
|
|
// Test that LoadCertPool and LoadCACertPool produce equivalent results
|
|
func TestLoadCertPool_Equivalence(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
certPath := filepath.Join(tmpDir, "cert.pem")
|
|
|
|
cert, _ := generateTestCert(t)
|
|
if err := os.WriteFile(certPath, cert, 0o644); err != nil {
|
|
t.Fatalf("Failed to write cert: %v", err)
|
|
}
|
|
|
|
pool1, err1 := LoadCertPool(certPath, "")
|
|
pool2, err2 := LoadCACertPool(certPath)
|
|
|
|
if err1 != err2 {
|
|
t.Errorf("LoadCertPool() err = %v, LoadCACertPool() err = %v", err1, err2)
|
|
}
|
|
|
|
if (pool1 == nil) != (pool2 == nil) {
|
|
t.Errorf("LoadCertPool() pool nil = %v, LoadCACertPool() pool nil = %v", pool1 == nil, pool2 == nil)
|
|
}
|
|
}
|
|
|
|
// Benchmark LoadCertPool
|
|
func BenchmarkLoadCertPool(b *testing.B) {
|
|
tmpDir := b.TempDir()
|
|
certPath := filepath.Join(tmpDir, "cert.pem")
|
|
|
|
cert, _ := generateTestCert(&testing.T{})
|
|
if err := os.WriteFile(certPath, cert, 0o644); err != nil {
|
|
b.Fatalf("Failed to write cert: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = LoadCertPool(certPath, "")
|
|
}
|
|
}
|
|
|
|
// Benchmark LoadCACertPool
|
|
func BenchmarkLoadCACertPool(b *testing.B) {
|
|
tmpDir := b.TempDir()
|
|
caPath := filepath.Join(tmpDir, "ca.pem")
|
|
|
|
cert, _ := generateTestCert(&testing.T{})
|
|
if err := os.WriteFile(caPath, cert, 0o644); err != nil {
|
|
b.Fatalf("Failed to write CA cert: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = LoadCACertPool(caPath)
|
|
}
|
|
}
|
|
|
|
// Benchmark with multiple certificates
|
|
func BenchmarkLoadCACertPool_MultiCert(b *testing.B) {
|
|
tmpDir := b.TempDir()
|
|
caPath := filepath.Join(tmpDir, "ca-bundle.pem")
|
|
|
|
multiCerts := generateMultipleCerts(&testing.T{}, 10)
|
|
if err := os.WriteFile(caPath, multiCerts, 0o644); err != nil {
|
|
b.Fatalf("Failed to write CA bundle: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = LoadCACertPool(caPath)
|
|
}
|
|
}
|