Golang ssl 证书 验证

代码平平无奇,需要注意的是http client初始化部分。

因为是短连接,如果是大量域名去验证的话,短时间内将导致大量TIME_WAIT

go 复制代码
package util

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/panjf2000/ants/v2"
	log "github.com/sirupsen/logrus"
)

var client = &http.Client{
	Transport: &http.Transport{
		// 注意如果证书已过期,那么只有在关闭证书校验的情况下链接才能建立成功
		TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
		Proxy:             http.ProxyFromEnvironment,
		DisableKeepAlives: true,
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			d := net.Dialer{Timeout: 10 * time.Second}
			conn, err := d.DialContext(ctx, network, addr)
			if err != nil {
				return nil, err
			}
			tcpConn, ok := conn.(*net.TCPConn)
			if ok {
				tcpConn.SetLinger(0) // RST fix TIME_WAIT问题
				return tcpConn, nil
			}
			return conn, nil
		},
	},
}

func BatchCheckSsl(domains []string, batch int) (map[string]CheckSslResp, error) {
	domains = Uniq(domains)
	var domainSyncMap sync.Map
	var wg sync.WaitGroup
	pool, err := ants.NewPool(batch)
	if err != nil {
		log.Errorln(err)
		return nil, err
	}
	defer pool.Release()
	wg.Add(len(domains))
	for _, item := range domains {
		err = pool.Submit(wrapperCheckSSL(item, &domainSyncMap, &wg))
		if err != nil {
			log.Errorln(err)
			continue
		}
	}
	wg.Wait()
	fmt.Printf("running goroutines: %d\n", ants.Running())
	fmt.Printf("finish all tasks.\n")

	result := make(map[string]CheckSslResp, 0)
	domainSyncMap.Range(func(key, value interface{}) bool {
		vv, ok := value.(*CheckSslResp)
		if !ok {
			log.Println(fmt.Sprintf("CheckSslResp类型转换失败:%s", key))
			return false
		}
		result[fmt.Sprintf("%v", key)] = *vv
		return true
	})
	return result, nil
}

func wrapperCheckSSL(hostname string, domainSslMap *sync.Map, wg *sync.WaitGroup) func() {
	return func() {
		checkSsl(hostname, domainSslMap, wg)
	}
}

func checkSsl(hostname string, domainSslMap *sync.Map, wg *sync.WaitGroup) {
	defer wg.Done()

	log.Println(fmt.Sprintf("开始检查:%s", hostname))
	notAfter, err := CheckSSL(hostname)
	if err != nil {
		log.Errorln(err)
	}
	log.Println(fmt.Sprintf("完成检查:%s,notAfter:%v:", hostname, notAfter))
	ri := new(CheckSslResp)
	ri.Domain = hostname
	if err != nil {
		ri.Success = false
		ri.Error = err
		ri.NotAfter = nil
	} else {
		ri.Success = true
		ri.NotAfter = notAfter
	}
	domainSslMap.Store(hostname, ri)
	return
}

func CheckSSL(url string) (*time.Time, error) {
	//client := &http.Client{
	//	Transport: &http.Transport{
	//		// 注意如果证书已过期,那么只有在关闭证书校验的情况下链接才能建立成功
	//		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	//	},
	//	// 10s 超时后认为服务挂了
	//	Timeout: 10 * time.Second,
	//}

	url = strings.ReplaceAll(url, "http://", "https://")
	if !strings.HasPrefix(url, "https://") {
		url = fmt.Sprintf("https://%s", url)
	}
	log.Println(url)
	resp, err := client.Get(url)
	if err != nil {
		log.Errorln(err)
		return nil, err
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.TLS == nil {
		return nil, errors.New("未获取到SSL信息")
	}
	// 遍历所有证书
	for _, cert := range resp.TLS.PeerCertificates {
		if cert.Issuer.CommonName == "Kubernetes Ingress Controller Fake Certificate" {
			return nil, errors.New("Kubernetes Ingress Controller Fake Certificate")
		}
		// 检测证书是否已经过期
		if !cert.NotAfter.After(time.Now()) {
			log.Warningln(fmt.Sprintf("Website [%s] certificate has expired: %s", url, cert.NotAfter.Local().Format("2006-01-02 15:04:05")))
		}
		return &cert.NotAfter, nil
	}
	return nil, err
}

type CheckSslResp struct {
	Domain   string
	Error    error
	Success  bool
	NotAfter *time.Time
}
相关推荐
Pandaconda21 分钟前
【Golang 面试题】每日 3 题(三十九)
开发语言·经验分享·笔记·后端·面试·golang·go
加油,旭杏25 分钟前
【go语言】变量和常量
服务器·开发语言·golang
行路见知25 分钟前
3.3 Go 返回值详解
开发语言·golang
编程小筑1 小时前
R语言的编程范式
开发语言·后端·golang
技术的探险家1 小时前
Elixir语言的文件操作
开发语言·后端·golang
Ai 编码助手1 小时前
Golang 中强大的重试机制,解决瞬态错误
开发语言·后端·golang
齐雅彤2 小时前
Lisp语言的区块链
开发语言·后端·golang
齐雅彤2 小时前
Lisp语言的循环实现
开发语言·后端·golang
梁雨珈2 小时前
Lisp语言的物联网
开发语言·后端·golang
邓熙榆3 小时前
Logo语言的网络编程
开发语言·后端·golang