// SPDX-FileCopyrightText: 2022-2023 nervuri // // SPDX-License-Identifier: BSD-3-Clause package clienthello import ( _ "embed" "encoding/csv" "fmt" "io" "log" "strconv" "strings" ) // RFC 8446, Section 4.2.3 // https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3 // https://www.iana.org/assignments/tls-parameters/tls-signaturescheme.csv // //go:embed signature-schemes.csv var signatureSchemesCSV string var SignatureSchemeList = parseSignatureSchemesCSV() type SignatureScheme any // (uint16 | SignatureSchemeInfo) type SignatureSchemeInfo struct { Code uint16 `json:"code"` HexCode string `json:"hex_code"` Name string `json:"name"` Recommended bool `json:"recommended"` Reference string `json:"-"` } // https://www.rfc-editor.org/rfc/rfc5246.html#section-7.4.1.4.1 type oldAlgo struct { Code uint8 Name string Reference string } // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18 var oldHashAlgoList = map[uint8]oldAlgo{ 0: {Code: 0, Name: "none", Reference: "[RFC5246]"}, 1: {Code: 1, Name: "md5", Reference: "[RFC5246]"}, 2: {Code: 2, Name: "sha1", Reference: "[RFC5246]"}, 3: {Code: 3, Name: "sha224", Reference: "[RFC5246]"}, 4: {Code: 4, Name: "sha256", Reference: "[RFC5246]"}, 5: {Code: 5, Name: "sha384", Reference: "[RFC5246]"}, 6: {Code: 6, Name: "sha512", Reference: "[RFC5246]"}, 8: {Code: 8, Name: "", Reference: "[RFC8422]"}, // Intrinsic } // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16 var oldSigAlgoList = map[uint8]oldAlgo{ 0: {Code: 0, Name: "anonymous", Reference: "[RFC5246]"}, 1: {Code: 1, Name: "rsa", Reference: "[RFC5246]"}, 2: {Code: 2, Name: "dsa", Reference: "[RFC5246]"}, 3: {Code: 3, Name: "ecdsa", Reference: "[RFC5246]"}, 7: {Code: 7, Name: "ed25519", Reference: "[RFC8422]"}, 8: {Code: 8, Name: "ed448", Reference: "[RFC8422]"}, 64: {Code: 64, Name: "gostr34102012_256", Reference: "[RFC9189]"}, 65: {Code: 65, Name: "gostr34102012_512", Reference: "[RFC9189]"}, } func parseSignatureSchemesCSV() map[uint16]SignatureSchemeInfo { signatureSchemes := map[uint16]SignatureSchemeInfo{} r := csv.NewReader(strings.NewReader(signatureSchemesCSV)) // Skip CSV header. _, err := r.Read() if err != nil { log.Fatal(err) } for { row, err := r.Read() if err == io.EOF { break } else if err != nil { log.Fatal(err) } var ( val = row[0] desc = row[1] rec = row[2] ref = row[3] ) if desc == "Unassigned" { continue } if strings.HasPrefix(desc, "Reserved") && ref != "[RFC8701]" { continue } val = strings.Replace(val, "0x", "", 1) code, err := strconv.ParseUint(val, 16, 16) if err != nil { log.Println(err) } sigScheme := SignatureSchemeInfo{ Code: uint16(code), HexCode: fmt.Sprintf("%04X", code), Name: desc, Reference: ref, } if rec == "Y" { sigScheme.Recommended = true } if ref == "[RFC8701]" { // Note: as of September 2023, the IANA signature scheme registry // doesn't include GREASE values. sigScheme.Name = "GREASE" } signatureSchemes[sigScheme.Code] = sigScheme } return signatureSchemes } func GetSignatureSchemeInfo(sigSchemeCode uint16, mustName bool) SignatureSchemeInfo { info, found := SignatureSchemeList[sigSchemeCode] if !found { info = SignatureSchemeInfo{ Code: sigSchemeCode, HexCode: fmt.Sprintf("%04X", sigSchemeCode), } if isGREASE16(sigSchemeCode) { // As of September 2023, the IANA signature scheme registry // doesn't include GREASE values, so we need to check here. info.Name = "GREASE" info.Reference = "[RFC8701]" } else { // If info for this code was not found in the signature schemes // CSV file, then we might be dealing with values which were // removed in TLSv1.3, so we can try to derive the name by // concatenating hash and signature algorithm names. var hashAlgoCode = uint8(sigSchemeCode >> 8) // first octet var sigAlgoCode = uint8(sigSchemeCode) // second octet // info.Name will be empty if either hashAlgoCode or sigAlgoCode // is not known. hashInfo, found := oldHashAlgoList[hashAlgoCode] if found { info.Name = hashInfo.Name if info.Name != "" { info.Name += "," } sigInfo, found := oldSigAlgoList[sigAlgoCode] if found { info.Name += sigInfo.Name } else { info.Name = "" } } info.Reference = oldHashAlgoList[hashAlgoCode].Reference if info.Reference != oldSigAlgoList[sigAlgoCode].Reference { info.Reference += oldSigAlgoList[sigAlgoCode].Reference } } } if mustName && info.Name == "" { info.Name = "0x" + info.HexCode } return info } func (m *ClientHelloMsg) AddSignatureSchemeInfo() { for i, ext := range m.Extensions { switch ext.Code { case extensionSignatureAlgorithms, extensionSignatureAlgorithmsCert, extensionDelegatedCredential: for j, sigAlg := range ext.Data.SupportedSignatureAlgorithms { m.Extensions[i].Data.SupportedSignatureAlgorithms[j] = GetSignatureSchemeInfo(sigAlg.(uint16), false) } } } } func (m *ClientHelloMsg) GetSignatureSchemes() []SignatureScheme { var supportedSchemes []SignatureScheme for _, ext := range m.Extensions { if ext.Code == extensionSignatureAlgorithms { supportedSchemes = append(supportedSchemes, ext.Data.SupportedSignatureAlgorithms...) } } return supportedSchemes }