Refactor of APIs to make testing easier.

Also travis.ci integration
This commit is contained in:
Sarah Jamie Lewis 2016-02-27 16:20:40 -08:00
parent 91036c918d
commit 2353fc41e2
8 changed files with 198 additions and 72 deletions

3
.travis.yml Normal file
View File

@ -0,0 +1,3 @@
language: go
script: go get github.com/golang/protobuf/proto && go get h12.me/socks && go test

48
authhandler.go Normal file
View File

@ -0,0 +1,48 @@
package goricochet
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
)
type AuthenticationHandler struct {
clientCookie [16]byte
serverCookie [16]byte
}
func (ah *AuthenticationHandler) AddClientCookie(cookie []byte) {
copy(ah.clientCookie[:], cookie[:16])
}
func (ah *AuthenticationHandler) AddServerCookie(cookie []byte) {
copy(ah.serverCookie[:], cookie[:16])
}
func (ah *AuthenticationHandler) GenRandom() [16]byte {
var cookie [16]byte
io.ReadFull(rand.Reader, cookie[:])
return cookie
}
func (ah *AuthenticationHandler) GenClientCookie() [16]byte {
ah.clientCookie = ah.GenRandom()
return ah.clientCookie
}
func (ah *AuthenticationHandler) GenServerCookie() [16]byte {
ah.serverCookie = ah.GenRandom()
return ah.serverCookie
}
func (ah *AuthenticationHandler) GenChallenge(clientHostname string, serverHostname string) []byte {
key := make([]byte, 32)
copy(key[0:16], ah.clientCookie[:])
copy(key[16:], ah.serverCookie[:])
value := []byte(clientHostname + serverHostname)
mac := hmac.New(sha256.New, key)
mac.Write(value)
hmac := mac.Sum(nil)
return hmac
}

16
authhandler_test.go Normal file
View File

@ -0,0 +1,16 @@
package goricochet
import "testing"
import "bytes"
func TestAuthHandler(t *testing.T) {
authHandler := new(AuthenticationHandler)
authHandler.AddClientCookie([]byte("abcdefghijklmnop"))
authHandler.AddServerCookie([]byte("qrstuvwxyz012345"))
challenge := authHandler.GenChallenge("test.onion", "notareal.onion")
expectedChallenge := []byte{0xf5, 0xdb, 0xfd, 0xf0, 0x3d, 0x94, 0x14, 0xf1, 0x4b, 0x37, 0x93, 0xe2, 0xa5, 0x11, 0x4a, 0x98, 0x31, 0x90, 0xea, 0xb8, 0x95, 0x7a, 0x2e, 0xaa, 0xd0, 0xd2, 0x0c, 0x74, 0x95, 0xba, 0xab, 0x73}
t.Log(challenge, expectedChallenge)
if bytes.Compare(challenge[:], expectedChallenge[:]) != 0 {
t.Errorf("AuthenticationHandler Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge)
}
}

37
controlbuilder.go Normal file
View File

@ -0,0 +1,37 @@
package goricochet
import (
"github.com/s-rah/go-ricochet/control"
"github.com/s-rah/go-ricochet/auth"
"github.com/golang/protobuf/proto"
)
type ControlBuilder struct {
}
func (cb *ControlBuilder) OpenChatChannel(channelId int32) ([]byte,error) {
oc := &Protocol_Data_Control.OpenChannel{
ChannelIdentifier: proto.Int32(channelId),
ChannelType: proto.String("im.ricochet.chat"),
}
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
}
return proto.Marshal(pc)
}
func (cb* ControlBuilder) OpenAuthenticationChannel(channelId int32, clientCookie [16]byte) ([]byte,error) {
oc := &Protocol_Data_Control.OpenChannel{
ChannelIdentifier: proto.Int32(channelId),
ChannelType: proto.String("im.ricochet.auth.hidden-service" ),
}
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
if err != nil {
return nil, err
}
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
}
return proto.Marshal(pc)
}

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/s-rah/go-ricochet" "github.com/s-rah/go-ricochet"
"time"
) )
func main() { func main() {
@ -17,7 +18,8 @@ func main() {
// ricochet.SendContactRequest("EchoBot", "I'm an EchoBot") // ricochet.SendContactRequest("EchoBot", "I'm an EchoBot")
go ricochet.ListenAndWait() go ricochet.ListenAndWait()
ricochet.OpenChannel("im.ricochet.chat", 5) ricochet.OpenChatChannel(5)
time.Sleep(time.Second * 1)
ricochet.SendMessage("Hi I'm an echo bot, I echo what you say! ", 5) ricochet.SendMessage("Hi I'm an echo bot, I echo what you say! ", 5)
for true { for true {

47
networkresolver.go Normal file
View File

@ -0,0 +1,47 @@
package goricochet
import (
"errors"
"h12.me/socks"
"net"
"strings"
)
// NetworkResolver allows a client to resolve various hostnames to connections
// The supported types are onions address are:
// * ricochet:jlq67qzo6s4yp3sp
// * jlq67qzo6s4yp3sp
// * 127.0.0.1:55555|jlq67qzo6s4yp3sp - Localhost Connection
type NetworkResolver struct {
}
// Resolve takes a hostname and returns a net.Conn to the derived endpoint
func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
if strings.HasPrefix(hostname, "127.0.0.1") {
addrParts := strings.Split(hostname, "|")
tcpAddr, err := net.ResolveTCPAddr("tcp", addrParts[0])
if err != nil {
return nil, "", errors.New("Cannot Resolve Local TCP Address")
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
return nil, "", errors.New("Cannot Dial Local TCP Address")
}
// return just the onion address, not the local override for the hostname
return conn, addrParts[1], nil
}
resolvedHostname := hostname
if strings.HasPrefix(hostname, "ricochet:") {
addrParts := strings.Split(hostname, ":")
resolvedHostname = addrParts[1]
}
dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, "127.0.0.1:9050")
conn, err := dialSocksProxy("", resolvedHostname+".onion:9878")
if err != nil {
return nil, "", errors.New("Cannot Dial Remote Ricochet Address")
}
return conn, resolvedHostname, nil
}

View File

@ -2,10 +2,7 @@ package goricochet
import ( import (
"crypto" "crypto"
"crypto/hmac"
"crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/binary" "encoding/binary"
@ -17,13 +14,10 @@ import (
"github.com/s-rah/go-ricochet/chat" "github.com/s-rah/go-ricochet/chat"
"github.com/s-rah/go-ricochet/contact" "github.com/s-rah/go-ricochet/contact"
"github.com/s-rah/go-ricochet/control" "github.com/s-rah/go-ricochet/control"
"h12.me/socks"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"os" "os"
"strings"
) )
// MessageType details the different kinds of messages used by Ricochet // MessageType details the different kinds of messages used by Ricochet
@ -96,6 +90,19 @@ func (r *Ricochet) Init(filename string, debugLog bool) {
r.channel = make(chan RicochetMessage) r.channel = make(chan RicochetMessage)
} }
func (r *Ricochet) StartService(server RicochetService, port string) {
// Listen
ln, _ := net.Listen("tcp", port)
conn, _ := ln.Accept()
go r.runService(conn, server)
}
func (r *Ricochet) runService(conn net.Conn, server RicochetService) {
// Negotiate Version
// Loop For Messages
}
// Connect sets up a ricochet connection between from and to which are // Connect sets up a ricochet connection between from and to which are
// both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this // both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this
// function finished successfully then the connection can be assumed to // function finished successfully then the connection can be assumed to
@ -103,51 +110,28 @@ func (r *Ricochet) Init(filename string, debugLog bool) {
// To specify a local port using the format "127.0.0.1:[port]|ricochet-id". // 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(from string, to string) error {
if strings.HasPrefix(to, "127.0.0.1") { var err error
toAddr := strings.Split(to, "|") networkResolver := new(NetworkResolver)
tcpAddr, err := net.ResolveTCPAddr("tcp", toAddr[0]) r.conn, to, err = networkResolver.Resolve(to)
if err != nil {
return errors.New("Cannot Resolve Local TCP Address") if err != nil {
} return err
r.conn, err = net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
return errors.New("Cannot Dial Local TCP Address")
}
r.logger.Print("Connected to " + to + " as " + toAddr[1])
to = toAddr[1]
} else {
dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, "127.0.0.1:9050")
r.logger.Print("Connecting to ", to+".onion:9878")
conn, err := dialSocksProxy("", to+".onion:9878")
if err != nil {
return errors.New("Cannot Dial Remote Ricochet Address")
}
r.conn = conn
r.logger.Print("Connected to ", to+".onion:9878")
} }
r.negotiateVersion() r.negotiateVersion()
// Construct an Open Channel Message
oc := &Protocol_Data_Control.OpenChannel{
ChannelIdentifier: proto.Int32(1),
ChannelType: proto.String("im.ricochet.auth.hidden-service"),
}
var cookie [16]byte authHandler := new(AuthenticationHandler)
io.ReadFull(rand.Reader, cookie[:]) clientCookie := authHandler.GenClientCookie()
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, cookie[:])
pc := &Protocol_Data_Control.Packet{ controlBuilder := new(ControlBuilder)
OpenChannel: oc, data, err := controlBuilder.OpenAuthenticationChannel(1, clientCookie)
}
data, err := proto.Marshal(pc)
if err != nil { if err != nil {
return errors.New("Cannot Marshal Open Channel Message") return errors.New("Cannot Marshal Open Channel Message")
} }
r.sendPacket(data, 0) r.sendPacket(data, 0)
r.logger.Print("Opening Channel: ", pc)
response, _ := r.getMessages() response, _ := r.getMessages()
openChannelResponse, _ := r.decodePacket(response[0], CONTROL) openChannelResponse, _ := r.decodePacket(response[0], CONTROL)
@ -159,20 +143,7 @@ func (r *Ricochet) Connect(from string, to string) error {
} }
sCookie, _ := proto.GetExtension(channelResult, Protocol_Data_AuthHiddenService.E_ServerCookie) sCookie, _ := proto.GetExtension(channelResult, Protocol_Data_AuthHiddenService.E_ServerCookie)
serverCookie, _ := sCookie.([]byte) authHandler.AddServerCookie(sCookie.([]byte))
r.logger.Print("Starting Authentication with Server Cookie: ", serverCookie)
key := make([]byte, 32)
copy(key[0:16], cookie[:])
copy(key[16:], serverCookie)
value := []byte(from + to)
r.logger.Print("Got Hmac Key: ", key)
r.logger.Print("Got Proof Value: ", string(value))
mac := hmac.New(sha256.New, key)
mac.Write(value)
hmac := mac.Sum(nil)
r.logger.Print("Got HMAC: ", hmac)
// DER Encode the Public Key // DER Encode the Public Key
publickeybytes, err := asn1.Marshal(rsa.PublicKey{ publickeybytes, err := asn1.Marshal(rsa.PublicKey{
@ -180,13 +151,11 @@ func (r *Ricochet) Connect(from string, to string) error {
E: r.privateKey.PublicKey.E, E: r.privateKey.PublicKey.E,
}) })
signature, _ := rsa.SignPKCS1v15(nil, r.privateKey, crypto.SHA256, hmac) signature, _ := rsa.SignPKCS1v15(nil, r.privateKey, crypto.SHA256, authHandler.GenChallenge(from, to))
signatureBytes := make([]byte, 128) signatureBytes := make([]byte, 128)
copy(signatureBytes[:], signature[:]) copy(signatureBytes[:], signature[:])
r.logger.Print("Signature Length: ", len(signatureBytes))
r.logger.Print("Public Key Length: ", len(publickeybytes), ", Bit Size: ", r.privateKey.PublicKey.N.BitLen())
// Construct a Proof Message // Construct a Proof Message
proof := &Protocol_Data_AuthHiddenService.Proof{ proof := &Protocol_Data_AuthHiddenService.Proof{
PublicKey: publickeybytes, PublicKey: publickeybytes,
@ -218,23 +187,19 @@ func (r *Ricochet) Connect(from string, to string) error {
return nil return nil
} }
// OpenChannel opens a new channel with the given type and id // OpenChannel opens a new chat channel with the given id
// Prerequisites: // Prerequisites:
// * Must have Previously issued a successful Connect() // * Must have Previously issued a successful Connect()
// * If acting as the client, id must be odd (currently this is the // * If acting as the client, id must be odd, else even
// only supported option. func (r *Ricochet) OpenChatChannel(id int32) error {
func (r *Ricochet) OpenChannel(channelType string, id int) error { controlBuilder := new(ControlBuilder)
oc := &Protocol_Data_Control.OpenChannel{ data,err := controlBuilder.OpenChatChannel(id)
ChannelIdentifier: proto.Int32(int32(id)),
ChannelType: proto.String(channelType), if err != nil {
return errors.New("error constructing control channel message to open channel")
} }
pc := &Protocol_Data_Control.Packet{ r.logger.Printf("Opening Chat Channel: %d", id)
OpenChannel: oc,
}
data, _ := proto.Marshal(pc)
r.logger.Printf("Opening %s Channel: %d", channelType, id)
r.sendPacket(data, 0) r.sendPacket(data, 0)
return nil return nil
} }
@ -319,6 +284,7 @@ func (r *Ricochet) sendPacket(data []byte, channel int) {
header[2] = 0x00 header[2] = 0x00
header[3] = byte(channel) header[3] = byte(channel)
copy(header[4:], data[:]) copy(header[4:], data[:])
fmt.Fprintf(r.conn, "%s", header) fmt.Fprintf(r.conn, "%s", header)
} }

7
ricochetservice.go Normal file
View File

@ -0,0 +1,7 @@
package goricochet
type RicochetService interface {
OnConnect(id string) error
OnContactRequest(id string) error
OnMessage(id string, message string, channel int) error
}