Skip to content

Commit

Permalink
packetbeat/protos/tls: capture tls random data and ocsp status (#30102)
Browse files Browse the repository at this point in the history
  • Loading branch information
efd6 authored Feb 1, 2022
1 parent 64dc77a commit d4bd46b
Show file tree
Hide file tree
Showing 15 changed files with 1,024 additions and 458 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
*Packetbeat*

- Add automated OEM Npcap installation handling. {pull}29112[29112]
- Add support for capturing TLS random number and OCSP status request details. {issue}29962[29962] {pull}30102[30102]

*Functionbeat*

Expand Down
78 changes: 78 additions & 0 deletions packetbeat/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -19402,13 +19402,33 @@ type: boolean
--
*`tls.detailed.ocsp_response`*::
+
--
The result of an OCSP request.
type: keyword
--
*`tls.detailed.client_hello.version`*::
+
--
The version of the TLS protocol by which the client wishes to communicate during this session.
type: keyword
--
*`tls.detailed.client_hello.random`*::
+
--
Random data used by the TLS protocol to generate the encryption key.
type: keyword
--
Expand Down Expand Up @@ -19508,6 +19528,39 @@ type: keyword
--
[float]
=== status_request
Status request made to the server.
*`tls.detailed.client_hello.extensions.status_request.type`*::
+
--
The type of the status request. Always "ocsp" if present.
type: keyword
--
*`tls.detailed.client_hello.extensions.status_request.responder_id_list_length`*::
+
--
The length of the list of trusted responders.
type: short
--
*`tls.detailed.client_hello.extensions.status_request.request_extensions`*::
+
--
The number of certificate extensions for the request.
type: short
--
*`tls.detailed.client_hello.extensions._unparsed_`*::
+
--
Expand All @@ -19525,6 +19578,16 @@ type: keyword
The version of the TLS protocol that is used for this session. It is the highest version supported by the server not exceeding the version requested in the client hello.


type: keyword

--

*`tls.detailed.server_hello.random`*::
+
--
Random data used by the TLS protocol to generate the encryption key.


type: keyword

--
Expand Down Expand Up @@ -19594,6 +19657,21 @@ type: keyword

--

[float]
=== status_request

Status request made to the server.


*`tls.detailed.server_hello.extensions.status_request.response`*::
+
--
Whether a certificate status request response was made.

type: boolean

--

*`tls.detailed.server_hello.extensions._unparsed_`*::
+
--
Expand Down
39 changes: 39 additions & 0 deletions packetbeat/protos/tls/_meta/fields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
Whether the server has requested the client to authenticate itself
using a client certificate.
- name: ocsp_response
type: keyword
description: >
The result of an OCSP request.
- name: client_hello
type: group
fields:
Expand All @@ -79,6 +84,11 @@
The version of the TLS protocol by which the client wishes to
communicate during this session.
- name: random
type: keyword
description: >
Random data used by the TLS protocol to generate the encryption key.
- name: session_id
type: keyword
description: >
Expand Down Expand Up @@ -133,6 +143,22 @@
List of Elliptic Curve (EC) point formats. Indicates the
set of point formats that the client can parse.
- name: status_request
type: group
description: Status request made to the server.
fields:
- name: type
type: keyword
description: The type of the status request. Always "ocsp" if present.

- name: responder_id_list_length
type: short
description: The length of the list of trusted responders.

- name: request_extensions
type: short
description: The number of certificate extensions for the request.

- name: _unparsed_
type: keyword
description: >
Expand All @@ -148,6 +174,11 @@
It is the highest version supported by the server not exceeding
the version requested in the client hello.
- name: random
type: keyword
description: >
Random data used by the TLS protocol to generate the encryption key.
- name: selected_compression_method
type: keyword
description: >
Expand Down Expand Up @@ -185,6 +216,14 @@
List of Elliptic Curve (EC) point formats. Indicates the
set of point formats that the server can parse.
- name: status_request
type: group
description: Status request made to the server.
fields:
- name: response
type: boolean
description: Whether a certificate status request response was made.

- name: _unparsed_
type: keyword
description: >
Expand Down
22 changes: 21 additions & 1 deletion packetbeat/protos/tls/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var extensionMap = map[uint16]extension{
2: {"client_certificate_url", expectEmpty, false},
3: {"trusted_ca_keys", ignoreContent, false},
4: {"truncated_hmac", expectEmpty, false},
5: {"status_request", ignoreContent, false},
5: {"status_request", parseStatusReq, false},
6: {"user_mapping", ignoreContent, false},
7: {"client_authz", ignoreContent, false},
8: {"server_authz", ignoreContent, false},
Expand Down Expand Up @@ -165,6 +165,26 @@ func ignoreContent(_ bufferView) interface{} {
return nil
}

func parseStatusReq(buffer bufferView) interface{} {
if buffer.length() == 0 {
// Initial server response.
return common.MapStr{"response": true}
}
// Client query.
var (
code uint8
list, exts uint16
)
if !buffer.read8(0, &code) || !buffer.read16Net(1, &list) || !buffer.read16Net(1, &exts) {
return nil
}
typ := "ocsp"
if code != 1 {
typ = fmt.Sprint(code)
}
return common.MapStr{"type": typ, "responder_id_list_length": list, "request_extensions": exts}
}

func expectEmpty(buffer bufferView) interface{} {
if buffer.length() != 0 {
return fmt.Sprintf("(expected empty: found %d bytes)", buffer.length())
Expand Down
2 changes: 1 addition & 1 deletion packetbeat/protos/tls/fields.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packetbeat/protos/tls/ja3.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"
)

// See https://engineering.salesforce.com/open-sourcing-ja3-92c9e53c3c41.
func getJa3Fingerprint(hello *helloMessage) (hash string, ja3str string) {
// build the array of arrays of numbers

Expand Down Expand Up @@ -93,6 +94,7 @@ func extractJa3Array(raw []byte, size int) []uint16 {
return array
}

// See https://tools.ietf.org/id/draft-ietf-tls-grease-01.html#rfc.section.2
func isGreaseValue(num uint16) bool {
hi, lo := byte(num>>8), byte(num)
return hi == lo && lo&0xf == 0xa
Expand Down
71 changes: 62 additions & 9 deletions packetbeat/protos/tls/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ type handshakeType uint8

const (
helloRequest handshakeType = 0
clientHello = 1
serverHello = 2
certificate = 11
serverKeyExchange = 12
certificateRequest = 13
clientKeyExchange = 16
clientHello handshakeType = 1
serverHello handshakeType = 2
certificate handshakeType = 11
serverKeyExchange handshakeType = 12
certificateRequest handshakeType = 13
clientKeyExchange handshakeType = 16
certificateStatus handshakeType = 22
)

type parserResult int8
Expand Down Expand Up @@ -99,10 +100,36 @@ type parser struct {
// for a certificate
certRequested bool

// ocspResponse is the top level OCSP response status.
ocspResponse ocspResponseStatus
ocspResponseIsValid bool

// If a key-exchange message has been sent. Used to detect session resumption
keyExchanged bool
}

// https://www.rfc-editor.org/rfc/rfc6960#section-4.2.1
type ocspResponseStatus byte

func (s ocspResponseStatus) String() string {
switch s {
case 0: // Response has valid confirmations
return "successful"
case 1: // Illegal confirmation request
return "malformedRequest"
case 2: // Internal error in issuer
return "internalError"
case 3: // Try again later
return "tryLater"
case 5: // Must sign the request
return "sigRequired"
case 6: // Request unauthorized
return "unauthorized"
default:
return fmt.Sprint(byte(s))
}
}

type tlsVersion struct {
major, minor uint8
}
Expand All @@ -120,6 +147,7 @@ type handshakeHeader struct {

type helloMessage struct {
version tlsVersion
random []byte
timestamp uint32
sessionID string
ticket tlsTicket
Expand Down Expand Up @@ -191,6 +219,9 @@ func (hello *helloMessage) toMap() common.MapStr {
if len(hello.sessionID) != 0 {
m["session_id"] = hello.sessionID
}
if len(hello.random) != 0 {
m["random"] = hex.EncodeToString(hello.random)
}

if len(hello.supported.compression) > 0 {
comp := make([]string, len(hello.supported.compression))
Expand Down Expand Up @@ -361,6 +392,9 @@ func (parser *parser) parseHandshake(handshakeType handshakeType, buffer bufferV
case serverKeyExchange:
parser.setDirection(dirServer)
parser.keyExchanged = true

case certificateStatus:
parser.ocspResponse, parser.ocspResponseIsValid = parseOCSPStatus(buffer)
}
return true
}
Expand Down Expand Up @@ -394,12 +428,14 @@ func parseCommonHello(buffer bufferView, dest *helloMessage) (int, bool) {
return 0, false
}

if bytes := buffer.readBytes(7+randomDataLength, int(sessionIDLength)); len(bytes) == int(sessionIDLength) {
dest.sessionID = hex.EncodeToString(bytes)
} else {
bytes := buffer.readBytes(7+randomDataLength, int(sessionIDLength))
if len(bytes) != int(sessionIDLength) {
logp.Warn("Not a TLS hello (failed reading session ID)")
return 0, false
}
dest.sessionID = hex.EncodeToString(bytes)
dest.random = buffer.readBytes(2, 4+randomDataLength)

return helloHeaderLength + randomDataLength + int(sessionIDLength), true
}

Expand Down Expand Up @@ -503,6 +539,23 @@ func parseCertificates(buffer bufferView) (certs []*x509.Certificate) {
return certs
}

func parseOCSPStatus(buffer bufferView) (status ocspResponseStatus, ok bool) {
const (
statusTypeLen = 1
respLengthLen = 3
ocspRespHeaderLen = 6

ocspStatusType = 1
)
var b byte
ok = buffer.read8(0, &b)
if !ok || b != ocspStatusType {
return 0, false
}
ok = buffer.read8(statusTypeLen+respLengthLen+ocspRespHeaderLen, &b)
return ocspResponseStatus(b), ok
}

func (version tlsVersion) String() string {
if version.major == 3 {
if version.minor > 0 {
Expand Down
Loading

0 comments on commit d4bd46b

Please sign in to comment.