152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
/* {{{ Copyright 2017 Paul Tagliamonte
|
|
*
|
|
* 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. }}} */
|
|
|
|
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
var TLSHeaderLength = 5
|
|
|
|
/* This function is basically all most folks want to invoke out of this
|
|
* jumble of bits. This will take an incoming TLS Client Hello (including
|
|
* all the fuzzy bits at the beginning of it - fresh out of the socket) and
|
|
* go ahead and give us the SNI Name they want. */
|
|
func GetHostname(data []byte) (string, error) {
|
|
if len(data) == 0 || data[0] != 0x16 {
|
|
return "", fmt.Errorf("Doesn't look like a TLS Client Hello")
|
|
}
|
|
|
|
extensions, err := GetExtensionBlock(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sn, err := GetSNBlock(extensions)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sni, err := GetSNIBlock(sn)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(sni), nil
|
|
}
|
|
|
|
/* Return the length computed from the two octets starting at index */
|
|
func lengthFromData(data []byte, index int) int {
|
|
b1 := int(data[index])
|
|
b2 := int(data[index+1])
|
|
|
|
return (b1 << 8) + b2
|
|
}
|
|
|
|
/* Given a Server Name TLS Extension block, parse out and return the SNI
|
|
* (Server Name Indication) payload */
|
|
func GetSNIBlock(data []byte) ([]byte, error) {
|
|
index := 0
|
|
|
|
for {
|
|
if index >= len(data) {
|
|
break
|
|
}
|
|
length := lengthFromData(data, index)
|
|
endIndex := index + 2 + length
|
|
if data[index+2] == 0x00 { /* SNI */
|
|
sni := data[index+3:]
|
|
sniLength := lengthFromData(sni, 0)
|
|
return sni[2 : sniLength+2], nil
|
|
}
|
|
index = endIndex
|
|
}
|
|
return []byte{}, fmt.Errorf(
|
|
"Finished parsing the SN block without finding an SNI",
|
|
)
|
|
}
|
|
|
|
/* Given a TLS Extensions data block, go ahead and find the SN block */
|
|
func GetSNBlock(data []byte) ([]byte, error) {
|
|
index := 0
|
|
|
|
if len(data) < 2 {
|
|
return []byte{}, fmt.Errorf("Not enough bytes to be an SN block")
|
|
}
|
|
|
|
extensionLength := lengthFromData(data, index)
|
|
if extensionLength+2 > len(data) {
|
|
return []byte{}, fmt.Errorf("Extension looks bonkers")
|
|
}
|
|
data = data[2 : extensionLength+2]
|
|
|
|
for {
|
|
if index+4 >= len(data) {
|
|
break
|
|
}
|
|
length := lengthFromData(data, index+2)
|
|
endIndex := index + 4 + length
|
|
if data[index] == 0x00 && data[index+1] == 0x00 {
|
|
return data[index+4 : endIndex], nil
|
|
}
|
|
|
|
index = endIndex
|
|
}
|
|
|
|
return []byte{}, fmt.Errorf(
|
|
"Finished parsing the Extension block without finding an SN block",
|
|
)
|
|
}
|
|
|
|
/* Given a raw TLS Client Hello, go ahead and find all the Extensions */
|
|
func GetExtensionBlock(data []byte) ([]byte, error) {
|
|
/* data[0] - content type
|
|
* data[1], data[2] - major/minor version
|
|
* data[3], data[4] - total length
|
|
* data[...38+5] - start of SessionID (length bit)
|
|
* data[38+5] - length of SessionID
|
|
*/
|
|
var index = TLSHeaderLength + 38
|
|
|
|
if len(data) <= index+1 {
|
|
return []byte{}, fmt.Errorf("Not enough bits to be a Client Hello")
|
|
}
|
|
|
|
/* Index is at SessionID Length bit */
|
|
if newIndex := index + 1 + int(data[index]); (newIndex + 2) < len(data) {
|
|
index = newIndex
|
|
} else {
|
|
return []byte{}, fmt.Errorf("Not enough bytes for the SessionID")
|
|
}
|
|
|
|
/* Index is at Cipher List Length bits */
|
|
if newIndex := (index + 2 + lengthFromData(data, index)); (newIndex + 1) < len(data) {
|
|
index = newIndex
|
|
} else {
|
|
return []byte{}, fmt.Errorf("Not enough bytes for the Cipher List")
|
|
}
|
|
|
|
/* Index is now at the compression length bit */
|
|
if newIndex := index + 1 + int(data[index]); newIndex < len(data) {
|
|
index = newIndex
|
|
} else {
|
|
return []byte{}, fmt.Errorf("Not enough bytes for the compression length")
|
|
}
|
|
|
|
/* Now we're at the Extension start */
|
|
if len(data[index:]) == 0 {
|
|
return nil, fmt.Errorf("No extensions")
|
|
}
|
|
return data[index:], nil
|
|
}
|
|
|
|
// vim: foldmethod=marker
|