package tls import ( "b612.me/starlog" "crypto/ecdsa" "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "fmt" "github.com/spf13/cobra" "golang.org/x/net/idna" "golang.org/x/net/proxy" "net" "os" "path/filepath" "strconv" "strings" "sync" "time" ) var hideDetail bool var dump string var reqRawIP int var timeoutMillSec int var socks5 string var socks5Auth string var showCA bool func init() { Cmd.Flags().BoolVarP(&hideDetail, "hide-detail", "H", false, "隐藏证书详细信息") Cmd.Flags().StringVarP(&dump, "dump", "d", "", "将证书保存到文件") Cmd.Flags().IntVarP(&reqRawIP, "resolve-ip", "r", 0, "使用解析到的IP地址进行连接,输入数字表示使用解析到的第几个IP地址") Cmd.Flags().IntVarP(&timeoutMillSec, "timeout", "t", 5000, "连接超时时间(毫秒)") Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080") Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证,示例:username:password") Cmd.Flags().BoolVarP(&showCA, "show-ca", "c", false, "显示CA证书") } var Cmd = &cobra.Command{ Use: "tls", Short: "查看TLS证书信息", Long: "查看TLS证书信息", Run: func(cmd *cobra.Command, args []string) { for _, target := range args { showTls(target, !hideDetail, showCA, reqRawIP, dump, time.Duration(timeoutMillSec)*time.Millisecond) } }, } func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath string, timeout time.Duration) { var err error { sp := strings.Split(target, ":") if len(sp) < 2 { target = target + ":443" } else { if _, err := strconv.Atoi(sp[len(sp)-1]); err != nil { target = target + ":443" } } } if timeout == 0 { timeout = 5 * time.Second } hostname := strings.Split(target, ":")[0] if strings.Count(target, ":") == 2 { strs := strings.Split(target, ":") if len(strs) != 3 { starlog.Errorln("invalid target format") return } target = strs[0] + ":" + strs[2] hostname = strs[1] } if reqRawIP > 0 { domain := strings.Split(target, ":")[0] ips, err := net.LookupIP(domain) if err != nil { starlog.Errorln("failed to resolve domain: " + err.Error()) return } if len(ips) == 0 { starlog.Errorln("no ip found for domain") return } for _, v := range ips { starlog.Infof("解析到的IP地址为: %s\n", v.String()) } if reqRawIP > len(ips) { reqRawIP = len(ips) } target = ips[reqRawIP-1].String() + ":443" hostname = ips[reqRawIP-1].String() starlog.Noticeln("使用解析到的IP地址进行连接:", target) } starlog.Noticef("将使用如下地址连接:%s ; ServerName: %s\n", target, hostname) punyCode, err := idna.ToASCII(hostname) if err == nil { if punyCode != hostname { starlog.Infoln("检测到域名中含有非ASCII字符,PunyCode转换后为:", punyCode) hostname = punyCode } } starlog.Infof("正在连接服务器: %s\n", target) var netDialer = &net.Dialer{ Timeout: timeout, } var socksDialer *proxy.Dialer if socks5 != "" { var auth *proxy.Auth if socks5Auth != "" { up := strings.SplitN(socks5Auth, ":", 2) if len(up) == 2 { auth = &proxy.Auth{ User: up[0], Password: up[1], } } else { starlog.Errorln("socks5认证格式错误") return } } s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct) if err == nil { socksDialer = &s5Dial } else { starlog.Errorln("socks5代理错误:", err) return } } var verifyErr error var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() var conn *tls.Conn if socksDialer == nil { conn, verifyErr = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{ InsecureSkipVerify: false, ServerName: hostname, MinVersion: tls.VersionSSL30, }) if verifyErr == nil { conn.Close() } } else { con, err := (*socksDialer).Dial("tcp", target) if err != nil { verifyErr = err return } conn = tls.Client(con, &tls.Config{ InsecureSkipVerify: false, ServerName: hostname, MinVersion: tls.VersionSSL30, }) verifyErr = conn.Handshake() con.Close() } }() var conn *tls.Conn if socksDialer == nil { conn, err = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{ InsecureSkipVerify: true, ServerName: hostname, MinVersion: tls.VersionSSL30, }) if err != nil { starlog.Errorln("failed to connect: " + err.Error()) return } } else { con, err := (*socksDialer).Dial("tcp", target) if err != nil { starlog.Errorln("failed to connect: " + err.Error()) return } defer con.Close() conn = tls.Client(con, &tls.Config{ InsecureSkipVerify: true, ServerName: hostname, MinVersion: tls.VersionSSL30, }) err = conn.Handshake() if err != nil { starlog.Errorln("failed to handshake: " + err.Error()) return } } defer conn.Close() starlog.Infof("连接成功,对方IP:%s,正在获取证书信息\n", conn.RemoteAddr().String()) certs := conn.ConnectionState().PeerCertificates if len(certs) == 0 { starlog.Errorln("no certificate found") return } starlog.Infof("证书获取成功,证书链上共有%d个证书\n", len(certs)) state := conn.ConnectionState() switch state.Version { case tls.VersionSSL30: starlog.Warningln("当前TLS版本: SSL 3.0") case tls.VersionTLS10: starlog.Warningln("当前TLS版本: TLS 1.0") case tls.VersionTLS11: starlog.Warningln("当前TLS版本: TLS 1.1") case tls.VersionTLS12: starlog.Infoln("当前TLS版本: TLS 1.2") case tls.VersionTLS13: starlog.Infoln("当前TLS版本: TLS 1.3") } switch state.CipherSuite { case tls.TLS_RSA_WITH_RC4_128_SHA: starlog.Infoln("当前加密套件: TLS_RSA_WITH_RC4_128_SHA") case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: starlog.Infoln("当前加密套件: TLS_RSA_WITH_3DES_EDE_CBC_SHA") case tls.TLS_RSA_WITH_AES_128_CBC_SHA: starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_128_CBC_SHA") case tls.TLS_RSA_WITH_AES_256_CBC_SHA: starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_256_CBC_SHA") case tls.TLS_RSA_WITH_AES_128_CBC_SHA256: starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_128_CBC_SHA256") case tls.TLS_RSA_WITH_AES_128_GCM_SHA256: starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_128_GCM_SHA256") case tls.TLS_RSA_WITH_AES_256_GCM_SHA384: starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_256_GCM_SHA384") case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA") case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") case tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") case tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_RC4_128_SHA") case tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA") case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") case tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256") case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256") case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305") case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305") case tls.TLS_AES_128_GCM_SHA256: starlog.Infoln("当前加密套件: TLS_AES_128_GCM_SHA256") case tls.TLS_AES_256_GCM_SHA384: starlog.Infoln("当前加密套件: TLS_AES_256_GCM_SHA384") case tls.TLS_CHACHA20_POLY1305_SHA256: starlog.Infoln("当前加密套件: TLS_CHACHA20_POLY1305_SHA256") default: starlog.Infoln("当前加密套件:", state.CipherSuite) } starlog.Infoln("服务器名称:", state.ServerName) wg.Wait() if verifyErr != nil { starlog.Red("证书验证失败: " + verifyErr.Error()) } else { starlog.Green("证书验证成功") } if showDetail { for _, c := range certs { if c.IsCA && !showCA { continue } fmt.Printf("----------\n") if c.IsCA { fmt.Println("!这是一个CA证书!") } fmt.Printf("证书基础信息: %+v\n", c.Subject) fmt.Printf("证书颁发者: %+v\n", c.Issuer) fmt.Printf("证书生效时间: %+v 距今:%.1f天\n", c.NotBefore.In(time.Local), time.Since(c.NotBefore).Hours()/24) fmt.Printf("证书过期时间: %+v 剩余:%.1f天\n", c.NotAfter.In(time.Local), c.NotAfter.Sub(time.Now()).Hours()/24) fmt.Printf("证书序列号: %s\n", c.SerialNumber.Text(16)) fmt.Printf("证书签名算法: %s\n", c.SignatureAlgorithm) fmt.Printf("证书公钥算法: %s\n", c.PublicKeyAlgorithm) switch pub := c.PublicKey.(type) { case *rsa.PublicKey: fmt.Printf("RSA公钥位数: %d\n", pub.Size()*8) // RSA公钥的位数 case *ecdsa.PublicKey: fmt.Printf("ECDSA Curve位数: %d\n", pub.Curve.Params().BitSize) // ECDSA公钥的位数 } if len(c.DNSNames) != 0 { fmt.Printf("可选使用的DNS: %s\n", strings.Join(c.DNSNames, ", ")) } if len(c.IPAddresses) != 0 { ipAddr := "" for _, ip := range c.IPAddresses { ipAddr += ip.String() + ", " } ipAddr = ipAddr[:len(ipAddr)-2] fmt.Printf("可选使用的IP: %s\n", ipAddr) } if len(c.EmailAddresses) != 0 { fmt.Printf("可选使用的Email: %s\n", strings.Join(c.EmailAddresses, ", ")) } if len(c.URIs) != 0 { fmt.Printf("可选使用的URI: %v\n", c.URIs) } if len(c.PermittedDNSDomains) != 0 { fmt.Printf("批准使用的DNS: %s\n", strings.Join(c.PermittedDNSDomains, ", ")) } if len(c.PermittedIPRanges) != 0 { ipRange := "" for _, ip := range c.PermittedIPRanges { ipRange += ip.String() + ", " } ipRange = ipRange[:len(ipRange)-2] fmt.Printf("批准使用的IP: %s\n", ipRange) } if len(c.PermittedEmailAddresses) != 0 { fmt.Printf("批准使用的Email: %s\n", strings.Join(c.PermittedEmailAddresses, ", ")) } if len(c.PermittedURIDomains) != 0 { fmt.Printf("批准使用的URI: %s\n", strings.Join(c.PermittedURIDomains, ", ")) } fmt.Printf("证书密钥用途: %s\n", strings.Join(KeyUsageToString(c.KeyUsage), ", ")) extKeyUsage := []string{} for _, v := range c.ExtKeyUsage { switch v { case x509.ExtKeyUsageAny: extKeyUsage = append(extKeyUsage, "任何用途") case x509.ExtKeyUsageServerAuth: extKeyUsage = append(extKeyUsage, "服务器认证") case x509.ExtKeyUsageClientAuth: extKeyUsage = append(extKeyUsage, "客户端认证") case x509.ExtKeyUsageCodeSigning: extKeyUsage = append(extKeyUsage, "代码签名") case x509.ExtKeyUsageEmailProtection: extKeyUsage = append(extKeyUsage, "电子邮件保护") case x509.ExtKeyUsageIPSECEndSystem: extKeyUsage = append(extKeyUsage, "IPSEC终端系统") case x509.ExtKeyUsageIPSECTunnel: extKeyUsage = append(extKeyUsage, "IPSEC隧道") case x509.ExtKeyUsageIPSECUser: extKeyUsage = append(extKeyUsage, "IPSEC用户") case x509.ExtKeyUsageTimeStamping: extKeyUsage = append(extKeyUsage, "时间戳") case x509.ExtKeyUsageOCSPSigning: extKeyUsage = append(extKeyUsage, "OCSP签名") case x509.ExtKeyUsageMicrosoftServerGatedCrypto: extKeyUsage = append(extKeyUsage, "Microsoft服务器门控加密") case x509.ExtKeyUsageNetscapeServerGatedCrypto: extKeyUsage = append(extKeyUsage, "Netscape服务器门控加密") case x509.ExtKeyUsageMicrosoftCommercialCodeSigning: extKeyUsage = append(extKeyUsage, "Microsoft商业代码签名") case x509.ExtKeyUsageMicrosoftKernelCodeSigning: extKeyUsage = append(extKeyUsage, "Microsoft内核代码签名") default: extKeyUsage = append(extKeyUsage, fmt.Sprintf("未知用途(%d)", v)) } } fmt.Printf("证书扩展密钥用途: %s\n", strings.Join(extKeyUsage, ", ")) fmt.Printf("证书版本: %d\n----------\n", c.Version) //fmt.Printf("证书扩展信息: %+v\n\n----------", c.Extensions) } if dumpPath != "" { var data []byte var name string for _, c := range certs { if name == "" { name = c.Subject.CommonName + ".crt" } certBlock := &pem.Block{ Type: "CERTIFICATE", Bytes: c.Raw, } data = append(data, pem.EncodeToMemory(certBlock)...) } err = os.WriteFile(filepath.Join(dumpPath, name), data, 0644) if err != nil { starlog.Errorln("failed to write file: " + err.Error()) return } starlog.Infoln("dumped to " + filepath.Join(dumpPath, name)) } } } func KeyUsageToString(ku x509.KeyUsage) []string { usages := []string{} flags := []struct { Flag x509.KeyUsage Name string }{ {x509.KeyUsageDigitalSignature, "数字签名"}, {x509.KeyUsageContentCommitment, "内容承诺"}, {x509.KeyUsageKeyEncipherment, "密钥加密"}, {x509.KeyUsageDataEncipherment, "数据加密"}, {x509.KeyUsageKeyAgreement, "密钥协商"}, {x509.KeyUsageCertSign, "证书签名"}, {x509.KeyUsageCRLSign, "CRL签名"}, {x509.KeyUsageEncipherOnly, "仅加密"}, {x509.KeyUsageDecipherOnly, "仅解密"}, } for _, flag := range flags { if ku&flag.Flag != 0 { usages = append(usages, flag.Name) } } return usages }