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 (
|
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 {
|
||||||
|
|
|
@ -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 (
|
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 {
|
if err != nil {
|
||||||
return errors.New("Cannot Resolve Local TCP Address")
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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