修复续期证书引用校验依赖工作目录权限的问题

This commit is contained in:
smail 2026-09-20 05:24:57 +08:00
parent 81cb57fb1d
commit b181fb6d6a
2 changed files with 74 additions and 3 deletions

View File

@ -0,0 +1,65 @@
package config
import (
"errors"
"os"
"runtime"
"testing"
)
func TestPreviousSSLReferenceValidation(t *testing.T) {
const base = "smail-tls-0123456789abcdef0123456789abcdef"
for _, test := range []struct {
name string
certificate string
key string
valid bool
}{
{"valid", "ssl/" + base + ".crt", "ssl/" + base + ".key", true},
{"missing_key", "ssl/" + base + ".crt", "", false},
{"absolute", "/ssl/" + base + ".crt", "/ssl/" + base + ".key", false},
{"traversal", "ssl/../ssl/" + base + ".crt", "ssl/" + base + ".key", false},
{"nested", "ssl/old/" + base + ".crt", "ssl/old/" + base + ".key", false},
{"outside", "data/" + base + ".crt", "data/" + base + ".key", false},
{"backslash", "ssl\\" + base + ".crt", "ssl\\" + base + ".key", false},
{"mismatch", "ssl/" + base + ".crt", "ssl/smail-tls-abcdef0123456789abcdef0123456789.key", false},
{"unmanaged", "ssl/manual.crt", "ssl/manual.key", false},
{"bad_token", "ssl/smail-tls-short.crt", "ssl/smail-tls-short.key", false},
} {
t.Run(test.name, func(t *testing.T) {
ssl := testRootConfig().SSL
ssl.PreviousCertificateRef = test.certificate
ssl.PreviousPrivateKeyRef = test.key
err := validateSSL(ssl)
if test.valid && err != nil {
t.Fatalf("validateSSL: %v", err)
}
if !test.valid && !errors.Is(err, ErrInvalidSSL) {
t.Fatalf("validateSSL = %v, want ErrInvalidSSL", err)
}
})
}
}
// 引用格式校验不得依赖调用者的工作目录权限,真实材料由加载器另行校验。
func TestPreviousSSLReferenceWithInaccessibleWorkingDirectory(t *testing.T) {
if runtime.GOOS == "windows" || os.Geteuid() == 0 {
t.Skip("需要非特权 Unix 用户验证目录权限")
}
directory := t.TempDir()
t.Chdir(directory)
if err := os.Chmod(directory, 0); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.Chmod(directory, 0700); err != nil {
t.Error(err)
}
})
ssl := testRootConfig().SSL
ssl.PreviousCertificateRef = "ssl/smail-tls-0123456789abcdef0123456789abcdef.crt"
ssl.PreviousPrivateKeyRef = "ssl/smail-tls-0123456789abcdef0123456789abcdef.key"
if err := validateSSL(ssl); err != nil {
t.Fatalf("validateSSL with inaccessible working directory: %v", err)
}
}

View File

@ -180,9 +180,15 @@ func validateSSL(ssl SSLConfig) error {
return ErrInvalidSSL
}
if ssl.PreviousCertificateRef != "" {
publicPath, err := resolveCustomSSLInput(".", ssl.PreviousCertificateRef)
privatePath, keyErr := resolveCustomSSLInput(".", ssl.PreviousPrivateKeyRef)
if err != nil || keyErr != nil || !isControlledRelativePath(ssl.PreviousCertificateRef) || !isControlledRelativePath(ssl.PreviousPrivateKeyRef) || !flatSSLPair(".", publicPath, privatePath) {
// 此处只校验受控引用,避免工作目录不可访问时误判有效配置。
// 实际文件仍由托管加载器按部署根目录校验路径、权限和密钥匹配。
certificate, privateKey := ssl.PreviousCertificateRef, ssl.PreviousPrivateKeyRef
name := strings.TrimSuffix(path.Base(certificate), ".crt")
if !isControlledRelativePath(certificate) || !isControlledRelativePath(privateKey) ||
path.Dir(certificate) != "ssl" || path.Dir(privateKey) != "ssl" ||
!strings.HasSuffix(certificate, ".crt") || !strings.HasPrefix(name, flatSSLPrefix) ||
!validSSLVersion("version-"+strings.TrimPrefix(name, flatSSLPrefix)) ||
path.Base(privateKey) != name+".key" {
return ErrInvalidSSL
}
}