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

Added basic PCAP capture and validation functionality. #24

Merged
merged 4 commits into from
Feb 8, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.*.swp
bin/
testdata/
generated/
17 changes: 13 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
TESTDATA_DIR = testdata
TESTDATA_DIR = generated/test-inputs
BIN_DIR = bin
UTIL = ${BIN_DIR}/util
UTIL_SRCS = $(wildcard cmd/util/*.go)

all: testdata
VALIDATEPCAP = ${BIN_DIR}/validatepcap
VALIDATEPCAP_SRCS = $(wildcard cmd/validatepcap/*.go)

all: testinputs

util: $(UTIL_SRCS)
mkdir -p ${BIN_DIR}
go get ./cmd/util/...
go build -o ${UTIL} ./cmd/util/...

.PHONY: testdata
testdata: util
validatepcap: $(VALIDATEPCAP_SRCS)
mkdir -p ${BIN_DIR}
go get ./cmd/validatepcap/...
go build -o ${VALIDATEPCAP} ./cmd/validatepcap/...

.PHONY: testinputs
testinputs: util
mkdir -p ${TESTDATA_DIR}
${UTIL} -make-root -out ${TESTDATA_DIR}/root.crt -key-out ${TESTDATA_DIR}/root.key -host root.com
${UTIL} -make-intermediate -cert-in ${TESTDATA_DIR}/root.crt -key-in ${TESTDATA_DIR}/root.key -out ${TESTDATA_DIR}/example.crt -key-out ${TESTDATA_DIR}/example.key -host example.com
Expand All @@ -24,4 +32,5 @@ clean:
rm -fr ${TESTDATA_DIR}

clean-docker:
docker network prune
docker builder prune
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Tests require certificates and other cryptographic artifacts to be generated
beforehand.

```
make testdata
make testinputs
```

This command will generate:
Expand Down
145 changes: 145 additions & 0 deletions cmd/validatepcap/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"strconv"
)

type clientHelloMsg struct {
version uint16
supportsDC bool
serverName string
supportedVersions []uint16
}

type serverHelloMsg struct {
version uint16
}

type tlsTranscript struct {
clientHello clientHelloMsg
serverHello serverHelloMsg
}

func parsePCap(tsharkPath string, pcapPath string, keylogPath string) (transcript tlsTranscript, err error) {
rawJSON, err := exec.Command(tsharkPath,
"-r", pcapPath,
"-d", "tcp.port==4433,tls",
"-2R", "tls",
"-o", fmt.Sprintf("tls.keylog_file:%s", keylogPath),
"-T", "ek",
"-J", "tls",
"-l").Output()
if err != nil {
return transcript, err
}

// rawJSON is formatted as newline-delimited JSON.
decoder := json.NewDecoder(bytes.NewReader(rawJSON))
for decoder.More() {
var obj map[string]interface{}
if err = decoder.Decode(&obj); err != nil {
return transcript, err
}
// "obj" can have one of two following forms:
// {
// "index": {
// "_index": "packets-1969-12-31",
// "_type": "doc"
// }
// }
//
// or
//
// {
// "timestamp": "0981"
// "layers": {
// "frame": { ... },
// "ppp": { ... },
// ...
// ...
// "tls": { ... }
// }
// }
//
// We ignore objects of the first form.
if obj["layers"] == nil {
continue
}

// We're only interested in the TLS layer.
// The "tls" object is a collection of key-value
// pairs where the values are either strings
// or arrays of strings.
tls := obj["layers"].(map[string]interface{})["tls"].(map[string]interface{})

// If "tls_tls_handshake_type" is an array
// of strings, then the tls object contains
// key-value pairs for multiple TLS messages.
//
// We first take stock of the types of TLS messages
// present before parsing the messages out.
messageTypes := map[string]bool{}
switch value := tls["tls_tls_handshake_type"].(type) {
case string:
messageTypes[value] = true
case []interface{}:
for _, messageType := range value {
messageTypes[messageType.(string)] = true
}
}

if messageTypes["1"] {
err = parseOutClientHello(tls, &transcript)
if err != nil {
return transcript, err
}

} else if messageTypes["2"] {
err = parseOutServerHello(tls, &transcript)
if err != nil {
return transcript, err
}
}
}

return transcript, err
}

func parseOutClientHello(raw map[string]interface{}, transcript *tlsTranscript) error {
version, err := strconv.ParseUint(raw["tls_tls_handshake_version"].(string), 0, 16)
if err != nil {
return err
}
transcript.clientHello.version = uint16(version)

transcript.clientHello.serverName = raw["tls_tls_handshake_extensions_server_name"].(string)

for _, val := range raw["tls_tls_handshake_extension_type"].([]interface{}) {
if val == "34" {
transcript.clientHello.supportsDC = true
}
}

for _, val := range raw["tls_tls_handshake_extensions_supported_version"].([]interface{}) {
version, err := strconv.ParseUint(val.(string), 0, 16)
if err != nil {
return err
}
transcript.clientHello.supportedVersions = append(transcript.clientHello.supportedVersions, uint16(version))
}
return nil
}

func parseOutServerHello(raw map[string]interface{}, transcript *tlsTranscript) error {
version, err := strconv.ParseUint(raw["tls_tls_handshake_version"].(string), 0, 16)
if err != nil {
return err
}
transcript.serverHello.version = uint16(version)

return nil
}
27 changes: 27 additions & 0 deletions cmd/validatepcap/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"errors"
)

func validateTranscript(transcript tlsTranscript, testCase string) error {
switch testCase {
case "dc":
if transcript.clientHello.version != 0x0303 {
return errors.New("ClientHello: legacy_version is not TLS 1.2.")
}
if !transcript.clientHello.supportsDC {
return errors.New("ClientHello: support for delegated credentials not indicated.")
}
if transcript.clientHello.serverName != "example.com" {
return errors.New("ClientHello: SNI should specify example.com")
}
for _, v := range transcript.clientHello.supportedVersions {
if v == 0x0304 {
return nil
}
}
return errors.New("ClientHello: supported_versions does not include TLS 1.3.")
}
return nil
}
68 changes: 68 additions & 0 deletions cmd/validatepcap/validatepcap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"flag"
"fmt"
"log"
"os/exec"
"strconv"
"strings"
)

const usage = `Usage:

$ validatepcap [-help] {-pcap-in} {-keylog-in} {-testcase}

Requires tshark with version >= 3.2.0
`

func main() {
log.SetFlags(0)
var (
pcapPath = flag.String("pcap-in", "", "")
keylogPath = flag.String("keylog-in", "", "")
testcase = flag.String("testcase", "", "")
help = flag.Bool("help", false, "")
)

flag.Parse()
if *help || *pcapPath == "" || *keylogPath == "" || *testcase == "" {
fmt.Print(usage)
return
}

tsharkPath, err := exec.LookPath("tshark")
fatalIfErr(err, "tshark not found in PATH.")

tsharkConfiguration, err := exec.Command(tsharkPath, "--version").Output()
fatalIfErr(err, "Could not retrieve tshark configuration.")

tsharkVersionLine := strings.Split(string(tsharkConfiguration), "\n")[0]
tsharkVersionFields := strings.Split(strings.Fields(tsharkVersionLine)[2], ".")
tsharkMajorVersion, err := strconv.Atoi(tsharkVersionFields[0])
fatalIfErr(err, "Could not retrieve tshark major version.")

tsharkMinorVersion, err := strconv.Atoi(tsharkVersionFields[1])
fatalIfErr(err, "Could not retrieve tshark minor version.")

if tsharkMajorVersion < 3 || tsharkMinorVersion < 2 {
log.Fatalf("Requires tshark with version >= 3.2.0.")
}

transcript, err := parsePCap(tsharkPath, *pcapPath, *keylogPath)
fatalIfErr(err, "Could not parse supplied PCap")

err = validateTranscript(transcript, *testcase)
if err != nil {
log.Fatalf("Testcase %s failed: %s", *testcase, err)
} else {
fmt.Printf("Testcase %s passed.\n", *testcase)
}

}

func fatalIfErr(err error, msg string) {
if err != nil {
log.Fatalf("ERROR: %s: %s\n", msg, err)
}
}
8 changes: 6 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ services:
hostname: network
stdin_open: true
tty: true
volumes:
- ./generated/test-outputs:/test-outputs
cap_add:
- NET_ADMIN
expose:
Expand All @@ -28,8 +30,9 @@ services:
stdin_open: true
tty: true
volumes:
- ./testdata:/testdata:ro
- ./generated/test-inputs:/test-inputs:ro
- ./network/endpoint-route-setup.sh:/setup-routes.sh:ro
- ./generated/test-outputs:/test-outputs
cap_add:
- NET_ADMIN
ulimits:
Expand All @@ -54,8 +57,9 @@ services:
stdin_open: true
tty: true
volumes:
- ./testdata:/testdata:ro
- ./generated/test-inputs:/test-inputs:ro
- ./network/endpoint-route-setup.sh:/setup-routes.sh:ro
- ./generated/test-outputs:/test-outputs
cap_add:
- NET_ADMIN
ulimits:
Expand Down
2 changes: 1 addition & 1 deletion impl-endpoints/boringssl/run_endpoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ else
echo "Running BoringSSL server."
echo "Server params: $SERVER_PARAMS"
echo "Test case: $TESTCASE"
bssl server -accept 4433 -cert /testdata/example.crt -key /testdata/example.key -subcert /testdata/dc.txt
bssl server -accept 4433 -cert /test-inputs/example.crt -key /test-inputs/example.key -subcert /test-inputs/dc.txt
fi
Loading