utls/u_clienthello_json.go
Gaukas Wang 3721531ea9
Implement ClientHelloSpec JSON Unmarshaler (#176)
* wip: staging work

* wip: staging work

* feat: ClientHello JSON Unmarshaler

Allowing unmarshalling a JSON object into a ClientHelloSpec.

* feat: ClientHello JSON Unmarshaler rev

- Revised JSON ClientHello format
- Implemented `TLSExtensionJSON` interface for some more extensions
2023-03-30 09:13:47 -06:00

168 lines
4.6 KiB
Go

package tls
import (
"encoding/json"
"errors"
"fmt"
"os"
"github.com/gaukas/godicttls"
)
var ErrUnknownExtension = errors.New("extension name is unknown to the dictionary")
type ClientHelloSpecJSONUnmarshaler struct {
CipherSuites *CipherSuitesJSONUnmarshaler `json:"cipher_suites"`
CompressionMethods *CompressionMethodsJSONUnmarshaler `json:"compression_methods"`
Extensions *TLSExtensionsJSONUnmarshaler `json:"extensions"`
TLSVersMin uint16 `json:"min_vers,omitempty"` // optional
TLSVersMax uint16 `json:"max_vers,omitempty"` // optional
}
func (chsju *ClientHelloSpecJSONUnmarshaler) ClientHelloSpec() ClientHelloSpec {
return ClientHelloSpec{
CipherSuites: chsju.CipherSuites.CipherSuites(),
CompressionMethods: chsju.CompressionMethods.CompressionMethods(),
Extensions: chsju.Extensions.Extensions(),
TLSVersMin: chsju.TLSVersMin,
TLSVersMax: chsju.TLSVersMax,
}
}
type CipherSuitesJSONUnmarshaler struct {
cipherSuites []uint16
}
func (c *CipherSuitesJSONUnmarshaler) UnmarshalJSON(jsonStr []byte) error {
var cipherSuiteNames []string
if err := json.Unmarshal(jsonStr, &cipherSuiteNames); err != nil {
return err
}
for _, name := range cipherSuiteNames {
if name == "GREASE" {
c.cipherSuites = append(c.cipherSuites, GREASE_PLACEHOLDER)
continue
}
if id, ok := godicttls.DictCipherSuiteNameIndexed[name]; ok {
c.cipherSuites = append(c.cipherSuites, id)
} else {
return fmt.Errorf("unknown cipher suite name: %s", name)
}
}
return nil
}
func (c *CipherSuitesJSONUnmarshaler) CipherSuites() []uint16 {
return c.cipherSuites
}
type CompressionMethodsJSONUnmarshaler struct {
compressionMethods []uint8
}
func (c *CompressionMethodsJSONUnmarshaler) UnmarshalJSON(jsonStr []byte) error {
var compressionMethodNames []string
if err := json.Unmarshal(jsonStr, &compressionMethodNames); err != nil {
return err
}
for _, name := range compressionMethodNames {
if id, ok := godicttls.DictCompMethNameIndexed[name]; ok {
c.compressionMethods = append(c.compressionMethods, id)
} else {
return fmt.Errorf("unknown compression method name: %s", name)
}
}
return nil
}
func (c *CompressionMethodsJSONUnmarshaler) CompressionMethods() []uint8 {
return c.compressionMethods
}
type TLSExtensionsJSONUnmarshaler struct {
extensions []TLSExtensionJSON
}
func (e *TLSExtensionsJSONUnmarshaler) UnmarshalJSON(jsonStr []byte) error {
var accepters []tlsExtensionJSONAccepter
if err := json.Unmarshal(jsonStr, &accepters); err != nil {
return err
}
var exts []TLSExtensionJSON = make([]TLSExtensionJSON, 0, len(accepters))
for _, accepter := range accepters {
if accepter.extNameOnly.Name == "GREASE" {
exts = append(exts, &UtlsGREASEExtension{})
continue
}
if extID, ok := godicttls.DictExtTypeNameIndexed[accepter.extNameOnly.Name]; !ok {
return fmt.Errorf("%w: %s", ErrUnknownExtension, accepter.extNameOnly.Name)
} else {
// get extension type from ID
var ext TLSExtension = ExtensionFromID(extID)
if ext == nil {
// fallback to generic extension
ext = genericExtension(extID, accepter.extNameOnly.Name)
}
if extJsonCompatible, ok := ext.(TLSExtensionJSON); ok {
exts = append(exts, extJsonCompatible)
} else {
return fmt.Errorf("extension %d (%s) is not JSON compatible", extID, accepter.extNameOnly.Name)
}
}
}
// unmashal extensions
for idx, ext := range exts {
// json.Unmarshal will call the UnmarshalJSON method of the extension
if err := json.Unmarshal(accepters[idx].origJsonInput, ext); err != nil {
return err
}
}
e.extensions = exts
return nil
}
func (e *TLSExtensionsJSONUnmarshaler) Extensions() []TLSExtension {
var exts []TLSExtension = make([]TLSExtension, 0, len(e.extensions))
for _, ext := range e.extensions {
exts = append(exts, ext)
}
return exts
}
func genericExtension(id uint16, name string) TLSExtension {
var warningMsg string = "WARNING: extension "
warningMsg += fmt.Sprintf("%d ", id)
if len(name) > 0 {
warningMsg += fmt.Sprintf("(%s) ", name)
}
warningMsg += "is falling back to generic extension"
warningMsg += "\n"
fmt.Fprint(os.Stderr, warningMsg)
// fallback to generic extension
return &GenericExtension{Id: id}
}
type tlsExtensionJSONAccepter struct {
extNameOnly struct {
Name string `json:"name"`
}
origJsonInput []byte
}
func (t *tlsExtensionJSONAccepter) UnmarshalJSON(jsonStr []byte) error {
t.origJsonInput = make([]byte, len(jsonStr))
copy(t.origJsonInput, jsonStr)
return json.Unmarshal(jsonStr, &t.extNameOnly)
}