view ARClient/MARC.cs @ 1:27ccad26a830 default tip

Fixed resource expiration
author Ivo Smits <Ivo@UCIS.nl>
date Thu, 04 Dec 2014 21:45:51 +0100
parents 90ea68d4f92f
children
line wrap: on
line source

???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 < currentTime - 365 * 24 * 60 * 60) || ((Expiration ?? long.MaxValue) < currentTime);
			}
		}
		public Boolean CanDelete {
			get {
				long currentTime = DateTimeToUnix(DateTime.UtcNow);
				return Serial < currentTime - 5 * 365 * 24 * 60 * 60;
			}
		}
		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);
		}
	}
}