forked from openprivacy/libricochet-go
v3 onions
This commit is contained in:
parent
1667868d8c
commit
5066380655
|
@ -2,6 +2,7 @@ package application
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// AcceptAllContactManager implements the contact manager interface an presumes
|
||||
|
@ -14,6 +15,11 @@ func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rs
|
|||
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 {
|
||||
return "Accepted"
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
type RicochetApplication struct {
|
||||
contactManager ContactManagerInterface
|
||||
privateKey *rsa.PrivateKey
|
||||
v3identity identity.Identity
|
||||
name string
|
||||
l net.Listener
|
||||
instances []*ApplicationInstance
|
||||
|
@ -30,6 +31,13 @@ func (ra *RicochetApplication) Init(name string, pk *rsa.PrivateKey, af Applicat
|
|||
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.
|
||||
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
||||
rc, err := goricochet.NegotiateVersionInbound(conn)
|
||||
|
@ -41,12 +49,18 @@ func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
|||
|
||||
ich := connection.HandleInboundConnection(rc)
|
||||
|
||||
err = ich.ProcessAuthAsServer(identity.Initialize(ra.name, ra.privateKey), ra.contactManager.LookupContact)
|
||||
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)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("There was an error")
|
||||
log.Printf("There was an error authenticating the connection: %v", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
rc.TraceLog(true)
|
||||
rai := ra.aif.GetApplicationInstance(rc)
|
||||
ra.lock.Lock()
|
||||
|
@ -81,7 +95,19 @@ func (ra *RicochetApplication) Open(onionAddress string, requestMessage string)
|
|||
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)
|
||||
go rc.Process(rai)
|
||||
|
||||
|
@ -99,7 +125,6 @@ func (ra *RicochetApplication) Open(onionAddress string, requestMessage string)
|
|||
log.Printf("could not contact %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
ra.HandleApplicationInstance(rai)
|
||||
return rai, nil
|
||||
}
|
||||
|
@ -126,7 +151,7 @@ func (ra *RicochetApplication) ConnectionCount() int {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
ra.l = l
|
||||
|
|
|
@ -2,10 +2,12 @@ package application
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// ContactManagerInterface provides a mechanism for autonous applications
|
||||
// to make decisions on what connections to accept or reject.
|
||||
type ContactManagerInterface interface {
|
||||
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 (
|
||||
"crypto/rsa"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"github.com/yawning/bulb"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"net"
|
||||
)
|
||||
|
||||
|
@ -25,3 +28,35 @@ func SetupOnion(torControlAddress string, torControlSocketType string, authentic
|
|||
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// Defining Versions
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/asn1"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ package channels
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"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
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
@ -75,7 +75,6 @@ func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler c
|
|||
}
|
||||
cm.lock.Unlock()
|
||||
|
||||
|
||||
// Some channels only allow us to open one of them per connection
|
||||
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
|
||||
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
|
||||
|
|
|
@ -3,10 +3,10 @@ package connection
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package connection
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -14,6 +16,11 @@ func ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known b
|
|||
return true, true
|
||||
}
|
||||
|
||||
// Server
|
||||
func ServerAuthValid3DH(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
func TestProcessAuthAsServer(t *testing.T) {
|
||||
|
||||
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) {
|
||||
|
||||
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package connection
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ package connection
|
|||
import (
|
||||
"crypto/rsa"
|
||||
"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/policies"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -88,3 +90,66 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(identity identity.Ident
|
|||
|
||||
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 (
|
||||
"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/policies"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
|
@ -88,3 +89,67 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(identity identity.Iden
|
|||
}
|
||||
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"
|
||||
"encoding/asn1"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
// Identity is an encapsulation of Name, PrivateKey and other features
|
||||
|
@ -12,8 +13,10 @@ import (
|
|||
// The purpose of Identity is to prevent other classes directly accessing private key
|
||||
// and to ensure the integrity of security-critical functions.
|
||||
type Identity struct {
|
||||
Name string
|
||||
pk *rsa.PrivateKey
|
||||
Name string
|
||||
pk *rsa.PrivateKey
|
||||
edpk *ed25519.PrivateKey
|
||||
edpubk *ed25519.PublicKey
|
||||
}
|
||||
|
||||
// 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 {
|
||||
pk, err := utils.LoadPrivateKeyFromFile(filename)
|
||||
if err == nil {
|
||||
return Identity{"", pk}
|
||||
return Identity{"", pk, nil, nil}
|
||||
}
|
||||
return Identity{}
|
||||
}
|
||||
|
||||
// Initialize is a courtesy function for initializing an Identity in-code.
|
||||
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
|
||||
// is ready to perform operations.
|
||||
func (i *Identity) Initialized() bool {
|
||||
if i.pk == nil {
|
||||
return false
|
||||
if i.edpk == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
if i.edpk != nil {
|
||||
return *i.edpubk
|
||||
}
|
||||
|
||||
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
||||
N: i.pk.PublicKey.N,
|
||||
E: i.pk.PublicKey.E,
|
||||
|
@ -51,9 +66,18 @@ func (i *Identity) PublicKeyBytes() []byte {
|
|||
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.
|
||||
func (i *Identity) Hostname() string {
|
||||
return utils.GetTorHostname(i.PublicKeyBytes())
|
||||
if i.edpk != nil {
|
||||
return utils.GetTorV3Hostname(*i.edpubk)
|
||||
} else {
|
||||
return utils.GetTorHostname(i.PublicKeyBytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Sign produces a cryptographic signature using this Identities private key.
|
||||
|
|
|
@ -2,12 +2,5 @@
|
|||
|
||||
set -e
|
||||
pwd
|
||||
go test ${1} -coverprofile=main.cover.out -v .
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"github.com/agl/ed25519/extra25519"
|
||||
"github.com/yawning/bulb/utils/pkcs1"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
|
@ -30,6 +33,21 @@ func GetRandNumber() *big.Int {
|
|||
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
|
||||
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, RicochetKeySize)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"math"
|
||||
"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) {
|
||||
num := GetRandNumber()
|
||||
if !num.IsUint64() || num.Uint64() > uint64(math.MaxUint32) {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"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/contact"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// a contact request on the given channelID, with the given nick and message.
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte {
|
||||
// Construct a Result Message
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
40
utils/tor.go
40
utils/tor.go
|
@ -2,7 +2,11 @@ package utils
|
|||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha512"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -17,3 +21,39 @@ func GetTorHostname(publicKeyBytes []byte) string {
|
|||
data := base32.StdEncoding.EncodeToString(sha1bytes)
|
||||
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;
|
||||
|
||||
}
|
Loading…
Reference in New Issue