pcap-cli/internal/transformer/json_tls_translator.go (129 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build json package transformer import ( "context" "encoding/binary" "errors" "github.com/Jeffail/gabs/v2" "github.com/google/gopacket/layers" "golang.org/x/crypto/cryptobyte" ) func (t *JSONPcapTranslator) translateTLSLayer_decodeClientHello(hs cryptobyte.String, TLS *gabs.Container) { ch := make(map[string]interface{}, 4) ch["type"] = "ClientHello" var clientHello cryptobyte.String hs.ReadUint24LengthPrefixed(&clientHello) var legacyVersion uint16 clientHello.ReadUint16(&legacyVersion) ch["legacy_version"] = layers.TLSVersion(legacyVersion).String() // var random []byte // clientHello.ReadBytes(&random, 32) // ch.Set(random, "random") // var legacySessionID []byte // clientHello.ReadUint8LengthPrefixed((*cryptobyte.String)(&legacySessionID)) // ch.Set(legacySessionID, "legacy_session_id") clientHello.Skip(33) var ciphersuitesBytes cryptobyte.String clientHello.ReadUint16LengthPrefixed(&ciphersuitesBytes) var ciphers []uint16 for !ciphersuitesBytes.Empty() { var ciphersuite uint16 ciphersuitesBytes.ReadUint16(&ciphersuite) ciphers = append(ciphers, ciphersuite) } ch["ciphers"] = ciphers var legacyCompressionMethods []uint8 clientHello.ReadUint8LengthPrefixed((*cryptobyte.String)(&legacyCompressionMethods)) ch["legacy_compression_methods"] = legacyCompressionMethods var extensionsBytes cryptobyte.String clientHello.ReadUint16LengthPrefixed(&extensionsBytes) var extensions []map[string]interface{} for !extensionsBytes.Empty() { var extType uint16 extensionsBytes.ReadUint16(&extType) ext := make(map[string]interface{}, 3) ext["type"] = extType var extData cryptobyte.String extensionsBytes.ReadUint16LengthPrefixed(&extData) switch extType { default: ext["name"] = "UNKNOWN" case 0: // Server Name ext["name"] = "server_name" extData.Skip(5) ext["data"] = string(extData) case 16: // ALPN ext["name"] = "application_layer_protocol_negotiation" var alpnData cryptobyte.String extData.ReadUint16LengthPrefixed(&alpnData) var data []string for !alpnData.Empty() { var length uint8 alpnData.ReadUint8(&length) var proto []byte alpnData.ReadBytes(&proto, int(length)) data = append(data, string(proto)) } ext["data"] = data } extensions = append(extensions, ext) } ch["extensions"] = extensions TLS.SetP(ch, "data.client_hello") } func (t *JSONPcapTranslator) decodeTLSRecords(it uint8, data []byte, TLS *gabs.Container) error { if len(data) < 5 { return errors.New("TLS record too short") } var h layers.TLSRecordHeader h.ContentType = layers.TLSType(data[0]) h.Version = layers.TLSVersion(binary.BigEndian.Uint16(data[1:3])) h.Length = binary.BigEndian.Uint16(data[3:5]) if h.ContentType.String() == "Unknown" { return errors.New("unknown TLS record type") } hl := 5 // header length tl := hl + int(h.Length) if len(data) < tl { return errors.New("TLS packet length mismatch") } switch h.ContentType { default: return errors.New("unknown TLS record type") case layers.TLSChangeCipherSpec, layers.TLSAlert, layers.TLSHandshake: b := data[hl:tl] hs := cryptobyte.String(b) var messageType uint8 if !hs.ReadUint8(&messageType) { return errors.New("failed to decode TLS layer") } // `ClientHello` and `ApplicationData` are the only full layers we have access to; // see: https://github.com/google/gopacket/blob/v1.1.19/layers/tls.go#L136-L139 // reason: when `gopacket` decodes `TLS`, it repaces content by the last layer parsed if messageType == 1 { t.translateTLSLayer_decodeClientHello(hs, TLS) } case layers.TLSApplicationData: } if len(data) == tl { return nil } return t.decodeTLSRecords(it+1, data[tl:], TLS) } func (t *JSONPcapTranslator) translateTLSLayer_RecordHeader(ctx context.Context, json *gabs.Container, recordHeader layers.TLSRecordHeader) { json.SetP(recordHeader.Version.String(), "version") json.SetP(recordHeader.ContentType.String(), "content_type") json.SetP(recordHeader.Length, "length") } func (t *JSONPcapTranslator) translateTLSLayer_ChangeCipherSpec(ctx context.Context, TLS *gabs.Container, tls *layers.TLS) { a, _ := TLS.ArrayOfSize(len(tls.ChangeCipherSpec), "change_cipher_spec") for i, changeCipherSpec := range tls.ChangeCipherSpec { o, _ := a.ObjectI(i) t.translateTLSLayer_RecordHeader(ctx, o, changeCipherSpec.TLSRecordHeader) o.Set(changeCipherSpec.Message.String(), "message") } } func (t *JSONPcapTranslator) translateTLSLayer_Handshake(ctx context.Context, TLS *gabs.Container, tls *layers.TLS) { a, _ := TLS.ArrayOfSize(len(tls.Handshake), "handshake") for i, handshake := range tls.Handshake { o, _ := a.ObjectI(i) t.translateTLSLayer_RecordHeader(ctx, o, handshake.TLSRecordHeader) } } func (t *JSONPcapTranslator) translateTLSLayer_AppData(ctx context.Context, TLS *gabs.Container, tls *layers.TLS) { a, _ := TLS.ArrayOfSize(len(tls.Handshake), "app_data") for i, appData := range tls.AppData { o, _ := a.ObjectI(i) t.translateTLSLayer_RecordHeader(ctx, o, appData.TLSRecordHeader) } }