From 73ab596ae62a5333d2c7ad4393d417f52dd8c58f Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Mon, 20 Sep 2010 15:14:22 +0200 Subject: [PATCH] Throw the code out there. :) --- AUTHORS | 1 + LICENSE | 32 + Makefile | 40 ++ README | 5 + client.go | 290 +++++++++ grumble.crt | 9 + grumble.go | 43 ++ grumble.key | 10 + message.go | 180 ++++++ pkg/cryptstate/Makefile | 6 + pkg/cryptstate/cryptstate.go | 360 +++++++++++ pkg/cryptstate/cryptstate_test.go | 147 +++++ pkg/cryptstate/testgen/CryptState.cpp | 322 ++++++++++ pkg/cryptstate/testgen/CryptState.h | 77 +++ pkg/cryptstate/testgen/Makefile | 9 + pkg/cryptstate/testgen/README | 1 + pkg/cryptstate/testgen/test.cpp | 28 + pkg/cryptstate/testgen/test2.cpp | 36 ++ pkg/cryptstate/testgen/test3.cpp | 34 + pkg/mumbleproto/Makefile | 7 + pkg/mumbleproto/Mumble.pb.go | 502 +++++++++++++++ pkg/mumbleproto/Mumble.proto | 274 ++++++++ pkg/packetdatastream/Makefile | 6 + pkg/packetdatastream/packetdatastream.go | 326 ++++++++++ pkg/packetdatastream/packetdatastream_test.go | 144 +++++ server.go | 588 ++++++++++++++++++ tlsserver.go | 88 +++ 27 files changed, 3565 insertions(+) create mode 100644 AUTHORS create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 client.go create mode 100644 grumble.crt create mode 100644 grumble.go create mode 100644 grumble.key create mode 100644 message.go create mode 100644 pkg/cryptstate/Makefile create mode 100644 pkg/cryptstate/cryptstate.go create mode 100644 pkg/cryptstate/cryptstate_test.go create mode 100644 pkg/cryptstate/testgen/CryptState.cpp create mode 100644 pkg/cryptstate/testgen/CryptState.h create mode 100644 pkg/cryptstate/testgen/Makefile create mode 100644 pkg/cryptstate/testgen/README create mode 100644 pkg/cryptstate/testgen/test.cpp create mode 100644 pkg/cryptstate/testgen/test2.cpp create mode 100644 pkg/cryptstate/testgen/test3.cpp create mode 100644 pkg/mumbleproto/Makefile create mode 100644 pkg/mumbleproto/Mumble.pb.go create mode 100644 pkg/mumbleproto/Mumble.proto create mode 100644 pkg/packetdatastream/Makefile create mode 100644 pkg/packetdatastream/packetdatastream.go create mode 100644 pkg/packetdatastream/packetdatastream_test.go create mode 100644 server.go create mode 100644 tlsserver.go 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 +}