-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmessage.go
201 lines (157 loc) · 4.72 KB
/
message.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// SPDX-FileCopyrightText: 2021 Alvar Penning
//
// SPDX-License-Identifier: GPL-3.0-or-later
package xochimilco
import (
"crypto/subtle"
"encoding"
"encoding/base64"
"fmt"
"strings"
)
// messageType identifies the message's type resp. its state and desired action.
type messageType byte
const (
_ messageType = iota
// sessOffer is Alice's initial message, asking Bob to upgrade their
// conversation by advertising her X3DH parameters.
sessOffer
// sessAck is Bob's first answer, including his X3DH parameters as well as
// a first nonsense message as a ciphertext to setup the Double Ratchet.
sessAck
// sessData are encrypted messages exchanged between the both parties.
sessData
// sessClose cancels a Xochimilco session. This is possible in each state
// and might occur due to a regular closing as well as rejecting an identity
// key.
// A MITM can also send this. However, a MITM can also drop messages.
sessClose
// Prefix indicates the beginning of an encoded message.
Prefix string = "!XO!"
// Suffix indicates the end of an encoded message.
Suffix string = "!OX!"
)
// marshalMessage creates the entire encoded message from a struct.
func marshalMessage(t messageType, m encoding.BinaryMarshaler) (out string, err error) {
b := new(strings.Builder)
_, _ = fmt.Fprint(b, Prefix)
_, _ = fmt.Fprint(b, int(t))
data, err := m.MarshalBinary()
if err != nil {
return
}
b64 := base64.NewEncoder(base64.StdEncoding, b)
if _, err = b64.Write(data); err != nil {
return
}
if err = b64.Close(); err != nil {
return
}
_, _ = fmt.Fprint(b, Suffix)
out = b.String()
return
}
// unmarshalMessage recreates the struct for an encoded message.
func unmarshalMessage(in string) (t messageType, m interface{}, err error) {
if !strings.HasPrefix(in, Prefix) || !strings.HasSuffix(in, Suffix) {
err = fmt.Errorf("message string misses pre- and/or suffix")
return
}
switch t = messageType(in[len(Prefix)] - '0'); t {
case sessOffer:
m = new(offerMessage)
case sessAck:
m = new(ackMessage)
case sessData:
m = new(dataMessage)
case sessClose:
m = new(closeMessage)
default:
err = fmt.Errorf("unsupported message type %d", t)
return
}
data, err := base64.StdEncoding.DecodeString(in[len(Prefix)+1 : len(in)-len(Suffix)])
if err != nil {
return
}
err = m.(encoding.BinaryUnmarshaler).UnmarshalBinary(data)
return
}
// offerMessage is the initial sessOffer message, announcing Alice's public
// Ed25519 Identity Key (32 byte), her X25519 signed prekey (32 byte), and the
// signature (64 bytes).
type offerMessage struct {
idKey []byte
spKey []byte
spSig []byte
}
func (msg offerMessage) MarshalBinary() (data []byte, err error) {
data = make([]byte, 32+32+64)
copy(data[:32], msg.idKey)
copy(data[32:64], msg.spKey)
copy(data[64:], msg.spSig)
return
}
func (msg *offerMessage) UnmarshalBinary(data []byte) (err error) {
if len(data) != 32+32+64 {
return fmt.Errorf("sessOffer payload MUST be of 128 byte")
}
msg.idKey = make([]byte, 32)
msg.spKey = make([]byte, 32)
msg.spSig = make([]byte, 64)
copy(msg.idKey, data[:32])
copy(msg.spKey, data[32:64])
copy(msg.spSig, data[64:])
return
}
// ackMessage is the second sessAck message for Bob to acknowledge Alice's
// sessOffer, finishing X3DH and starting his Double Ratchet. The fields are
// Bob's Ed25519 public key (32 byte), his ephemeral X25519 key (32 byte) and a
// nonsense initial ciphertext.
type ackMessage struct {
idKey []byte
eKey []byte
cipher []byte
}
func (msg ackMessage) MarshalBinary() (data []byte, err error) {
data = make([]byte, 32+32+len(msg.cipher))
copy(data[:32], msg.idKey)
copy(data[32:64], msg.eKey)
copy(data[64:], msg.cipher)
return
}
func (msg *ackMessage) UnmarshalBinary(data []byte) (err error) {
if len(data) <= 32+32 {
return fmt.Errorf("sessAck payload MUST be >= 64 byte")
}
msg.idKey = make([]byte, 32)
msg.eKey = make([]byte, 32)
msg.cipher = make([]byte, len(data)-64)
copy(msg.idKey, data[:32])
copy(msg.eKey, data[32:64])
copy(msg.cipher, data[64:])
return
}
// dataMessage is the sessData message for the bidirectional exchange of
// encrypted ciphertext. Thus, its length is dynamic.
type dataMessage []byte
func (msg dataMessage) MarshalBinary() (data []byte, err error) {
return msg, nil
}
func (msg *dataMessage) UnmarshalBinary(data []byte) (err error) {
*msg = data
return
}
// closeMessage is the bidirectional sessClose message. Its payload ix 0xff.
type closeMessage []byte
func (msg closeMessage) MarshalBinary() (data []byte, err error) {
return msg, nil
}
func (msg *closeMessage) UnmarshalBinary(data []byte) (err error) {
if subtle.ConstantTimeCompare(data, []byte{0xff}) != 1 {
err = fmt.Errorf("sessClose has an inavlid payload")
} else {
*msg = data
}
return
}