client-hello-mirror/formatting.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")
}