Refactor GoRicochet
* New Service Interface * Server functionality * 90% Code Coverage * Regression Testing of Protocol Compliance
This commit is contained in:
		
							parent
							
								
									93754f2916
								
							
						
					
					
						commit
						bfe5b74364
					
				| 
						 | 
				
			
			@ -1 +1,2 @@
 | 
			
		|||
go-ricochet-coverage.out
 | 
			
		||||
*~
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,3 @@
 | 
			
		|||
language: go
 | 
			
		||||
 | 
			
		||||
script: go get github.com/golang/protobuf/proto && go get h12.me/socks && go test -cover
 | 
			
		||||
script: go get github.com/golang/protobuf/proto && go get h12.me/socks && go test -v -cover
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
# How To Contribute to GoRicochet
 | 
			
		||||
 | 
			
		||||
This document highlights some useful tips for contributing to this project. Feel
 | 
			
		||||
free to submit pull requests to update this document as needed.
 | 
			
		||||
 | 
			
		||||
# Requesting a New Feature
 | 
			
		||||
 | 
			
		||||
You can request a new feature by submitting an issue to the [Github Repository](https://github.com/s-rah/go-ricochet).
 | 
			
		||||
 | 
			
		||||
# Writing New Code
 | 
			
		||||
 | 
			
		||||
So you want to dig in? Awesome! Here are a few steps to consider:
 | 
			
		||||
 | 
			
		||||
## 1. Before working on a Change
 | 
			
		||||
 | 
			
		||||
First, check to see if your proposed change is already in active development. This
 | 
			
		||||
can be done by searching issues and pull requests on Github.
 | 
			
		||||
 | 
			
		||||
If no issue exists for your change then please open one. You **do not** need to wait
 | 
			
		||||
for the maintainers or the community to discuss your change before starting work but, for
 | 
			
		||||
larger changes, we suggest attaching some design notes and requesting feedback from the 
 | 
			
		||||
maintainers to avoid the change being rejected after all that hard work!
 | 
			
		||||
 | 
			
		||||
## 2. Make the Change
 | 
			
		||||
 | 
			
		||||
Crack open the editor of your choice and start programming. As you go along ensure
 | 
			
		||||
to run `go test github.com/s-rah/go-ricochet` to ensure that everything is working!
 | 
			
		||||
 | 
			
		||||
Please write tests for any new functionality. As a rule, aim for >80% code coverage. You
 | 
			
		||||
can check coverage `with go test --cover github.com/s-rah/go-ricochet`
 | 
			
		||||
 | 
			
		||||
## 3. Before Submitting a Pull Request
 | 
			
		||||
 | 
			
		||||
Format your code (the path might be slightly different):
 | 
			
		||||
 | 
			
		||||
* `gofmt -l=true -s -w src/github.com/s-rah/go-ricochet/`
 | 
			
		||||
 | 
			
		||||
Run the following commands, and address any issues which arise:
 | 
			
		||||
 | 
			
		||||
* `go vet github.com/s-rah/go-ricochet/...`
 | 
			
		||||
* `golint github.com/s-rah/go-ricochet`
 | 
			
		||||
* `golint github.com/s-rah/go-ricochet/utils`
 | 
			
		||||
 | 
			
		||||
## 4. Code Review
 | 
			
		||||
 | 
			
		||||
Once you submit the pull request it will be reviewed by one of the maintainers 
 | 
			
		||||
of the project. At this point there are 3 possible paths:
 | 
			
		||||
 | 
			
		||||
1. Your change is accepted!
 | 
			
		||||
2. Your change is acknowledged as necessary, but requires some rework before being accepted.
 | 
			
		||||
3. Your change is rejected - this can happen for a number of reasons. To minimize the chances of this happening, please see step #1
 | 
			
		||||
							
								
								
									
										51
									
								
								LICENSE
								
								
								
								
							
							
						
						
									
										51
									
								
								LICENSE
								
								
								
								
							| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
All code in this project, unless explicitly stated elsewhere in this document is
 | 
			
		||||
covered under the following license.
 | 
			
		||||
 | 
			
		||||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2015 Sarah Jamie Lewis
 | 
			
		||||
Copyright (c) 2016 Sarah Jamie Lewis
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
| 
						 | 
				
			
			@ -19,3 +22,49 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
Autogenerated protobuf code was generated using the proto file from Ricochet. 
 | 
			
		||||
They are covered under the following license. 
 | 
			
		||||
 | 
			
		||||
              Ricochet - https://ricochet.im/
 | 
			
		||||
Copyright (C) 2014, John Brooks <john.brooks@dereferenced.net>
 | 
			
		||||
 | 
			
		||||
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 names of the copyright owners 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 COPYRIGHT
 | 
			
		||||
OWNER 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.
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
The go-ricochet logo is based on an image by Olga Shalakhina 
 | 
			
		||||
<osshalakhina@gmail.com> who in turn modified the original gopher images made by 
 | 
			
		||||
Renee French.  The image is licensed under Creative Commons 3.0 Attributions.
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
all:
 | 
			
		||||
	go install github.com/go-ricochet
 | 
			
		||||
 | 
			
		||||
test:
 | 
			
		||||
	go test -v github.com/s-rah/go-ricochet/...
 | 
			
		||||
 | 
			
		||||
cover:
 | 
			
		||||
	go test github.com/s-rah/go-ricochet/... -cover
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										68
									
								
								README.md
								
								
								
								
							
							
						
						
									
										68
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -1,19 +1,63 @@
 | 
			
		|||
# GoRicochet [](https://travis-ci.org/s-rah/go-ricochet)
 | 
			
		||||
 | 
			
		||||
GoRicochet is an implementation of the [Ricochet Protocol](https://ricochet.im)
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
GoRicochet is an experimental implementation of the [Ricochet Protocol](https://ricochet.im)
 | 
			
		||||
in Go.
 | 
			
		||||
 | 
			
		||||
**NOTE:** This project is in the very early stages and is in no way meant to be
 | 
			
		||||
a replacement for the core Ricochet implementation. This version exists for
 | 
			
		||||
the purpose of writing testing tools for Ricochet in Go.
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
## Current Features
 | 
			
		||||
* A simple API that you can use to build Automated Ricochet Applications
 | 
			
		||||
* A suite of regression tests that test protocol compliance.
 | 
			
		||||
 | 
			
		||||
* Connect to a Local Ricochet Client
 | 
			
		||||
* Issue an Authentication Request
 | 
			
		||||
* Issue a Contact Request
 | 
			
		||||
* Open a new Channel
 | 
			
		||||
* Send a Chat Message
 | 
			
		||||
## Building an Automated Ricochet Application
 | 
			
		||||
 | 
			
		||||
If you have questions or want to contribute please contact Sarah @ 
 | 
			
		||||
`ricochet:qn6uo4cmsrfv4kzq`
 | 
			
		||||
Below is a simple echo bot, which responds to any chat message. You can also find this code under `examples/echobot`
 | 
			
		||||
 | 
			
		||||
                package main
 | 
			
		||||
 | 
			
		||||
                import (
 | 
			
		||||
                        "github.com/s-rah/go-ricochet"
 | 
			
		||||
                        "log"
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                type EchoBotService struct {
 | 
			
		||||
                        goricochet.StandardRicochetService
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Always Accept Contact Requests
 | 
			
		||||
                func (ts *EchoBotService) IsKnownContact(hostname string) bool {
 | 
			
		||||
                        return true
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                func (ts *EchoBotService) OnContactRequest(oc *goricochet.OpenConnection, channelID int32, nick string, message string) {
 | 
			
		||||
                        ts.StandardRicochetService.OnContactRequest(oc, channelID, nick, message)
 | 
			
		||||
                        oc.AckContactRequestOnResponse(channelID, "Accepted")
 | 
			
		||||
                        oc.CloseChannel(channelID)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                func (ebs *EchoBotService) OnChatMessage(oc *goricochet.OpenConnection, channelID int32, messageId int32, message string) {
 | 
			
		||||
                        log.Printf("Received Message from %s: %s", oc.OtherHostname, message)
 | 
			
		||||
                        oc.AckChatMessage(channelID, messageId)
 | 
			
		||||
                        if oc.GetChannelType(6) == "none" {
 | 
			
		||||
                                oc.OpenChatChannel(6)
 | 
			
		||||
                        }
 | 
			
		||||
                        oc.SendMessage(6, message)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                func main() {
 | 
			
		||||
                        ricochetService := new(EchoBotService)
 | 
			
		||||
                        ricochetService.Init("./private_key")
 | 
			
		||||
                        ricochetService.Listen(ricochetService, 12345)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
Each automated ricochet service can extend of the `StandardRicochetService`. From there
 | 
			
		||||
certain functions can be extended to fully build out a complete application.
 | 
			
		||||
 | 
			
		||||
Currently GoRicochet does not establish a hidden service, so to make this service
 | 
			
		||||
available to the world you will have to [set up a hidden service](https://www.torproject.org/docs/tor-hidden-service.html.en)
 | 
			
		||||
 | 
			
		||||
## Security and Usage Note
 | 
			
		||||
 | 
			
		||||
This project is experimental and has not been independently reviewed. If you are
 | 
			
		||||
looking for a quick and easy way to use ricochet please check out [Ricochet Protocol](https://ricochet.im).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import (
 | 
			
		|||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AuthenticationHandler manages the stae required for the AuthHiddenService
 | 
			
		||||
// AuthenticationHandler manages the state required for the AuthHiddenService
 | 
			
		||||
// authentication scheme for ricochet.
 | 
			
		||||
type AuthenticationHandler struct {
 | 
			
		||||
	clientCookie [16]byte
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ func TestGenClientCookie(t *testing.T) {
 | 
			
		|||
	authHandler := new(AuthenticationHandler)
 | 
			
		||||
	clientCookie := authHandler.GenClientCookie()
 | 
			
		||||
	if clientCookie != authHandler.clientCookie {
 | 
			
		||||
		t.Errorf("AuthenticationHandler Client Cookies are Different", clientCookie, authHandler.clientCookie)
 | 
			
		||||
		t.Errorf("AuthenticationHandler Client Cookies are Different %x %x", clientCookie, authHandler.clientCookie)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +27,6 @@ func TestGenServerCookie(t *testing.T) {
 | 
			
		|||
	authHandler := new(AuthenticationHandler)
 | 
			
		||||
	serverCookie := authHandler.GenServerCookie()
 | 
			
		||||
	if serverCookie != authHandler.serverCookie {
 | 
			
		||||
		t.Errorf("AuthenticationHandler Server Cookies are Different", serverCookie, authHandler.serverCookie)
 | 
			
		||||
		t.Errorf("AuthenticationHandler Server Cookies are Different %x %x", serverCookie, authHandler.serverCookie)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,30 +2,35 @@ package main
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/s-rah/go-ricochet"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type EchoBotService struct {
 | 
			
		||||
    goricochet.StandardRicochetService
 | 
			
		||||
	goricochet.StandardRicochetService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ebs * EchoBotService) OnAuthenticationResult(channelID int32, serverHostname string, result bool) {
 | 
			
		||||
    if true {
 | 
			
		||||
        ebs.Ricochet().OpenChatChannel(5)
 | 
			
		||||
        ebs.Ricochet().SendMessage(5, "Hi I'm an echo bot, I echo what you say!")
 | 
			
		||||
    }
 | 
			
		||||
// Always Accept Contact Requests
 | 
			
		||||
func (ts *EchoBotService) IsKnownContact(hostname string) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ebs * EchoBotService) OnChatMessage(channelID int32, serverHostname string, messageId int32, message string) {
 | 
			
		||||
   ebs.Ricochet().AckChatMessage(channelID, messageId)
 | 
			
		||||
   ebs.Ricochet().SendMessage(5, message)
 | 
			
		||||
func (ts *EchoBotService) OnContactRequest(oc *goricochet.OpenConnection, channelID int32, nick string, message string) {
 | 
			
		||||
	ts.StandardRicochetService.OnContactRequest(oc, channelID, nick, message)
 | 
			
		||||
	oc.AckContactRequestOnResponse(channelID, "Accepted")
 | 
			
		||||
	oc.CloseChannel(channelID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ebs *EchoBotService) OnChatMessage(oc *goricochet.OpenConnection, channelID int32, messageId int32, message string) {
 | 
			
		||||
	log.Printf("Received Message from %s: %s", oc.OtherHostname, message)
 | 
			
		||||
	oc.AckChatMessage(channelID, messageId)
 | 
			
		||||
	if oc.GetChannelType(6) == "none" {
 | 
			
		||||
		oc.OpenChatChannel(6)
 | 
			
		||||
	}
 | 
			
		||||
	oc.SendMessage(6, message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
    ricochetService := new(EchoBotService)
 | 
			
		||||
    ricochetService.Init("./private_key", "kwke2hntvyfqm7dr") 
 | 
			
		||||
	err := ricochetService.Ricochet().Connect("kwke2hntvyfqm7dr", "127.0.0.1:55555|jlq67qzo6s4yp3sp")
 | 
			
		||||
	if err == nil { 
 | 
			
		||||
	    ricochetService.OnConnect("jlq67qzo6s4yp3sp")
 | 
			
		||||
        ricochetService.Ricochet().ListenAndWait("jlq67qzo6s4yp3sp", ricochetService)
 | 
			
		||||
	}
 | 
			
		||||
	ricochetService := new(EchoBotService)
 | 
			
		||||
	ricochetService.Init("./private_key")
 | 
			
		||||
	ricochetService.Listen(ricochetService, 12345)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import (
 | 
			
		|||
	"github.com/s-rah/go-ricochet/chat"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/contact"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/control"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MessageBuilder allows a client to construct specific data packets for the
 | 
			
		||||
| 
						 | 
				
			
			@ -13,12 +14,12 @@ import (
 | 
			
		|||
type MessageBuilder struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenChatChannel contructs a message which will request to open a channel for
 | 
			
		||||
// OpenChannel contructs a message which will request to open a channel for
 | 
			
		||||
// chat on the given channelID.
 | 
			
		||||
func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) {
 | 
			
		||||
func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) ([]byte, error) {
 | 
			
		||||
	oc := &Protocol_Data_Control.OpenChannel{
 | 
			
		||||
		ChannelIdentifier: proto.Int32(channelID),
 | 
			
		||||
		ChannelType:       proto.String("im.ricochet.chat"),
 | 
			
		||||
		ChannelType:       proto.String(channelType),
 | 
			
		||||
	}
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		OpenChannel: oc,
 | 
			
		||||
| 
						 | 
				
			
			@ -27,10 +28,10 @@ func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// AckOpenChannel constructs a message to acknowledge a previous open channel operation.
 | 
			
		||||
func (mb *MessageBuilder) AckOpenChannel(channelID int32, opened bool) ([]byte, error) {
 | 
			
		||||
func (mb *MessageBuilder) AckOpenChannel(channelID int32) ([]byte, error) {
 | 
			
		||||
	cr := &Protocol_Data_Control.ChannelResult{
 | 
			
		||||
		ChannelIdentifier: proto.Int32(channelID),
 | 
			
		||||
		Opened:            proto.Bool(opened),
 | 
			
		||||
		Opened:            proto.Bool(true),
 | 
			
		||||
	}
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		ChannelResult: cr,
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +39,39 @@ func (mb *MessageBuilder) AckOpenChannel(channelID int32, opened bool) ([]byte,
 | 
			
		|||
	return proto.Marshal(pc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RejectOpenChannel constructs a channel result message, stating the channel failed to open and a reason
 | 
			
		||||
func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) ([]byte, error) {
 | 
			
		||||
 | 
			
		||||
	errorNum := Protocol_Data_Control.ChannelResult_CommonError_value[error]
 | 
			
		||||
	commonError := Protocol_Data_Control.ChannelResult_CommonError(errorNum)
 | 
			
		||||
 | 
			
		||||
	cr := &Protocol_Data_Control.ChannelResult{
 | 
			
		||||
		ChannelIdentifier: proto.Int32(channelID),
 | 
			
		||||
		Opened:            proto.Bool(false),
 | 
			
		||||
		CommonError:       &commonError,
 | 
			
		||||
	}
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		ChannelResult: cr,
 | 
			
		||||
	}
 | 
			
		||||
	return proto.Marshal(pc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConfirmAuthChannel constructs a message to acknowledge a previous open channel operation.
 | 
			
		||||
func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]byte) ([]byte, error) {
 | 
			
		||||
	cr := &Protocol_Data_Control.ChannelResult{
 | 
			
		||||
		ChannelIdentifier: proto.Int32(channelID),
 | 
			
		||||
		Opened:            proto.Bool(true),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := proto.SetExtension(cr, Protocol_Data_AuthHiddenService.E_ServerCookie, serverCookie[:])
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		ChannelResult: cr,
 | 
			
		||||
	}
 | 
			
		||||
	return proto.Marshal(pc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenContactRequestChannel contructs a message which will reuqest to open a channel for
 | 
			
		||||
// a contact request on the given channelID, with the given nick and message.
 | 
			
		||||
func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) ([]byte, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -53,10 +87,7 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		OpenChannel: oc,
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +95,38 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string
 | 
			
		|||
	return proto.Marshal(pc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplyToContactRequestOnResponse constructs a message to acknowledge contact request
 | 
			
		||||
func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, status string) ([]byte, error) {
 | 
			
		||||
	cr := &Protocol_Data_Control.ChannelResult{
 | 
			
		||||
		ChannelIdentifier: proto.Int32(channelID),
 | 
			
		||||
		Opened:            proto.Bool(true),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	statusNum := Protocol_Data_ContactRequest.Response_Status_value[status]
 | 
			
		||||
	responseStatus := Protocol_Data_ContactRequest.Response_Status(statusNum)
 | 
			
		||||
	contactRequest := &Protocol_Data_ContactRequest.Response{
 | 
			
		||||
		Status: &responseStatus,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := proto.SetExtension(cr, Protocol_Data_ContactRequest.E_Response, contactRequest)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		ChannelResult: cr,
 | 
			
		||||
	}
 | 
			
		||||
	return proto.Marshal(pc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplyToContactRequest constructs a message to acknowledge a contact request
 | 
			
		||||
func (mb *MessageBuilder) ReplyToContactRequest(channelID int32, status string) ([]byte, error) {
 | 
			
		||||
	statusNum := Protocol_Data_ContactRequest.Response_Status_value[status]
 | 
			
		||||
	responseStatus := Protocol_Data_ContactRequest.Response_Status(statusNum)
 | 
			
		||||
	contactRequest := &Protocol_Data_ContactRequest.Response{
 | 
			
		||||
		Status: &responseStatus,
 | 
			
		||||
	}
 | 
			
		||||
	return proto.Marshal(contactRequest)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenAuthenticationChannel constructs a message which will reuqest to open a channel for
 | 
			
		||||
// authentication on the given channelID, with the given cookie
 | 
			
		||||
func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCookie [16]byte) ([]byte, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -72,18 +135,49 @@ func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCooki
 | 
			
		|||
		ChannelType:       proto.String("im.ricochet.auth.hidden-service"),
 | 
			
		||||
	}
 | 
			
		||||
	err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	pc := &Protocol_Data_Control.Packet{
 | 
			
		||||
		OpenChannel: oc,
 | 
			
		||||
	}
 | 
			
		||||
	return proto.Marshal(pc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Proof constructs a proof message with the given public key and signature.
 | 
			
		||||
func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) ([]byte, error) {
 | 
			
		||||
	proof := &Protocol_Data_AuthHiddenService.Proof{
 | 
			
		||||
		PublicKey: publicKeyBytes,
 | 
			
		||||
		Signature: signatureBytes,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
 | 
			
		||||
		Proof:  proof,
 | 
			
		||||
		Result: nil,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return proto.Marshal(ahsPacket)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AuthResult constructs a response to a Proof
 | 
			
		||||
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) ([]byte, error) {
 | 
			
		||||
	// Construct a Result Message
 | 
			
		||||
	result := &Protocol_Data_AuthHiddenService.Result{
 | 
			
		||||
		Accepted:       proto.Bool(accepted),
 | 
			
		||||
		IsKnownContact: proto.Bool(isKnownContact),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
 | 
			
		||||
		Proof:  nil,
 | 
			
		||||
		Result: result,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return proto.Marshal(ahsPacket)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChatMessage constructs a chat message with the given content.
 | 
			
		||||
func (mb *MessageBuilder) ChatMessage(message string) ([]byte, error) {
 | 
			
		||||
func (mb *MessageBuilder) ChatMessage(message string, messageID int32) ([]byte, error) {
 | 
			
		||||
	cm := &Protocol_Data_Chat.ChatMessage{
 | 
			
		||||
		MessageId:   proto.Uint32(uint32(messageID)),
 | 
			
		||||
		MessageText: proto.String(message),
 | 
			
		||||
	}
 | 
			
		||||
	chatPacket := &Protocol_Data_Chat.Packet{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import "testing"
 | 
			
		|||
 | 
			
		||||
func TestOpenChatChannel(t *testing.T) {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	_, err := messageBuilder.OpenChatChannel(1)
 | 
			
		||||
	_, err := messageBuilder.OpenChannel(1, "im.ricochet.chat")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error building open chat channel message: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ func TestOpenAuthenticationChannel(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestChatMessage(t *testing.T) {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	_, err := messageBuilder.ChatMessage("Hello World")
 | 
			
		||||
	_, err := messageBuilder.ChatMessage("Hello World", 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error building chat message: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,110 +0,0 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/golang/protobuf/proto"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/auth"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/chat"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/control"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MessageDecoder struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Conceptual Chat Message - we construct this to avoid polluting the
 | 
			
		||||
// the main ricochet code with protobuf cruft - and enable us to minimise the
 | 
			
		||||
// code that may break in the future.
 | 
			
		||||
type RicochetChatMessage struct {
 | 
			
		||||
	Ack       bool
 | 
			
		||||
	MessageID int32
 | 
			
		||||
	Message   string
 | 
			
		||||
	Accepted  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Conceptual Control Message - we construct this to avoid polluting the
 | 
			
		||||
// the main ricochet code with protobuf cruft - and enable us to minimise the
 | 
			
		||||
// code that may break in the future.
 | 
			
		||||
type RicochetControlMessage struct {
 | 
			
		||||
	Ack          bool
 | 
			
		||||
	Type         string
 | 
			
		||||
	ChannelID    int32
 | 
			
		||||
	Accepted     bool
 | 
			
		||||
	ClientCookie [16]byte
 | 
			
		||||
	ServerCookie [16]byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DecodeAuthMessage
 | 
			
		||||
func (md *MessageDecoder) DecodeAuthMessage(data []byte) (bool, error) {
 | 
			
		||||
	res := new(Protocol_Data_AuthHiddenService.Packet)
 | 
			
		||||
	err := proto.Unmarshal(data[:], res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.New("error unmarshalling control message type")
 | 
			
		||||
	}
 | 
			
		||||
	return res.GetResult().GetAccepted(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DecodeControlMessage
 | 
			
		||||
func (md *MessageDecoder) DecodeControlMessage(data []byte) (*RicochetControlMessage, error) {
 | 
			
		||||
	res := new(Protocol_Data_Control.Packet)
 | 
			
		||||
	err := proto.Unmarshal(data[:], res)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.New("error unmarshalling control message type")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res.GetOpenChannel() != nil {
 | 
			
		||||
		ricochetControlMessage := new(RicochetControlMessage)
 | 
			
		||||
		ricochetControlMessage.Ack = false
 | 
			
		||||
 | 
			
		||||
		if res.GetOpenChannel().GetChannelType() == "im.ricochet.auth.hidden-service" {
 | 
			
		||||
			ricochetControlMessage.Type = "openauthchannel"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ricochetControlMessage.Type = "openchannel"
 | 
			
		||||
		ricochetControlMessage.ChannelID = int32(res.GetOpenChannel().GetChannelIdentifier())
 | 
			
		||||
		return ricochetControlMessage, nil
 | 
			
		||||
	} else if res.GetChannelResult() != nil {
 | 
			
		||||
		ricochetControlMessage := new(RicochetControlMessage)
 | 
			
		||||
		ricochetControlMessage.Ack = true
 | 
			
		||||
		ricochetControlMessage.ChannelID = int32(res.GetOpenChannel().GetChannelIdentifier())
 | 
			
		||||
 | 
			
		||||
		serverCookie, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_AuthHiddenService.E_ServerCookie)
 | 
			
		||||
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			ricochetControlMessage.Type = "openauthchannel"
 | 
			
		||||
			copy(ricochetControlMessage.ServerCookie[:], serverCookie.([]byte))
 | 
			
		||||
		} else {
 | 
			
		||||
			ricochetControlMessage.Type = "openchannel"
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return ricochetControlMessage, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, errors.New("unknown control message type")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DecodeChatMessage takes a byte representing a data packet and returns a
 | 
			
		||||
// constructed RicochetControlMessage
 | 
			
		||||
func (md *MessageDecoder) DecodeChatMessage(data []byte) (*RicochetChatMessage, error) {
 | 
			
		||||
	res := new(Protocol_Data_Chat.Packet)
 | 
			
		||||
	err := proto.Unmarshal(data[:], res)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res.GetChatMessage() != nil {
 | 
			
		||||
		ricochetChatMessage := new(RicochetChatMessage)
 | 
			
		||||
		ricochetChatMessage.Ack = false
 | 
			
		||||
		ricochetChatMessage.MessageID = int32(res.GetChatMessage().GetMessageId())
 | 
			
		||||
		ricochetChatMessage.Message = res.GetChatMessage().GetMessageText()
 | 
			
		||||
		return ricochetChatMessage, nil
 | 
			
		||||
	} else if res.GetChatAcknowledge != nil {
 | 
			
		||||
		ricochetChatMessage := new(RicochetChatMessage)
 | 
			
		||||
		ricochetChatMessage.Ack = true
 | 
			
		||||
		ricochetChatMessage.MessageID = int32(res.GetChatAcknowledge().GetMessageId())
 | 
			
		||||
		ricochetChatMessage.Accepted = res.GetChatAcknowledge().GetAccepted()
 | 
			
		||||
		return ricochetChatMessage, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, errors.New("chat message type not supported")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,300 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"encoding/asn1"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/utils"
 | 
			
		||||
	"net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// OpenConnection encapsulates the state required to maintain a connection to
 | 
			
		||||
// a ricochet service.
 | 
			
		||||
// Notably OpenConnection does not enforce limits on the channelIDs, channel Assignments
 | 
			
		||||
// or the direction of messages. These are considered to be service enforced rules.
 | 
			
		||||
// (and services are considered to be the best to define them).
 | 
			
		||||
type OpenConnection struct {
 | 
			
		||||
	conn        net.Conn
 | 
			
		||||
	authHandler map[int32]*AuthenticationHandler
 | 
			
		||||
	channels    map[int32]string
 | 
			
		||||
	rni         utils.RicochetNetworkInterface
 | 
			
		||||
 | 
			
		||||
	Client        bool
 | 
			
		||||
	IsAuthed      bool
 | 
			
		||||
	MyHostname    string
 | 
			
		||||
	OtherHostname string
 | 
			
		||||
	Closed        bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Init intializes a OpenConnection object to a default state.
 | 
			
		||||
func (oc *OpenConnection) Init(outbound bool, conn net.Conn) {
 | 
			
		||||
	oc.conn = conn
 | 
			
		||||
	oc.authHandler = make(map[int32]*AuthenticationHandler)
 | 
			
		||||
	oc.channels = make(map[int32]string)
 | 
			
		||||
	oc.rni = new(utils.RicochetNetwork)
 | 
			
		||||
 | 
			
		||||
	oc.Client = outbound
 | 
			
		||||
	oc.IsAuthed = false
 | 
			
		||||
	oc.MyHostname = ""
 | 
			
		||||
	oc.OtherHostname = ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnsetChannel removes a type association from the channel.
 | 
			
		||||
func (oc *OpenConnection) UnsetChannel(channel int32) {
 | 
			
		||||
	oc.channels[channel] = "none"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetChannelType returns the type of the channel on this connection
 | 
			
		||||
func (oc *OpenConnection) GetChannelType(channel int32) string {
 | 
			
		||||
	if val, ok := oc.channels[channel]; ok {
 | 
			
		||||
		return val
 | 
			
		||||
	}
 | 
			
		||||
	return "none"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (oc *OpenConnection) setChannel(channel int32, channelType string) {
 | 
			
		||||
	oc.channels[channel] = channelType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasChannel returns true if the connection has a channel of an associated type, false otherwise
 | 
			
		||||
func (oc *OpenConnection) HasChannel(channelType string) bool {
 | 
			
		||||
	for _, val := range oc.channels {
 | 
			
		||||
		if val == channelType {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloseChannel closes a given channel
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
func (oc *OpenConnection) CloseChannel(channel int32) {
 | 
			
		||||
	oc.UnsetChannel(channel)
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, channel, []byte{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the entire connection
 | 
			
		||||
func (oc *OpenConnection) Close() {
 | 
			
		||||
	oc.conn.Close()
 | 
			
		||||
	oc.Closed = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Authenticate opens an Authentication Channel and send a client cookie
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
func (oc *OpenConnection) Authenticate(channel int32) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
 | 
			
		||||
	oc.authHandler[channel] = new(AuthenticationHandler)
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenAuthenticationChannel(channel, oc.authHandler[channel].GenClientCookie())
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, "im.ricochet.auth.hidden-service")
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConfirmAuthChannel responds to a new authentication request.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
func (oc *OpenConnection) ConfirmAuthChannel(channel int32, clientCookie [16]byte) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
 | 
			
		||||
	oc.authHandler[channel] = new(AuthenticationHandler)
 | 
			
		||||
	oc.authHandler[channel].AddClientCookie(clientCookie[:])
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.ConfirmAuthChannel(channel, oc.authHandler[channel].GenServerCookie())
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, "im.ricochet.auth.hidden-service")
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendProof sends an authentication proof in response to a challenge.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
//              * channel must be of type auth
 | 
			
		||||
func (oc *OpenConnection) SendProof(channel int32, serverCookie [16]byte, publicKeyBytes []byte, privateKey *rsa.PrivateKey) {
 | 
			
		||||
 | 
			
		||||
	if oc.authHandler[channel] == nil {
 | 
			
		||||
		return // NoOp
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oc.authHandler[channel].AddServerCookie(serverCookie[:])
 | 
			
		||||
 | 
			
		||||
	challenge := oc.authHandler[channel].GenChallenge(oc.MyHostname, oc.OtherHostname)
 | 
			
		||||
	signature, _ := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, challenge)
 | 
			
		||||
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.Proof(publicKeyBytes, signature)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, channel, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateProof determines if the given public key and signature align with the
 | 
			
		||||
// already established challenge vector for this communication
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
//              * Client and Server must have already sent their respective cookies (Authenticate and ConfirmAuthChannel)
 | 
			
		||||
func (oc *OpenConnection) ValidateProof(channel int32, publicKeyBytes []byte, signature []byte) bool {
 | 
			
		||||
 | 
			
		||||
	if oc.authHandler[channel] == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	provisionalHostname := utils.GetTorHostname(publicKeyBytes)
 | 
			
		||||
	publicKey := new(rsa.PublicKey)
 | 
			
		||||
	_, err := asn1.Unmarshal(publicKeyBytes, publicKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	challenge := oc.authHandler[channel].GenChallenge(provisionalHostname, oc.MyHostname)
 | 
			
		||||
	err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, challenge[:], signature)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return true
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendAuthenticationResult responds to an existed authentication Proof
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
//              * channel must be of type auth
 | 
			
		||||
func (oc *OpenConnection) SendAuthenticationResult(channel int32, accepted bool, isKnownContact bool) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.AuthResult(accepted, isKnownContact)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, channel, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenChatChannel opens a new chat channel with the given id
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
//              * If acting as the client, id must be odd, else even
 | 
			
		||||
func (oc *OpenConnection) OpenChatChannel(channel int32) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenChannel(channel, "im.ricochet.chat")
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, "im.ricochet.chat")
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenChannel opens a new chat channel with the given id
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have previously connected to a service
 | 
			
		||||
//              * If acting as the client, id must be odd, else even
 | 
			
		||||
func (oc *OpenConnection) OpenChannel(channel int32, channelType string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenChannel(channel, channelType)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, channelType)
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AckOpenChannel acknowledges a previously received open channel message
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected and authenticated to a service
 | 
			
		||||
func (oc *OpenConnection) AckOpenChannel(channel int32, channeltype string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
 | 
			
		||||
	data, err := messageBuilder.AckOpenChannel(channel)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, channeltype)
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RejectOpenChannel acknowledges a rejects a previously received open channel message
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected
 | 
			
		||||
func (oc *OpenConnection) RejectOpenChannel(channel int32, errortype string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.RejectOpenChannel(channel, errortype)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendContactRequest initiates a contact request to the server.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected and authenticated to a service
 | 
			
		||||
func (oc *OpenConnection) SendContactRequest(channel int32, nick string, message string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, "im.ricochet.contact.request")
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AckContactRequestOnResponse responds a contact request from a client
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected and authenticated to a service
 | 
			
		||||
//             * Must have previously received a Contact Request
 | 
			
		||||
func (oc *OpenConnection) AckContactRequestOnResponse(channel int32, status string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.ReplyToContactRequestOnResponse(channel, status)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, "im.ricochet.contact.request")
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, 0, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AckContactRequest responds to contact request from a client
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected and authenticated to a service
 | 
			
		||||
//             * Must have previously received a Contact Request
 | 
			
		||||
func (oc *OpenConnection) AckContactRequest(channel int32, status string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.ReplyToContactRequest(channel, status)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.setChannel(channel, "im.ricochet.contact.request")
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, channel, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AckChatMessage acknowledges a previously received chat message.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected and authenticated to a service
 | 
			
		||||
//             * Must have established a known contact status with the other service
 | 
			
		||||
//             * Must have received a Chat message on an open im.ricochet.chat channel with the messageID
 | 
			
		||||
func (oc *OpenConnection) AckChatMessage(channel int32, messageID int32) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.AckChatMessage(messageID)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, channel, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendMessage sends a Chat Message (message) to a give Channel (channel).
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously connected and authenticated to a service
 | 
			
		||||
//             * Must have established a known contact status with the other service
 | 
			
		||||
//             * Must have previously opened channel with OpenChanel of type im.ricochet.chat
 | 
			
		||||
func (oc *OpenConnection) SendMessage(channel int32, message string) {
 | 
			
		||||
	defer utils.RecoverFromError()
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.ChatMessage(message, 0)
 | 
			
		||||
	utils.CheckError(err)
 | 
			
		||||
	oc.rni.SendRicochetPacket(oc.conn, channel, data)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestOpenConnectionAuth(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
-----BEGIN RSA PRIVATE KEY-----
 | 
			
		||||
MIICXgIBAAKBgQC3xEJBH4oVFaotPJw6dezx67Gv4Xukw8CZRGqNFO8yF7Rejtcj
 | 
			
		||||
/0RTqqZwj6H6FjxY60dgYnN6IphW0juemNZhxOXeM/5Gb5xO+kWGi5Qt87aSDxnA
 | 
			
		||||
MDLgqw79ihuD3m1C1TBz0olmjXPU1VtadZuZcVBST7SLs2/k55GNNr7BoQIDAQAB
 | 
			
		||||
AoGBAK3ybVCdnSQWLM7DJ5LC23Wnx7sXceVlkiLCOyWuYjiFbatwBD/DupaD2yaD
 | 
			
		||||
HyzN7XOxyg93QZ2jr5XHTL30KEAn/3akNBsX3sjHZnjVfTwD5+oZKd7HYMMxekWf
 | 
			
		||||
87TIx2IHvGEo2NaFMLkEZ5TX3Gre8CYOofjFcpj4661ZfYp9AkEA9I0EmQX26ibs
 | 
			
		||||
CRGkwPuEj5q5N/PmIHgMWr1pepOlmzJjnxy6SI3NUwmzKrqM6YUM8loSywqfVMrJ
 | 
			
		||||
RVzA5jp76wJBAMBeu2hS8KcUTIu66j0pXMhI5wDA3yLiO53TEMwufCPXcaWUMH+e
 | 
			
		||||
5AIPL7aZ8ouf895OH0TZKxPNMnbrJ+5F0aMCQDoi/CDUxipMLnjJdP1bzdvF0Jp4
 | 
			
		||||
pRC6+VTpCpZVW11V0VEWJ0LwUwuWlr1ls/If60ACIc2bLN2fh9Gxhzo0VRkCQQCS
 | 
			
		||||
nKCAVhYLgLEGHaLAknGgQ8+rB1QIphuBoYc/1n3OYzi+VT7RRSvJVgGrTZFJUNLw
 | 
			
		||||
LuIt+sWWBeHcOETqmFO5AkEAwwfcxs8QZtX6hCj2MTPi8Q28LIoA/M6eAqYc2I0B
 | 
			
		||||
eXxf2J2Qco7sMmBLr1Jp3jZNd5W2fMtlhUZAomOj4piVOA==
 | 
			
		||||
-----END RSA PRIVATE KEY-----
 | 
			
		||||
							
								
								
									
										569
									
								
								ricochet.go
								
								
								
								
							
							
						
						
									
										569
									
								
								ricochet.go
								
								
								
								
							| 
						 | 
				
			
			@ -1,319 +1,372 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/golang/protobuf/proto"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/auth"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/chat"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/contact"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/control"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/utils"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Ricochet is a protocol to conducting anonymous IM.
 | 
			
		||||
type Ricochet struct {
 | 
			
		||||
	conn   net.Conn
 | 
			
		||||
	logger *log.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RicochetData is a structure containing the raw data and the channel it the
 | 
			
		||||
// message originated on.
 | 
			
		||||
type RicochetData struct {
 | 
			
		||||
	Channel int32
 | 
			
		||||
	Data    []byte
 | 
			
		||||
	newconns        chan *OpenConnection
 | 
			
		||||
	networkResolver utils.NetworkResolver
 | 
			
		||||
	rni             utils.RicochetNetworkInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Init sets up the Ricochet object.
 | 
			
		||||
func (r *Ricochet) Init(debugLog bool) {
 | 
			
		||||
 | 
			
		||||
	if debugLog {
 | 
			
		||||
		r.logger = log.New(os.Stdout, "[Ricochet]: ", log.Ltime|log.Lmicroseconds)
 | 
			
		||||
	} else {
 | 
			
		||||
		r.logger = log.New(ioutil.Discard, "[Ricochet]: ", log.Ltime|log.Lmicroseconds)
 | 
			
		||||
	}
 | 
			
		||||
func (r *Ricochet) Init() {
 | 
			
		||||
	r.newconns = make(chan *OpenConnection)
 | 
			
		||||
	r.networkResolver = utils.NetworkResolver{}
 | 
			
		||||
	r.rni = new(utils.RicochetNetwork)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Connect sets up a ricochet connection between from and to which are
 | 
			
		||||
// both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this
 | 
			
		||||
// Connect sets up a client ricochet connection to host e.g. qn6uo4cmsrfv4kzq.onion. If this
 | 
			
		||||
// function finished successfully then the connection can be assumed to
 | 
			
		||||
// be open and authenticated.
 | 
			
		||||
// To specify a local port using the format "127.0.0.1:[port]|ricochet-id".
 | 
			
		||||
func (r *Ricochet) Connect(from string, to string) error {
 | 
			
		||||
func (r *Ricochet) Connect(host string) (*OpenConnection, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	networkResolver := new(NetworkResolver)
 | 
			
		||||
	r.conn, to, err = networkResolver.Resolve(to)
 | 
			
		||||
	conn, host, err := r.networkResolver.Resolve(host)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.negotiateVersion()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Authenticate opens an Authentication Channel and send a client cookie
 | 
			
		||||
func (r *Ricochet) Authenticate(channelID int32, clientCookie [16]byte) error {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenAuthenticationChannel(channelID, clientCookie)
 | 
			
		||||
 | 
			
		||||
	oc, err := r.negotiateVersion(conn, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("Cannot Marshal Open Channel Message")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	r.logger.Printf("Sending Open Channel with Auth Request (channel:%d)", channelID)
 | 
			
		||||
	r.sendPacket(data, 0)
 | 
			
		||||
	return nil
 | 
			
		||||
	oc.OtherHostname = host
 | 
			
		||||
	r.newconns <- oc
 | 
			
		||||
	return oc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendProof sends an authentication proof in response to a challenge.
 | 
			
		||||
func (r *Ricochet) SendProof(channelID int32, publickeyBytes []byte, signatureBytes []byte) error {
 | 
			
		||||
	// Construct a Proof Message
 | 
			
		||||
	proof := &Protocol_Data_AuthHiddenService.Proof{
 | 
			
		||||
		PublicKey: publickeyBytes,
 | 
			
		||||
		Signature: signatureBytes,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
 | 
			
		||||
		Proof:  proof,
 | 
			
		||||
		Result: nil,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := proto.Marshal(ahsPacket)
 | 
			
		||||
 | 
			
		||||
// Server launches a new server listening on port
 | 
			
		||||
func (r *Ricochet) Server(service RicochetService, port int) {
 | 
			
		||||
	ln, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(port))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		log.Printf("Cannot Listen on Port %v", port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.logger.Printf("Sending Proof Auth Request (channel:%d)", channelID)
 | 
			
		||||
	r.sendPacket(data, channelID)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OpenChannel opens a new chat channel with the given id
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have Previously issued a successful Connect()
 | 
			
		||||
//              * If acting as the client, id must be odd, else even
 | 
			
		||||
func (r *Ricochet) OpenChatChannel(id int32) error {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenChatChannel(id)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("error constructing control channel message to open channel")
 | 
			
		||||
	go r.ProcessMessages(service)
 | 
			
		||||
	service.OnReady()
 | 
			
		||||
	for {
 | 
			
		||||
		// accept connection on port
 | 
			
		||||
		conn, err := ln.Accept()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		go r.processNewConnection(conn, service)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.logger.Printf("Opening Chat Channel: %d", id)
 | 
			
		||||
	r.sendPacket(data, 0)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendContactRequest initiates a contact request to the server.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have Previously issued a successful Connect()
 | 
			
		||||
func (r *Ricochet) SendContactRequest(channel int32, nick string, message string) error {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("error constructing control channel message to send contact request")
 | 
			
		||||
// processNewConnection sets up a new connection
 | 
			
		||||
func (r *Ricochet) processNewConnection(conn net.Conn, service RicochetService) {
 | 
			
		||||
	oc, err := r.negotiateVersion(conn, false)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		r.newconns <- oc
 | 
			
		||||
		service.OnConnect(oc)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.sendPacket(data, 0)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AckOpenChannel acknowledges a previously received open channel message
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have Previously issued a successful Connect()
 | 
			
		||||
func (r *Ricochet) AckOpenChannel(channel int32, result bool) error {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.AckOpenChannel(channel, result)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("Failed to serialize open channel ack")
 | 
			
		||||
	}
 | 
			
		||||
	r.sendPacket(data, 0)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AckChatMessage acknowledges a previously received chat message.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//              * Must have Previously issued a successful Connect()
 | 
			
		||||
func (r *Ricochet) AckChatMessage(channel int32, messageID int32) error {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.AckChatMessage(messageID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("Failed to serialize chat message ack")
 | 
			
		||||
	}
 | 
			
		||||
	r.sendPacket(data, channel)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendMessage sends a Chat Message (message) to a give Channel (channel).
 | 
			
		||||
// ProcessMessages is intended to be a background thread listening for all messages
 | 
			
		||||
// a client will send. The given RicochetService will be used to respond to messages.
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously issued a successful Connect()
 | 
			
		||||
//             * Must have previously opened channel with OpenChanel
 | 
			
		||||
func (r *Ricochet) SendMessage(channel int32, message string) error {
 | 
			
		||||
	messageBuilder := new(MessageBuilder)
 | 
			
		||||
	data, err := messageBuilder.ChatMessage(message)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("error constructing control channel message to send chat message")
 | 
			
		||||
func (r *Ricochet) ProcessMessages(service RicochetService) {
 | 
			
		||||
	for {
 | 
			
		||||
		oc := <-r.newconns
 | 
			
		||||
		go r.processConnection(oc, service)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	r.logger.Printf("Sending Message on Channel: %d", channel)
 | 
			
		||||
	r.sendPacket(data, channel)
 | 
			
		||||
	return nil
 | 
			
		||||
// ProcessConnection starts a blocking process loop which continually waits for
 | 
			
		||||
// new messages to arrive from the connection and uses the given RicochetService
 | 
			
		||||
// to process them.
 | 
			
		||||
func (r *Ricochet) processConnection(oc *OpenConnection, service RicochetService) {
 | 
			
		||||
	service.OnConnect(oc)
 | 
			
		||||
	for {
 | 
			
		||||
		if oc.Closed {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packets, err := r.rni.RecvRicochetPackets(oc.conn)
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, packet := range packets {
 | 
			
		||||
 | 
			
		||||
			if len(packet.Data) == 0 {
 | 
			
		||||
				service.OnChannelClosed(oc, packet.Channel)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if packet.Channel == 0 {
 | 
			
		||||
 | 
			
		||||
				res := new(Protocol_Data_Control.Packet)
 | 
			
		||||
				err := proto.Unmarshal(packet.Data[:], res)
 | 
			
		||||
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					service.OnGenericError(oc, packet.Channel)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if res.GetOpenChannel() != nil {
 | 
			
		||||
					opm := res.GetOpenChannel()
 | 
			
		||||
 | 
			
		||||
					if oc.GetChannelType(opm.GetChannelIdentifier()) != "none" {
 | 
			
		||||
						// Channel is already in use.
 | 
			
		||||
						service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// If I am a Client, the server can only open even numbered channels
 | 
			
		||||
					if oc.Client && opm.GetChannelIdentifier()%2 != 0 {
 | 
			
		||||
						service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// If I am a Server, the client can only open odd numbered channels
 | 
			
		||||
					if !oc.Client && opm.GetChannelIdentifier()%2 != 1 {
 | 
			
		||||
						service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					switch opm.GetChannelType() {
 | 
			
		||||
					case "im.ricochet.auth.hidden-service":
 | 
			
		||||
						if oc.Client {
 | 
			
		||||
							// Servers are authed by default and can't auth with hidden-service
 | 
			
		||||
							service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else if oc.IsAuthed {
 | 
			
		||||
							// Can't auth if already authed
 | 
			
		||||
							service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else if oc.HasChannel("im.ricochet.auth.hidden-service") {
 | 
			
		||||
							// Can't open more than 1 auth channel
 | 
			
		||||
							service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else {
 | 
			
		||||
							clientCookie, err := proto.GetExtension(opm, Protocol_Data_AuthHiddenService.E_ClientCookie)
 | 
			
		||||
							if err == nil {
 | 
			
		||||
								clientCookieB := [16]byte{}
 | 
			
		||||
								copy(clientCookieB[:], clientCookie.([]byte)[:])
 | 
			
		||||
								service.OnAuthenticationRequest(oc, opm.GetChannelIdentifier(), clientCookieB)
 | 
			
		||||
							} else {
 | 
			
		||||
								// Must include Client Cookie
 | 
			
		||||
								service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "im.ricochet.chat":
 | 
			
		||||
						if !oc.IsAuthed {
 | 
			
		||||
							// Can't open chat channel if not authorized
 | 
			
		||||
							service.OnUnauthorizedError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else if !service.IsKnownContact(oc.OtherHostname) {
 | 
			
		||||
							// Can't open chat channel if not a known contact
 | 
			
		||||
							service.OnUnauthorizedError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else {
 | 
			
		||||
							service.OnOpenChannelRequest(oc, opm.GetChannelIdentifier(), "im.ricochet.chat")
 | 
			
		||||
						}
 | 
			
		||||
					case "im.ricochet.contact.request":
 | 
			
		||||
						if oc.Client {
 | 
			
		||||
							// Servers are not allowed to send contact requests
 | 
			
		||||
							service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else if !oc.IsAuthed {
 | 
			
		||||
							// Can't open a contact channel if not authed
 | 
			
		||||
							service.OnUnauthorizedError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else if oc.HasChannel("im.ricochet.contact.request") {
 | 
			
		||||
							// Only 1 contact channel is allowed to be open at a time
 | 
			
		||||
							service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						} else {
 | 
			
		||||
							contactRequestI, err := proto.GetExtension(opm, Protocol_Data_ContactRequest.E_ContactRequest)
 | 
			
		||||
							if err == nil {
 | 
			
		||||
								contactRequest, check := contactRequestI.(*Protocol_Data_ContactRequest.ContactRequest)
 | 
			
		||||
								if check {
 | 
			
		||||
									service.OnContactRequest(oc, opm.GetChannelIdentifier(), contactRequest.GetNickname(), contactRequest.GetMessageText())
 | 
			
		||||
									break
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							service.OnBadUsageError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
						}
 | 
			
		||||
					default:
 | 
			
		||||
						service.OnUnknownTypeError(oc, opm.GetChannelIdentifier())
 | 
			
		||||
					}
 | 
			
		||||
				} else if res.GetChannelResult() != nil {
 | 
			
		||||
					crm := res.GetChannelResult()
 | 
			
		||||
					if crm.GetOpened() {
 | 
			
		||||
						switch oc.GetChannelType(crm.GetChannelIdentifier()) {
 | 
			
		||||
						case "im.ricochet.auth.hidden-service":
 | 
			
		||||
							serverCookie, err := proto.GetExtension(crm, Protocol_Data_AuthHiddenService.E_ServerCookie)
 | 
			
		||||
							if err == nil {
 | 
			
		||||
								serverCookieB := [16]byte{}
 | 
			
		||||
								copy(serverCookieB[:], serverCookie.([]byte)[:])
 | 
			
		||||
								service.OnAuthenticationChallenge(oc, crm.GetChannelIdentifier(), serverCookieB)
 | 
			
		||||
							} else {
 | 
			
		||||
								service.OnBadUsageError(oc, crm.GetChannelIdentifier())
 | 
			
		||||
							}
 | 
			
		||||
						case "im.ricochet.chat":
 | 
			
		||||
							service.OnOpenChannelRequestSuccess(oc, crm.GetChannelIdentifier())
 | 
			
		||||
						case "im.ricochet.contact.request":
 | 
			
		||||
							responseI, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_ContactRequest.E_Response)
 | 
			
		||||
							if err == nil {
 | 
			
		||||
								response, check := responseI.(*Protocol_Data_ContactRequest.Response)
 | 
			
		||||
								if check {
 | 
			
		||||
									service.OnContactRequestAck(oc, crm.GetChannelIdentifier(), response.GetStatus().String())
 | 
			
		||||
									break
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							service.OnBadUsageError(oc, crm.GetChannelIdentifier())
 | 
			
		||||
						default:
 | 
			
		||||
							service.OnBadUsageError(oc, crm.GetChannelIdentifier())
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						if oc.GetChannelType(crm.GetChannelIdentifier()) != "none" {
 | 
			
		||||
							service.OnFailedChannelOpen(oc, crm.GetChannelIdentifier(), crm.GetCommonError().String())
 | 
			
		||||
						} else {
 | 
			
		||||
							oc.CloseChannel(crm.GetChannelIdentifier())
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					// Unknown Message
 | 
			
		||||
					oc.CloseChannel(packet.Channel)
 | 
			
		||||
				}
 | 
			
		||||
			} else if oc.GetChannelType(packet.Channel) == "im.ricochet.auth.hidden-service" {
 | 
			
		||||
				res := new(Protocol_Data_AuthHiddenService.Packet)
 | 
			
		||||
				err := proto.Unmarshal(packet.Data[:], res)
 | 
			
		||||
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					oc.CloseChannel(packet.Channel)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if res.GetProof() != nil && !oc.Client { // Only Clients Send Proofs
 | 
			
		||||
					service.OnAuthenticationProof(oc, packet.Channel, res.GetProof().GetPublicKey(), res.GetProof().GetSignature(), service.IsKnownContact(oc.OtherHostname))
 | 
			
		||||
				} else if res.GetResult() != nil && oc.Client { // Only Servers Send Results
 | 
			
		||||
					service.OnAuthenticationResult(oc, packet.Channel, res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact())
 | 
			
		||||
				} else {
 | 
			
		||||
					// If neither of the above are satisfied we just close the connection
 | 
			
		||||
					oc.Close()
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			} else if oc.GetChannelType(packet.Channel) == "im.ricochet.chat" {
 | 
			
		||||
 | 
			
		||||
				// NOTE: These auth checks should be redundant, however they
 | 
			
		||||
				// are included here for defense-in-depth if for some reason
 | 
			
		||||
				// a previously authed connection becomes untrusted / not known and
 | 
			
		||||
				// the state is not cleaned up.
 | 
			
		||||
				if !oc.IsAuthed {
 | 
			
		||||
					// Can't send chat messages if not authorized
 | 
			
		||||
					service.OnUnauthorizedError(oc, packet.Channel)
 | 
			
		||||
				} else if !service.IsKnownContact(oc.OtherHostname) {
 | 
			
		||||
					// Can't send chat message if not a known contact
 | 
			
		||||
					service.OnUnauthorizedError(oc, packet.Channel)
 | 
			
		||||
				} else {
 | 
			
		||||
					res := new(Protocol_Data_Chat.Packet)
 | 
			
		||||
					err := proto.Unmarshal(packet.Data[:], res)
 | 
			
		||||
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						oc.CloseChannel(packet.Channel)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if res.GetChatMessage() != nil {
 | 
			
		||||
						service.OnChatMessage(oc, packet.Channel, int32(res.GetChatMessage().GetMessageId()), res.GetChatMessage().GetMessageText())
 | 
			
		||||
					} else if res.GetChatAcknowledge() != nil {
 | 
			
		||||
						service.OnChatMessageAck(oc, packet.Channel, int32(res.GetChatMessage().GetMessageId()))
 | 
			
		||||
					} else {
 | 
			
		||||
						// If neither of the above are satisfied we just close the connection
 | 
			
		||||
						oc.Close()
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else if oc.GetChannelType(packet.Channel) == "im.ricochet.contact.request" {
 | 
			
		||||
 | 
			
		||||
				// NOTE: These auth checks should be redundant, however they
 | 
			
		||||
				// are included here for defense-in-depth if for some reason
 | 
			
		||||
				// a previously authed connection becomes untrusted / not known and
 | 
			
		||||
				// the state is not cleaned up.
 | 
			
		||||
				if !oc.Client {
 | 
			
		||||
					// Clients are not allowed to send contact request responses
 | 
			
		||||
					service.OnBadUsageError(oc, packet.Channel)
 | 
			
		||||
				} else if !oc.IsAuthed {
 | 
			
		||||
					// Can't send a contact request if not authed
 | 
			
		||||
					service.OnBadUsageError(oc, packet.Channel)
 | 
			
		||||
				} else {
 | 
			
		||||
					res := new(Protocol_Data_ContactRequest.Response)
 | 
			
		||||
					err := proto.Unmarshal(packet.Data[:], res)
 | 
			
		||||
					log.Printf("%v", res)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						oc.CloseChannel(packet.Channel)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					service.OnContactRequestAck(oc, packet.Channel, res.GetStatus().String())
 | 
			
		||||
				}
 | 
			
		||||
			} else if oc.GetChannelType(packet.Channel) == "none" {
 | 
			
		||||
				// Invalid Channel Assignment
 | 
			
		||||
				oc.CloseChannel(packet.Channel)
 | 
			
		||||
			} else {
 | 
			
		||||
				oc.Close()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// negotiateVersion Perform version negotiation with the connected host.
 | 
			
		||||
func (r *Ricochet) negotiateVersion() error {
 | 
			
		||||
func (r *Ricochet) negotiateVersion(conn net.Conn, outbound bool) (*OpenConnection, error) {
 | 
			
		||||
	version := make([]byte, 4)
 | 
			
		||||
	version[0] = 0x49
 | 
			
		||||
	version[1] = 0x4D
 | 
			
		||||
	version[2] = 0x01
 | 
			
		||||
	version[3] = 0x01
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(r.conn, "%s", version)
 | 
			
		||||
	r.logger.Print("Negotiating Version ", version)
 | 
			
		||||
	res, err := r.recv()
 | 
			
		||||
	// If this was initiated by us then we need to initiate the version info.
 | 
			
		||||
	if outbound {
 | 
			
		||||
		// Send Version String
 | 
			
		||||
 | 
			
		||||
	if len(res) != 1 || err != nil {
 | 
			
		||||
		return errors.New("Failed Version Negotiating")
 | 
			
		||||
	}
 | 
			
		||||
		conn.Write(version)
 | 
			
		||||
		res, err := r.rni.Recv(conn)
 | 
			
		||||
 | 
			
		||||
	if res[0] != 1 {
 | 
			
		||||
		return errors.New("Failed Version Negotiating - Invalid Version ")
 | 
			
		||||
	}
 | 
			
		||||
		if len(res) != 1 || err != nil {
 | 
			
		||||
			return nil, errors.New("Failed Version Negotiating")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	r.logger.Print("Successfully Negotiated Version ", res[0])
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
		if res[0] != 1 {
 | 
			
		||||
			return nil, errors.New("Failed Version Negotiating - Invalid Version ")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Do Version Negotiation
 | 
			
		||||
 | 
			
		||||
// sendPacket places the data into a structure needed for the client to
 | 
			
		||||
// decode the packet and writes the packet to the network.
 | 
			
		||||
func (r *Ricochet) sendPacket(data []byte, channel int32) {
 | 
			
		||||
	header := make([]byte, 4+len(data))
 | 
			
		||||
	header[0] = byte(len(header) >> 8)
 | 
			
		||||
	header[1] = byte(len(header) & 0x00FF)
 | 
			
		||||
	header[2] = 0x00
 | 
			
		||||
	header[3] = byte(channel)
 | 
			
		||||
	copy(header[4:], data[:])
 | 
			
		||||
	fmt.Fprintf(r.conn, "%s", header)
 | 
			
		||||
}
 | 
			
		||||
		buf := make([]byte, 10)
 | 
			
		||||
		n, err := conn.Read(buf)
 | 
			
		||||
		if err != nil && n >= 4 {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
// ListenAndWait is intended to be a background thread listening for all messages
 | 
			
		||||
// a client will send, automaticall responding to some, and making the others available to
 | 
			
		||||
// Listen()
 | 
			
		||||
// Prerequisites:
 | 
			
		||||
//             * Must have previously issued a successful Connect()
 | 
			
		||||
func (r *Ricochet) ListenAndWait(serverHostname string, service RicochetService) error {
 | 
			
		||||
	for true {
 | 
			
		||||
		packets, err := r.getMessages()
 | 
			
		||||
		r.handleFatal(err, "Error attempted to get new messages")
 | 
			
		||||
 | 
			
		||||
		messageDecoder := new(MessageDecoder)
 | 
			
		||||
 | 
			
		||||
		for _, packet := range packets {
 | 
			
		||||
 | 
			
		||||
			if len(packet.Data) == 0 {
 | 
			
		||||
				r.logger.Printf("Closing Channel %d", packet.Channel)
 | 
			
		||||
				service.OnChannelClose(packet.Channel, serverHostname)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if packet.Channel == 0 {
 | 
			
		||||
 | 
			
		||||
				message, err := messageDecoder.DecodeControlMessage(packet.Data)
 | 
			
		||||
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					r.logger.Printf("Failed to decode data packet, discarding")
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if message.Type == "openchannel" && message.Ack == false {
 | 
			
		||||
					r.logger.Printf("new open channel request %d %s", message.ChannelID, serverHostname)
 | 
			
		||||
					service.OnOpenChannelRequest(message.ChannelID, serverHostname)
 | 
			
		||||
				} else if message.Type == "openchannel" && message.Ack == true {
 | 
			
		||||
					r.logger.Printf("new open channel request ack %d %s", message.ChannelID, serverHostname)
 | 
			
		||||
					service.OnOpenChannelRequestAck(message.ChannelID, serverHostname, message.Accepted)
 | 
			
		||||
				} else if message.Type == "openauthchannel" && message.Ack == true {
 | 
			
		||||
					r.logger.Printf("new authentication challenge %d %s", message.ChannelID, serverHostname)
 | 
			
		||||
					service.OnAuthenticationChallenge(message.ChannelID, serverHostname, message.ServerCookie)
 | 
			
		||||
				} else {
 | 
			
		||||
					r.logger.Printf("Received Unknown Control Message\n", message)
 | 
			
		||||
				}
 | 
			
		||||
			} else if packet.Channel == 1 {
 | 
			
		||||
				result, _ := messageDecoder.DecodeAuthMessage(packet.Data)
 | 
			
		||||
				r.logger.Printf("newreceived auth result %d", packet.Channel)
 | 
			
		||||
				service.OnAuthenticationResult(1, serverHostname, result)
 | 
			
		||||
			} else {
 | 
			
		||||
 | 
			
		||||
				// At this point the only other expected type of message is a Chat Message
 | 
			
		||||
				messageDecoder := new(MessageDecoder)
 | 
			
		||||
				message, err := messageDecoder.DecodeChatMessage(packet.Data)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					r.logger.Printf("Failed to decode data packet, discarding on channel %d", packet.Channel)
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if message.Ack == true {
 | 
			
		||||
					service.OnChatMessageAck(packet.Channel, serverHostname, message.MessageID)
 | 
			
		||||
				} else {
 | 
			
		||||
					service.OnChatMessage(packet.Channel, serverHostname, message.MessageID, message.Message)
 | 
			
		||||
		if buf[0] == version[0] && buf[1] == version[1] {
 | 
			
		||||
			foundVersion := false
 | 
			
		||||
			if buf[2] >= 1 {
 | 
			
		||||
				for i := 3; i < n; i++ {
 | 
			
		||||
					if buf[i] == 0x01 {
 | 
			
		||||
						conn.Write([]byte{0x01})
 | 
			
		||||
						foundVersion = true
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !foundVersion {
 | 
			
		||||
				return nil, errors.New("Failed Version Negotiating - No Available Version")
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			return nil, errors.New("Failed Version Negotiating - Invalid Version Header")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getMessages returns an array of new messages received from the ricochet client
 | 
			
		||||
func (r *Ricochet) getMessages() ([]RicochetData, error) {
 | 
			
		||||
	buf, err := r.recv()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.New("Failed to retrieve new messages from the client")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pos := 0
 | 
			
		||||
	finished := false
 | 
			
		||||
	datas := []RicochetData{}
 | 
			
		||||
 | 
			
		||||
	for !finished {
 | 
			
		||||
		size := int(binary.BigEndian.Uint16(buf[pos+0 : pos+2]))
 | 
			
		||||
		channel := int(binary.BigEndian.Uint16(buf[pos+2 : pos+4]))
 | 
			
		||||
 | 
			
		||||
		if pos+size > len(buf) {
 | 
			
		||||
			return datas, errors.New("Partial data packet received")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data := RicochetData{
 | 
			
		||||
			Channel: int32(channel),
 | 
			
		||||
			Data:    buf[pos+4 : pos+size],
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		datas = append(datas, data)
 | 
			
		||||
		pos += size
 | 
			
		||||
		if pos >= len(buf) {
 | 
			
		||||
			finished = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.logger.Printf("Got %d Packets", len(datas))
 | 
			
		||||
	return datas, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// recv reads data from the client, and returns the raw byte array, else error.
 | 
			
		||||
func (r *Ricochet) recv() ([]byte, error) {
 | 
			
		||||
	buf := make([]byte, 4096)
 | 
			
		||||
	n, err := r.conn.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	ret := make([]byte, n)
 | 
			
		||||
	copy(ret[:], buf[:])
 | 
			
		||||
	return ret, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Ricochet) handleFatal(err error, message string) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		r.logger.Fatal(message)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oc := new(OpenConnection)
 | 
			
		||||
	oc.Init(outbound, conn)
 | 
			
		||||
	return oc, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,35 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
// RicochetService provides an interface for building automated ricochet applications.
 | 
			
		||||
type RicochetService interface {
 | 
			
		||||
	OnConnect(serverHostname string)
 | 
			
		||||
	OnAuthenticationChallenge(channelID int32, serverHostname string, serverCookie [16]byte)
 | 
			
		||||
	OnAuthenticationResult(channelID int32, serverHostname string, result bool)
 | 
			
		||||
	OnReady()
 | 
			
		||||
	OnConnect(oc *OpenConnection)
 | 
			
		||||
 | 
			
		||||
	OnOpenChannelRequest(channelID int32, serverHostname string)
 | 
			
		||||
	OnOpenChannelRequestAck(channelID int32, serverHostname string, result bool)
 | 
			
		||||
	OnChannelClose(channelID int32, serverHostname string)
 | 
			
		||||
	// Authentication Management
 | 
			
		||||
	OnAuthenticationRequest(oc *OpenConnection, channelID int32, clientCookie [16]byte)
 | 
			
		||||
	OnAuthenticationChallenge(oc *OpenConnection, channelID int32, serverCookie [16]byte)
 | 
			
		||||
	OnAuthenticationProof(oc *OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool)
 | 
			
		||||
	OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool)
 | 
			
		||||
 | 
			
		||||
	OnContactRequest(channelID string, serverHostname string, nick string, message string)
 | 
			
		||||
	// Contact Management
 | 
			
		||||
	IsKnownContact(hostname string) bool
 | 
			
		||||
	OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string)
 | 
			
		||||
	OnContactRequestAck(oc *OpenConnection, channelID int32, status string)
 | 
			
		||||
 | 
			
		||||
	OnChatMessage(channelID int32, serverHostname string, messageID int32, message string)
 | 
			
		||||
	OnChatMessageAck(channelID int32, serverHostname string, messageID int32)
 | 
			
		||||
	// Managing Channels
 | 
			
		||||
	OnOpenChannelRequest(oc *OpenConnection, channelID int32, channelType string)
 | 
			
		||||
	OnOpenChannelRequestSuccess(oc *OpenConnection, channelID int32)
 | 
			
		||||
	OnChannelClosed(oc *OpenConnection, channelID int32)
 | 
			
		||||
 | 
			
		||||
	// Chat Messages
 | 
			
		||||
	OnChatMessage(oc *OpenConnection, channelID int32, messageID int32, message string)
 | 
			
		||||
	OnChatMessageAck(oc *OpenConnection, channelID int32, messageID int32)
 | 
			
		||||
 | 
			
		||||
	// Handle Errors
 | 
			
		||||
	OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string)
 | 
			
		||||
	OnGenericError(oc *OpenConnection, channelID int32)
 | 
			
		||||
	OnUnknownTypeError(oc *OpenConnection, channelID int32)
 | 
			
		||||
	OnUnauthorizedError(oc *OpenConnection, channelID int32)
 | 
			
		||||
	OnBadUsageError(oc *OpenConnection, channelID int32)
 | 
			
		||||
	OnFailedError(oc *OpenConnection, channelID int32)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,89 +1,178 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/asn1"
 | 
			
		||||
	//"encoding/binary"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/s-rah/go-ricochet/utils"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StandardRicochetService implements all the necessary flows to implement a
 | 
			
		||||
// minimal, protocol compliant Ricochet Service. It can be built on by other
 | 
			
		||||
// applications to produce automated riochet applications.
 | 
			
		||||
type StandardRicochetService struct {
 | 
			
		||||
	ricochet    *Ricochet
 | 
			
		||||
	authHandler map[string]*AuthenticationHandler
 | 
			
		||||
	privateKey  *rsa.PrivateKey
 | 
			
		||||
	hostname    string
 | 
			
		||||
	ricochet       *Ricochet
 | 
			
		||||
	privateKey     *rsa.PrivateKey
 | 
			
		||||
	serverHostname string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) Init(filename string, hostname string) {
 | 
			
		||||
// Init initializes a StandardRicochetService with the cryptographic key given
 | 
			
		||||
// by filename.
 | 
			
		||||
func (srs *StandardRicochetService) Init(filename string) error {
 | 
			
		||||
	srs.ricochet = new(Ricochet)
 | 
			
		||||
	srs.ricochet.Init(true)
 | 
			
		||||
	srs.authHandler = make(map[string]*AuthenticationHandler)
 | 
			
		||||
	srs.hostname = hostname
 | 
			
		||||
	srs.ricochet.Init()
 | 
			
		||||
 | 
			
		||||
	pemData, err := ioutil.ReadFile(filename)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 	    r.logger.Print("Error Reading Private Key: ", err)
 | 
			
		||||
		return errors.New("Could not setup ricochet service: could not read private key")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	block, _ := pem.Decode(pemData)
 | 
			
		||||
	if block == nil || block.Type != "RSA PRIVATE KEY" {
 | 
			
		||||
		//		r.logger.Print("No valid PEM data found")
 | 
			
		||||
		return errors.New("Could not setup ricochet service: no valid PEM data found")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	srs.privateKey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
 | 
			
		||||
	srs.privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("Could not setup ricochet service: could not parse private key")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
 | 
			
		||||
		N: srs.privateKey.PublicKey.N,
 | 
			
		||||
		E: srs.privateKey.PublicKey.E,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	srs.serverHostname = utils.GetTorHostname(publicKeyBytes)
 | 
			
		||||
	log.Printf("Initialised ricochet service for %s", srs.serverHostname)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnConnect(serverHostname string) {
 | 
			
		||||
	srs.authHandler[serverHostname] = new(AuthenticationHandler)
 | 
			
		||||
	clientCookie := srs.authHandler[serverHostname].GenClientCookie()
 | 
			
		||||
	srs.ricochet.Authenticate(1, clientCookie)
 | 
			
		||||
// OnReady is called once a Server has been established (by calling Listen)
 | 
			
		||||
func (srs *StandardRicochetService) OnReady() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Listen starts the ricochet service. Listen must be called before any other method (apart from Init)
 | 
			
		||||
func (srs *StandardRicochetService) Listen(service RicochetService, port int) {
 | 
			
		||||
	srs.ricochet.Server(service, port)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Connect can be called to initiate a new client connection to a server
 | 
			
		||||
func (srs *StandardRicochetService) Connect(hostname string) error {
 | 
			
		||||
	log.Printf("Connecting to...%s", hostname)
 | 
			
		||||
	oc, err := srs.ricochet.Connect(hostname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("Could not connect to: " + hostname)
 | 
			
		||||
	}
 | 
			
		||||
	oc.MyHostname = srs.serverHostname
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnConnect is called when a client or server sucessfully passes Version Negotiation.
 | 
			
		||||
func (srs *StandardRicochetService) OnConnect(oc *OpenConnection) {
 | 
			
		||||
	if oc.Client {
 | 
			
		||||
		log.Printf("Sucessefully Connected to %s", oc.OtherHostname)
 | 
			
		||||
		oc.IsAuthed = true // Connections to Servers are Considered Authenticated by Default
 | 
			
		||||
		oc.Authenticate(1)
 | 
			
		||||
	} else {
 | 
			
		||||
		oc.MyHostname = srs.serverHostname
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnAuthenticationRequest is called when a client requests Authentication
 | 
			
		||||
func (srs *StandardRicochetService) OnAuthenticationRequest(oc *OpenConnection, channelID int32, clientCookie [16]byte) {
 | 
			
		||||
	oc.ConfirmAuthChannel(channelID, clientCookie)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnAuthenticationChallenge constructs a valid authentication challenge to the serverCookie
 | 
			
		||||
func (srs *StandardRicochetService) OnAuthenticationChallenge(channelID int32, serverHostname string, serverCookie [16]byte) {
 | 
			
		||||
	srs.authHandler[serverHostname].AddServerCookie(serverCookie[:])
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnAuthenticationChallenge(oc *OpenConnection, channelID int32, serverCookie [16]byte) {
 | 
			
		||||
	// DER Encode the Public Key
 | 
			
		||||
	publickeyBytes, _ := asn1.Marshal(rsa.PublicKey{
 | 
			
		||||
		N: srs.privateKey.PublicKey.N,
 | 
			
		||||
		E: srs.privateKey.PublicKey.E,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	signature, _ := rsa.SignPKCS1v15(nil, srs.privateKey, crypto.SHA256, srs.authHandler[serverHostname].GenChallenge(srs.hostname, serverHostname))
 | 
			
		||||
	// TODO Handle Errors
 | 
			
		||||
	signatureBytes := make([]byte, 128)
 | 
			
		||||
	copy(signatureBytes[:], signature[:])
 | 
			
		||||
	srs.ricochet.SendProof(1, publickeyBytes, signatureBytes)
 | 
			
		||||
	oc.SendProof(1, serverCookie, publickeyBytes, srs.privateKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) Ricochet() *Ricochet {
 | 
			
		||||
	return srs.ricochet
 | 
			
		||||
// OnAuthenticationProof is called when a client sends Proof for an existing authentication challenge
 | 
			
		||||
func (srs *StandardRicochetService) OnAuthenticationProof(oc *OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool) {
 | 
			
		||||
	result := oc.ValidateProof(channelID, publicKey, signature)
 | 
			
		||||
	oc.SendAuthenticationResult(channelID, result, isKnownContact)
 | 
			
		||||
	oc.IsAuthed = result
 | 
			
		||||
	oc.CloseChannel(channelID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnAuthenticationResult(channelID int32, serverHostname string, result bool) {
 | 
			
		||||
 | 
			
		||||
// OnAuthenticationResult is called once a server has returned the result of the Proof Verification
 | 
			
		||||
func (srs *StandardRicochetService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
 | 
			
		||||
	oc.IsAuthed = result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnOpenChannelRequest(channelID int32, serverHostname string) {
 | 
			
		||||
	srs.ricochet.AckOpenChannel(channelID, true)
 | 
			
		||||
// IsKnownContact allows a caller to determine if a hostname an authorized contact.
 | 
			
		||||
func (srs *StandardRicochetService) IsKnownContact(hostname string) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnOpenChannelRequestAck(channelID int32, serverHostname string, result bool) {
 | 
			
		||||
// OnContactRequest is called when a client sends a new contact request
 | 
			
		||||
func (srs *StandardRicochetService) OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnChannelClose(channelID int32, serverHostname string) {
 | 
			
		||||
// OnContactRequestAck is called when a server sends a reply to an existing contact request
 | 
			
		||||
func (srs *StandardRicochetService) OnContactRequestAck(oc *OpenConnection, channelID int32, status string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnContactRequest(channelID string, serverHostname string, nick string, message string) {
 | 
			
		||||
// OnOpenChannelRequest is called when a client or server requests to open a new channel
 | 
			
		||||
func (srs *StandardRicochetService) OnOpenChannelRequest(oc *OpenConnection, channelID int32, channelType string) {
 | 
			
		||||
	oc.AckOpenChannel(channelID, channelType)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnChatMessage(channelID int32, serverHostname string, messageId int32, message string) {
 | 
			
		||||
	srs.ricochet.AckChatMessage(channelID, messageId)
 | 
			
		||||
// OnOpenChannelRequestSuccess is called when a client or server responds to an open channel request
 | 
			
		||||
func (srs *StandardRicochetService) OnOpenChannelRequestSuccess(oc *OpenConnection, channelID int32) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (srs *StandardRicochetService) OnChatMessageAck(channelID int32, serverHostname string, messageId int32) {
 | 
			
		||||
// OnChannelClose is called when a client or server closes an existing channel
 | 
			
		||||
func (srs *StandardRicochetService) OnChannelClosed(oc *OpenConnection, channelID int32) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnChatMessage is called when a new chat message is received.
 | 
			
		||||
func (srs *StandardRicochetService) OnChatMessage(oc *OpenConnection, channelID int32, messageID int32, message string) {
 | 
			
		||||
	oc.AckChatMessage(channelID, messageID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnChatMessageAck is called when a new chat message is ascknowledged.
 | 
			
		||||
func (srs *StandardRicochetService) OnChatMessageAck(oc *OpenConnection, channelID int32, messageID int32) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnFailedChannelOpen is called when a server fails to open a channel
 | 
			
		||||
func (srs *StandardRicochetService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
 | 
			
		||||
	oc.UnsetChannel(channelID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnGenericError is called when a generalized error is returned from the peer
 | 
			
		||||
func (srs *StandardRicochetService) OnGenericError(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	oc.RejectOpenChannel(channelID, "GenericError")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//OnUnknownTypeError is called when an unknown type error is returned from the peer
 | 
			
		||||
func (srs *StandardRicochetService) OnUnknownTypeError(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	oc.RejectOpenChannel(channelID, "UnknownTypeError")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnUnauthorizedError is called when an unathorized error is returned from the peer
 | 
			
		||||
func (srs *StandardRicochetService) OnUnauthorizedError(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	oc.RejectOpenChannel(channelID, "UnauthorizedError")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnBadUsageError is called when a bad usage error is returned from the peer
 | 
			
		||||
func (srs *StandardRicochetService) OnBadUsageError(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	oc.RejectOpenChannel(channelID, "BadUsageError")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnFailedError is called when a failed error is returned from the peer
 | 
			
		||||
func (srs *StandardRicochetService) OnFailedError(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	oc.RejectOpenChannel(channelID, "FailedError")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import "time"
 | 
			
		||||
import "log"
 | 
			
		||||
 | 
			
		||||
type TestBadUsageService struct {
 | 
			
		||||
	StandardRicochetService
 | 
			
		||||
	BadUsageErrorCount    int
 | 
			
		||||
	UnknownTypeErrorCount int
 | 
			
		||||
	ChannelClosed         int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestBadUsageService) OnConnect(oc *OpenConnection) {
 | 
			
		||||
	if oc.Client {
 | 
			
		||||
		oc.OpenChannel(17, "im.ricochet.auth.hidden-service") // Fail because no Extension
 | 
			
		||||
	}
 | 
			
		||||
	ts.StandardRicochetService.OnConnect(oc)
 | 
			
		||||
	if oc.Client {
 | 
			
		||||
		oc.Authenticate(103) // Should Fail because cannot open more than one auth-hidden-service channel at once
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestBadUsageService) OnAuthenticationProof(oc *OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool) {
 | 
			
		||||
	oc.Authenticate(2)                       // Try to authenticate again...will fail servers don't auth
 | 
			
		||||
	oc.SendContactRequest(4, "test", "test") // Only clients can send contact requests
 | 
			
		||||
	ts.StandardRicochetService.OnAuthenticationProof(oc, channelID, publicKey, signature, isKnownContact)
 | 
			
		||||
	oc.OpenChatChannel(5) // Fail because server can only open even numbered channels
 | 
			
		||||
	oc.OpenChatChannel(3) // Fail because already in use...
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnContactRequest is called when a client sends a new contact request
 | 
			
		||||
func (ts *TestBadUsageService) OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string) {
 | 
			
		||||
	oc.AckContactRequestOnResponse(channelID, "Pending") // Done to keep the contact request channel open
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestBadUsageService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
 | 
			
		||||
	ts.StandardRicochetService.OnAuthenticationResult(oc, channelID, result, isKnownContact)
 | 
			
		||||
 | 
			
		||||
	oc.OpenChatChannel(3) // Succeed
 | 
			
		||||
	oc.OpenChatChannel(3) // Should fail as duplicate (channel already in use)
 | 
			
		||||
 | 
			
		||||
	oc.OpenChatChannel(6) // Should fail because clients are not allowed to open even numbered channels
 | 
			
		||||
 | 
			
		||||
	oc.SendMessage(101, "test") // Should fail as 101 doesn't exist
 | 
			
		||||
 | 
			
		||||
	oc.Authenticate(1) // Try to authenticate again...will fail because we have already authenticated
 | 
			
		||||
 | 
			
		||||
	oc.OpenChannel(19, "im.ricochet.contact.request") // Will Fail
 | 
			
		||||
	oc.SendContactRequest(11, "test", "test")         // Succeed
 | 
			
		||||
	oc.SendContactRequest(13, "test", "test")         // Trigger singleton contact request check
 | 
			
		||||
 | 
			
		||||
	oc.OpenChannel(15, "im.ricochet.not-a-real-type") // Fail UnknownType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnChannelClose is called when a client or server closes an existing channel
 | 
			
		||||
func (ts *TestBadUsageService) OnChannelClosed(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	if channelID == 101 {
 | 
			
		||||
		log.Printf("Received Channel Closed: %v", channelID)
 | 
			
		||||
		ts.ChannelClosed++
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestBadUsageService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
 | 
			
		||||
	log.Printf("Failed Channel Open %v %v", channelID, errorType)
 | 
			
		||||
	ts.StandardRicochetService.OnFailedChannelOpen(oc, channelID, errorType)
 | 
			
		||||
	if errorType == "BadUsageError" {
 | 
			
		||||
		ts.BadUsageErrorCount++
 | 
			
		||||
	} else if errorType == "UnknownTypeError" {
 | 
			
		||||
		ts.UnknownTypeErrorCount++
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestBadUsageService) IsKnownContact(hostname string) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBadUsageServer(t *testing.T) {
 | 
			
		||||
	ricochetService := new(TestBadUsageService)
 | 
			
		||||
	err := ricochetService.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService.Listen(ricochetService, 9884)
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
 | 
			
		||||
	ricochetService2 := new(TestBadUsageService)
 | 
			
		||||
	err = ricochetService2.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService2.Listen(ricochetService2, 9885)
 | 
			
		||||
	err = ricochetService2.Connect("127.0.0.1:9884|kwke2hntvyfqm7dr")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not connect to ricochet service:  %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 3)
 | 
			
		||||
	if ricochetService2.ChannelClosed != 1 || ricochetService2.BadUsageErrorCount != 7 || ricochetService.BadUsageErrorCount != 4 || ricochetService2.UnknownTypeErrorCount != 1 {
 | 
			
		||||
		t.Errorf("Invalid number of errors seen Closed:%v, Client Bad Usage:%v UnknownTypeErrorCount: %v, Server Bad Usage: %v ", ricochetService2.ChannelClosed, ricochetService2.BadUsageErrorCount, ricochetService2.UnknownTypeErrorCount, ricochetService.BadUsageErrorCount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import "time"
 | 
			
		||||
import "log"
 | 
			
		||||
 | 
			
		||||
type TestService struct {
 | 
			
		||||
	StandardRicochetService
 | 
			
		||||
	ReceivedMessage bool
 | 
			
		||||
	KnownContact    bool // Mocking contact request
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
 | 
			
		||||
	ts.StandardRicochetService.OnAuthenticationResult(oc, channelID, result, isKnownContact)
 | 
			
		||||
	if !isKnownContact {
 | 
			
		||||
		log.Printf("Sending Contact Request")
 | 
			
		||||
		oc.SendContactRequest(3, "test", "test")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestService) OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string) {
 | 
			
		||||
	ts.StandardRicochetService.OnContactRequest(oc, channelID, nick, message)
 | 
			
		||||
	oc.AckContactRequestOnResponse(channelID, "Pending")
 | 
			
		||||
	oc.AckContactRequest(channelID, "Accepted")
 | 
			
		||||
	ts.KnownContact = true
 | 
			
		||||
	oc.CloseChannel(channelID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestService) OnOpenChannelRequestSuccess(oc *OpenConnection, channelID int32) {
 | 
			
		||||
	ts.StandardRicochetService.OnOpenChannelRequestSuccess(oc, channelID)
 | 
			
		||||
	oc.SendMessage(channelID, "TEST MESSAGE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestService) OnContactRequestAck(oc *OpenConnection, channelID int32, status string) {
 | 
			
		||||
	ts.StandardRicochetService.OnContactRequestAck(oc, channelID, status)
 | 
			
		||||
	if status == "Accepted" {
 | 
			
		||||
		log.Printf("Got accepted contact request")
 | 
			
		||||
		ts.KnownContact = true
 | 
			
		||||
		oc.OpenChatChannel(5)
 | 
			
		||||
	} else if status == "Pending" {
 | 
			
		||||
		log.Printf("Got pending contact request")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestService) OnChatMessage(oc *OpenConnection, channelID int32, messageID int32, message string) {
 | 
			
		||||
	ts.StandardRicochetService.OnChatMessage(oc, channelID, messageID, message)
 | 
			
		||||
	if message == "TEST MESSAGE" {
 | 
			
		||||
		ts.ReceivedMessage = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestService) IsKnownContact(hostname string) bool {
 | 
			
		||||
	return ts.KnownContact
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer(t *testing.T) {
 | 
			
		||||
	ricochetService := new(TestService)
 | 
			
		||||
	err := ricochetService.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService.Listen(ricochetService, 9878)
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
 | 
			
		||||
	ricochetService2 := new(TestService)
 | 
			
		||||
	err = ricochetService2.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService2.Listen(ricochetService2, 9879)
 | 
			
		||||
	err = ricochetService2.Connect("127.0.0.1:9878|kwke2hntvyfqm7dr")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not connect to ricochet service:  %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 5) // Wait a bit longer
 | 
			
		||||
	if !ricochetService.ReceivedMessage {
 | 
			
		||||
		t.Errorf("Test server did not receive message")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServerInvalidKey(t *testing.T) {
 | 
			
		||||
	ricochetService := new(TestService)
 | 
			
		||||
	err := ricochetService.Init("./private_key.does.not.exist")
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Errorf("Should not have initate ricochet service, private key should not exist")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServerCouldNotConnect(t *testing.T) {
 | 
			
		||||
	ricochetService := new(TestService)
 | 
			
		||||
	err := ricochetService.Init("./private_key")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ricochetService.Connect("127.0.0.1:65535|kwke2hntvyfqm7dr")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Errorf("Should not have been been able to connect to 127.0.0.1:65535|kwke2hntvyfqm7dr")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import "time"
 | 
			
		||||
import "log"
 | 
			
		||||
 | 
			
		||||
// The purpose of this test is to exercise the Unauthorized Error flows that occur
 | 
			
		||||
// when a client attempts to open a Chat Channel or Send a Contact Reuqest before Authentication
 | 
			
		||||
// itself with the Service.
 | 
			
		||||
 | 
			
		||||
type TestUnauthorizedService struct {
 | 
			
		||||
	StandardRicochetService
 | 
			
		||||
	FailedToOpen int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestUnauthorizedService) OnConnect(oc *OpenConnection) {
 | 
			
		||||
	if oc.Client {
 | 
			
		||||
		log.Printf("Attempting Authentication Not Authorized")
 | 
			
		||||
		oc.IsAuthed = true // Connections to Servers are Considered Authenticated by Default
 | 
			
		||||
		// REMOVED Authenticate
 | 
			
		||||
		oc.OpenChatChannel(5)
 | 
			
		||||
		oc.SendContactRequest(3, "test", "test")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestUnauthorizedService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
 | 
			
		||||
	oc.UnsetChannel(channelID)
 | 
			
		||||
	if errorType == "UnauthorizedError" {
 | 
			
		||||
		ts.FailedToOpen++
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnauthorizedClientReject(t *testing.T) {
 | 
			
		||||
	ricochetService := new(TestService)
 | 
			
		||||
	err := ricochetService.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService.Listen(ricochetService, 9880)
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
 | 
			
		||||
	ricochetService2 := new(TestUnauthorizedService)
 | 
			
		||||
	err = ricochetService2.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService2.Listen(ricochetService2, 9881)
 | 
			
		||||
	err = ricochetService2.Connect("127.0.0.1:9880|kwke2hntvyfqm7dr")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not connect to ricochet service:  %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
	if ricochetService2.FailedToOpen != 2 {
 | 
			
		||||
		t.Errorf("Test server did not reject open channels with unauthorized error")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import "time"
 | 
			
		||||
import "log"
 | 
			
		||||
 | 
			
		||||
type TestUnknownContactService struct {
 | 
			
		||||
	StandardRicochetService
 | 
			
		||||
	FailedToOpen bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestUnknownContactService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
 | 
			
		||||
	log.Printf("Authentication Result")
 | 
			
		||||
	ts.StandardRicochetService.OnAuthenticationResult(oc, channelID, result, isKnownContact)
 | 
			
		||||
	oc.OpenChatChannel(5)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestUnknownContactService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
 | 
			
		||||
	log.Printf("Failed Channel Open %v", errorType)
 | 
			
		||||
	oc.UnsetChannel(channelID)
 | 
			
		||||
	if errorType == "UnauthorizedError" {
 | 
			
		||||
		ts.FailedToOpen = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ts *TestUnknownContactService) IsKnownContact(hostname string) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnknownContactServer(t *testing.T) {
 | 
			
		||||
	ricochetService := new(StandardRicochetService)
 | 
			
		||||
	err := ricochetService.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService.Listen(ricochetService, 9882)
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
 | 
			
		||||
	ricochetService2 := new(TestUnknownContactService)
 | 
			
		||||
	err = ricochetService2.Init("./private_key")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not initate ricochet service: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go ricochetService2.Listen(ricochetService2, 9883)
 | 
			
		||||
	err = ricochetService2.Connect("127.0.0.1:9882|kwke2hntvyfqm7dr")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Could not connect to ricochet service:  %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
	if !ricochetService2.FailedToOpen {
 | 
			
		||||
		t.Errorf("Test server did receive message should have failed")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
import "log"
 | 
			
		||||
 | 
			
		||||
func RecoverFromError() {
 | 
			
		||||
	if r := recover(); r != nil {
 | 
			
		||||
		// This should only really happen if there is a failure de/serializing. If
 | 
			
		||||
		// this does happen then we currently error. In the future we might be
 | 
			
		||||
		// able to make this nicer.
 | 
			
		||||
		log.Fatalf("Recovered from panic() - this really shouldn't happen. Reason: %v", r)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CheckError(err error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("%v", err))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RicochetData is a structure containing the raw data and the channel it the
 | 
			
		||||
// message originated on.
 | 
			
		||||
type RicochetData struct {
 | 
			
		||||
	Channel int32
 | 
			
		||||
	Data    []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RicochetNetworkInterface abstract operations that interact with ricochet's
 | 
			
		||||
// packet layer.
 | 
			
		||||
type RicochetNetworkInterface interface {
 | 
			
		||||
	Recv(conn net.Conn) ([]byte, error)
 | 
			
		||||
	SendRicochetPacket(conn net.Conn, channel int32, data []byte)
 | 
			
		||||
	RecvRicochetPackets(conn net.Conn) ([]RicochetData, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RicochetNetwork is a concrete implementation of the RicochetNetworkInterface
 | 
			
		||||
type RicochetNetwork struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Recv reads data from the client, and returns the raw byte array, else error.
 | 
			
		||||
func (rn *RicochetNetwork) Recv(conn net.Conn) ([]byte, error) {
 | 
			
		||||
	buf := make([]byte, 4096)
 | 
			
		||||
	n, err := conn.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	ret := make([]byte, n)
 | 
			
		||||
	copy(ret[:], buf[:])
 | 
			
		||||
	return ret, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendRicochetPacket places the data into a structure needed for the client to
 | 
			
		||||
// decode the packet and writes the packet to the network.
 | 
			
		||||
func (rn *RicochetNetwork) SendRicochetPacket(conn net.Conn, channel int32, data []byte) {
 | 
			
		||||
	header := make([]byte, 4+len(data))
 | 
			
		||||
	header[0] = byte(len(header) >> 8)
 | 
			
		||||
	header[1] = byte(len(header) & 0x00FF)
 | 
			
		||||
	header[2] = 0x00
 | 
			
		||||
	header[3] = byte(channel)
 | 
			
		||||
	copy(header[4:], data[:])
 | 
			
		||||
	conn.Write(header)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RecvRicochetPackets returns an array of new messages received from the ricochet client
 | 
			
		||||
func (rn *RicochetNetwork) RecvRicochetPackets(conn net.Conn) ([]RicochetData, error) {
 | 
			
		||||
	buf, err := rn.Recv(conn)
 | 
			
		||||
	if err != nil && len(buf) < 4 {
 | 
			
		||||
		return nil, errors.New("failed to retrieve new messages from the client")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pos := 0
 | 
			
		||||
	finished := false
 | 
			
		||||
	var datas []RicochetData
 | 
			
		||||
 | 
			
		||||
	for !finished {
 | 
			
		||||
		size := int(binary.BigEndian.Uint16(buf[pos+0 : pos+2]))
 | 
			
		||||
		channel := int(binary.BigEndian.Uint16(buf[pos+2 : pos+4]))
 | 
			
		||||
 | 
			
		||||
		if size < 4 {
 | 
			
		||||
			return datas, errors.New("invalid ricochet packet received (size=" + strconv.Itoa(size) + ")")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if pos+size > len(buf) {
 | 
			
		||||
			return datas, errors.New("partial data packet received")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data := RicochetData{}
 | 
			
		||||
		data.Channel = int32(channel)
 | 
			
		||||
 | 
			
		||||
		if pos+4 >= len(buf) {
 | 
			
		||||
			data.Data = make([]byte, 0)
 | 
			
		||||
		} else {
 | 
			
		||||
			data.Data = buf[pos+4 : pos+size]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		datas = append(datas, data)
 | 
			
		||||
		pos += size
 | 
			
		||||
		if pos >= len(buf) {
 | 
			
		||||
			finished = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return datas, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import "net"
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type MockConn struct {
 | 
			
		||||
	Written    []byte
 | 
			
		||||
	MockOutput []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) Read(b []byte) (int, error) {
 | 
			
		||||
	copy(b[:], mc.MockOutput[:])
 | 
			
		||||
	return len(mc.MockOutput), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) Write(written []byte) (int, error) {
 | 
			
		||||
	mc.Written = written
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) LocalAddr() net.Addr {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) RemoteAddr() net.Addr {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) Close() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) SetDeadline(t time.Time) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) SetReadDeadline(t time.Time) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MockConn) SetWriteDeadline(t time.Time) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSentRicochetPacket(t *testing.T) {
 | 
			
		||||
	conn := new(MockConn)
 | 
			
		||||
	rni := RicochetNetwork{}
 | 
			
		||||
	rni.SendRicochetPacket(conn, 1, []byte{})
 | 
			
		||||
	if len(conn.Written) != 4 && conn.Written[0] != 0x00 && conn.Written[1] != 0x00 && conn.Written[2] != 0x01 && conn.Written[3] != 0x00 {
 | 
			
		||||
		t.Errorf("Output of SentRicochetPacket was Unexpected: %x", conn.Written)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecv(t *testing.T) {
 | 
			
		||||
	conn := new(MockConn)
 | 
			
		||||
	conn.MockOutput = []byte{0xDE, 0xAD, 0xBE, 0xEF}
 | 
			
		||||
	rni := RicochetNetwork{}
 | 
			
		||||
	buf, err := rni.Recv(conn)
 | 
			
		||||
	if err != nil || len(buf) != 4 || buf[0] != 0xDE || buf[1] != 0xAD || buf[2] != 0xBE || buf[3] != 0xEF {
 | 
			
		||||
		t.Errorf("Output of Recv was Unexpected: %x", buf)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecvRicochetPacket(t *testing.T) {
 | 
			
		||||
	conn := new(MockConn)
 | 
			
		||||
	conn.MockOutput = []byte{00, 0x04, 0x00, 0x01}
 | 
			
		||||
 | 
			
		||||
	rni := RicochetNetwork{}
 | 
			
		||||
	rp, err := rni.RecvRicochetPackets(conn)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error extracting ricochet packets: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(rp) != 1 {
 | 
			
		||||
		t.Errorf("unexpected number of ricochet packets: %d", len(rp))
 | 
			
		||||
	} else {
 | 
			
		||||
		if rp[0].Channel != 1 {
 | 
			
		||||
			t.Errorf("channel number is Unexpected expected 1: %d", rp[0].Channel)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(rp[0].Data) != 0 {
 | 
			
		||||
			t.Errorf("expected emptry packet, instead got %x", rp[0].Data)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecvRicochetPacketInvalid(t *testing.T) {
 | 
			
		||||
	conn := new(MockConn)
 | 
			
		||||
	conn.MockOutput = []byte{00, 0x01, 0x00, 0x01}
 | 
			
		||||
 | 
			
		||||
	rni := RicochetNetwork{}
 | 
			
		||||
	_, err := rni.RecvRicochetPackets(conn)
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Errorf("recv should have errored due to invalid packets %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conn.MockOutput = []byte{00, 0x0A, 0x00, 0x01}
 | 
			
		||||
 | 
			
		||||
	_, err = rni.RecvRicochetPackets(conn)
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Errorf("recv should have errored due to invalid packets %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecvRicochetPacketLong(t *testing.T) {
 | 
			
		||||
	conn := new(MockConn)
 | 
			
		||||
	conn.MockOutput = []byte{0x00, 0x08, 0x00, 0xFF, 0xDE, 0xAD, 0xBE, 0xEF}
 | 
			
		||||
 | 
			
		||||
	rni := RicochetNetwork{}
 | 
			
		||||
	rp, err := rni.RecvRicochetPackets(conn)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error extracting ricochet packets: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(rp) != 1 {
 | 
			
		||||
		t.Errorf("unexpected number of ricochet packets: %d", len(rp))
 | 
			
		||||
	} else {
 | 
			
		||||
		if rp[0].Channel != 255 {
 | 
			
		||||
			t.Errorf("channel number is Unexpected expected 255 got: %d", rp[0].Channel)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(rp[0].Data) != 4 || rp[0].Data[0] != 0xDE || rp[0].Data[1] != 0xAD || rp[0].Data[2] != 0xBE || rp[0].Data[3] != 0xEF {
 | 
			
		||||
			t.Errorf("expected 0xDEADBEEF packet, instead got %x", rp[0].Data)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRecvRicochetPacketMultiplex(t *testing.T) {
 | 
			
		||||
	conn := new(MockConn)
 | 
			
		||||
	conn.MockOutput = []byte{0x00, 0x04, 0x00, 0x01, 0x00, 0x08, 0x00, 0xFF, 0xDE, 0xAD, 0xBE, 0xEF}
 | 
			
		||||
 | 
			
		||||
	rni := RicochetNetwork{}
 | 
			
		||||
	rp, err := rni.RecvRicochetPackets(conn)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error extracting ricochet packets: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(rp) != 2 {
 | 
			
		||||
		t.Errorf("unexpected number of ricochet packets, expected 2 gt: %d", len(rp))
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		if rp[0].Channel != 1 {
 | 
			
		||||
			t.Errorf("channel number is Unexpected expected 1: %d", rp[0].Channel)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(rp[0].Data) != 0 {
 | 
			
		||||
			t.Errorf("expected empty packet, instead got %x", rp[0].Data)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if rp[1].Channel != 255 {
 | 
			
		||||
			t.Errorf("channel number is Unexpected expected 255 got: %d", rp[0].Channel)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(rp[1].Data) != 4 || rp[1].Data[0] != 0xDE || rp[1].Data[1] != 0xAD || rp[1].Data[2] != 0xBE || rp[1].Data[3] != 0xEF {
 | 
			
		||||
			t.Errorf("expected 0xDEADBEEF packet, instead got %x", rp[0].Data)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package goricochet
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
| 
						 | 
				
			
			@ -43,5 +43,6 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		return nil, "", errors.New("Cannot Dial Remote Ricochet Address")
 | 
			
		||||
	}
 | 
			
		||||
	//conn.SetDeadline(time.Now().Add(5 * time.Second))
 | 
			
		||||
	return conn, resolvedHostname, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/sha1"
 | 
			
		||||
	"encoding/base32"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetTorHostname takes a []byte contained a DER-encoded RSA public key
 | 
			
		||||
// and returns the first 16 bytes of the base32 encoded sha1 hash of the key.
 | 
			
		||||
// This is the onion hostname of the tor service represented by the public key.
 | 
			
		||||
func GetTorHostname(publicKeyBytes []byte) string {
 | 
			
		||||
	h := sha1.New()
 | 
			
		||||
	h.Write(publicKeyBytes)
 | 
			
		||||
	sha1bytes := h.Sum(nil)
 | 
			
		||||
 | 
			
		||||
	data := base32.StdEncoding.EncodeToString(sha1bytes)
 | 
			
		||||
	return strings.ToLower(data[0:16])
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/asn1"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const privateKeyData = `-----BEGIN RSA PRIVATE KEY-----
 | 
			
		||||
MIICXgIBAAKBgQC3xEJBH4oVFaotPJw6dezx67Gv4Xukw8CZRGqNFO8yF7Rejtcj
 | 
			
		||||
/0RTqqZwj6H6FjxY60dgYnN6IphW0juemNZhxOXeM/5Gb5xO+kWGi5Qt87aSDxnA
 | 
			
		||||
MDLgqw79ihuD3m1C1TBz0olmjXPU1VtadZuZcVBST7SLs2/k55GNNr7BoQIDAQAB
 | 
			
		||||
AoGBAK3ybVCdnSQWLM7DJ5LC23Wnx7sXceVlkiLCOyWuYjiFbatwBD/DupaD2yaD
 | 
			
		||||
HyzN7XOxyg93QZ2jr5XHTL30KEAn/3akNBsX3sjHZnjVfTwD5+oZKd7HYMMxekWf
 | 
			
		||||
87TIx2IHvGEo2NaFMLkEZ5TX3Gre8CYOofjFcpj4661ZfYp9AkEA9I0EmQX26ibs
 | 
			
		||||
CRGkwPuEj5q5N/PmIHgMWr1pepOlmzJjnxy6SI3NUwmzKrqM6YUM8loSywqfVMrJ
 | 
			
		||||
RVzA5jp76wJBAMBeu2hS8KcUTIu66j0pXMhI5wDA3yLiO53TEMwufCPXcaWUMH+e
 | 
			
		||||
5AIPL7aZ8ouf895OH0TZKxPNMnbrJ+5F0aMCQDoi/CDUxipMLnjJdP1bzdvF0Jp4
 | 
			
		||||
pRC6+VTpCpZVW11V0VEWJ0LwUwuWlr1ls/If60ACIc2bLN2fh9Gxhzo0VRkCQQCS
 | 
			
		||||
nKCAVhYLgLEGHaLAknGgQ8+rB1QIphuBoYc/1n3OYzi+VT7RRSvJVgGrTZFJUNLw
 | 
			
		||||
LuIt+sWWBeHcOETqmFO5AkEAwwfcxs8QZtX6hCj2MTPi8Q28LIoA/M6eAqYc2I0B
 | 
			
		||||
eXxf2J2Qco7sMmBLr1Jp3jZNd5W2fMtlhUZAomOj4piVOA==
 | 
			
		||||
-----END RSA PRIVATE KEY-----`
 | 
			
		||||
 | 
			
		||||
func TestGetTorHostname(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	block, _ := pem.Decode([]byte(privateKeyData))
 | 
			
		||||
	privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
 | 
			
		||||
 | 
			
		||||
	// DER Encode the Public Key
 | 
			
		||||
	publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
 | 
			
		||||
		N: privateKey.PublicKey.N,
 | 
			
		||||
		E: privateKey.PublicKey.E,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	hostname := GetTorHostname(publicKeyBytes)
 | 
			
		||||
	t.Log(hostname)
 | 
			
		||||
	if hostname != "kwke2hntvyfqm7dr" {
 | 
			
		||||
		t.Errorf("Hostname %s does not equal %s", hostname, "kwke2hntvyfqm7dr")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue