commit dba6ae8097f9c48fa79b1adab5f65ea1acce7a50 Author: Sarah Jamie Lewis Date: Sat Oct 10 21:33:07 2015 -0700 Initial Commit This commit provides connection, authentication and the start of contact requests. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8c652d --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# GoRicochet + +GoRicochet is an implementation of the [Ricochet Protocol](https://ricochet.im) +in Go. + +**NOTE:** This project is in the very early stages and is in no way meant to be +a replacement for the core Ricochet implementation. This version exists for +the purpose of writing testing tools for Ricochet in Go. + +## Current Features + +* Connect to a Local Ricochet Client +* Issue an Authentication Request +* Issue a Contact Request + +If you have questions or want to contribute please contact Sarah @ +`ricochet:qn6uo4cmsrfv4kzq` diff --git a/auth/auth_message.go b/auth/auth_message.go new file mode 100644 index 0000000..9f20266 --- /dev/null +++ b/auth/auth_message.go @@ -0,0 +1,119 @@ +// Code generated by protoc-gen-go. +// source: AuthHiddenService.proto +// DO NOT EDIT! + +/* +Package Protocol_Data_AuthHiddenService is a generated protocol buffer package. + +It is generated from these files: + AuthHiddenService.proto + +It has these top-level messages: + Packet + Proof + Result +*/ +package Protocol_Data_AuthHiddenService + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import Protocol_Data_Control "github.com/s-rah/go-ricochet/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 { + PublicKey []byte `protobuf:"bytes,1,opt,name=public_key" json:"public_key,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,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) GetPublicKey() []byte { + if m != nil { + return m.PublicKey + } + return nil +} + +func (m *Proof) GetSignature() []byte { + if m != nil { + return m.Signature + } + 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:"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_ClientCookie = &proto.ExtensionDesc{ + ExtendedType: (*Protocol_Data_Control.OpenChannel)(nil), + ExtensionType: ([]byte)(nil), + Field: 7200, + Name: "Protocol.Data.AuthHiddenService.client_cookie", + Tag: "bytes,7200,opt,name=client_cookie", +} + +var E_ServerCookie = &proto.ExtensionDesc{ + ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil), + ExtensionType: ([]byte)(nil), + Field: 7200, + Name: "Protocol.Data.AuthHiddenService.server_cookie", + Tag: "bytes,7200,opt,name=server_cookie", +} + +func init() { + proto.RegisterExtension(E_ClientCookie) + proto.RegisterExtension(E_ServerCookie) +} diff --git a/contact/request.go b/contact/request.go new file mode 100644 index 0000000..f1996e8 --- /dev/null +++ b/contact/request.go @@ -0,0 +1,165 @@ +// Code generated by protoc-gen-go. +// source: ContactRequestChannel.proto +// DO NOT EDIT! + +/* +Package Protocol_Data_ContactRequest is a generated protocol buffer package. + +It is generated from these files: + ContactRequestChannel.proto + +It has these top-level messages: + ContactRequest + Response +*/ +package Protocol_Data_ContactRequest + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import Protocol_Data_Control "github.com/s-rah/go-ricochet/control" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Limits int32 + +const ( + Limits_MessageMaxCharacters Limits = 2000 + Limits_NicknameMaxCharacters Limits = 30 +) + +var Limits_name = map[int32]string{ + 2000: "MessageMaxCharacters", + 30: "NicknameMaxCharacters", +} +var Limits_value = map[string]int32{ + "MessageMaxCharacters": 2000, + "NicknameMaxCharacters": 30, +} + +func (x Limits) Enum() *Limits { + p := new(Limits) + *p = x + return p +} +func (x Limits) String() string { + return proto.EnumName(Limits_name, int32(x)) +} +func (x *Limits) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Limits_value, data, "Limits") + if err != nil { + return err + } + *x = Limits(value) + return nil +} + +type Response_Status int32 + +const ( + Response_Undefined Response_Status = 0 + Response_Pending Response_Status = 1 + Response_Accepted Response_Status = 2 + Response_Rejected Response_Status = 3 + Response_Error Response_Status = 4 +) + +var Response_Status_name = map[int32]string{ + 0: "Undefined", + 1: "Pending", + 2: "Accepted", + 3: "Rejected", + 4: "Error", +} +var Response_Status_value = map[string]int32{ + "Undefined": 0, + "Pending": 1, + "Accepted": 2, + "Rejected": 3, + "Error": 4, +} + +func (x Response_Status) Enum() *Response_Status { + p := new(Response_Status) + *p = x + return p +} +func (x Response_Status) String() string { + return proto.EnumName(Response_Status_name, int32(x)) +} +func (x *Response_Status) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Response_Status_value, data, "Response_Status") + if err != nil { + return err + } + *x = Response_Status(value) + return nil +} + +// Sent only as an attachment to OpenChannel +type ContactRequest struct { + Nickname *string `protobuf:"bytes,1,opt,name=nickname" json:"nickname,omitempty"` + MessageText *string `protobuf:"bytes,2,opt,name=message_text" json:"message_text,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ContactRequest) Reset() { *m = ContactRequest{} } +func (m *ContactRequest) String() string { return proto.CompactTextString(m) } +func (*ContactRequest) ProtoMessage() {} + +func (m *ContactRequest) GetNickname() string { + if m != nil && m.Nickname != nil { + return *m.Nickname + } + return "" +} + +func (m *ContactRequest) GetMessageText() string { + if m != nil && m.MessageText != nil { + return *m.MessageText + } + return "" +} + +// Response is the only valid message to send on the channel +type Response struct { + Status *Response_Status `protobuf:"varint,1,req,name=status,enum=Protocol.Data.ContactRequest.Response_Status" json:"status,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} + +func (m *Response) GetStatus() Response_Status { + if m != nil && m.Status != nil { + return *m.Status + } + return Response_Undefined +} + +var E_ContactRequest = &proto.ExtensionDesc{ + ExtendedType: (*Protocol_Data_Control.OpenChannel)(nil), + ExtensionType: (*ContactRequest)(nil), + Field: 200, + Name: "Protocol.Data.ContactRequest.contact_request", + Tag: "bytes,200,opt,name=contact_request", +} + +var E_Response = &proto.ExtensionDesc{ + ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil), + ExtensionType: (*Response)(nil), + Field: 201, + Name: "Protocol.Data.ContactRequest.response", + Tag: "bytes,201,opt,name=response", +} + +func init() { + proto.RegisterEnum("Protocol.Data.ContactRequest.Limits", Limits_name, Limits_value) + proto.RegisterEnum("Protocol.Data.ContactRequest.Response_Status", Response_Status_name, Response_Status_value) + proto.RegisterExtension(E_ContactRequest) + proto.RegisterExtension(E_Response) +} diff --git a/control/control_message.go b/control/control_message.go new file mode 100644 index 0000000..09e99b4 --- /dev/null +++ b/control/control_message.go @@ -0,0 +1,287 @@ +// Code generated by protoc-gen-go. +// source: ControlChannel.proto +// DO NOT EDIT! + +/* +Package Protocol_Data_Control is a generated protocol buffer package. + +It is generated from these files: + ControlChannel.proto + +It has these top-level messages: + Packet + OpenChannel + ChannelResult + KeepAlive + EnableFeatures + FeaturesEnabled +*/ +package Protocol_Data_Control + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type ChannelResult_CommonError int32 + +const ( + ChannelResult_GenericError ChannelResult_CommonError = 0 + ChannelResult_UnknownTypeError ChannelResult_CommonError = 1 + ChannelResult_UnauthorizedError ChannelResult_CommonError = 2 + ChannelResult_BadUsageError ChannelResult_CommonError = 3 + ChannelResult_FailedError ChannelResult_CommonError = 4 +) + +var ChannelResult_CommonError_name = map[int32]string{ + 0: "GenericError", + 1: "UnknownTypeError", + 2: "UnauthorizedError", + 3: "BadUsageError", + 4: "FailedError", +} +var ChannelResult_CommonError_value = map[string]int32{ + "GenericError": 0, + "UnknownTypeError": 1, + "UnauthorizedError": 2, + "BadUsageError": 3, + "FailedError": 4, +} + +func (x ChannelResult_CommonError) Enum() *ChannelResult_CommonError { + p := new(ChannelResult_CommonError) + *p = x + return p +} +func (x ChannelResult_CommonError) String() string { + return proto.EnumName(ChannelResult_CommonError_name, int32(x)) +} +func (x *ChannelResult_CommonError) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(ChannelResult_CommonError_value, data, "ChannelResult_CommonError") + if err != nil { + return err + } + *x = ChannelResult_CommonError(value) + return nil +} + +type Packet struct { + // Must contain exactly one field + OpenChannel *OpenChannel `protobuf:"bytes,1,opt,name=open_channel" json:"open_channel,omitempty"` + ChannelResult *ChannelResult `protobuf:"bytes,2,opt,name=channel_result" json:"channel_result,omitempty"` + KeepAlive *KeepAlive `protobuf:"bytes,3,opt,name=keep_alive" json:"keep_alive,omitempty"` + EnableFeatures *EnableFeatures `protobuf:"bytes,4,opt,name=enable_features" json:"enable_features,omitempty"` + FeaturesEnabled *FeaturesEnabled `protobuf:"bytes,5,opt,name=features_enabled" json:"features_enabled,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) GetOpenChannel() *OpenChannel { + if m != nil { + return m.OpenChannel + } + return nil +} + +func (m *Packet) GetChannelResult() *ChannelResult { + if m != nil { + return m.ChannelResult + } + return nil +} + +func (m *Packet) GetKeepAlive() *KeepAlive { + if m != nil { + return m.KeepAlive + } + return nil +} + +func (m *Packet) GetEnableFeatures() *EnableFeatures { + if m != nil { + return m.EnableFeatures + } + return nil +} + +func (m *Packet) GetFeaturesEnabled() *FeaturesEnabled { + if m != nil { + return m.FeaturesEnabled + } + return nil +} + +type OpenChannel struct { + ChannelIdentifier *int32 `protobuf:"varint,1,req,name=channel_identifier" json:"channel_identifier,omitempty"` + ChannelType *string `protobuf:"bytes,2,req,name=channel_type" json:"channel_type,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OpenChannel) Reset() { *m = OpenChannel{} } +func (m *OpenChannel) String() string { return proto.CompactTextString(m) } +func (*OpenChannel) ProtoMessage() {} + +var extRange_OpenChannel = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_OpenChannel +} +func (m *OpenChannel) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *OpenChannel) GetChannelIdentifier() int32 { + if m != nil && m.ChannelIdentifier != nil { + return *m.ChannelIdentifier + } + return 0 +} + +func (m *OpenChannel) GetChannelType() string { + if m != nil && m.ChannelType != nil { + return *m.ChannelType + } + return "" +} + +type ChannelResult struct { + ChannelIdentifier *int32 `protobuf:"varint,1,req,name=channel_identifier" json:"channel_identifier,omitempty"` + Opened *bool `protobuf:"varint,2,req,name=opened" json:"opened,omitempty"` + CommonError *ChannelResult_CommonError `protobuf:"varint,3,opt,name=common_error,enum=Protocol.Data.Control.ChannelResult_CommonError" json:"common_error,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ChannelResult) Reset() { *m = ChannelResult{} } +func (m *ChannelResult) String() string { return proto.CompactTextString(m) } +func (*ChannelResult) ProtoMessage() {} + +var extRange_ChannelResult = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_ChannelResult +} +func (m *ChannelResult) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *ChannelResult) GetChannelIdentifier() int32 { + if m != nil && m.ChannelIdentifier != nil { + return *m.ChannelIdentifier + } + return 0 +} + +func (m *ChannelResult) GetOpened() bool { + if m != nil && m.Opened != nil { + return *m.Opened + } + return false +} + +func (m *ChannelResult) GetCommonError() ChannelResult_CommonError { + if m != nil && m.CommonError != nil { + return *m.CommonError + } + return ChannelResult_GenericError +} + +type KeepAlive struct { + ResponseRequested *bool `protobuf:"varint,1,req,name=response_requested" json:"response_requested,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *KeepAlive) Reset() { *m = KeepAlive{} } +func (m *KeepAlive) String() string { return proto.CompactTextString(m) } +func (*KeepAlive) ProtoMessage() {} + +func (m *KeepAlive) GetResponseRequested() bool { + if m != nil && m.ResponseRequested != nil { + return *m.ResponseRequested + } + return false +} + +type EnableFeatures struct { + Feature []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EnableFeatures) Reset() { *m = EnableFeatures{} } +func (m *EnableFeatures) String() string { return proto.CompactTextString(m) } +func (*EnableFeatures) ProtoMessage() {} + +var extRange_EnableFeatures = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_EnableFeatures +} +func (m *EnableFeatures) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *EnableFeatures) GetFeature() []string { + if m != nil { + return m.Feature + } + return nil +} + +type FeaturesEnabled struct { + Feature []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FeaturesEnabled) Reset() { *m = FeaturesEnabled{} } +func (m *FeaturesEnabled) String() string { return proto.CompactTextString(m) } +func (*FeaturesEnabled) ProtoMessage() {} + +var extRange_FeaturesEnabled = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_FeaturesEnabled +} +func (m *FeaturesEnabled) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *FeaturesEnabled) GetFeature() []string { + if m != nil { + return m.Feature + } + return nil +} + +func init() { + proto.RegisterEnum("Protocol.Data.Control.ChannelResult_CommonError", ChannelResult_CommonError_name, ChannelResult_CommonError_value) +} diff --git a/ricochet.go b/ricochet.go new file mode 100644 index 0000000..3037803 --- /dev/null +++ b/ricochet.go @@ -0,0 +1,281 @@ +package goricochet + +import ( + "crypto" + "crypto/hmac" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "github.com/golang/protobuf/proto" + "github.com/s-rah/go-ricochet/auth" + "github.com/s-rah/go-ricochet/contact" + "github.com/s-rah/go-ricochet/control" + "io/ioutil" + "log" + "net" + "os" +) + +// Ricochet is a protocol to conducting anonymous IM. +type Ricochet struct { + conn net.Conn + privateKey *pem.Block + logger *log.Logger +} + +// Init sets up the Ricochet object. It takes in a filename of a hidden service +// private_key file so it can successfully authenticate itself with other +// clients. +func (r *Ricochet) Init(filename string) { + r.logger = log.New(os.Stdout, "[Ricochet]: ", log.Ltime|log.Lmicroseconds) + pemData, err := ioutil.ReadFile(filename) + + if err != nil { + r.logger.Print("Error Reading Private Key: ", err) + } + + block, _ := pem.Decode(pemData) + if block == nil || block.Type != "RSA PRIVATE KEY" { + r.logger.Print("No valid PEM data found") + } + + r.privateKey = block +} + +func (r *Ricochet) send(data []byte) { + fmt.Fprintf(r.conn, "%s", data) +} + +func (r *Ricochet) recv() ([]byte, error) { + buf := make([]byte, 4096) + n, err := r.conn.Read(buf) + r.logger.Print("Received Response From Service: ", n, err) + if err != nil { + return nil, err + } + ret := make([]byte, n) + copy(ret[:], buf[:]) + return ret, nil +} + +func (r *Ricochet) decodePacket(response []byte) *Protocol_Data_Control.Packet { + // TODO: Check Length and Channel are Sane + if len(response) < 4 { + r.logger.Fatal("Response is too short ", response) + return nil + } + res := new(Protocol_Data_Control.Packet) + err := proto.Unmarshal(response[4:], res) + + if err != nil { + r.logger.Fatal("Error Unmarshalling Response", err) + panic(err) + } + + return res +} + +func (r *Ricochet) decodeResult(response []byte) *Protocol_Data_AuthHiddenService.Packet { + // TODO: Check Length and Channel are Sane + if len(response) < 4 { + r.logger.Fatal("Response is too short ", response) + return nil + } + length := response[1] + + r.logger.Print(response) + res := new(Protocol_Data_AuthHiddenService.Packet) + err := proto.Unmarshal(response[4:length], res) + + if err != nil { + r.logger.Fatal("Error Unmarshalling Response: ", err) + panic(err) + } + + return res +} + +func (r *Ricochet) constructProtocol(data []byte, channel byte) []byte { + header := make([]byte, 4+len(data)) + r.logger.Print("Wrting Packet of Size: ", len(header)) + header[0] = byte(len(header) >> 8) + header[1] = byte(len(header) & 0x00FF) + header[2] = 0x00 + header[3] = channel + copy(header[4:], data[:]) + return header +} + +// Connect sets up a ricochet connection between from and to which are +// both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this +// function finished successfully then the connection can be assumed to +// be open and authenticated. +func (r *Ricochet) Connect(from string, to string) error { + + // TODO: In the future we will want the ability to connect + // through Tor to a hidden service address. This works if + // you uncomment this, but is slower for testing purposes + // dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, "127.0.0.1:9050") + //return dialSocksProxy("", "qn6uo4cmsrfv4kzq.onion:9878") + + // TODO: For now hardcoding port numbers, these change + // on startup so need to be reset every time. + tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:49952") + if err != nil { + r.logger.Fatal("Cannot Resolve TCP Address ", err) + return err + } + r.conn, err = net.DialTCP("tcp", nil, tcpAddr) + if err != nil { + r.logger.Fatal("Cannot Dial TCP Address ", err) + return err + } + + r.negotiateVersion() + + // Construct an Open Channel Message + oc := &Protocol_Data_Control.OpenChannel{ + ChannelIdentifier: proto.Int32(1), + ChannelType: proto.String("im.ricochet.auth.hidden-service"), + } + err = proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, []byte("0000000000000000")) + pc := &Protocol_Data_Control.Packet{ + OpenChannel: oc, + } + data, err := proto.Marshal(pc) + + if err != nil { + r.logger.Fatal("Cannot Marshal Open Channel Message: ", err) + panic("Cannot Marshal Open Channel Message") + } + + openChannel := r.constructProtocol(data, 0) + r.logger.Print("Opening Channel: ", pc) + r.send(openChannel) + + response, _ := r.recv() + openChannelResponse := r.decodePacket(response) + r.logger.Print("Received Response: ", openChannelResponse) + channelResult := openChannelResponse.GetChannelResult() + + if channelResult.GetOpened() == true { + r.logger.Print("Channel Opened Successfully: ", channelResult.GetChannelIdentifier()) + } + + sCookie, _ := proto.GetExtension(channelResult, Protocol_Data_AuthHiddenService.E_ServerCookie) + serverCookie, _ := sCookie.([]byte) + + r.logger.Print("Starting Authentication with Server Cookie: ", serverCookie) + + key := make([]byte, 32) + copy(key[0:16], []byte("0000000000000000")) + copy(key[16:], serverCookie) + value := []byte(from + to) + r.logger.Print("Got Hmac Key: ", key) + r.logger.Print("Got Proof Value: ", string(value)) + mac := hmac.New(sha256.New, key) + mac.Write(value) + hmac := mac.Sum(nil) + r.logger.Print("Got HMAC: ", hmac) + + privateKey, err := x509.ParsePKCS1PrivateKey(r.privateKey.Bytes) + if err != nil { + r.logger.Fatalf("Private key can't be decoded: %s", err) + } + + // DER Encode the Public Key + publickeybytes, err := asn1.Marshal(rsa.PublicKey{ + N: privateKey.PublicKey.N, + E: privateKey.PublicKey.E, + }) + + signature, _ := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, hmac) + signatureBytes := make([]byte, 128) + copy(signatureBytes[:], signature[:]) + + r.logger.Print("Signature Length: ", len(signatureBytes)) + r.logger.Print("Public Key Length: ", len(publickeybytes), ", Bit Size: ", privateKey.PublicKey.N.BitLen()) + + // Construct a Proof Message + proof := &Protocol_Data_AuthHiddenService.Proof{ + PublicKey: publickeybytes, + Signature: signatureBytes, + } + + ahsPacket := &Protocol_Data_AuthHiddenService.Packet{ + Proof: proof, + Result: nil, + } + + data, err = proto.Marshal(ahsPacket) + + sendProof := r.constructProtocol(data, 1) + r.logger.Print("Constructed Proof: ", ahsPacket) + r.send(sendProof) + + response, _ = r.recv() + resultResponse := r.decodeResult(response) + r.logger.Print("Received Result: ", resultResponse) + return nil +} + +// SendContactRequest initiates a contact request to the server. +// Prerequisites: +// * Must have Previously issued a successful Connect() +func (r *Ricochet) SendContactRequest(nick string, message string) { + // Construct a Contact Request Channel + oc := &Protocol_Data_Control.OpenChannel{ + ChannelIdentifier: proto.Int32(3), + ChannelType: proto.String("im.ricochet.contact.request"), + } + + contactRequest := &Protocol_Data_ContactRequest.ContactRequest{ + Nickname: proto.String(nick), + MessageText: proto.String(message), + } + + err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest) + pc := &Protocol_Data_Control.Packet{ + OpenChannel: oc, + } + data, err := proto.Marshal(pc) + + if err != nil { + r.logger.Fatal("Cannot Marshal Open Channel Message: ", err) + panic("Cannot Marshal Open Channel Message") + } + + openChannel := r.constructProtocol(data, 0) + r.logger.Print("Opening Channel: ", pc) + r.send(openChannel) + response, _ := r.recv() + openChannelResponse := r.decodePacket(response) + r.logger.Print("Received Response: ", openChannelResponse) +} + +// negotiateVersion Perform version negotiation with the connected host. +func (r *Ricochet) negotiateVersion() { + version := make([]byte, 4) + version[0] = 0x49 + version[1] = 0x4D + version[2] = 0x01 + version[3] = 0x01 + r.send(version) + r.logger.Print("Negotiating Version ", version) + res, err := r.recv() + + if len(res) != 1 || err != nil { + r.logger.Fatal("Failed Version Negotiating: ", res, err) + panic("Failed Version Negotiating") + } + + if res[0] != 1 { + r.logger.Fatal("Failed Version Negotiating - Invalid Version ", res) + panic("Failed Version Negotiating") + } + + r.logger.Print("Successfully Negotiated Version ", res[0]) +}