diff --git a/channels/contactrequestchannel.go b/channels/contactrequestchannel.go index 4ca1b2d..d358ccc 100644 --- a/channels/contactrequestchannel.go +++ b/channels/contactrequestchannel.go @@ -128,6 +128,7 @@ func (crc *ContactRequestChannel) OpenOutboundResult(err error, crm *Protocol_Da crc.channel.SendMessage([]byte{}) } +// SendResponse sends a contact request status response to the requester. func (crc *ContactRequestChannel) SendResponse(status string) { messageBuilder := new(utils.MessageBuilder) crc.channel.SendMessage(messageBuilder.ReplyToContactRequest(crc.channel.ID, status)) diff --git a/channels/hiddenserviceauthchannel.go b/channels/hiddenserviceauthchannel.go index cb90f4c..498fd69 100644 --- a/channels/hiddenserviceauthchannel.go +++ b/channels/hiddenserviceauthchannel.go @@ -8,6 +8,7 @@ import ( "crypto/sha256" "encoding/asn1" "github.com/golang/protobuf/proto" + "github.com/s-rah/go-ricochet/identity" "github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/wire/auth" "github.com/s-rah/go-ricochet/wire/control" @@ -15,14 +16,14 @@ import ( ) const ( + // InvalidClientCookieError - returned when the client provides a cookie with the wrong length InvalidClientCookieError = utils.Error("InvalidClientCookieError") ) // HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service" type HiddenServiceAuthChannel struct { // PrivateKey must be set for client-side authentication channels - PrivateKey *rsa.PrivateKey - // Server Hostname must be set for client-side authentication channels + Identity identity.Identity ServerHostname string // Callbacks @@ -71,7 +72,7 @@ func (ah *HiddenServiceAuthChannel) Closed(err error) { // Remote -> [Open Authentication Channel] -> Local func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) { - if ah.PrivateKey == nil { + if !ah.Identity.Initialized() { return nil, utils.PrivateKeyNotSetError } @@ -93,7 +94,7 @@ func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_D // Local -> [Open Authentication Channel] -> Remote func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) { - if ah.PrivateKey == nil { + if !ah.Identity.Initialized() { return nil, utils.PrivateKeyNotSetError } @@ -122,15 +123,9 @@ func (ah *HiddenServiceAuthChannel) OpenOutboundResult(err error, crm *Protocol_ ah.AddServerCookie(serverCookie.([]byte)[:]) - publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{ - N: ah.PrivateKey.PublicKey.N, - E: ah.PrivateKey.PublicKey.E, - }) + challenge := ah.GenChallenge(ah.Identity.Hostname(), ah.ServerHostname) - clientHostname := utils.GetTorHostname(publicKeyBytes) - challenge := ah.GenChallenge(clientHostname, ah.ServerHostname) - - signature, err := rsa.SignPKCS1v15(nil, ah.PrivateKey, crypto.SHA256, challenge) + signature, err := ah.Identity.Sign(challenge) if err != nil { ah.channel.SendMessage([]byte{}) @@ -138,14 +133,14 @@ func (ah *HiddenServiceAuthChannel) OpenOutboundResult(err error, crm *Protocol_ } messageBuilder := new(utils.MessageBuilder) - proof := messageBuilder.Proof(publicKeyBytes, signature) + proof := messageBuilder.Proof(ah.Identity.PublicKeyBytes(), signature) ah.channel.SendMessage(proof) } } } // Packet is called for each raw packet received on this channel. -// Input: Remote -> [Proof] -> Client +// Input: Client -> [Proof] -> Remote // OR // Input: Remote -> [Result] -> Client func (ah *HiddenServiceAuthChannel) Packet(data []byte) { @@ -160,18 +155,13 @@ func (ah *HiddenServiceAuthChannel) Packet(data []byte) { if res.GetProof() != nil && ah.channel.Direction == Inbound { provisionalClientHostname := utils.GetTorHostname(res.GetProof().GetPublicKey()) - publicKeyBytes, err := asn1.Marshal(rsa.PublicKey{ - N: ah.PrivateKey.PublicKey.N, - E: ah.PrivateKey.PublicKey.E, - }) - if err != nil { ah.ServerAuthInvalid(err) ah.channel.SendMessage([]byte{}) return } - serverHostname := utils.GetTorHostname(publicKeyBytes) + serverHostname := ah.Identity.Hostname() publicKey := rsa.PublicKey{} _, err = asn1.Unmarshal(res.GetProof().GetPublicKey(), &publicKey) diff --git a/channels/hiddenserviceauthchannel_test.go b/channels/hiddenserviceauthchannel_test.go index e219100..686f81a 100644 --- a/channels/hiddenserviceauthchannel_test.go +++ b/channels/hiddenserviceauthchannel_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/rsa" "github.com/golang/protobuf/proto" + "github.com/s-rah/go-ricochet/identity" "github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/wire/control" "testing" @@ -71,10 +72,10 @@ func GetOpenAuthenticationChannelMessage() *Protocol_Data_Control.OpenChannel { } func TestAuthenticationOpenInbound(t *testing.T) { - privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key") + id := identity.Init("../testing/private_key") opm := GetOpenAuthenticationChannelMessage() authHandler := new(HiddenServiceAuthChannel) - authHandler.PrivateKey = privateKey + authHandler.Identity = id channel := Channel{ID: 1} response, err := authHandler.OpenInbound(&channel, opm) @@ -86,14 +87,15 @@ func TestAuthenticationOpenInbound(t *testing.T) { t.Errorf("Response not a Open Channel Result %v", res) } } else { - t.Errorf("HiddenServiceAuthChannel OpenOutbound Failed: %v", err) + t.Errorf("HiddenServiceAuthChannel OpenInbound Failed: %v", err) } } func TestAuthenticationOpenOutbound(t *testing.T) { - privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key") + id := identity.Init("../testing/private_key") authHandler := new(HiddenServiceAuthChannel) - authHandler.PrivateKey = privateKey + authHandler.Identity = id + authHandler.ServerHostname = "kwke2hntvyfqm7dr" channel := Channel{ID: 1} response, err := authHandler.OpenOutbound(&channel) @@ -105,20 +107,20 @@ func TestAuthenticationOpenOutbound(t *testing.T) { t.Errorf("Open Channel Packet not included %v", res) } } else { - t.Errorf("HiddenServiceAuthChannel OpenInbound Failed: %v", err) + t.Errorf("HiddenServiceAuthChannel OpenOutbound Failed: %v", err) } } func TestAuthenticationOpenOutboundResult(t *testing.T) { - privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key") + id := identity.Init("../testing/private_key") authHandlerA := new(HiddenServiceAuthChannel) authHandlerB := new(HiddenServiceAuthChannel) + authHandlerA.Identity = id authHandlerA.ServerHostname = "kwke2hntvyfqm7dr" - authHandlerA.PrivateKey = privateKey authHandlerA.ClientAuthResult = func(accepted, known bool) {} channelA := Channel{ID: 1, Direction: Outbound} channelA.SendMessage = func(message []byte) { @@ -130,8 +132,7 @@ func TestAuthenticationOpenOutboundResult(t *testing.T) { res := new(Protocol_Data_Control.Packet) proto.Unmarshal(response[:], res) - authHandlerB.ServerHostname = "kwke2hntvyfqm7dr" - authHandlerB.PrivateKey = privateKey + authHandlerB.Identity = id authHandlerB.ServerAuthValid = func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { return true, true } authHandlerB.ServerAuthInvalid = func(err error) { t.Error("server received invalid auth") } channelB := Channel{ID: 1, Direction: Inbound} diff --git a/connection/connection.go b/connection/connection.go index 2fd8142..1d28dfe 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -84,6 +84,9 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn return rc } +// TraceLog turns on debug logging, you shouldn't need to do this but if for some +// reason ricochet isn't working, you can use this to see at what point in the +// protcol trace ricochet is failing. func (rc *Connection) TraceLog(enabled bool) { rc.trace = enabled } diff --git a/connection/inboundconnectionhandler.go b/connection/inboundconnectionhandler.go index e74bd47..e6ddadb 100644 --- a/connection/inboundconnectionhandler.go +++ b/connection/inboundconnectionhandler.go @@ -3,6 +3,7 @@ package connection import ( "crypto/rsa" "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/identity" "github.com/s-rah/go-ricochet/policies" "github.com/s-rah/go-ricochet/utils" "sync" @@ -63,7 +64,7 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.Private ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", func() channels.Handler { return &channels.HiddenServiceAuthChannel{ - PrivateKey: privateKey, + Identity: identity.Initialize("", privateKey), ServerAuthValid: onAuthValid, ServerAuthInvalid: onAuthInvalid, } diff --git a/connection/outboundconnectionhandler.go b/connection/outboundconnectionhandler.go index be13328..f8c09bd 100644 --- a/connection/outboundconnectionhandler.go +++ b/connection/outboundconnectionhandler.go @@ -3,6 +3,7 @@ package connection import ( "crypto/rsa" "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/identity" "github.com/s-rah/go-ricochet/policies" "github.com/s-rah/go-ricochet/utils" "sync" @@ -68,7 +69,7 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.Privat err := och.connection.Do(func() error { _, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service", &channels.HiddenServiceAuthChannel{ - PrivateKey: privateKey, + Identity: identity.Initialize("", privateKey), ServerHostname: och.connection.RemoteHostname, ClientAuthResult: authCallback, }) diff --git a/identity/identity.go b/identity/identity.go new file mode 100644 index 0000000..a55050f --- /dev/null +++ b/identity/identity.go @@ -0,0 +1,62 @@ +package identity + +import ( + "crypto" + "crypto/rsa" + "encoding/asn1" + "github.com/s-rah/go-ricochet/utils" +) + +// Identity is an encapsulation of Name, PrivateKey and other features +// that make up a Ricochet client. +// 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 +} + +// Init loads an identity from a file. Currently file should be a private_key +// but this may change in the future. //XXX +func Init(filename string) Identity { + pk, err := utils.LoadPrivateKeyFromFile(filename) + if err == nil { + return Identity{"", pk} + } + 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} +} + +// 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 + } + return true +} + +// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly +// format. //TODO Not sure I like this. +func (i *Identity) PublicKeyBytes() []byte { + publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{ + N: i.pk.PublicKey.N, + E: i.pk.PublicKey.E, + }) + + return publicKeyBytes +} + +// Hostname provides the onion address associated with this Identity. +func (i *Identity) Hostname() string { + return utils.GetTorHostname(i.PublicKeyBytes()) +} + +// Sign produces a cryptographic signature using this Identities private key. +func (i *Identity) Sign(challenge []byte) ([]byte, error) { + return rsa.SignPKCS1v15(nil, i.pk, crypto.SHA256, challenge) +} diff --git a/identity/identity_test.go b/identity/identity_test.go new file mode 100644 index 0000000..307776c --- /dev/null +++ b/identity/identity_test.go @@ -0,0 +1,13 @@ +package identity + +import ( + "github.com/s-rah/go-ricochet/identity" + "testing" +) + +func TestIdentity(t *testing.T) { + id := identity.Init("../testing/private_key") + if id.Hostname() != "kwke2hntvyfqm7dr" { + t.Errorf("Expected %v as Hostname() got: ", "kwke2hntvyfqm7dr", id.Hostname()) + } +} diff --git a/ricochet.go b/ricochet.go index d92ae20..1fd9547 100644 --- a/ricochet.go +++ b/ricochet.go @@ -28,7 +28,7 @@ func Open(remoteHostname string) (*connection.Connection, error) { return rc, nil } -// negotiate version takes an open network connection and executes +// NegotiateVersionOutbound takes an open network connection and executes // the ricochet version negotiation procedure. func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection.Connection, error) { versions := []byte{0x49, 0x4D, 0x01, 0x01} diff --git a/utils/crypto_test.go b/utils/crypto_test.go index 993f1f6..2436dae 100644 --- a/utils/crypto_test.go +++ b/utils/crypto_test.go @@ -5,15 +5,15 @@ import ( ) func TestGeneratePrivateKey(t *testing.T) { - _, err := GeneratePrivateKey() - if err != nil { - t.Errorf("Error while generating private key: %v", err) - } + _, err := GeneratePrivateKey() + if err != nil { + t.Errorf("Error while generating private key: %v", err) + } } func TestLoadPrivateKey(t *testing.T) { - _,err := LoadPrivateKeyFromFile("../testing/private_key") - if err != nil { - t.Errorf("Error while loading private key from file: %v", err) - } + _, err := LoadPrivateKeyFromFile("../testing/private_key") + if err != nil { + t.Errorf("Error while loading private key from file: %v", err) + } }