-
Notifications
You must be signed in to change notification settings - Fork 0
/
constants.clj
313 lines (278 loc) · 13 KB
/
constants.clj
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
(ns frereth.cp.shared.constants
"Magical names, numbers, and data structures"
(:require [clojure.spec.alpha :as s]
[frereth.cp.shared.specs :as specs])
(:import io.netty.buffer.ByteBuf))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Magic Constants
(set! *warn-on-reflection* true)
;; Q: How many of the rest of this could benefit enough by
;; getting a ^:const metadata hint to justify adding it?
;; TODO: benchmarking
(def box-zero-bytes specs/box-zero-bytes)
(def ^Integer decrypt-box-zero-bytes 32)
(def ^Integer key-length specs/key-length)
(def max-random-nonce (long (Math/pow 2 48)))
(def client-key-length specs/client-key-length)
;; Might as well move these into specs for consistency
;; with server-nonce-suffix-length
;; FIXME: Make it so (soon)
(def extension-length specs/extension-length)
(def header-length specs/header-length)
(def message-len 1104)
;; FIXME: Move this into specs, where it's defined
;; based on prefix/suffix lengths
;; Then again, all the constants in there should
;; really move back into here. And then that
;; should require this, instead of vice-versa.
(def nonce-length 24)
(def server-key-length key-length)
(def shared-key-length key-length)
(def ^:const client-header-prefix-string "QvnQ5Xl")
(def server-header-prefix-string "RL3aNMX")
;; Using an ordinary ^bytes type-hint here caused an
;; IllegalArgumentException at compile-time elsewhere
;; with the message "Unable to resolve classname: clojure.core$bytes@4efdc044"
(def ^{:tag 'bytes} client-header-prefix (.getBytes client-header-prefix-string))
(def send-child-message-timeout
"in milliseconds"
;; Q: What's realistic?
;; (TODO: this should probably be dynamically customizable)
2500)
(def ^:const max-8-int 128)
(def ^:const max-8-uint 255)
(def ^:const max-16-int 32768)
(def ^:const max-16-uint 65535)
;; (dec (pow 2 32))
(def ^:const max-32-uint 4294967295)
;; (comment (dec (long (Math/pow 2 31))))
(def ^:const max-32-int 2147483647)
(def ^:const max-64-uint 18446744073709551999N)
(def ^:const two-pow-48 (bit-shift-left 1 48))
(def ^:const m-1
"1 Meg"
1048576)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Specs
;;; Q: Why are these here instead of top-level shared?
;;; A: Because they're used in here, and I want to avoid
;;; circular dependencies.
;;; Q: Would it make sense to have a dedicated shared.specs ns
;;; that everything could use?
;;; The way these things are split up now is a bit of a mess.
(s/def ::client-nonce-suffix (s/and bytes?
#(= (count %) specs/client-nonce-suffix-length)))
;; The prefixes are all a series of constant bytes.
;; Callers shouldn't need to know/worry about them.
;; FIXME: Add a ::constant-bytes type that just
;; hard-codes the magic.
(s/def ::prefix ::specs/prefix)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Hello packets
(def hello-packet-length 224)
(def ^:const hello-header-string (str client-header-prefix-string "H"))
(comment (vec hello-header-string))
(def hello-header (.getBytes hello-header-string))
(s/def ::hello-prefix (s/and ::specs/prefix
;; This is annoying.
;; ::specs/prefix already verified that we're
;; dealing with bytes.
;; Oh well.
#(= hello-header-string (String. (bytes %)))))
(def hello-nonce-prefix (.getBytes "CurveCP-client-H"))
(def ^Integer hello-crypto-box-length 80)
(def ^Integer ^:const zero-box-length (- hello-crypto-box-length box-zero-bytes))
;; FIXME: It would be really nice to be able to generate this from the spec
;; or vice-versa.
;; Specs, coercion, and serialization are currently a hot topic on the mailing list.
(def hello-packet-dscr (array-map ::hello-prefix {::type ::const ::contents hello-header}
::srvr-xtn {::type ::bytes ::length extension-length}
::clnt-xtn {::type ::bytes ::length extension-length}
::clnt-short-pk {::type ::bytes ::length client-key-length}
::zeros {::type ::zeroes ::length zero-box-length}
;; This gets weird/confusing.
;; It's a 64-bit number, so 8 octets
;; But, really, that's just integer?
;; It would probably be more tempting to
;; just spec this like that if the jvm had
;; unsigned ints
::client-nonce-suffix {::type ::bytes
::length specs/client-nonce-suffix-length}
::crypto-box {::type ::bytes
::length hello-crypto-box-length}))
;; Here's a bit of nastiness:
;; I can define these in shared.specs. But I have to redefine
;; them here because of the namespacing.
;; Unless I want to update my dscr templates, which really is the correct
;; answer.
;; TODO: Make that so
;; (just do it one step at a time)
(s/def ::srvr-xtn ::specs/srvr-xtn)
#_(s/def ::clnt-xtn ::specs/clnt-xtn)
#_(s/def ::clnt-xtn ::specs/extension)
(s/def ::clnt-xtn (s/and bytes?
#(= (count %) extension-length)))
(s/def ::clnt-short-pk ::specs/public-short)
(s/def ::zeros (s/and bytes
#(= (count %) zero-box-length)
#(every? zero? %)))
(s/def ::crypto-box (s/and bytes
#(= (count %) hello-crypto-box-length)))
(s/def ::hello-spec (s/keys :req [::hello-prefix
::srvr-xtn
::clnt-xtn
::clnt-short-pk
::zeros
::client-nonce-suffix
::crypto-box]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Cookie packets
(def ^Integer ^:const cookie-frame-length 144)
(def ^:const cookie-header-string (str server-header-prefix-string "K"))
(def cookie-header (.getBytes cookie-header-string))
(def cookie-nonce-prefix (.getBytes "CurveCPK"))
(def cookie-nonce-minute-prefix (.getBytes "minute-k"))
(def ^Integer server-cookie-length 96)
(def ^Integer cookie-packet-length 200)
(def unboxed-crypto-cookie-length 128)
(s/def ::srvr-nonce-suffix ::specs/server-nonce-suffix)
(s/def ::cookie-packet (partial specs/counted-bytes cookie-packet-length))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Vouch/Initiate Packets
;; Header, cookie, server name, extensions, keys, nonces
(def vouch-overhead 544)
(def max-vouch-message-length 640)
(def max-initiate-packet-size (+ vouch-overhead max-vouch-message-length))
;; Q: Can this ever be < 16?
;; A: Well, in the reference implementation, trying to write
;; too few (< 16) or too many (> 640 in the Initiatet/Vouch phase)
;; bytes causes the process to exit.
;; And, because of the way those bytes are buffered, it really
;; has to always write a multiple of 16 (Q: What about once
;; EOF hits?)
;; Actually, this part *is* a message.
;; And, according to the spec, that has to be at least 16
;; bytes.
(def min-vouch-message-length 16)
(s/def ::hidden-client-short-pk ::specs/public-short)
(s/def ::inner-i-nonce ::specs/inner-i-nonce)
(s/def ::long-term-public-key ::specs/public-long)
;; FIXME: Actually, this should be a full-blown
;; :frereth.cp.message.specs/packet, with a better
;; name.
;; FIXME: Switch to that name.
(s/def ::message (s/and bytes?
;; This predicate is nonsense.
;; Q: What's wrong with it?
;; A: comparing count against nothing, in last clause
;; FIXME: Switch to something sensible (soon)
#(<= max-vouch-message-length (count %))
#(<= (count %))))
(s/def ::outer-i-nonce ::client-nonce-suffix)
(s/def ::srvr-name ::specs/srvr-name)
(def vouch-nonce-prefix (.getBytes "CurveCPV"))
(def initiate-nonce-prefix (.getBytes "CurveCP-client-I"))
(def initiate-header (.getBytes (str client-header-prefix-string "I")))
(def vouch-length specs/vouch-length)
;; The way this is wrapped up seems odd.
;; We have a box containing the short-term key encrypted
;; by the long-term public key.
;; The long-term key is part of this outer box, which
;; is encrypted with that short-term key.
;; Which, in turn, is included in the message's plain
;; text.
;; This does let us verify that the client has access
;; to the long-term secret key it's claiming without
;; needing to maintain any state on our part up to this
;; point.
;; (spoiler: it's
;; (+ 16 32 16 48 256)
;; => 368
(def minimum-vouch-length (+ box-zero-bytes ; 16
client-key-length ; 32
specs/server-nonce-suffix-length ; 16
vouch-length ; 48
;; 256
specs/server-name-length))
(s/fdef legal-vouch-message-length?
:args (s/cat :bytes bytes?)
:ret boolean?)
(defn legal-vouch-message-length?
"Is a byte array a legal vouch sub-message?"
;; The maximum length for the message associated with an Initiate packet is 640 bytes.
;; However, it must be evenly divisible by 16.
;; This feels a little...odd.
;; It leaves the message child tightly coupled with this implementation
;; detail.
;; And also tied in with the detail that the rules change after
;; the server sends back a response.
;; I'm not sure there's any way to avoid that.
[^bytes bs]
(let [n (count bs)]
(and (< n max-vouch-message-length)
(zero? (rem n 16)))))
;;; FIXME: Move the rest of these into templates
;; This has been obsoleted by
;; initiate-client-vouch-wrapper in templates.
;; FIXME: Make this version go away.
(def vouch-wrapper
"Template for composing the inner part of an Initiate Packet's Vouch that holds everything interesting"
{::client-long-term-key {::type ::bytes
::length client-key-length}
::inner-i-nonce {::type ::bytes ::length specs/server-nonce-suffix-length}
::inner-vouch {::type ::bytes ::length vouch-length}
::srvr-name {::type ::bytes ::length specs/server-name-length}
;; Q: Do I want to allow compose to accept parameters for things like
;; the length?
::child-message {::type ::bytes ::length '?child-message-length}})
(def initiate-packet-dscr
(array-map ::prefix {::type ::bytes
::length header-length}
;; Note that this is really receiver-extension
::srvr-xtn {::type ::bytes
::length extension-length}
;; And this is sender-extension
;; TODO: Get the names refactored
;; So I can use this for everything
::clnt-xtn {::type ::bytes
::length extension-length}
::clnt-short-pk {::type ::bytes
::length client-key-length}
::cookie {::type ::bytes
::length server-cookie-length}
::outer-i-nonce {::type ::bytes
::length specs/client-nonce-suffix-length}
;; It seems like it would be nice to enable nested
;; definitions.
;; This isn't "just" vouch-wrapper.
;; It's the cryptographic box that contains vouch-wrapper
;; and its related message
::vouch-wrapper {::type ::bytes
::length minimum-vouch-length}))
(s/def ::cookie (s/and bytes?
#(= (count %) server-cookie-length)))
;; Note that this is really the wrapper for the vouch received from the client
(s/def ::vouch-wrapper (s/and bytes?
#(<= minimum-vouch-length
(count %)
;; This max size looks wrong, but it adds up.
(+ minimum-vouch-length max-vouch-message-length))
;; Evenly divisible by 16
#(zero? (mod (count %) 16))))
(s/def ::initiate-packet-spec (s/keys :req [::prefix
::srvr-xtn
::clnt-xtn
::clnt-short-pk
::cookie
::outer-i-nonce
::vouch-wrapper]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Utility helpers
(defn zero-bytes
[n]
(byte-array n (repeat 0)))
(def ^{:tag 'bytes} all-zeros
"To avoid creating this over and over.
Q: Refactor this to a function?
(note that that makes life quite a bit more difficult for zero-out!)"
(zero-bytes 128))