commit 73ab596ae62a5333d2c7ad4393d417f52dd8c58f Author: Mikkel Krautz Date: Mon Sep 20 15:14:22 2010 +0200 Throw the code out there. :) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..2beef6c --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Mikkel Krautz diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d6e1060 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +// +// Grumble - an implementation of Murmur in Go +// +// Copyright (c) 2010 The Grumble Authors +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// - Neither the name of the Mumble Developers nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..721aab1 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# Copyright (c) 2010 The Grumble Authors +# The use of this source code is goverened by a BSD-style +# license that can be found in the LICENSE-file. + +include $(GOROOT)/src/Make.inc + +TARG = grumble + +PACKAGES = \ + pkg/packetdatastream \ + pkg/cryptstate \ + pkg/mumbleproto + +GCFLAGS = -Ipkg/cryptstate/_obj -Ipkg/packetdatastream/_obj -Ipkg/mumbleproto/_obj +LDFLAGS = -Lpkg/cryptstate/_obj -Lpkg/packetdatastream/_obj -Lpkg/mumbleproto/_obj + +GOFILES = \ + grumble.go \ + message.go \ + tlsserver.go \ + server.go \ + client.go + +.PHONY: grumble +grumble: pkg + $(GC) $(GCFLAGS) -o $(TARG).$(O) $(GOFILES) + $(LD) $(LDFLAGS) -o $(TARG) $(TARG).$(O) + +.PHONY: pkg +pkg: + for dir in $(PACKAGES); do $(MAKE) -C $$dir; done + +.PHONY: pkgclean +pkgclean: + for dir in $(PACKAGES); do $(MAKE) -C $$dir clean; done + +.PHONY: clean +clean: pkgclean + rm -f grumble + rm -f *.$(O) diff --git a/README b/README new file mode 100644 index 0000000..2dac789 --- /dev/null +++ b/README @@ -0,0 +1,5 @@ +What is Grumble? +================ + +Grumble is an implementation of Murmur (the server component of Mumble, an +open-source VoIP application) in the Go language. diff --git a/client.go b/client.go new file mode 100644 index 0000000..7388d08 --- /dev/null +++ b/client.go @@ -0,0 +1,290 @@ +// Copyright (c) 2010 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package main + +import ( + "net" + "bufio" + "log" + "os" + "encoding/binary" + "goprotobuf.googlecode.com/hg/proto" + "mumbleproto" + "cryptstate" + "packetdatastream" +) + +// A client connection +type ClientConnection struct { + // Connection-related + tcpaddr *net.TCPAddr + udpaddr *net.UDPAddr + conn net.Conn + reader *bufio.Reader + writer *bufio.Writer + state int + server *Server + + msgchan chan *Message + udprecv chan []byte + + disconnected bool + + crypt *cryptstate.CryptState + codecs []int32 + udp bool + + // Personal + Session uint32 + Username string + Tokens []string +} + +// Something invalid happened on the wire. +func (client *ClientConnection) Panic(reason string) { + client.disconnected = true + // fixme(mkrautz): we should inform the server "handler" method through a channel of this event, + // so it can perform a proper disconnect. +} + +// Read a protobuf message from a client +func (client *ClientConnection) readProtoMessage() (msg *Message, err os.Error) { + var length uint32 + var kind uint16 + + // Read the message type (16-bit big-endian unsigned integer) + err = binary.Read(client.reader, binary.BigEndian, &kind) + if err != nil { + client.Panic("Unable to read packet kind") + return + } + + // Read the message length (32-bit big-endian unsigned integer) + err = binary.Read(client.reader, binary.BigEndian, &length) + if err != nil { + client.Panic("Unable to read packet length") + return + } + + buf := make([]byte, length) + _, err = client.reader.Read(buf) + if err != nil { + client.Panic("Unable to read packet content") + return + } + + msg = &Message{ + buf: buf, + kind: kind, + client: client, + } + + return +} + +// Send a protobuf-encoded message +func (c *ClientConnection) sendProtoMessage(kind uint16, msg interface{}) (err os.Error) { + d, err := proto.Marshal(msg) + if err != nil { + return + } + + c.msgchan <- &Message{ + buf: d, + kind: kind, + } + + return +} + +// UDP receiver. +func (client *ClientConnection) udpreceiver() { + for { + buf := <-client.udprecv + kind := (buf[0] >> 5) & 0x07; + + switch kind { + case UDPMessageVoiceSpeex: fallthrough; + case UDPMessageVoiceCELTAlpha: fallthrough; + case UDPMessageVoiceCELTBeta: + kind := buf[0] & 0xe0 + target := buf[0] & 0x1f + var counter uint8 + outbuf := make([]byte, 1024) + + incoming := packetdatastream.New(buf[1:1+(len(buf)-1)]) + outgoing := packetdatastream.New(outbuf[1:1+(len(outbuf)-1)]) + _ = incoming.GetUint32() + + for { + counter = incoming.Next8() + incoming.Skip(int(counter & 0x7f)) + if !((counter & 0x80) != 0 && incoming.IsValid()) { + break + } + } + + outgoing.PutUint32(client.Session) + outgoing.PutBytes(buf[1:1+(len(buf)-1)]) + + // Sever loopback + if target == 0x1f { + outbuf[0] = kind + client.sendUdp(&Message{ + buf: outbuf[0:1+outgoing.Size()], + client: client, + }) + } + case UDPMessagePing: + client.server.udpsend <- &Message{ + buf: buf, + client: client, + } + } + } +} + +func (client *ClientConnection) sendUdp(msg *Message) { + if client.udp { + // Send as UDP + log.Stdoutf("Sent UDP!") + client.server.udpsend <- msg + } else { + // Tunnel through TCP + log.Stdoutf("Sent TCP!") + msg.kind = MessageUDPTunnel + client.msgchan <- msg + } +} + + +// +// Sender Goroutine +// +func (client *ClientConnection) sender() { + for { + msg := <-client.msgchan + + // First, we write out the message type as a big-endian uint16 + err := binary.Write(client.writer, binary.BigEndian, msg.kind) + if err != nil { + client.Panic("Unable to write message type to client") + return + } + + // Then the length of the protobuf message + err = binary.Write(client.writer, binary.BigEndian, uint32(len(msg.buf))) + if err != nil { + client.Panic("Unable to write message length to client") + return + } + + // At last, write the buffer itself + _, err = client.writer.Write(msg.buf) + if err != nil { + client.Panic("Unable to write message content to client") + return + } + + // Flush the write buffer + err = client.writer.Flush() + if err != nil { + client.Panic("Unable to flush client write buffer") + return + } + } +} + +// Receiver Goroutine +func (client *ClientConnection) receiver() { + for { + + // The version handshake is done. Forward this message to the synchronous request handler. + if client.state == StateClientAuthenticated || client.state == StateClientSentVersion { + // Try to read the next message in the pool + msg, err := client.readProtoMessage() + if err != nil { + return + } + // Special case UDPTunnel messages. They're high priority and shouldn't + // go through our synchronous path. + if msg.kind == MessageUDPTunnel { + client.udp = false + client.udprecv <- msg.buf + } else { + client.server.incoming <- msg + } + } + + // The client has just connected. Before it sends its authentication + // information we must send it our version information so it knows + // what version of the protocol it should speak. + if client.state == StateClientConnected { + client.sendProtoMessage(MessageVersion, &mumbleproto.Version{ + Version: proto.Uint32(0x10203), + Release: proto.String("1.2.2"), + }) + // fixme(mkrautz): Re-add OS information... Does it break anything? It seems like + // the client discards the version message if there is no OS information in it. + client.state = StateServerSentVersion + continue + } else if client.state == StateServerSentVersion { + msg, err := client.readProtoMessage() + if err != nil { + return + } + + version := &mumbleproto.Version{} + err = proto.Unmarshal(msg.buf, version) + if err != nil { + client.Panic("Unable to unmarshal client version packet.") + return + } + + // Don't really do anything with it... + + client.state = StateClientSentVersion + } + } +} + +// Send the channel list to a client. +func (client *ClientConnection) sendChannelList() { + server := client.server + root := server.root + + // Start at the root channel. + err := client.sendProtoMessage(MessageChannelState, &mumbleproto.ChannelState{ + ChannelId: proto.Uint32(uint32(root.Id)), + Name: proto.String(root.Name), + Description: proto.String(root.Description), + }) + if err != nil { + // panic! + log.Stdoutf("poanic!") + } +} + +// Send the userlist to a client. +func (client *ClientConnection) sendUserList() { + server := client.server + + server.cmutex.RLock() + defer server.cmutex.RUnlock() + + for x := range server.clients.Iter() { + user := x.(*ClientConnection) + err := user.sendProtoMessage(MessageUserState, &mumbleproto.UserState{ + Session: proto.Uint32(client.Session), + Name: proto.String(client.Username), + ChannelId: proto.Uint32(0), + }) + if err != nil { + log.Stdoutf("unable to send!") + continue + } + } +} + diff --git a/grumble.crt b/grumble.crt new file mode 100644 index 0000000..7c72cd7 --- /dev/null +++ b/grumble.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBNTCB4KADAgECAgQmyMwOMAsGCSqGSIb3DQEBBTASMRAwDgYDVQQDDAdncnVt +YmxlMB4XDTEwMDMyNTE1MzE0N1oXDTEwMDQyNDE1MzE0N1owEjEQMA4GA1UEAwwH +Z3J1bWJsZTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDH0Hy33EgJbHBupPXfoukc +b3CRwP891PPYdsDyCXoxWcYYSTC3BNC8/b2u+qtaE2EAcnfMwuNJBTMvn9UCXwwV +AgMBAAGjIDAeMAsGA1UdDwQEAwIBtjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBBQUAA0EAFjT5pPcVmXmDghqkZHZQcHsqSnIIH1MbULzNFErPHNabvdOp6gw1 +ycGs6CI1gdO3EuF+cHvS1ocm9ylWULf2cA== +-----END CERTIFICATE----- diff --git a/grumble.go b/grumble.go new file mode 100644 index 0000000..9f19225 --- /dev/null +++ b/grumble.go @@ -0,0 +1,43 @@ +// Copyright (c) 2010 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package main + +import ( + "flag" + "fmt" + "os" +) + +var help *bool = flag.Bool("help", false, "Show this help") +var port *int = flag.Int("port", 64738, "Default port to listen on") +var host *string = flag.String("host", "0.0.0.0", "Default host to listen on") + +func usage() { + fmt.Fprintf(os.Stderr, "usage: grumble [options]\n") + flag.PrintDefaults() +} + +func main() { + flag.Parse() + if *help == true { + usage() + return + } + + // Create our default server + m, err := NewServer(*host, *port) + if err != nil { + return + } + + // And launch it. + go m.ListenAndMurmur() + + // Listen forever + sleeper := make(chan int) + zzz := <-sleeper + if zzz > 0 { + } +} diff --git a/grumble.key b/grumble.key new file mode 100644 index 0000000..05ee6e6 --- /dev/null +++ b/grumble.key @@ -0,0 +1,10 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAMfQfLfcSAlscG6k9d+i6RxvcJHA/z3U89h2wPIJejFZxhhJMLcE +0Lz9va76q1oTYQByd8zC40kFMy+f1QJfDBUCAwEAAQJBALuKJDTRTM+DdvdyXs96 +8T5eHhK/SRF4uTHXK/tAB+8eQdSx7D2jsEASX+XSvO5Oc78jFTdG/9EYLW85nv2f +yQECIQDnRvppEVbvSVHbOZXTGCH85Raq+triXrQjT8qrQ2RAVQIhAN0sgv6rM8n0 +enIu7lhgQq4islgskCeagTZmI8V1/FzBAiBQBOPRBHnSssiKlCL9dYUU7eJo6ABh +gCjNaucRWHDQPQIgfVwvW116WyuhA3sqSRk2cjDkWSnZAzmFp2m5OMCpK4ECIG93 +VI6MTx5PJvOg2Nd6+6qbKF4uFNe/n0CHZ3QtDqhK +-----END RSA PRIVATE KEY----- + diff --git a/message.go b/message.go new file mode 100644 index 0000000..863718d --- /dev/null +++ b/message.go @@ -0,0 +1,180 @@ +// Grumble - an implementation of Murmur in Go +// Copyright (c) 2010 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package main + +import ( + "log" + "mumbleproto" + "goprotobuf.googlecode.com/hg/proto" + "net" + "container/list" + "cryptstate" +) + +// These are the different kinds of messages +// that are defined for the Mumble protocol +const ( + MessageVersion = iota + MessageUDPTunnel + MessageAuthenticate + MessagePing + MessageReject + MessageServerSync + MessageChannelRemove + MessageChannelState + MessageUserRemove + MessageUserState + MessageBanList + MessageTextMessage + MessagePermissionDenied + MessageACL + MessageQueryUsers + MessageCryptSetup + MessageContextActionAdd + MessageContextAction + MessageUserList + MessageVoiceTarget + MessagePermissionQuery + MessageCodecVersion + MessageUserStats + MessageRequestBlob + MessageServerConfig +) + +const ( + UDPMessageVoiceCELTAlpha = iota + UDPMessagePing + UDPMessageVoiceSpeex + UDPMessageVoiceCELTBeta +) + +type Message struct { + buf []byte + + // Kind denotes a message kind for TCP packets. This field + // is ignored for UDP packets. + kind uint16 + + // For UDP datagrams one of these fiels have to be filled out. + // If there is no connection established, address must be used. + // If the datagram comes from an already-connected client, the + // client field should point to that client. + client *ClientConnection + address net.Addr +} + +func (server *Server) handleCryptSetup(client *ClientConnection, msg *Message) { + cs := &mumbleproto.CryptSetup{} + err := proto.Unmarshal(msg.buf, cs) + if err != nil { + client.Panic(err.String()) + return + } + + // No client nonce. This means the client + // is requesting that we re-sync our nonces. + if len(cs.ClientNonce) == 0 { + log.Stdoutf("Requested crypt-nonce resync") + cs.ClientNonce = make([]byte, cryptstate.AESBlockSize) + if copy(cs.ClientNonce, client.crypt.EncryptIV[0:]) != cryptstate.AESBlockSize { + return + } + client.sendProtoMessage(MessageCryptSetup, cs) + } else { + log.Stdoutf("Received client nonce") + if len(cs.ClientNonce) != cryptstate.AESBlockSize { + return + } + + client.crypt.Resync += 1 + if copy(client.crypt.DecryptIV[0:], cs.ClientNonce) != cryptstate.AESBlockSize { + return + } + log.Stdoutf("Crypt re-sync successful") + } +} + +func (server *Server) handlePingMessage(client *ClientConnection, msg *Message) { + ping := &mumbleproto.Ping{} + err := proto.Unmarshal(msg.buf, ping) + if err != nil { + client.Panic(err.String()) + return + } + + // Phony response for ping messages. We don't keep stats + // for this yet. + client.sendProtoMessage(MessagePing, &mumbleproto.Ping{ + Timestamp: ping.Timestamp, + Good: proto.Uint32(uint32(client.crypt.Good)), + Late: proto.Uint32(uint32(client.crypt.Late)), + Lost: proto.Uint32(uint32(client.crypt.Lost)), + Resync: proto.Uint32(uint32(client.crypt.Resync)), + }) +} + +func (server *Server) handleChannelAddMessage(client *ClientConnection, msg *Message) { +} + +func (server *Server) handleChannelRemoveMessage(client *ClientConnection, msg *Message) { +} + +func (server *Server) handleChannelStateMessage(client *ClientConnection, msg *Message) { +} + +func (server *Server) handleUserRemoveMessage(client *ClientConnection, msg *Message) { +} + +func (server *Server) handleUserStateMessage(client *ClientConnection, msg *Message) { +} + +func (server *Server) handleBanListMessage(client *ClientConnection, msg *Message) { +} + +func (server *Server) handleTextMessage(client *ClientConnection, msg *Message) { + txtmsg := &mumbleproto.TextMessage{} + err := proto.Unmarshal(msg.buf, txtmsg) + if err != nil { + client.Panic(err.String()) + return + } + + // Receiver list... + users := list.New() + + for i := 0; i < len(txtmsg.Session); i++ { + // Lookup user by ID + user := server.getClientConnection(txtmsg.Session[i]) + if user != nil { + users.PushBack(user) + } + } + + for x := range users.Iter() { + user := x.(*ClientConnection) + user.sendProtoMessage(MessageTextMessage, &mumbleproto.TextMessage{ + Actor: proto.Uint32(client.Session), + Message: txtmsg.Message, + }) + } +} + +func (server *Server) handleAclMessage(client *ClientConnection, msg *Message) { +} + +// User query +func (server *Server) handleQueryUsers(client *ClientConnection, msg *Message) { +} + +// User stats message. Shown in the Mumble client when a +// user right clicks a user and selects 'User Information'. +func (server *Server) handleUserStatsMessage(client *ClientConnection, msg *Message) { + stats := &mumbleproto.UserStats{} + err := proto.Unmarshal(msg.buf, stats) + if err != nil { + client.Panic(err.String()) + } +} diff --git a/pkg/cryptstate/Makefile b/pkg/cryptstate/Makefile new file mode 100644 index 0000000..0aee113 --- /dev/null +++ b/pkg/cryptstate/Makefile @@ -0,0 +1,6 @@ +include $(GOROOT)/src/Make.inc + +TARG = cryptstate +GOFILES = cryptstate.go + +include $(GOROOT)/src/Make.pkg diff --git a/pkg/cryptstate/cryptstate.go b/pkg/cryptstate/cryptstate.go new file mode 100644 index 0000000..e857c9a --- /dev/null +++ b/pkg/cryptstate/cryptstate.go @@ -0,0 +1,360 @@ +// Grumble - an implementation of Murmur in Go +// Copyright (c) 2010 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package cryptstate + +import ( + "crypto/aes" + "crypto/rand" + "os" +) + +const AESBlockSize = 16 +const DecryptHistorySize = 0x100 + +type CryptState struct { + RawKey [AESBlockSize]byte + EncryptIV [AESBlockSize]byte + DecryptIV [AESBlockSize]byte + decryptHistory [DecryptHistorySize]byte + + Good int + Late int + Lost int + Resync int + + RemoteGood int + RemoteLate int + RemoteLost int + RemoteResync int + + cipher *aes.Cipher +} + +func New() (cs *CryptState, err os.Error) { + cs = new(CryptState) + + for i := 0; i < DecryptHistorySize; i++ { + cs.decryptHistory[i] = 0 + } + + return +} + +func (cs *CryptState) GenerateKey() (err os.Error) { + rand.Read(cs.RawKey[0:]) + rand.Read(cs.EncryptIV[0:]) + rand.Read(cs.DecryptIV[0:]) + + cs.cipher, err = aes.NewCipher(cs.RawKey[0:]) + if err != nil { + return + } + + return +} + +func (cs *CryptState) SetKey(key []byte, eiv []byte, div[]byte) (err os.Error) { + if copy(cs.RawKey[0:], key[0:]) != AESBlockSize { + err = os.NewError("Unable to copy key") + return + } + + if copy(cs.EncryptIV[0:], eiv[0:]) != AESBlockSize { + err = os.NewError("Unable to copy EIV") + return + } + + if copy(cs.DecryptIV[0:], div[0:]) != AESBlockSize { + err = os.NewError("Unable to copy DIV") + return + } + + cs.cipher, err = aes.NewCipher(cs.RawKey[0:]) + if err != nil { + return + } + + return +} + +func (cs *CryptState) Decrypt(src, dst []byte) (err os.Error) { + if len(src) < 4 { + err = os.NewError("Crypted length too short to decrypt") + return + } + + plain_len := len(src) - 4 + if len(dst) != plain_len { + err = os.NewError("plain_len and src len mismatch") + return + } + + var saveiv [AESBlockSize]byte + var tag [AESBlockSize]byte + var ivbyte byte + var restore bool + lost := 0 + late := 0 + + ivbyte = src[0] + restore = false + + if copy(saveiv[0:], cs.DecryptIV[0:]) != AESBlockSize { + err = os.NewError("Copy failed") + return + } + + if byte(cs.DecryptIV[0] + 1) == ivbyte { + // In order as expected + if ivbyte > cs.DecryptIV[0] { + cs.DecryptIV[0] = ivbyte + } else if ivbyte < cs.DecryptIV[0] { + cs.DecryptIV[0] = ivbyte + for i := 1; i < AESBlockSize; i++ { + cs.DecryptIV[i] += 1 + if cs.DecryptIV[i] > 0 { + break + } + } + } else { + err = os.NewError("invalid ivbyte") + } + } else { + // Out of order or repeat + var diff int + diff = int(ivbyte - cs.DecryptIV[0]) + if diff > 128 { + diff = diff - 256 + } else if diff < -128 { + diff = diff + 256 + } + + if ivbyte < cs.DecryptIV[0] && diff > -30 && diff < 0 { + // Late packet, but no wraparound + late = 1 + lost = -1 + cs.DecryptIV[0] = ivbyte + restore = true + } else if ivbyte > cs.DecryptIV[0] && diff > -30 && diff < 0 { + // Last was 0x02, here comes 0xff from last round + late = 1 + lost = -1 + cs.DecryptIV[0] = ivbyte + for i := 1; i < AESBlockSize; i++ { + cs.DecryptIV[0] -= 1 + if cs.DecryptIV[0] > 0 { + break + } + } + restore = true + } else if ivbyte > cs.DecryptIV[0] && diff > 0 { + // Lost a few packets, but beyond that we're good. + lost = int(ivbyte - cs.DecryptIV[0] - 1) + cs.DecryptIV[0] = ivbyte + } else if ivbyte < cs.DecryptIV[0] && diff > 0 { + // Lost a few packets, and wrapped around + lost = int(256 - int(cs.DecryptIV[0]) + int(ivbyte) - 1) + cs.DecryptIV[0] = ivbyte + for i := 1; i < AESBlockSize; i++ { + cs.DecryptIV[0] += 1 + if cs.DecryptIV[0] > 0 { + break + } + } + } else { + err = os.NewError("No matching ivbyte") + return + } + + if cs.decryptHistory[cs.DecryptIV[0]] == cs.DecryptIV[0] { + if copy(cs.DecryptIV[0:], saveiv[0:]) != AESBlockSize { + err = os.NewError("Failed to copy AESBlockSize bytes") + return + } + } + } + + cs.OCBDecrypt(src[4:], dst[0:], cs.DecryptIV[0:], tag[0:]) + + for i := 0; i < 3; i++ { + if tag[i] != src[i+1] { + if copy(cs.DecryptIV[0:], saveiv[0:]) != AESBlockSize { + err = os.NewError("Error while trying to recover from error") + return + } + err = os.NewError("tag mismatch") + return + } + } + + cs.decryptHistory[cs.DecryptIV[0]] = cs.DecryptIV[0] + + if restore { + if copy(cs.DecryptIV[0:], saveiv[0:]) != AESBlockSize { + err = os.NewError("Error while trying to recover IV") + return + } + } + + cs.Good += 1 + cs.Late += late + cs.Lost += lost + + // restart timer + + return +} + +func (cs *CryptState) Encrypt(src, dst []byte) { + var tag [AESBlockSize]byte + + // First, increase our IV + for i := 0; i < AESBlockSize; i++ { + cs.EncryptIV[i] += 1; + if cs.EncryptIV[i] > 0 { + break; + } + } + + cs.OCBEncrypt(src, dst[4:], cs.EncryptIV[0:], tag[0:]) + + dst[0] = cs.EncryptIV[0] + dst[1] = tag[0]; + dst[2] = tag[1]; + dst[3] = tag[2]; + + return +} + +func zeros(block []byte) { + for i := 0; i < AESBlockSize; i++ { + block[i] = 0 + } +} + +func xor(dst []byte, a []byte, b []byte) { + for i := 0; i < AESBlockSize; i++ { + dst[i] = a[i] ^ b[i] + } +} + +func times2(block []byte) { + carry := (block[0] >> 7) & 0x1 + for i := 0; i < AESBlockSize-1; i++ { + block[i] = (block[i] << 1) | ((block[i+1] >> 7) & 0x1) + } + block[AESBlockSize-1] = (block[AESBlockSize-1] << 1) ^ (carry * 135) +} + +func times3(block []byte) { + carry := (block[0] >> 7) & 0x1; + for i := 0; i < AESBlockSize-1; i++ { + block[i] ^= (block[i] << 1) | ((block[i+1] >> 7) & 0x1) + } + block[AESBlockSize-1] ^= ((block[AESBlockSize-1] << 1) ^ (carry * 135)) +} + +func (cs *CryptState) OCBEncrypt(src []byte, dst []byte, nonce []byte, tag []byte) (err os.Error) { + var delta [AESBlockSize]byte + var checksum [AESBlockSize]byte + var tmp [AESBlockSize]byte + var pad [AESBlockSize]byte + off := 0 + + cs.cipher.Encrypt(cs.EncryptIV[0:], delta[0:]) + zeros(checksum[0:]) + + remain := len(src) + for remain > AESBlockSize { + times2(delta[0:]) + xor(tmp[0:], delta[0:], src[off:off+AESBlockSize]) + cs.cipher.Encrypt(tmp[0:], tmp[0:]) + xor(dst[off:off+AESBlockSize], delta[0:], tmp[0:]) + xor(checksum[0:], checksum[0:], src[off:off+AESBlockSize]) + remain -= AESBlockSize + off += AESBlockSize + } + + times2(delta[0:]) + zeros(tmp[0:]) + num := remain * 8 + tmp[AESBlockSize-2] = uint8((uint32(num) >> 8) & 0xff) + tmp[AESBlockSize-1] = uint8(num & 0xff) + xor(tmp[0:], tmp[0:], delta[0:]) + cs.cipher.Encrypt(tmp[0:], pad[0:]) + copied := copy(tmp[0:], src[off:]) + if copied != remain { + err = os.NewError("Copy failed") + return + } + if copy(tmp[copied:], pad[copied:]) != (AESBlockSize-remain) { + err = os.NewError("Copy failed") + return + } + xor(checksum[0:], checksum[0:], tmp[0:]) + xor(tmp[0:], pad[0:], tmp[0:]) + if copy(dst[off:], tmp[0:]) != remain { + err = os.NewError("Copy failed") + return + } + + times3(delta[0:]) + xor(tmp[0:], delta[0:], checksum[0:]) + cs.cipher.Encrypt(tmp[0:], tag[0:]) + + return +} + +func (cs *CryptState) OCBDecrypt(encrypted []byte, plain []byte, nonce []byte, tag []byte) (err os.Error) { + var checksum [AESBlockSize]byte + var delta [AESBlockSize]byte + var tmp [AESBlockSize]byte + var pad [AESBlockSize]byte + off := 0 + + cs.cipher.Encrypt(nonce[0:], delta[0:]) + zeros(checksum[0:]) + + remain := len(encrypted) + for remain > AESBlockSize { + times2(delta[0:]) + xor(tmp[0:], delta[0:], encrypted[off:off+AESBlockSize]) + cs.cipher.Decrypt(tmp[0:], tmp[0:]) + xor(plain[off:off+AESBlockSize], delta[0:], tmp[0:]) + xor(checksum[0:], checksum[0:], plain[off:off+AESBlockSize]) + off += AESBlockSize + remain -= AESBlockSize + } + + times2(delta[0:]) + zeros(tmp[0:]) + num := remain * 8 + tmp[AESBlockSize-2] = uint8((uint32(num) >> 8) & 0xff) + tmp[AESBlockSize-1] = uint8(num & 0xff) + xor(tmp[0:], tmp[0:], delta[0:]) + cs.cipher.Encrypt(tmp[0:], pad[0:]) + for i := 0; i < AESBlockSize; i++ { + tmp[i] = 0 + } + copied := copy(tmp[0:remain], encrypted[off:off+remain]) + if copied != remain { + err = os.NewError("Copy failed") + return + } + xor(tmp[0:], tmp[0:], pad[0:]) + xor(checksum[0:], checksum[0:], tmp[0:]) + copied = copy(plain[off:off+remain], tmp[0:remain]) + if copied != remain { + err = os.NewError("Copy failed") + return + } + + times3(delta[0:]) + xor(tmp[0:], delta[0:], checksum[0:]) + cs.cipher.Encrypt(tmp[0:], tag[0:]) + + return +} diff --git a/pkg/cryptstate/cryptstate_test.go b/pkg/cryptstate/cryptstate_test.go new file mode 100644 index 0000000..b185989 --- /dev/null +++ b/pkg/cryptstate/cryptstate_test.go @@ -0,0 +1,147 @@ +package cryptstate + +import ( + "testing" +) + +func BlockCompare(a []byte, b []byte) (match bool) { + if len(a) != len(b) { + return + } + + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return + } + } + + match = true + return +} + +func TestTimes2(t *testing.T) { + msg := [AESBlockSize]byte{ + 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + } + expected := [AESBlockSize]byte{ + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7b, + } + + times2(msg[0:]) + if BlockCompare(msg[0:], expected[0:]) == false { + t.Errorf("times2 produces invalid output: %v, expected: %v", msg, expected) + } +} + +func TestTimes3(t *testing.T) { + msg := [AESBlockSize]byte{ + 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + } + expected := [AESBlockSize]byte { + 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, + } + + times3(msg[0:]) + if BlockCompare(msg[0:], expected[0:]) == false { + t.Errorf("times3 produces invalid output: %v, expected: %v", msg, expected) + } +} + +func TestZeros(t *testing.T) { + var msg [AESBlockSize]byte + zeros(msg[0:]) + for i := 0; i < len(msg); i++ { + if msg[i] != 0 { + t.Errorf("zeros does not zero slice.") + } + } +} + +func TestXor(t *testing.T) { + msg := [AESBlockSize]byte{ + 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + } + var out [AESBlockSize]byte + xor(out[0:], msg[0:], msg[0:]) + for i := 0; i < len(out); i++ { + if out[i] != 0 { + t.Errorf("XOR broken") + } + } +} + +func TestEncrypt(t *testing.T) { + msg := [15]byte { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + } + key := [AESBlockSize]byte { + 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, + } + eiv := [AESBlockSize]byte { + 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, + } + div := [AESBlockSize]byte { + 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, + } + expected := [19]byte { + 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, + } + expected_eiv := [AESBlockSize]byte { + 0x1f, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, + } + + cs, err := New() + if err != nil { + t.Errorf("%v", err) + } + + out := make([]byte, 19) + cs.SetKey(key[0:], eiv[0:], div[0:]) + cs.Encrypt(msg[0:], out[0:]) + + if BlockCompare(out[0:], expected[0:]) == false { + t.Errorf("Mismatch in output") + } + + if BlockCompare(cs.EncryptIV[0:], expected_eiv[0:]) == false { + t.Errorf("EIV mismatch") + } +} + +func TestDecrypt(t *testing.T) { + key := [AESBlockSize]byte { + 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, + } + eiv := [AESBlockSize]byte { + 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, + } + div := [AESBlockSize]byte { + 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, + } + crypted := [19]byte { + 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, + } + expected := [15]byte { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + } + post_div := [AESBlockSize]byte { + 0x1f, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, + } + + cs, err := New() + if err != nil { + t.Errorf("%v", err) + } + + out := make([]byte, 15) + cs.SetKey(key[0:], div[0:], eiv[0:]) + cs.Decrypt(crypted[0:], out[0:]) + + if BlockCompare(out[0:], expected[0:]) == false { + t.Errorf("Mismatch in output") + } + + if BlockCompare(cs.DecryptIV[0:], post_div[0:]) == false { + t.Errorf("Mismatch in DIV") + } +} diff --git a/pkg/cryptstate/testgen/CryptState.cpp b/pkg/cryptstate/testgen/CryptState.cpp new file mode 100644 index 0000000..baff9e1 --- /dev/null +++ b/pkg/cryptstate/testgen/CryptState.cpp @@ -0,0 +1,322 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + * This code implements OCB-AES128. + * In the US, OCB is covered by patents. The inventor has given a license + * to all programs distributed under the GPL. + * Mumble is BSD (revised) licensed, meaning you can use the code in a + * closed-source program. If you do, you'll have to either replace + * OCB with something else or get yourself a license. + */ + +#include "CryptState.h" + +#include +#include +#include + +namespace MumbleClient { + +CryptState::CryptState() { + for (int i = 0; i < 0x100; i++) + decrypt_history[i] = 0; + + bInit = false; + uiGood = uiLate = uiLost = uiResync = 0; + uiRemoteGood = uiRemoteLate = uiRemoteLost = uiRemoteResync = 0; +} + +bool CryptState::isValid() const { + return bInit; +} + +void CryptState::genKey() { + RAND_bytes(raw_key, AES_BLOCK_SIZE); + RAND_bytes(encrypt_iv, AES_BLOCK_SIZE); + RAND_bytes(decrypt_iv, AES_BLOCK_SIZE); + AES_set_encrypt_key(raw_key, 128, &encrypt_key); + AES_set_decrypt_key(raw_key, 128, &decrypt_key); + bInit = true; +} + +void CryptState::setKey(const unsigned char* rkey, const unsigned char* eiv, const unsigned char* div) { + memcpy(raw_key, rkey, AES_BLOCK_SIZE); + memcpy(encrypt_iv, eiv, AES_BLOCK_SIZE); + memcpy(decrypt_iv, div, AES_BLOCK_SIZE); + AES_set_encrypt_key(raw_key, 128, &encrypt_key); + AES_set_decrypt_key(raw_key, 128, &decrypt_key); + bInit = true; +} + +void CryptState::setDecryptIV(const unsigned char* iv) { + memcpy(decrypt_iv, iv, AES_BLOCK_SIZE); +} + +const unsigned char* CryptState::getEncryptIV() const { + return encrypt_iv; +} + +void CryptState::encrypt(const unsigned char* source, unsigned char* dst, unsigned int plain_length) { + unsigned char tag[AES_BLOCK_SIZE]; + + // First, increase our IV. + for (int i = 0; i < AES_BLOCK_SIZE; i++) + if (++encrypt_iv[i]) + break; + + ocb_encrypt(source, dst+4, plain_length, encrypt_iv, tag); + + dst[0] = encrypt_iv[0]; + dst[1] = tag[0]; + dst[2] = tag[1]; + dst[3] = tag[2]; +} + +bool CryptState::decrypt(const unsigned char* source, unsigned char* dst, unsigned int crypted_length) { + if (crypted_length < 4) + return false; + + unsigned int plain_length = crypted_length - 4; + + unsigned char saveiv[AES_BLOCK_SIZE]; + unsigned char ivbyte = source[0]; + bool restore = false; + unsigned char tag[AES_BLOCK_SIZE]; + + int lost = 0; + int late = 0; + + memcpy(saveiv, decrypt_iv, AES_BLOCK_SIZE); + + if (((decrypt_iv[0] + 1) & 0xFF) == ivbyte) { + // In order as expected. + if (ivbyte > decrypt_iv[0]) { + decrypt_iv[0] = ivbyte; + } else if (ivbyte < decrypt_iv[0]) { + decrypt_iv[0] = ivbyte; + for (int i = 1;i < AES_BLOCK_SIZE; i++) + if (++decrypt_iv[i]) + break; + } else { + return false; + } + } else { + // This is either out of order or a repeat. + + int diff = ivbyte - decrypt_iv[0]; + if (diff > 128) + diff = diff-256; + else if (diff < -128) + diff = diff+256; + + if ((ivbyte < decrypt_iv[0]) && (diff > -30) && (diff < 0)) { + // Late packet, but no wraparound. + late = 1; + lost = -1; + decrypt_iv[0] = ivbyte; + restore = true; + } else if ((ivbyte > decrypt_iv[0]) && (diff > -30) && (diff < 0)) { + // Last was 0x02, here comes 0xff from last round + late = 1; + lost = -1; + decrypt_iv[0] = ivbyte; + for (int i = 1; i < AES_BLOCK_SIZE; i++) + if (decrypt_iv[i]--) + break; + restore = true; + } else if ((ivbyte > decrypt_iv[0]) && (diff > 0)) { + // Lost a few packets, but beyond that we're good. + lost = ivbyte - decrypt_iv[0] - 1; + decrypt_iv[0] = ivbyte; + } else if ((ivbyte < decrypt_iv[0]) && (diff > 0)) { + // Lost a few packets, and wrapped around + lost = 256 - decrypt_iv[0] + ivbyte - 1; + decrypt_iv[0] = ivbyte; + for (int i = 1; i < AES_BLOCK_SIZE; i++) + if (++decrypt_iv[i]) + break; + } else { + return false; + } + + if (decrypt_history[decrypt_iv[0]] == decrypt_iv[1]) { + memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); + return false; + } + } + + ocb_decrypt(source + 4, dst, plain_length, decrypt_iv, tag); + + if (memcmp(tag, source + 1, 3) != 0) { + memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); + return false; + } + decrypt_history[decrypt_iv[0]] = decrypt_iv[1]; + + if (restore) + memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); + + uiGood++; + uiLate += late; + uiLost += lost; + + return true; +} + +#if defined(__LP64__) + +#define BLOCKSIZE 2 +#define SHIFTBITS 63 +typedef uint64_t subblock; + +#ifdef __x86_64__ +static inline uint64_t SWAP64(register uint64_t __in) { register uint64_t __out; __asm__("bswap %q0" : "=r"(__out) : "0"(__in)); return __out; } +#else +#define SWAP64(x) ((static_cast(x) << 56) | \ + ((static_cast(x) << 40) & 0xff000000000000ULL) | \ + ((static_cast(x) << 24) & 0xff0000000000ULL) | \ + ((static_cast(x) << 8) & 0xff00000000ULL) | \ + ((static_cast(x) >> 8) & 0xff000000ULL) | \ + ((static_cast(x) >> 24) & 0xff0000ULL) | \ + ((static_cast(x) >> 40) & 0xff00ULL) | \ + ((static_cast(x) >> 56))) +#endif + +#define SWAPPED(x) SWAP64(x) + +#else +#define BLOCKSIZE 4 +#define SHIFTBITS 31 +typedef uint32_t subblock; +#define SWAPPED(x) htonl(x) +#endif + +typedef subblock keyblock[BLOCKSIZE]; + +#define HIGHBIT (1<> SHIFTBITS; + for (int i = 0; i < BLOCKSIZE - 1; i++) + block[i] = SWAPPED((SWAPPED(block[i]) << 1) | (SWAPPED(block[i + 1]) >> SHIFTBITS)); + block[BLOCKSIZE - 1] = SWAPPED((SWAPPED(block[BLOCKSIZE - 1]) << 1) ^(carry * 0x87)); +} + +static void inline S3(subblock* block) { + subblock carry = SWAPPED(block[0]) >> SHIFTBITS; + for (int i = 0; i < BLOCKSIZE - 1; i++) + block[i] ^= SWAPPED((SWAPPED(block[i]) << 1) | (SWAPPED(block[i + 1]) >> SHIFTBITS)); + block[BLOCKSIZE - 1] ^= SWAPPED((SWAPPED(block[BLOCKSIZE - 1]) << 1) ^(carry * 0x87)); +} + +static void inline ZERO(keyblock &block) { + for (int i = 0; i < BLOCKSIZE; i++) + block[i] = 0; +} + +#define AESencrypt(src,dst,key) AES_encrypt(reinterpret_cast(src),reinterpret_cast(dst), key); +#define AESdecrypt(src,dst,key) AES_decrypt(reinterpret_cast(src),reinterpret_cast(dst), key); + +void CryptState::ocb_encrypt(const unsigned char* plain, unsigned char* encrypted, unsigned int len, const unsigned char* nonce, unsigned char* tag) { + keyblock checksum, delta, tmp, pad; + + // Initialize + AESencrypt(nonce, delta, &encrypt_key); + ZERO(checksum); + + while (len > AES_BLOCK_SIZE) { + S2(delta); + XOR(tmp, delta, reinterpret_cast(plain)); + AESencrypt(tmp, tmp, &encrypt_key); + XOR(reinterpret_cast(encrypted), delta, tmp); + XOR(checksum, checksum, reinterpret_cast(plain)); + len -= AES_BLOCK_SIZE; + plain += AES_BLOCK_SIZE; + encrypted += AES_BLOCK_SIZE; + } + + S2(delta); + ZERO(tmp); + tmp[BLOCKSIZE - 1] = SWAPPED(len * 8); + XOR(tmp, tmp, delta); + AESencrypt(tmp, pad, &encrypt_key); + memcpy(tmp, plain, len); + memcpy(reinterpret_cast(tmp) + len, reinterpret_cast(pad) + len, AES_BLOCK_SIZE - len); + XOR(checksum, checksum, tmp); + XOR(tmp, pad, tmp); + memcpy(encrypted, tmp, len); + + S3(delta); + XOR(tmp, delta, checksum); + AESencrypt(tmp, tag, &encrypt_key); +} + +void CryptState::ocb_decrypt(const unsigned char* encrypted, unsigned char* plain, unsigned int len, const unsigned char* nonce, unsigned char* tag) { + keyblock checksum, delta, tmp, pad; + + // Initialize + AESencrypt(nonce, delta, &encrypt_key); + ZERO(checksum); + + while (len > AES_BLOCK_SIZE) { + S2(delta); + XOR(tmp, delta, reinterpret_cast(encrypted)); + AESdecrypt(tmp, tmp, &decrypt_key); + XOR(reinterpret_cast(plain), delta, tmp); + XOR(checksum, checksum, reinterpret_cast(plain)); + len -= AES_BLOCK_SIZE; + plain += AES_BLOCK_SIZE; + encrypted += AES_BLOCK_SIZE; + } + + S2(delta); + ZERO(tmp); + tmp[BLOCKSIZE - 1] = SWAPPED(len * 8); + XOR(tmp, tmp, delta); + AESencrypt(tmp, pad, &encrypt_key); + memset(tmp, 0, AES_BLOCK_SIZE); + memcpy(tmp, encrypted, len); + XOR(tmp, tmp, pad); + XOR(checksum, checksum, tmp); + memcpy(plain, tmp, len); + + S3(delta); + XOR(tmp, delta, checksum); + AESencrypt(tmp, tag, &encrypt_key); +} + +} // end namespace MumbleClient diff --git a/pkg/cryptstate/testgen/CryptState.h b/pkg/cryptstate/testgen/CryptState.h new file mode 100644 index 0000000..4c08d96 --- /dev/null +++ b/pkg/cryptstate/testgen/CryptState.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2005-2010, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _CRYPTSTATE_H +#define _CRYPTSTATE_H + +#include + +namespace MumbleClient { + +class CryptState { + public: + unsigned char raw_key[AES_BLOCK_SIZE]; + unsigned char encrypt_iv[AES_BLOCK_SIZE]; + unsigned char decrypt_iv[AES_BLOCK_SIZE]; + unsigned char decrypt_history[0x100]; + + unsigned int uiGood; + unsigned int uiLate; + unsigned int uiLost; + unsigned int uiResync; + + unsigned int uiRemoteGood; + unsigned int uiRemoteLate; + unsigned int uiRemoteLost; + unsigned int uiRemoteResync; + + AES_KEY encrypt_key; + AES_KEY decrypt_key; + bool bInit; + + public: + CryptState(); + + bool isValid() const; + void genKey(); + void setKey(const unsigned char* rkey, const unsigned char* eiv, const unsigned char* div); + void setDecryptIV(const unsigned char* iv); + const unsigned char* getEncryptIV() const; + + void ocb_encrypt(const unsigned char* plain, unsigned char* encrypted, unsigned int len, const unsigned char* nonce, unsigned char* tag); + void ocb_decrypt(const unsigned char* encrypted, unsigned char* plain, unsigned int len, const unsigned char* nonce, unsigned char* tag); + + bool decrypt(const unsigned char* source, unsigned char* dst, unsigned int crypted_length); + void encrypt(const unsigned char* source, unsigned char* dst, unsigned int plain_length); +}; + +} // end namespace MumbleClient + +#endif diff --git a/pkg/cryptstate/testgen/Makefile b/pkg/cryptstate/testgen/Makefile new file mode 100644 index 0000000..fede5ec --- /dev/null +++ b/pkg/cryptstate/testgen/Makefile @@ -0,0 +1,9 @@ +.PHONY: default +default: + g++ test.cpp CryptState.cpp -o test -lcrypto + g++ test2.cpp CryptState.cpp -o test2 -lcrypto + g++ test3.cpp CryptState.cpp -o test3 -lcrypto + +.PHONY: clean +clean: + rm -f test test2 test3 diff --git a/pkg/cryptstate/testgen/README b/pkg/cryptstate/testgen/README new file mode 100644 index 0000000..9785000 --- /dev/null +++ b/pkg/cryptstate/testgen/README @@ -0,0 +1 @@ +C++ code for generating some of the test vectors used in cryptstate_test.go diff --git a/pkg/cryptstate/testgen/test.cpp b/pkg/cryptstate/testgen/test.cpp new file mode 100644 index 0000000..b085f3a --- /dev/null +++ b/pkg/cryptstate/testgen/test.cpp @@ -0,0 +1,28 @@ +#include "CryptState.h" +#include + +unsigned char msg[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +}; + +static void DumpBytes(unsigned char *bytes, unsigned int len, const char *name) { + printf("unsigned char %s[] = { ", name); + for (int i = 0; i < len; i++) { + printf("0x%.2x, ", bytes[i]); + } + printf("}\n"); +} + +int main(int argc, char *argv[]) { + MumbleClient::CryptState cs; + cs.genKey(); + + DumpBytes(cs.raw_key, AES_BLOCK_SIZE, "rawkey"); + DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "encrypt_iv"); + DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "decrypt_iv"); + + unsigned char buf[19]; + cs.encrypt(msg, &buf[0], 15); + + DumpBytes(buf, 19, "crypted"); +} diff --git a/pkg/cryptstate/testgen/test2.cpp b/pkg/cryptstate/testgen/test2.cpp new file mode 100644 index 0000000..6052a0d --- /dev/null +++ b/pkg/cryptstate/testgen/test2.cpp @@ -0,0 +1,36 @@ +#include "CryptState.h" +#include + +unsigned char msg[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +}; + +unsigned char rawkey[] = { 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, }; +unsigned char encrypt_iv[] = { 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, }; +unsigned char decrypt_iv[] = { 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, }; +unsigned char crypted[] = { 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, }; + + +static void DumpBytes(unsigned char *bytes, unsigned int len, const char *name) { + printf("unsigned char %s[] = { ", name); + for (int i = 0; i < len; i++) { + printf("0x%.2x, ", bytes[i]); + } + printf("}\n"); +} + +int main(int argc, char *argv[]) { + MumbleClient::CryptState cs; +// cs.genKey(); + cs.setKey(rawkey, encrypt_iv, decrypt_iv); + + DumpBytes(cs.raw_key, AES_BLOCK_SIZE, "rawkey"); + DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "encrypt_iv"); + DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "decrypt_iv"); + + unsigned char buf[19]; + cs.encrypt(msg, &buf[0], 15); + + DumpBytes(buf, 19, "crypted"); + DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "post_eiv"); +} diff --git a/pkg/cryptstate/testgen/test3.cpp b/pkg/cryptstate/testgen/test3.cpp new file mode 100644 index 0000000..d7941bb --- /dev/null +++ b/pkg/cryptstate/testgen/test3.cpp @@ -0,0 +1,34 @@ +#include "CryptState.h" +#include + +unsigned char msg[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +}; + +unsigned char rawkey[] = { 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, }; +unsigned char encrypt_iv[] = { 0x1e, 0x2a, 0x9b, 0xd0, 0x2d, 0xa6, 0x8e, 0x46, 0x26, 0x85, 0x83, 0xe9, 0x14, 0x2a, 0xff, 0x2a, }; +unsigned char decrypt_iv[] = { 0x73, 0x99, 0x9d, 0xa2, 0x03, 0x70, 0x00, 0x96, 0xef, 0x55, 0x06, 0x7a, 0x8b, 0xbe, 0x00, 0x07, }; +unsigned char crypted[] = { 0x1f, 0xfc, 0xdd, 0xb4, 0x68, 0x13, 0x68, 0xb7, 0x92, 0x67, 0xca, 0x2d, 0xba, 0xb7, 0x0d, 0x44, 0xdf, 0x32, 0xd4, }; + +static void DumpBytes(unsigned char *bytes, unsigned int len, const char *name) { + printf("unsigned char %s[] = { ", name); + for (int i = 0; i < len; i++) { + printf("0x%.2x, ", bytes[i]); + } + printf("}\n"); +} + +int main(int argc, char *argv[]) { + MumbleClient::CryptState cs; + cs.setKey(rawkey, decrypt_iv, encrypt_iv); + + DumpBytes(cs.raw_key, AES_BLOCK_SIZE, "rawkey"); + DumpBytes(cs.encrypt_iv, AES_BLOCK_SIZE, "encrypt_iv"); + DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "decrypt_iv"); + + unsigned char buf[15]; + cs.decrypt(crypted, &buf[0], 19); + + DumpBytes(buf, 15, "plain"); + DumpBytes(cs.decrypt_iv, AES_BLOCK_SIZE, "post_div"); +} diff --git a/pkg/mumbleproto/Makefile b/pkg/mumbleproto/Makefile new file mode 100644 index 0000000..973144d --- /dev/null +++ b/pkg/mumbleproto/Makefile @@ -0,0 +1,7 @@ +include $(GOROOT)/src/Make.inc + +TARG = mumbleproto +GOFILES = Mumble.pb.go + +include $(GOROOT)/src/Make.pkg +include $(GOROOT)/src/pkg/goprotobuf.googlecode.com/hg/Make.protobuf diff --git a/pkg/mumbleproto/Mumble.pb.go b/pkg/mumbleproto/Mumble.pb.go new file mode 100644 index 0000000..50f60cb --- /dev/null +++ b/pkg/mumbleproto/Mumble.pb.go @@ -0,0 +1,502 @@ +// Code generated by protoc-gen-go from "Mumble.proto" +// DO NOT EDIT! + +package mumbleproto + +import proto "goprotobuf.googlecode.com/hg/proto" + +// Reference proto import to suppress error if it's not otherwise used. +var _ = proto.GetString + +type Reject_RejectType int32 +const ( + Reject_None = 0 + Reject_WrongVersion = 1 + Reject_InvalidUsername = 2 + Reject_WrongUserPW = 3 + Reject_WrongServerPW = 4 + Reject_UsernameInUse = 5 + Reject_ServerFull = 6 + Reject_NoCertificate = 7 +) +var Reject_RejectType_name = map[int32] string { + 0: "None", + 1: "WrongVersion", + 2: "InvalidUsername", + 3: "WrongUserPW", + 4: "WrongServerPW", + 5: "UsernameInUse", + 6: "ServerFull", + 7: "NoCertificate", +} +var Reject_RejectType_value = map[string] int32 { + "None": 0, + "WrongVersion": 1, + "InvalidUsername": 2, + "WrongUserPW": 3, + "WrongServerPW": 4, + "UsernameInUse": 5, + "ServerFull": 6, + "NoCertificate": 7, +} +func NewReject_RejectType(x int32) *Reject_RejectType { + e := Reject_RejectType(x) + return &e +} + +type PermissionDenied_DenyType int32 +const ( + PermissionDenied_Text = 0 + PermissionDenied_Permission = 1 + PermissionDenied_SuperUser = 2 + PermissionDenied_ChannelName = 3 + PermissionDenied_TextTooLong = 4 + PermissionDenied_H9K = 5 + PermissionDenied_TemporaryChannel = 6 + PermissionDenied_MissingCertificate = 7 + PermissionDenied_UserName = 8 + PermissionDenied_ChannelFull = 9 +) +var PermissionDenied_DenyType_name = map[int32] string { + 0: "Text", + 1: "Permission", + 2: "SuperUser", + 3: "ChannelName", + 4: "TextTooLong", + 5: "H9K", + 6: "TemporaryChannel", + 7: "MissingCertificate", + 8: "UserName", + 9: "ChannelFull", +} +var PermissionDenied_DenyType_value = map[string] int32 { + "Text": 0, + "Permission": 1, + "SuperUser": 2, + "ChannelName": 3, + "TextTooLong": 4, + "H9K": 5, + "TemporaryChannel": 6, + "MissingCertificate": 7, + "UserName": 8, + "ChannelFull": 9, +} +func NewPermissionDenied_DenyType(x int32) *PermissionDenied_DenyType { + e := PermissionDenied_DenyType(x) + return &e +} + +type ContextActionAdd_Context int32 +const ( + ContextActionAdd_Server = 1 + ContextActionAdd_Channel = 2 + ContextActionAdd_User = 4 +) +var ContextActionAdd_Context_name = map[int32] string { + 1: "Server", + 2: "Channel", + 4: "User", +} +var ContextActionAdd_Context_value = map[string] int32 { + "Server": 1, + "Channel": 2, + "User": 4, +} +func NewContextActionAdd_Context(x int32) *ContextActionAdd_Context { + e := ContextActionAdd_Context(x) + return &e +} + +type Version struct { + Version *uint32 "PB(varint,1,opt,name=version)" + Release *string "PB(bytes,2,opt,name=release)" + Os *string "PB(bytes,3,opt,name=os)" + OsVersion *string "PB(bytes,4,opt,name=os_version)" + XXX_unrecognized []byte +} +func (this *Version) Reset() { + *this = Version{} +} + +type UDPTunnel struct { + Packet []byte "PB(bytes,1,req,name=packet)" + XXX_unrecognized []byte +} +func (this *UDPTunnel) Reset() { + *this = UDPTunnel{} +} + +type Authenticate struct { + Username *string "PB(bytes,1,opt,name=username)" + Password *string "PB(bytes,2,opt,name=password)" + Tokens []string "PB(bytes,3,rep,name=tokens)" + CeltVersions []int32 "PB(varint,4,rep,name=celt_versions)" + XXX_unrecognized []byte +} +func (this *Authenticate) Reset() { + *this = Authenticate{} +} + +type Ping struct { + Timestamp *uint64 "PB(varint,1,opt,name=timestamp)" + Good *uint32 "PB(varint,2,opt,name=good)" + Late *uint32 "PB(varint,3,opt,name=late)" + Lost *uint32 "PB(varint,4,opt,name=lost)" + Resync *uint32 "PB(varint,5,opt,name=resync)" + UdpPackets *uint32 "PB(varint,6,opt,name=udp_packets)" + TcpPackets *uint32 "PB(varint,7,opt,name=tcp_packets)" + UdpPingAvg *float32 "PB(fixed32,8,opt,name=udp_ping_avg)" + UdpPingVar *float32 "PB(fixed32,9,opt,name=udp_ping_var)" + TcpPingAvg *float32 "PB(fixed32,10,opt,name=tcp_ping_avg)" + TcpPingVar *float32 "PB(fixed32,11,opt,name=tcp_ping_var)" + XXX_unrecognized []byte +} +func (this *Ping) Reset() { + *this = Ping{} +} + +type Reject struct { + Type *Reject_RejectType "PB(varint,1,opt,name=type,enum=mumbleproto.Reject_RejectType)" + Reason *string "PB(bytes,2,opt,name=reason)" + XXX_unrecognized []byte +} +func (this *Reject) Reset() { + *this = Reject{} +} + +type ServerConfig struct { + MaxBandwidth *uint32 "PB(varint,1,opt,name=max_bandwidth)" + WelcomeText *string "PB(bytes,2,opt,name=welcome_text)" + AllowHtml *bool "PB(varint,3,opt,name=allow_html)" + MessageLength *uint32 "PB(varint,4,opt,name=message_length)" + ImageMessageLength *uint32 "PB(varint,5,opt,name=image_message_length)" + XXX_unrecognized []byte +} +func (this *ServerConfig) Reset() { + *this = ServerConfig{} +} + +type ServerSync struct { + Session *uint32 "PB(varint,1,opt,name=session)" + MaxBandwidth *uint32 "PB(varint,2,opt,name=max_bandwidth)" + WelcomeText *string "PB(bytes,3,opt,name=welcome_text)" + Permissions *uint64 "PB(varint,4,opt,name=permissions)" + XXX_unrecognized []byte +} +func (this *ServerSync) Reset() { + *this = ServerSync{} +} + +type ChannelRemove struct { + ChannelId *uint32 "PB(varint,1,req,name=channel_id)" + XXX_unrecognized []byte +} +func (this *ChannelRemove) Reset() { + *this = ChannelRemove{} +} + +type ChannelState struct { + ChannelId *uint32 "PB(varint,1,opt,name=channel_id)" + Parent *uint32 "PB(varint,2,opt,name=parent)" + Name *string "PB(bytes,3,opt,name=name)" + Links []uint32 "PB(varint,4,rep,name=links)" + Description *string "PB(bytes,5,opt,name=description)" + LinksAdd []uint32 "PB(varint,6,rep,name=links_add)" + LinksRemove []uint32 "PB(varint,7,rep,name=links_remove)" + Temporary *bool "PB(varint,8,opt,name=temporary,def=0)" + Position *int32 "PB(varint,9,opt,name=position,def=0)" + DescriptionHash []byte "PB(bytes,10,opt,name=description_hash)" + XXX_unrecognized []byte +} +func (this *ChannelState) Reset() { + *this = ChannelState{} +} +const Default_ChannelState_Temporary bool = false +const Default_ChannelState_Position int32 = 0 + +type UserRemove struct { + Session *uint32 "PB(varint,1,req,name=session)" + Actor *uint32 "PB(varint,2,opt,name=actor)" + Reason *string "PB(bytes,3,opt,name=reason)" + Ban *bool "PB(varint,4,opt,name=ban)" + XXX_unrecognized []byte +} +func (this *UserRemove) Reset() { + *this = UserRemove{} +} + +type UserState struct { + Session *uint32 "PB(varint,1,opt,name=session)" + Actor *uint32 "PB(varint,2,opt,name=actor)" + Name *string "PB(bytes,3,opt,name=name)" + UserId *uint32 "PB(varint,4,opt,name=user_id)" + ChannelId *uint32 "PB(varint,5,opt,name=channel_id)" + Mute *bool "PB(varint,6,opt,name=mute)" + Deaf *bool "PB(varint,7,opt,name=deaf)" + Suppress *bool "PB(varint,8,opt,name=suppress)" + SelfMute *bool "PB(varint,9,opt,name=self_mute)" + SelfDeaf *bool "PB(varint,10,opt,name=self_deaf)" + Texture []byte "PB(bytes,11,opt,name=texture)" + PluginContext []byte "PB(bytes,12,opt,name=plugin_context)" + PluginIdentity *string "PB(bytes,13,opt,name=plugin_identity)" + Comment *string "PB(bytes,14,opt,name=comment)" + Hash *string "PB(bytes,15,opt,name=hash)" + CommentHash []byte "PB(bytes,16,opt,name=comment_hash)" + TextureHash []byte "PB(bytes,17,opt,name=texture_hash)" + XXX_unrecognized []byte +} +func (this *UserState) Reset() { + *this = UserState{} +} + +type BanList struct { + Bans []*BanList_BanEntry "PB(bytes,1,rep,name=bans)" + Query *bool "PB(varint,2,opt,name=query,def=0)" + XXX_unrecognized []byte +} +func (this *BanList) Reset() { + *this = BanList{} +} +const Default_BanList_Query bool = false + +type BanList_BanEntry struct { + Address []byte "PB(bytes,1,req,name=address)" + Mask *uint32 "PB(varint,2,req,name=mask)" + Name *string "PB(bytes,3,opt,name=name)" + Hash *string "PB(bytes,4,opt,name=hash)" + Reason *string "PB(bytes,5,opt,name=reason)" + Start *string "PB(bytes,6,opt,name=start)" + Duration *uint32 "PB(varint,7,opt,name=duration)" + XXX_unrecognized []byte +} +func (this *BanList_BanEntry) Reset() { + *this = BanList_BanEntry{} +} + +type TextMessage struct { + Actor *uint32 "PB(varint,1,opt,name=actor)" + Session []uint32 "PB(varint,2,rep,name=session)" + ChannelId []uint32 "PB(varint,3,rep,name=channel_id)" + TreeId []uint32 "PB(varint,4,rep,name=tree_id)" + Message *string "PB(bytes,5,req,name=message)" + XXX_unrecognized []byte +} +func (this *TextMessage) Reset() { + *this = TextMessage{} +} + +type PermissionDenied struct { + Permission *uint32 "PB(varint,1,opt,name=permission)" + ChannelId *uint32 "PB(varint,2,opt,name=channel_id)" + Session *uint32 "PB(varint,3,opt,name=session)" + Reason *string "PB(bytes,4,opt,name=reason)" + Type *PermissionDenied_DenyType "PB(varint,5,opt,name=type,enum=mumbleproto.PermissionDenied_DenyType)" + Name *string "PB(bytes,6,opt,name=name)" + XXX_unrecognized []byte +} +func (this *PermissionDenied) Reset() { + *this = PermissionDenied{} +} + +type ACL struct { + ChannelId *uint32 "PB(varint,1,req,name=channel_id)" + InheritAcls *bool "PB(varint,2,opt,name=inherit_acls,def=1)" + Groups []*ACL_ChanGroup "PB(bytes,3,rep,name=groups)" + Acls []*ACL_ChanACL "PB(bytes,4,rep,name=acls)" + Query *bool "PB(varint,5,opt,name=query,def=0)" + XXX_unrecognized []byte +} +func (this *ACL) Reset() { + *this = ACL{} +} +const Default_ACL_InheritAcls bool = true +const Default_ACL_Query bool = false + +type ACL_ChanGroup struct { + Name *string "PB(bytes,1,req,name=name)" + Inherited *bool "PB(varint,2,opt,name=inherited,def=1)" + Inherit *bool "PB(varint,3,opt,name=inherit,def=1)" + Inheritable *bool "PB(varint,4,opt,name=inheritable,def=1)" + Add []uint32 "PB(varint,5,rep,name=add)" + Remove []uint32 "PB(varint,6,rep,name=remove)" + InheritedMembers []uint32 "PB(varint,7,rep,name=inherited_members)" + XXX_unrecognized []byte +} +func (this *ACL_ChanGroup) Reset() { + *this = ACL_ChanGroup{} +} +const Default_ACL_ChanGroup_Inherited bool = true +const Default_ACL_ChanGroup_Inherit bool = true +const Default_ACL_ChanGroup_Inheritable bool = true + +type ACL_ChanACL struct { + ApplyHere *bool "PB(varint,1,opt,name=apply_here,def=1)" + ApplySubs *bool "PB(varint,2,opt,name=apply_subs,def=1)" + Inherited *bool "PB(varint,3,opt,name=inherited,def=1)" + UserId *uint32 "PB(varint,4,opt,name=user_id)" + Group *string "PB(bytes,5,opt,name=group)" + Grant *uint32 "PB(varint,6,opt,name=grant)" + Deny *uint32 "PB(varint,7,opt,name=deny)" + XXX_unrecognized []byte +} +func (this *ACL_ChanACL) Reset() { + *this = ACL_ChanACL{} +} +const Default_ACL_ChanACL_ApplyHere bool = true +const Default_ACL_ChanACL_ApplySubs bool = true +const Default_ACL_ChanACL_Inherited bool = true + +type QueryUsers struct { + Ids []uint32 "PB(varint,1,rep,name=ids)" + Names []string "PB(bytes,2,rep,name=names)" + XXX_unrecognized []byte +} +func (this *QueryUsers) Reset() { + *this = QueryUsers{} +} + +type CryptSetup struct { + Key []byte "PB(bytes,1,opt,name=key)" + ClientNonce []byte "PB(bytes,2,opt,name=client_nonce)" + ServerNonce []byte "PB(bytes,3,opt,name=server_nonce)" + XXX_unrecognized []byte +} +func (this *CryptSetup) Reset() { + *this = CryptSetup{} +} + +type ContextActionAdd struct { + Action *string "PB(bytes,1,req,name=action)" + Text *string "PB(bytes,2,req,name=text)" + Context *uint32 "PB(varint,3,opt,name=context)" + XXX_unrecognized []byte +} +func (this *ContextActionAdd) Reset() { + *this = ContextActionAdd{} +} + +type ContextAction struct { + Session *uint32 "PB(varint,1,opt,name=session)" + ChannelId *uint32 "PB(varint,2,opt,name=channel_id)" + Action *string "PB(bytes,3,req,name=action)" + XXX_unrecognized []byte +} +func (this *ContextAction) Reset() { + *this = ContextAction{} +} + +type UserList struct { + Users []*UserList_User "PB(bytes,1,rep,name=users)" + XXX_unrecognized []byte +} +func (this *UserList) Reset() { + *this = UserList{} +} + +type UserList_User struct { + UserId *uint32 "PB(varint,1,req,name=user_id)" + Name *string "PB(bytes,2,opt,name=name)" + XXX_unrecognized []byte +} +func (this *UserList_User) Reset() { + *this = UserList_User{} +} + +type VoiceTarget struct { + Id *uint32 "PB(varint,1,opt,name=id)" + Targets []*VoiceTarget_Target "PB(bytes,2,rep,name=targets)" + XXX_unrecognized []byte +} +func (this *VoiceTarget) Reset() { + *this = VoiceTarget{} +} + +type VoiceTarget_Target struct { + Session []uint32 "PB(varint,1,rep,name=session)" + ChannelId *uint32 "PB(varint,2,opt,name=channel_id)" + Group *string "PB(bytes,3,opt,name=group)" + Links *bool "PB(varint,4,opt,name=links,def=0)" + Children *bool "PB(varint,5,opt,name=children,def=0)" + XXX_unrecognized []byte +} +func (this *VoiceTarget_Target) Reset() { + *this = VoiceTarget_Target{} +} +const Default_VoiceTarget_Target_Links bool = false +const Default_VoiceTarget_Target_Children bool = false + +type PermissionQuery struct { + ChannelId *uint32 "PB(varint,1,opt,name=channel_id)" + Permissions *uint32 "PB(varint,2,opt,name=permissions)" + Flush *bool "PB(varint,3,opt,name=flush,def=0)" + XXX_unrecognized []byte +} +func (this *PermissionQuery) Reset() { + *this = PermissionQuery{} +} +const Default_PermissionQuery_Flush bool = false + +type CodecVersion struct { + Alpha *int32 "PB(varint,1,req,name=alpha)" + Beta *int32 "PB(varint,2,req,name=beta)" + PreferAlpha *bool "PB(varint,3,req,name=prefer_alpha,def=1)" + XXX_unrecognized []byte +} +func (this *CodecVersion) Reset() { + *this = CodecVersion{} +} +const Default_CodecVersion_PreferAlpha bool = true + +type UserStats struct { + Session *uint32 "PB(varint,1,opt,name=session)" + StatsOnly *bool "PB(varint,2,opt,name=stats_only,def=0)" + Certificates [][]byte "PB(bytes,3,rep,name=certificates)" + FromClient *UserStats_Stats "PB(bytes,4,opt,name=from_client)" + FromServer *UserStats_Stats "PB(bytes,5,opt,name=from_server)" + UdpPackets *uint32 "PB(varint,6,opt,name=udp_packets)" + TcpPackets *uint32 "PB(varint,7,opt,name=tcp_packets)" + UdpPingAvg *float32 "PB(fixed32,8,opt,name=udp_ping_avg)" + UdpPingVar *float32 "PB(fixed32,9,opt,name=udp_ping_var)" + TcpPingAvg *float32 "PB(fixed32,10,opt,name=tcp_ping_avg)" + TcpPingVar *float32 "PB(fixed32,11,opt,name=tcp_ping_var)" + Version *Version "PB(bytes,12,opt,name=version)" + CeltVersions []int32 "PB(varint,13,rep,name=celt_versions)" + Address []byte "PB(bytes,14,opt,name=address)" + Bandwidth *uint32 "PB(varint,15,opt,name=bandwidth)" + Onlinesecs *uint32 "PB(varint,16,opt,name=onlinesecs)" + Idlesecs *uint32 "PB(varint,17,opt,name=idlesecs)" + StrongCertificate *bool "PB(varint,18,opt,name=strong_certificate,def=0)" + XXX_unrecognized []byte +} +func (this *UserStats) Reset() { + *this = UserStats{} +} +const Default_UserStats_StatsOnly bool = false +const Default_UserStats_StrongCertificate bool = false + +type UserStats_Stats struct { + Good *uint32 "PB(varint,1,opt,name=good)" + Late *uint32 "PB(varint,2,opt,name=late)" + Lost *uint32 "PB(varint,3,opt,name=lost)" + Resync *uint32 "PB(varint,4,opt,name=resync)" + XXX_unrecognized []byte +} +func (this *UserStats_Stats) Reset() { + *this = UserStats_Stats{} +} + +type RequestBlob struct { + SessionTexture []uint32 "PB(varint,1,rep,name=session_texture)" + SessionComment []uint32 "PB(varint,2,rep,name=session_comment)" + ChannelDescription []uint32 "PB(varint,3,rep,name=channel_description)" + XXX_unrecognized []byte +} +func (this *RequestBlob) Reset() { + *this = RequestBlob{} +} + +func init() { + proto.RegisterEnum("mumbleproto.Reject_RejectType", Reject_RejectType_name, Reject_RejectType_value) + proto.RegisterEnum("mumbleproto.PermissionDenied_DenyType", PermissionDenied_DenyType_name, PermissionDenied_DenyType_value) + proto.RegisterEnum("mumbleproto.ContextActionAdd_Context", ContextActionAdd_Context_name, ContextActionAdd_Context_value) +} diff --git a/pkg/mumbleproto/Mumble.proto b/pkg/mumbleproto/Mumble.proto new file mode 100644 index 0000000..fdfd3a8 --- /dev/null +++ b/pkg/mumbleproto/Mumble.proto @@ -0,0 +1,274 @@ +package mumbleproto; + +option optimize_for = SPEED; + +message Version { + optional uint32 version = 1; + optional string release = 2; + optional string os = 3; + optional string os_version = 4; +} + +message UDPTunnel { + required bytes packet = 1; +} + +message Authenticate { + optional string username = 1; + optional string password = 2; + repeated string tokens = 3; + repeated int32 celt_versions = 4; +} + +message Ping { + optional uint64 timestamp = 1; + optional uint32 good = 2; + optional uint32 late = 3; + optional uint32 lost = 4; + optional uint32 resync = 5; + optional uint32 udp_packets = 6; + optional uint32 tcp_packets = 7; + optional float udp_ping_avg = 8; + optional float udp_ping_var = 9; + optional float tcp_ping_avg = 10; + optional float tcp_ping_var = 11; +} + +message Reject { + enum RejectType { + None = 0; + WrongVersion = 1; + InvalidUsername = 2; + WrongUserPW = 3; + WrongServerPW = 4; + UsernameInUse = 5; + ServerFull = 6; + NoCertificate = 7; + } + optional RejectType type = 1; + optional string reason = 2; +} + +message ServerConfig { + optional uint32 max_bandwidth = 1; + optional string welcome_text = 2; + optional bool allow_html = 3; + optional uint32 message_length = 4; + optional uint32 image_message_length = 5; +} + +message ServerSync { + optional uint32 session = 1; + optional uint32 max_bandwidth = 2; + optional string welcome_text = 3; + optional uint64 permissions = 4; +} + +message ChannelRemove { + required uint32 channel_id = 1; +} + +message ChannelState { + optional uint32 channel_id = 1; + optional uint32 parent = 2; + optional string name = 3; + repeated uint32 links = 4; + optional string description = 5; + repeated uint32 links_add = 6; + repeated uint32 links_remove = 7; + optional bool temporary = 8 [default = false]; + optional int32 position = 9 [default = 0]; + optional bytes description_hash = 10; +} + +message UserRemove { + required uint32 session = 1; + optional uint32 actor = 2; + optional string reason = 3; + optional bool ban = 4; +} + +message UserState { + optional uint32 session = 1; + optional uint32 actor = 2; + optional string name = 3; + optional uint32 user_id = 4; + optional uint32 channel_id = 5; + optional bool mute = 6; + optional bool deaf = 7; + optional bool suppress = 8; + optional bool self_mute = 9; + optional bool self_deaf = 10; + optional bytes texture = 11; + optional bytes plugin_context = 12; + optional string plugin_identity = 13; + optional string comment = 14; + optional string hash = 15; + optional bytes comment_hash = 16; + optional bytes texture_hash = 17; +} + +message BanList { + message BanEntry { + required bytes address = 1; + required uint32 mask = 2; + optional string name = 3; + optional string hash = 4; + optional string reason = 5; + optional string start = 6; + optional uint32 duration = 7; + } + repeated BanEntry bans = 1; + optional bool query = 2 [default = false]; +} + +message TextMessage { + optional uint32 actor = 1; + repeated uint32 session = 2; + repeated uint32 channel_id = 3; + repeated uint32 tree_id = 4; + required string message = 5; +} + +message PermissionDenied { + enum DenyType { + Text = 0; + Permission = 1; + SuperUser = 2; + ChannelName = 3; + TextTooLong = 4; + H9K = 5; + TemporaryChannel = 6; + MissingCertificate = 7; + UserName = 8; + ChannelFull = 9; + } + optional uint32 permission = 1; + optional uint32 channel_id = 2; + optional uint32 session = 3; + optional string reason = 4; + optional DenyType type = 5; + optional string name = 6; +} + +message ACL { + message ChanGroup { + required string name = 1; + optional bool inherited = 2 [default = true]; + optional bool inherit = 3 [default = true]; + optional bool inheritable = 4 [default = true]; + repeated uint32 add = 5; + repeated uint32 remove = 6; + repeated uint32 inherited_members = 7; + } + message ChanACL { + optional bool apply_here = 1 [default = true]; + optional bool apply_subs = 2 [default = true]; + optional bool inherited = 3 [default = true]; + optional uint32 user_id = 4; + optional string group = 5; + optional uint32 grant = 6; + optional uint32 deny = 7; + } + required uint32 channel_id = 1; + optional bool inherit_acls = 2 [default = true]; + repeated ChanGroup groups = 3; + repeated ChanACL acls = 4; + optional bool query = 5 [default = false]; +} + +message QueryUsers { + repeated uint32 ids = 1; + repeated string names = 2; +} + +message CryptSetup { + optional bytes key = 1; + optional bytes client_nonce = 2; + optional bytes server_nonce = 3; +} + +message ContextActionAdd { + enum Context { + Server = 0x01; + Channel = 0x02; + User = 0x04; + } + required string action = 1; + required string text = 2; + optional uint32 context = 3; +} + +message ContextAction { + optional uint32 session = 1; + optional uint32 channel_id = 2; + required string action = 3; +} + +message UserList { + message User { + required uint32 user_id = 1; + optional string name = 2; + } + repeated User users = 1; +} + +message VoiceTarget { + message Target { + repeated uint32 session = 1; + optional uint32 channel_id = 2; + optional string group = 3; + optional bool links = 4 [default = false]; + optional bool children = 5 [default = false]; + } + optional uint32 id = 1; + repeated Target targets = 2; +} + +message PermissionQuery { + optional uint32 channel_id = 1; + optional uint32 permissions = 2; + optional bool flush = 3 [default = false]; +} + +message CodecVersion { + required int32 alpha = 1; + required int32 beta = 2; + required bool prefer_alpha = 3 [default = true]; +} + +message UserStats { + message Stats { + optional uint32 good = 1; + optional uint32 late = 2; + optional uint32 lost = 3; + optional uint32 resync = 4; + } + + optional uint32 session = 1; + optional bool stats_only = 2 [default = false]; + repeated bytes certificates = 3; + optional Stats from_client = 4; + optional Stats from_server = 5; + + optional uint32 udp_packets = 6; + optional uint32 tcp_packets = 7; + optional float udp_ping_avg = 8; + optional float udp_ping_var = 9; + optional float tcp_ping_avg = 10; + optional float tcp_ping_var = 11; + + optional Version version = 12; + repeated int32 celt_versions = 13; + optional bytes address = 14; + optional uint32 bandwidth = 15; + optional uint32 onlinesecs = 16; + optional uint32 idlesecs = 17; + optional bool strong_certificate = 18 [default = false]; +} + +message RequestBlob { + repeated uint32 session_texture = 1; + repeated uint32 session_comment = 2; + repeated uint32 channel_description = 3; +} diff --git a/pkg/packetdatastream/Makefile b/pkg/packetdatastream/Makefile new file mode 100644 index 0000000..f207321 --- /dev/null +++ b/pkg/packetdatastream/Makefile @@ -0,0 +1,6 @@ +include $(GOROOT)/src/Make.inc + +TARG = packetdatastream +GOFILES = packetdatastream.go + +include $(GOROOT)/src/Make.pkg diff --git a/pkg/packetdatastream/packetdatastream.go b/pkg/packetdatastream/packetdatastream.go new file mode 100644 index 0000000..ae5175b --- /dev/null +++ b/pkg/packetdatastream/packetdatastream.go @@ -0,0 +1,326 @@ +// Grumble - an implementation of Murmur in Go +// Copyright (c) 2010 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package packetdatastream + +import ( + "math" +) + +type PacketDataStream struct { + Buf []byte + offset int + maxsize int + overshoot int + ok bool +} + +func New(buf []byte) (pds *PacketDataStream) { + pds = new(PacketDataStream) + pds.Buf = buf + pds.maxsize = len(buf) + pds.ok = true + return +} + +func (pds *PacketDataStream) IsValid() bool { + return pds.ok +} + +func (pds *PacketDataStream) Skip(skip int) { + if pds.Left() >= skip { + pds.offset += skip + } else { + pds.ok = false + } +} + +// Returns number of bytes remaining in +// the buffer. +func (pds *PacketDataStream) Left() int { + return int(pds.maxsize - pds.offset) +} + +// Returns the size of the currently-assembled data +// stream +func (pds *PacketDataStream) Size() int { + return pds.offset +} + +// Get the next byte from the PacketDataStream as a uint64 +func (pds *PacketDataStream) next() (ret uint64) { + if pds.offset < pds.maxsize { + ret = uint64(pds.Buf[pds.offset]) + pds.offset += 1 + return + } else { + pds.ok = false + } + return 0 +} + +// Get the next byte from the PacketDataStream as a byte (uint8) +func (pds *PacketDataStream) Next8() (ret uint8) { + if pds.offset < pds.maxsize { + ret = uint8(pds.Buf[pds.offset]) + pds.offset += 1 + return + } else { + pds.ok = false + } + return 0 +} + +// Put a byte (represented in an uint64) into the +// PacketDataStream. +func (pds *PacketDataStream) append(val uint64) { + if (val > 0xff) { + pds.ok = false + return + } + + if pds.offset < pds.maxsize { + pds.Buf[pds.offset] = byte(val) + pds.offset += 1 + } else { + pds.ok = false + pds.overshoot++ + } + +} + +// Add a variably-sized integer to the PacketDataStream. +// The PacketDataStream will figure out the most efficient +// encoding based on the binary representation of the value. +func (pds *PacketDataStream) addVarint(val uint64) { + i := val + + if (i & 0x8000000000000000) != 0 && ^i < 0x100000000 { + // Signed number + i = ^i + if i <= 0x3 { + // Short for -1 to -4 + pds.append(0xfc | i) + } else { + pds.append(0xf8) + } + } + if i < 0x80 { + // Needs top bit clear + pds.append(i) + } else if i < 0x4000 { + // Needs two top bits clear + pds.append((i >> 8) | 0x80) + pds.append(i & 0xff) + } else if i < 0x10000000 { + // Needs three top bits clear + pds.append((i >> 16) | 0xc0) + pds.append((i >> 8) & 0xff) + pds.append(i & 0xff) + } else if i < 0x100000000 { + // Full 32 bit integer + pds.append(0xf0) + pds.append((i >> 24) & 0xff) + pds.append((i >> 16) & 0xff) + pds.append((i >> 8) & 0xff) + pds.append(i & 0xff) + } else { + // 64 bit val + pds.append(0xf4) + pds.append((i >> 56) & 0xff) + pds.append((i >> 48) & 0xff) + pds.append((i >> 40) & 0xff) + pds.append((i >> 32) & 0xff) + pds.append((i >> 24) & 0xff) + pds.append((i >> 16) & 0xff) + pds.append((i >> 8) & 0xff) + pds.append(i & 0xff) + } +} + +func (pds *PacketDataStream) getVarint() (i uint64) { + v := pds.next() + + if (v & 0x80) == 0x00 { + i = (v & 0x7f) + } else if (v & 0xc0) == 0x80 { + i = (v & 0x3f) << 8 | pds.next() + } else if (v & 0xf0) == 0xf0 { + switch v & 0xfc { + case 0xf0: + i = pds.next() << 24 | pds.next() << 16 | pds.next() << 8 | pds.next() + case 0xf4: + i = pds.next() << 56 | pds.next() << 48 | pds.next() << 40 | pds.next() << 32 | pds.next() << 24 | pds.next() << 16 | pds.next() << 8 | pds.next() + case 0xf8: + i = ^pds.getVarint() + case 0xfc: + i = ^(v & 0x03) + default: + pds.ok = false + i = 0 + } + } else if (v & 0xf0) == 0xe0 { + i = (v & 0x0f) << 24 | pds.next() << 16 | pds.next() << 8 | pds.next() + } else if (v & 0xe0) == 0xc0 { + i = (v & 0x1f) << 16 | pds.next() << 8 | pds.next() + } + + return +} + +// Read a uint64 from the PacketDataStream +func (pds *PacketDataStream) GetUint64() uint64 { + return pds.getVarint() +} + +// Write a uint64 to the PacketDataStream +func (pds *PacketDataStream) PutUint64(val uint64) { + pds.addVarint(val) +} + +// Read a uint32 from the PacketDataStream +func (pds *PacketDataStream) GetUint32() uint32 { + return uint32(pds.getVarint()) +} + +// Write a uint32 to the PacketDataStream +func (pds *PacketDataStream) PutUint32(val uint32) { + pds.addVarint(uint64(val)) +} + +// Read a uint16 from the PacketDataStream +func (pds *PacketDataStream) GetUint16() uint16 { + return uint16(pds.getVarint()) +} + +// Write a uint16 to the PacketDataStream +func (pds *PacketDataStream) PutUint16(val uint16) { + pds.addVarint(uint64(val)) +} + +// Read a uint8 from the PacketDataStream +func (pds *PacketDataStream) GetUint8() uint8 { + varint := pds.getVarint() + return uint8(varint) +} + +// Write a uint8 to the PacketDataStream +func (pds *PacketDataStream) PutUint8(val uint8) { + pds.addVarint(uint64(val)) +} + +// Read a int64 from the PacketDataStream +func (pds *PacketDataStream) GetInt64() int64 { + return int64(pds.getVarint()) +} + +// Write a int64 to the PacketDataStream +func (pds *PacketDataStream) PutInt64(val int64) { + pds.addVarint(uint64(val)) +} + +// Read a int32 from the PacketDataStream +func (pds *PacketDataStream) GetInt32() int32 { + return int32(pds.getVarint()) +} + +// Write a int32 to the PacketDataStream +func (pds *PacketDataStream) PutInt32(val int32) { + pds.addVarint(uint64(val)) +} + +// Read a int16 from the PacketDataStream +func (pds *PacketDataStream) GetInt16() int16 { + return int16(pds.getVarint()) +} + +// Write a int16 to the PacketDataStream +func (pds *PacketDataStream) PutInt16(val int16) { + pds.addVarint(uint64(val)) +} + +// Read a int8 from the PacketDataStream +func (pds *PacketDataStream) GetInt8() int8 { + return int8(pds.getVarint()) +} + +// Write a int8 to the PacketDataStream +func (pds *PacketDataStream) PutInt8(val int8) { + pds.addVarint(uint64(val)) +} + +// Read a float32 from the PacketDataStream +func (pds *PacketDataStream) GetFloat32() float32 { + if pds.Left() < 4 { + pds.ok = false + return 0 + } + + var val uint32 + + val = uint32(pds.Next8()) << 24 | uint32(pds.Next8()) << 16 | uint32(pds.Next8()) << 8 | uint32(pds.Next8()) + return math.Float32frombits(val) +} + +// Write a float32 to the PacketDataStream +func (pds *PacketDataStream) PutFloat32(val float32) { + bits := math.Float32bits(val) + pds.append(uint64((bits >> 24) & 0xff)) + pds.append(uint64((bits >> 16) & 0xff)) + pds.append(uint64((bits >> 8) & 0xff)) + pds.append(uint64(bits & 0xff)) +} + +// Read a float64 from the PacketDataStream. +func (pds *PacketDataStream) GetFloat64() float64 { + if pds.Left() < 8 { + pds.ok = false + return 0 + } + + var val uint64 + val = uint64(pds.Next8()) << 56 | uint64(pds.Next8()) << 48 | uint64(pds.Next8()) << 40 | uint64(pds.Next8()) << 32 | uint64(pds.Next8()) << 24 | uint64(pds.Next8()) << 16 | uint64(pds.Next8()) << 8 | uint64(pds.Next8()) + + return math.Float64frombits(val) +} + +// Write a float64 to the PacketDataStream +func (pds *PacketDataStream) PutFloat64(val float64) { + bits := math.Float64bits(val) + pds.append((bits >> 56) & 0xff) + pds.append((bits >> 48) & 0xff) + pds.append((bits >> 40) & 0xff) + pds.append((bits >> 32) & 0xff) + pds.append((bits >> 24) & 0xff) + pds.append((bits >> 16) & 0xff) + pds.append((bits >> 8) & 0xff) + pds.append(bits & 0xff) +} + +// Copy a buffer out of the PacketDataStream into dst. +func (pds *PacketDataStream) CopyBytes(dst []byte) { + if pds.Left() >= len(dst) { + if copy(dst, pds.Buf[pds.offset:pds.offset+len(dst)]) != len(dst) { + pds.ok = false + } + } else { + pds.ok = false + } +} + +// Put a buffer src into the PacketDataStream at the +// current offset. +func (pds *PacketDataStream) PutBytes(src []byte) { + if pds.Left() >= len(src) { + if copy(pds.Buf[pds.offset:pds.offset+len(src)], src) != len(src) { + pds.ok = false + } else { + pds.offset += len(src) + } + } else { + pds.overshoot += len(src) - pds.Left() + pds.ok = false + } +} diff --git a/pkg/packetdatastream/packetdatastream_test.go b/pkg/packetdatastream/packetdatastream_test.go new file mode 100644 index 0000000..b978c9f --- /dev/null +++ b/pkg/packetdatastream/packetdatastream_test.go @@ -0,0 +1,144 @@ +package packetdatastream + +import ( + "testing" + "math" + "crypto/rand" +) + +func TestSelfUint8(t *testing.T) { + buf := make([]byte, 500) + pds := New(buf) + + for i := uint8(0); i < 0xff; i++ { + pds.PutUint8(i) + if !pds.IsValid() { + t.Errorf("Invalid PDS") + return + } + } + + pds2 := New(pds.Buf) + for i := uint8(0); i < 0xff; i++ { + val := pds2.GetUint8() + if val != i { + t.Errorf("Mismatch (read: %v, expected: %v)", val, i) + return + } + } +} + +func TestSelfUint64(t *testing.T) { + buf := make([]byte, 500) + pds := New(buf) + + for i := uint64(1 << 54); i < (uint64(1 << 54)+10); i++ { + pds.PutUint64(i) + if !pds.IsValid() { + t.Errorf("Invalid PDS") + return + } + } + + pds2 := New(buf) + for i := uint64(1 << 54); i < (uint64(1 << 54)+10); i++ { + val := pds2.GetUint64() + if !pds.IsValid() { + t.Errorf("Invalid PDS") + } + if val != i { + t.Errorf("Mismatch (read: %v, expected: %v)", val, i) + return + } + } +} + +func TestSelfMumbleVoicePacket(t *testing.T) { + buf := make([]byte, 500) + pds := New(buf) + data := make([]byte, 54) + + rand.Read(data) + + pds.PutUint32(1) + pds.PutBytes(data) + + pds2 := New(buf) + if pds2.GetUint32() != 1 { + t.Errorf("Session mismatch") + } + + outbuf := make([]byte, 54) + pds2.CopyBytes(outbuf) + + if !pds.IsValid() { + t.Errorf("Invalid PDS") + return + } + + for i := 0; i < 54; i++ { + if outbuf[i] != data[i] { + t.Errorf("Voice data mismatch (got %v, expected %v)", outbuf[i], data[i]) + return + } + } +} + +func TestSelfFloat64(t *testing.T) { + buf := make([]byte, 500) + pds := New(buf) + pds2 := New(buf) + + pds.PutFloat64(math.Pi) + pi := pds2.GetFloat64() + + if !pds.IsValid() || !pds2.IsValid() { + t.Errorf("Invalid PDS") + return + } + + if pi != float64(math.Pi) { + t.Errorf("Unexpected result. Got %v, expected %v", pi, float64(math.Pi)) + return + } +} + +func TestSelfFloat32(t *testing.T) { + buf := make([]byte, 500) + pds := New(buf) + pds2 := New(buf) + + pds.PutFloat32(math.E) + e := pds2.GetFloat32() + + if !pds.IsValid() || !pds2.IsValid() { + t.Errorf("Invalid PDS") + } + + if e != float32(math.E) { + t.Errorf("Unexpected result. Got %v, expected %v", e, float32(math.E)) + } +} + +func TestSelfBytes(t *testing.T) { + msg := [15]byte{ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, } + buf := make([]byte, 500) + pds := New(buf) + pds2 := New(buf) + + pds.PutBytes(msg[0:]) + out := make([]byte, 15) + pds2.CopyBytes(out) + + if !pds.IsValid() || !pds.IsValid() { + t.Errorf("Invalid PDS") + return + } + + for i := 0; i < 15; i++ { + if msg[i] != out[i] { + t.Errorf("Mismatch at index %v. Got %v, expected %v", i, out[i], msg[i]) + return + } + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..a73a2ce --- /dev/null +++ b/server.go @@ -0,0 +1,588 @@ +// Copyright (c) 2010 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package main + +import ( + "log" + "crypto/tls" + "os" + "net" + "bufio" + "bytes" + "encoding/binary" + "container/list" + "sync" + "goprotobuf.googlecode.com/hg/proto" + "mumbleproto" + "cryptstate" +) + +// The default port a Murmur server listens on +const DefaultPort = 64738 +const UDPPacketSize = 1024 + +const CeltCompatBitstream = -2147483638 + +// Client connection states +const ( + StateClientConnected = iota + StateServerSentVersion + StateClientSentVersion + StateClientAuthenticated + StateClientDead +) + +// A Murmur server instance +type Server struct { + listener tls.Listener + address string + port int + udpconn *net.UDPConn + + incoming chan *Message + outgoing chan *Message + + udpsend chan *Message + + // Config-related + MaxUsers int + MaxBandwidth uint32 + + session uint32 + + // A list of all connected clients + cmutex *sync.RWMutex + clients *list.List + + // Codec information + AlphaCodec int32 + BetaCodec int32 + PreferAlphaCodec bool + + root *Channel +} + +// A Mumble channel +type Channel struct { + Id int + Name string + Description string + Temporary bool + Position int + Channels *list.List +} + +// Allocate a new Murmur instance +func NewServer(addr string, port int) (s *Server, err os.Error) { + s = new(Server) + + s.address = addr + s.port = port + + // Create the list of connected clients + s.cmutex = new(sync.RWMutex) + s.clients = list.New() + + s.outgoing = make(chan *Message) + s.incoming = make(chan *Message) + s.udpsend = make(chan *Message) + + s.MaxBandwidth = 300000 + s.MaxUsers = 10 + + // Allocate the root channel + + s.root = &Channel{ + Id: 0, + Name: "Root", + } + + go s.handler() + go s.multiplexer() + + return +} + +// Called by the server to initiate a new client connection. +func (server *Server) NewClient(conn net.Conn) (err os.Error) { + client := new(ClientConnection) + + // Get the address of the connected client + if addr := conn.RemoteAddr(); addr != nil { + client.tcpaddr = addr.(*net.TCPAddr) + log.Stdoutf("client connected: %s", client.tcpaddr.String()) + } + + client.server = server + client.conn = conn + client.reader = bufio.NewReader(client.conn) + client.writer = bufio.NewWriter(client.conn) + client.state = StateClientConnected + + client.msgchan = make(chan *Message) + client.udprecv = make(chan []byte) + + // New client connection.... + server.session += 1 + client.Session = server.session + + // Add it to the list of connected clients + server.cmutex.Lock() + server.clients.PushBack(client) + server.cmutex.Unlock() + + go client.receiver() + go client.udpreceiver() + go client.sender() + + return +} + +// Lookup a client by it's session id. Optimize this by using a map. +func (server *Server) getClientConnection(session uint32) (client *ClientConnection) { + server.cmutex.RLock() + defer server.cmutex.RUnlock() + + for x := range server.clients.Iter() { + user := x.(*ClientConnection) + if user.Session == session { + return user + } + } + + return nil +} + +// This is the synchronous request handler for all incoming messages. +func (server *Server) handler() { + for { + msg := <-server.incoming + client := msg.client + + if client.state == StateClientAuthenticated { + server.handleIncomingMessage(client, msg) + } else if client.state == StateClientSentVersion { + server.handleAuthenticate(client, msg) + } + } +} + +func (server *Server) handleAuthenticate(client *ClientConnection, msg *Message) { + // Is this message not an authenticate message? If not, discard it... + if msg.kind != MessageAuthenticate { + client.Panic("Unexpected message. Expected Authenticate.") + return + } + + auth := &mumbleproto.Authenticate{} + err := proto.Unmarshal(msg.buf, auth) + if err != nil { + client.Panic("Unable to unmarshal Authenticate message.") + return + } + + // Did we get a username? + if auth.Username == nil { + client.Panic("No username in auth message...") + return + } + + client.Username = *auth.Username + + // Setup the cryptstate for the client. + client.crypt, err = cryptstate.New() + if err != nil { + client.Panic(err.String()) + return + } + err = client.crypt.GenerateKey() + if err != nil { + client.Panic(err.String()) + return + } + + // Send CryptState information to the client so it can establish an UDP connection + // (if it wishes)... + err = client.sendProtoMessage(MessageCryptSetup, &mumbleproto.CryptSetup{ + Key: client.crypt.RawKey[0:], + ClientNonce: client.crypt.DecryptIV[0:], + ServerNonce: client.crypt.EncryptIV[0:], + }) + if err != nil { + client.Panic(err.String()) + } + + client.codecs = auth.CeltVersions + server.updateCodecVersions() + + client.sendChannelList() + + client.state = StateClientAuthenticated + + // Broadcast that we, the client, entered a channel... + err = server.broadcastProtoMessage(MessageUserState, &mumbleproto.UserState{ + Session: proto.Uint32(client.Session), + Name: proto.String(client.Username), + ChannelId: proto.Uint32(0), + }) + if err != nil { + client.Panic(err.String()) + } + + server.sendUserList(client) + + err = client.sendProtoMessage(MessageServerSync, &mumbleproto.ServerSync{ + Session: proto.Uint32(client.Session), + MaxBandwidth: proto.Uint32(server.MaxBandwidth), + }) + if err != nil { + client.Panic(err.String()) + return + } + + err = client.sendProtoMessage(MessageServerConfig, &mumbleproto.ServerConfig{ + AllowHtml: proto.Bool(true), + MessageLength: proto.Uint32(1000), + ImageMessageLength: proto.Uint32(1000), + }) + if err != nil { + client.Panic(err.String()) + return + } + + client.state = StateClientAuthenticated +} + +func (server *Server) updateCodecVersions() { + codecusers := map[int32]int{} + var winner int32 + var count int + + server.cmutex.RLock() + defer server.cmutex.RUnlock() + + for x := range server.clients.Iter() { + client := x.(*ClientConnection) + for i := 0; i < len(client.codecs); i++ { + codecusers[client.codecs[i]] += 1 + } + } + + // result? + for codec, users := range codecusers { + if users > count { + count = users + winner = codec + } + } + + var current int32 + if server.PreferAlphaCodec { + current = server.AlphaCodec + } else { + current = server.BetaCodec + } + + if winner == current { + return + } + + if winner == CeltCompatBitstream { + server.PreferAlphaCodec = true + } else { + server.PreferAlphaCodec = !server.PreferAlphaCodec + } + + if (server.PreferAlphaCodec) { + server.AlphaCodec = winner + } else { + server.BetaCodec = winner + } + + err := server.broadcastProtoMessage(MessageCodecVersion, &mumbleproto.CodecVersion{ + Alpha: proto.Int32(server.AlphaCodec), + Beta: proto.Int32(server.BetaCodec), + PreferAlpha: proto.Bool(server.PreferAlphaCodec), + }) + if err != nil { + log.Stdoutf("Unable to broadcast..") + return + } + + log.Stdoutf("CELT codec switch %v %v (PreferAlpha %v)", server.AlphaCodec, server.BetaCodec, server.PreferAlphaCodec) + + return +} + +func (server *Server) sendUserList(client *ClientConnection) { + server.cmutex.RLock() + defer server.cmutex.RUnlock() + + for x := range server.clients.Iter() { + user := x.(*ClientConnection) + if user.state != StateClientAuthenticated { + continue + } + + err := client.sendProtoMessage(MessageUserState, &mumbleproto.UserState{ + Session: proto.Uint32(user.Session), + Name: proto.String(user.Username), + ChannelId: proto.Uint32(0), + }) + + log.Stdoutf("Sent One User...") + + if err != nil { + log.Stdoutf("unable to send!") + continue + } + } + +} + +func (server *Server) broadcastProtoMessage(kind uint16, msg interface{}) (err os.Error) { + server.cmutex.RLock() + defer server.cmutex.RUnlock() + + for x := range server.clients.Iter() { + client := x.(*ClientConnection) + if client.state != StateClientAuthenticated { + continue + } + err :=client.sendProtoMessage(kind, msg) + if err != nil { + return + } + } + + return +} + +func (server *Server) handleIncomingMessage(client *ClientConnection, msg *Message) { + log.Stdoutf("Handle Incoming Message") + switch msg.kind { + case MessagePing: + server.handlePingMessage(msg.client, msg) + case MessageChannelRemove: + server.handlePingMessage(msg.client, msg) + case MessageChannelState: + server.handleChannelStateMessage(msg.client, msg) + case MessageUserState: + server.handleUserStateMessage(msg.client, msg) + case MessageUserRemove: + server.handleUserRemoveMessage(msg.client, msg) + case MessageBanList: + server.handleBanListMessage(msg.client, msg) + case MessageTextMessage: + server.handleTextMessage(msg.client, msg) + case MessageACL: + server.handleAclMessage(msg.client, msg) + case MessageQueryUsers: + server.handleQueryUsers(msg.client, msg) + case MessageCryptSetup: + server.handleCryptSetup(msg.client, msg) + case MessageContextActionAdd: + log.Stdoutf("MessageContextActionAdd from client") + case MessageContextAction: + log.Stdoutf("MessageContextAction from client") + case MessageUserList: + log.Stdoutf("MessageUserList from client") + case MessageVoiceTarget: + log.Stdoutf("MessageVoiceTarget from client") + case MessagePermissionQuery: + log.Stdoutf("MessagePermissionQuery from client") + case MessageCodecVersion: + log.Stdoutf("MessageCodecVersion from client") + case MessageUserStats: + server.handleUserStatsMessage(msg.client, msg) + case MessageRequestBlob: + log.Stdoutf("MessageRequestBlob from client") + case MessageServerConfig: + log.Stdoutf("MessageServerConfig from client") + } +} + +func (server *Server) multiplexer() { + for { + _ = <-server.outgoing + log.Stdoutf("recvd message to multiplex") + } +} + +func (s *Server) SetupUDP() (err os.Error) { + addr := &net.UDPAddr{ + Port: s.port, + } + s.udpconn, err = net.ListenUDP("udp", addr) + if err != nil { + return + } + + return +} + +func (s *Server) SendUDP() { + for { + msg := <-s.udpsend + if msg.client != nil { + // These are to be crypted... + crypted := make([]byte, len(msg.buf)+4) + msg.client.crypt.Encrypt(msg.buf, crypted) + s.udpconn.WriteTo(crypted, msg.client.udpaddr) + } else if msg.address != nil { + s.udpconn.WriteTo(msg.buf, msg.address) + } else { + // Skipping + } + } +} + +// Listen for and handle UDP packets. +func (server *Server) ListenUDP() { + buf := make([]byte, UDPPacketSize) + for { + nread, remote, err := server.udpconn.ReadFrom(buf) + if err != nil { + // Not much to do here. This is bad, of course. Should we panic this server instance? + continue + } + + udpaddr, ok := remote.(*net.UDPAddr) + if !ok { + log.Stdoutf("No UDPAddr in read packet. Disabling UDP. (Windows?)") + return + } + + // Length 12 is for ping datagrams from the ConnectDialog. + if nread == 12 { + readbuf := bytes.NewBuffer(buf) + var ( + tmp32 uint32 + rand uint64 + ) + _ = binary.Read(readbuf, binary.BigEndian, &tmp32) + _ = binary.Read(readbuf, binary.BigEndian, &rand) + + buffer := bytes.NewBuffer(make([]byte, 0, 24)) + _ = binary.Write(buffer, binary.BigEndian, uint32((1<<16)|(2<<8)|2)) + _ = binary.Write(buffer, binary.BigEndian, rand) + _ = binary.Write(buffer, binary.BigEndian, uint32(server.clients.Len())) + _ = binary.Write(buffer, binary.BigEndian, uint32(server.MaxUsers)) + _ = binary.Write(buffer, binary.BigEndian, uint32(server.MaxBandwidth)) + + server.udpsend <- &Message{ + buf: buffer.Bytes(), + address: udpaddr, + } + } else { + var match *ClientConnection + plain := make([]byte, nread-4) + decrypted := false + + // First, check if any of our clients match the net.UDPAddr... + server.cmutex.RLock() + for x := range server.clients.Iter() { + client := x.(*ClientConnection) + if client.udpaddr.String() == udpaddr.String() { + match = client + } + } + server.cmutex.RUnlock() + + // No matching client found. We must try to decrypt... + if match == nil { + server.cmutex.RLock() + for x := range server.clients.Iter() { + client := x.(*ClientConnection) + + // Try to decrypt. + err = client.crypt.Decrypt(buf[0:nread], plain[0:]) + if err != nil { + // Decryption failed. Try another client... + continue + } + + // Decryption succeeded. + decrypted = true + + // If we were able to successfully decrpyt, add + // the UDPAddr to the ClientConnection struct. + log.Stdoutf("Client UDP connection established.") + client.udpaddr = remote.(*net.UDPAddr) + match = client + + break + } + server.cmutex.RUnlock() + } + + // We were not able to find a client that could decrypt the incoming + // packet. Log it? + if match == nil { + continue + } + + if !decrypted { + err = match.crypt.Decrypt(buf[0:nread], plain[0:]) + if err != nil { + log.Stdoutf("Unable to decrypt from client..") + } + } + + match.udp = true + match.udprecv <- plain + } + } +} + +// The accept loop of the server. +func (s *Server) ListenAndMurmur() { + + // Setup our UDP listener and spawn our reader and writer goroutines + s.SetupUDP() + go s.ListenUDP() + go s.SendUDP() + + // Create a new listening TLS socket. + l := NewTLSListener(s.port) + if l == nil { + log.Stderrf("Unable to create TLS listener") + return + } + + log.Stderrf("Created new Murmur instance on port %v", s.port) + + // The main accept loop. Basically, we block + // until we get a new client connection, and + // when we do get a new connection, we spawn + // a new Go-routine to handle the client. + for { + + // New client connected + conn, err := l.Accept() + if err != nil { + log.Stderrf("unable to accept()") + } + + tls, ok := conn.(*tls.Conn) + if !ok { + log.Stderrf("Not tls :(") + } + + // Force the TLS handshake to get going. We'd like + // this to happen as soon as possible, so we can get + // at client certificates sooner. + tls.Handshake() + + // Create a new client connection from our *tls.Conn + // which wraps net.TCPConn. + err = s.NewClient(conn) + if err != nil { + log.Stderrf("Unable to start new client") + } + + log.Stdoutf("num clients = %v", s.clients.Len()) + } +} diff --git a/tlsserver.go b/tlsserver.go new file mode 100644 index 0000000..2e05796 --- /dev/null +++ b/tlsserver.go @@ -0,0 +1,88 @@ +package main + +import ( + "log" + "time" + "net" + "crypto/tls" + "crypto/rand" + "encoding/pem" + "crypto/x509" + "io/ioutil" +) + +func NewTLSListener(port int) (rl *tls.Listener) { + rl = nil + + // Load the certificate + pemBytes, err := ioutil.ReadFile("grumble.crt") + if err != nil { + log.Stderr("Failed to read server.crt:", err) + return + } + + // Decode the certificate + cert, _ := pem.Decode(pemBytes) + if cert == nil { + log.Stderr("Failed to parse server.crt") + return + } + + // Load the private key + keyBytes, err := ioutil.ReadFile("grumble.key") + if err != nil { + log.Stderr("Failed to read server.key.insecure: %s", err) + return + } + + // Decode the private key + pkPEM, _ := pem.Decode(keyBytes) + if pkPEM == nil { + log.Stderrf("Failed to parse server.key.insecure: %s", err) + return + } + + // Determine if we are an RSA private key + if pkPEM.Type != "RSA PRIVATE KEY" { + log.Stderrf("server.key.insecure is not an RSA private key. Found '%s'", + pkPEM.Type) + return + } + + // Check if the PEM file has headers. This will typically + // mean that it requires a passphrase to decrypt it. For now, + // let us just assume that people will decrypt them for us, so + // we can use them without too much work. + if len(pkPEM.Headers) != 0 { + log.Stderr("server.key.insecure has headers and is probably encrypted.") + return + } + + // Parse the PKCS12 private key. + priv, err := x509.ParsePKCS1PrivateKey(pkPEM.Bytes) + if err != nil { + log.Stderrf("Invalid key in server.key.insecure: %s", err) + return + } + + // Create a new TLS config. + config := new(tls.Config) + config.Rand = rand.Reader + config.Time = time.Seconds + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0].Certificate = [][]byte{cert.Bytes} + config.Certificates[0].PrivateKey = priv + + l, err := net.ListenTCP("tcp", &net.TCPAddr{ + net.ParseIP("0.0.0.0"), + port, + }) + if err != nil { + log.Stderrf("Cannot bind: %s\n", err) + return + } + + rl = tls.NewListener(l, config) + + return +}