diff --git a/core/identity.go b/core/identity.go index 9a1da8d..e0ffe48 100644 --- a/core/identity.go +++ b/core/identity.go @@ -4,16 +4,21 @@ import ( "crypto/rsa" "encoding/base64" "errors" + "github.com/special/notricochet/core/utils" "github.com/yawning/bulb/utils/pkcs1" "log" + "sync" ) +// Identity represents the local user, including their contact address, +// and contains the contacts list. type Identity struct { core *Ricochet - address string - privateKey *rsa.PrivateKey + mutex sync.Mutex + address string + privateKey *rsa.PrivateKey contactList *ContactList } @@ -22,19 +27,19 @@ func CreateIdentity(core *Ricochet) (*Identity, error) { core: core, } - err := me.loadIdentity() - if err != nil { - log.Printf("Loading identity failed: %v", err) + 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("Loading contact list failed: %v", err) + log.Printf("Failed loading contact list: %v", err) return nil, err } me.contactList = contactList + go me.publishService(me.privateKey) return me, nil } @@ -52,19 +57,69 @@ func (me *Identity) loadIdentity() error { if err != nil { return err } - - me.address, err = pkcs1.OnionAddr(&me.privateKey.PublicKey) + me.address, err = utils.RicochetAddressFromKey(&me.privateKey.PublicKey) if err != nil { return err - } else if me.address == "" { - return errors.New("Invalid onion address") } - me.address = "ricochet:" + me.address + + 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.OpenWrite() + config.Identity.ServiceKey = base64.StdEncoding.EncodeToString(keyData) + config.Save() + + // Update Identity + me.address, err = utils.RicochetAddressFromKey(&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) { + 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 { + 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") + go me.core.Protocol.ServeListener(listener) +} + func (me *Identity) Address() string { return me.address } @@ -72,3 +127,7 @@ func (me *Identity) Address() string { func (me *Identity) ContactList() *ContactList { return me.contactList } + +func (me *Identity) PrivateKey() rsa.PrivateKey { + return *me.privateKey +} diff --git a/core/network.go b/core/network.go index 72b8fbc..b1e0a8c 100644 --- a/core/network.go +++ b/core/network.go @@ -154,7 +154,7 @@ func (n *Network) getConnection() *bulb.Conn { // This function will block until a control connection is available and // the service is added or the command has failed. If the control connection // is lost and reconnected, the service will be re-added automatically. -// BUG: Errors that occur after reconnecting cannot be detected. +// BUG(special): Errors that occur after reconnecting cannot be detected. func (n *Network) AddOnionPorts(ports []bulb.OnionPortSpec, key crypto.PrivateKey) (*OnionService, error) { if key == nil { // Ask for a new key, force RSA1024 diff --git a/core/protocol.go b/core/protocol.go new file mode 100644 index 0000000..ad50008 --- /dev/null +++ b/core/protocol.go @@ -0,0 +1,135 @@ +package core + +import ( + "encoding/asn1" + protocol "github.com/s-rah/go-ricochet" + "log" + "net" +) + +type Protocol struct { + core *Ricochet + + service *protocol.Ricochet + handler *protocolHandler +} + +// Implements protocol.RicochetService +type protocolHandler struct { + p *Protocol +} + +func CreateProtocol(core *Ricochet) *Protocol { + p := &Protocol{ + core: core, + service: new(protocol.Ricochet), + } + p.handler = &protocolHandler{p: p} + p.service.Init() + return p +} + +func (p *Protocol) ServeListener(listener net.Listener) { + p.service.ServeListener(p.handler, listener) +} + +// Strangely, ServeListener starts a background routine that watches a channel +// on p.service for new connections and dispatches their events to the handler +// for the listener. API needs a little work here. +func (p *Protocol) Connect(address string) (*protocol.OpenConnection, error) { + oc, err := p.service.Connect(address) + if err != nil { + return nil, err + } + oc.MyHostname = p.core.Identity.Address()[9:] + return oc, nil +} + +func (handler *protocolHandler) OnReady() { + log.Printf("protocol: OnReady") +} + +func (handler *protocolHandler) OnConnect(oc *protocol.OpenConnection) { + log.Printf("protocol: OnConnect: %v", oc) + if oc.Client { + log.Printf("Connected to %s", oc.OtherHostname) + oc.IsAuthed = true // Outbound connections are authenticated + oc.Authenticate(1) + } else { + // Strip ricochet: + oc.MyHostname = handler.p.core.Identity.Address()[9:] + } +} + +// Authentication Management +func (handler *protocolHandler) OnAuthenticationRequest(oc *protocol.OpenConnection, channelID int32, clientCookie [16]byte) { + log.Printf("protocol: OnAuthenticationRequest") + oc.ConfirmAuthChannel(channelID, clientCookie) +} + +func (handler *protocolHandler) OnAuthenticationChallenge(oc *protocol.OpenConnection, channelID int32, serverCookie [16]byte) { + log.Printf("protocol: OnAuthenticationChallenge") + privateKey := handler.p.core.Identity.PrivateKey() + publicKeyBytes, _ := asn1.Marshal(privateKey.PublicKey) + oc.SendProof(1, serverCookie, publicKeyBytes, &privateKey) +} + +func (handler *protocolHandler) OnAuthenticationProof(oc *protocol.OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool) { + result := oc.ValidateProof(channelID, publicKey, signature) + log.Printf("protocol: OnAuthenticationProof, result: %v", result) + oc.SendAuthenticationResult(channelID, result, isKnownContact) + oc.IsAuthed = result + oc.CloseChannel(channelID) +} + +func (handler *protocolHandler) OnAuthenticationResult(oc *protocol.OpenConnection, channelID int32, result bool, isKnownContact bool) { + log.Printf("protocol: OnAuthenticationResult, result: %v, known: %v", result, isKnownContact) + oc.IsAuthed = result +} + +// Contact Management +func (handler *protocolHandler) IsKnownContact(hostname string) bool { + return true +} + +func (handler *protocolHandler) OnContactRequest(oc *protocol.OpenConnection, channelID int32, nick string, message string) { +} + +func (handler *protocolHandler) OnContactRequestAck(oc *protocol.OpenConnection, channelID int32, status string) { +} + +// Managing Channels +func (handler *protocolHandler) OnOpenChannelRequest(oc *protocol.OpenConnection, channelID int32, channelType string) { + oc.AckOpenChannel(channelID, channelType) +} + +func (handler *protocolHandler) OnOpenChannelRequestSuccess(oc *protocol.OpenConnection, channelID int32) { +} +func (handler *protocolHandler) OnChannelClosed(oc *protocol.OpenConnection, channelID int32) { +} + +// Chat Messages +func (handler *protocolHandler) OnChatMessage(oc *protocol.OpenConnection, channelID int32, messageID int32, message string) { +} +func (handler *protocolHandler) OnChatMessageAck(oc *protocol.OpenConnection, channelID int32, messageID int32) { +} + +// Handle Errors +func (handler *protocolHandler) OnFailedChannelOpen(oc *protocol.OpenConnection, channelID int32, errorType string) { + oc.UnsetChannel(channelID) +} +func (handler *protocolHandler) OnGenericError(oc *protocol.OpenConnection, channelID int32) { + oc.RejectOpenChannel(channelID, "GenericError") +} +func (handler *protocolHandler) OnUnknownTypeError(oc *protocol.OpenConnection, channelID int32) { + oc.RejectOpenChannel(channelID, "UnknownTypeError") +} +func (handler *protocolHandler) OnUnauthorizedError(oc *protocol.OpenConnection, channelID int32) { + oc.RejectOpenChannel(channelID, "UnauthorizedError") +} +func (handler *protocolHandler) OnBadUsageError(oc *protocol.OpenConnection, channelID int32) { + oc.RejectOpenChannel(channelID, "BadUsageError") +} +func (handler *protocolHandler) OnFailedError(oc *protocol.OpenConnection, channelID int32) { + oc.RejectOpenChannel(channelID, "FailedError") +} diff --git a/core/ricochet.go b/core/ricochet.go index 839e54c..d9ec46b 100644 --- a/core/ricochet.go +++ b/core/ricochet.go @@ -3,6 +3,7 @@ package core type Ricochet struct { Config *Config Network *Network + Protocol *Protocol Identity *Identity } @@ -10,6 +11,7 @@ func (core *Ricochet) Init(conf *Config) error { var err error core.Config = conf core.Network = CreateNetwork() + core.Protocol = CreateProtocol(core) core.Identity, err = CreateIdentity(core) if err != nil { return err