This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
package sslconfigs
import (
"crypto/tls"
"github.com/iwind/TeaGo/maps"
)
// 认证类型
type SSLClientAuthType = int
const (
SSLClientAuthTypeNoClientCert SSLClientAuthType = 0
SSLClientAuthTypeRequestClientCert SSLClientAuthType = 1
SSLClientAuthTypeRequireAnyClientCert SSLClientAuthType = 2
SSLClientAuthTypeVerifyClientCertIfGiven SSLClientAuthType = 3
SSLClientAuthTypeRequireAndVerifyClientCert SSLClientAuthType = 4
)
// 所有的客户端认证类型
func AllSSLClientAuthTypes() []maps.Map {
return []maps.Map{
{
"name": "不需要客户端证书",
"type": SSLClientAuthTypeNoClientCert,
"requireCA": false,
},
{
"name": "请求客户端证书",
"type": SSLClientAuthTypeRequestClientCert,
"requireCA": true,
},
{
"name": "需要客户端证书,但不校验",
"type": SSLClientAuthTypeRequireAnyClientCert,
"requireCA": true,
},
{
"name": "有客户端证书的时候才校验",
"type": SSLClientAuthTypeVerifyClientCertIfGiven,
"requireCA": true,
},
{
"name": "校验客户端提供的证书",
"type": SSLClientAuthTypeRequireAndVerifyClientCert,
"requireCA": true,
},
}
}
// 查找单个认证方式的名称
func FindSSLClientAuthTypeName(authType SSLClientAuthType) string {
for _, m := range AllSSLClientAuthTypes() {
if m.GetInt("type") == authType {
return m.GetString("name")
}
}
return ""
}
// 认证类型和tls包内类型的映射
func GoSSLClientAuthType(authType SSLClientAuthType) tls.ClientAuthType {
switch authType {
case SSLClientAuthTypeNoClientCert:
return tls.NoClientCert
case SSLClientAuthTypeRequestClientCert:
return tls.RequestClientCert
case SSLClientAuthTypeRequireAnyClientCert:
return tls.RequireAnyClientCert
case SSLClientAuthTypeVerifyClientCertIfGiven:
return tls.VerifyClientCertIfGiven
case SSLClientAuthTypeRequireAndVerifyClientCert:
return tls.RequireAndVerifyClientCert
}
return tls.NoClientCert
}

View File

@@ -0,0 +1,200 @@
package sslconfigs
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/lists"
"reflect"
"strconv"
"time"
)
// SSLCertConfig SSL证书
type SSLCertConfig struct {
Id int64 `yaml:"id" json:"id"`
IsOn bool `yaml:"isOn" json:"isOn"`
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"` // 说明
CertData []byte `yaml:"certData" json:"certData"` // 证书数据
KeyData []byte `yaml:"keyData" json:"keyData"` // 密钥数据
ServerName string `yaml:"serverName" json:"serverName"` // 证书使用的主机名在请求TLS服务器时需要
IsCA bool `yaml:"isCA" json:"isCA"` // 是否为CA证书
IsACME bool `yaml:"isACME" json:"isACME"` // 是否通过ACME协议免费申请
// 以下是从证书中分析所得
TimeBeginAt int64 `yaml:"timeBeginAt" json:"timeBeginAt"`
TimeEndAt int64 `yaml:"timeEndAt" json:"timeEndAt"`
DNSNames []string `yaml:"dnsNames" json:"dnsNames"`
CommonNames []string `yaml:"commonNames" json:"commonNames"`
// OCSP
OCSP []byte `yaml:"ocsp" json:"ocsp"`
OCSPExpiresAt int64 `yaml:"ocspExpiresAt" json:"ocspExpiresAt"`
OCSPError string `yaml:"ocspError" json:"ocspError"`
cert *tls.Certificate
caCerts []*x509.Certificate
timeBegin time.Time
timeEnd time.Time
}
// Init 校验
func (this *SSLCertConfig) Init(ctx context.Context) error {
// 如果没有指定数据, 则从ctx中读取数据
if ctx != nil && len(this.CertData) < 128 {
var dataMapOne = ctx.Value("DataMap")
if dataMapOne != nil && !reflect.ValueOf(dataMapOne).IsNil() {
dataMap, ok := dataMapOne.(*shared.DataMap)
if !ok {
return errors.New("SSLCertConfig.init(): invalid 'DataMap' in context")
}
if dataMap != nil { // 再次检查是否为nil
this.KeyData = dataMap.Read(this.KeyData)
this.CertData = dataMap.Read(this.CertData)
this.OCSP = dataMap.Read(this.OCSP)
}
}
}
var commonNames []string // 发行组织
var dnsNames []string // 域名
this.caCerts = []*x509.Certificate{}
// 分析证书
if this.IsCA { // CA证书
var data = this.CertData
var index = -1
this.cert = &tls.Certificate{
Certificate: [][]byte{},
}
for {
index++
block, rest := pem.Decode(data)
if block == nil {
break
}
this.cert.Certificate = append(this.cert.Certificate, block.Bytes)
data = rest
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return err
}
if c == nil {
return errors.New("no available certificates in file")
}
this.caCerts = append(this.caCerts, c)
for _, dnsName := range c.DNSNames {
if !lists.ContainsString(dnsNames, dnsName) {
dnsNames = append(dnsNames, dnsName)
}
}
for _, ipAddress := range c.IPAddresses {
if ipAddress == nil {
continue
}
var ipAddressString = ipAddress.String()
if !lists.ContainsString(dnsNames, ipAddressString) {
dnsNames = append(dnsNames, ipAddressString)
}
}
commonNames = append(commonNames, c.Issuer.CommonName)
if index == 0 {
this.timeBegin = c.NotBefore
this.timeEnd = c.NotAfter
}
if len(rest) == 0 {
break
}
}
} else { // 证书+私钥
cert, err := tls.X509KeyPair(this.CertData, this.KeyData)
if err != nil {
return fmt.Errorf("load certificate '%s' failed: %w", strconv.FormatInt(this.Id, 10), err)
}
for index, data := range cert.Certificate {
c, err := x509.ParseCertificate(data)
if err != nil {
continue
}
if cert.Leaf == nil {
cert.Leaf = c
}
for _, dnsName := range c.DNSNames {
if !lists.ContainsString(dnsNames, dnsName) {
dnsNames = append(dnsNames, dnsName)
}
}
for _, ipAddress := range c.IPAddresses {
if ipAddress == nil {
continue
}
var ipAddressString = ipAddress.String()
if !lists.ContainsString(dnsNames, ipAddressString) {
dnsNames = append(dnsNames, ipAddressString)
}
}
commonNames = append(commonNames, c.Issuer.CommonName)
if index == 0 {
this.timeBegin = c.NotBefore
this.timeEnd = c.NotAfter
}
}
this.cert = &cert
}
// 赋值分析结果
this.DNSNames = dnsNames
this.CommonNames = commonNames
this.TimeBeginAt = this.timeBegin.Unix()
this.TimeEndAt = this.timeEnd.Unix()
return nil
}
// MatchDomain 校验是否匹配某个域名
func (this *SSLCertConfig) MatchDomain(domain string) bool {
if len(this.DNSNames) == 0 {
return false
}
return configutils.MatchDomains(this.DNSNames, domain)
}
// CertObject 获取证书对象
func (this *SSLCertConfig) CertObject() *tls.Certificate {
return this.cert
}
func (this *SSLCertConfig) CACerts() []*x509.Certificate {
return this.caCerts
}
// TimeBegin 开始时间
func (this *SSLCertConfig) TimeBegin() time.Time {
return this.timeBegin
}
// TimeEnd 结束时间
func (this *SSLCertConfig) TimeEnd() time.Time {
return this.timeEnd
}

View File

@@ -0,0 +1,32 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package sslconfigs_test
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestSSLCertConfig_MatchDomain(t *testing.T) {
var a = assert.NewAssertion(t)
var cert = &sslconfigs.SSLCertConfig{
DNSNames: []string{"a.com", "b.com"},
}
a.IsTrue(cert.MatchDomain("a.com"))
a.IsFalse(cert.MatchDomain("z.com"))
}
/**func TestSSLCertConfig_DNSNames(t *testing.T) {
var config = sslconfigs.SSLCertConfig{}
config.CertData = []byte(`YOUR CERT DATA`)
config.KeyData = []byte(`YOUR KEY DATA`)
err := config.Init(context.Background())
if err != nil {
t.Fatal(err)
}
t.Log(config.DNSNames)
}**/

View File

@@ -0,0 +1,6 @@
package sslconfigs
type SSLCertRef struct {
IsOn bool `yaml:"isOn" json:"isOn"`
CertId int64 `yaml:"certId" json:"certId"`
}

View File

@@ -0,0 +1,143 @@
package sslconfigs
import (
"crypto/tls"
)
var AllTlsVersions = []TLSVersion{ /**"SSL 3.0",**/ "TLS 1.0", "TLS 1.1", "TLS 1.2", "TLS 1.3"}
var AllTLSCipherSuites = []TLSCipherSuite{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
}
var TLSModernCipherSuites = []string{
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}
var TLSIntermediateCipherSuites = []string{
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
}
func (this *SSLPolicy) convertMinVersion() {
switch this.MinVersion {
case "SSL 3.0":
this.minVersion = tls.VersionSSL30
case "TLS 1.0":
this.minVersion = tls.VersionTLS10
case "TLS 1.1":
this.minVersion = tls.VersionTLS11
case "TLS 1.2":
this.minVersion = tls.VersionTLS12
case "TLS 1.3":
this.minVersion = tls.VersionTLS13
default:
this.minVersion = tls.VersionTLS10
}
}
func (this *SSLPolicy) initCipherSuites() {
// cipher suites
suites := []uint16{}
for _, suite := range this.CipherSuites {
switch suite {
case "TLS_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_RSA_WITH_RC4_128_SHA)
case "TLS_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case "TLS_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
case "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
case "TLS_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_AES_128_GCM_SHA256)
case "TLS_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_AES_256_GCM_SHA384)
case "TLS_CHACHA20_POLY1305_SHA256":
suites = append(suites, tls.TLS_CHACHA20_POLY1305_SHA256)
}
}
this.cipherSuites = suites
}

View File

@@ -0,0 +1,63 @@
package sslconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"strconv"
"strings"
)
// HSTS设置
// 参考: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
type HSTSConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"`
MaxAge int `yaml:"maxAge" json:"maxAge"` // 单位秒
IncludeSubDomains bool `yaml:"includeSubDomains" json:"includeSubDomains"`
Preload bool `yaml:"preload" json:"preload"`
Domains []string `yaml:"domains" json:"domains"`
hasDomains bool
headerValue string
}
// 校验
func (this *HSTSConfig) Init() error {
this.hasDomains = len(this.Domains) > 0
this.headerValue = this.asHeaderValue()
return nil
}
// 判断是否匹配域名
func (this *HSTSConfig) Match(domain string) bool {
if !this.hasDomains {
return true
}
return configutils.MatchDomains(this.Domains, domain)
}
// Header Key
func (this *HSTSConfig) HeaderKey() string {
return "Strict-Transport-Security"
}
// 取得当前的Header值
func (this *HSTSConfig) HeaderValue() string {
return this.headerValue
}
// 转换为Header值
func (this *HSTSConfig) asHeaderValue() string {
b := strings.Builder{}
b.WriteString("max-age=")
if this.MaxAge > 0 {
b.WriteString(strconv.Itoa(this.MaxAge))
} else {
b.WriteString("31536000") // 1 year
}
if this.IncludeSubDomains {
b.WriteString("; includeSubDomains")
}
if this.Preload {
b.WriteString("; preload")
}
return b.String()
}

View File

@@ -0,0 +1,60 @@
package sslconfigs
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestHSTSConfig(t *testing.T) {
h := &HSTSConfig{}
err := h.Init()
if err != nil {
t.Fatal(err)
}
t.Log(h.HeaderValue())
h.IncludeSubDomains = true
err = h.Init()
if err != nil {
t.Fatal(err)
}
t.Log(h.HeaderValue())
h.Preload = true
err = h.Init()
if err != nil {
t.Fatal(err)
}
t.Log(h.HeaderValue())
h.IncludeSubDomains = false
err = h.Init()
if err != nil {
t.Fatal(err)
}
t.Log(h.HeaderValue())
h.MaxAge = 86400
err = h.Init()
if err != nil {
t.Fatal(err)
}
t.Log(h.HeaderValue())
a := assert.NewAssertion(t)
a.IsTrue(h.Match("abc.com"))
h.Domains = []string{"abc.com"}
err = h.Init()
if err != nil {
t.Fatal(err)
}
a.IsTrue(h.Match("abc.com"))
h.Domains = []string{"1.abc.com"}
err = h.Init()
if err != nil {
t.Fatal(err)
}
a.IsFalse(h.Match("abc.com"))
}

View File

@@ -0,0 +1,261 @@
package sslconfigs
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"golang.org/x/net/http2"
"time"
)
// TLSVersion TLS Version
type TLSVersion = string
// TLSCipherSuite Cipher Suites
type TLSCipherSuite = string
// SSLPolicy SSL配置
type SSLPolicy struct {
Id int64 `yaml:"id" json:"id"` // ID
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
CertRefs []*SSLCertRef `yaml:"certRefs" json:"certRefs"`
Certs []*SSLCertConfig `yaml:"certs" json:"certs"`
ClientAuthType SSLClientAuthType `yaml:"clientAuthType" json:"clientAuthType"` // 客户端认证类型
ClientCARefs []*SSLCertRef `yaml:"clientCARefs" json:"clientCARefs"` // 客户端认证CA证书引用
ClientCACerts []*SSLCertConfig `yaml:"clientCACerts" json:"clientCACerts"` // 客户端认证CA
MinVersion TLSVersion `yaml:"minVersion" json:"minVersion"` // 支持的最小版本
CipherSuitesIsOn bool `yaml:"cipherSuitesIsOn" json:"cipherSuitesIsOn"` // 是否自定义加密算法套件
CipherSuites []TLSCipherSuite `yaml:"cipherSuites" json:"cipherSuites"` // 加密算法套件
HSTS *HSTSConfig `yaml:"hsts" json:"hsts"` // HSTS配置
HTTP2Enabled bool `yaml:"http2Enabled" json:"http2Enabled"` // 是否启用HTTP/2
HTTP3Enabled bool `yaml:"http3Enabled" json:"http3Enabled"` // 是否启用HTTP/3
OCSPIsOn bool `yaml:"ocspIsOn" json:"ocspIsOn"` // 是否启用OCSP
nameMapping map[string]*tls.Certificate // dnsName => cert
minVersion uint16
cipherSuites []uint16
clientCAPool *x509.CertPool
tlsConfig *tls.Config
ocspExpiresAt int64 // OCSP最早过期时间
}
// Init 校验配置
func (this *SSLPolicy) Init(ctx context.Context) error {
this.nameMapping = map[string]*tls.Certificate{}
// certs
var certs = []tls.Certificate{}
for _, cert := range this.Certs {
err := cert.Init(ctx)
if err != nil {
return err
}
if this.OCSPIsOn && len(cert.OCSP) > 0 && cert.OCSPExpiresAt > time.Now().Unix() {
if this.ocspExpiresAt == 0 || cert.OCSPExpiresAt < this.ocspExpiresAt {
this.ocspExpiresAt = cert.OCSPExpiresAt
}
cert.CertObject().OCSPStaple = cert.OCSP
}
certs = append(certs, *cert.CertObject())
for _, dnsName := range cert.DNSNames {
this.nameMapping[dnsName] = cert.CertObject()
}
}
// CA certs
this.clientCAPool = x509.NewCertPool()
for _, cert := range this.ClientCACerts {
err := cert.Init(ctx)
if err != nil {
return err
}
certs = append(certs, *cert.CertObject())
for _, dnsName := range cert.DNSNames {
this.nameMapping[dnsName] = cert.CertObject()
}
for _, caCert := range cert.CACerts() {
this.clientCAPool.AddCert(caCert)
}
}
// min version
this.convertMinVersion()
// cipher suite categories
this.initCipherSuites()
// hsts
if this.HSTS != nil {
err := this.HSTS.Init()
if err != nil {
return err
}
}
// tls config
this.tlsConfig = &tls.Config{}
cipherSuites := this.TLSCipherSuites()
if !this.CipherSuitesIsOn || len(cipherSuites) == 0 {
cipherSuites = nil
}
var nextProto = []string{}
if this.HTTP2Enabled {
nextProto = []string{http2.NextProtoTLS}
}
this.tlsConfig = &tls.Config{
Certificates: certs,
MinVersion: this.TLSMinVersion(),
CipherSuites: cipherSuites,
GetCertificate: nil,
ClientAuth: GoSSLClientAuthType(this.ClientAuthType),
ClientCAs: this.CAPool(),
NextProtos: nextProto,
}
return nil
}
// TLSMinVersion 取得最小版本
func (this *SSLPolicy) TLSMinVersion() uint16 {
return this.minVersion
}
// TLSCipherSuites 套件
func (this *SSLPolicy) TLSCipherSuites() []uint16 {
return this.cipherSuites
}
// MatchDomain 校验是否匹配某个域名
func (this *SSLPolicy) MatchDomain(domain string) (cert *tls.Certificate, ok bool) {
cert, ok = this.nameMapping[domain]
if ok {
return
}
for name, cert := range this.nameMapping {
if configutils.MatchDomain(name, domain) {
return cert, true
}
}
return nil, false
}
// FirstCert 取得第一个证书
func (this *SSLPolicy) FirstCert() *tls.Certificate {
for _, cert := range this.Certs {
return cert.CertObject()
}
return nil
}
// CAPool CA证书Pool用于TLS对客户端进行认证
func (this *SSLPolicy) CAPool() *x509.CertPool {
return this.clientCAPool
}
func (this *SSLPolicy) TLSConfig() *tls.Config {
return this.tlsConfig
}
// ContainsCert 检查是否包括某个证书
func (this *SSLPolicy) ContainsCert(certId int64) bool {
for _, cert := range this.Certs {
if cert.Id == certId {
return true
}
}
return false
}
// UpdateCertOCSP 修改某个证书的OCSP
func (this *SSLPolicy) UpdateCertOCSP(certId int64, ocsp []byte, expiresAt int64) {
var nowTime = time.Now().Unix()
for _, cert := range this.Certs {
if cert.Id == certId {
cert.OCSP = ocsp
cert.OCSPExpiresAt = expiresAt
cert.CertObject().OCSPStaple = cert.OCSP
// 修改tlsConfig中的cert
for index, certObj := range this.tlsConfig.Certificates {
if this.certIsEqual(*cert.CertObject(), certObj) {
if len(cert.OCSP) > 0 && cert.OCSPExpiresAt > nowTime {
this.tlsConfig.Certificates[index].OCSPStaple = ocsp
// 重置过期时间
if this.ocspExpiresAt == 0 || cert.OCSPExpiresAt < this.ocspExpiresAt {
this.ocspExpiresAt = cert.OCSPExpiresAt
}
} else {
this.tlsConfig.Certificates[index].OCSPStaple = nil
}
}
}
break
}
}
}
// CheckOCSP 检查OCSP过期时间
func (this *SSLPolicy) CheckOCSP() {
if !this.OCSPIsOn || this.ocspExpiresAt == 0 {
return
}
var nowTime = time.Now().Unix()
if this.ocspExpiresAt > nowTime {
return
}
this.ocspExpiresAt = 0
for _, cert := range this.Certs {
if cert.OCSPExpiresAt > 0 && cert.OCSPExpiresAt < nowTime+1 {
// 重置OCSP
cert.OCSP = nil
cert.OCSPExpiresAt = 0
for index, certObj := range this.tlsConfig.Certificates {
if this.certIsEqual(*cert.CertObject(), certObj) {
this.tlsConfig.Certificates[index].OCSPStaple = nil
}
}
} else if len(cert.OCSP) > 0 && cert.OCSPExpiresAt > nowTime && (this.ocspExpiresAt == 0 || cert.OCSPExpiresAt < this.ocspExpiresAt) {
// 重置过期时间
this.ocspExpiresAt = cert.OCSPExpiresAt
}
}
}
// OcspExpiresAt OCSP最近过期时间
func (this *SSLPolicy) OcspExpiresAt() int64 {
return this.ocspExpiresAt
}
func (this *SSLPolicy) certIsEqual(cert1 tls.Certificate, cert2 tls.Certificate) bool {
var b1 = cert1.Certificate
var b2 = cert2.Certificate
if len(b1) != len(b2) {
return false
}
for index, b := range b1 {
if !bytes.Equal(b, b2[index]) {
return false
}
}
return true
}

View File

@@ -0,0 +1,6 @@
package sslconfigs
type SSLPolicyRef struct {
IsOn bool `yaml:"isOn" json:"isOn"`
SSLPolicyId int64 `yaml:"sslPolicyId" json:"sslPolicyId"`
}

View File

@@ -0,0 +1,134 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package sslconfigs_test
import (
"context"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/assert"
"testing"
"time"
)
func TestSSLPolicy_MatchDomain(t *testing.T) {
var a = assert.NewAssertion(t)
var policy = &sslconfigs.SSLPolicy{}
policy.Certs = []*sslconfigs.SSLCertConfig{
{
Id: 1,
DNSNames: []string{"a.com", "b.com"},
},
{
Id: 2,
DNSNames: []string{"c.com", "d.com"},
},
{
Id: 3,
DNSNames: []string{"e.com", "f.com"},
},
}
{
_, ok := policy.MatchDomain("c.com")
a.IsTrue(ok)
}
}
func TestSSLPolicy_CheckOCSP(t *testing.T) {
var certData = []byte(`-----BEGIN CERTIFICATE-----
MIIEcTCCA9qgAwIBAgIDbhMuMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYDVQQGEwJD
TjESMBAGA1UECBMJR3Vhbmdkb25nMREwDwYDVQQHEwhTaGVuemhlbjEQMA4GA1UE
ChMHVGVuY2VudDEMMAoGA1UECxMDV1hHMRMwEQYDVQQDEwpNbXBheW1jaENBMR8w
HQYJKoZIhvcNAQkBFhBtbXBheW1jaEB0ZW5jZW50MB4XDTE2MTIxMjA5NDAwM1oX
DTI2MTIxMDA5NDAwM1owgaExCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlHdWFuZ2Rv
bmcxETAPBgNVBAcTCFNoZW56aGVuMRAwDgYDVQQKEwdUZW5jZW50MQ4wDAYDVQQL
EwVNTVBheTE2MDQGA1UEAxQt5YyX5Lqs5LiJ55m+5YWt5Y2B6KGM5LqS6IGU56eR
5oqA5pyJ6ZmQ5YWs5Y+4MREwDwYDVQQEEwgxNzIyNzc0NDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAN2/1axdFhLKgMAGpkM9kpBfz88IvVYLFLaRrsIO
aM4RLDup5ye0GrOvQtq8gvPFbn+GuekyBfoVRNHW1OSv/uQfDYd5tcmAy/0BDZSL
OfPHaYOS2fj2y9KvLZTFTMBszG9kwV/FFlHgK4SJKbikdqTPd9vnt6Yr7FyfTIws
K9RQ77vetOTduWZttON+RK/Tlz6AepiVfl9LZ/XOVveYI/6TfEbI6uUoeXrlSKCf
w8/yfo69tcZV0g9yjSnVYDvgp6BFXJ1QK1CnJB4Dnol8XoBgUIrUyJqO+LvPr2Qy
wsnyONc15AJK/23vebDGGvTvYtu47qRywISD4ioW15YBK1UCAwEAAaOCAUYwggFC
MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHSJDRVMtQ0EgR2VuZXJhdGUgQ2Vy
dGlmaWNhdGUiMB0GA1UdDgQWBBQVQCAalLY0TuS+z80biOcWb0QkzjCBvwYDVR0j
BIG3MIG0gBQ+BSb2ImK0FVuIzWR+sNRip+WGdKGBkKSBjTCBijELMAkGA1UEBhMC
Q04xEjAQBgNVBAgTCUd1YW5nZG9uZzERMA8GA1UEBxMIU2hlbnpoZW4xEDAOBgNV
BAoTB1RlbmNlbnQxDDAKBgNVBAsTA1dYRzETMBEGA1UEAxMKTW1wYXltY2hDQTEf
MB0GCSqGSIb3DQEJARYQbW1wYXltY2hAdGVuY2VudIIJALtUlyu8AOhXMA4GA1Ud
DwEB/wQEAwIGwDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF
AAOBgQA/Zr9PIRE8c3mAnb0lmx/DToFJrUB4Sr51szjiX5XiKymBoC2hnwJvI+7B
EkRdNv4S7rvu33GS7BcZvjEwyrZdA9ZRIQz1MiaBIXdayIkkUCxaStB1junI8Jfc
dG6S+JIMJU8y0tG53vEG2JRw8Mmm1qloAxs1Zl92UtlZoiHHCQ==
-----END CERTIFICATE-----
`)
var keyData = []byte(`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdv9WsXRYSyoDA
BqZDPZKQX8/PCL1WCxS2ka7CDmjOESw7qecntBqzr0LavILzxW5/hrnpMgX6FUTR
1tTkr/7kHw2HebXJgMv9AQ2Uiznzx2mDktn49svSry2UxUzAbMxvZMFfxRZR4CuE
iSm4pHakz3fb57emK+xcn0yMLCvUUO+73rTk3blmbbTjfkSv05c+gHqYlX5fS2f1
zlb3mCP+k3xGyOrlKHl65Uign8PP8n6OvbXGVdIPco0p1WA74KegRVydUCtQpyQe
A56JfF6AYFCK1Miajvi7z69kMsLJ8jjXNeQCSv9t73mwxhr072LbuO6kcsCEg+Iq
FteWAStVAgMBAAECggEAVkqTfMqQj2lsJs2vn5TzVulh9cAB5dzUB6OzbOKsmBwI
qYMZZ9LnXSsDihk3oGMg99FWwU9tEf9602mVWRS/zMfkvOZ4/lv3hZIGVdrEB4B/
J+talU58zJTM2QraLjtoZqS/t2P7porkhGPX73lYjhQKIXIPfkOza+u1nwqFV84Z
YowiRTowuBHAAduW7W5uv4MaGG6P9w/JzR4zHCUjc5rnh/3a3+TN6KRxkXDh+1yi
6wg0S54qtTyAEIeGMjIqhzUgN0fxlhyMgtROi8h3DN/tvBoOCT9jGFeTsBcfk6Ib
p4sMDo/OcC1NXsENsccVprH297jKmwV0vZFGUebPAQKBgQD9bfrWU0TvLlLILJmT
52HRy6HCddKV6SdCBF04Rz3a5L32epKREql6l8KewHo05wlty90UL4sltHwZo9h6
QuukNMMuLvaye2qOAkuFw1x5qD4R2VvbQsPDHoJt0zOzzF77/Faob+3NSHk9Yt3h
s7/LrU9vDfoPVROMatJR01XzFQKBgQDf/5kofDYQ/qcddosktkgxIyZBFuE4C/s+
nhiXl/Kd5Q+AP2o6kPsl5o4Jz2s3zBrmyRb733Zhb/rx/gbebTvqLjrTpyxXovmQ
8ecKeAS+IlrvAEDDT4c6ecAXR4zHZER00g0zbL4sX+fpKzON+jL6poA/el4MQySR
/DLJUx1nQQKBgQCLNQFG/2BrPXfNaupFWyDZW9CT/6JYJEUjN0B5bHCmr2VFYdjm
hWjA5WHLUBEQxCPiwsvCjccSRAyzDNQZfG7xuOXJlZR/P9ms/ce8Ry6hyO+nYEzb
qNXddQHSD+RjjAxUwCxdw3XNgFTQimE03EarO5zZdMT57RKa3AaBWePpbQKBgQCq
D4fcMNFrfaqqt8FUEgAlLiZw7En5Hz+Ufrr0/Kt6LNnj6EFiTYgfcjcMQ6mHJzKV
XL5SY4mg2D+RUectH4mJdae74QPNVTJcVQuv6wbOw45+PZbtsYddYenwwqWjDADd
IExdaoXHctjDMcVmWTozCg38I48biC5Pl0WHi86bAQKBgGBK6XUJPRYOsQFshunq
edxSbZBiYFDUj6SfOdaTSuU61KOWRTXJyuOBaB77usmZdwOrB4vy1XUT1uuPWKlx
SKmNoe/mk2xYiGdKvFDRRHh25zCxDWsQ2nMQfUFczTZ9wBwGs40wzm36fSgHZybq
Z3NIV2eNt6YBwkC69DzdazXT
-----END PRIVATE KEY-----
`)
var policy = &sslconfigs.SSLPolicy{
OCSPIsOn: true,
}
var nowTime = time.Now().Unix()
policy.Certs = append(policy.Certs, &sslconfigs.SSLCertConfig{
Id: 1,
CertData: certData,
KeyData: keyData,
OCSP: []byte("ocsp"),
OCSPExpiresAt: nowTime + 1,
})
policy.Certs = append(policy.Certs, &sslconfigs.SSLCertConfig{
Id: 1,
CertData: certData,
KeyData: keyData,
OCSP: []byte("ocsp"),
OCSPExpiresAt: nowTime + 3,
})
policy.Certs = append(policy.Certs, &sslconfigs.SSLCertConfig{
Id: 1,
CertData: certData,
KeyData: keyData,
OCSP: []byte("ocsp"),
OCSPExpiresAt: nowTime + 2,
})
err := policy.Init(context.TODO())
if err != nil {
t.Fatal(err)
}
t.Log(policy.OcspExpiresAt(), policy.OcspExpiresAt() == nowTime+1)
time.Sleep(1 * time.Second)
policy.CheckOCSP()
t.Log(policy.OcspExpiresAt(), policy.OcspExpiresAt() == nowTime+2)
}