302 lines
8.4 KiB
Go
302 lines
8.4 KiB
Go
// SPDX-FileCopyrightText: 2022-2023 nervuri <https://nervuri.net/contact>
|
|
//
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
htmlTemplate "html/template"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"tildegit.org/nervuri/client-hello-mirror/clienthello"
|
|
)
|
|
|
|
func toHex(n uint16) string { // for extension codes
|
|
return fmt.Sprintf("%04X", n)
|
|
}
|
|
|
|
// Convert Unix timestamp to human-readable form.
|
|
func formatTimestamp(timestamp uint32) string {
|
|
gmtUnixTime := time.Unix(int64(timestamp), 0)
|
|
tz, err := time.LoadLocation("GMT")
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
return gmtUnixTime.In(tz).Format(time.UnixDate)
|
|
}
|
|
|
|
// === HTML functions ===
|
|
|
|
func getBoolHTML(b bool, trueClass, falseClass string) htmlTemplate.HTML {
|
|
if b {
|
|
return htmlTemplate.HTML("<span class=\"" + trueClass + "\">true</span>")
|
|
} else {
|
|
return htmlTemplate.HTML("<span class=\"" + falseClass + "\">false</span>")
|
|
}
|
|
}
|
|
|
|
func getTLSVersionHTML(v clienthello.TLSVersion) string {
|
|
var s string
|
|
vInfo := clienthello.GetTLSVersionInfo(v.(uint16), true)
|
|
if vInfo.Name == "GREASE" {
|
|
s = "<span class=\"dim\">0x" + vInfo.HexCode + " (GREASE)</span>"
|
|
} else if v.(uint16) < 771 { // < TLSv1.2
|
|
s = "<span class=\"bad\">" + vInfo.Name + " (deprecated)</span>"
|
|
} else if v.(uint16) == 771 { // = TLSv1.2
|
|
s = vInfo.Name
|
|
} else { // TLSv1.3 or later
|
|
s = "<span class=\"good\">" + vInfo.Name + "</span>"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func getTLSVersionsHTML(versions []clienthello.TLSVersion) htmlTemplate.HTML {
|
|
var s = "<ul>\n"
|
|
for _, v := range versions {
|
|
s += "<li><code>" + getTLSVersionHTML(v.(uint16)) + "</code></li>\n"
|
|
}
|
|
s += "</ul>\n"
|
|
return htmlTemplate.HTML(s)
|
|
}
|
|
|
|
func getCipherSuiteHTML(cs clienthello.CipherSuite) string {
|
|
var s string
|
|
csInfo := clienthello.GetCipherSuiteInfo(cs.(uint16), true)
|
|
if csInfo.Name == "GREASE" {
|
|
s = "<span class=\"dim\">0x" + csInfo.HexCode + " (GREASE)</span>"
|
|
} else {
|
|
visibleName := strings.Join(strings.Split(csInfo.Name, "_"), "_<wbr/>")
|
|
if csInfo.Code == clienthello.SCSVRenegotiation {
|
|
// TLS_EMPTY_RENEGOTIATION_INFO_SCSV
|
|
s = visibleName
|
|
} else if csInfo.Recommended {
|
|
if csInfo.HexCode[:2] == "13" { // TLS 1.3 cipher suites
|
|
s = "<a href=\"https://ciphersuite.info/cs/" + csInfo.Name +
|
|
"/\" class=\"good\" target=\"_blank\">" + visibleName + "</a>"
|
|
} else {
|
|
s = "<a href=\"https://ciphersuite.info/cs/" + csInfo.Name +
|
|
"/\" target=\"_blank\">" + visibleName + "</a>"
|
|
}
|
|
} else {
|
|
s = "<a href=\"https://ciphersuite.info/cs/" + csInfo.Name +
|
|
"/\" class=\"meh\" target=\"_blank\">" + visibleName +
|
|
" (not recommended)</a>"
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func getCipherSuitesHTML(suites []clienthello.CipherSuite) htmlTemplate.HTML {
|
|
var s = "<ul>\n"
|
|
for _, cs := range suites {
|
|
s += "<li><code>" + getCipherSuiteHTML(cs.(uint16)) + "</code></li>\n"
|
|
}
|
|
s += "</ul>\n"
|
|
return htmlTemplate.HTML(s)
|
|
}
|
|
|
|
func getExtensionsHTML(extensions []clienthello.Extension) htmlTemplate.HTML {
|
|
var s = "<ul>\n"
|
|
for _, ext := range extensions {
|
|
if ext.Name == "GREASE" {
|
|
s += "<li><code class=\"dim\">0x" + toHex(ext.Code) + " (GREASE)</code></li>\n"
|
|
} else {
|
|
if ext.Name == "" {
|
|
s += "<li><code>" + fmt.Sprint(ext.Code) + "</code></li>\n"
|
|
} else {
|
|
ext.Name = strings.Join(strings.Split(ext.Name, "_"), "_<wbr/>")
|
|
s += "<li><code>" + ext.Name + "</code></li>\n"
|
|
}
|
|
}
|
|
}
|
|
s += "</ul>\n"
|
|
return htmlTemplate.HTML(s)
|
|
}
|
|
|
|
func getSupportedGroupsHTML(groups []clienthello.NamedGroup) htmlTemplate.HTML {
|
|
var s = "<ul>\n"
|
|
for _, g := range groups {
|
|
gInfo := clienthello.GetNamedGroupInfo(g.(uint16), true)
|
|
if gInfo.Name == "GREASE" {
|
|
s += "<li><code class=\"dim\">0x" + gInfo.HexCode +
|
|
" (GREASE)</code></li>\n"
|
|
} else {
|
|
s += "<li><code>" + gInfo.Name + "</code></li>\n"
|
|
}
|
|
}
|
|
s += "</ul>\n"
|
|
return htmlTemplate.HTML(s)
|
|
}
|
|
|
|
//func getSupportedPointFormatsHTML(pFormats []clienthello.ECPointFormat) htmlTemplate.HTML {
|
|
// var s = "<ul>\n"
|
|
// for _, pf := range pFormats {
|
|
// name := clienthello.GetECPointFormatInfo(pf.(uint8), true).Name
|
|
// name = strings.Join(strings.Split(name, "_"), "_<wbr/>")
|
|
// s += "<li><code>" + name + "</code></li>\n"
|
|
// }
|
|
// s += "</ul>\n"
|
|
// return htmlTemplate.HTML(s)
|
|
//}
|
|
|
|
func getSignatureSchemesHTML(sigSchemes []clienthello.SignatureScheme) htmlTemplate.HTML {
|
|
var s = "<ul>\n"
|
|
for _, ss := range sigSchemes {
|
|
schemeInfo := clienthello.GetSignatureSchemeInfo(ss.(uint16), true)
|
|
schemeInfo.Name = strings.Join(strings.Split(schemeInfo.Name, "_"), "_<wbr/>")
|
|
if schemeInfo.Name == "GREASE" {
|
|
s += "<li><code class=\"dim\">" + "0x" + schemeInfo.HexCode +
|
|
" (GREASE)</code></li>\n"
|
|
} else if schemeInfo.Recommended {
|
|
s += "<li><code>" + schemeInfo.Name + "</code></li>\n"
|
|
} else {
|
|
s += "<li><code class=\"meh\">" + schemeInfo.Name +
|
|
" (not recommended)</code></li>\n"
|
|
}
|
|
}
|
|
s += "</ul>\n"
|
|
return htmlTemplate.HTML(s)
|
|
}
|
|
|
|
func formatJA3(ja3 string) string {
|
|
titleArr := map[int]string{
|
|
0: "TLS version",
|
|
1: "cipher suites",
|
|
2: "extensions",
|
|
3: "supported groups",
|
|
4: "supported point formats",
|
|
}
|
|
sections := strings.Split(ja3, ",")
|
|
for i, s := range sections {
|
|
s = strings.Join(strings.Split(s, "-"), "-<wbr/>")
|
|
sections[i] = "<span title=\"" + titleArr[i] + "\" class=\"ja3\">" + s + "</span>"
|
|
}
|
|
return strings.Join(sections, ",<wbr/>")
|
|
}
|
|
|
|
func formatNJA3v1(nja3v1 string) string {
|
|
titleArr := map[int]string{
|
|
0: "TLS version (record header)",
|
|
1: "TLS version (handshake)",
|
|
2: "cipher suites",
|
|
3: "extensions (sorted, conditional extensions ignored)",
|
|
4: "supported groups",
|
|
5: "supported point formats",
|
|
6: "supported TLS versions",
|
|
7: "signature algorithms",
|
|
8: "pre-shared key exchange modes",
|
|
9: "certificate compression algorithms",
|
|
}
|
|
sections := strings.Split(nja3v1, ",")
|
|
for i, s := range sections {
|
|
s = strings.Join(strings.Split(s, "-"), "-<wbr/>")
|
|
sections[i] = "<span title=\"" + titleArr[i] + "\" class=\"ja3\">" + s + "</span>"
|
|
}
|
|
return strings.Join(sections, ",<wbr/>")
|
|
}
|
|
|
|
// === Gemtext functions ===
|
|
|
|
func getTLSVersionGemtext(v clienthello.TLSVersion) string {
|
|
var s string
|
|
vInfo := clienthello.GetTLSVersionInfo(v.(uint16), true)
|
|
if vInfo.Name == "GREASE" {
|
|
s = "0x" + vInfo.HexCode + " (GREASE)"
|
|
} else {
|
|
s = vInfo.Name
|
|
if v.(uint16) < 771 { // < TLSv1.2
|
|
s += " (deprecated)"
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func getTLSVersionsGemtext(versions []clienthello.TLSVersion) string {
|
|
var s string
|
|
for _, v := range versions {
|
|
s += "* " + getTLSVersionGemtext(v.(uint16)) + "\n"
|
|
}
|
|
return strings.TrimSuffix(s, "\n")
|
|
}
|
|
|
|
func getCipherSuiteGemtext(cs clienthello.CipherSuite, link bool) string {
|
|
var s string
|
|
csInfo := clienthello.GetCipherSuiteInfo(cs.(uint16), true)
|
|
if csInfo.Name == "GREASE" {
|
|
s = "0x" + csInfo.HexCode + " (GREASE)"
|
|
} else if csInfo.Code == clienthello.SCSVRenegotiation {
|
|
// TLS_EMPTY_RENEGOTIATION_INFO_SCSV
|
|
s = csInfo.Name
|
|
} else {
|
|
if link {
|
|
s = "=> https://ciphersuite.info/cs/" + csInfo.Name + "/ "
|
|
}
|
|
s += csInfo.Name
|
|
if !csInfo.Recommended {
|
|
s += " (not recommended)"
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func getCipherSuitesGemtext(suites []clienthello.CipherSuite) string {
|
|
var s string
|
|
for _, cs := range suites {
|
|
s += getCipherSuiteGemtext(cs.(uint16), true) + "\n"
|
|
}
|
|
return strings.TrimSuffix(s, "\n")
|
|
}
|
|
|
|
func getExtensionsGemtext(extensions []clienthello.Extension) string {
|
|
var s string
|
|
for _, ext := range extensions {
|
|
if ext.Name == "GREASE" {
|
|
s += "* 0x" + toHex(ext.Code) + " (GREASE)\n"
|
|
} else {
|
|
s += "* " + ext.Name + "\n"
|
|
}
|
|
}
|
|
return strings.TrimSuffix(s, "\n")
|
|
}
|
|
|
|
func getSupportedGroupsGemtext(groups []clienthello.NamedGroup) string {
|
|
var s string
|
|
for _, g := range groups {
|
|
gInfo := clienthello.GetNamedGroupInfo(g.(uint16), true)
|
|
if gInfo.Name == "GREASE" {
|
|
s += "* 0x" + gInfo.HexCode + " (GREASE)\n"
|
|
} else {
|
|
s += "* " + gInfo.Name + "\n"
|
|
}
|
|
}
|
|
return strings.TrimSuffix(s, "\n")
|
|
}
|
|
|
|
//func getSupportedPointFormatsGemtext(pFormats []clienthello.ECPointFormat) string {
|
|
// var s string
|
|
// for _, pf := range pFormats {
|
|
// s += "* " + clienthello.GetECPointFormatInfo(pf.(uint8), true).Name + "\n"
|
|
// }
|
|
// return strings.TrimSuffix(s, "\n")
|
|
//}
|
|
|
|
func getSignatureSchemesGemtext(sigSchemes []clienthello.SignatureScheme) string {
|
|
var s string
|
|
for _, ss := range sigSchemes {
|
|
schemeInfo := clienthello.GetSignatureSchemeInfo(ss.(uint16), true)
|
|
if schemeInfo.Name == "GREASE" {
|
|
s += "* 0x" + schemeInfo.HexCode + " (GREASE)"
|
|
} else {
|
|
s += "* " + schemeInfo.Name
|
|
if !schemeInfo.Recommended {
|
|
s += " (not recommended)"
|
|
}
|
|
}
|
|
s += "\n"
|
|
}
|
|
return strings.TrimSuffix(s, "\n")
|
|
}
|