v3 onions
This commit is contained in:
parent
1667868d8c
commit
5066380655
|
@ -2,6 +2,7 @@ package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AcceptAllContactManager implements the contact manager interface an presumes
|
// AcceptAllContactManager implements the contact manager interface an presumes
|
||||||
|
@ -14,6 +15,11 @@ func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rs
|
||||||
return true, true
|
return true, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupContact returns that a contact is known and allowed to communicate for all cases.
|
||||||
|
func (aacm *AcceptAllContactManager) LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
|
||||||
func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string {
|
func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string {
|
||||||
return "Accepted"
|
return "Accepted"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
type RicochetApplication struct {
|
type RicochetApplication struct {
|
||||||
contactManager ContactManagerInterface
|
contactManager ContactManagerInterface
|
||||||
privateKey *rsa.PrivateKey
|
privateKey *rsa.PrivateKey
|
||||||
|
v3identity identity.Identity
|
||||||
name string
|
name string
|
||||||
l net.Listener
|
l net.Listener
|
||||||
instances []*ApplicationInstance
|
instances []*ApplicationInstance
|
||||||
|
@ -30,6 +31,13 @@ func (ra *RicochetApplication) Init(name string, pk *rsa.PrivateKey, af Applicat
|
||||||
ra.contactManager = cm
|
ra.contactManager = cm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ra *RicochetApplication) InitV3(name string, v3identity identity.Identity, af ApplicationInstanceFactory, cm ContactManagerInterface) {
|
||||||
|
ra.name = name
|
||||||
|
ra.v3identity = v3identity
|
||||||
|
ra.aif = af
|
||||||
|
ra.contactManager = cm
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Reimplement OnJoin, OnLeave Events.
|
// TODO: Reimplement OnJoin, OnLeave Events.
|
||||||
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
||||||
rc, err := goricochet.NegotiateVersionInbound(conn)
|
rc, err := goricochet.NegotiateVersionInbound(conn)
|
||||||
|
@ -41,12 +49,18 @@ func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
||||||
|
|
||||||
ich := connection.HandleInboundConnection(rc)
|
ich := connection.HandleInboundConnection(rc)
|
||||||
|
|
||||||
|
if ra.v3identity.Initialized() {
|
||||||
|
err = ich.ProcessAuthAsV3Server(ra.v3identity, ra.contactManager.LookupContactV3)
|
||||||
|
} else {
|
||||||
err = ich.ProcessAuthAsServer(identity.Initialize(ra.name, ra.privateKey), ra.contactManager.LookupContact)
|
err = ich.ProcessAuthAsServer(identity.Initialize(ra.name, ra.privateKey), ra.contactManager.LookupContact)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("There was an error")
|
log.Printf("There was an error authenticating the connection: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.TraceLog(true)
|
rc.TraceLog(true)
|
||||||
rai := ra.aif.GetApplicationInstance(rc)
|
rai := ra.aif.GetApplicationInstance(rc)
|
||||||
ra.lock.Lock()
|
ra.lock.Lock()
|
||||||
|
@ -81,7 +95,19 @@ func (ra *RicochetApplication) Open(onionAddress string, requestMessage string)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(identity.Initialize(ra.name, ra.privateKey))
|
och := connection.HandleOutboundConnection(rc)
|
||||||
|
|
||||||
|
var known bool
|
||||||
|
if ra.v3identity.Initialized() {
|
||||||
|
known, err = och.ProcessAuthAsV3Client(ra.v3identity)
|
||||||
|
} else {
|
||||||
|
known, err = och.ProcessAuthAsClient(identity.Initialize(ra.name, ra.privateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("There was an error authenticating the connection: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
rai := ra.aif.GetApplicationInstance(rc)
|
rai := ra.aif.GetApplicationInstance(rc)
|
||||||
go rc.Process(rai)
|
go rc.Process(rai)
|
||||||
|
|
||||||
|
@ -99,7 +125,6 @@ func (ra *RicochetApplication) Open(onionAddress string, requestMessage string)
|
||||||
log.Printf("could not contact %s", err)
|
log.Printf("could not contact %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ra.HandleApplicationInstance(rai)
|
ra.HandleApplicationInstance(rai)
|
||||||
return rai, nil
|
return rai, nil
|
||||||
}
|
}
|
||||||
|
@ -126,7 +151,7 @@ func (ra *RicochetApplication) ConnectionCount() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ra *RicochetApplication) Run(l net.Listener) {
|
func (ra *RicochetApplication) Run(l net.Listener) {
|
||||||
if ra.privateKey == nil || ra.contactManager == nil {
|
if (ra.privateKey == nil && !ra.v3identity.Initialized()) || ra.contactManager == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ra.l = l
|
ra.l = l
|
||||||
|
|
|
@ -2,10 +2,12 @@ package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContactManagerInterface provides a mechanism for autonous applications
|
// ContactManagerInterface provides a mechanism for autonous applications
|
||||||
// to make decisions on what connections to accept or reject.
|
// to make decisions on what connections to accept or reject.
|
||||||
type ContactManagerInterface interface {
|
type ContactManagerInterface interface {
|
||||||
LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
|
LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
|
||||||
|
LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base32"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An example of how to setup a v3 onion service in go
|
||||||
|
func main() {
|
||||||
|
cpubk, cprivk, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
l, err := application.SetupOnionV3("127.0.0.1:9051", "tcp4", "", cprivk, 9878)
|
||||||
|
utils.CheckError(err)
|
||||||
|
log.Printf("Got Listener %v", l.Addr().String())
|
||||||
|
decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(l.Addr().String()[:56]))
|
||||||
|
log.Printf("Decoded Public Key: %x %v", decodedPub[:32], err)
|
||||||
|
log.Printf("ed25519 Public Key: %x", cpubk)
|
||||||
|
}
|
|
@ -2,7 +2,10 @@ package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
"github.com/yawning/bulb"
|
"github.com/yawning/bulb"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,3 +28,35 @@ func SetupOnion(torControlAddress string, torControlSocketType string, authentic
|
||||||
|
|
||||||
return c.NewListener(cfg, onionport)
|
return c.NewListener(cfg, onionport)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetupOnionV3(torControlAddress string, torControlSocketType string, authentication string, pk ed25519.PrivateKey, onionport uint16) (net.Listener, error) {
|
||||||
|
c, err := bulb.Dial(torControlSocketType, torControlAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Authenticate(authentication); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha512.Sum512(pk[:32])
|
||||||
|
digest[0] &= 248
|
||||||
|
digest[31] &= 127
|
||||||
|
digest[31] |= 64
|
||||||
|
|
||||||
|
var privkey [64]byte
|
||||||
|
copy(privkey[0:32], digest[:32])
|
||||||
|
copy(privkey[32:64], digest[32:])
|
||||||
|
|
||||||
|
onionPK := &bulb.OnionPrivateKey{
|
||||||
|
KeyType: "ED25519-V3",
|
||||||
|
Key: base64.StdEncoding.EncodeToString(privkey[0:64]),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &bulb.NewOnionConfig{
|
||||||
|
DiscardPK: true,
|
||||||
|
PrivateKey: onionPK,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.NewListener(cfg, onionport)
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package channels
|
package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package channels
|
package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package channels
|
package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defining Versions
|
// Defining Versions
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package channels
|
package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ package channels
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server3DHAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
|
||||||
|
type Server3DHAuthChannel struct {
|
||||||
|
// PrivateKey must be set for client-side authentication channels
|
||||||
|
ServerIdentity identity.Identity
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
ServerAuthValid func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)
|
||||||
|
ServerAuthInvalid func(err error)
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
clientPubKey, clientEphmeralPublicKey, serverEphemeralPublicKey ed25519.PublicKey
|
||||||
|
serverEphemeralPrivateKey ed25519.PrivateKey
|
||||||
|
channel *channels.Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
|
||||||
|
func (ah *Server3DHAuthChannel) Type() string {
|
||||||
|
return "im.ricochet.auth.3dh"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton Returns whether or not the given channel type is a singleton
|
||||||
|
func (ah *Server3DHAuthChannel) Singleton() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyClientCanOpen ...
|
||||||
|
func (ah *Server3DHAuthChannel) OnlyClientCanOpen() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bidirectional Returns whether or not the given channel allows anyone to send messages
|
||||||
|
func (ah *Server3DHAuthChannel) Bidirectional() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiresAuthentication Returns whether or not the given channel type requires authentication
|
||||||
|
func (ah *Server3DHAuthChannel) RequiresAuthentication() string {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closed is called when the channel is closed for any reason.
|
||||||
|
func (ah *Server3DHAuthChannel) Closed(err error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenInbound is the first method called for an inbound channel request.
|
||||||
|
// If an error is returned, the channel is rejected. If a RawMessage is
|
||||||
|
// returned, it will be sent as the ChannelResult message.
|
||||||
|
// Remote -> [Open Authentication Channel] -> Local
|
||||||
|
func (ah *Server3DHAuthChannel) OpenInbound(channel *channels.Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
||||||
|
ah.channel = channel
|
||||||
|
clientPublicKey, _ := proto.GetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientPublicKey)
|
||||||
|
clientEphmeralPublicKey, _ := proto.GetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientEphmeralPublicKey)
|
||||||
|
clientPubKeyBytes := clientPublicKey.([]byte)
|
||||||
|
ah.clientPubKey = ed25519.PublicKey(clientPubKeyBytes[:])
|
||||||
|
|
||||||
|
clientEphmeralPublicKeyBytes := clientEphmeralPublicKey.([]byte)
|
||||||
|
ah.clientEphmeralPublicKey = ed25519.PublicKey(clientEphmeralPublicKeyBytes[:])
|
||||||
|
|
||||||
|
clientHostname := utils.GetTorV3Hostname(clientPubKeyBytes)
|
||||||
|
log.Printf("Received inbound auth 3DH request from %v", clientHostname)
|
||||||
|
|
||||||
|
// Generate Ephemeral Keys
|
||||||
|
pubkey, privkey, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
ah.serverEphemeralPublicKey = pubkey
|
||||||
|
ah.serverEphemeralPrivateKey = privkey
|
||||||
|
|
||||||
|
var serverPubKeyBytes, serverEphemeralPubKeyBytes [32]byte
|
||||||
|
copy(serverPubKeyBytes[:], ah.ServerIdentity.PublicKeyBytes()[:])
|
||||||
|
copy(serverEphemeralPubKeyBytes[:], ah.serverEphemeralPublicKey[:])
|
||||||
|
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
channel.Pending = false
|
||||||
|
return messageBuilder.Confirm3EDHAuthChannel(ah.channel.ID, serverPubKeyBytes, serverEphemeralPubKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOutbound is the first method called for an outbound channel request.
|
||||||
|
// If an error is returned, the channel is not opened. If a RawMessage is
|
||||||
|
// returned, it will be sent as the OpenChannel message.
|
||||||
|
// Local -> [Open Authentication Channel] -> Remote
|
||||||
|
func (ah *Server3DHAuthChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
||||||
|
return nil, errors.New("server is not allowed to open 3dh channels")
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOutboundResult is called when a response is received for an
|
||||||
|
// outbound OpenChannel request. If `err` is non-nil, the channel was
|
||||||
|
// rejected and Closed will be called immediately afterwards. `raw`
|
||||||
|
// contains the raw protocol message including any extension data.
|
||||||
|
// Input: Remote -> [ChannelResult] -> {Client}
|
||||||
|
// Output: {Client} -> [Proof] -> Remote
|
||||||
|
func (ah *Server3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet is called for each raw packet received on this channel.
|
||||||
|
// Input: Client -> [Proof] -> Remote
|
||||||
|
// OR
|
||||||
|
// Input: Remote -> [Result] -> Client
|
||||||
|
func (ah *Server3DHAuthChannel) Packet(data []byte) {
|
||||||
|
res := new(Protocol_Data_Auth_TripleEDH.Packet)
|
||||||
|
err := proto.Unmarshal(data[:], res)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ah.channel.CloseChannel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.GetProof() != nil && ah.channel.Direction == channels.Inbound {
|
||||||
|
|
||||||
|
// Server Identity <-> Client Ephemeral
|
||||||
|
secret1 := ah.ServerIdentity.EDH(ah.clientEphmeralPublicKey)
|
||||||
|
|
||||||
|
// Server Ephemeral <-> Client Identity
|
||||||
|
secret2 := utils.EDH(ah.serverEphemeralPrivateKey, ah.clientPubKey)
|
||||||
|
|
||||||
|
// Ephemeral <-> Ephemeral
|
||||||
|
secret3 := utils.EDH(ah.serverEphemeralPrivateKey, ah.clientEphmeralPublicKey)
|
||||||
|
|
||||||
|
var secret [96]byte
|
||||||
|
copy(secret[0:32], secret1[:])
|
||||||
|
copy(secret[32:64], secret2[:])
|
||||||
|
copy(secret[64:96], secret3[:])
|
||||||
|
|
||||||
|
pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512)
|
||||||
|
var key [32]byte
|
||||||
|
copy(key[:], pkey[:])
|
||||||
|
var decryptNonce [24]byte
|
||||||
|
ciphertext := res.GetProof().GetProof()
|
||||||
|
|
||||||
|
if len(ciphertext) > 24 {
|
||||||
|
copy(decryptNonce[:], ciphertext[:24])
|
||||||
|
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &key)
|
||||||
|
|
||||||
|
if ok && string(decrypted) == "Hello World" {
|
||||||
|
allowed, known := ah.ServerAuthValid(utils.GetTorV3Hostname(ah.clientPubKey), ah.clientPubKey)
|
||||||
|
ah.channel.DelegateAuthorization()
|
||||||
|
log.Printf("3DH Session Decrypted OK. Authenticating Connection!")
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
result := messageBuilder.AuthResult3DH(allowed, known)
|
||||||
|
ah.channel.SendMessage(result)
|
||||||
|
ah.channel.CloseChannel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
result := messageBuilder.AuthResult3DH(false, false)
|
||||||
|
ah.channel.SendMessage(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other combination of packets is completely invalid
|
||||||
|
// Fail the Authorization right here.
|
||||||
|
ah.channel.CloseChannel()
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/outbound"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer3DHAuthChannel(t *testing.T) {
|
||||||
|
|
||||||
|
cc := new(channels.Channel)
|
||||||
|
cc.ID = 1
|
||||||
|
cc.CloseChannel = func() {}
|
||||||
|
clientChannel := new(outbound.Client3DHAuthChannel)
|
||||||
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
cid := identity.InitializeV3("", &priv, &pub)
|
||||||
|
clientChannel.ClientIdentity = cid
|
||||||
|
ocb, _ := clientChannel.OpenOutbound(cc)
|
||||||
|
|
||||||
|
packet := new(Protocol_Data_Control.Packet)
|
||||||
|
proto.Unmarshal(ocb, packet)
|
||||||
|
|
||||||
|
s3dhchannel := new(Server3DHAuthChannel)
|
||||||
|
pub, priv, _ = ed25519.GenerateKey(rand.Reader)
|
||||||
|
sid := identity.InitializeV3("", &priv, &pub)
|
||||||
|
s3dhchannel.ServerIdentity = sid
|
||||||
|
cr, _ := s3dhchannel.OpenInbound(cc, packet.GetOpenChannel())
|
||||||
|
|
||||||
|
proto.Unmarshal(cr, packet)
|
||||||
|
if packet.GetChannelResult() != nil {
|
||||||
|
|
||||||
|
authPacket := new(Protocol_Data_Auth_TripleEDH.Packet)
|
||||||
|
var lastMessage []byte
|
||||||
|
cc.SendMessage = func(message []byte) {
|
||||||
|
proto.Unmarshal(message, authPacket)
|
||||||
|
lastMessage = message
|
||||||
|
}
|
||||||
|
clientChannel.OpenOutboundResult(nil, packet.GetChannelResult())
|
||||||
|
if authPacket.Proof == nil {
|
||||||
|
t.Errorf("Was expected a Proof Packet, instead %v", authPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
s3dhchannel.ServerAuthValid = func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||||
|
if hostname != clientChannel.ClientIdentity.Hostname() {
|
||||||
|
t.Errorf("Hostname and public key did not match %v %v", hostname, pub)
|
||||||
|
}
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
cc.DelegateAuthorization = func() {}
|
||||||
|
s3dhchannel.Packet(lastMessage)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
t.Errorf("Should have received a Channel Response from OpenInbound: %v", packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer3DHAuthChannelReject(t *testing.T) {
|
||||||
|
|
||||||
|
cc := new(channels.Channel)
|
||||||
|
cc.ID = 1
|
||||||
|
cc.CloseChannel = func() {}
|
||||||
|
clientChannel := new(outbound.Client3DHAuthChannel)
|
||||||
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
cid := identity.InitializeV3("", &priv, &pub)
|
||||||
|
clientChannel.ClientIdentity = cid
|
||||||
|
ocb, _ := clientChannel.OpenOutbound(cc)
|
||||||
|
|
||||||
|
packet := new(Protocol_Data_Control.Packet)
|
||||||
|
proto.Unmarshal(ocb, packet)
|
||||||
|
|
||||||
|
s3dhchannel := new(Server3DHAuthChannel)
|
||||||
|
pub, priv, _ = ed25519.GenerateKey(rand.Reader)
|
||||||
|
sid := identity.InitializeV3("", &priv, &pub)
|
||||||
|
s3dhchannel.ServerIdentity = sid
|
||||||
|
cr, _ := s3dhchannel.OpenInbound(cc, packet.GetOpenChannel())
|
||||||
|
|
||||||
|
proto.Unmarshal(cr, packet)
|
||||||
|
if packet.GetChannelResult() != nil {
|
||||||
|
|
||||||
|
authPacket := new(Protocol_Data_Auth_TripleEDH.Packet)
|
||||||
|
var lastMessage []byte
|
||||||
|
cc.SendMessage = func(message []byte) {
|
||||||
|
proto.Unmarshal(message, authPacket)
|
||||||
|
// Replace the Auth Proof Packet to cause this to fail.
|
||||||
|
if authPacket.GetProof() != nil {
|
||||||
|
authPacket.GetProof().Proof = []byte{}
|
||||||
|
lastMessage, _ = proto.Marshal(authPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clientChannel.OpenOutboundResult(nil, packet.GetChannelResult())
|
||||||
|
if authPacket.Proof == nil {
|
||||||
|
t.Errorf("Was expected a Proof Packet, instead %v", authPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
s3dhchannel.ServerAuthInvalid = func(err error) {
|
||||||
|
}
|
||||||
|
cc.DelegateAuthorization = func() {}
|
||||||
|
s3dhchannel.Packet(lastMessage)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
t.Errorf("Should have received a Channel Response from OpenInbound: %v", packet)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client3DHAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
|
||||||
|
type Client3DHAuthChannel struct {
|
||||||
|
// PrivateKey must be set for client-side authentication channels
|
||||||
|
ClientIdentity identity.Identity
|
||||||
|
ServerHostname string
|
||||||
|
ClientAuthResult func(bool, bool)
|
||||||
|
// Internal state
|
||||||
|
serverPubKey, serverEphemeralPublicKey, clientEphemeralPublicKey ed25519.PublicKey
|
||||||
|
clientEphemeralPrivateKey ed25519.PrivateKey
|
||||||
|
channel *channels.Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
|
||||||
|
func (ah *Client3DHAuthChannel) Type() string {
|
||||||
|
return "im.ricochet.auth.3dh"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton Returns whether or not the given channel type is a singleton
|
||||||
|
func (ah *Client3DHAuthChannel) Singleton() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyClientCanOpen ...
|
||||||
|
func (ah *Client3DHAuthChannel) OnlyClientCanOpen() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bidirectional Returns whether or not the given channel allows anyone to send messages
|
||||||
|
func (ah *Client3DHAuthChannel) Bidirectional() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiresAuthentication Returns whether or not the given channel type requires authentication
|
||||||
|
func (ah *Client3DHAuthChannel) RequiresAuthentication() string {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closed is called when the channel is closed for any reason.
|
||||||
|
func (ah *Client3DHAuthChannel) Closed(err error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenInbound is the first method called for an inbound channel request.
|
||||||
|
// If an error is returned, the channel is rejected. If a RawMessage is
|
||||||
|
// returned, it will be sent as the ChannelResult message.
|
||||||
|
// Remote -> [Open Authentication Channel] -> Local
|
||||||
|
func (ah *Client3DHAuthChannel) OpenInbound(channel *channels.Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
|
||||||
|
return nil, errors.New("server is not allowed to open inbound auth.3dh channels")
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOutbound is the first method called for an outbound channel request.
|
||||||
|
// If an error is returned, the channel is not opened. If a RawMessage is
|
||||||
|
// returned, it will be sent as the OpenChannel message.
|
||||||
|
// Local -> [Open Authentication Channel] -> Remote
|
||||||
|
func (ah *Client3DHAuthChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
|
||||||
|
ah.channel = channel
|
||||||
|
|
||||||
|
log.Printf("Opening an outbound connection to %v", ah.ServerHostname)
|
||||||
|
|
||||||
|
// Generate Ephemeral Keys
|
||||||
|
pubkey, privkey, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
ah.clientEphemeralPublicKey = pubkey
|
||||||
|
ah.clientEphemeralPrivateKey = privkey
|
||||||
|
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
channel.Pending = false
|
||||||
|
|
||||||
|
var clientPubKeyBytes, clientEphemeralPubKeyBytes [32]byte
|
||||||
|
copy(clientPubKeyBytes[:], ah.ClientIdentity.PublicKeyBytes()[:])
|
||||||
|
copy(clientEphemeralPubKeyBytes[:], ah.clientEphemeralPublicKey[:])
|
||||||
|
|
||||||
|
return messageBuilder.Open3EDHAuthenticationChannel(ah.channel.ID, clientPubKeyBytes, clientEphemeralPubKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOutboundResult is called when a response is received for an
|
||||||
|
// outbound OpenChannel request. If `err` is non-nil, the channel was
|
||||||
|
// rejected and Closed will be called immediately afterwards. `raw`
|
||||||
|
// contains the raw protocol message including any extension data.
|
||||||
|
// Input: Remote -> [ChannelResult] -> {Client}
|
||||||
|
// Output: {Client} -> [Proof] -> Remote
|
||||||
|
func (ah *Client3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
|
||||||
|
|
||||||
|
serverPublicKey, _ := proto.GetExtension(crm, Protocol_Data_Auth_TripleEDH.E_ServerPublicKey)
|
||||||
|
serverEphemeralPublicKey, _ := proto.GetExtension(crm, Protocol_Data_Auth_TripleEDH.E_ServerEphmeralPublicKey)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
serverPubKeyBytes := serverPublicKey.([]byte)
|
||||||
|
ah.serverPubKey = ed25519.PublicKey(serverPubKeyBytes[:])
|
||||||
|
|
||||||
|
if utils.GetTorV3Hostname(ah.serverPubKey) != ah.ServerHostname {
|
||||||
|
ah.channel.CloseChannel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serverEphmeralPublicKeyBytes := serverEphemeralPublicKey.([]byte)
|
||||||
|
ah.serverEphemeralPublicKey = ed25519.PublicKey(serverEphmeralPublicKeyBytes[:])
|
||||||
|
|
||||||
|
log.Printf("Public Keys Exchanged. Deriving Encryption Keys and Sending Encrypted Test Message")
|
||||||
|
|
||||||
|
// Server Ephemeral <-> Client Identity
|
||||||
|
secret1 := utils.EDH(ah.clientEphemeralPrivateKey, ah.serverPubKey)
|
||||||
|
|
||||||
|
// Server Identity <-> Client Ephemeral
|
||||||
|
secret2 := ah.ClientIdentity.EDH(ah.serverEphemeralPublicKey)
|
||||||
|
|
||||||
|
// Ephemeral <-> Ephemeral
|
||||||
|
secret3 := utils.EDH(ah.clientEphemeralPrivateKey, ah.serverEphemeralPublicKey)
|
||||||
|
|
||||||
|
var secret [96]byte
|
||||||
|
copy(secret[0:32], secret1[:])
|
||||||
|
copy(secret[32:64], secret2[:])
|
||||||
|
copy(secret[64:96], secret3[:])
|
||||||
|
|
||||||
|
var nonce [24]byte
|
||||||
|
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512)
|
||||||
|
var key [32]byte
|
||||||
|
copy(key[:], pkey[:])
|
||||||
|
encrypted := secretbox.Seal(nonce[:], []byte("Hello World"), &nonce, &key)
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
proof := messageBuilder.Proof3DH(encrypted)
|
||||||
|
ah.channel.SendMessage(proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet is called for each raw packet received on this channel.
|
||||||
|
// Input: Client -> [Proof] -> Remote
|
||||||
|
// OR
|
||||||
|
// Input: Remote -> [Result] -> Client
|
||||||
|
func (ah *Client3DHAuthChannel) Packet(data []byte) {
|
||||||
|
res := new(Protocol_Data_Auth_TripleEDH.Packet)
|
||||||
|
err := proto.Unmarshal(data[:], res)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ah.channel.CloseChannel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.GetResult() != nil {
|
||||||
|
ah.ClientAuthResult(res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact())
|
||||||
|
if res.GetResult().GetAccepted() {
|
||||||
|
log.Printf("3DH Session Accepted OK. Authenticated! Connection!")
|
||||||
|
ah.channel.DelegateAuthorization()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other combination of packets is completely invalid
|
||||||
|
// Fail the Authorization right here.
|
||||||
|
ah.channel.CloseChannel()
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package connection
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,6 @@ func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler c
|
||||||
}
|
}
|
||||||
cm.lock.Unlock()
|
cm.lock.Unlock()
|
||||||
|
|
||||||
|
|
||||||
// Some channels only allow us to open one of them per connection
|
// Some channels only allow us to open one of them per connection
|
||||||
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
|
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
|
||||||
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
|
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
|
||||||
|
|
|
@ -3,10 +3,10 @@ package connection
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package connection
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,6 +16,11 @@ func ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known b
|
||||||
return true, true
|
return true, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server
|
||||||
|
func ServerAuthValid3DH(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
|
||||||
func TestProcessAuthAsServer(t *testing.T) {
|
func TestProcessAuthAsServer(t *testing.T) {
|
||||||
|
|
||||||
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
@ -45,6 +52,99 @@ func TestProcessAuthAsServer(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProcessAuthAs3DHServer(t *testing.T) {
|
||||||
|
|
||||||
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
|
||||||
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cconn, _ := net.Dial("tcp", ln.Addr().String())
|
||||||
|
|
||||||
|
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
hostname := utils.GetTorV3Hostname(pub)
|
||||||
|
orc := NewOutboundConnection(cconn, hostname)
|
||||||
|
orc.TraceLog(true)
|
||||||
|
|
||||||
|
known, err := HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error while testing ProcessAuthAsClient (in ProcessAuthAsServer) %v", err)
|
||||||
|
return
|
||||||
|
} else if !known {
|
||||||
|
t.Errorf("Client should have been known to the server, instead known was: %v", known)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
conn, _ := ln.Accept()
|
||||||
|
|
||||||
|
rc := NewInboundConnection(conn)
|
||||||
|
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error while testing ProcessAuthAsServer: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessAuthAsV3ServerFail(t *testing.T) {
|
||||||
|
|
||||||
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
|
||||||
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cconn, _ := net.Dial("tcp", ln.Addr().String())
|
||||||
|
|
||||||
|
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
|
||||||
|
// Setting the RemoteHostname to the client pub key approximates a server sending the wrong public key.
|
||||||
|
hostname := utils.GetTorV3Hostname(cpub)
|
||||||
|
orc := NewOutboundConnection(cconn, hostname)
|
||||||
|
orc.TraceLog(true)
|
||||||
|
|
||||||
|
HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
|
||||||
|
}()
|
||||||
|
|
||||||
|
conn, _ := ln.Accept()
|
||||||
|
|
||||||
|
rc := NewInboundConnection(conn)
|
||||||
|
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Error while testing ProcessAuthAsServer: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestProcessAuthAsV3ClientFail(t *testing.T) {
|
||||||
|
|
||||||
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
|
||||||
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cconn, _ := net.Dial("tcp", ln.Addr().String())
|
||||||
|
|
||||||
|
// Giving the client inconsistent keypair to make EDH fail
|
||||||
|
cpub, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
_,cpriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
hostname := utils.GetTorV3Hostname(pub)
|
||||||
|
orc := NewOutboundConnection(cconn, hostname)
|
||||||
|
orc.TraceLog(true)
|
||||||
|
|
||||||
|
HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
|
||||||
|
}()
|
||||||
|
|
||||||
|
conn, _ := ln.Accept()
|
||||||
|
|
||||||
|
rc := NewInboundConnection(conn)
|
||||||
|
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Error while testing ProcessAuthAsServer: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProcessServerAuthFail(t *testing.T) {
|
func TestProcessServerAuthFail(t *testing.T) {
|
||||||
|
|
||||||
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package connection
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@ package connection
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/inbound"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/policies"
|
"git.openprivacy.ca/openprivacy/libricochet-go/policies"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,3 +90,66 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(identity identity.Ident
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessAuthAsServer blocks until authentication has succeeded, failed, or the
|
||||||
|
// connection is closed. A non-nil error is returned in all cases other than successful
|
||||||
|
// and accepted authentication.
|
||||||
|
//
|
||||||
|
// ProcessAuthAsServer cannot be called at the same time as any other call to a Process
|
||||||
|
// function. Another Process function must be called after this function successfully
|
||||||
|
// returns to continue handling connection events.
|
||||||
|
//
|
||||||
|
// The acceptCallback function is called after receiving a valid authentication proof
|
||||||
|
// with the client's authenticated hostname and public key. acceptCallback must return
|
||||||
|
// true to accept authentication and allow the connection to continue, and also returns a
|
||||||
|
// boolean indicating whether the contact is known and recognized. Unknown contacts will
|
||||||
|
// assume they are required to send a contact request before any other activity.
|
||||||
|
func (ich *InboundConnectionHandler) ProcessAuthAsV3Server(v3identity identity.Identity, sach func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)) error {
|
||||||
|
|
||||||
|
var breakOnce sync.Once
|
||||||
|
|
||||||
|
var authAllowed, authKnown bool
|
||||||
|
var authHostname string
|
||||||
|
|
||||||
|
onAuthValid := func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||||
|
authAllowed, authKnown = sach(hostname, publicKey)
|
||||||
|
if authAllowed {
|
||||||
|
authHostname = hostname
|
||||||
|
}
|
||||||
|
breakOnce.Do(func() { go ich.connection.Break() })
|
||||||
|
return authAllowed, authKnown
|
||||||
|
}
|
||||||
|
onAuthInvalid := func(err error) {
|
||||||
|
// err is ignored at the moment
|
||||||
|
breakOnce.Do(func() { go ich.connection.Break() })
|
||||||
|
}
|
||||||
|
|
||||||
|
ach := new(AutoConnectionHandler)
|
||||||
|
ach.Init()
|
||||||
|
ach.RegisterChannelHandler("im.ricochet.auth.3dh",
|
||||||
|
func() channels.Handler {
|
||||||
|
return &inbound.Server3DHAuthChannel{
|
||||||
|
ServerIdentity: v3identity,
|
||||||
|
ServerAuthValid: onAuthValid,
|
||||||
|
ServerAuthInvalid: onAuthInvalid,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ensure that the call to Process() cannot outlive this function,
|
||||||
|
// particularly for the case where the policy timeout expires
|
||||||
|
defer breakOnce.Do(func() { ich.connection.Break() })
|
||||||
|
policy := policies.UnknownPurposeTimeout
|
||||||
|
err := policy.ExecuteAction(func() error {
|
||||||
|
return ich.connection.Process(ach)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if authAllowed == true {
|
||||||
|
ich.connection.RemoteHostname = authHostname
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return utils.ClientFailedToAuthenticateError
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/outbound"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/policies"
|
"git.openprivacy.ca/openprivacy/libricochet-go/policies"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
@ -88,3 +89,67 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(identity identity.Iden
|
||||||
}
|
}
|
||||||
return false, utils.ServerRejectedClientConnectionError
|
return false, utils.ServerRejectedClientConnectionError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessAuthAs3DGClient blocks until authentication has succeeded or failed with the
|
||||||
|
// provided identity, or the connection is closed. A non-nil error is returned in all
|
||||||
|
// cases other than successful authentication.
|
||||||
|
//
|
||||||
|
// ProcessAuthAsClient cannot be called at the same time as any other call to a Porcess
|
||||||
|
// function. Another Process function must be called after this function successfully
|
||||||
|
// returns to continue handling connection events.
|
||||||
|
//
|
||||||
|
// For successful authentication, the `known` return value indicates whether the peer
|
||||||
|
// accepts us as a known contact. Unknown contacts will generally need to send a contact
|
||||||
|
// request before any other activity.
|
||||||
|
func (och *OutboundConnectionHandler) ProcessAuthAsV3Client(v3identity identity.Identity) (bool, error) {
|
||||||
|
|
||||||
|
ach := new(AutoConnectionHandler)
|
||||||
|
ach.Init()
|
||||||
|
|
||||||
|
// Make sure that calls to Break in this function cannot race
|
||||||
|
var breakOnce sync.Once
|
||||||
|
|
||||||
|
var accepted, isKnownContact bool
|
||||||
|
authCallback := func(accept, known bool) {
|
||||||
|
accepted = accept
|
||||||
|
isKnownContact = known
|
||||||
|
// Cause the Process() call below to return.
|
||||||
|
// If Break() is called from here, it _must_ use go, because this will
|
||||||
|
// execute in the Process goroutine, and Break() will deadlock.
|
||||||
|
breakOnce.Do(func() { go och.connection.Break() })
|
||||||
|
}
|
||||||
|
|
||||||
|
processResult := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
// Break Process() if timed out; no-op if Process returned a conn error
|
||||||
|
defer func() { breakOnce.Do(func() { och.connection.Break() }) }()
|
||||||
|
policy := policies.UnknownPurposeTimeout
|
||||||
|
err := policy.ExecuteAction(func() error {
|
||||||
|
return och.connection.Process(ach)
|
||||||
|
})
|
||||||
|
processResult <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := och.connection.Do(func() error {
|
||||||
|
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.3dh",
|
||||||
|
&outbound.Client3DHAuthChannel{
|
||||||
|
ClientIdentity: v3identity,
|
||||||
|
ServerHostname: och.connection.RemoteHostname,
|
||||||
|
ClientAuthResult: authCallback,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
breakOnce.Do(func() { och.connection.Break() })
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = <-processResult; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if accepted == true {
|
||||||
|
return isKnownContact, nil
|
||||||
|
}
|
||||||
|
return false, utils.ServerRejectedClientConnectionError
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Identity is an encapsulation of Name, PrivateKey and other features
|
// Identity is an encapsulation of Name, PrivateKey and other features
|
||||||
|
@ -14,6 +15,8 @@ import (
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
Name string
|
Name string
|
||||||
pk *rsa.PrivateKey
|
pk *rsa.PrivateKey
|
||||||
|
edpk *ed25519.PrivateKey
|
||||||
|
edpubk *ed25519.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init loads an identity from a file. Currently file should be a private_key
|
// Init loads an identity from a file. Currently file should be a private_key
|
||||||
|
@ -21,28 +24,40 @@ type Identity struct {
|
||||||
func Init(filename string) Identity {
|
func Init(filename string) Identity {
|
||||||
pk, err := utils.LoadPrivateKeyFromFile(filename)
|
pk, err := utils.LoadPrivateKeyFromFile(filename)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return Identity{"", pk}
|
return Identity{"", pk, nil, nil}
|
||||||
}
|
}
|
||||||
return Identity{}
|
return Identity{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize is a courtesy function for initializing an Identity in-code.
|
// Initialize is a courtesy function for initializing an Identity in-code.
|
||||||
func Initialize(name string, pk *rsa.PrivateKey) Identity {
|
func Initialize(name string, pk *rsa.PrivateKey) Identity {
|
||||||
return Identity{name, pk}
|
return Identity{name, pk, nil, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize is a courtesy function for initializing an Identity in-code.
|
||||||
|
func InitializeV3(name string, pk *ed25519.PrivateKey, pubk *ed25519.PublicKey) Identity {
|
||||||
|
return Identity{name, nil, pk, pubk}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialized ensures that an Identity has been assigned a private_key and
|
// Initialized ensures that an Identity has been assigned a private_key and
|
||||||
// is ready to perform operations.
|
// is ready to perform operations.
|
||||||
func (i *Identity) Initialized() bool {
|
func (i *Identity) Initialized() bool {
|
||||||
if i.pk == nil {
|
if i.pk == nil {
|
||||||
|
if i.edpk == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly
|
// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly
|
||||||
// format. //TODO Not sure I like this.
|
// format.
|
||||||
func (i *Identity) PublicKeyBytes() []byte {
|
func (i *Identity) PublicKeyBytes() []byte {
|
||||||
|
|
||||||
|
if i.edpk != nil {
|
||||||
|
return *i.edpubk
|
||||||
|
}
|
||||||
|
|
||||||
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
||||||
N: i.pk.PublicKey.N,
|
N: i.pk.PublicKey.N,
|
||||||
E: i.pk.PublicKey.E,
|
E: i.pk.PublicKey.E,
|
||||||
|
@ -51,10 +66,19 @@ func (i *Identity) PublicKeyBytes() []byte {
|
||||||
return publicKeyBytes
|
return publicKeyBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
|
||||||
|
secret := utils.EDH(*i.edpk, key)
|
||||||
|
return secret[:]
|
||||||
|
}
|
||||||
|
|
||||||
// Hostname provides the onion address associated with this Identity.
|
// Hostname provides the onion address associated with this Identity.
|
||||||
func (i *Identity) Hostname() string {
|
func (i *Identity) Hostname() string {
|
||||||
|
if i.edpk != nil {
|
||||||
|
return utils.GetTorV3Hostname(*i.edpubk)
|
||||||
|
} else {
|
||||||
return utils.GetTorHostname(i.PublicKeyBytes())
|
return utils.GetTorHostname(i.PublicKeyBytes())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sign produces a cryptographic signature using this Identities private key.
|
// Sign produces a cryptographic signature using this Identities private key.
|
||||||
func (i *Identity) Sign(challenge []byte) ([]byte, error) {
|
func (i *Identity) Sign(challenge []byte) ([]byte, error) {
|
||||||
|
|
|
@ -2,12 +2,5 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
pwd
|
pwd
|
||||||
go test ${1} -coverprofile=main.cover.out -v .
|
go test ${1} -coverprofile=coverage.out -coverpkg="git.openprivacy.ca/openprivacy/libricochet-go/channels,git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/inbound,git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/outbound,git.openprivacy.ca/openprivacy/libricochet-go/identity,git.openprivacy.ca/openprivacy/libricochet-go/utils,git.openprivacy.ca/openprivacy/libricochet-go/policies,git.openprivacy.ca/openprivacy/libricochet-go/connection,git.openprivacy.ca/openprivacy/libricochet-go" -v . ./utils/ ./channels/ ./channels/v3/inbound ./connection ./policies ./identity ./utils
|
||||||
go test ${1} -coverprofile=utils.cover.out -v ./utils
|
|
||||||
go test ${1} -coverprofile=channels.cover.out -v ./channels
|
|
||||||
go test ${1} -coverprofile=connection.cover.out -v ./connection
|
|
||||||
go test ${1} -coverprofile=policies.cover.out -v ./policies
|
|
||||||
go test ${1} -coverprofile=policies.cover.out -v ./identity
|
|
||||||
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
|
|
||||||
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
|
|
||||||
rm -rf *.cover.out
|
|
||||||
|
|
|
@ -6,7 +6,10 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/agl/ed25519/extra25519"
|
||||||
"github.com/yawning/bulb/utils/pkcs1"
|
"github.com/yawning/bulb/utils/pkcs1"
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -30,6 +33,21 @@ func GetRandNumber() *big.Int {
|
||||||
return num
|
return num
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EDH implements diffie hellman using curve25519 keys derived from ed25519 keys
|
||||||
|
// NOTE: This uses a 3rd party library extra25519 as the key conversion is not in the core golang lib
|
||||||
|
// as such this definitely needs further review.
|
||||||
|
func EDH(privateKey ed25519.PrivateKey, remotePublicKey ed25519.PublicKey) [32]byte {
|
||||||
|
var privKeyBytes [64]byte
|
||||||
|
var remotePubKeyBytes [32]byte
|
||||||
|
copy(privKeyBytes[:], privateKey[:])
|
||||||
|
copy(remotePubKeyBytes[:], remotePublicKey[:])
|
||||||
|
var secret, curve25519priv, curve25519pub [32]byte
|
||||||
|
extra25519.PrivateKeyToCurve25519(&curve25519priv, &privKeyBytes)
|
||||||
|
extra25519.PublicKeyToCurve25519(&curve25519pub, &remotePubKeyBytes)
|
||||||
|
curve25519.ScalarMult(&secret, &curve25519priv, &curve25519pub)
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
|
||||||
// GeneratePrivateKey generates a new private key for use
|
// GeneratePrivateKey generates a new private key for use
|
||||||
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, RicochetKeySize)
|
privateKey, err := rsa.GenerateKey(rand.Reader, RicochetKeySize)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -23,6 +25,16 @@ func TestLoadPrivateKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEDH(t *testing.T) {
|
||||||
|
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
spub, spriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
cedh := EDH(cpriv, spub)
|
||||||
|
sedh := EDH(spriv, cpub)
|
||||||
|
if string(cedh[:]) != string(sedh[:]) {
|
||||||
|
t.Errorf("Client and Server should see the same secret %v %v", cedh, sedh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRandNumber(t *testing.T) {
|
func TestGetRandNumber(t *testing.T) {
|
||||||
num := GetRandNumber()
|
num := GetRandNumber()
|
||||||
if !num.IsUint64() || num.Uint64() > uint64(math.MaxUint32) {
|
if !num.IsUint64() || num.Uint64() > uint64(math.MaxUint32) {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageBuilder allows a client to construct specific data packets for the
|
// MessageBuilder allows a client to construct specific data packets for the
|
||||||
|
@ -79,6 +80,63 @@ func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]b
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open3EDHAuthenticationChannel constructs a message which will reuqest to open a channel for
|
||||||
|
// authentication on the given channelID, with the given cookie
|
||||||
|
func (mb *MessageBuilder) Open3EDHAuthenticationChannel(channelID int32, pubkey [32]byte, ephemeralKey [32]byte) []byte {
|
||||||
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
|
ChannelType: proto.String("im.ricochet.auth.3dh"),
|
||||||
|
}
|
||||||
|
err := proto.SetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientPublicKey, pubkey[:])
|
||||||
|
CheckError(err)
|
||||||
|
|
||||||
|
err = proto.SetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientEphmeralPublicKey, ephemeralKey[:])
|
||||||
|
CheckError(err)
|
||||||
|
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
OpenChannel: oc,
|
||||||
|
}
|
||||||
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmAuthChannel constructs a message to acknowledge a previous open channel operation.
|
||||||
|
func (mb *MessageBuilder) Confirm3EDHAuthChannel(channelID int32, pubkey [32]byte, ephemeralKey [32]byte) []byte {
|
||||||
|
cr := &Protocol_Data_Control.ChannelResult{
|
||||||
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
|
Opened: proto.Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := proto.SetExtension(cr, Protocol_Data_Auth_TripleEDH.E_ServerPublicKey, pubkey[:])
|
||||||
|
CheckError(err)
|
||||||
|
|
||||||
|
err = proto.SetExtension(cr, Protocol_Data_Auth_TripleEDH.E_ServerEphmeralPublicKey, ephemeralKey[:])
|
||||||
|
CheckError(err)
|
||||||
|
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
ChannelResult: cr,
|
||||||
|
}
|
||||||
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHProof constructs a proof message with the given public key and signature.
|
||||||
|
func (mb *MessageBuilder) Proof3DH(proofBytes []byte) []byte {
|
||||||
|
proof := &Protocol_Data_Auth_TripleEDH.Proof{
|
||||||
|
Proof: proofBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
ahsPacket := &Protocol_Data_Auth_TripleEDH.Packet{
|
||||||
|
Proof: proof,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := proto.Marshal(ahsPacket)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// OpenContactRequestChannel contructs a message which will reuqest to open a channel for
|
// 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.
|
// a contact request on the given channelID, with the given nick and message.
|
||||||
func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) []byte {
|
func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) []byte {
|
||||||
|
@ -176,6 +234,24 @@ func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) []
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AuthResult constructs a response to a Proof
|
||||||
|
func (mb *MessageBuilder) AuthResult3DH(accepted bool, isKnownContact bool) []byte {
|
||||||
|
// Construct a Result Message
|
||||||
|
result := &Protocol_Data_Auth_TripleEDH.Result{
|
||||||
|
Accepted: proto.Bool(accepted),
|
||||||
|
IsKnownContact: proto.Bool(isKnownContact),
|
||||||
|
}
|
||||||
|
|
||||||
|
ahsPacket := &Protocol_Data_Auth_TripleEDH.Packet{
|
||||||
|
Proof: nil,
|
||||||
|
Result: result,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := proto.Marshal(ahsPacket)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// AuthResult constructs a response to a Proof
|
// AuthResult constructs a response to a Proof
|
||||||
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte {
|
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte {
|
||||||
// Construct a Result Message
|
// Construct a Result Message
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
40
utils/tor.go
40
utils/tor.go
|
@ -2,7 +2,11 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,3 +21,39 @@ func GetTorHostname(publicKeyBytes []byte) string {
|
||||||
data := base32.StdEncoding.EncodeToString(sha1bytes)
|
data := base32.StdEncoding.EncodeToString(sha1bytes)
|
||||||
return strings.ToLower(data[0:16])
|
return strings.ToLower(data[0:16])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expand ed25519.PrivateKey to (a || RH) form, return base64
|
||||||
|
func expandKey(pri ed25519.PrivateKey) string {
|
||||||
|
h := sha512.Sum512(pri[:32])
|
||||||
|
// Set bits so that h[:32] is private scalar "a"
|
||||||
|
h[0] &= 248
|
||||||
|
h[31] &= 127
|
||||||
|
h[31] |= 64
|
||||||
|
// Since h[32:] is RH, h is now (a || RH)
|
||||||
|
return base64.StdEncoding.EncodeToString(h[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hidden service version
|
||||||
|
const version = byte(0x03)
|
||||||
|
|
||||||
|
// Salt used to create checkdigits
|
||||||
|
const salt = ".onion checksum"
|
||||||
|
|
||||||
|
func getCheckdigits(pub ed25519.PublicKey) []byte {
|
||||||
|
// Calculate checksum sha3(".onion checksum" || publicKey || version)
|
||||||
|
checkstr := []byte(salt)
|
||||||
|
checkstr = append(checkstr, pub...)
|
||||||
|
checkstr = append(checkstr, version)
|
||||||
|
checksum := sha3.Sum256(checkstr)
|
||||||
|
return checksum[:2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTorV3Hostname(pub ed25519.PublicKey) string {
|
||||||
|
// Construct onion address base32(publicKey || checkdigits || version)
|
||||||
|
checkdigits := getCheckdigits(pub)
|
||||||
|
combined := pub[:]
|
||||||
|
combined = append(combined, checkdigits...)
|
||||||
|
combined = append(combined, version)
|
||||||
|
serviceID := base32.StdEncoding.EncodeToString(combined)
|
||||||
|
return strings.ToLower(serviceID)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: Auth3EDH.proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package Protocol_Data_Auth_TripleEDH is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
Auth3EDH.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
Packet
|
||||||
|
Proof
|
||||||
|
Result
|
||||||
|
*/
|
||||||
|
package Protocol_Data_Auth_TripleEDH
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
import Protocol_Data_Control "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
Proof *Proof `protobuf:"bytes,1,opt,name=proof" json:"proof,omitempty"`
|
||||||
|
Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Packet) Reset() { *m = Packet{} }
|
||||||
|
func (m *Packet) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Packet) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *Packet) GetProof() *Proof {
|
||||||
|
if m != nil {
|
||||||
|
return m.Proof
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Packet) GetResult() *Result {
|
||||||
|
if m != nil {
|
||||||
|
return m.Result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proof struct {
|
||||||
|
Proof []byte `protobuf:"bytes,1,opt,name=proof" json:"proof,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Proof) Reset() { *m = Proof{} }
|
||||||
|
func (m *Proof) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Proof) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *Proof) GetProof() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.Proof
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Accepted *bool `protobuf:"varint,1,req,name=accepted" json:"accepted,omitempty"`
|
||||||
|
IsKnownContact *bool `protobuf:"varint,2,opt,name=is_known_contact,json=isKnownContact" json:"is_known_contact,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Result) Reset() { *m = Result{} }
|
||||||
|
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Result) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (m *Result) GetAccepted() bool {
|
||||||
|
if m != nil && m.Accepted != nil {
|
||||||
|
return *m.Accepted
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Result) GetIsKnownContact() bool {
|
||||||
|
if m != nil && m.IsKnownContact != nil {
|
||||||
|
return *m.IsKnownContact
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var E_ClientPublicKey = &proto.ExtensionDesc{
|
||||||
|
ExtendedType: (*Protocol_Data_Control.OpenChannel)(nil),
|
||||||
|
ExtensionType: ([]byte)(nil),
|
||||||
|
Field: 9200,
|
||||||
|
Name: "Protocol.Data.Auth.TripleEDH.client_public_key",
|
||||||
|
Tag: "bytes,9200,opt,name=client_public_key,json=clientPublicKey",
|
||||||
|
Filename: "Auth3EDH.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
var E_ClientEphmeralPublicKey = &proto.ExtensionDesc{
|
||||||
|
ExtendedType: (*Protocol_Data_Control.OpenChannel)(nil),
|
||||||
|
ExtensionType: ([]byte)(nil),
|
||||||
|
Field: 9300,
|
||||||
|
Name: "Protocol.Data.Auth.TripleEDH.client_ephmeral_public_key",
|
||||||
|
Tag: "bytes,9300,opt,name=client_ephmeral_public_key,json=clientEphmeralPublicKey",
|
||||||
|
Filename: "Auth3EDH.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
var E_ServerPublicKey = &proto.ExtensionDesc{
|
||||||
|
ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil),
|
||||||
|
ExtensionType: ([]byte)(nil),
|
||||||
|
Field: 9200,
|
||||||
|
Name: "Protocol.Data.Auth.TripleEDH.server_public_key",
|
||||||
|
Tag: "bytes,9200,opt,name=server_public_key,json=serverPublicKey",
|
||||||
|
Filename: "Auth3EDH.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
var E_ServerEphmeralPublicKey = &proto.ExtensionDesc{
|
||||||
|
ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil),
|
||||||
|
ExtensionType: ([]byte)(nil),
|
||||||
|
Field: 9300,
|
||||||
|
Name: "Protocol.Data.Auth.TripleEDH.server_ephmeral_public_key",
|
||||||
|
Tag: "bytes,9300,opt,name=server_ephmeral_public_key,json=serverEphmeralPublicKey",
|
||||||
|
Filename: "Auth3EDH.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*Packet)(nil), "Protocol.Data.Auth.TripleEDH.Packet")
|
||||||
|
proto.RegisterType((*Proof)(nil), "Protocol.Data.Auth.TripleEDH.Proof")
|
||||||
|
proto.RegisterType((*Result)(nil), "Protocol.Data.Auth.TripleEDH.Result")
|
||||||
|
proto.RegisterExtension(E_ClientPublicKey)
|
||||||
|
proto.RegisterExtension(E_ClientEphmeralPublicKey)
|
||||||
|
proto.RegisterExtension(E_ServerPublicKey)
|
||||||
|
proto.RegisterExtension(E_ServerEphmeralPublicKey)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
package Protocol.Data.Auth.TripleEDH;
|
||||||
|
import "ControlChannel.proto";
|
||||||
|
|
||||||
|
extend Control.OpenChannel {
|
||||||
|
optional bytes client_public_key = 9200;
|
||||||
|
optional bytes client_ephmeral_public_key = 9300;
|
||||||
|
}
|
||||||
|
|
||||||
|
extend Control.ChannelResult {
|
||||||
|
optional bytes server_public_key = 9200;
|
||||||
|
optional bytes server_ephmeral_public_key = 9300;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Packet {
|
||||||
|
optional Proof proof = 1;
|
||||||
|
optional Result result = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Proof {
|
||||||
|
optional bytes proof = 1; // Encrypted Onion Address
|
||||||
|
}
|
||||||
|
|
||||||
|
message Result {
|
||||||
|
required bool accepted = 1;
|
||||||
|
optional bool is_known_contact = 2;
|
||||||
|
|
||||||
|
}
|
Reference in New Issue