Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support parsing TLS 1.3 supported_versions extension (#8647) #8772

Merged
merged 2 commits into from Oct 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Expand Up @@ -212,6 +212,8 @@ https://github.com/elastic/beats/compare/v6.4.0...master[Check the HEAD diff]

*Packetbeat*

- Support new TLS version negotiation introduced in TLS 1.3. {issue}8647[8647].

*Winlogbeat*

*Functionbeat*
Expand Down
22 changes: 11 additions & 11 deletions packetbeat/_meta/kibana/6/dashboard/Packetbeat-tls.json
Expand Up @@ -447,7 +447,7 @@
"id": "2",
"params": {
"customLabel": "TLS version",
"field": "tls.server_hello.version",
"field": "tls.version",
"order": "desc",
"orderBy": "1",
"size": 5
Expand Down Expand Up @@ -485,7 +485,7 @@
}
},
"savedSearchId": "8f0ff590-d37d-11e7-9914-4982455b3063",
"title": "TLS Client Version",
"title": "TLS Server Public Key Size",
"uiStateJSON": {},
"version": 1,
"visState": {
Expand All @@ -501,8 +501,8 @@
"enabled": true,
"id": "2",
"params": {
"customLabel": "Client version",
"field": "tls.client_hello.version",
"customLabel": "Public Key Size",
"field": "tls.server_certificate.public_key_size",
"order": "desc",
"orderBy": "1",
"size": 5
Expand All @@ -518,7 +518,7 @@
"legendPosition": "right",
"type": "pie"
},
"title": "TLS Client Version",
"title": "Server Public Key Size",
"type": "pie"
}
},
Expand Down Expand Up @@ -1153,13 +1153,13 @@
"store": "appState"
},
"exists": {
"field": "tls.server_hello.version"
"field": "tls.version"
},
"meta": {
"alias": null,
"disabled": false,
"index": "packetbeat-*",
"key": "tls.server_hello.version",
"key": "tls.version",
"negate": false,
"type": "exists",
"value": "exists"
Expand Down Expand Up @@ -1207,13 +1207,13 @@
"store": "appState"
},
"exists": {
"field": "tls.client_hello.version"
"field": "tls.server_certificate.public_key_size"
},
"meta": {
"alias": null,
"disabled": false,
"index": "packetbeat-*",
"key": "tls.client_hello.version",
"key": "tls.server_certificate.public_key_size",
"negate": false,
"type": "exists",
"value": "exists"
Expand All @@ -1238,7 +1238,7 @@
"@timestamp",
"desc"
],
"title": "TLS Client Version",
"title": "Server Public Key Size",
"version": 1
},
"id": "8f0ff590-d37d-11e7-9914-4982455b3063",
Expand Down Expand Up @@ -1626,4 +1626,4 @@
}
],
"version": "6.2.4"
}
}
32 changes: 32 additions & 0 deletions packetbeat/docs/fields.asciidoc
Expand Up @@ -5595,6 +5595,18 @@ TLS-specific event fields.



*`tls.version`*::
+
--
type: keyword

example: TLS 1.3

The version of the TLS protocol used.


--

*`tls.handshake_completed`*::
+
--
Expand Down Expand Up @@ -5699,6 +5711,16 @@ type: keyword
Length of the session ticket, if provided, or an empty string to advertise support for tickets.


--

*`tls.client_hello.extensions.supported_versions`*::
+
--
type: keyword

List of TLS versions that the client is willing to use.


--


Expand Down Expand Up @@ -5755,6 +5777,16 @@ type: keyword
Used to announce that a session ticket will be provided by the server. Always an empty string.


--

*`tls.server_hello.extensions.supported_versions`*::
+
--
type: keyword

Negotiated TLS version to be used.


--

[float]
Expand Down
2 changes: 1 addition & 1 deletion packetbeat/include/fields.go

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions packetbeat/protos/tls/_meta/fields.yml
Expand Up @@ -6,6 +6,12 @@
- name: tls
type: group
fields:
- name: version
type: keyword
description: >
The version of the TLS protocol used.
example: "TLS 1.3"

- name: handshake_completed
type: boolean
description: >
Expand Down Expand Up @@ -69,6 +75,11 @@
Length of the session ticket, if provided, or an empty string
to advertise support for tickets.

- name: supported_versions
type: keyword
description: >
List of TLS versions that the client is willing to use.

- name: server_hello
type: group
fields:
Expand Down Expand Up @@ -105,6 +116,11 @@
Used to announce that a session ticket will be provided
by the server. Always an empty string.

- name: supported_versions
type: keyword
description: >
Negotiated TLS version to be used.

- name: client_certificate
type: group
description: Certificate provided by the client for authentication.
Expand Down
45 changes: 45 additions & 0 deletions packetbeat/protos/tls/extensions.go
Expand Up @@ -66,6 +66,7 @@ var extensionMap = map[uint16]extension{
13: {"signature_algorithms", parseSignatureSchemes, false},
16: {"application_layer_protocol_negotiation", parseALPN, false},
35: {"session_ticket", parseTicket, false},
43: {"supported_versions", parseSupportedVersions, false},
0xff01: {"renegotiation_info", ignoreContent, false},
}

Expand Down Expand Up @@ -272,3 +273,47 @@ func parseALPN(buffer bufferView) interface{} {
}
return protos
}

func parseSupportedVersions(buffer bufferView) interface{} {
// Parsing the supported_versions extensions requires knowing whether the
// extension is included in a client_hello or server_hello, but a workaround
// can be done by looking at the extension length.

// Server-side extension has length 2: Selected version (2 bytes)
if buffer.length() == 2 {
var ver tlsVersion
if !buffer.read8(0, &ver.major) || !buffer.read8(1, &ver.minor) {
return nil
}
return ver.String()
}

// Client-side extension has at least 3 bytes: 1 byte length + 2 byte entry
if buffer.length() >= 3 {
var listBytes uint8
if !buffer.read8(0, &listBytes) {
return nil
}
if 1+int(listBytes) > buffer.length() || listBytes&1 != 0 {
return nil
}

numEntries := int(listBytes) / 2
if numEntries == 0 {
return nil
}
list := make([]string, 0, numEntries)
for i := 0; i < numEntries; i++ {
var val uint16
if !buffer.read16Net(1+2*i, &val) {
return nil
}
if !isGreaseValue(val) {
list = append(list, tlsVersion{major: uint8(val >> 8), minor: uint8(val & 0xff)}.String())
}
}
return list
}

return nil
}
71 changes: 71 additions & 0 deletions packetbeat/protos/tls/extensions_test.go
Expand Up @@ -157,3 +157,74 @@ func TestParseSrp(t *testing.T) {
r = parseSrp(*mkBuf(t, "FF726f6f74", 5))
assert.Nil(t, r)
}

func TestParseSupportedVersions(t *testing.T) {
for _, testCase := range []struct {
title string
data string
expected interface{}
}{
{
title: "negotiation",
data: "080304030303020301",
expected: []string{"TLS 1.3", "TLS 1.2", "TLS 1.1", "TLS 1.0"},
},
{
title: "negotiation with GREASE",
data: "0c7a7a0304030303020301fafa",
expected: []string{"TLS 1.3", "TLS 1.2", "TLS 1.1", "TLS 1.0"},
},
{
title: "selected TLS 1.3",
data: "0304",
expected: "TLS 1.3",
},
{
title: "selected future version",
data: "0305",
expected: "TLS 1.4",
},
{
title: "empty error",
data: "00",
},
{
title: "odd length error",
data: "0b7a7a0304030303020301FF",
},
{
title: "out of bounds",
data: "FF",
},
{
title: "out of bounds (2)",
data: "805a5a03040302",
},
{
title: "valid excess data",
data: "0403030304FFFFFFFFFFFFFF",
expected: []string{"TLS 1.2", "TLS 1.3"},
},
} {
t.Run(testCase.title, func(t *testing.T) {
r := parseSupportedVersions(*mkBuf(t, testCase.data, len(testCase.data)/2))
if testCase.expected == nil {
assert.Nil(t, r, testCase.data)
return
}
switch v := testCase.expected.(type) {
case string:
version, ok := r.(string)
assert.True(t, ok)
assert.Equal(t, v, version)
case []string:
list, ok := r.([]string)
assert.True(t, ok)
assert.Len(t, list, len(v))
assert.Equal(t, v, list)
default:
assert.Fail(t, "wrong expected type", v)
}
})
}
}
18 changes: 18 additions & 0 deletions packetbeat/protos/tls/tls.go
Expand Up @@ -358,6 +358,24 @@ func (plugin *tlsPlugin) createEvent(conn *tlsConnectionData) beat.Event {
if len(fingerprints) > 0 {
tls["fingerprints"] = fingerprints
}

// TLS version in use
if conn.handshakeCompleted > 1 {
var version string
if serverHello != nil {
var ok bool
if value, exists := serverHello.extensions.Parsed["supported_versions"]; exists {
version, ok = value.(string)
}
if !ok {
version = serverHello.version.String()
}
} else if clientHello != nil {
version = clientHello.version.String()
}
tls["version"] = version
}

fields := common.MapStr{
"type": "tls",
"status": status,
Expand Down