0
|
1 ???using System; |
|
2 using System.Collections; |
|
3 using System.IO; |
|
4 using System.Net; |
|
5 using System.Text; |
|
6 using UCIS.NaCl.v2; |
|
7 //using UCIS.NaCl.crypto_sign; |
|
8 using UCIS.Util; |
|
9 |
|
10 namespace ARClient { |
|
11 public static class Functions { |
|
12 public static String bin2hex(Byte[] bytes) { |
|
13 StringBuilder sb = new StringBuilder(); |
|
14 foreach (Byte b in bytes) sb.Append(b.ToString("x2")); |
|
15 return sb.ToString(); |
|
16 } |
|
17 public static Byte[] hex2bin(String hex) { |
|
18 if (hex.Length % 2 != 0) hex = "0" + hex; |
|
19 Byte[] r = new Byte[hex.Length / 2]; |
|
20 for (int i = 0; i < r.Length; i++) if (!Byte.TryParse(hex.Substring(2 * i, 2), System.Globalization.NumberStyles.HexNumber, null, out r[i])) return null; |
|
21 return r; |
|
22 } |
|
23 public static UInt32 DecodeInt32BigEndian(Byte[] data, int i) { |
|
24 return (UInt32)(data[i + 3] + 256 * (data[i + 2] + 256 * (data[i + 1] + 256 * data[i]))); |
|
25 } |
|
26 public static Byte[] EncodeInt32BigEndian(UInt32 v) { |
|
27 Byte[] r = new Byte[4]; |
|
28 UInt32 j; |
|
29 j = v % 256; r[3] = (Byte)j; v >>= 8; |
|
30 j = v % 256; r[2] = (Byte)j; v >>= 8; |
|
31 j = v % 256; r[1] = (Byte)j; v >>= 8; |
|
32 j = v % 256; r[0] = (Byte)j; |
|
33 return r; |
|
34 } |
|
35 public static UInt16 DecodeInt16BigEndian(Byte[] data, int i) { |
|
36 return (UInt16)(data[i + 1] + 256 * data[i]); |
|
37 } |
|
38 public static Byte[] EncodeInt16BigEndian(UInt16 v) { |
|
39 Byte[] r = new Byte[4]; |
|
40 int j; |
|
41 j = v % 256; r[1] = (Byte)j; v >>= 8; |
|
42 j = v % 256; r[0] = (Byte)j; |
|
43 return r; |
|
44 } |
|
45 public static UInt32 ToBigEndian(UInt32 value) { |
|
46 if (!BitConverter.IsLittleEndian) return value; |
|
47 return ((value >> 24) & 0xff) | ((value >> 8) & 0xff00) | ((value << 8) & 0xff0000) | ((value << 24) & 0xff000000); |
|
48 } |
|
49 public static UInt32 FromBigEndian(UInt32 value) { |
|
50 if (!BitConverter.IsLittleEndian) return value; |
|
51 return ((value >> 24) & 0xff) | ((value >> 8) & 0xff00) | ((value << 8) & 0xff0000) | ((value << 24) & 0xff000000); |
|
52 } |
|
53 public static int CompareByteArrays(Byte[] a, Byte[] b) { |
|
54 for (int i = 0; i < Math.Min(b.Length, a.Length); i++) if (b[i] != a[i]) return a[i] - b[i]; |
|
55 return b.Length - a.Length; |
|
56 } |
|
57 public static String GetNameForLabel(Byte[] label) { |
|
58 if (label == null || label.Length < 1) return String.Empty; |
|
59 switch (label[0]) { |
|
60 case 0: |
|
61 if (label.Length != 33) break; |
|
62 return "Key: " + bin2hex(ArrayUtil.Slice(label, 1)); |
|
63 case 1: |
|
64 if (label.Length != 6) break; |
|
65 return "IPv4: " + label[1].ToString() + "." + label[2].ToString() + "." + label[3].ToString() + "." + label[4].ToString() + "/" + label[5].ToString(); |
|
66 case 2: |
|
67 if (label.Length != 18) break; |
|
68 return "IPv6: " + (new IPAddress(ArrayUtil.Slice(label, 1, 16))).ToString() + "/" + label[17].ToString(); |
|
69 case 3: |
|
70 if (label.Length != 5) break; |
|
71 return "AS Number: " + DecodeInt32BigEndian(label, 1).ToString(); |
|
72 case 4: |
|
73 return "Domain: " + Encoding.UTF8.GetString(label, 1, label.Length - 1); |
|
74 |
|
75 } |
|
76 return bin2hex(label); |
|
77 } |
|
78 } |
|
79 public struct BinaryString : IComparable { |
|
80 public Byte[] Bytes { get; private set; } |
|
81 public BinaryString(Byte[] bytes) : this() { |
|
82 if (bytes == null) throw new ArgumentNullException("bytes"); |
|
83 this.Bytes = bytes; |
|
84 } |
|
85 public BinaryString(Byte[] bytes, int offset, int length) : this(ArrayUtil.Slice(bytes, offset, length)) { } |
|
86 public override string ToString() { |
|
87 return Encoding.UTF8.GetString(Bytes); |
|
88 } |
|
89 public static explicit operator Byte[](BinaryString bs) { |
|
90 return bs.Bytes; |
|
91 } |
|
92 public static explicit operator String(BinaryString bs) { |
|
93 return bs.ToString(); |
|
94 } |
|
95 public int CompareTo(Object otherobj) { |
|
96 if (otherobj == null) return 1; |
|
97 if (otherobj is BinaryString) return Functions.CompareByteArrays(Bytes, ((BinaryString)otherobj).Bytes); |
|
98 if (otherobj is Byte[]) return Functions.CompareByteArrays(Bytes, ((Byte[])otherobj)); |
|
99 if (otherobj is String) return ToString().CompareTo(otherobj); |
|
100 return 1; |
|
101 } |
|
102 public override bool Equals(object obj) { |
|
103 return CompareTo(obj) == 0; |
|
104 } |
|
105 public static Boolean operator ==(BinaryString a, Object b) { |
|
106 return a.Equals(b); |
|
107 } |
|
108 public static Boolean operator !=(BinaryString a, Object b) { |
|
109 return !a.Equals(b); |
|
110 } |
|
111 public override int GetHashCode() { |
|
112 int hash = Bytes.Length; |
|
113 foreach (Byte b in Bytes) hash += b; |
|
114 return hash; |
|
115 } |
|
116 } |
|
117 public class MARCUpdate { |
|
118 public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); |
|
119 public static long DateTimeToUnix(DateTime value) { return (long)(value.ToUniversalTime() - UnixEpoch).TotalSeconds; } |
|
120 public static DateTime DateTimeFromUnix(long seconds) { return UnixEpoch.AddSeconds(seconds);} |
|
121 |
|
122 public Byte Version { get; private set; } |
|
123 public MARCKey Key { get; private set; } |
|
124 public UInt32 Serial { get; private set; } |
|
125 public MARCLabel Label { get; private set; } |
|
126 public MARCKey? Transfer { get; private set; } |
|
127 public UInt32? Expiration { get; private set; } |
|
128 public Byte[] UpdateMessage { get; private set; } |
|
129 private int ValueOffset = -1; |
|
130 private Object mValue = null; |
|
131 public Object Value { |
|
132 get { |
|
133 if (mValue == null) mValue = DecodeValue(UpdateMessage, ValueOffset, UpdateMessage.Length - ValueOffset); |
|
134 return mValue; |
|
135 } |
|
136 private set { |
|
137 mValue = value; |
|
138 } |
|
139 } |
|
140 public Boolean Expired { |
|
141 get { |
|
142 long currentTime = DateTimeToUnix(DateTime.UtcNow); |
|
143 return (Serial + 365 * 24 * 60 * 60 < currentTime) || ((Expiration ?? UInt32.MaxValue) < currentTime); |
|
144 } |
|
145 } |
|
146 public Boolean CanUpdate(MARCKey key) { |
|
147 if (key == this.Key) return true; |
|
148 if (Expired) return true; |
|
149 if (Transfer != null && (Transfer.Value.Bytes.Length == 0 || Transfer.Value == key)) return true; |
|
150 return false; |
|
151 } |
|
152 public DateTime UpdateTimestamp { get { return DateTimeFromUnix(Serial); } } |
|
153 public DateTime ExpirationTimestamp { get { return Expiration == null ? DateTime.MaxValue : DateTimeFromUnix(Expiration.Value); } } |
|
154 private MARCUpdate() { |
|
155 } |
|
156 public static MARCUpdate Create(ed25519keypair key, UInt32 serial, Byte[] label, Object value, UInt32? expiration, Byte[] transfer) { |
|
157 MARCUpdate update = new MARCUpdate(); |
|
158 update.Version = 2; |
|
159 update.Key = new MARCKey(key.PublicKey); |
|
160 update.Serial = serial; |
|
161 if (label.Length > 255) throw new ArgumentException("label"); |
|
162 update.Label = new MARCLabel(label); |
|
163 update.Value = value; |
|
164 update.Expiration = expiration; |
|
165 update.Transfer = transfer == null ? null : new MARCKey?(new MARCKey(transfer)); |
|
166 |
|
167 MemoryStream buffer = new MemoryStream(1 + 32 + 64 + 4 + 1 + update.Label.Bytes.Length + 1); |
|
168 buffer.WriteByte(update.Version); |
|
169 buffer.Write(key.PublicKey, 0, 32); |
|
170 buffer.Write(new Byte[64], 0, 64); //Signature placeholder |
|
171 buffer.Write(Functions.EncodeInt32BigEndian(update.Serial), 0, 4); |
|
172 buffer.WriteByte(checked((Byte)update.Label.Bytes.Length)); |
|
173 buffer.Write(update.Label.Bytes, 0, update.Label.Bytes.Length); |
|
174 int extcnt = 0; |
|
175 if (update.Transfer != null) extcnt++; |
|
176 if (update.Expiration != null) extcnt++; |
|
177 buffer.WriteByte((Byte)extcnt); |
|
178 if (update.Transfer != null) { |
|
179 buffer.WriteByte(1); |
|
180 buffer.Write(Functions.EncodeInt16BigEndian(checked((UInt16)update.Transfer.Value.Bytes.Length)), 0, 2); |
|
181 buffer.Write(update.Transfer.Value.Bytes, 0, update.Transfer.Value.Bytes.Length); |
|
182 } |
|
183 if (update.Expiration != null) { |
|
184 buffer.WriteByte(4); |
|
185 buffer.Write(Functions.EncodeInt16BigEndian(4), 0, 2); |
|
186 buffer.Write(Functions.EncodeInt32BigEndian(update.Expiration.Value), 0, 4); |
|
187 } |
|
188 update.ValueOffset = (int)buffer.Position; |
|
189 if (update.Value != null) EncodeValue(buffer, update.Value); |
|
190 Byte[] signature = key.GetSignature(buffer.GetBuffer(), 1 + 32 + 64, (int)buffer.Length - 1 - 32 - 64); |
|
191 buffer.Seek(1 + 32, SeekOrigin.Begin); |
|
192 buffer.Write(signature, 0, 64); |
|
193 update.UpdateMessage = buffer.ToArray(); |
|
194 if (!update.VerifySignature()) throw new Exception("Key pair does not match"); |
|
195 return update; |
|
196 } |
|
197 public static MARCUpdate Decode(Byte[] data) { |
|
198 int i = 0; |
|
199 int l = data.Length; |
|
200 if (l < 1 + 32 + 64) throw new Exception("Truncated data"); |
|
201 MARCUpdate update = new MARCUpdate(); |
|
202 update.Version = data[i++]; |
|
203 if (update.Version != 2) throw new Exception(String.Format("Unexpected version number {0}", update.Version)); |
|
204 update.Key = new MARCKey(data, i, 32); i += 32; |
|
205 i += 64; |
|
206 if (data.Length < i + 4 + 1 + 1) throw new Exception("Truncated data"); |
|
207 update.Serial = (UInt32)Functions.DecodeInt32BigEndian(data, i); i += 4; |
|
208 int labellen = data[i++]; |
|
209 if (data.Length < i + labellen + 1) throw new Exception("Truncated data"); |
|
210 update.Label = new MARCLabel(data, i, labellen); i += labellen; |
|
211 for (int j = data[i++]; j > 0; j--) { |
|
212 if (data.Length < i + 3) throw new Exception("Truncated data"); |
|
213 int extid = data[i++]; |
|
214 int extlen = Functions.DecodeInt16BigEndian(data, i); i += 2; |
|
215 if (data.Length < i + extlen) throw new Exception("Truncated data"); |
|
216 switch (extid) { |
|
217 case 1: if (extlen == 0 || extlen >= 32) update.Transfer = new MARCKey(data, i, Math.Min(extlen, 32)); break; |
|
218 case 4: if (extlen >= 4) update.Expiration = Functions.DecodeInt32BigEndian(data, i); break; |
|
219 } |
|
220 i += extlen; |
|
221 } |
|
222 update.ValueOffset = i; |
|
223 update.UpdateMessage = data; |
|
224 return update; |
|
225 } |
|
226 public Boolean VerifySignature() { |
|
227 if (UpdateMessage == null) return false; |
|
228 if (UpdateMessage.Length < 1 + 32 + 64) return false; |
|
229 return ed25519.VerifySignature(new ArraySegment<Byte>(UpdateMessage, 1 + 32 + 64, UpdateMessage.Length - 1 - 32 - 64), new ArraySegment<byte>(UpdateMessage, 1 + 32, 64), ArrayUtil.Slice(UpdateMessage, 1, 32)); |
|
230 } |
|
231 |
|
232 public static Byte[] EncodeValue(Object value) { |
|
233 using (MemoryStream ms = new MemoryStream()) { |
|
234 EncodeValue(ms, value); |
|
235 return ms.ToArray(); |
|
236 } |
|
237 } |
|
238 private static void EncodeValue(MemoryStream to, Object value) { |
|
239 if (value == null) { |
|
240 to.WriteByte(0); |
|
241 } else if (value is Byte[]) { |
|
242 Byte[] s = (Byte[])value; |
|
243 to.WriteByte(1); |
|
244 to.Write(s, 0, s.Length); |
|
245 } else if (value is IDictionary) { |
|
246 to.WriteByte(3); |
|
247 IDictionary list = (IDictionary)value; |
|
248 foreach (DictionaryEntry item in list) { |
|
249 Byte[] s = Encoding.UTF8.GetBytes(item.Key.ToString()); |
|
250 to.WriteByte(checked((Byte)s.Length)); |
|
251 to.Write(s, 0, s.Length); |
|
252 long startpos = to.Position; |
|
253 to.Write(new Byte[4], 0, 4); |
|
254 EncodeValue(to, item.Value); |
|
255 uint length = (uint)(to.Position - startpos - 4); |
|
256 to.Seek(startpos, SeekOrigin.Begin); |
|
257 to.Write(Functions.EncodeInt32BigEndian(length), 0, 4); |
|
258 to.Seek(length, SeekOrigin.Current); |
|
259 } |
|
260 } else if (value is IList) { |
|
261 to.WriteByte(2); |
|
262 IList list = (IList)value; |
|
263 foreach (Object item in list) { |
|
264 long startpos = to.Position; |
|
265 to.Write(new Byte[4], 0, 4); |
|
266 EncodeValue(to, item); |
|
267 uint length = (uint)(to.Position - startpos - 4); |
|
268 to.Seek(startpos, SeekOrigin.Begin); |
|
269 to.Write(Functions.EncodeInt32BigEndian(length), 0, 4); |
|
270 to.Seek(length, SeekOrigin.Current); |
|
271 } |
|
272 } else { |
|
273 to.WriteByte(1); |
|
274 Byte[] s; |
|
275 if (value is BinaryString) { |
|
276 s = ((BinaryString)value).Bytes; |
|
277 } else if (value is Byte[]) { |
|
278 s = (Byte[])value; |
|
279 } else { |
|
280 s = Encoding.UTF8.GetBytes(value.ToString()); |
|
281 } |
|
282 to.Write(s, 0, s.Length); |
|
283 } |
|
284 } |
|
285 public static Object DecodeValue(Byte[] data) { |
|
286 return DecodeValue(data, 0, data.Length); |
|
287 } |
|
288 private static Object DecodeValue(Byte[] data, int i, int l) { |
|
289 if (l < 1) return null; |
|
290 Byte type = data[i]; i++; l--; |
|
291 switch (type) { |
|
292 case 0: return null; |
|
293 case 1: return new BinaryString(data, i, l); |
|
294 case 2: { |
|
295 ArrayList value = new ArrayList(); |
|
296 while (l > 0) { |
|
297 if (l < 4) throw new Exception("Truncated"); |
|
298 Int32 len = (Int32)Functions.DecodeInt32BigEndian(data, i); i += 4; l -= 4; |
|
299 if (l < len) throw new Exception("Truncated"); |
|
300 value.Add(DecodeValue(data, i, len)); i += len; l -= len; |
|
301 } |
|
302 return value; |
|
303 } |
|
304 case 3: { |
|
305 Hashtable value = new Hashtable(); |
|
306 while (l > 0) { |
|
307 if (l < 5) throw new Exception("Truncated"); |
|
308 Int32 len = data[i]; i++; l--; |
|
309 if (l < 4 + len) throw new Exception("Truncated"); |
|
310 String key = Encoding.UTF8.GetString(data, i, len); i += len; l -= len; |
|
311 len = (Int32)Functions.DecodeInt32BigEndian(data, i); i += 4; l -= 4; |
|
312 if (l < len) throw new Exception("Truncated"); |
|
313 value.Add(key, DecodeValue(data, i, len)); i += len; l -= len; |
|
314 } |
|
315 return value; |
|
316 } |
|
317 default: return null; |
|
318 } |
|
319 } |
|
320 |
|
321 public override String ToString() { |
|
322 return Label.ToString(); |
|
323 } |
|
324 } |
|
325 public struct MARCLabel : IComparable { |
|
326 public Byte[] Bytes { get; private set; } |
|
327 public MARCLabel(Byte[] label) |
|
328 : this() { |
|
329 if (label == null) throw new ArgumentNullException("label"); |
|
330 if (label.Length > 255) throw new ArgumentException("Parameter label is too long", "label"); |
|
331 this.Bytes = label; |
|
332 } |
|
333 public MARCLabel(Byte[] label, int offset, int len) |
|
334 : this() { |
|
335 this.Bytes = new Byte[len]; |
|
336 Buffer.BlockCopy(label, offset, this.Bytes, 0, len); |
|
337 } |
|
338 public int CompareTo(Object otherobj) { |
|
339 if (otherobj == null || !(otherobj is MARCLabel)) return 1; |
|
340 return Functions.CompareByteArrays(Bytes, ((MARCLabel)otherobj).Bytes); |
|
341 } |
|
342 public override bool Equals(object obj) { |
|
343 return CompareTo(obj) == 0; |
|
344 } |
|
345 public static Boolean operator ==(MARCLabel a, Object b) { |
|
346 return a.Equals(b); |
|
347 } |
|
348 public static Boolean operator !=(MARCLabel a, Object b) { |
|
349 return !a.Equals(b); |
|
350 } |
|
351 public override int GetHashCode() { |
|
352 int hash = Bytes.Length; |
|
353 foreach (Byte b in Bytes) hash += b; |
|
354 return hash; |
|
355 } |
|
356 |
|
357 public override String ToString() { |
|
358 return Functions.GetNameForLabel(Bytes); |
|
359 } |
|
360 } |
|
361 public struct MARCKey : IComparable { |
|
362 public Byte[] Bytes { get; private set; } |
|
363 public MARCKey(Byte[] key) |
|
364 : this() { |
|
365 if (key == null) throw new ArgumentNullException("key"); |
|
366 if (key.Length != 32 && key.Length != 0) throw new ArgumentException("Incorrect key length", "key"); |
|
367 this.Bytes = key; |
|
368 } |
|
369 public MARCKey(Byte[] label, int offset, int len) : this(ArrayUtil.Slice(label, offset, len)) { } |
|
370 public int CompareTo(Object otherobj) { |
|
371 if (otherobj == null || !(otherobj is MARCKey)) return 1; |
|
372 return Functions.CompareByteArrays(Bytes, ((MARCKey)otherobj).Bytes); |
|
373 } |
|
374 public override bool Equals(object obj) { |
|
375 return CompareTo(obj) == 0; |
|
376 } |
|
377 public static Boolean operator ==(MARCKey a, Object b) { |
|
378 return a.Equals(b); |
|
379 } |
|
380 public static Boolean operator !=(MARCKey a, Object b) { |
|
381 return !a.Equals(b); |
|
382 } |
|
383 public override int GetHashCode() { |
|
384 int hash = Bytes.Length; |
|
385 foreach (Byte b in Bytes) hash += b; |
|
386 return hash; |
|
387 } |
|
388 public override string ToString() { |
|
389 return Functions.bin2hex(Bytes); |
|
390 } |
|
391 } |
|
392 } |