217 lines
5.0 KiB
Go
217 lines
5.0 KiB
Go
package core
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"errors"
|
|
"github.com/ricochet-im/ricochet-go/core/utils"
|
|
"github.com/ricochet-im/ricochet-go/rpc"
|
|
protocol "github.com/s-rah/go-ricochet"
|
|
connection "github.com/s-rah/go-ricochet/connection"
|
|
"github.com/yawning/bulb/utils/pkcs1"
|
|
"log"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
// Identity represents the local user, including their contact address,
|
|
// and contains the contacts list.
|
|
type Identity struct {
|
|
core *Ricochet
|
|
|
|
mutex sync.Mutex
|
|
|
|
address string
|
|
privateKey *rsa.PrivateKey
|
|
contactList *ContactList
|
|
|
|
ConversationStream *utils.Publisher
|
|
}
|
|
|
|
func CreateIdentity(core *Ricochet) (*Identity, error) {
|
|
me := &Identity{
|
|
core: core,
|
|
ConversationStream: utils.CreatePublisher(),
|
|
}
|
|
|
|
if err := me.loadIdentity(); err != nil {
|
|
log.Printf("Failed loading identity: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
contactList, err := LoadContactList(core)
|
|
if err != nil {
|
|
log.Printf("Failed loading contact list: %v", err)
|
|
return nil, err
|
|
}
|
|
me.contactList = contactList
|
|
|
|
contactList.StartConnections()
|
|
go me.publishService(me.privateKey)
|
|
return me, nil
|
|
}
|
|
|
|
func (me *Identity) loadIdentity() error {
|
|
config := me.core.Config.Read()
|
|
|
|
if keyData := config.Secrets.GetServicePrivateKey(); keyData != nil {
|
|
var err error
|
|
me.privateKey, _, err = pkcs1.DecodePrivateKeyDER(keyData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
me.address, err = AddressFromKey(&me.privateKey.PublicKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("Loaded identity %s", me.address)
|
|
} else {
|
|
log.Printf("Initializing new identity")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (me *Identity) setPrivateKey(key *rsa.PrivateKey) error {
|
|
me.mutex.Lock()
|
|
defer me.mutex.Unlock()
|
|
|
|
if me.privateKey != nil || me.address != "" {
|
|
return errors.New("Cannot change private key on identity")
|
|
}
|
|
|
|
// Save key to config
|
|
keyData, err := pkcs1.EncodePrivateKeyDER(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config := me.core.Config.Lock()
|
|
if config.Secrets == nil {
|
|
config.Secrets = &ricochet.Secrets{}
|
|
}
|
|
config.Secrets.ServicePrivateKey = keyData
|
|
me.core.Config.Unlock()
|
|
|
|
// Update Identity
|
|
me.address, err = AddressFromKey(&key.PublicKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
me.privateKey = key
|
|
|
|
log.Printf("Created new identity %s", me.address)
|
|
return nil
|
|
}
|
|
|
|
// BUG(special): No error handling for failures under publishService
|
|
func (me *Identity) publishService(key *rsa.PrivateKey) {
|
|
// This call will block until a control connection is available and the
|
|
// ADD_ONION command has returned. After creating the listener, it will
|
|
// be automatically re-published if the control connection is lost and
|
|
// later reconnected.
|
|
service, listener, err := me.core.Network.NewOnionListener(9878, key)
|
|
if err != nil {
|
|
log.Printf("Identity listener failed: %v", err)
|
|
// XXX handle
|
|
return
|
|
}
|
|
|
|
if key == nil {
|
|
if service.PrivateKey == nil {
|
|
log.Printf("Setting private key failed: no key returned")
|
|
// XXX handle
|
|
return
|
|
}
|
|
|
|
err := me.setPrivateKey(service.PrivateKey.(*rsa.PrivateKey))
|
|
if err != nil {
|
|
log.Printf("Setting private key failed: %v", err)
|
|
// XXX handle
|
|
return
|
|
}
|
|
}
|
|
|
|
log.Printf("Identity service published, accepting connections")
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
log.Printf("Identity listener failed: %v", err)
|
|
// XXX handle
|
|
return
|
|
}
|
|
|
|
// Handle connection in a separate goroutine and continue listening
|
|
go me.handleInboundConnection(conn)
|
|
}
|
|
}
|
|
|
|
func (me *Identity) handleInboundConnection(conn net.Conn) error {
|
|
defer func() {
|
|
// Close conn on return unless explicitly cleared
|
|
if conn != nil {
|
|
conn.Close()
|
|
}
|
|
}()
|
|
|
|
contactByHostname := func(hostname string) (*Contact, error) {
|
|
address, ok := AddressFromPlainHost(hostname)
|
|
if !ok {
|
|
// This would be a bug
|
|
return nil, errors.New("invalid authenticated hostname")
|
|
}
|
|
return me.contactList.ContactByAddress(address), nil
|
|
}
|
|
lookupContactAuth := func(hostname string, publicKey rsa.PublicKey) (bool, bool) {
|
|
contact, err := contactByHostname(hostname)
|
|
if err != nil {
|
|
return false, false
|
|
}
|
|
// allowed, known
|
|
return true, contact != nil
|
|
}
|
|
|
|
rc, err := protocol.NegotiateVersionInbound(conn)
|
|
if err != nil {
|
|
log.Printf("Inbound connection failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
authHandler := connection.HandleInboundConnection(rc)
|
|
err = authHandler.ProcessAuthAsServer(me.privateKey, lookupContactAuth)
|
|
if err != nil {
|
|
log.Printf("Inbound connection auth failed: %v", err)
|
|
return err
|
|
}
|
|
contact, err := contactByHostname(rc.RemoteHostname)
|
|
if err != nil {
|
|
log.Printf("Inbound connection lookup failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
if contact != nil {
|
|
// Known contact, pass the new connection to Contact
|
|
contact.AssignConnection(rc)
|
|
conn = nil
|
|
return nil
|
|
}
|
|
|
|
err = HandleInboundRequestConnection(rc, me.contactList)
|
|
if err == nil {
|
|
// Connection now belongs to an accepted contact, don't close it
|
|
conn = nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (me *Identity) Address() string {
|
|
return me.address
|
|
}
|
|
|
|
func (me *Identity) ContactList() *ContactList {
|
|
return me.contactList
|
|
}
|
|
|
|
func (me *Identity) PrivateKey() rsa.PrivateKey {
|
|
return *me.privateKey
|
|
}
|