Mercurial > hg > marc.net
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); + } + } +}