diff ARClient/MARC.cs @ 0:90ea68d4f92f

First release
author Ivo Smits <Ivo@UCIS.nl>
date Sat, 08 Nov 2014 22:43:51 +0100
parents
children 27ccad26a830
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ARClient/MARC.cs	Sat Nov 08 22:43:51 2014 +0100
@@ -0,0 +1,392 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Net;
+using System.Text;
+using UCIS.NaCl.v2;
+//using UCIS.NaCl.crypto_sign;
+using UCIS.Util;
+
+namespace ARClient {
+	public static class Functions {
+		public static String bin2hex(Byte[] bytes) {
+			StringBuilder sb = new StringBuilder();
+			foreach (Byte b in bytes) sb.Append(b.ToString("x2"));
+			return sb.ToString();
+		}
+		public static Byte[] hex2bin(String hex) {
+			if (hex.Length % 2 != 0) hex = "0" + hex;
+			Byte[] r = new Byte[hex.Length / 2];
+			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;
+			return r;
+		}
+		public static UInt32 DecodeInt32BigEndian(Byte[] data, int i) {
+			return (UInt32)(data[i + 3] + 256 * (data[i + 2] + 256 * (data[i + 1] + 256 * data[i])));
+		}
+		public static Byte[] EncodeInt32BigEndian(UInt32 v) {
+			Byte[] r = new Byte[4];
+			UInt32 j;
+			j = v % 256; r[3] = (Byte)j; v >>= 8;
+			j = v % 256; r[2] = (Byte)j; v >>= 8;
+			j = v % 256; r[1] = (Byte)j; v >>= 8;
+			j = v % 256; r[0] = (Byte)j;
+			return r;
+		}
+		public static UInt16 DecodeInt16BigEndian(Byte[] data, int i) {
+			return (UInt16)(data[i + 1] + 256 * data[i]);
+		}
+		public static Byte[] EncodeInt16BigEndian(UInt16 v) {
+			Byte[] r = new Byte[4];
+			int j;
+			j = v % 256; r[1] = (Byte)j; v >>= 8;
+			j = v % 256; r[0] = (Byte)j;
+			return r;
+		}
+		public static UInt32 ToBigEndian(UInt32 value) {
+			if (!BitConverter.IsLittleEndian) return value;
+			return ((value >> 24) & 0xff) | ((value >> 8) & 0xff00) | ((value << 8) & 0xff0000) | ((value << 24) & 0xff000000);
+		}
+		public static UInt32 FromBigEndian(UInt32 value) {
+			if (!BitConverter.IsLittleEndian) return value;
+			return ((value >> 24) & 0xff) | ((value >> 8) & 0xff00) | ((value << 8) & 0xff0000) | ((value << 24) & 0xff000000);
+		}
+		public static int CompareByteArrays(Byte[] a, Byte[] b) {
+			for (int i = 0; i < Math.Min(b.Length, a.Length); i++) if (b[i] != a[i]) return a[i] - b[i];
+			return b.Length - a.Length;
+		}
+		public static String GetNameForLabel(Byte[] label) {
+			if (label == null || label.Length < 1) return String.Empty;
+			switch (label[0]) {
+				case 0:
+					if (label.Length != 33) break;
+					return "Key: " + bin2hex(ArrayUtil.Slice(label, 1));
+				case 1:
+					if (label.Length != 6) break;
+					return "IPv4: " + label[1].ToString() + "." + label[2].ToString() + "." + label[3].ToString() + "." + label[4].ToString() + "/" + label[5].ToString();
+				case 2:
+					if (label.Length != 18) break;
+					return "IPv6: " + (new IPAddress(ArrayUtil.Slice(label, 1, 16))).ToString() + "/" + label[17].ToString();
+				case 3:
+					if (label.Length != 5) break;
+					return "AS Number: " + DecodeInt32BigEndian(label, 1).ToString();
+				case 4:
+					return "Domain: " + Encoding.UTF8.GetString(label, 1, label.Length - 1);
+
+			}
+			return bin2hex(label);
+		}
+	}
+	public struct BinaryString : IComparable {
+		public Byte[] Bytes { get; private set; }
+		public BinaryString(Byte[] bytes) : this() {
+			if (bytes == null) throw new ArgumentNullException("bytes");
+			this.Bytes = bytes;
+		}
+		public BinaryString(Byte[] bytes, int offset, int length) : this(ArrayUtil.Slice(bytes, offset, length)) { }
+		public override string ToString() {
+			return Encoding.UTF8.GetString(Bytes);
+		}
+		public static explicit operator Byte[](BinaryString bs) {
+			return bs.Bytes;
+		}
+		public static explicit operator String(BinaryString bs) {
+			return bs.ToString();
+		}
+		public int CompareTo(Object otherobj) {
+			if (otherobj == null) return 1;
+			if (otherobj is BinaryString) return Functions.CompareByteArrays(Bytes, ((BinaryString)otherobj).Bytes);
+			if (otherobj is Byte[]) return Functions.CompareByteArrays(Bytes, ((Byte[])otherobj));
+			if (otherobj is String) return ToString().CompareTo(otherobj);
+			return 1;
+		}
+		public override bool Equals(object obj) {
+			return CompareTo(obj) == 0;
+		}
+		public static Boolean operator ==(BinaryString a, Object b) {
+			return a.Equals(b);
+		}
+		public static Boolean operator !=(BinaryString a, Object b) {
+			return !a.Equals(b);
+		}
+		public override int GetHashCode() {
+			int hash = Bytes.Length;
+			foreach (Byte b in Bytes) hash += b;
+			return hash;
+		}
+	}
+	public class MARCUpdate {
+		public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+		public static long DateTimeToUnix(DateTime value) { return (long)(value.ToUniversalTime() - UnixEpoch).TotalSeconds; }
+		public static DateTime DateTimeFromUnix(long seconds) { return UnixEpoch.AddSeconds(seconds);}
+
+		public Byte Version { get; private set; }
+		public MARCKey Key { get; private set; }
+		public UInt32 Serial { get; private set; }
+		public MARCLabel Label { get; private set; }
+		public MARCKey? Transfer { get; private set; }
+		public UInt32? Expiration { get; private set; }
+		public Byte[] UpdateMessage { get; private set; }
+		private int ValueOffset = -1;
+		private Object mValue = null;
+		public Object Value {
+			get {
+				if (mValue == null) mValue = DecodeValue(UpdateMessage, ValueOffset, UpdateMessage.Length - ValueOffset);
+				return mValue;
+			}
+			private set {
+				mValue = value;
+			}
+		}
+		public Boolean Expired {
+			get {
+				long currentTime = DateTimeToUnix(DateTime.UtcNow);
+				return (Serial + 365 * 24 * 60 * 60 < currentTime) || ((Expiration ?? UInt32.MaxValue) < currentTime);
+			}
+		}
+		public Boolean CanUpdate(MARCKey key) {
+			if (key == this.Key) return true;
+			if (Expired) return true;
+			if (Transfer != null && (Transfer.Value.Bytes.Length == 0 || Transfer.Value == key)) return true;
+			return false;
+		}
+		public DateTime UpdateTimestamp { get { return DateTimeFromUnix(Serial); } }
+		public DateTime ExpirationTimestamp { get { return Expiration == null ? DateTime.MaxValue : DateTimeFromUnix(Expiration.Value); } }
+		private MARCUpdate() {
+		}
+		public static MARCUpdate Create(ed25519keypair key, UInt32 serial, Byte[] label, Object value, UInt32? expiration, Byte[] transfer) {
+			MARCUpdate update = new MARCUpdate();
+			update.Version = 2;
+			update.Key = new MARCKey(key.PublicKey);
+			update.Serial = serial;
+			if (label.Length > 255) throw new ArgumentException("label");
+			update.Label = new MARCLabel(label);
+			update.Value = value;
+			update.Expiration = expiration;
+			update.Transfer = transfer == null ? null : new MARCKey?(new MARCKey(transfer));
+
+			MemoryStream buffer = new MemoryStream(1 + 32 + 64 + 4 + 1 + update.Label.Bytes.Length + 1);
+			buffer.WriteByte(update.Version);
+			buffer.Write(key.PublicKey, 0, 32);
+			buffer.Write(new Byte[64], 0, 64); //Signature placeholder
+			buffer.Write(Functions.EncodeInt32BigEndian(update.Serial), 0, 4);
+			buffer.WriteByte(checked((Byte)update.Label.Bytes.Length));
+			buffer.Write(update.Label.Bytes, 0, update.Label.Bytes.Length);
+			int extcnt = 0;
+			if (update.Transfer != null) extcnt++;
+			if (update.Expiration != null) extcnt++;
+			buffer.WriteByte((Byte)extcnt);
+			if (update.Transfer != null) {
+				buffer.WriteByte(1);
+				buffer.Write(Functions.EncodeInt16BigEndian(checked((UInt16)update.Transfer.Value.Bytes.Length)), 0, 2);
+				buffer.Write(update.Transfer.Value.Bytes, 0, update.Transfer.Value.Bytes.Length);
+			}
+			if (update.Expiration != null) {
+				buffer.WriteByte(4);
+				buffer.Write(Functions.EncodeInt16BigEndian(4), 0, 2);
+				buffer.Write(Functions.EncodeInt32BigEndian(update.Expiration.Value), 0, 4);
+			}
+			update.ValueOffset = (int)buffer.Position;
+			if (update.Value != null) EncodeValue(buffer, update.Value);
+			Byte[] signature = key.GetSignature(buffer.GetBuffer(), 1 + 32 + 64, (int)buffer.Length - 1 - 32 - 64);
+			buffer.Seek(1 + 32, SeekOrigin.Begin);
+			buffer.Write(signature, 0, 64);
+			update.UpdateMessage = buffer.ToArray();
+			if (!update.VerifySignature()) throw new Exception("Key pair does not match");
+			return update;
+		}
+		public static MARCUpdate Decode(Byte[] data) {
+			int i = 0;
+			int l = data.Length;
+			if (l < 1 + 32 + 64) throw new Exception("Truncated data");
+			MARCUpdate update = new MARCUpdate();
+			update.Version = data[i++];
+			if (update.Version != 2) throw new Exception(String.Format("Unexpected version number {0}", update.Version));
+			update.Key = new MARCKey(data, i, 32); i += 32;
+			i += 64;
+			if (data.Length < i + 4 + 1 + 1) throw new Exception("Truncated data");
+			update.Serial = (UInt32)Functions.DecodeInt32BigEndian(data, i); i += 4;
+			int labellen = data[i++];
+			if (data.Length < i + labellen + 1) throw new Exception("Truncated data");
+			update.Label = new MARCLabel(data, i, labellen); i += labellen;
+			for (int j = data[i++]; j > 0; j--) {
+				if (data.Length < i + 3) throw new Exception("Truncated data");
+				int extid = data[i++];
+				int extlen = Functions.DecodeInt16BigEndian(data, i); i += 2;
+				if (data.Length < i + extlen) throw new Exception("Truncated data");
+				switch (extid) {
+					case 1: if (extlen == 0 || extlen >= 32) update.Transfer = new MARCKey(data, i, Math.Min(extlen, 32)); break;
+					case 4: if (extlen >= 4) update.Expiration = Functions.DecodeInt32BigEndian(data, i); break;
+				}
+				i += extlen;
+			}
+			update.ValueOffset = i;
+			update.UpdateMessage = data;
+			return update;
+		}
+		public Boolean VerifySignature() {
+			if (UpdateMessage == null) return false;
+			if (UpdateMessage.Length < 1 + 32 + 64) return false;
+			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));
+		}
+
+		public static Byte[] EncodeValue(Object value) {
+			using (MemoryStream ms = new MemoryStream()) {
+				EncodeValue(ms, value);
+				return ms.ToArray();
+			}
+		}
+		private static void EncodeValue(MemoryStream to, Object value) {
+			if (value == null) {
+				to.WriteByte(0);
+			} else if (value is Byte[]) {
+				Byte[] s = (Byte[])value;
+				to.WriteByte(1);
+				to.Write(s, 0, s.Length);
+			} else if (value is IDictionary) {
+				to.WriteByte(3);
+				IDictionary list = (IDictionary)value;
+				foreach (DictionaryEntry item in list) {
+					Byte[] s = Encoding.UTF8.GetBytes(item.Key.ToString());
+					to.WriteByte(checked((Byte)s.Length));
+					to.Write(s, 0, s.Length);
+					long startpos = to.Position;
+					to.Write(new Byte[4], 0, 4);
+					EncodeValue(to, item.Value);
+					uint length = (uint)(to.Position - startpos - 4);
+					to.Seek(startpos, SeekOrigin.Begin);
+					to.Write(Functions.EncodeInt32BigEndian(length), 0, 4);
+					to.Seek(length, SeekOrigin.Current);
+				}
+			} else if (value is IList) {
+				to.WriteByte(2);
+				IList list = (IList)value;
+				foreach (Object item in list) {
+					long startpos = to.Position;
+					to.Write(new Byte[4], 0, 4);
+					EncodeValue(to, item);
+					uint length = (uint)(to.Position - startpos - 4);
+					to.Seek(startpos, SeekOrigin.Begin);
+					to.Write(Functions.EncodeInt32BigEndian(length), 0, 4);
+					to.Seek(length, SeekOrigin.Current);
+				}
+			} else {
+				to.WriteByte(1);
+				Byte[] s;
+				if (value is BinaryString) {
+					s = ((BinaryString)value).Bytes;
+				} else if (value is Byte[]) {
+					s = (Byte[])value;
+				} else {
+					s = Encoding.UTF8.GetBytes(value.ToString());
+				}
+				to.Write(s, 0, s.Length);
+			}
+		}
+		public static Object DecodeValue(Byte[] data) {
+			return DecodeValue(data, 0, data.Length);
+		}
+		private static Object DecodeValue(Byte[] data, int i, int l) {
+			if (l < 1) return null;
+			Byte type = data[i]; i++; l--;
+			switch (type) {
+				case 0: return null;
+				case 1: return new BinaryString(data, i, l);
+				case 2: {
+						ArrayList value = new ArrayList();
+						while (l > 0) {
+							if (l < 4) throw new Exception("Truncated");
+							Int32 len = (Int32)Functions.DecodeInt32BigEndian(data, i); i += 4; l -= 4;
+							if (l < len) throw new Exception("Truncated");
+							value.Add(DecodeValue(data, i, len)); i += len; l -= len;
+						}
+						return value;
+					}
+				case 3: {
+						Hashtable value = new Hashtable();
+						while (l > 0) {
+							if (l < 5) throw new Exception("Truncated");
+							Int32 len = data[i]; i++; l--;
+							if (l < 4 + len) throw new Exception("Truncated");
+							String key = Encoding.UTF8.GetString(data, i, len); i += len; l -= len;
+							len = (Int32)Functions.DecodeInt32BigEndian(data, i); i += 4; l -= 4;
+							if (l < len) throw new Exception("Truncated");
+							value.Add(key, DecodeValue(data, i, len)); i += len; l -= len;
+						}
+						return value;
+					}
+				default: return null;
+			}
+		}
+
+		public override String ToString() {
+			return Label.ToString();
+		}
+	}
+	public struct MARCLabel : IComparable {
+		public Byte[] Bytes { get; private set; }
+		public MARCLabel(Byte[] label)
+			: this() {
+			if (label == null) throw new ArgumentNullException("label");
+			if (label.Length > 255) throw new ArgumentException("Parameter label is too long", "label");
+			this.Bytes = label;
+		}
+		public MARCLabel(Byte[] label, int offset, int len)
+			: this() {
+			this.Bytes = new Byte[len];
+			Buffer.BlockCopy(label, offset, this.Bytes, 0, len);
+		}
+		public int CompareTo(Object otherobj) {
+			if (otherobj == null || !(otherobj is MARCLabel)) return 1;
+			return Functions.CompareByteArrays(Bytes, ((MARCLabel)otherobj).Bytes);
+		}
+		public override bool Equals(object obj) {
+			return CompareTo(obj) == 0;
+		}
+		public static Boolean operator ==(MARCLabel a, Object b) {
+			return a.Equals(b);
+		}
+		public static Boolean operator !=(MARCLabel a, Object b) {
+			return !a.Equals(b);
+		}
+		public override int GetHashCode() {
+			int hash = Bytes.Length;
+			foreach (Byte b in Bytes) hash += b;
+			return hash;
+		}
+
+		public override String ToString() {
+			return Functions.GetNameForLabel(Bytes);
+		}
+	}
+	public struct MARCKey : IComparable {
+		public Byte[] Bytes { get; private set; }
+		public MARCKey(Byte[] key)
+			: this() {
+			if (key == null) throw new ArgumentNullException("key");
+			if (key.Length != 32 && key.Length != 0) throw new ArgumentException("Incorrect key length", "key");
+			this.Bytes = key;
+		}
+		public MARCKey(Byte[] label, int offset, int len) : this(ArrayUtil.Slice(label, offset, len)) { }
+		public int CompareTo(Object otherobj) {
+			if (otherobj == null || !(otherobj is MARCKey)) return 1;
+			return Functions.CompareByteArrays(Bytes, ((MARCKey)otherobj).Bytes);
+		}
+		public override bool Equals(object obj) {
+			return CompareTo(obj) == 0;
+		}
+		public static Boolean operator ==(MARCKey a, Object b) {
+			return a.Equals(b);
+		}
+		public static Boolean operator !=(MARCKey a, Object b) {
+			return !a.Equals(b);
+		}
+		public override int GetHashCode() {
+			int hash = Bytes.Length;
+			foreach (Byte b in Bytes) hash += b;
+			return hash;
+		}
+		public override string ToString() {
+			return Functions.bin2hex(Bytes);
+		}
+	}
+}