1package tg
2
3import (
4	crand "crypto/rand"
5	"fmt"
6	"math/rand"
7
8	"github.com/ooni/psiphon/oopsi/github.com/redjack/marionette"
9	"github.com/ooni/psiphon/oopsi/github.com/redjack/marionette/fte"
10)
11
12type AmazonMsgLensCipher struct {
13	key    string
14	min    int
15	max    int
16	target int
17	regex  string
18}
19
20func NewAmazonMsgLensCipher(key, regex string) *AmazonMsgLensCipher {
21	return &AmazonMsgLensCipher{
22		key:    key,
23		min:    fte.COVERTEXT_HEADER_LEN_CIPHERTTEXT + fte.CTXT_EXPANSION + 32,
24		max:    1 << 18,
25		target: 0,
26		regex:  regex,
27	}
28}
29
30func (h *AmazonMsgLensCipher) Key() string { return h.key }
31
32func (h *AmazonMsgLensCipher) Capacity(fsm marionette.FSM) (int, error) {
33	h.target = amazonMsgLens[rand.Intn(len(amazonMsgLens))]
34	if h.target < h.min {
35		return 0, nil
36	} else if h.target > h.max {
37		// We do this to prevent unranking really large slices
38		// in practice this is probably bad since it unnaturally caps
39		// our message sizes to whatever FTE can support
40		h.target = h.max
41		return h.max, nil
42	}
43	n := h.target - fte.COVERTEXT_HEADER_LEN_CIPHERTTEXT
44	n -= fte.CTXT_EXPANSION
45	n -= 1
46	return n, nil
47}
48
49func (h *AmazonMsgLensCipher) Encrypt(fsm marionette.FSM, template string, plaintext []byte) (ciphertext []byte, err error) {
50	if h.target < h.min || h.target > h.max {
51		dfa, err := fsm.DFA(h.regex, h.target)
52		if err != nil {
53			return nil, err
54		}
55
56		numWords, err := dfa.NumWordsInSlice(h.target)
57		if err != nil {
58			return nil, err
59		}
60
61		rnd, err := crand.Int(crand.Reader, numWords)
62		if err != nil {
63			return nil, err
64		}
65
66		ret, err := dfa.Unrank(rnd)
67		if err != nil {
68			return nil, err
69		}
70		return []byte(ret), nil
71	}
72
73	cipher, err := fsm.Cipher(h.regex, h.min)
74	if err != nil {
75		return nil, err
76	}
77
78	ciphertext, err = cipher.Encrypt(plaintext)
79	if err != nil {
80		return nil, err
81	} else if len(ciphertext) != h.target {
82		return nil, fmt.Errorf("Could not find ciphertext of len %d (%d)", h.target, len(ciphertext))
83	}
84	return ciphertext, nil
85}
86
87func (h *AmazonMsgLensCipher) Decrypt(fsm marionette.FSM, ciphertext []byte) (plaintext []byte, err error) {
88	if len(ciphertext) < h.min {
89		return nil, nil
90	}
91	cipher, err := fsm.Cipher(h.regex, h.min)
92	if err != nil {
93		return nil, err
94	}
95	plaintext, _, err = cipher.Decrypt(ciphertext)
96	return plaintext, err
97}
98
99// This a weighted list of message lengths.
100var amazonMsgLens []int
101
102func init() {
103	for _, item := range []struct {
104		n      int
105		weight int
106	}{
107		{n: 2049, weight: 1},
108		{n: 2052, weight: 2},
109		{n: 2054, weight: 2},
110		{n: 2057, weight: 3},
111		{n: 2058, weight: 2},
112		{n: 2059, weight: 1},
113		{n: 2065, weight: 1},
114		{n: 17429, weight: 1},
115		{n: 3098, weight: 1},
116		{n: 687, weight: 3},
117		{n: 2084, weight: 1},
118		{n: 42, weight: 58},
119		{n: 43, weight: 107},
120		{n: 9260, weight: 1},
121		{n: 11309, weight: 1},
122		{n: 11829, weight: 1},
123		{n: 9271, weight: 1},
124		{n: 6154, weight: 1},
125		{n: 64, weight: 15},
126		{n: 1094, weight: 1},
127		{n: 12376, weight: 1},
128		{n: 89, weight: 1},
129		{n: 10848, weight: 1},
130		{n: 5223, weight: 1},
131		{n: 69231, weight: 1},
132		{n: 7795, weight: 1},
133		{n: 2678, weight: 1},
134		{n: 8830, weight: 1},
135		{n: 29826, weight: 1},
136		{n: 16006, weight: 10},
137		{n: 8938, weight: 1},
138		{n: 17055, weight: 2},
139		{n: 87712, weight: 1},
140		{n: 23202, weight: 1},
141		{n: 7441, weight: 1},
142		{n: 17681, weight: 1},
143		{n: 12456, weight: 1},
144		{n: 41132, weight: 1},
145		{n: 25263, weight: 6},
146		{n: 689, weight: 1},
147		{n: 9916, weight: 1},
148		{n: 10101, weight: 2},
149		{n: 1730, weight: 1},
150		{n: 10948, weight: 1},
151		{n: 26826, weight: 1},
152		{n: 6357, weight: 1},
153		{n: 13021, weight: 2},
154		{n: 1246, weight: 4},
155		{n: 19683, weight: 1},
156		{n: 1765, weight: 1},
157		{n: 1767, weight: 1},
158		{n: 1768, weight: 1},
159		{n: 1769, weight: 4},
160		{n: 1770, weight: 6},
161		{n: 1771, weight: 3},
162		{n: 1772, weight: 2},
163		{n: 1773, weight: 4},
164		{n: 1774, weight: 4},
165		{n: 1775, weight: 1},
166		{n: 1776, weight: 1},
167		{n: 1779, weight: 1},
168		{n: 40696, weight: 1},
169		{n: 767, weight: 1},
170		{n: 17665, weight: 1},
171		{n: 27909, weight: 1},
172		{n: 12550, weight: 1},
173		{n: 5385, weight: 1},
174		{n: 16651, weight: 1},
175		{n: 5392, weight: 1},
176		{n: 26385, weight: 1},
177		{n: 12056, weight: 1},
178		{n: 41245, weight: 2},
179		{n: 13097, weight: 1},
180		{n: 15152, weight: 1},
181		{n: 310, weight: 1},
182		{n: 40759, weight: 1},
183		{n: 9528, weight: 1},
184		{n: 8000, weight: 7},
185		{n: 471, weight: 1},
186		{n: 15180, weight: 1},
187		{n: 14158, weight: 3},
188		{n: 37719, weight: 2},
189		{n: 1895, weight: 1},
190		{n: 31082, weight: 1},
191		{n: 19824, weight: 1},
192		{n: 30956, weight: 1},
193		{n: 18807, weight: 1},
194		{n: 11095, weight: 1},
195		{n: 37756, weight: 2},
196		{n: 746, weight: 1},
197		{n: 10475, weight: 1},
198		{n: 4332, weight: 1},
199		{n: 35730, weight: 1},
200		{n: 11667, weight: 1},
201		{n: 16788, weight: 1},
202		{n: 12182, weight: 4},
203		{n: 39663, weight: 1},
204		{n: 9126, weight: 1},
205		{n: 35760, weight: 1},
206		{n: 12735, weight: 1},
207		{n: 6594, weight: 1},
208		{n: 451, weight: 15},
209		{n: 19402, weight: 1},
210		{n: 463, weight: 3},
211		{n: 10193, weight: 1},
212		{n: 16853, weight: 6},
213		{n: 982, weight: 1},
214		{n: 15865, weight: 1},
215		{n: 2008, weight: 2},
216		{n: 476, weight: 1},
217		{n: 13655, weight: 1},
218		{n: 10213, weight: 1},
219		{n: 10737, weight: 1},
220		{n: 15858, weight: 1},
221		{n: 2035, weight: 6},
222		{n: 2039, weight: 1},
223		{n: 2041, weight: 2},
224	} {
225		for i := 0; i < item.weight; i++ {
226			amazonMsgLens = append(amazonMsgLens, item.n)
227		}
228	}
229}
230