Refactor of APIs to make testing easier.
Also travis.ci integration
This commit is contained in:
parent
91036c918d
commit
2353fc41e2
|
@ -0,0 +1,3 @@
|
|||
language: go
|
||||
|
||||
script: go get github.com/golang/protobuf/proto && go get h12.me/socks && go test
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/s-rah/go-ricochet"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -17,7 +18,8 @@ func main() {
|
|||
// ricochet.SendContactRequest("EchoBot", "I'm an EchoBot")
|
||||
|
||||
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)
|
||||
|
||||
for true {
|
||||
|
|
|
@ -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
|
||||
}
|
106
ricochet.go
106
ricochet.go
|
@ -2,10 +2,7 @@ package goricochet
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
|
@ -17,13 +14,10 @@ import (
|
|||
"github.com/s-rah/go-ricochet/chat"
|
||||
"github.com/s-rah/go-ricochet/contact"
|
||||
"github.com/s-rah/go-ricochet/control"
|
||||
"h12.me/socks"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
// both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this
|
||||
// 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".
|
||||
func (r *Ricochet) Connect(from string, to string) error {
|
||||
|
||||
if strings.HasPrefix(to, "127.0.0.1") {
|
||||
toAddr := strings.Split(to, "|")
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", toAddr[0])
|
||||
var err error
|
||||
networkResolver := new(NetworkResolver)
|
||||
r.conn, to, err = networkResolver.Resolve(to)
|
||||
|
||||
if err != nil {
|
||||
return errors.New("Cannot Resolve Local TCP Address")
|
||||
}
|
||||
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")
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
io.ReadFull(rand.Reader, cookie[:])
|
||||
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, cookie[:])
|
||||
pc := &Protocol_Data_Control.Packet{
|
||||
OpenChannel: oc,
|
||||
}
|
||||
data, err := proto.Marshal(pc)
|
||||
authHandler := new(AuthenticationHandler)
|
||||
clientCookie := authHandler.GenClientCookie()
|
||||
|
||||
controlBuilder := new(ControlBuilder)
|
||||
data, err := controlBuilder.OpenAuthenticationChannel(1, clientCookie)
|
||||
|
||||
if err != nil {
|
||||
return errors.New("Cannot Marshal Open Channel Message")
|
||||
}
|
||||
|
||||
r.sendPacket(data, 0)
|
||||
r.logger.Print("Opening Channel: ", pc)
|
||||
|
||||
response, _ := r.getMessages()
|
||||
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)
|
||||
serverCookie, _ := 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)
|
||||
authHandler.AddServerCookie(sCookie.([]byte))
|
||||
|
||||
// DER Encode the Public Key
|
||||
publickeybytes, err := asn1.Marshal(rsa.PublicKey{
|
||||
|
@ -180,13 +151,11 @@ func (r *Ricochet) Connect(from string, to string) error {
|
|||
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)
|
||||
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
|
||||
proof := &Protocol_Data_AuthHiddenService.Proof{
|
||||
PublicKey: publickeybytes,
|
||||
|
@ -218,23 +187,19 @@ func (r *Ricochet) Connect(from string, to string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OpenChannel opens a new channel with the given type and id
|
||||
// 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 (currently this is the
|
||||
// only supported option.
|
||||
func (r *Ricochet) OpenChannel(channelType string, id int) error {
|
||||
oc := &Protocol_Data_Control.OpenChannel{
|
||||
ChannelIdentifier: proto.Int32(int32(id)),
|
||||
ChannelType: proto.String(channelType),
|
||||
// * If acting as the client, id must be odd, else even
|
||||
func (r *Ricochet) OpenChatChannel(id int32) error {
|
||||
controlBuilder := new(ControlBuilder)
|
||||
data,err := controlBuilder.OpenChatChannel(id)
|
||||
|
||||
if err != nil {
|
||||
return errors.New("error constructing control channel message to open channel")
|
||||
}
|
||||
|
||||
pc := &Protocol_Data_Control.Packet{
|
||||
OpenChannel: oc,
|
||||
}
|
||||
|
||||
data, _ := proto.Marshal(pc)
|
||||
r.logger.Printf("Opening %s Channel: %d", channelType, id)
|
||||
r.logger.Printf("Opening Chat Channel: %d", id)
|
||||
r.sendPacket(data, 0)
|
||||
return nil
|
||||
}
|
||||
|
@ -319,6 +284,7 @@ func (r *Ricochet) sendPacket(data []byte, channel int) {
|
|||
header[2] = 0x00
|
||||
header[3] = byte(channel)
|
||||
copy(header[4:], data[:])
|
||||
|
||||
fmt.Fprintf(r.conn, "%s", header)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue