--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * An AsnElt instance represents a decoded ASN.1 DER object. It is
+ * immutable.
+ */
+
+public class AsnElt {
+
+ /*
+ * Universal tag values.
+ */
+ public const int BOOLEAN = 1;
+ public const int INTEGER = 2;
+ public const int BIT_STRING = 3;
+ public const int OCTET_STRING = 4;
+ public const int NULL = 5;
+ public const int OBJECT_IDENTIFIER = 6;
+ public const int Object_Descriptor = 7;
+ public const int EXTERNAL = 8;
+ public const int REAL = 9;
+ public const int ENUMERATED = 10;
+ public const int EMBEDDED_PDV = 11;
+ public const int UTF8String = 12;
+ public const int RELATIVE_OID = 13;
+ public const int SEQUENCE = 16;
+ public const int SET = 17;
+ public const int NumericString = 18;
+ public const int PrintableString = 19;
+ public const int T61String = 20;
+ public const int TeletexString = 20;
+ public const int VideotexString = 21;
+ public const int IA5String = 22;
+ public const int UTCTime = 23;
+ public const int GeneralizedTime = 24;
+ public const int GraphicString = 25;
+ public const int VisibleString = 26;
+ public const int GeneralString = 27;
+ public const int UniversalString = 28;
+ public const int CHARACTER_STRING = 29;
+ public const int BMPString = 30;
+
+ /*
+ * Tag classes.
+ */
+ public const int UNIVERSAL = 0;
+ public const int APPLICATION = 1;
+ public const int CONTEXT = 2;
+ public const int PRIVATE = 3;
+
+ /*
+ * Internal rules
+ * ==============
+ *
+ * Instances are immutable. They reference an internal buffer
+ * that they never modify. The buffer is never shown to the
+ * outside; when decoding and creating, copies are performed
+ * where necessary.
+ *
+ * If the instance was created by decoding, then:
+ * objBuf points to the array containing the complete object
+ * objOff start offset for the object header
+ * objLen complete object length
+ * valOff offset for the first value byte
+ * valLen value length (excluding the null-tag, if applicable)
+ * hasEncodedHeader is true
+ *
+ * If the instance was created from an explicit value or from
+ * sub-elements, then:
+ * objBuf contains the value, or is null
+ * objOff is 0
+ * objLen is -1, or contains the computed object length
+ * valOff is 0
+ * valLen is -1, or contains the computed value length
+ * hasEncodedHeader is false
+ *
+ * If objBuf is null, then the object is necessarily constructed
+ * (Sub is not null). If objBuf is not null, then the encoded
+ * value is known (the object may be constructed or primitive),
+ * and valOff/valLen identify the actual value within objBuf.
+ *
+ * Tag class and value, and sub-elements, are referenced from
+ * specific properties.
+ */
+
+ byte[] objBuf;
+ int objOff;
+ int objLen;
+ int valOff;
+ int valLen;
+ bool hasEncodedHeader;
+
+ AsnElt()
+ {
+ }
+
+ /*
+ * The tag class for this element.
+ */
+ int tagClass_;
+ public int TagClass {
+ get {
+ return tagClass_;
+ }
+ private set {
+ tagClass_ = value;
+ }
+ }
+
+ /*
+ * The tag value for this element.
+ */
+ int tagValue_;
+ public int TagValue {
+ get {
+ return tagValue_;
+ }
+ private set {
+ tagValue_ = value;
+ }
+ }
+
+ /*
+ * The sub-elements. This is null if this element is primitive.
+ * DO NOT MODIFY this array.
+ */
+ AsnElt[] sub_;
+ public AsnElt[] Sub {
+ get {
+ return sub_;
+ }
+ private set {
+ sub_ = value;
+ }
+ }
+
+ /*
+ * The "constructed" flag: true for an elements with sub-elements,
+ * false for a primitive element.
+ */
+ public bool Constructed {
+ get {
+ return Sub != null;
+ }
+ }
+
+ /*
+ * The value length. When the object is BER-encoded with an
+ * indefinite length, the value length includes all the sub-objects
+ * but NOT the formal null-tag marker.
+ */
+ public int ValueLength {
+ get {
+ if (valLen < 0) {
+ if (Constructed) {
+ int vlen = 0;
+ foreach (AsnElt a in Sub) {
+ vlen += a.EncodedLength;
+ }
+ valLen = vlen;
+ } else {
+ valLen = objBuf.Length;
+ }
+ }
+ return valLen;
+ }
+ }
+
+ /*
+ * The encoded object length (complete with header).
+ */
+ public int EncodedLength {
+ get {
+ if (objLen < 0) {
+ int vlen = ValueLength;
+ objLen = TagLength(TagValue)
+ + LengthLength(vlen) + vlen;
+ }
+ return objLen;
+ }
+ }
+
+ /*
+ * Check that this element is constructed. An exception is thrown
+ * if this is not the case.
+ */
+ public void CheckConstructed()
+ {
+ if (!Constructed) {
+ throw new AsnException("not constructed");
+ }
+ }
+
+ /*
+ * Check that this element is primitive. An exception is thrown
+ * if this is not the case.
+ */
+ public void CheckPrimitive()
+ {
+ if (Constructed) {
+ throw new AsnException("not primitive");
+ }
+ }
+
+ /*
+ * Get a sub-element. This method throws appropriate exceptions
+ * if this element is not constructed, or the requested index
+ * is out of range.
+ */
+ public AsnElt GetSub(int n)
+ {
+ CheckConstructed();
+ if (n < 0 || n >= Sub.Length) {
+ throw new AsnException("no such sub-object: n=" + n);
+ }
+ return Sub[n];
+ }
+
+ /*
+ * Check that the tag is UNIVERSAL with the provided value.
+ */
+ public void CheckTag(int tv)
+ {
+ CheckTag(UNIVERSAL, tv);
+ }
+
+ /*
+ * Check that the tag has the specified class and value.
+ */
+ public void CheckTag(int tc, int tv)
+ {
+ if (TagClass != tc || TagValue != tv) {
+ throw new AsnException("unexpected tag: " + TagString);
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains exactly
+ * 'n' sub-elements.
+ */
+ public void CheckNumSub(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length != n) {
+ throw new AsnException("wrong number of sub-elements: "
+ + Sub.Length + " (expected: " + n + ")");
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains at least
+ * 'n' sub-elements.
+ */
+ public void CheckNumSubMin(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length < n) {
+ throw new AsnException("not enough sub-elements: "
+ + Sub.Length + " (minimum: " + n + ")");
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains no more
+ * than 'n' sub-elements.
+ */
+ public void CheckNumSubMax(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length > n) {
+ throw new AsnException("too many sub-elements: "
+ + Sub.Length + " (maximum: " + n + ")");
+ }
+ }
+
+ /*
+ * Get a string representation of the tag class and value.
+ */
+ public string TagString {
+ get {
+ return TagToString(TagClass, TagValue);
+ }
+ }
+
+ static string TagToString(int tc, int tv)
+ {
+ switch (tc) {
+ case UNIVERSAL:
+ break;
+ case APPLICATION:
+ return "APPLICATION:" + tv;
+ case CONTEXT:
+ return "CONTEXT:" + tv;
+ case PRIVATE:
+ return "PRIVATE:" + tv;
+ default:
+ return String.Format("INVALID:{0}/{1}", tc, tv);
+ }
+
+ switch (tv) {
+ case BOOLEAN: return "BOOLEAN";
+ case INTEGER: return "INTEGER";
+ case BIT_STRING: return "BIT_STRING";
+ case OCTET_STRING: return "OCTET_STRING";
+ case NULL: return "NULL";
+ case OBJECT_IDENTIFIER: return "OBJECT_IDENTIFIER";
+ case Object_Descriptor: return "Object_Descriptor";
+ case EXTERNAL: return "EXTERNAL";
+ case REAL: return "REAL";
+ case ENUMERATED: return "ENUMERATED";
+ case EMBEDDED_PDV: return "EMBEDDED_PDV";
+ case UTF8String: return "UTF8String";
+ case RELATIVE_OID: return "RELATIVE_OID";
+ case SEQUENCE: return "SEQUENCE";
+ case SET: return "SET";
+ case NumericString: return "NumericString";
+ case PrintableString: return "PrintableString";
+ case TeletexString: return "TeletexString";
+ case VideotexString: return "VideotexString";
+ case IA5String: return "IA5String";
+ case UTCTime: return "UTCTime";
+ case GeneralizedTime: return "GeneralizedTime";
+ case GraphicString: return "GraphicString";
+ case VisibleString: return "VisibleString";
+ case GeneralString: return "GeneralString";
+ case UniversalString: return "UniversalString";
+ case CHARACTER_STRING: return "CHARACTER_STRING";
+ case BMPString: return "BMPString";
+ default:
+ return String.Format("UNIVERSAL:" + tv);
+ }
+ }
+
+ /*
+ * Get the encoded length for a tag.
+ */
+ static int TagLength(int tv)
+ {
+ if (tv <= 0x1F) {
+ return 1;
+ }
+ int z = 1;
+ while (tv > 0) {
+ z ++;
+ tv >>= 7;
+ }
+ return z;
+ }
+
+ /*
+ * Get the encoded length for a length.
+ */
+ static int LengthLength(int len)
+ {
+ if (len < 0x80) {
+ return 1;
+ }
+ int z = 1;
+ while (len > 0) {
+ z ++;
+ len >>= 8;
+ }
+ return z;
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. Trailing garbage is not tolerated.
+ */
+ public static AsnElt Decode(byte[] buf)
+ {
+ return Decode(buf, 0, buf.Length, true);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. Trailing garbage is not tolerated.
+ */
+ public static AsnElt Decode(byte[] buf, int off, int len)
+ {
+ return Decode(buf, off, len, true);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. If 'exactLength' is true, then trailing garbage is
+ * not tolerated (it triggers an exception).
+ */
+ public static AsnElt Decode(byte[] buf, bool exactLength)
+ {
+ return Decode(buf, 0, buf.Length, exactLength);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. If 'exactLength' is true, then trailing garbage is
+ * not tolerated (it triggers an exception).
+ */
+ public static AsnElt Decode(byte[] buf, int off, int len,
+ bool exactLength)
+ {
+ int tc, tv, valOff, valLen, objLen;
+ bool cons;
+ objLen = Decode(buf, off, len,
+ out tc, out tv, out cons,
+ out valOff, out valLen);
+ if (exactLength && objLen != len) {
+ throw new AsnException("trailing garbage");
+ }
+ byte[] nbuf = new byte[objLen];
+ Array.Copy(buf, off, nbuf, 0, objLen);
+ return DecodeNoCopy(nbuf, 0, objLen);
+ }
+
+ /*
+ * Internal recursive decoder. The provided array is NOT copied.
+ * Trailing garbage is ignored (caller should use the 'objLen'
+ * field to learn the total object length).
+ */
+ static AsnElt DecodeNoCopy(byte[] buf, int off, int len)
+ {
+ int tc, tv, valOff, valLen, objLen;
+ bool cons;
+ objLen = Decode(buf, off, len,
+ out tc, out tv, out cons,
+ out valOff, out valLen);
+ AsnElt a = new AsnElt();
+ a.TagClass = tc;
+ a.TagValue = tv;
+ a.objBuf = buf;
+ a.objOff = off;
+ a.objLen = objLen;
+ a.valOff = valOff;
+ a.valLen = valLen;
+ a.hasEncodedHeader = true;
+ if (cons) {
+ List<AsnElt> subs = new List<AsnElt>();
+ off = valOff;
+ int lim = valOff + valLen;
+ while (off < lim) {
+ AsnElt b = DecodeNoCopy(buf, off, lim - off);
+ off += b.objLen;
+ subs.Add(b);
+ }
+ a.Sub = subs.ToArray();
+ } else {
+ a.Sub = null;
+ }
+ return a;
+ }
+
+ /*
+ * Decode the tag and length, and get the value offset and length.
+ * Returned value if the total object length.
+ * Note: when an object has indefinite length, the terminated
+ * "null tag" will NOT be considered part of the "value length".
+ */
+ static int Decode(byte[] buf, int off, int maxLen,
+ out int tc, out int tv, out bool cons,
+ out int valOff, out int valLen)
+ {
+ int lim = off + maxLen;
+ int orig = off;
+
+ /*
+ * Decode tag.
+ */
+ CheckOff(off, lim);
+ tv = buf[off ++];
+ cons = (tv & 0x20) != 0;
+ tc = tv >> 6;
+ tv &= 0x1F;
+ if (tv == 0x1F) {
+ tv = 0;
+ for (;;) {
+ CheckOff(off, lim);
+ int c = buf[off ++];
+ if (tv > 0xFFFFFF) {
+ throw new AsnException(
+ "tag value overflow");
+ }
+ tv = (tv << 7) | (c & 0x7F);
+ if ((c & 0x80) == 0) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Decode length.
+ */
+ CheckOff(off, lim);
+ int vlen = buf[off ++];
+ if (vlen == 0x80) {
+ /*
+ * Indefinite length. This is not strict DER, but
+ * we allow it nonetheless; we must check that
+ * the value was tagged as constructed, though.
+ */
+ vlen = -1;
+ if (!cons) {
+ throw new AsnException("indefinite length"
+ + " but not constructed");
+ }
+ } else if (vlen > 0x80) {
+ int lenlen = vlen - 0x80;
+ CheckOff(off + lenlen - 1, lim);
+ vlen = 0;
+ while (lenlen -- > 0) {
+ if (vlen > 0x7FFFFF) {
+ throw new AsnException(
+ "length overflow");
+ }
+ vlen = (vlen << 8) + buf[off ++];
+ }
+ }
+
+ /*
+ * Length was decoded, so the value starts here.
+ */
+ valOff = off;
+
+ /*
+ * If length is indefinite then we must explore sub-objects
+ * to get the value length.
+ */
+ if (vlen < 0) {
+ for (;;) {
+ int tc2, tv2, valOff2, valLen2;
+ bool cons2;
+ int slen;
+
+ slen = Decode(buf, off, lim - off,
+ out tc2, out tv2, out cons2,
+ out valOff2, out valLen2);
+ if (tc2 == 0 && tv2 == 0) {
+ if (cons2 || valLen2 != 0) {
+ throw new AsnException(
+ "invalid null tag");
+ }
+ valLen = off - valOff;
+ off += slen;
+ break;
+ } else {
+ off += slen;
+ }
+ }
+ } else {
+ if (vlen > (lim - off)) {
+ throw new AsnException("value overflow");
+ }
+ off += vlen;
+ valLen = off - valOff;
+ }
+
+ return off - orig;
+ }
+
+ static void CheckOff(int off, int lim)
+ {
+ if (off >= lim) {
+ throw new AsnException("offset overflow");
+ }
+ }
+
+ /*
+ * Get a specific byte from the value. This provided offset is
+ * relative to the value start (first value byte has offset 0).
+ */
+ public int ValueByte(int off)
+ {
+ if (off < 0) {
+ throw new AsnException("invalid value offset: " + off);
+ }
+ if (objBuf == null) {
+ int k = 0;
+ foreach (AsnElt a in Sub) {
+ int slen = a.EncodedLength;
+ if ((k + slen) > off) {
+ return a.ValueByte(off - k);
+ }
+ }
+ } else {
+ if (off < valLen) {
+ return objBuf[valOff + off];
+ }
+ }
+ throw new AsnException(String.Format(
+ "invalid value offset {0} (length = {1})",
+ off, ValueLength));
+ }
+
+ /*
+ * Encode this object into a newly allocated array.
+ */
+ public byte[] Encode()
+ {
+ byte[] r = new byte[EncodedLength];
+ Encode(r, 0);
+ return r;
+ }
+
+ /*
+ * Encode this object into the provided array. Encoded object
+ * length is returned.
+ */
+ public int Encode(byte[] dst, int off)
+ {
+ return Encode(0, Int32.MaxValue, dst, off);
+ }
+
+ /*
+ * Encode this object into the provided array. Only bytes
+ * at offset between 'start' (inclusive) and 'end' (exclusive)
+ * are actually written. The number of written bytes is returned.
+ * Offsets are relative to the object start (first tag byte).
+ */
+ int Encode(int start, int end, byte[] dst, int dstOff)
+ {
+ /*
+ * If the encoded value is already known, then we just
+ * dump it.
+ */
+ if (hasEncodedHeader) {
+ int from = objOff + Math.Max(0, start);
+ int to = objOff + Math.Min(objLen, end);
+ int len = to - from;
+ if (len > 0) {
+ Array.Copy(objBuf, from, dst, dstOff, len);
+ return len;
+ } else {
+ return 0;
+ }
+ }
+
+ int off = 0;
+
+ /*
+ * Encode tag.
+ */
+ int fb = (TagClass << 6) + (Constructed ? 0x20 : 0x00);
+ if (TagValue < 0x1F) {
+ fb |= (TagValue & 0x1F);
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)fb;
+ }
+ off ++;
+ } else {
+ fb |= 0x1F;
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)fb;
+ }
+ off ++;
+ int k = 0;
+ for (int v = TagValue; v > 0; v >>= 7, k += 7);
+ while (k > 0) {
+ k -= 7;
+ int v = (TagValue >> k) & 0x7F;
+ if (k != 0) {
+ v |= 0x80;
+ }
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)v;
+ }
+ off ++;
+ }
+ }
+
+ /*
+ * Encode length.
+ */
+ int vlen = ValueLength;
+ if (vlen < 0x80) {
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)vlen;
+ }
+ off ++;
+ } else {
+ int k = 0;
+ for (int v = vlen; v > 0; v >>= 8, k += 8);
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)(0x80 + (k >> 3));
+ }
+ off ++;
+ while (k > 0) {
+ k -= 8;
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)(vlen >> k);
+ }
+ off ++;
+ }
+ }
+
+ /*
+ * Encode value. We must adjust the start/end window to
+ * make it relative to the value.
+ */
+ EncodeValue(start - off, end - off, dst, dstOff);
+ off += vlen;
+
+ /*
+ * Compute copied length.
+ */
+ return Math.Max(0, Math.Min(off, end) - Math.Max(0, start));
+ }
+
+ /*
+ * Encode the value into the provided buffer. Only value bytes
+ * at offsets between 'start' (inclusive) and 'end' (exclusive)
+ * are written. Actual number of written bytes is returned.
+ * Offsets are relative to the start of the value.
+ */
+ int EncodeValue(int start, int end, byte[] dst, int dstOff)
+ {
+ int orig = dstOff;
+ if (objBuf == null) {
+ int k = 0;
+ foreach (AsnElt a in Sub) {
+ int slen = a.EncodedLength;
+ dstOff += a.Encode(start - k, end - k,
+ dst, dstOff);
+ k += slen;
+ }
+ } else {
+ int from = Math.Max(0, start);
+ int to = Math.Min(valLen, end);
+ int len = to - from;
+ if (len > 0) {
+ Array.Copy(objBuf, valOff + from,
+ dst, dstOff, len);
+ dstOff += len;
+ }
+ }
+ return dstOff - orig;
+ }
+
+ /*
+ * Copy a value chunk. The provided offset ('off') and length ('len')
+ * define the chunk to copy; the offset is relative to the value
+ * start (first value byte has offset 0). If the requested window
+ * exceeds the value boundaries, an exception is thrown.
+ */
+ public void CopyValueChunk(int off, int len, byte[] dst, int dstOff)
+ {
+ int vlen = ValueLength;
+ if (off < 0 || len < 0 || len > (vlen - off)) {
+ throw new AsnException(String.Format(
+ "invalid value window {0}:{1}"
+ + " (value length = {2})", off, len, vlen));
+ }
+ EncodeValue(off, off + len, dst, dstOff);
+ }
+
+ /*
+ * Copy the value into the specified array. The value length is
+ * returned.
+ */
+ public int CopyValue(byte[] dst, int off)
+ {
+ return EncodeValue(0, Int32.MaxValue, dst, off);
+ }
+
+ /*
+ * Get a copy of the value as a freshly allocated array.
+ */
+ public byte[] CopyValue()
+ {
+ byte[] r = new byte[ValueLength];
+ EncodeValue(0, r.Length, r, 0);
+ return r;
+ }
+
+ /*
+ * Get the value. This may return a shared buffer, that MUST NOT
+ * be modified.
+ */
+ byte[] GetValue(out int off, out int len)
+ {
+ if (objBuf == null) {
+ /*
+ * We can modify objBuf because CopyValue()
+ * called ValueLength, thus valLen has been
+ * filled.
+ */
+ objBuf = CopyValue();
+ off = 0;
+ len = objBuf.Length;
+ } else {
+ off = valOff;
+ len = valLen;
+ }
+ return objBuf;
+ }
+
+ /*
+ * Interpret the value as a BOOLEAN.
+ */
+ public bool GetBoolean()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid BOOLEAN (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen != 1) {
+ throw new AsnException(String.Format(
+ "invalid BOOLEAN (length = {0})", vlen));
+ }
+ return ValueByte(0) != 0;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. An exception is thrown if
+ * the value does not fit in a 'long'.
+ */
+ public long GetInteger()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid INTEGER (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException("invalid INTEGER (length = 0)");
+ }
+ int v = ValueByte(0);
+ long x;
+ if ((v & 0x80) != 0) {
+ x = -1;
+ for (int k = 0; k < vlen; k ++) {
+ if (x < ((-1L) << 55)) {
+ throw new AsnException(
+ "integer overflow (negative)");
+ }
+ x = (x << 8) + (long)ValueByte(k);
+ }
+ } else {
+ x = 0;
+ for (int k = 0; k < vlen; k ++) {
+ if (x >= (1L << 55)) {
+ throw new AsnException(
+ "integer overflow (positive)");
+ }
+ x = (x << 8) + (long)ValueByte(k);
+ }
+ }
+ return x;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. An exception is thrown if
+ * the value is outside of the provided range.
+ */
+ public long GetInteger(long min, long max)
+ {
+ long v = GetInteger();
+ if (v < min || v > max) {
+ throw new AsnException("integer out of allowed range");
+ }
+ return v;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. Return its hexadecimal
+ * representation (uppercase), preceded by a '0x' or '-0x'
+ * header, depending on the integer sign. The number of
+ * hexadecimal digits is even. Leading zeroes are returned (but
+ * one may remain, to ensure an even number of digits). If the
+ * integer has value 0, then 0x00 is returned.
+ */
+ public string GetIntegerHex()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid INTEGER (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException("invalid INTEGER (length = 0)");
+ }
+ StringBuilder sb = new StringBuilder();
+ byte[] tmp = CopyValue();
+ if (tmp[0] >= 0x80) {
+ sb.Append('-');
+ int cc = 1;
+ for (int i = tmp.Length - 1; i >= 0; i --) {
+ int v = ((~tmp[i]) & 0xFF) + cc;
+ tmp[i] = (byte)v;
+ cc = v >> 8;
+ }
+ }
+ int k = 0;
+ while (k < tmp.Length && tmp[k] == 0) {
+ k ++;
+ }
+ if (k == tmp.Length) {
+ return "0x00";
+ }
+ sb.Append("0x");
+ while (k < tmp.Length) {
+ sb.AppendFormat("{0:X2}", tmp[k ++]);
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Interpret the value as an OCTET STRING. The value bytes are
+ * returned. This method supports constructed values and performs
+ * the reassembly.
+ */
+ public byte[] GetOctetString()
+ {
+ int len = GetOctetString(null, 0);
+ byte[] r = new byte[len];
+ GetOctetString(r, 0);
+ return r;
+ }
+
+ /*
+ * Interpret the value as an OCTET STRING. The value bytes are
+ * written in dst[], starting at offset 'off', and the total value
+ * length is returned. If 'dst' is null, then no byte is written
+ * anywhere, but the total length is still returned. This method
+ * supports constructed values and performs the reassembly.
+ */
+ public int GetOctetString(byte[] dst, int off)
+ {
+ if (Constructed) {
+ int orig = off;
+ foreach (AsnElt ae in Sub) {
+ ae.CheckTag(AsnElt.OCTET_STRING);
+ off += ae.GetOctetString(dst, off);
+ }
+ return off - orig;
+ }
+ if (dst != null) {
+ return CopyValue(dst, off);
+ } else {
+ return ValueLength;
+ }
+ }
+
+ /*
+ * Interpret the value as a BIT STRING. The bits are returned,
+ * with the "ignored bits" cleared.
+ */
+ public byte[] GetBitString()
+ {
+ int bitLength;
+ return GetBitString(out bitLength);
+ }
+
+ /*
+ * Interpret the value as a BIT STRING. The bits are returned,
+ * with the "ignored bits" cleared. The actual bit length is
+ * written in 'bitLength'.
+ */
+ public byte[] GetBitString(out int bitLength)
+ {
+ if (Constructed) {
+ /*
+ * TODO: support constructed BIT STRING values.
+ */
+ throw new AsnException(
+ "invalid BIT STRING (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException(
+ "invalid BIT STRING (length = 0)");
+ }
+ int fb = ValueByte(0);
+ if (fb > 7 || (vlen == 1 && fb != 0)) {
+ throw new AsnException(String.Format(
+ "invalid BIT STRING (start = 0x{0:X2})", fb));
+ }
+ byte[] r = new byte[vlen - 1];
+ CopyValueChunk(1, vlen - 1, r, 0);
+ if (vlen > 1) {
+ r[r.Length - 1] &= (byte)(0xFF << fb);
+ }
+ bitLength = (r.Length << 3) - fb;
+ return r;
+ }
+
+ /*
+ * Interpret the value as a NULL.
+ */
+ public void CheckNull()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid NULL (constructed)");
+ }
+ if (ValueLength != 0) {
+ throw new AsnException(String.Format(
+ "invalid NULL (length = {0})", ValueLength));
+ }
+ }
+
+ /*
+ * Interpret the value as an OBJECT IDENTIFIER, and return it
+ * (in decimal-dotted string format).
+ */
+ public string GetOID()
+ {
+ CheckPrimitive();
+ if (valLen == 0) {
+ throw new AsnException("zero-length OID");
+ }
+ int v = objBuf[valOff];
+ if (v >= 120) {
+ throw new AsnException(
+ "invalid OID: first byte = " + v);
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.Append(v / 40);
+ sb.Append('.');
+ sb.Append(v % 40);
+ long acc = 0;
+ bool uv = false;
+ for (int i = 1; i < valLen; i ++) {
+ v = objBuf[valOff + i];
+ if ((acc >> 56) != 0) {
+ throw new AsnException(
+ "invalid OID: integer overflow");
+ }
+ acc = (acc << 7) + (long)(v & 0x7F);
+ if ((v & 0x80) == 0) {
+ sb.Append('.');
+ sb.Append(acc);
+ acc = 0;
+ uv = false;
+ } else {
+ uv = true;
+ }
+ }
+ if (uv) {
+ throw new AsnException(
+ "invalid OID: truncated");
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Get the object value as a string. The string type is inferred
+ * from the tag.
+ */
+ public string GetString()
+ {
+ if (TagClass != UNIVERSAL) {
+ throw new AsnException(String.Format(
+ "cannot infer string type: {0}:{1}",
+ TagClass, TagValue));
+ }
+ return GetString(TagValue);
+ }
+
+ /*
+ * Get the object value as a string. The string type is provided
+ * (universal tag value). Supported string types include
+ * NumericString, PrintableString, IA5String, TeletexString
+ * (interpreted as ISO-8859-1), UTF8String, BMPString and
+ * UniversalString; the "time types" (UTCTime and GeneralizedTime)
+ * are also supported, though, in their case, the internal
+ * contents are not checked (they are decoded as PrintableString).
+ */
+ public string GetString(int type)
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid string (constructed)");
+ }
+ switch (type) {
+ case NumericString:
+ case PrintableString:
+ case IA5String:
+ case TeletexString:
+ case UTCTime:
+ case GeneralizedTime:
+ return DecodeMono(objBuf, valOff, valLen, type);
+ case UTF8String:
+ return DecodeUTF8(objBuf, valOff, valLen);
+ case BMPString:
+ return DecodeUTF16(objBuf, valOff, valLen);
+ case UniversalString:
+ return DecodeUTF32(objBuf, valOff, valLen);
+ default:
+ throw new AsnException(
+ "unsupported string type: " + type);
+ }
+ }
+
+ static string DecodeMono(byte[] buf, int off, int len, int type)
+ {
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ tc[i] = (char)buf[off + i];
+ }
+ VerifyChars(tc, type);
+ return new string(tc);
+ }
+
+ static string DecodeUTF8(byte[] buf, int off, int len)
+ {
+ /*
+ * Skip BOM.
+ */
+ if (len >= 3 && buf[off] == 0xEF
+ && buf[off + 1] == 0xBB && buf[off + 2] == 0xBF)
+ {
+ off += 3;
+ len -= 3;
+ }
+ char[] tc = null;
+ for (int k = 0; k < 2; k ++) {
+ int tcOff = 0;
+ for (int i = 0; i < len; i ++) {
+ int c = buf[off + i];
+ int e;
+ if (c < 0x80) {
+ e = 0;
+ } else if (c < 0xC0) {
+ throw BadByte(c, UTF8String);
+ } else if (c < 0xE0) {
+ c &= 0x1F;
+ e = 1;
+ } else if (c < 0xF0) {
+ c &= 0x0F;
+ e = 2;
+ } else if (c < 0xF8) {
+ c &= 0x07;
+ e = 3;
+ } else {
+ throw BadByte(c, UTF8String);
+ }
+ while (e -- > 0) {
+ if (++ i >= len) {
+ throw new AsnException(
+ "invalid UTF-8 string");
+ }
+ int d = buf[off + i];
+ if (d < 0x80 || d > 0xBF) {
+ throw BadByte(d, UTF8String);
+ }
+ c = (c << 6) + (d & 0x3F);
+ }
+ if (c > 0x10FFFF) {
+ throw BadChar(c, UTF8String);
+ }
+ if (c > 0xFFFF) {
+ c -= 0x10000;
+ int hi = 0xD800 + (c >> 10);
+ int lo = 0xDC00 + (c & 0x3FF);
+ if (tc != null) {
+ tc[tcOff] = (char)hi;
+ tc[tcOff + 1] = (char)lo;
+ }
+ tcOff += 2;
+ } else {
+ if (tc != null) {
+ tc[tcOff] = (char)c;
+ }
+ tcOff ++;
+ }
+ }
+ if (tc == null) {
+ tc = new char[tcOff];
+ }
+ }
+ VerifyChars(tc, UTF8String);
+ return new string(tc);
+ }
+
+ static string DecodeUTF16(byte[] buf, int off, int len)
+ {
+ if ((len & 1) != 0) {
+ throw new AsnException(
+ "invalid UTF-16 string: length = " + len);
+ }
+ len >>= 1;
+ if (len == 0) {
+ return "";
+ }
+ bool be = true;
+ int hi = buf[off];
+ int lo = buf[off + 1];
+ if (hi == 0xFE && lo == 0xFF) {
+ off += 2;
+ len --;
+ } else if (hi == 0xFF && lo == 0xFE) {
+ off += 2;
+ len --;
+ be = false;
+ }
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int b0 = buf[off ++];
+ int b1 = buf[off ++];
+ if (be) {
+ tc[i] = (char)((b0 << 8) + b1);
+ } else {
+ tc[i] = (char)((b1 << 8) + b0);
+ }
+ }
+ VerifyChars(tc, BMPString);
+ return new string(tc);
+ }
+
+ static string DecodeUTF32(byte[] buf, int off, int len)
+ {
+ if ((len & 3) != 0) {
+ throw new AsnException(
+ "invalid UTF-32 string: length = " + len);
+ }
+ len >>= 2;
+ if (len == 0) {
+ return "";
+ }
+ bool be = true;
+ if (buf[off] == 0x00
+ && buf[off + 1] == 0x00
+ && buf[off + 2] == 0xFE
+ && buf[off + 3] == 0xFF)
+ {
+ off += 4;
+ len --;
+ } else if (buf[off] == 0xFF
+ && buf[off + 1] == 0xFE
+ && buf[off + 2] == 0x00
+ && buf[off + 3] == 0x00)
+ {
+ off += 4;
+ len --;
+ be = false;
+ }
+
+ char[] tc = null;
+ for (int k = 0; k < 2; k ++) {
+ int tcOff = 0;
+ for (int i = 0; i < len; i ++) {
+ uint b0 = buf[off + 0];
+ uint b1 = buf[off + 1];
+ uint b2 = buf[off + 2];
+ uint b3 = buf[off + 3];
+ uint c;
+ if (be) {
+ c = (b0 << 24) | (b1 << 16)
+ | (b2 << 8) | b3;
+ } else {
+ c = (b3 << 24) | (b2 << 16)
+ | (b1 << 8) | b0;
+ }
+ if (c > 0x10FFFF) {
+ throw BadChar((int)c, UniversalString);
+ }
+ if (c > 0xFFFF) {
+ c -= 0x10000;
+ int hi = 0xD800 + (int)(c >> 10);
+ int lo = 0xDC00 + (int)(c & 0x3FF);
+ if (tc != null) {
+ tc[tcOff] = (char)hi;
+ tc[tcOff + 1] = (char)lo;
+ }
+ tcOff += 2;
+ } else {
+ if (tc != null) {
+ tc[tcOff] = (char)c;
+ }
+ tcOff ++;
+ }
+ }
+ if (tc == null) {
+ tc = new char[tcOff];
+ }
+ }
+ VerifyChars(tc, UniversalString);
+ return new string(tc);
+ }
+
+ static void VerifyChars(char[] tc, int type)
+ {
+ switch (type) {
+ case NumericString:
+ foreach (char c in tc) {
+ if (!IsNum(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case PrintableString:
+ case UTCTime:
+ case GeneralizedTime:
+ foreach (char c in tc) {
+ if (!IsPrintable(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case IA5String:
+ foreach (char c in tc) {
+ if (!IsIA5(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case TeletexString:
+ foreach (char c in tc) {
+ if (!IsLatin1(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ }
+
+ /*
+ * For Unicode string types (UTF-8, BMP...).
+ */
+ for (int i = 0; i < tc.Length; i ++) {
+ int c = tc[i];
+ if (c >= 0xFDD0 && c <= 0xFDEF) {
+ throw BadChar(c, type);
+ }
+ if (c == 0xFFFE || c == 0xFFFF) {
+ throw BadChar(c, type);
+ }
+ if (c < 0xD800 || c > 0xDFFF) {
+ continue;
+ }
+ if (c > 0xDBFF) {
+ throw BadChar(c, type);
+ }
+ int hi = c & 0x3FF;
+ if (++ i >= tc.Length) {
+ throw BadChar(c, type);
+ }
+ c = tc[i];
+ if (c < 0xDC00 || c > 0xDFFF) {
+ throw BadChar(c, type);
+ }
+ int lo = c & 0x3FF;
+ c = 0x10000 + lo + (hi << 10);
+ if ((c & 0xFFFE) == 0xFFFE) {
+ throw BadChar(c, type);
+ }
+ }
+ }
+
+ static bool IsNum(int c)
+ {
+ return c == ' ' || (c >= '0' && c <= '9');
+ }
+
+ internal static bool IsPrintable(int c)
+ {
+ if (c >= 'A' && c <= 'Z') {
+ return true;
+ }
+ if (c >= 'a' && c <= 'z') {
+ return true;
+ }
+ if (c >= '0' && c <= '9') {
+ return true;
+ }
+ switch (c) {
+ case ' ': case '(': case ')': case '+':
+ case ',': case '-': case '.': case '/':
+ case ':': case '=': case '?': case '\'':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsIA5(int c)
+ {
+ return c < 128;
+ }
+
+ static bool IsLatin1(int c)
+ {
+ return c < 256;
+ }
+
+ static AsnException BadByte(int c, int type)
+ {
+ return new AsnException(String.Format(
+ "unexpected byte 0x{0:X2} in string of type {1}",
+ c, type));
+ }
+
+ static AsnException BadChar(int c, int type)
+ {
+ return new AsnException(String.Format(
+ "unexpected character U+{0:X4} in string of type {1}",
+ c, type));
+ }
+
+ /*
+ * Decode the value as a date/time. Returned object is in UTC.
+ * Type of date is inferred from the tag value.
+ */
+ public DateTime GetTime()
+ {
+ if (TagClass != UNIVERSAL) {
+ throw new AsnException(String.Format(
+ "cannot infer date type: {0}:{1}",
+ TagClass, TagValue));
+ }
+ return GetTime(TagValue);
+ }
+
+ /*
+ * Decode the value as a date/time. Returned object is in UTC.
+ * The time string type is provided as parameter (UTCTime or
+ * GeneralizedTime).
+ */
+ public DateTime GetTime(int type)
+ {
+ bool isGen = false;
+ switch (type) {
+ case UTCTime:
+ break;
+ case GeneralizedTime:
+ isGen = true;
+ break;
+ default:
+ throw new AsnException(
+ "unsupported date type: " + type);
+ }
+ string s = GetString(type);
+ string orig = s;
+
+ /*
+ * UTCTime has format:
+ * YYMMDDhhmm[ss](Z|(+|-)hhmm)
+ *
+ * GeneralizedTime has format:
+ * YYYYMMDDhhmmss[.uu*][Z|(+|-)hhmm]
+ *
+ * Differences between the two types:
+ * -- UTCTime encodes year over two digits; GeneralizedTime
+ * uses four digits. UTCTime years map to 1950..2049 (00 is
+ * 2000).
+ * -- Seconds are optional with UTCTime, mandatory with
+ * GeneralizedTime.
+ * -- GeneralizedTime can have fractional seconds (optional).
+ * -- Time zone is optional for GeneralizedTime. However,
+ * a missing time zone means "local time" which depends on
+ * the locality, so this is discouraged.
+ *
+ * Some other notes:
+ * -- If there is a fractional second, then it must include
+ * at least one digit. This implementation processes the
+ * first three digits, and ignores the rest (if present).
+ * -- Time zone offset ranges from -23:59 to +23:59.
+ * -- The calendar computations are delegated to .NET's
+ * DateTime (and DateTimeOffset) so this implements a
+ * Gregorian calendar, even for dates before 1589. Year 0
+ * is not supported.
+ */
+
+ /*
+ * Check characters.
+ */
+ foreach (char c in s) {
+ if (c >= '0' && c <= '9') {
+ continue;
+ }
+ if (c == '.' || c == '+' || c == '-' || c == 'Z') {
+ continue;
+ }
+ throw BadTime(type, orig);
+ }
+
+ bool good = true;
+
+ /*
+ * Parse the time zone.
+ */
+ int tzHours = 0;
+ int tzMinutes = 0;
+ bool negZ = false;
+ bool noTZ = false;
+ if (s.EndsWith("Z")) {
+ s = s.Substring(0, s.Length - 1);
+ } else {
+ int j = s.IndexOf('+');
+ if (j < 0) {
+ j = s.IndexOf('-');
+ negZ = true;
+ }
+ if (j < 0) {
+ noTZ = true;
+ } else {
+ string t = s.Substring(j + 1);
+ s = s.Substring(0, j);
+ if (t.Length != 4) {
+ throw BadTime(type, orig);
+ }
+ tzHours = Dec2(t, 0, ref good);
+ tzMinutes = Dec2(t, 2, ref good);
+ if (tzHours < 0 || tzHours > 23
+ || tzMinutes < 0 || tzMinutes > 59)
+ {
+ throw BadTime(type, orig);
+ }
+ }
+ }
+
+ /*
+ * Lack of time zone is allowed only for GeneralizedTime.
+ */
+ if (noTZ && !isGen) {
+ throw BadTime(type, orig);
+ }
+
+ /*
+ * Parse the date elements.
+ */
+ if (s.Length < 4) {
+ throw BadTime(type, orig);
+ }
+ int year = Dec2(s, 0, ref good);
+ if (isGen) {
+ year = year * 100 + Dec2(s, 2, ref good);
+ s = s.Substring(4);
+ } else {
+ if (year < 50) {
+ year += 100;
+ }
+ year += 1900;
+ s = s.Substring(2);
+ }
+ int month = Dec2(s, 0, ref good);
+ int day = Dec2(s, 2, ref good);
+ int hour = Dec2(s, 4, ref good);
+ int minute = Dec2(s, 6, ref good);
+ int second = 0;
+ int millisecond = 0;
+ if (isGen) {
+ second = Dec2(s, 8, ref good);
+ if (s.Length >= 12 && s[10] == '.') {
+ s = s.Substring(11);
+ foreach (char c in s) {
+ if (c < '0' || c > '9') {
+ good = false;
+ break;
+ }
+ }
+ s += "0000";
+ millisecond = 10 * Dec2(s, 0, ref good)
+ + Dec2(s, 2, ref good) / 10;
+ } else if (s.Length != 10) {
+ good = false;
+ }
+ } else {
+ switch (s.Length) {
+ case 8:
+ break;
+ case 10:
+ second = Dec2(s, 8, ref good);
+ break;
+ default:
+ throw BadTime(type, orig);
+ }
+ }
+
+ /*
+ * Parsing is finished; if any error occurred, then
+ * the 'good' flag has been cleared.
+ */
+ if (!good) {
+ throw BadTime(type, orig);
+ }
+
+ /*
+ * Leap seconds are not supported by .NET, so we claim
+ * they do not occur.
+ */
+ if (second == 60) {
+ second = 59;
+ }
+
+ /*
+ * .NET implementation performs all the checks (including
+ * checks on month length depending on year, as per the
+ * proleptic Gregorian calendar).
+ */
+ try {
+ if (noTZ) {
+ DateTime dt = new DateTime(year, month, day,
+ hour, minute, second, millisecond,
+ DateTimeKind.Local);
+ return dt.ToUniversalTime();
+ }
+ TimeSpan tzOff = new TimeSpan(tzHours, tzMinutes, 0);
+ if (negZ) {
+ tzOff = tzOff.Negate();
+ }
+ DateTimeOffset dto = new DateTimeOffset(
+ year, month, day, hour, minute, second,
+ millisecond, tzOff);
+ return dto.UtcDateTime;
+ } catch (Exception e) {
+ throw BadTime(type, orig, e);
+ }
+ }
+
+ static int Dec2(string s, int off, ref bool good)
+ {
+ if (off < 0 || off >= (s.Length - 1)) {
+ good = false;
+ return -1;
+ }
+ char c1 = s[off];
+ char c2 = s[off + 1];
+ if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') {
+ good = false;
+ return -1;
+ }
+ return 10 * (c1 - '0') + (c2 - '0');
+ }
+
+ static AsnException BadTime(int type, string s)
+ {
+ return BadTime(type, s, null);
+ }
+
+ static AsnException BadTime(int type, string s, Exception e)
+ {
+ string tt = (type == UTCTime) ? "UTCTime" : "GeneralizedTime";
+ string msg = String.Format("invalid {0} string: '{1}'", tt, s);
+ if (e == null) {
+ return new AsnException(msg);
+ } else {
+ return new AsnException(msg, e);
+ }
+ }
+
+ /* =============================================================== */
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagValue, byte[] val)
+ {
+ return MakePrimitive(UNIVERSAL, tagValue, val, 0, val.Length);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagValue,
+ byte[] val, int off, int len)
+ {
+ return MakePrimitive(UNIVERSAL, tagValue, val, off, len);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(
+ int tagClass, int tagValue, byte[] val)
+ {
+ return MakePrimitive(tagClass, tagValue, val, 0, val.Length);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagClass, int tagValue,
+ byte[] val, int off, int len)
+ {
+ byte[] nval = new byte[len];
+ Array.Copy(val, off, nval, 0, len);
+ return MakePrimitiveInner(tagClass, tagValue, nval, 0, len);
+ }
+
+ /*
+ * Like MakePrimitive(), but the provided array is NOT copied.
+ * This is for other factory methods that already allocate a
+ * new array.
+ */
+ static AsnElt MakePrimitiveInner(int tagValue, byte[] val)
+ {
+ return MakePrimitiveInner(UNIVERSAL, tagValue,
+ val, 0, val.Length);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagValue,
+ byte[] val, int off, int len)
+ {
+ return MakePrimitiveInner(UNIVERSAL, tagValue, val, off, len);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagClass, int tagValue, byte[] val)
+ {
+ return MakePrimitiveInner(tagClass, tagValue,
+ val, 0, val.Length);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagClass, int tagValue,
+ byte[] val, int off, int len)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = new byte[len];
+ Array.Copy(val, off, a.objBuf, 0, len);
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = len;
+ a.hasEncodedHeader = false;
+ if (tagClass < 0 || tagClass > 3) {
+ throw new AsnException(
+ "invalid tag class: " + tagClass);
+ }
+ if (tagValue < 0) {
+ throw new AsnException(
+ "invalid tag value: " + tagValue);
+ }
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ a.Sub = null;
+ return a;
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer.
+ */
+ public static AsnElt MakeInteger(long x)
+ {
+ if (x >= 0) {
+ return MakeInteger((ulong)x);
+ }
+ int k = 1;
+ for (long w = x; w <= -(long)0x80; w >>= 8) {
+ k ++;
+ }
+ byte[] v = new byte[k];
+ for (long w = x; k > 0; w >>= 8) {
+ v[-- k] = (byte)w;
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer.
+ */
+ public static AsnElt MakeInteger(ulong x)
+ {
+ int k = 1;
+ for (ulong w = x; w >= 0x80; w >>= 8) {
+ k ++;
+ }
+ byte[] v = new byte[k];
+ for (ulong w = x; k > 0; w >>= 8) {
+ v[-- k] = (byte)w;
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer. The x[]
+ * array uses _unsigned_ big-endian encoding.
+ */
+ public static AsnElt MakeInteger(byte[] x)
+ {
+ int xLen = x.Length;
+ int j = 0;
+ while (j < xLen && x[j] == 0x00) {
+ j ++;
+ }
+ if (j == xLen) {
+ return MakePrimitiveInner(INTEGER, new byte[] { 0x00 });
+ }
+ byte[] v;
+ if (x[j] < 0x80) {
+ v = new byte[xLen - j];
+ Array.Copy(x, j, v, 0, v.Length);
+ } else {
+ v = new byte[1 + xLen - j];
+ Array.Copy(x, j, v, 1, v.Length - 1);
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer. The x[]
+ * array uses _signed_ big-endian encoding.
+ */
+ public static AsnElt MakeIntegerSigned(byte[] x)
+ {
+ int xLen = x.Length;
+ if (xLen == 0) {
+ throw new AsnException(
+ "Invalid signed integer (empty)");
+ }
+ int j = 0;
+ if (x[0] >= 0x80) {
+ while (j < (xLen - 1)
+ && x[j] == 0xFF
+ && x[j + 1] >= 0x80)
+ {
+ j ++;
+ }
+ } else {
+ while (j < (xLen - 1)
+ && x[j] == 0x00
+ && x[j + 1] < 0x80)
+ {
+ j ++;
+ }
+ }
+ byte[] v = new byte[xLen - j];
+ Array.Copy(x, j, v, 0, v.Length);
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a BIT STRING from the provided value. The number of
+ * "unused bits" is set to 0.
+ */
+ public static AsnElt MakeBitString(byte[] buf)
+ {
+ return MakeBitString(buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBitString(byte[] buf, int off, int len)
+ {
+ byte[] tmp = new byte[len + 1];
+ Array.Copy(buf, off, tmp, 1, len);
+ return MakePrimitiveInner(BIT_STRING, tmp);
+ }
+
+ /*
+ * Create a BIT STRING from the provided value. The number of
+ * "unused bits" is specified.
+ */
+ public static AsnElt MakeBitString(int unusedBits, byte[] buf)
+ {
+ return MakeBitString(unusedBits, buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBitString(int unusedBits,
+ byte[] buf, int off, int len)
+ {
+ if (unusedBits < 0 || unusedBits > 7
+ || (unusedBits != 0 && len == 0))
+ {
+ throw new AsnException(
+ "Invalid number of unused bits in BIT STRING: "
+ + unusedBits);
+ }
+ byte[] tmp = new byte[len + 1];
+ tmp[0] = (byte)unusedBits;
+ Array.Copy(buf, off, tmp, 1, len);
+ if (len > 0) {
+ tmp[len - 1] &= (byte)(0xFF << unusedBits);
+ }
+ return MakePrimitiveInner(BIT_STRING, tmp);
+ }
+
+ /*
+ * Create an OCTET STRING from the provided value.
+ */
+ public static AsnElt MakeBlob(byte[] buf)
+ {
+ return MakeBlob(buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBlob(byte[] buf, int off, int len)
+ {
+ return MakePrimitive(OCTET_STRING, buf, off, len);
+ }
+
+ /*
+ * Create a new constructed elements, by providing the relevant
+ * sub-elements.
+ */
+ public static AsnElt Make(int tagValue, params AsnElt[] subs)
+ {
+ return Make(UNIVERSAL, tagValue, subs);
+ }
+
+ /*
+ * Create a new constructed elements, by providing the relevant
+ * sub-elements.
+ */
+ public static AsnElt Make(int tagClass, int tagValue,
+ params AsnElt[] subs)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = null;
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = -1;
+ a.hasEncodedHeader = false;
+ if (tagClass < 0 || tagClass > 3) {
+ throw new AsnException(
+ "invalid tag class: " + tagClass);
+ }
+ if (tagValue < 0) {
+ throw new AsnException(
+ "invalid tag value: " + tagValue);
+ }
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ if (subs == null) {
+ a.Sub = new AsnElt[0];
+ } else {
+ a.Sub = new AsnElt[subs.Length];
+ Array.Copy(subs, 0, a.Sub, 0, subs.Length);
+ }
+ return a;
+ }
+
+ /*
+ * Create a SET OF: sub-elements are automatically sorted by
+ * lexicographic order of their DER encodings. Identical elements
+ * are merged.
+ */
+ public static AsnElt MakeSetOf(params AsnElt[] subs)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = null;
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = -1;
+ a.hasEncodedHeader = false;
+ a.TagClass = UNIVERSAL;
+ a.TagValue = SET;
+ if (subs == null) {
+ a.Sub = new AsnElt[0];
+ } else {
+ SortedDictionary<byte[], AsnElt> d =
+ new SortedDictionary<byte[], AsnElt>(
+ COMPARER_LEXICOGRAPHIC);
+ foreach (AsnElt ax in subs) {
+ d[ax.Encode()] = ax;
+ }
+ AsnElt[] tmp = new AsnElt[d.Count];
+ int j = 0;
+ foreach (AsnElt ax in d.Values) {
+ tmp[j ++] = ax;
+ }
+ a.Sub = tmp;
+ }
+ return a;
+ }
+
+ static IComparer<byte[]> COMPARER_LEXICOGRAPHIC =
+ new ComparerLexicographic();
+
+ class ComparerLexicographic : IComparer<byte[]> {
+
+ public int Compare(byte[] x, byte[] y)
+ {
+ int xLen = x.Length;
+ int yLen = y.Length;
+ int cLen = Math.Min(xLen, yLen);
+ for (int i = 0; i < cLen; i ++) {
+ if (x[i] != y[i]) {
+ return (int)x[i] - (int)y[i];
+ }
+ }
+ return xLen - yLen;
+ }
+ }
+
+ /*
+ * Wrap an element into an explicit tag.
+ */
+ public static AsnElt MakeExplicit(int tagClass, int tagValue, AsnElt x)
+ {
+ return Make(tagClass, tagValue, x);
+ }
+
+ /*
+ * Wrap an element into an explicit CONTEXT tag.
+ */
+ public static AsnElt MakeExplicit(int tagValue, AsnElt x)
+ {
+ return Make(CONTEXT, tagValue, x);
+ }
+
+ /*
+ * Apply an implicit tag to a value. The source AsnElt object
+ * is unmodified; a new object is returned.
+ */
+ public static AsnElt MakeImplicit(int tagClass, int tagValue, AsnElt x)
+ {
+ if (x.Constructed) {
+ return Make(tagClass, tagValue, x.Sub);
+ }
+ AsnElt a = new AsnElt();
+ a.objBuf = x.GetValue(out a.valOff, out a.valLen);
+ a.objOff = 0;
+ a.objLen = -1;
+ a.hasEncodedHeader = false;
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ a.Sub = null;
+ return a;
+ }
+
+ public static AsnElt NULL_V = AsnElt.MakePrimitive(
+ NULL, new byte[0]);
+
+ public static AsnElt BOOL_TRUE = AsnElt.MakePrimitive(
+ BOOLEAN, new byte[] { 0xFF });
+ public static AsnElt BOOL_FALSE = AsnElt.MakePrimitive(
+ BOOLEAN, new byte[] { 0x00 });
+
+ /*
+ * Create an OBJECT IDENTIFIER from its string representation.
+ * This function tolerates extra leading zeros.
+ */
+ public static AsnElt MakeOID(string str)
+ {
+ List<long> r = new List<long>();
+ int n = str.Length;
+ long x = -1;
+ for (int i = 0; i < n; i ++) {
+ int c = str[i];
+ if (c == '.') {
+ if (x < 0) {
+ throw new AsnException(
+ "invalid OID (empty element)");
+ }
+ r.Add(x);
+ x = -1;
+ continue;
+ }
+ if (c < '0' || c > '9') {
+ throw new AsnException(String.Format(
+ "invalid character U+{0:X4} in OID",
+ c));
+ }
+ if (x < 0) {
+ x = 0;
+ } else if (x > ((Int64.MaxValue - 9) / 10)) {
+ throw new AsnException("OID element overflow");
+ }
+ x = x * (long)10 + (long)(c - '0');
+ }
+ if (x < 0) {
+ throw new AsnException(
+ "invalid OID (empty element)");
+ }
+ r.Add(x);
+ if (r.Count < 2) {
+ throw new AsnException(
+ "invalid OID (not enough elements)");
+ }
+ if (r[0] > 2 || r[1] > 40) {
+ throw new AsnException(
+ "invalid OID (first elements out of range)");
+ }
+
+ MemoryStream ms = new MemoryStream();
+ ms.WriteByte((byte)(40 * (int)r[0] + (int)r[1]));
+ for (int i = 2; i < r.Count; i ++) {
+ long v = r[i];
+ if (v < 0x80) {
+ ms.WriteByte((byte)v);
+ continue;
+ }
+ int k = -7;
+ for (long w = v; w != 0; w >>= 7, k += 7);
+ ms.WriteByte((byte)(0x80 + (int)(v >> k)));
+ for (k -= 7; k >= 0; k -= 7) {
+ int z = (int)(v >> k) & 0x7F;
+ if (k > 0) {
+ z |= 0x80;
+ }
+ ms.WriteByte((byte)z);
+ }
+ }
+ byte[] buf = ms.ToArray();
+ return MakePrimitiveInner(OBJECT_IDENTIFIER,
+ buf, 0, buf.Length);
+ }
+
+ /*
+ * Create a string of the provided type and contents. The string
+ * type is a universal tag value for one of the string or time
+ * types.
+ */
+ public static AsnElt MakeString(int type, string str)
+ {
+ VerifyChars(str.ToCharArray(), type);
+ byte[] buf;
+ switch (type) {
+ case NumericString:
+ case PrintableString:
+ case UTCTime:
+ case GeneralizedTime:
+ case IA5String:
+ case TeletexString:
+ buf = EncodeMono(str);
+ break;
+ case UTF8String:
+ buf = EncodeUTF8(str);
+ break;
+ case BMPString:
+ buf = EncodeUTF16(str);
+ break;
+ case UniversalString:
+ buf = EncodeUTF32(str);
+ break;
+ default:
+ throw new AsnException(
+ "unsupported string type: " + type);
+ }
+ return MakePrimitiveInner(type, buf);
+ }
+
+ static byte[] EncodeMono(string str)
+ {
+ byte[] r = new byte[str.Length];
+ int k = 0;
+ foreach (char c in str) {
+ r[k ++] = (byte)c;
+ }
+ return r;
+ }
+
+ /*
+ * Get the code point at offset 'off' in the string. Either one
+ * or two 'char' slots are used; 'off' is updated accordingly.
+ */
+ static int CodePoint(string str, ref int off)
+ {
+ int c = str[off ++];
+ if (c >= 0xD800 && c < 0xDC00 && off < str.Length) {
+ int d = str[off];
+ if (d >= 0xDC00 && d < 0xE000) {
+ c = ((c & 0x3FF) << 10)
+ + (d & 0x3FF) + 0x10000;
+ off ++;
+ }
+ }
+ return c;
+ }
+
+ static byte[] EncodeUTF8(string str)
+ {
+ int k = 0;
+ int n = str.Length;
+ MemoryStream ms = new MemoryStream();
+ while (k < n) {
+ int cp = CodePoint(str, ref k);
+ if (cp < 0x80) {
+ ms.WriteByte((byte)cp);
+ } else if (cp < 0x800) {
+ ms.WriteByte((byte)(0xC0 + (cp >> 6)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ } else if (cp < 0x10000) {
+ ms.WriteByte((byte)(0xE0 + (cp >> 12)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ } else {
+ ms.WriteByte((byte)(0xF0 + (cp >> 18)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 12) & 63)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ }
+ }
+ return ms.ToArray();
+ }
+
+ static byte[] EncodeUTF16(string str)
+ {
+ byte[] buf = new byte[str.Length << 1];
+ int k = 0;
+ foreach (char c in str) {
+ buf[k ++] = (byte)(c >> 8);
+ buf[k ++] = (byte)c;
+ }
+ return buf;
+ }
+
+ static byte[] EncodeUTF32(string str)
+ {
+ int k = 0;
+ int n = str.Length;
+ MemoryStream ms = new MemoryStream();
+ while (k < n) {
+ int cp = CodePoint(str, ref k);
+ ms.WriteByte((byte)(cp >> 24));
+ ms.WriteByte((byte)(cp >> 16));
+ ms.WriteByte((byte)(cp >> 8));
+ ms.WriteByte((byte)cp);
+ }
+ return ms.ToArray();
+ }
+
+ /*
+ * Create a time value of the specified type (UTCTime or
+ * GeneralizedTime).
+ */
+ public static AsnElt MakeTime(int type, DateTime dt)
+ {
+ dt = dt.ToUniversalTime();
+ string str;
+ switch (type) {
+ case UTCTime:
+ int year = dt.Year;
+ if (year < 1950 || year >= 2050) {
+ throw new AsnException(String.Format(
+ "cannot encode year {0} as UTCTime",
+ year));
+ }
+ year = year % 100;
+ str = String.Format(
+ "{0:d2}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}Z",
+ year, dt.Month, dt.Day,
+ dt.Hour, dt.Minute, dt.Second);
+ break;
+ case GeneralizedTime:
+ str = String.Format(
+ "{0:d4}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}",
+ dt.Year, dt.Month, dt.Day,
+ dt.Hour, dt.Minute, dt.Second);
+ int millis = dt.Millisecond;
+ if (millis != 0) {
+ if (millis % 100 == 0) {
+ str = String.Format("{0}.{1:d1}",
+ str, millis / 100);
+ } else if (millis % 10 == 0) {
+ str = String.Format("{0}.{1:d2}",
+ str, millis / 10);
+ } else {
+ str = String.Format("{0}.{1:d3}",
+ str, millis);
+ }
+ }
+ str = str + "Z";
+ break;
+ default:
+ throw new AsnException(
+ "unsupported time type: " + type);
+ }
+ return MakeString(type, str);
+ }
+
+ /*
+ * Create a time value of the specified type (UTCTime or
+ * GeneralizedTime).
+ */
+ public static AsnElt MakeTime(int type, DateTimeOffset dto)
+ {
+ return MakeTime(type, dto.UtcDateTime);
+ }
+
+ /*
+ * Create a time value with an automatic type selection
+ * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+ * otherwise).
+ */
+ public static AsnElt MakeTimeAuto(DateTime dt)
+ {
+ dt = dt.ToUniversalTime();
+ return MakeTime((dt.Year >= 1950 && dt.Year <= 2049)
+ ? UTCTime : GeneralizedTime, dt);
+ }
+
+ /*
+ * Create a time value with an automatic type selection
+ * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+ * otherwise).
+ */
+ public static AsnElt MakeTimeAuto(DateTimeOffset dto)
+ {
+ return MakeTimeAuto(dto.UtcDateTime);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace Asn1 {
+
+/*
+ * An AsnException is thrown whenever decoding of an object fails. It is
+ * made a subclass of IOException under the assumption that decoding
+ * failures mostly occur when processing incoming data from external
+ * sources.
+ */
+
+public class AsnException : IOException {
+
+ public AsnException(string message)
+ : base(message)
+ {
+ }
+
+ public AsnException(string message, Exception nested)
+ : base(message, nested)
+ {
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * Helper functions for located BER/DER-encoded object, and in particular
+ * handle PEM format.
+ */
+
+public static class AsnIO {
+
+ public static byte[] FindDER(byte[] buf)
+ {
+ return FindBER(buf, true);
+ }
+
+ public static byte[] FindBER(byte[] buf)
+ {
+ return FindBER(buf, false);
+ }
+
+ /*
+ * Find a BER/DER object in the provided buffer. If the data is
+ * not already in the right format, conversion to string then
+ * Base64 decoding is attempted; in the latter case, PEM headers
+ * are detected and skipped. In any case, the returned buffer
+ * must begin with a well-formed tag and length, corresponding to
+ * the object length.
+ *
+ * If 'strictDER' is true, then the function furthermore insists
+ * on the object to use a defined DER length.
+ *
+ * The returned buffer may be the source buffer itself, or a newly
+ * allocated buffer.
+ *
+ * On error, null is returned.
+ */
+ public static byte[] FindBER(byte[] buf, bool strictDER)
+ {
+ string pemType = null;
+ return FindBER(buf, strictDER, out pemType);
+ }
+
+ /*
+ * Find a BER/DER object in the provided buffer. If the data is
+ * not already in the right format, conversion to string then
+ * Base64 decoding is attempted; in the latter case, PEM headers
+ * are detected and skipped. In any case, the returned buffer
+ * must begin with a well-formed tag and length, corresponding to
+ * the object length.
+ *
+ * If 'strictDER' is true, then the function furthermore insists
+ * on the object to use a defined DER length.
+ *
+ * If the source was detected to use PEM, then the object type
+ * indicated by the PEM header is written in 'pemType'; otherwise,
+ * that variable is set to null.
+ *
+ * The returned buffer may be the source buffer itself, or a newly
+ * allocated buffer.
+ *
+ * On error, null is returned.
+ */
+ public static byte[] FindBER(byte[] buf,
+ bool strictDER, out string pemType)
+ {
+ pemType = null;
+
+ /*
+ * If it is already (from the outside) a BER object,
+ * return it.
+ */
+ if (LooksLikeBER(buf, strictDER)) {
+ return buf;
+ }
+
+ string str = BinToString(buf);
+ if (str == null) {
+ return null;
+ }
+
+ /*
+ * Try to detect a PEM header and footer; if we find both
+ * then we remove both, keeping only the characters that
+ * occur in between.
+ */
+ int p = str.IndexOf("-----BEGIN ");
+ int q = str.IndexOf("-----END ");
+ if (p >= 0 && q >= 0) {
+ p += 11;
+ int r = str.IndexOf((char)10, p) + 1;
+ int px = str.IndexOf('-', p);
+ if (px > 0 && px < r && r > 0 && r <= q) {
+ pemType = string.Copy(str.Substring(p, px - p));
+ str = str.Substring(r, q - r);
+ }
+ }
+
+ /*
+ * Convert from Base64.
+ */
+ try {
+ buf = Convert.FromBase64String(str);
+ if (LooksLikeBER(buf, strictDER)) {
+ return buf;
+ }
+ } catch {
+ // ignored: not Base64
+ }
+
+ /*
+ * Decoding failed.
+ */
+ return null;
+ }
+
+ /*
+ * Decode multiple PEM objects from a file. Each object is
+ * returned with its name.
+ */
+ public static PEMObject[] DecodePEM(byte[] buf)
+ {
+ string str = BinToString(buf);
+ if (str == null) {
+ return new PEMObject[0];
+ }
+
+ List<PEMObject> fpo = new List<PEMObject>();
+ TextReader tr = new StringReader(str);
+ StringBuilder sb = new StringBuilder();
+ string currentType = null;
+ for (;;) {
+ string line = tr.ReadLine();
+ if (line == null) {
+ break;
+ }
+ if (currentType == null) {
+ if (line.StartsWith("-----BEGIN ")) {
+ line = line.Substring(11);
+ int n = line.IndexOf("-----");
+ if (n >= 0) {
+ line = line.Substring(0, n);
+ }
+ currentType = string.Copy(line);
+ sb = new StringBuilder();
+ }
+ continue;
+ }
+ if (line.StartsWith("-----END ")) {
+ string s = sb.ToString();
+ try {
+ byte[] data =
+ Convert.FromBase64String(s);
+ fpo.Add(new PEMObject(
+ currentType, data));
+ } catch {
+ /*
+ * Base64 decoding failed... we skip
+ * the object.
+ */
+ }
+ currentType = null;
+ sb.Clear();
+ continue;
+ }
+ sb.Append(line);
+ sb.Append("\n");
+ }
+ return fpo.ToArray();
+ }
+
+ /* =============================================================== */
+
+ /*
+ * Decode a tag; returned value is true on success, false otherwise.
+ * On success, 'off' is updated to point to the first byte after
+ * the tag.
+ */
+ static bool DecodeTag(byte[] buf, int lim, ref int off)
+ {
+ int p = off;
+ if (p >= lim) {
+ return false;
+ }
+ int v = buf[p ++];
+ if ((v & 0x1F) == 0x1F) {
+ do {
+ if (p >= lim) {
+ return false;
+ }
+ v = buf[p ++];
+ } while ((v & 0x80) != 0);
+ }
+ off = p;
+ return true;
+ }
+
+ /*
+ * Decode a BER length. Returned value is:
+ * -2 no decodable length
+ * -1 indefinite length
+ * 0+ definite length
+ * If a definite or indefinite length could be decoded, then 'off'
+ * is updated to point to the first byte after the length.
+ */
+ static int DecodeLength(byte[] buf, int lim, ref int off)
+ {
+ int p = off;
+ if (p >= lim) {
+ return -2;
+ }
+ int v = buf[p ++];
+ if (v < 0x80) {
+ off = p;
+ return v;
+ } else if (v == 0x80) {
+ off = p;
+ return -1;
+ }
+ v &= 0x7F;
+ if ((lim - p) < v) {
+ return -2;
+ }
+ int acc = 0;
+ while (v -- > 0) {
+ if (acc > 0x7FFFFF) {
+ return -2;
+ }
+ acc = (acc << 8) + buf[p ++];
+ }
+ off = p;
+ return acc;
+ }
+
+ /*
+ * Get the length, in bytes, of the object in the provided
+ * buffer. The object begins at offset 'off' but does not extend
+ * farther than offset 'lim'. If no such BER object can be
+ * decoded, then -1 is returned. The returned length includes
+ * that of the tag and length fields.
+ */
+ static int BERLength(byte[] buf, int lim, int off)
+ {
+ int orig = off;
+ if (!DecodeTag(buf, lim, ref off)) {
+ return -1;
+ }
+ int len = DecodeLength(buf, lim, ref off);
+ if (len >= 0) {
+ if (len > (lim - off)) {
+ return -1;
+ }
+ return off + len - orig;
+ } else if (len < -1) {
+ return -1;
+ }
+
+ /*
+ * Indefinite length: we must do some recursive exploration.
+ * End of structure is marked by a "null tag": object has
+ * total length 2 and its tag byte is 0.
+ */
+ for (;;) {
+ int slen = BERLength(buf, lim, off);
+ if (slen < 0) {
+ return -1;
+ }
+ off += slen;
+ if (slen == 2 && buf[off] == 0) {
+ return off - orig;
+ }
+ }
+ }
+
+ static bool LooksLikeBER(byte[] buf, bool strictDER)
+ {
+ return LooksLikeBER(buf, 0, buf.Length, strictDER);
+ }
+
+ static bool LooksLikeBER(byte[] buf, int off, int len, bool strictDER)
+ {
+ int lim = off + len;
+ int objLen = BERLength(buf, lim, off);
+ if (objLen != len) {
+ return false;
+ }
+ if (strictDER) {
+ DecodeTag(buf, lim, ref off);
+ return DecodeLength(buf, lim, ref off) >= 0;
+ } else {
+ return true;
+ }
+ }
+
+ static string ConvertMono(byte[] buf, int off)
+ {
+ int len = buf.Length - off;
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int v = buf[off + i];
+ if (v < 1 || v > 126) {
+ v = '?';
+ }
+ tc[i] = (char)v;
+ }
+ return new string(tc);
+ }
+
+ static string ConvertBi(byte[] buf, int off, bool be)
+ {
+ int len = buf.Length - off;
+ if ((len & 1) != 0) {
+ return null;
+ }
+ len >>= 1;
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int b0 = buf[off + (i << 1) + 0];
+ int b1 = buf[off + (i << 1) + 1];
+ int v = be ? ((b0 << 8) + b1) : (b0 + (b1 << 8));
+ if (v < 1 || v > 126) {
+ v = '?';
+ }
+ tc[i] = (char)v;
+ }
+ return new string(tc);
+ }
+
+ /*
+ * Convert a blob to a string. This supports UTF-16 (with and
+ * without a BOM), UTF-8 (with and without a BOM), and
+ * ASCII-compatible encodings. Non-ASCII characters get
+ * replaced with '?'. This function is meant to be used
+ * with heuristic PEM decoders.
+ *
+ * If conversion is not possible, then null is returned.
+ */
+ static string BinToString(byte[] buf)
+ {
+ if (buf.Length < 3) {
+ return null;
+ }
+ string str = null;
+ if ((buf.Length & 1) == 0) {
+ if (buf[0] == 0xFE && buf[1] == 0xFF) {
+ // Starts with big-endian UTF-16 BOM
+ str = ConvertBi(buf, 2, true);
+ } else if (buf[0] == 0xFF && buf[1] == 0xFE) {
+ // Starts with little-endian UTF-16 BOM
+ str = ConvertBi(buf, 2, false);
+ } else if (buf[0] == 0) {
+ // First byte is 0 -> big-endian UTF-16
+ str = ConvertBi(buf, 0, true);
+ } else if (buf[1] == 0) {
+ // Second byte is 0 -> little-endian UTF-16
+ str = ConvertBi(buf, 0, false);
+ }
+ }
+ if (str == null) {
+ if (buf[0] == 0xEF
+ && buf[1] == 0xBB
+ && buf[2] == 0xBF)
+ {
+ // Starts with UTF-8 BOM
+ str = ConvertMono(buf, 3);
+ } else {
+ // Assumed ASCII-compatible mono-byte encoding
+ str = ConvertMono(buf, 0);
+ }
+ }
+ return str;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * This class contains some helper methods to convert known OID to
+ * symbolic names and back.
+ */
+
+public class AsnOID {
+
+ static Dictionary<string, string> OIDToName =
+ new Dictionary<string, string>();
+ static Dictionary<string, string> NameToOID =
+ new Dictionary<string, string>();
+
+ static AsnOID()
+ {
+ /*
+ * From RFC 5280, PKIX1Explicit88 module.
+ */
+ Reg("1.3.6.1.5.5.7", "id-pkix");
+ Reg("1.3.6.1.5.5.7.1", "id-pe");
+ Reg("1.3.6.1.5.5.7.2", "id-qt");
+ Reg("1.3.6.1.5.5.7.3", "id-kp");
+ Reg("1.3.6.1.5.5.7.48", "id-ad");
+ Reg("1.3.6.1.5.5.7.2.1", "id-qt-cps");
+ Reg("1.3.6.1.5.5.7.2.2", "id-qt-unotice");
+ Reg("1.3.6.1.5.5.7.48.1", "id-ad-ocsp");
+ Reg("1.3.6.1.5.5.7.48.2", "id-ad-caIssuers");
+ Reg("1.3.6.1.5.5.7.48.3", "id-ad-timeStamping");
+ Reg("1.3.6.1.5.5.7.48.5", "id-ad-caRepository");
+
+ Reg("2.5.4", "id-at");
+ Reg("2.5.4.41", "id-at-name");
+ Reg("2.5.4.4", "id-at-surname");
+ Reg("2.5.4.42", "id-at-givenName");
+ Reg("2.5.4.43", "id-at-initials");
+ Reg("2.5.4.44", "id-at-generationQualifier");
+ Reg("2.5.4.3", "id-at-commonName");
+ Reg("2.5.4.7", "id-at-localityName");
+ Reg("2.5.4.8", "id-at-stateOrProvinceName");
+ Reg("2.5.4.10", "id-at-organizationName");
+ Reg("2.5.4.11", "id-at-organizationalUnitName");
+ Reg("2.5.4.12", "id-at-title");
+ Reg("2.5.4.46", "id-at-dnQualifier");
+ Reg("2.5.4.6", "id-at-countryName");
+ Reg("2.5.4.5", "id-at-serialNumber");
+ Reg("2.5.4.65", "id-at-pseudonym");
+ Reg("0.9.2342.19200300.100.1.25", "id-domainComponent");
+
+ Reg("1.2.840.113549.1.9", "pkcs-9");
+ Reg("1.2.840.113549.1.9.1", "id-emailAddress");
+
+ /*
+ * From RFC 5280, PKIX1Implicit88 module.
+ */
+ Reg("2.5.29", "id-ce");
+ Reg("2.5.29.35", "id-ce-authorityKeyIdentifier");
+ Reg("2.5.29.14", "id-ce-subjectKeyIdentifier");
+ Reg("2.5.29.15", "id-ce-keyUsage");
+ Reg("2.5.29.16", "id-ce-privateKeyUsagePeriod");
+ Reg("2.5.29.32", "id-ce-certificatePolicies");
+ Reg("2.5.29.33", "id-ce-policyMappings");
+ Reg("2.5.29.17", "id-ce-subjectAltName");
+ Reg("2.5.29.18", "id-ce-issuerAltName");
+ Reg("2.5.29.9", "id-ce-subjectDirectoryAttributes");
+ Reg("2.5.29.19", "id-ce-basicConstraints");
+ Reg("2.5.29.30", "id-ce-nameConstraints");
+ Reg("2.5.29.36", "id-ce-policyConstraints");
+ Reg("2.5.29.31", "id-ce-cRLDistributionPoints");
+ Reg("2.5.29.37", "id-ce-extKeyUsage");
+
+ Reg("2.5.29.37.0", "anyExtendedKeyUsage");
+ Reg("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth");
+ Reg("1.3.6.1.5.5.7.3.2", "id-kp-clientAuth");
+ Reg("1.3.6.1.5.5.7.3.3", "id-kp-codeSigning");
+ Reg("1.3.6.1.5.5.7.3.4", "id-kp-emailProtection");
+ Reg("1.3.6.1.5.5.7.3.8", "id-kp-timeStamping");
+ Reg("1.3.6.1.5.5.7.3.9", "id-kp-OCSPSigning");
+
+ Reg("2.5.29.54", "id-ce-inhibitAnyPolicy");
+ Reg("2.5.29.46", "id-ce-freshestCRL");
+ Reg("1.3.6.1.5.5.7.1.1", "id-pe-authorityInfoAccess");
+ Reg("1.3.6.1.5.5.7.1.11", "id-pe-subjectInfoAccess");
+ Reg("2.5.29.20", "id-ce-cRLNumber");
+ Reg("2.5.29.28", "id-ce-issuingDistributionPoint");
+ Reg("2.5.29.27", "id-ce-deltaCRLIndicator");
+ Reg("2.5.29.21", "id-ce-cRLReasons");
+ Reg("2.5.29.29", "id-ce-certificateIssuer");
+ Reg("2.5.29.23", "id-ce-holdInstructionCode");
+ Reg("2.2.840.10040.2", "WRONG-holdInstruction");
+ Reg("2.2.840.10040.2.1", "WRONG-id-holdinstruction-none");
+ Reg("2.2.840.10040.2.2", "WRONG-id-holdinstruction-callissuer");
+ Reg("2.2.840.10040.2.3", "WRONG-id-holdinstruction-reject");
+ Reg("2.5.29.24", "id-ce-invalidityDate");
+
+ /*
+ * These are the "right" OID. RFC 5280 mistakenly defines
+ * the first OID element as "2".
+ */
+ Reg("1.2.840.10040.2", "holdInstruction");
+ Reg("1.2.840.10040.2.1", "id-holdinstruction-none");
+ Reg("1.2.840.10040.2.2", "id-holdinstruction-callissuer");
+ Reg("1.2.840.10040.2.3", "id-holdinstruction-reject");
+
+ /*
+ * From PKCS#1.
+ */
+ Reg("1.2.840.113549.1.1", "pkcs-1");
+ Reg("1.2.840.113549.1.1.1", "rsaEncryption");
+ Reg("1.2.840.113549.1.1.7", "id-RSAES-OAEP");
+ Reg("1.2.840.113549.1.1.9", "id-pSpecified");
+ Reg("1.2.840.113549.1.1.10", "id-RSASSA-PSS");
+ Reg("1.2.840.113549.1.1.2", "md2WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.4", "md5WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.5", "sha1WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.11", "sha256WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.12", "sha384WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.13", "sha512WithRSAEncryption");
+ Reg("1.3.14.3.2.26", "id-sha1");
+ Reg("1.2.840.113549.2.2", "id-md2");
+ Reg("1.2.840.113549.2.5", "id-md5");
+ Reg("1.2.840.113549.1.1.8", "id-mgf1");
+
+ /*
+ * From NIST: http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html
+ */
+ Reg("2.16.840.1.101.3", "csor");
+ Reg("2.16.840.1.101.3.4", "nistAlgorithms");
+ Reg("2.16.840.1.101.3.4.0", "csorModules");
+ Reg("2.16.840.1.101.3.4.0.1", "aesModule1");
+
+ Reg("2.16.840.1.101.3.4.1", "aes");
+ Reg("2.16.840.1.101.3.4.1.1", "id-aes128-ECB");
+ Reg("2.16.840.1.101.3.4.1.2", "id-aes128-CBC");
+ Reg("2.16.840.1.101.3.4.1.3", "id-aes128-OFB");
+ Reg("2.16.840.1.101.3.4.1.4", "id-aes128-CFB");
+ Reg("2.16.840.1.101.3.4.1.5", "id-aes128-wrap");
+ Reg("2.16.840.1.101.3.4.1.6", "id-aes128-GCM");
+ Reg("2.16.840.1.101.3.4.1.7", "id-aes128-CCM");
+ Reg("2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad");
+ Reg("2.16.840.1.101.3.4.1.21", "id-aes192-ECB");
+ Reg("2.16.840.1.101.3.4.1.22", "id-aes192-CBC");
+ Reg("2.16.840.1.101.3.4.1.23", "id-aes192-OFB");
+ Reg("2.16.840.1.101.3.4.1.24", "id-aes192-CFB");
+ Reg("2.16.840.1.101.3.4.1.25", "id-aes192-wrap");
+ Reg("2.16.840.1.101.3.4.1.26", "id-aes192-GCM");
+ Reg("2.16.840.1.101.3.4.1.27", "id-aes192-CCM");
+ Reg("2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad");
+ Reg("2.16.840.1.101.3.4.1.41", "id-aes256-ECB");
+ Reg("2.16.840.1.101.3.4.1.42", "id-aes256-CBC");
+ Reg("2.16.840.1.101.3.4.1.43", "id-aes256-OFB");
+ Reg("2.16.840.1.101.3.4.1.44", "id-aes256-CFB");
+ Reg("2.16.840.1.101.3.4.1.45", "id-aes256-wrap");
+ Reg("2.16.840.1.101.3.4.1.46", "id-aes256-GCM");
+ Reg("2.16.840.1.101.3.4.1.47", "id-aes256-CCM");
+ Reg("2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad");
+
+ Reg("2.16.840.1.101.3.4.2", "hashAlgs");
+ Reg("2.16.840.1.101.3.4.2.1", "id-sha256");
+ Reg("2.16.840.1.101.3.4.2.2", "id-sha384");
+ Reg("2.16.840.1.101.3.4.2.3", "id-sha512");
+ Reg("2.16.840.1.101.3.4.2.4", "id-sha224");
+ Reg("2.16.840.1.101.3.4.2.5", "id-sha512-224");
+ Reg("2.16.840.1.101.3.4.2.6", "id-sha512-256");
+
+ Reg("2.16.840.1.101.3.4.3", "sigAlgs");
+ Reg("2.16.840.1.101.3.4.3.1", "id-dsa-with-sha224");
+ Reg("2.16.840.1.101.3.4.3.2", "id-dsa-with-sha256");
+
+ Reg("1.2.840.113549", "rsadsi");
+ Reg("1.2.840.113549.2", "digestAlgorithm");
+ Reg("1.2.840.113549.2.7", "id-hmacWithSHA1");
+ Reg("1.2.840.113549.2.8", "id-hmacWithSHA224");
+ Reg("1.2.840.113549.2.9", "id-hmacWithSHA256");
+ Reg("1.2.840.113549.2.10", "id-hmacWithSHA384");
+ Reg("1.2.840.113549.2.11", "id-hmacWithSHA512");
+
+ /*
+ * From X9.57: http://oid-info.com/get/1.2.840.10040.4
+ */
+ Reg("1.2.840.10040.4", "x9algorithm");
+ Reg("1.2.840.10040.4", "x9cm");
+ Reg("1.2.840.10040.4.1", "dsa");
+ Reg("1.2.840.10040.4.3", "dsa-with-sha1");
+
+ /*
+ * From SEC: http://oid-info.com/get/1.3.14.3.2
+ */
+ Reg("1.3.14.3.2.2", "md4WithRSA");
+ Reg("1.3.14.3.2.3", "md5WithRSA");
+ Reg("1.3.14.3.2.4", "md4WithRSAEncryption");
+ Reg("1.3.14.3.2.12", "dsaSEC");
+ Reg("1.3.14.3.2.13", "dsaWithSHASEC");
+ Reg("1.3.14.3.2.27", "dsaWithSHA1SEC");
+
+ /*
+ * From Microsoft: http://oid-info.com/get/1.3.6.1.4.1.311.20.2
+ */
+ Reg("1.3.6.1.4.1.311.20.2", "ms-certType");
+ Reg("1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon");
+ Reg("1.3.6.1.4.1.311.20.2.3", "ms-UserPrincipalName");
+ Reg("1.3.6.1.4.1.311.20.2.3", "ms-UPN");
+ }
+
+ static void Reg(string oid, string name)
+ {
+ if (!OIDToName.ContainsKey(oid)) {
+ OIDToName.Add(oid, name);
+ }
+ string nn = Normalize(name);
+ if (NameToOID.ContainsKey(nn)) {
+ throw new Exception("OID name collision: " + nn);
+ }
+ NameToOID.Add(nn, oid);
+
+ /*
+ * Many names start with 'id-??-' and we want to support
+ * the short names (without that prefix) as aliases. But
+ * we must take care of some collisions on short names.
+ */
+ if (name.StartsWith("id-")
+ && name.Length >= 7 && name[5] == '-')
+ {
+ if (name.StartsWith("id-ad-")) {
+ Reg(oid, name.Substring(6) + "-IA");
+ } else if (name.StartsWith("id-kp-")) {
+ Reg(oid, name.Substring(6) + "-EKU");
+ } else {
+ Reg(oid, name.Substring(6));
+ }
+ }
+ }
+
+ static string Normalize(string name)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (char c in name) {
+ int d = (int)c;
+ if (d <= 32 || d == '-') {
+ continue;
+ }
+ if (d >= 'A' && d <= 'Z') {
+ d += 'a' - 'A';
+ }
+ sb.Append((char)c);
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Convert an OID in numeric form to its symbolic name, if known.
+ * Otherwise, the provided string is returned as is.
+ */
+ public static string ToName(string oid)
+ {
+ return OIDToName.ContainsKey(oid) ? OIDToName[oid] : oid;
+ }
+
+ /*
+ * Convert a symbolic OID name to its numeric representation. If
+ * the source string is already numeric OID, then it is returned
+ * as is. Otherwise, the symbolic name is looked up; this lookup
+ * ignores case as well as spaces and dash characters. If the name
+ * is not recognized, an AsnException is thrown.
+ */
+ public static string ToOID(string name)
+ {
+ if (IsNumericOID(name)) {
+ return name;
+ }
+ string nn = Normalize(name);
+ if (!NameToOID.ContainsKey(nn)) {
+ throw new AsnException(
+ "unrecognized OID name: " + name);
+ }
+ return NameToOID[nn];
+ }
+
+ /*
+ * Test whether a given string "looks like" an OID in numeric
+ * representation. Criteria applied by this function:
+ * - only decimal ASCII digits and dots
+ * - does not start or end with a dot
+ * - at least one dot
+ * - no two consecutive dots
+ */
+ public static bool IsNumericOID(string oid)
+ {
+ foreach (char c in oid) {
+ if (!(c >= '0' && c <= '9') && c != '.') {
+ return false;
+ }
+ }
+ if (oid.StartsWith(".") || oid.EndsWith(".")) {
+ return false;
+ }
+ if (oid.IndexOf("..") >= 0) {
+ return false;
+ }
+ if (oid.IndexOf('.') < 0) {
+ return false;
+ }
+ return true;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Asn1 {
+
+/*
+ * A PEMObject is a structure that corresponds to a binary object that
+ * was found PEM-encoded in some source stream. The object contents and
+ * PEM type are provided.
+ */
+
+public struct PEMObject {
+
+ public string type;
+ public byte[] data;
+
+ public PEMObject(string type, byte[] data)
+ {
+ this.type = type;
+ this.data = data;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Classic AES implementation, with tables (8->32 lookup tables, four
+ * tables for encryption, four other tables for decryption). This is
+ * relatively efficient, but not constant-time.
+ */
+
+public sealed class AES : BlockCipherCore {
+
+ uint[] skey;
+ uint[] iskey;
+ int rounds;
+
+ /*
+ * Initialize a new instance.
+ */
+ public AES()
+ {
+ skey = new uint[4 * 15];
+ iskey = new uint[skey.Length];
+ }
+
+ /* see IBlockCipher */
+ public override IBlockCipher Dup()
+ {
+ AES a = new AES();
+ Array.Copy(skey, 0, a.skey, 0, skey.Length);
+ Array.Copy(iskey, 0, a.iskey, 0, iskey.Length);
+ a.rounds = rounds;
+ return a;
+ }
+
+ /*
+ * Get the block size in bytes (always 16).
+ */
+ public override int BlockSize {
+ get {
+ return 16;
+ }
+ }
+
+ /*
+ * Set the key (16, 24 or 32 bytes).
+ */
+ public override void SetKey(byte[] key, int off, int len)
+ {
+ switch (len) {
+ case 16:
+ rounds = 10;
+ break;
+ case 24:
+ rounds = 12;
+ break;
+ case 32:
+ rounds = 14;
+ break;
+ default:
+ throw new ArgumentException(
+ "bad AES key length: " + len);
+ }
+ int nk = len >> 2;
+ int nkf = (rounds + 1) << 2;
+ for (int i = 0; i < nk; i ++) {
+ skey[i] = Dec32be(key, off + (i << 2));
+ }
+ for (int i = nk, j = 0, k = 0; i < nkf; i ++) {
+ uint tmp = skey[i - 1];
+ if (j == 0) {
+ tmp = (tmp << 8) | (tmp >> 24);
+ tmp = SubWord(tmp) ^ Rcon[k];
+ } else if (nk > 6 && j == 4) {
+ tmp = SubWord(tmp);
+ }
+ skey[i] = skey[i - nk] ^ tmp;
+ if (++ j == nk) {
+ j = 0;
+ k ++;
+ }
+ }
+
+ /*
+ * Subkeys for decryption (with InvMixColumns() already
+ * applied for the inner rounds).
+ */
+ Array.Copy(skey, 0, iskey, 0, 4);
+ for (int i = 4; i < (rounds << 2); i ++) {
+ uint p = skey[i];
+ uint p0 = p >> 24;
+ uint p1 = (p >> 16) & 0xFF;
+ uint p2 = (p >> 8) & 0xFF;
+ uint p3 = p & 0xFF;
+ uint q0 = mule(p0) ^ mulb(p1) ^ muld(p2) ^ mul9(p3);
+ uint q1 = mul9(p0) ^ mule(p1) ^ mulb(p2) ^ muld(p3);
+ uint q2 = muld(p0) ^ mul9(p1) ^ mule(p2) ^ mulb(p3);
+ uint q3 = mulb(p0) ^ muld(p1) ^ mul9(p2) ^ mule(p3);
+ iskey[i] = (q0 << 24) | (q1 << 16) | (q2 << 8) | q3;
+ }
+ Array.Copy(skey, rounds << 2, iskey, rounds << 2, 4);
+ }
+
+ /* see IBlockCipher */
+ public override void BlockEncrypt(byte[] buf, int off)
+ {
+ uint s0 = Dec32be(buf, off);
+ uint s1 = Dec32be(buf, off + 4);
+ uint s2 = Dec32be(buf, off + 8);
+ uint s3 = Dec32be(buf, off + 12);
+ s0 ^= skey[0];
+ s1 ^= skey[1];
+ s2 ^= skey[2];
+ s3 ^= skey[3];
+ for (int i = 1; i < rounds; i ++) {
+ uint v0 = Ssm0[s0 >> 24]
+ ^ Ssm1[(s1 >> 16) & 0xFF]
+ ^ Ssm2[(s2 >> 8) & 0xFF]
+ ^ Ssm3[s3 & 0xFF];
+ uint v1 = Ssm0[s1 >> 24]
+ ^ Ssm1[(s2 >> 16) & 0xFF]
+ ^ Ssm2[(s3 >> 8) & 0xFF]
+ ^ Ssm3[s0 & 0xFF];
+ uint v2 = Ssm0[s2 >> 24]
+ ^ Ssm1[(s3 >> 16) & 0xFF]
+ ^ Ssm2[(s0 >> 8) & 0xFF]
+ ^ Ssm3[s1 & 0xFF];
+ uint v3 = Ssm0[s3 >> 24]
+ ^ Ssm1[(s0 >> 16) & 0xFF]
+ ^ Ssm2[(s1 >> 8) & 0xFF]
+ ^ Ssm3[s2 & 0xFF];
+ s0 = v0;
+ s1 = v1;
+ s2 = v2;
+ s3 = v3;
+ s0 ^= skey[i << 2];
+ s1 ^= skey[(i << 2) + 1];
+ s2 ^= skey[(i << 2) + 2];
+ s3 ^= skey[(i << 2) + 3];
+ }
+ uint t0 = (S[s0 >> 24] << 24)
+ | (S[(s1 >> 16) & 0xFF] << 16)
+ | (S[(s2 >> 8) & 0xFF] << 8)
+ | S[s3 & 0xFF];
+ uint t1 = (S[s1 >> 24] << 24)
+ | (S[(s2 >> 16) & 0xFF] << 16)
+ | (S[(s3 >> 8) & 0xFF] << 8)
+ | S[s0 & 0xFF];
+ uint t2 = (S[s2 >> 24] << 24)
+ | (S[(s3 >> 16) & 0xFF] << 16)
+ | (S[(s0 >> 8) & 0xFF] << 8)
+ | S[s1 & 0xFF];
+ uint t3 = (S[s3 >> 24] << 24)
+ | (S[(s0 >> 16) & 0xFF] << 16)
+ | (S[(s1 >> 8) & 0xFF] << 8)
+ | S[s2 & 0xFF];
+ s0 = t0 ^ skey[rounds << 2];
+ s1 = t1 ^ skey[(rounds << 2) + 1];
+ s2 = t2 ^ skey[(rounds << 2) + 2];
+ s3 = t3 ^ skey[(rounds << 2) + 3];
+ Enc32be(s0, buf, off);
+ Enc32be(s1, buf, off + 4);
+ Enc32be(s2, buf, off + 8);
+ Enc32be(s3, buf, off + 12);
+ }
+
+ /* see IBlockCipher */
+ public override void BlockDecrypt(byte[] buf, int off)
+ {
+ uint s0 = Dec32be(buf, off);
+ uint s1 = Dec32be(buf, off + 4);
+ uint s2 = Dec32be(buf, off + 8);
+ uint s3 = Dec32be(buf, off + 12);
+ s0 ^= iskey[rounds << 2];
+ s1 ^= iskey[(rounds << 2) + 1];
+ s2 ^= iskey[(rounds << 2) + 2];
+ s3 ^= iskey[(rounds << 2) + 3];
+ for (int i = rounds - 1; i > 0; i --) {
+ uint v0 = iSsm0[s0 >> 24]
+ ^ iSsm1[(s3 >> 16) & 0xFF]
+ ^ iSsm2[(s2 >> 8) & 0xFF]
+ ^ iSsm3[s1 & 0xFF];
+ uint v1 = iSsm0[s1 >> 24]
+ ^ iSsm1[(s0 >> 16) & 0xFF]
+ ^ iSsm2[(s3 >> 8) & 0xFF]
+ ^ iSsm3[s2 & 0xFF];
+ uint v2 = iSsm0[s2 >> 24]
+ ^ iSsm1[(s1 >> 16) & 0xFF]
+ ^ iSsm2[(s0 >> 8) & 0xFF]
+ ^ iSsm3[s3 & 0xFF];
+ uint v3 = iSsm0[s3 >> 24]
+ ^ iSsm1[(s2 >> 16) & 0xFF]
+ ^ iSsm2[(s1 >> 8) & 0xFF]
+ ^ iSsm3[s0 & 0xFF];
+ s0 = v0;
+ s1 = v1;
+ s2 = v2;
+ s3 = v3;
+ s0 ^= iskey[i << 2];
+ s1 ^= iskey[(i << 2) + 1];
+ s2 ^= iskey[(i << 2) + 2];
+ s3 ^= iskey[(i << 2) + 3];
+ }
+ uint t0 = (iS[s0 >> 24] << 24)
+ | (iS[(s3 >> 16) & 0xFF] << 16)
+ | (iS[(s2 >> 8) & 0xFF] << 8)
+ | iS[s1 & 0xFF];
+ uint t1 = (iS[s1 >> 24] << 24)
+ | (iS[(s0 >> 16) & 0xFF] << 16)
+ | (iS[(s3 >> 8) & 0xFF] << 8)
+ | iS[s2 & 0xFF];
+ uint t2 = (iS[s2 >> 24] << 24)
+ | (iS[(s1 >> 16) & 0xFF] << 16)
+ | (iS[(s0 >> 8) & 0xFF] << 8)
+ | iS[s3 & 0xFF];
+ uint t3 = (iS[s3 >> 24] << 24)
+ | (iS[(s2 >> 16) & 0xFF] << 16)
+ | (iS[(s1 >> 8) & 0xFF] << 8)
+ | iS[s0 & 0xFF];
+ s0 = t0 ^ iskey[0];
+ s1 = t1 ^ iskey[1];
+ s2 = t2 ^ iskey[2];
+ s3 = t3 ^ iskey[3];
+ Enc32be(s0, buf, off);
+ Enc32be(s1, buf, off + 4);
+ Enc32be(s2, buf, off + 8);
+ Enc32be(s3, buf, off + 12);
+ }
+
+ static uint Dec32be(byte[] buf, int off)
+ {
+ return ((uint)buf[off] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ }
+
+ static void Enc32be(uint x, byte[] buf, int off)
+ {
+ buf[off] = (byte)(x >> 24);
+ buf[off + 1] = (byte)(x >> 16);
+ buf[off + 2] = (byte)(x >> 8);
+ buf[off + 3] = (byte)x;
+ }
+
+ static uint mul2(uint x)
+ {
+ x <<= 1;
+ return x ^ ((uint)(-(int)(x >> 8)) & 0x11B);
+ }
+
+ static uint mul3(uint x)
+ {
+ return x ^ mul2(x);
+ }
+
+ static uint mul9(uint x)
+ {
+ return x ^ mul2(mul2(mul2(x)));
+ }
+
+ static uint mulb(uint x)
+ {
+ uint x2 = mul2(x);
+ return x ^ x2 ^ mul2(mul2(x2));
+ }
+
+ static uint muld(uint x)
+ {
+ uint x4 = mul2(mul2(x));
+ return x ^ x4 ^ mul2(x4);
+ }
+
+ static uint mule(uint x)
+ {
+ uint x2 = mul2(x);
+ uint x4 = mul2(x2);
+ return x2 ^ x4 ^ mul2(x4);
+ }
+
+ static uint aff(uint x)
+ {
+ x |= x << 8;
+ x ^= (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63;
+ return x & 0xFF;
+ }
+
+ static uint[] Rcon;
+ static uint[] S;
+ static uint[] Ssm0, Ssm1, Ssm2, Ssm3;
+ static uint[] iS;
+ static uint[] iSsm0, iSsm1, iSsm2, iSsm3;
+
+ static uint SubWord(uint x)
+ {
+ return (S[x >> 24] << 24)
+ | (S[(x >> 16) & 0xFF] << 16)
+ | (S[(x >> 8) & 0xFF] << 8)
+ | S[x & 0xFF];
+ }
+
+ static AES()
+ {
+ /*
+ * The Rcon[] constants are used in the key schedule.
+ */
+ Rcon = new uint[10];
+ uint x = 1;
+ for (int i = 0; i < Rcon.Length; i ++) {
+ Rcon[i] = x << 24;
+ x = mul2(x);
+ }
+
+ /*
+ * Generate the map x -> 3^x in GF(2^8). "3" happens to
+ * be a generator for GF(2^8)*, so we get all 255 non-zero
+ * elements.
+ */
+ uint[] pow3 = new uint[255];
+ x = 1;
+ for (int i = 0; i < 255; i ++) {
+ pow3[i] = x;
+ x ^= mul2(x);
+ }
+
+ /*
+ * Compute the log3 map 3^x -> x that maps any non-zero
+ * element in GF(2^8) to its logarithm in base 3 (in the
+ * 0..254 range).
+ */
+ int[] log3 = new int[256];
+ for (int i = 0; i < 255; i ++) {
+ log3[pow3[i]] = i;
+ }
+
+ /*
+ * Compute the S-box.
+ */
+ S = new uint[256];
+ S[0] = aff(0);
+ S[1] = aff(1);
+ for (uint y = 2; y < 0x100; y ++) {
+ S[y] = aff(pow3[255 - log3[y]]);
+ }
+
+ /*
+ * Compute the inverse S-box (for decryption).
+ */
+ iS = new uint[256];
+ for (uint y = 0; y < 0x100; y ++) {
+ iS[S[y]] = y;
+ }
+
+ /*
+ * The Ssm*[] arrays combine SubBytes() and MixColumns():
+ * SsmX[v] is the effect of byte of value v when appearing
+ * on row X.
+ *
+ * The iSsm*[] arrays similarly combine InvSubBytes() and
+ * InvMixColumns(), for decryption.
+ */
+ Ssm0 = new uint[256];
+ Ssm1 = new uint[256];
+ Ssm2 = new uint[256];
+ Ssm3 = new uint[256];
+ iSsm0 = new uint[256];
+ iSsm1 = new uint[256];
+ iSsm2 = new uint[256];
+ iSsm3 = new uint[256];
+ for (uint p = 0; p < 0x100; p ++) {
+ uint q = S[p];
+ Ssm0[p] = (mul2(q) << 24)
+ | (q << 16)
+ | (q << 8)
+ | mul3(q);
+ Ssm1[p] = (mul3(q) << 24)
+ | (mul2(q) << 16)
+ | (q << 8)
+ | q;
+ Ssm2[p] = (q << 24)
+ | (mul3(q) << 16)
+ | (mul2(q) << 8)
+ | q;
+ Ssm3[p] = (q << 24)
+ | (q << 16)
+ | (mul3(q) << 8)
+ | mul2(q);
+ q = iS[p];
+ iSsm0[p] = (mule(q) << 24)
+ | (mul9(q) << 16)
+ | (muld(q) << 8)
+ | mulb(q);
+ iSsm1[p] = (mulb(q) << 24)
+ | (mule(q) << 16)
+ | (mul9(q) << 8)
+ | muld(q);
+ iSsm2[p] = (muld(q) << 24)
+ | (mulb(q) << 16)
+ | (mule(q) << 8)
+ | mul9(q);
+ iSsm3[p] = (mul9(q) << 24)
+ | (muld(q) << 16)
+ | (mulb(q) << 8)
+ | mule(q);
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Helper methods for handling big integers.
+ */
+
+static class BigInt {
+
+ /*
+ * Normalize a big integer to its minimal size encoding
+ * (unsigned big-endian). This function always returns
+ * a new, fresh array.
+ */
+ internal static byte[] NormalizeBE(byte[] val)
+ {
+ return NormalizeBE(val, false);
+ }
+
+ /*
+ * Normalize a big integer to its minimal size encoding
+ * (big-endian). If 'signed' is true, then room for a sign
+ * bit (of value 0) is kept. Note that even if 'signed' is
+ * true, the source array is assumed positive (i.e. unsigned).
+ * This function always returns a new, fresh array.
+ */
+ internal static byte[] NormalizeBE(byte[] val, bool signed)
+ {
+ int n = val.Length;
+ int i = 0;
+ while (i < n && val[i] == 0) {
+ i ++;
+ }
+ if (signed && (i == n || val[i] >= 0x80)) {
+ i --;
+ }
+ byte[] nval = new byte[n - i];
+ if (i < 0) {
+ Array.Copy(val, 0, nval, 1, n);
+ } else {
+ Array.Copy(val, i, nval, 0, n - i);
+ }
+ return nval;
+ }
+
+ /*
+ * Compute the exact bit length of an integer (unsigned big-endian
+ * encoding).
+ */
+ internal static int BitLength(byte[] val)
+ {
+ return BitLength(val, 0, val.Length);
+ }
+
+ /*
+ * Compute the exact bit length of an integer (unsigned big-endian
+ * encoding).
+ */
+ internal static int BitLength(byte[] val, int off, int len)
+ {
+ int tlen = 0;
+ uint hb = 0;
+ uint nf = ~(uint)0;
+ for (int i = 0; i < len; i ++) {
+ int b = (int)(val[off + i] & nf);
+ uint bnz = nf & (uint)((b | -b) >> 31);
+ tlen |= (int)bnz & (len - i);
+ hb |= bnz & (uint)b;
+ nf &= ~bnz;
+ }
+ return (tlen << 3) - 8 + BitLength(hb);
+ }
+
+ /*
+ * Compute the exact bit length of an integer (in a 32-bit word).
+ */
+ internal static int BitLength(uint w)
+ {
+ int bitLen = 0;
+ for (int f = 16; f > 0; f >>= 1) {
+ uint nw = w >> f;
+ int x = (int)nw;
+ uint ctl = (uint)((x | -x) >> 31);
+ w = (w & ~ctl) | (nw & ctl);
+ bitLen += f & (int)ctl;
+ }
+ return bitLen + (int)w;
+ }
+
+ /*
+ * Compute a simple hashcode on an integer. The returned value
+ * depends on the numerical value (assuming that the array is
+ * in unsigned big-endian representation) but not on the presence
+ * of leading zeros. The memory access pattern of this method
+ * depends only on the length of the array x.
+ */
+ public static uint HashInt(byte[] x)
+ {
+ /*
+ * We simply compute x modulo 4294967291 (0xFFFFFFFB),
+ * which is a prime number. The value is injected byte
+ * by byte, and we keep the running state on two words
+ * (16 bits per word, but we allow a few extra bits of
+ * carry).
+ *
+ * For all iterations, "hi" contains at most 16 bits,
+ * and "lo" is less than 16*2^16 (i.e. it fits on 20 bits).
+ */
+ uint hi = 0, lo = 0;
+ for (int i = 0; i < x.Length; i ++) {
+ hi = (hi << 8) + (lo >> 8);
+ lo = (lo & 0xFF) << 8;
+ lo += (uint)5 * (hi >> 16) + (uint)x[i];
+ hi &= 0xFFFF;
+ }
+
+ /*
+ * Final reduction. We first propagate the extra bits
+ * from the low word, which may induce one extra bit
+ * on the high word, which we propagate back.
+ */
+ hi += (lo >> 16);
+ lo &= 0xFFFF;
+ lo += (uint)5 * (hi >> 16);
+ hi &= 0xFFFF;
+
+ /*
+ * If we have extra bits at that point, then this means
+ * that adding a 4-bits-or-less value to "hi" implied
+ * a carry, so now "hi" is small and the addition below
+ * won't imply a carry.
+ */
+ hi += (lo >> 16);
+ lo &= 0xFFFF;
+
+ /*
+ * At that point, value is on 32 bits. We want to do a
+ * final reduction for 0xFFFFFFFB..0xFFFFFFFF. Value is
+ * in this range if and only if 'hi' is 0xFFFF and 'lo'
+ * is at least 0xFFFB.
+ */
+ int z = (int)(((hi + 1) >> 16) | ((lo + 5) >> 16));
+ return (hi << 16) + lo + ((uint)5 & (uint)((z | -z) >> 31));
+ }
+
+ /*
+ * Add two integers together. The two operands a[] and b[]
+ * use big-endian encoding. The returned product array is newly
+ * allocated and normalized. The operands are not modified. The
+ * operands need not be normalized and may be the same array.
+ */
+ public static byte[] Add(byte[] a, byte[] b)
+ {
+ int aLen = a.Length;
+ int bLen = b.Length;
+ int xLen = Math.Max(aLen, bLen) + 1;
+ byte[] x = new byte[xLen];
+ int cc = 0;
+ for (int i = 0; i < xLen; i ++) {
+ int wa = (i < aLen) ? (int)a[aLen - 1 - i] : 0;
+ int wb = (i < bLen) ? (int)b[bLen - 1 - i] : 0;
+ int wx = wa + wb + cc;
+ x[xLen - 1 - i] = (byte)wx;
+ cc = wx >> 8;
+ }
+ return NormalizeBE(x);
+ }
+
+ /*
+ * Subtract integer b[] from integer a[]. Both operands use
+ * big-endian encoding. If b[] turns out to be greater than a[],
+ * then this method returns null.
+ */
+ public static byte[] Sub(byte[] a, byte[] b)
+ {
+ int aLen = a.Length;
+ int bLen = b.Length;
+ int xLen = Math.Max(aLen, bLen);
+ byte[] x = new byte[aLen];
+ int cc = 0;
+ for (int i = 0; i < xLen; i ++) {
+ int wa = (i < aLen) ? (int)a[aLen - 1 - i] : 0;
+ int wb = (i < bLen) ? (int)b[bLen - 1 - i] : 0;
+ int wx = wa - wb - cc;
+ x[xLen - 1 - i] = (byte)wx;
+ cc = (wx >> 8) & 1;
+ }
+ if (cc != 0) {
+ return null;
+ }
+ return NormalizeBE(x);
+ }
+
+ /*
+ * Multiply two integers together. The two operands a[] and b[]
+ * use big-endian encoding. The returned product array is newly
+ * allocated and normalized. The operands are not modified. The
+ * operands need not be normalized and may be the same array.
+ *
+ * The two source operands MUST NOT have length larger than
+ * 32767 bytes.
+ */
+ public static byte[] Mul(byte[] a, byte[] b)
+ {
+ a = NormalizeBE(a);
+ b = NormalizeBE(b);
+ if (a.Length > 32767 || b.Length > 32767) {
+ throw new CryptoException(
+ "Operands too large for multiplication");
+ }
+ int aLen = a.Length;
+ int bLen = b.Length;
+ int xLen = aLen + bLen;
+ uint[] x = new uint[xLen];
+ for (int i = 0; i < aLen; i ++) {
+ uint u = (uint)a[aLen - 1 - i];
+ for (int j = 0; j < bLen; j ++) {
+ x[i + j] += u * (uint)b[bLen - 1 - j];
+ }
+ }
+ byte[] y = new byte[xLen];
+ uint cc = 0;
+ for (int i = 0; i < xLen; i ++) {
+ uint w = x[i] + cc;
+ y[xLen - 1 - i] = (byte)w;
+ cc = w >> 8;
+ }
+ if (cc != 0) {
+ throw new CryptoException(
+ "Multiplication: internal error");
+ }
+ return NormalizeBE(y);
+ }
+
+ /*
+ * Compare two integers (unsigned, big-endian). Returned value
+ * is -1, 0 or 1, depending on whether a[] is lower than, equal
+ * to, or greater then b[]. a[] and b[] may have distinct sizes.
+ *
+ * Memory access pattern depends on the most significant index
+ * (i.e. lowest index, since we use big-endian convention) for
+ * which the two values differ.
+ */
+ public static int Compare(byte[] a, byte[] b)
+ {
+ int na = a.Length;
+ int nb = b.Length;
+ for (int i = Math.Max(a.Length, b.Length); i > 0; i --) {
+ byte xa = (i > na) ? (byte)0x00 : a[na - i];
+ byte xb = (i > nb) ? (byte)0x00 : b[nb - i];
+ if (xa != xb) {
+ return xa < xb ? -1 : 1;
+ }
+ }
+ return 0;
+ }
+
+ /*
+ * Compare two integers (unsigned, big-endian). Returned value
+ * is -1, 0 or 1, depending on whether a[] is lower than, equal
+ * to, or greater then b[]. a[] and b[] may have distinct sizes.
+ *
+ * This method's memory access pattern is independent of the
+ * contents of the a[] and b[] arrays.
+ */
+ public static int CompareCT(byte[] a, byte[] b)
+ {
+ int na = a.Length;
+ int nb = b.Length;
+ uint lt = 0;
+ uint gt = 0;
+ for (int i = Math.Max(a.Length, b.Length); i > 0; i --) {
+ int xa = (i > na) ? 0 : a[na - i];
+ int xb = (i > nb) ? 0 : b[nb - i];
+ lt |= (uint)((xa - xb) >> 31) & ~(lt | gt);
+ gt |= (uint)((xb - xa) >> 31) & ~(lt | gt);
+ }
+ return (int)lt | -(int)gt;
+ }
+
+ /*
+ * Check whether an integer (unsigned, big-endian) is zero.
+ */
+ public static bool IsZero(byte[] x)
+ {
+ return IsZeroCT(x) != 0;
+ }
+
+ /*
+ * Check whether an integer (unsigned, big-endian) is zero.
+ * Memory access pattern depends only on the length of x[].
+ * Returned value is 0xFFFFFFFF is the value is zero, 0x00000000
+ * otherwise.
+ */
+ public static uint IsZeroCT(byte[] x)
+ {
+ int z = 0;
+ for (int i = 0; i < x.Length; i ++) {
+ z |= x[i];
+ }
+ return ~(uint)((z | -z) >> 31);
+ }
+
+ /*
+ * Check whether an integer (unsigned, big-endian) is one.
+ */
+ public static bool IsOne(byte[] x)
+ {
+ return IsOneCT(x) != 0;
+ }
+
+ /*
+ * Check whether an integer (unsigned, big-endian) is one.
+ * Memory access pattern depends only on the length of x[].
+ * Returned value is 0xFFFFFFFF is the value is one, 0x00000000
+ * otherwise.
+ */
+ public static uint IsOneCT(byte[] x)
+ {
+ int n = x.Length;
+ if (n == 0) {
+ return 0x00000000;
+ }
+ int z = 0;
+ for (int i = 0; i < n - 1; i ++) {
+ z |= x[i];
+ }
+ z |= x[n - 1] - 1;
+ return ~(uint)((z | -z) >> 31);
+ }
+
+ /*
+ * Check whether the provided integer is odd (the source integer
+ * is in unsigned big-endian notation).
+ */
+ public static bool IsOdd(byte[] x)
+ {
+ return x.Length > 0 && (x[x.Length - 1] & 0x01) != 0;
+ }
+
+ /*
+ * Compute a modular exponentiation (x^e mod n). Conditions:
+ * -- x[], e[] and n[] use big-endian encoding.
+ * -- x[] must be numerically smaller than n[].
+ * -- n[] must be odd.
+ * Result is returned as a newly allocated array of bytes of
+ * the same length as n[].
+ */
+ public static byte[] ModPow(byte[] x, byte[] e, byte[] n)
+ {
+ ModInt mx = new ModInt(n);
+ mx.Decode(x);
+ mx.Pow(e);
+ return mx.Encode();
+ }
+
+ /*
+ * Create a new random integer, chosen uniformly among integers
+ * modulo the provided max[].
+ */
+ public static byte[] RandInt(byte[] max)
+ {
+ return RandInt(max, false);
+ }
+
+ /*
+ * Create a new random integer, chosen uniformly among non-zero
+ * integers modulo the provided max[].
+ */
+ public static byte[] RandIntNZ(byte[] max)
+ {
+ return RandInt(max, true);
+ }
+
+ static byte[] RandInt(byte[] max, bool notZero)
+ {
+ int mlen = BitLength(max);
+ if (mlen == 0 || (notZero && mlen == 1)) {
+ throw new CryptoException(
+ "Null maximum for random generation");
+ }
+ byte[] x = new byte[(mlen + 7) >> 3];
+ byte hm = (byte)(0xFF >> ((8 - mlen) & 7));
+ for (;;) {
+ RNG.GetBytes(x);
+ x[0] &= hm;
+ if (notZero && IsZero(x)) {
+ continue;
+ }
+ if (CompareCT(x, max) >= 0) {
+ continue;
+ }
+ return x;
+ }
+ }
+
+ /*
+ * Create a new random prime with a specific length (in bits). The
+ * returned prime will have its two top bits set, _and_ its two
+ * least significant bits set as well. The size parameter must be
+ * greater than or equal to 9 (that is, the unsigned encoding of
+ * the prime will need at least two bytes).
+ */
+ public static byte[] RandPrime(int size)
+ {
+ if (size < 9) {
+ throw new CryptoException(
+ "Invalid size for prime generation");
+ }
+ int len = (size + 7) >> 3;
+ byte[] buf = new byte[len];
+ int hm1 = 0xFFFF >> ((len << 3) - size);
+ int hm2 = 0xC000 >> ((len << 3) - size);
+ for (;;) {
+ RNG.GetBytes(buf);
+ buf[len - 1] |= (byte)0x03;
+ int x = (buf[0] << 8) | buf[1];
+ x &= hm1;
+ x |= hm2;
+ buf[0] = (byte)(x >> 8);
+ buf[1] = (byte)x;
+ if (IsPrime(buf)) {
+ return buf;
+ }
+ }
+ }
+
+ /*
+ * A bit-field for primes in the 0..255 range.
+ */
+ static uint[] SMALL_PRIMES_BF = {
+ 0xA08A28AC, 0x28208A20, 0x02088288, 0x800228A2,
+ 0x20A00A08, 0x80282088, 0x800800A2, 0x08028228
+ };
+
+ static bool IsSmallPrime(int x)
+ {
+ if (x < 2 || x >= 256) {
+ return false;
+ }
+ return ((SMALL_PRIMES_BF[x >> 5] >> (x & 31)) & (uint)1) != 0;
+ }
+
+ /*
+ * Test an integer for primality. This function runs up to 50
+ * Miller-Rabin rounds, which is a lot of overkill but ensures
+ * that non-primes will be reliably detected (with overwhelming
+ * probability) even with maliciously crafted inputs. "Normal"
+ * non-primes will be detected most of the time at the first
+ * iteration.
+ *
+ * This function is not constant-time.
+ */
+ public static bool IsPrime(byte[] x)
+ {
+ x = NormalizeBE(x);
+
+ /*
+ * Handle easy cases:
+ * 0 is not prime
+ * small primes (one byte) are known in a constant bit-field
+ * even numbers (larger than one byte) are non-primes
+ */
+ if (x.Length == 0) {
+ return false;
+ }
+ if (x.Length == 1) {
+ return IsSmallPrime(x[0]);
+ }
+ if ((x[x.Length - 1] & 0x01) == 0) {
+ return false;
+ }
+
+ /*
+ * Perform some trial divisions by small primes.
+ */
+ for (int sp = 3; sp < 256; sp += 2) {
+ if (!IsSmallPrime(sp)) {
+ continue;
+ }
+ int z = 0;
+ foreach (byte b in x) {
+ z = ((z << 8) + b) % sp;
+ }
+ if (z == 0) {
+ return false;
+ }
+ }
+
+ /*
+ * Run some Miller-Rabin rounds. We use as basis random
+ * integers that are one byte smaller than the modulus.
+ */
+ ModInt xm1 = new ModInt(x);
+ ModInt y = xm1.Dup();
+ y.Set(1);
+ xm1.Sub(y);
+ byte[] e = xm1.Encode();
+ ModInt a = new ModInt(x);
+ byte[] buf = new byte[x.Length - 1];
+ for (int i = 0; i < 50; i ++) {
+ RNG.GetBytes(buf);
+ a.Decode(buf);
+ a.Pow(e);
+ if (!a.IsOne) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Right-shift an array of bytes by some bits. The bit count MUST
+ * be positive or zero. Extra bits are dropped on the right, and
+ * left positions are filled with zeros.
+ */
+ public static void RShift(byte[] buf, int numBits)
+ {
+ RShift(buf, 0, buf.Length, numBits);
+ }
+
+ /*
+ * Right-shift an array of bytes by some bits. The bit count MUST
+ * be positive or zero. Extra bits are dropped on the right, and
+ * left positions are filled with zeros.
+ */
+ public static void RShift(byte[] buf, int off, int len, int numBits)
+ {
+ if (numBits >= 8) {
+ int zlen = numBits >> 3;
+ if (zlen >= len) {
+ for (int i = 0; i < len; i ++) {
+ buf[off + i] = 0;
+ }
+ return;
+ }
+ Array.Copy(buf, off, buf, off + zlen, len - zlen);
+ for (int i = 0; i < zlen; i ++) {
+ buf[off + i] = 0;
+ }
+ off += zlen;
+ len -= zlen;
+ numBits &= 7;
+ }
+
+ int cc = 0;
+ for (int i = 0; i < len; i ++) {
+ int x = buf[off + i];
+ buf[off + i] = (byte)((x >> numBits) + cc);
+ cc = x << (8 - numBits);
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class is a convenient base class for implementations of
+ * IBlockCipher. Block cipher implementations must implement:
+ * int BlockSize { get; }
+ * void SetKey(byte[] key, int off, int len)
+ * void BlockEncrypt(byte[] data, int off)
+ * void BlockDecrypt(byte[] data, int off)
+ *
+ * Note that 'BlockSize' is invoked from the constructor of this class.
+ *
+ * Implementations MAY also override the default implementations of:
+ * void CBCEncrypt(byte[] iv, byte[] data)
+ * void CBCEncrypt(byte[] iv, byte[] data, int off, int len)
+ * void CBCDecrypt(byte[] iv, byte[] data)
+ * void CBCDecrypt(byte[] iv, byte[] data, int off, int len)
+ * uint CTRRun(byte[] iv, uint cc, byte[] data)
+ * uint CTRRun(byte[] iv, uint cc, byte[] data, int off, int len)
+ * Note that CBCEncrypt(byte[],byte[]) (respectively
+ * CBCDecrypt(byte[],byte[]) and CTRRun(byte[],uint,byte[])) simply
+ * calls CBCEncrypt(byte[],byte[],int,int) (respectively
+ * CBCDecrypt(byte[],byte[],int,int) and
+ * CTRRun(byte[],uint,byte[],int,int)) so implementations who wish to
+ * override these methods may content themselves with overriding the two
+ * methods with the "off" and "len" extra parameters.
+ */
+
+public abstract class BlockCipherCore : IBlockCipher {
+
+ byte[] tmp;
+
+ /*
+ * This constructor invokes 'BlockSize'.
+ */
+ public BlockCipherCore()
+ {
+ tmp = new byte[BlockSize];
+ }
+
+ /* see IBlockCipher */
+ public abstract int BlockSize { get; }
+
+ /*
+ * This method is implemented by calling SetKey(byte[],int,int).
+ */
+ public virtual void SetKey(byte[] key)
+ {
+ SetKey(key, 0, key.Length);
+ }
+
+ /* see IBlockCipher */
+ public abstract void SetKey(byte[] key, int off, int len);
+
+ /*
+ * This method is implemented by calling BlockEncrypt(byte[],int).
+ */
+ public virtual void BlockEncrypt(byte[] buf)
+ {
+ BlockEncrypt(buf, 0);
+ }
+
+ /* see IBlockCipher */
+ public abstract void BlockEncrypt(byte[] data, int off);
+
+ /*
+ * This method is implemented by calling BlockDecrypt(byte[],int).
+ */
+ public virtual void BlockDecrypt(byte[] buf)
+ {
+ BlockDecrypt(buf, 0);
+ }
+
+ /* see IBlockCipher */
+ public abstract void BlockDecrypt(byte[] data, int off);
+
+ /*
+ * This method is implemented by calling
+ * CBCEncrypt(byte[],byte[],int,int).
+ */
+ public virtual void CBCEncrypt(byte[] iv, byte[] data)
+ {
+ CBCEncrypt(iv, data, 0, data.Length);
+ }
+
+ /* see IBlockCipher */
+ public virtual void CBCEncrypt(
+ byte[] iv, byte[] data, int off, int len)
+ {
+ int blen = BlockSize;
+ if (iv.Length != blen) {
+ throw new CryptoException("wrong IV length");
+ }
+ if (len >= blen) {
+ for (int i = 0; i < blen; i ++) {
+ data[off + i] ^= iv[i];
+ }
+ BlockEncrypt(data, off);
+ off += blen;
+ len -= blen;
+ while (len >= blen) {
+ for (int i = 0; i < blen; i ++) {
+ data[off + i] ^= data[off + i - blen];
+ }
+ BlockEncrypt(data, off);
+ off += blen;
+ len -= blen;
+ }
+ }
+ if (len != 0) {
+ throw new CryptoException("data length is not"
+ + " multiple of the block size");
+ }
+ }
+
+ /*
+ * This method is implemented by calling
+ * CBCDecrypt(byte[],byte[],int,int).
+ */
+ public virtual void CBCDecrypt(byte[] iv, byte[] data)
+ {
+ CBCDecrypt(iv, data, 0, data.Length);
+ }
+
+ /* see IBlockCipher */
+ public virtual void CBCDecrypt(
+ byte[] iv, byte[] data, int off, int len)
+ {
+ int blen = BlockSize;
+ if (iv.Length != blen) {
+ throw new CryptoException("wrong IV length");
+ }
+ int dblen = blen << 1;
+ off += len;
+ while (len >= dblen) {
+ off -= blen;
+ BlockDecrypt(data, off);
+ for (int i = 0; i < blen; i ++) {
+ data[off + i] ^= data[off + i - blen];
+ }
+ len -= blen;
+ }
+ if (len >= blen) {
+ off -= blen;
+ BlockDecrypt(data, off);
+ for (int i = 0; i < blen; i ++) {
+ data[off + i] ^= iv[i];
+ }
+ len -= blen;
+ }
+ if (len != 0) {
+ throw new CryptoException("data length is not"
+ + " multiple of the block size");
+ }
+ }
+
+ /*
+ * This method is implemented by calling
+ * CTRRun(byte[],uint,byte[],int,int).
+ */
+ public virtual uint CTRRun(byte[] iv, uint cc, byte[] data)
+ {
+ return CTRRun(iv, cc, data, 0, data.Length);
+ }
+
+ /* see IBlockCipher */
+ public virtual uint CTRRun(
+ byte[] iv, uint cc, byte[] data, int off, int len)
+ {
+ int blen = BlockSize;
+ if (iv.Length != blen - 4) {
+ throw new CryptoException("wrong IV length");
+ }
+ while (len > 0) {
+ Array.Copy(iv, 0, tmp, 0, blen - 4);
+ tmp[blen - 4] = (byte)(cc >> 24);
+ tmp[blen - 3] = (byte)(cc >> 16);
+ tmp[blen - 2] = (byte)(cc >> 8);
+ tmp[blen - 1] = (byte)cc;
+ BlockEncrypt(tmp, 0);
+ int clen = Math.Min(blen, len);
+ for (int i = 0; i < clen; i ++) {
+ data[off + i] ^= tmp[i];
+ }
+ off += clen;
+ len -= clen;
+ cc ++;
+ }
+ return cc;
+ }
+
+ /* see IBlockCipher */
+ public abstract IBlockCipher Dup();
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * ChaCha20 implementation.
+ */
+
+public sealed class ChaCha20 {
+
+ uint k0, k1, k2, k3, k4, k5, k6, k7;
+
+ const uint CW0 = 0x61707865;
+ const uint CW1 = 0x3320646E;
+ const uint CW2 = 0x79622D32;
+ const uint CW3 = 0x6B206574;
+
+ /*
+ * Initialize a new instance.
+ */
+ public ChaCha20()
+ {
+ }
+
+ /*
+ * Set the key (32 bytes).
+ */
+ public void SetKey(byte[] key)
+ {
+ SetKey(key, 0, key.Length);
+ }
+
+ /*
+ * Set the key (32 bytes).
+ */
+ public void SetKey(byte[] key, int off, int len)
+ {
+ if (len != 32) {
+ throw new ArgumentException(
+ "bad ChaCha20 key length: " + len);
+ }
+ k0 = Dec32le(key, off + 0);
+ k1 = Dec32le(key, off + 4);
+ k2 = Dec32le(key, off + 8);
+ k3 = Dec32le(key, off + 12);
+ k4 = Dec32le(key, off + 16);
+ k5 = Dec32le(key, off + 20);
+ k6 = Dec32le(key, off + 24);
+ k7 = Dec32le(key, off + 28);
+ }
+
+ /*
+ * Encrypt (or decrypt) some bytes. The current block counter
+ * is provided, and the new block counter value is returned.
+ * Each block is 64 bytes; if the data length is not a multiple
+ * of 64, then the extra bytes from the last block are dropped;
+ * thus, a long stream of bytes can be encrypted or decrypted
+ * in several calls, as long as all calls (except possibly the
+ * last) provide a length that is a multiple of 64.
+ *
+ * IV must be exactly 12 bytes.
+ */
+ public uint Run(byte[] iv, uint cc, byte[] data)
+ {
+ return Run(iv, cc, data, 0, data.Length);
+ }
+
+ /*
+ * Encrypt (or decrypt) some bytes. The current block counter
+ * is provided, and the new block counter value is returned.
+ * Each block is 64 bytes; if the data length is not a multiple
+ * of 64, then the extra bytes from the last block are dropped;
+ * thus, a long stream of bytes can be encrypted or decrypted
+ * in several calls, as long as all calls (except possibly the
+ * last) provide a length that is a multiple of 64.
+ *
+ * IV must be exactly 12 bytes.
+ */
+ public uint Run(byte[] iv, uint cc, byte[] data, int off, int len)
+ {
+ uint iv0 = Dec32le(iv, 0);
+ uint iv1 = Dec32le(iv, 4);
+ uint iv2 = Dec32le(iv, 8);
+ while (len > 0) {
+ uint s0, s1, s2, s3, s4, s5, s6, s7;
+ uint s8, s9, sA, sB, sC, sD, sE, sF;
+
+ s0 = CW0;
+ s1 = CW1;
+ s2 = CW2;
+ s3 = CW3;
+ s4 = k0;
+ s5 = k1;
+ s6 = k2;
+ s7 = k3;
+ s8 = k4;
+ s9 = k5;
+ sA = k6;
+ sB = k7;
+ sC = cc;
+ sD = iv0;
+ sE = iv1;
+ sF = iv2;
+
+ for (int i = 0; i < 10; i ++) {
+ s0 += s4;
+ sC ^= s0;
+ sC = (sC << 16) | (sC >> 16);
+ s8 += sC;
+ s4 ^= s8;
+ s4 = (s4 << 12) | (s4 >> 20);
+ s0 += s4;
+ sC ^= s0;
+ sC = (sC << 8) | (sC >> 24);
+ s8 += sC;
+ s4 ^= s8;
+ s4 = (s4 << 7) | (s4 >> 25);
+
+ s1 += s5;
+ sD ^= s1;
+ sD = (sD << 16) | (sD >> 16);
+ s9 += sD;
+ s5 ^= s9;
+ s5 = (s5 << 12) | (s5 >> 20);
+ s1 += s5;
+ sD ^= s1;
+ sD = (sD << 8) | (sD >> 24);
+ s9 += sD;
+ s5 ^= s9;
+ s5 = (s5 << 7) | (s5 >> 25);
+
+ s2 += s6;
+ sE ^= s2;
+ sE = (sE << 16) | (sE >> 16);
+ sA += sE;
+ s6 ^= sA;
+ s6 = (s6 << 12) | (s6 >> 20);
+ s2 += s6;
+ sE ^= s2;
+ sE = (sE << 8) | (sE >> 24);
+ sA += sE;
+ s6 ^= sA;
+ s6 = (s6 << 7) | (s6 >> 25);
+
+ s3 += s7;
+ sF ^= s3;
+ sF = (sF << 16) | (sF >> 16);
+ sB += sF;
+ s7 ^= sB;
+ s7 = (s7 << 12) | (s7 >> 20);
+ s3 += s7;
+ sF ^= s3;
+ sF = (sF << 8) | (sF >> 24);
+ sB += sF;
+ s7 ^= sB;
+ s7 = (s7 << 7) | (s7 >> 25);
+
+ s0 += s5;
+ sF ^= s0;
+ sF = (sF << 16) | (sF >> 16);
+ sA += sF;
+ s5 ^= sA;
+ s5 = (s5 << 12) | (s5 >> 20);
+ s0 += s5;
+ sF ^= s0;
+ sF = (sF << 8) | (sF >> 24);
+ sA += sF;
+ s5 ^= sA;
+ s5 = (s5 << 7) | (s5 >> 25);
+
+ s1 += s6;
+ sC ^= s1;
+ sC = (sC << 16) | (sC >> 16);
+ sB += sC;
+ s6 ^= sB;
+ s6 = (s6 << 12) | (s6 >> 20);
+ s1 += s6;
+ sC ^= s1;
+ sC = (sC << 8) | (sC >> 24);
+ sB += sC;
+ s6 ^= sB;
+ s6 = (s6 << 7) | (s6 >> 25);
+
+ s2 += s7;
+ sD ^= s2;
+ sD = (sD << 16) | (sD >> 16);
+ s8 += sD;
+ s7 ^= s8;
+ s7 = (s7 << 12) | (s7 >> 20);
+ s2 += s7;
+ sD ^= s2;
+ sD = (sD << 8) | (sD >> 24);
+ s8 += sD;
+ s7 ^= s8;
+ s7 = (s7 << 7) | (s7 >> 25);
+
+ s3 += s4;
+ sE ^= s3;
+ sE = (sE << 16) | (sE >> 16);
+ s9 += sE;
+ s4 ^= s9;
+ s4 = (s4 << 12) | (s4 >> 20);
+ s3 += s4;
+ sE ^= s3;
+ sE = (sE << 8) | (sE >> 24);
+ s9 += sE;
+ s4 ^= s9;
+ s4 = (s4 << 7) | (s4 >> 25);
+ }
+
+ s0 += CW0;
+ s1 += CW1;
+ s2 += CW2;
+ s3 += CW3;
+ s4 += k0;
+ s5 += k1;
+ s6 += k2;
+ s7 += k3;
+ s8 += k4;
+ s9 += k5;
+ sA += k6;
+ sB += k7;
+ sC += cc;
+ sD += iv0;
+ sE += iv1;
+ sF += iv2;
+
+ int limit = off + len;
+ Xor32le(s0, data, off + 0, limit);
+ Xor32le(s1, data, off + 4, limit);
+ Xor32le(s2, data, off + 8, limit);
+ Xor32le(s3, data, off + 12, limit);
+ Xor32le(s4, data, off + 16, limit);
+ Xor32le(s5, data, off + 20, limit);
+ Xor32le(s6, data, off + 24, limit);
+ Xor32le(s7, data, off + 28, limit);
+ Xor32le(s8, data, off + 32, limit);
+ Xor32le(s9, data, off + 36, limit);
+ Xor32le(sA, data, off + 40, limit);
+ Xor32le(sB, data, off + 44, limit);
+ Xor32le(sC, data, off + 48, limit);
+ Xor32le(sD, data, off + 52, limit);
+ Xor32le(sE, data, off + 56, limit);
+ Xor32le(sF, data, off + 60, limit);
+
+ off += 64;
+ len -= 64;
+ cc ++;
+ }
+ return cc;
+ }
+
+ static uint Dec32le(byte[] buf, int off)
+ {
+ return (uint)buf[off]
+ | ((uint)buf[off + 1] << 8)
+ | ((uint)buf[off + 2] << 16)
+ | ((uint)buf[off + 3] << 24);
+ }
+
+ static void Xor32le(uint x, byte[] buf, int off, int limit)
+ {
+ if (off + 4 <= limit) {
+ buf[off] ^= (byte)x;
+ buf[off + 1] ^= (byte)(x >> 8);
+ buf[off + 2] ^= (byte)(x >> 16);
+ buf[off + 3] ^= (byte)(x >> 24);
+ } else {
+ if (off + 2 <= limit) {
+ if (off + 3 <= limit) {
+ buf[off] ^= (byte)x;
+ buf[off + 1] ^= (byte)(x >> 8);
+ buf[off + 2] ^= (byte)(x >> 16);
+ } else {
+ buf[off] ^= (byte)x;
+ buf[off + 1] ^= (byte)(x >> 8);
+ }
+ } else {
+ if (off + 1 <= limit) {
+ buf[off] ^= (byte)x;
+ }
+ }
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * A basic exception class, to be thrown when a cryptographic
+ * algorithm encounters an unfixable issue.
+ */
+
+public class CryptoException : Exception {
+
+ public CryptoException(string msg) : base(msg)
+ {
+ }
+
+ public CryptoException(string msg, Exception e) : base(msg, e)
+ {
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Perfunctory implementation of the Triple-DES block cipher (also works
+ * as "single DES" when used with an 8-byte DES key). It only processes
+ * single blocks, and does it "in place" (the input block is replaced
+ * with the output block in the same array). This code is a direct
+ * translation of the specification and is not optimized for speed.
+ *
+ * Supported key sizes are 8, 16 and 24 bytes. When an 8-byte key is
+ * used, this is the original DES (64-bit key, out of which 8 are
+ * unused, so the "true key size" is 56 bits). When a 16-byte or 24-byte
+ * key is used, this is Triple-DES (with a 16-byte key, the key is
+ * expanded to 24 bytes by reusing bytes 0..7 as bytes 16..23 of the
+ * key).
+ *
+ * Instances are not thread-safe; however, distinct threads may use
+ * distinct instances concurrently.
+ *
+ * Since instances are pure managed code, there is no need for explicit
+ * disposal after usage.
+ */
+
+public sealed class DES : BlockCipherCore {
+
+ ulong[] skey;
+ int rounds;
+
+ /*
+ * Initialize a new instance.
+ */
+ public DES()
+ {
+ skey = new ulong[48];
+ }
+
+ /* see IBlockCipher */
+ public override IBlockCipher Dup()
+ {
+ DES d = new DES();
+ Array.Copy(skey, 0, d.skey, 0, skey.Length);
+ d.rounds = rounds;
+ return d;
+ }
+
+ /*
+ * Get the block size in bytes (always 8).
+ */
+ public override int BlockSize {
+ get {
+ return 8;
+ }
+ }
+
+ /*
+ * Set the key (8, 16 or 24 bytes).
+ */
+ public override void SetKey(byte[] key, int off, int len)
+ {
+ switch (len) {
+ case 8:
+ KeySchedule(key, off, 0);
+ rounds = 1;
+ break;
+ case 16:
+ KeySchedule(key, off, 0);
+ KeySchedule(key, off + 8, 16);
+ KeySchedule(key, off, 32);
+ rounds = 3;
+ break;
+ case 24:
+ KeySchedule(key, off, 0);
+ KeySchedule(key, off + 8, 16);
+ KeySchedule(key, off + 16, 32);
+ rounds = 3;
+ break;
+ default:
+ throw new ArgumentException(
+ "bad DES/3DES key length: " + len);
+ }
+
+ /*
+ * Inverse order of subkeys for the middle-DES (3DES
+ * uses the "EDE" configuration).
+ */
+ for (int j = 16; j < 24; j ++) {
+ ulong w = skey[j];
+ skey[j] = skey[47 - j];
+ skey[47 - j] = w;
+ }
+ }
+
+ void KeySchedule(byte[] key, int off, int skeyOff)
+ {
+ ulong k = Dec64be(key, off);
+ k = Perm(tabPC1, k);
+ ulong kl = k >> 28;
+ ulong kr = k & 0x0FFFFFFF;
+ for (int i = 0; i < 16; i ++) {
+ int r = rotK[i];
+ kl = ((kl << r) | (kl >> (28 - r))) & 0x0FFFFFFF;
+ kr = ((kr << r) | (kr >> (28 - r))) & 0x0FFFFFFF;
+ skey[skeyOff + i] = Perm(tabPC2, (kl << 28) | kr);
+ }
+ }
+
+ /*
+ * Encrypt one block; the block consists in the 8 bytes beginning
+ * at offset 'off'. Other bytes in the array are unaltered.
+ */
+ public override void BlockEncrypt(byte[] buf, int off)
+ {
+ if (rounds == 0) {
+ throw new Exception("no key provided");
+ }
+ ulong x = Dec64be(buf, off);
+ x = DoIP(x);
+ uint xl = (uint)(x >> 32);
+ uint xr = (uint)x;
+ for (int i = 0, k = 0; i < rounds; i ++) {
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ xr ^= FConf(xl, skey[k ++]);
+ xl ^= FConf(xr, skey[k ++]);
+ uint tmp = xr ^ FConf(xl, skey[k ++]);
+ xr = xl;
+ xl = tmp;
+ }
+ x = ((ulong)xl << 32) | (ulong)xr;
+ x = DoIPInv(x);
+ Enc64be(x, buf, off);
+ }
+
+ /*
+ * Decrypt one block; the block consists in the 8 bytes beginning
+ * at offset 'off'. Other bytes in the array are unaltered.
+ */
+ public override void BlockDecrypt(byte[] buf, int off)
+ {
+ if (rounds == 0) {
+ throw new Exception("no key provided");
+ }
+ ulong x = Dec64be(buf, off);
+ x = DoIP(x);
+ uint xl = (uint)(x >> 32);
+ uint xr = (uint)x;
+ for (int i = 0, k = rounds << 4; i < rounds; i ++) {
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ xr ^= FConf(xl, skey[-- k]);
+ xl ^= FConf(xr, skey[-- k]);
+ uint tmp = xr ^ FConf(xl, skey[-- k]);
+ xr = xl;
+ xl = tmp;
+ }
+ x = ((ulong)xl << 32) | (ulong)xr;
+ x = DoIPInv(x);
+ Enc64be(x, buf, off);
+ }
+
+ /*
+ * Arrays below are extracted exactly from FIPS 46-3. They use
+ * the conventions defined in that document:
+ *
+ * -- Bits are numbered from 1, in the left-to-right order; in
+ * a 64-bit integer, the most significant (leftmost) bit is 1,
+ * while the least significant (rightmost) bit is 64.
+ *
+ * -- For each permutation (or extraction), the defined array
+ * lists the source index of each bit.
+ *
+ * -- For the S-boxes, bits 1 (leftmost) and 6 (rightmost) select
+ * the row, and bits 2 to 5 select the index within the row.
+ */
+
+ static uint[,] Sdef = {
+ {
+ 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+ 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+ 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+ 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
+ }, {
+ 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+ 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+ 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+ 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
+ }, {
+ 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+ 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+ 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+ 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
+ }, {
+ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+ 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+ 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+ 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
+ }, {
+ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+ 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+ 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+ 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
+ }, {
+ 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+ 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+ 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+ 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
+ }, {
+ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+ 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+ 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+ 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
+ }, {
+ 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+ 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+ 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+ 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
+ }
+ };
+
+ static int[] defP = {
+ 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
+ 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25
+ };
+
+ static int[] defPC1 = {
+ 57, 49, 41, 33, 25, 17, 9,
+ 1, 58, 50, 42, 34, 26, 18,
+ 10, 2, 59, 51, 43, 35, 27,
+ 19, 11, 3, 60, 52, 44, 36,
+ 63, 55, 47, 39, 31, 23, 15,
+ 7, 62, 54, 46, 38, 30, 22,
+ 14, 6, 61, 53, 45, 37, 29,
+ 21, 13, 5, 28, 20, 12, 4
+ };
+
+ static int[] defPC2 = {
+ 14, 17, 11, 24, 1, 5,
+ 3, 28, 15, 6, 21, 10,
+ 23, 19, 12, 4, 26, 8,
+ 16, 7, 27, 20, 13, 2,
+ 41, 52, 31, 37, 47, 55,
+ 30, 40, 51, 45, 33, 48,
+ 44, 49, 39, 56, 34, 53,
+ 46, 42, 50, 36, 29, 32
+ };
+
+ static int[] rotK = {
+ 1, 1, 2, 2, 2, 2, 2, 2,
+ 1, 2, 2, 2, 2, 2, 2, 1
+ };
+
+ /*
+ * Permutations (and extractions) are implemented with the
+ * following tables, that are initialized from the class
+ * initialization code. The representation of a permutation is
+ * an array of words, where word at index k is a one-bit mask
+ * that identifies the source bit that should go at position k
+ * in the output. The "k" index here is in right-to-left
+ * convention: least significant bit (rightmost) is numbered 0.
+ *
+ * The Perm() method applies a permutation, using one of the
+ * tab*[] arrays.
+ *
+ * The S*[] arrays contain the S-boxes, with the permutation P
+ * effect merged in, and expecting their 6-bit input "as is".
+ */
+ static ulong[] tabPC1;
+ static ulong[] tabPC2;
+
+ static uint[] S1, S2, S3, S4, S5, S6, S7, S8;
+
+ static DES()
+ {
+ tabPC1 = new ulong[defPC1.Length];
+ for (int i = 0; i < defPC1.Length; i ++) {
+ tabPC1[55 - i] = (ulong)1 << (64 - defPC1[i]);
+ }
+ tabPC2 = new ulong[defPC2.Length];
+ for (int i = 0; i < defPC2.Length; i ++) {
+ tabPC2[47 - i] = (ulong)1 << (56 - defPC2[i]);
+ }
+
+ int[] PInv = new int[32];
+ for (int i = 0; i < defP.Length; i ++) {
+ PInv[32 - defP[i]] = 31 - i;
+ }
+ S1 = MakeSbox(PInv, 0);
+ S2 = MakeSbox(PInv, 1);
+ S3 = MakeSbox(PInv, 2);
+ S4 = MakeSbox(PInv, 3);
+ S5 = MakeSbox(PInv, 4);
+ S6 = MakeSbox(PInv, 5);
+ S7 = MakeSbox(PInv, 6);
+ S8 = MakeSbox(PInv, 7);
+ }
+
+ static uint[] MakeSbox(int[] PInv, int i)
+ {
+ uint[] S = new uint[64];
+ for (int j = 0; j < 64; j ++) {
+ int idx = ((j & 0x01) << 4) | (j & 0x20)
+ | ((j & 0x1E) >> 1);
+ uint zb = Sdef[i, idx];
+ uint za = 0;
+ for (int k = 0; k < 4; k ++) {
+ za |= ((zb >> k) & 1)
+ << PInv[((7 - i) << 2) + k];
+ }
+ S[j] = za;
+ }
+ return S;
+ }
+
+ static ulong IPStep(ulong x, int size, ulong mask)
+ {
+ uint left = (uint)(x >> 32);
+ uint right = (uint)x;
+ uint tmp = ((left >> size) ^ right) & (uint)mask;
+ right ^= tmp;
+ left ^= tmp << size;
+ return ((ulong)left << 32) | (ulong)right;
+ }
+
+ static ulong DoIP(ulong x)
+ {
+ /*
+ * Permutation algorithm is initially from Richard
+ * Outerbridge; this implementation has been adapted
+ * from Crypto++ "des.cpp" file (which is in public
+ * domain).
+ */
+ uint l = (uint)(x >> 32);
+ uint r = (uint)x;
+ uint t;
+ t = ((l >> 4) ^ r) & 0x0F0F0F0F;
+ r ^= t;
+ l ^= t << 4;
+ t = ((l >> 16) ^ r) & 0x0000FFFF;
+ r ^= t;
+ l ^= t << 16;
+ t = ((r >> 2) ^ l) & 0x33333333;
+ l ^= t;
+ r ^= t << 2;
+ t = ((r >> 8) ^ l) & 0x00FF00FF;
+ l ^= t;
+ r ^= t << 8;
+ t = ((l >> 1) ^ r) & 0x55555555;
+ r ^= t;
+ l ^= t << 1;
+ x = ((ulong)l << 32) | (ulong)r;
+ return x;
+ }
+
+ static ulong DoIPInv(ulong x)
+ {
+ /*
+ * See DoIP().
+ */
+ uint l = (uint)(x >> 32);
+ uint r = (uint)x;
+ uint t;
+ t = ((l >> 1) ^ r) & 0x55555555;
+ r ^= t;
+ l ^= t << 1;
+ t = ((r >> 8) ^ l) & 0x00FF00FF;
+ l ^= t;
+ r ^= t << 8;
+ t = ((r >> 2) ^ l) & 0x33333333;
+ l ^= t;
+ r ^= t << 2;
+ t = ((l >> 16) ^ r) & 0x0000FFFF;
+ r ^= t;
+ l ^= t << 16;
+ t = ((l >> 4) ^ r) & 0x0F0F0F0F;
+ r ^= t;
+ l ^= t << 4;
+ x = ((ulong)l << 32) | (ulong)r;
+ return x;
+ }
+
+ /*
+ * Apply a permutation or extraction. For all k, bit k of the
+ * output (right-to-left numbering) is set if and only if the
+ * source bit in x defined by the tab[k] mask is set.
+ */
+ static ulong Perm(ulong[] tab, ulong x)
+ {
+ ulong y = 0;
+ for (int i = 0; i < tab.Length; i ++) {
+ if ((x & tab[i]) != 0) {
+ y |= (ulong)1 << i;
+ }
+ }
+ return y;
+ }
+
+ static uint FConf(uint r0, ulong sk)
+ {
+ uint skhi = (uint)(sk >> 24);
+ uint sklo = (uint)sk;
+ uint r1 = (r0 >> 16) | (r0 << 16);
+ return
+ S1[((r1 >> 11) ^ (skhi >> 18)) & 0x3F]
+ | S2[((r0 >> 23) ^ (skhi >> 12)) & 0x3F]
+ | S3[((r0 >> 19) ^ (skhi >> 6)) & 0x3F]
+ | S4[((r0 >> 15) ^ (skhi )) & 0x3F]
+ | S5[((r0 >> 11) ^ (sklo >> 18)) & 0x3F]
+ | S6[((r0 >> 7) ^ (sklo >> 12)) & 0x3F]
+ | S7[((r0 >> 3) ^ (sklo >> 6)) & 0x3F]
+ | S8[((r1 >> 15) ^ (sklo )) & 0x3F];
+ }
+
+ /*
+ * 64-bit big-endian decoding.
+ */
+ static ulong Dec64be(byte[] buf, int off)
+ {
+ return ((ulong)buf[off] << 56)
+ | ((ulong)buf[off + 1] << 48)
+ | ((ulong)buf[off + 2] << 40)
+ | ((ulong)buf[off + 3] << 32)
+ | ((ulong)buf[off + 4] << 24)
+ | ((ulong)buf[off + 5] << 16)
+ | ((ulong)buf[off + 6] << 8)
+ | (ulong)buf[off + 7];
+ }
+
+ /*
+ * 64-bit big-endian encoding.
+ */
+ static void Enc64be(ulong v, byte[] buf, int off)
+ {
+ buf[off + 0] = (byte)(v >> 56);
+ buf[off + 1] = (byte)(v >> 48);
+ buf[off + 2] = (byte)(v >> 40);
+ buf[off + 3] = (byte)(v >> 32);
+ buf[off + 4] = (byte)(v >> 24);
+ buf[off + 5] = (byte)(v >> 16);
+ buf[off + 6] = (byte)(v >> 8);
+ buf[off + 7] = (byte)v;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains some utility methods used for DSA and ECDSA.
+ */
+
+public class DSAUtils {
+
+ /*
+ * Convert an ASN.1 DSA signature to "raw" format. A "raw" signature
+ * is the concatenation of two unsigned big-endian integers of
+ * the same length. An ASN.1 signature is a DER-encoded SEQUENCE
+ * of two INTEGER values. The returned signature will have the
+ * minimum length that can hold the two signature elements; use
+ * SigRawNormalize() to adjust that length.
+ *
+ * If the source signature is syntaxically invalid (not valid DER),
+ * then null is returned.
+ */
+ public static byte[] SigAsn1ToRaw(byte[] sig)
+ {
+ return SigAsn1ToRaw(sig, 0, sig.Length);
+ }
+
+ public static byte[] SigAsn1ToRaw(byte[] sig, int off, int len)
+ {
+ int lim = off + len;
+ if (len <= 2 || sig[off ++] != 0x30) {
+ return null;
+ }
+ int tlen = DecodeLength(sig, ref off, lim);
+ if (tlen != (lim - off)) {
+ return null;
+ }
+ int roff, rlen;
+ int soff, slen;
+ if (!DecodeInteger(sig, ref off, lim, out roff, out rlen)) {
+ return null;
+ }
+ if (!DecodeInteger(sig, ref off, lim, out soff, out slen)) {
+ return null;
+ }
+ if (off != lim) {
+ return null;
+ }
+ int ulen = Math.Max(rlen, slen);
+ byte[] raw = new byte[ulen << 1];
+ Array.Copy(sig, roff, raw, ulen - rlen, rlen);
+ Array.Copy(sig, soff, raw, (ulen << 1) - slen, slen);
+ return raw;
+ }
+
+ static int DecodeLength(byte[] buf, ref int off, int lim)
+ {
+ if (off >= lim) {
+ return -1;
+ }
+ int fb = buf[off ++];
+ if (fb < 0x80) {
+ return fb;
+ }
+ int elen = fb - 0x80;
+ if (elen == 0) {
+ return -1;
+ }
+ int acc = 0;
+ while (elen -- > 0) {
+ if (off >= lim) {
+ return -1;
+ }
+ if (acc > 0x7FFFFF) {
+ return -1;
+ }
+ acc = (acc << 8) + buf[off ++];
+ }
+ return acc;
+ }
+
+ static bool DecodeInteger(byte[] buf, ref int off, int lim,
+ out int voff, out int vlen)
+ {
+ voff = -1;
+ vlen = -1;
+ if (off >= lim || buf[off ++] != 0x02) {
+ return false;
+ }
+ int len = DecodeLength(buf, ref off, lim);
+ if (len <= 0 || len > (lim - off)) {
+ return false;
+ }
+ voff = off;
+ vlen = len;
+ off += len;
+ while (vlen > 1 && buf[voff] == 0x00) {
+ voff ++;
+ vlen --;
+ }
+ return true;
+ }
+
+ /*
+ * Reduce a "raw" signature to its minimal length. The minimal
+ * length depends on the values of the inner elements; normally,
+ * that length is equal to twice the length of the encoded
+ * subgroup order, but it can be shorter by a few bytes
+ * (occasionally by two bytes; shorter signatures are very
+ * rare).
+ *
+ * If the source signature is null or has an odd length, then
+ * null is returned. If the source signature already has
+ * minimal length, then it is returned as is. Otherwise,
+ * a new array is created with the minimal length, filled,
+ * and returned.
+ */
+ public static byte[] SigRawMinimalize(byte[] sigRaw)
+ {
+ int minLen = GetMinRawLength(sigRaw);
+ if (minLen <= 0) {
+ return null;
+ }
+ if (minLen == sigRaw.Length) {
+ return sigRaw;
+ }
+ int m = sigRaw.Length >> 1;
+ int lh = minLen >> 1;
+ byte[] sig = new byte[lh + lh];
+ Array.Copy(sigRaw, m - lh, sig, 0, lh);
+ Array.Copy(sigRaw, m + m - lh, sig, lh, lh);
+ return sig;
+ }
+
+ /*
+ * Normalize a "raw" signature to the specified length. If
+ * the source array already has the right length, then it is
+ * returned as is. Otherwise, a new array is created with the
+ * requested length, and filled with the signature elements.
+ *
+ * If the source signature is null, or has an odd length, then
+ * null is returned. If the requested length is not valid (odd
+ * length) or cannot be achieved (because the signature elements
+ * are too large), then null is returned.
+ */
+ public static byte[] SigRawNormalize(byte[] sigRaw, int len)
+ {
+ int minLen = GetMinRawLength(sigRaw);
+ if (minLen <= 0) {
+ return null;
+ }
+ if ((len & 1) != 0) {
+ return null;
+ }
+ int hlen = len >> 1;
+ if (sigRaw.Length == len) {
+ return sigRaw;
+ }
+ int m = sigRaw.Length >> 1;
+ int lh = minLen >> 1;
+ byte[] sig = new byte[len];
+ Array.Copy(sigRaw, m - lh, sig, hlen - lh, lh);
+ Array.Copy(sigRaw, m + m - lh, sig, len - lh, lh);
+ return sig;
+ }
+
+ static int GetMinRawLength(byte[] sig)
+ {
+ if (sig == null || (sig.Length & 1) != 0) {
+ return -1;
+ }
+ int m = sig.Length << 1;
+ int lr, ls;
+ for (lr = m; lr > 0; lr --) {
+ if (sig[m - lr] != 0) {
+ break;
+ }
+ }
+ for (ls = m; ls > 0; ls --) {
+ if (sig[m + m - ls] != 0) {
+ break;
+ }
+ }
+ return Math.Max(lr, ls) << 1;
+ }
+
+ /*
+ * Convert a "raw" DSA signature to ASN.1. A "raw" signature
+ * is the concatenation of two unsigned big-endian integers of
+ * the same length. An ASN.1 signature is a DER-encoded SEQUENCE
+ * of two INTEGER values.
+ *
+ * If the source signature is syntaxically invalid (zero length,
+ * or odd length), then null is returned.
+ */
+ public static byte[] SigRawToAsn1(byte[] sig)
+ {
+ return SigRawToAsn1(sig, 0, sig.Length);
+ }
+
+ public static byte[] SigRawToAsn1(byte[] sig, int off, int len)
+ {
+ if (len <= 0 || (len & 1) != 0) {
+ return null;
+ }
+ int tlen = len >> 1;
+ int rlen = LengthOfInteger(sig, off, tlen);
+ int slen = LengthOfInteger(sig, off + tlen, tlen);
+ int ulen = 1 + LengthOfLength(rlen) + rlen
+ + 1 + LengthOfLength(slen) + slen;
+ byte[] s = new byte[1 + LengthOfLength(ulen) + ulen];
+ int k = 0;
+ s[k ++] = 0x30;
+ k += EncodeLength(ulen, s, k);
+ k += EncodeInteger(sig, off, tlen, s, k);
+ k += EncodeInteger(sig, off + tlen, tlen, s, k);
+ // DEBUG
+ if (k != s.Length) {
+ throw new Exception("DSA internal error");
+ }
+ return s;
+ }
+
+ /*
+ * Get the length of the value of an INTEGER containing a
+ * specified value. Returned length includes the leading 0x00
+ * byte (if applicable) but not the tag or length fields.
+ */
+ static int LengthOfInteger(byte[] x, int off, int len)
+ {
+ while (len > 0 && x[off] == 0) {
+ off ++;
+ len --;
+ }
+ if (len == 0) {
+ return 1;
+ }
+ return (x[off] >= 0x80) ? len + 1 : len;
+ }
+
+ static int LengthOfLength(int len)
+ {
+ if (len < 0x80) {
+ return 1;
+ } else if (len < 0x100) {
+ return 2;
+ } else if (len < 0x10000) {
+ return 3;
+ } else if (len < 0x1000000) {
+ return 4;
+ } else {
+ return 5;
+ }
+ }
+
+ static int EncodeLength(int len, byte[] dst, int off)
+ {
+ if (len < 0x80) {
+ dst[off] = (byte)len;
+ return 1;
+ }
+ int k = 0;
+ for (int z = len; z != 0; z >>= 8) {
+ k ++;
+ }
+ dst[off] = (byte)(0x80 + k);
+ for (int i = 0; i < k; i ++) {
+ dst[off + k - i] = (byte)(len >> (i << 3));
+ }
+ return k + 1;
+ }
+
+ static int EncodeInteger(byte[] x, int off, int len,
+ byte[] dst, int dstOff)
+ {
+ int orig = dstOff;
+ dst[dstOff ++] = 0x02;
+ while (len > 0 && x[off] == 0) {
+ off ++;
+ len --;
+ }
+ if (len == 0) {
+ dst[dstOff ++] = 0x01;
+ dst[dstOff ++] = 0x00;
+ return dstOff - orig;
+ }
+ if (x[off] >= 0x80) {
+ dstOff += EncodeLength(len + 1, dst, dstOff);
+ dst[dstOff ++] = 0x00;
+ } else {
+ dstOff += EncodeLength(len, dst, dstOff);
+ }
+ Array.Copy(x, off, dst, dstOff, len);
+ dstOff += len;
+ return dstOff - orig;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class is a convenient base class for implementations of IDigest.
+ * Hash function implementations must implement:
+ * int DigestSize { get; }
+ * int BlockSize { get; }
+ * int PaddingOverhead { get; }
+ * void Update(byte b)
+ * void Update(byte[] buf, int off, int len)
+ * void DoPartial(byte[] outBuf, int off)
+ * void Reset()
+ * IDigest Dup()
+ *
+ * Implementations SHOULD provide overrides for:
+ * void CurrentState(byte[], int)
+ *
+ * In this class:
+ * Update(byte[]) calls Update(byte[],int,int)
+ * DoPartial() calls DoPartial(byte[],int)
+ * DoFinal() calls DoPartial() and Reset()
+ * DoFinal(byte[],int) calls DoPartial(byte[],int) and Reset()
+ */
+
+public abstract class DigestCore : IDigest {
+
+ /* see IDigest */
+ public abstract string Name { get; }
+
+ /* see IDigest */
+ public abstract int DigestSize { get; }
+
+ /* see IDigest */
+ public abstract int BlockSize { get; }
+
+ /* see IDigest */
+ public abstract void Update(byte b);
+
+ /* see IDigest */
+ public virtual void Update(byte[] buf)
+ {
+ Update(buf, 0, buf.Length);
+ }
+
+ /* see IDigest */
+ public abstract void Update(byte[] buf, int off, int len);
+
+ /* see IDigest */
+ public abstract void DoPartial(byte[] outBuf, int off);
+
+ /* see IDigest */
+ public virtual byte[] DoPartial()
+ {
+ byte[] buf = new byte[DigestSize];
+ DoPartial(buf, 0);
+ return buf;
+ }
+
+ /* see IDigest */
+ public virtual void DoFinal(byte[] outBuf, int off)
+ {
+ DoPartial(outBuf, off);
+ Reset();
+ }
+
+ /* see IDigest */
+ public virtual byte[] DoFinal()
+ {
+ byte[] r = DoPartial();
+ Reset();
+ return r;
+ }
+
+ /* see IDigest */
+ public abstract void Reset();
+
+ /* see IDigest */
+ public abstract IDigest Dup();
+
+ /*
+ * Default implementation throws a NotSupportedException.
+ */
+ public virtual void CurrentState(byte[] outBuf, int off)
+ {
+ throw new NotSupportedException();
+ }
+
+ /* see IDigest */
+ public virtual byte[] Hash(byte[] buf)
+ {
+ return Hash(buf, 0, buf.Length);
+ }
+
+ /* see IDigest */
+ public virtual byte[] Hash(byte[] buf, int off, int len)
+ {
+ IDigest h = Dup();
+ h.Reset();
+ h.Update(buf, off, len);
+ return h.DoFinal();
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains static definitions for some standard elliptic
+ * curves.
+ */
+
+public class EC {
+
+ // public static ECCurve P192 = NIST.P192;
+ // public static ECCurve P224 = NIST.P224;
+ public static ECCurve P256 = NIST.P256;
+ public static ECCurve P384 = NIST.P384;
+ public static ECCurve P521 = NIST.P521;
+
+ public static ECCurve Curve25519 = new ECCurve25519();
+
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class represents an elliptic curve.
+ */
+
+public abstract class ECCurve {
+
+ /*
+ * Get the subgroup order for this curve (big-endian unsigned
+ * notation). The subgroup order is supposed to be a prime
+ * integer.
+ */
+ public byte[] SubgroupOrder {
+ get {
+ return subgroupOrder;
+ }
+ }
+
+ /*
+ * Get the cofactor fors this curve (big-endian unsigned notation).
+ * The cofactor is the quotient of the curve order by the subgroup
+ * order.
+ */
+ public byte[] Cofactor {
+ get {
+ return cofactor;
+ }
+ }
+
+ /*
+ * Get the curve symbolic name.
+ */
+ public abstract string Name {
+ get;
+ }
+
+ /*
+ * Get the curve type.
+ */
+ public abstract ECCurveType CurveType {
+ get;
+ }
+
+ /*
+ * Get the length (in bytes) of an encoded point. The "point at
+ * infinity" may use a shorter encoding than other points. This
+ * uses the "normal" encoding (not compressed).
+ */
+ public abstract int EncodedLength {
+ get;
+ }
+
+ /*
+ * Get the length (in bytes) of an encoded compressed point.
+ * The "point at infinity" may use a shorter encoding than
+ * other points.
+ */
+ public abstract int EncodedLengthCompressed {
+ get;
+ }
+
+ /*
+ * Perform extensive curve validity checks. These tests may be
+ * computationally expensive.
+ */
+ public abstract void CheckValid();
+
+ /*
+ * Get the encoded generator for this curve. This is
+ * a conventional point that generates the subgroup of prime
+ * order on which computations are normally done.
+ */
+ public virtual byte[] GetGenerator(bool compressed)
+ {
+ return MakeGenerator().Encode(compressed);
+ }
+
+ /*
+ * Get the offset and length of the X coordinate of a point,
+ * within its encoded representation. When doing a Diffie-Hellman
+ * key exchange, the resulting shared secret is the X coordinate
+ * of the resulting point.
+ */
+ public abstract int GetXoff(out int len);
+
+ /*
+ * Multiply the provided (encoded) point G by a scalar x. Scalar
+ * encoding is big-endian. The scalar value shall be non-zero and
+ * lower than the subgroup order (exception: some curves allow
+ * larger ranges).
+ *
+ * The result is written in the provided D[] array, using either
+ * compressed or uncompressed format (for some curves, output is
+ * always compressed). The array shall have the appropriate length.
+ * Returned value is -1 on success, 0 on error. If 0 is returned
+ * then the array contents are indeterminate.
+ *
+ * G and D need not be distinct arrays.
+ */
+ public uint Mul(byte[] G, byte[] x, byte[] D, bool compressed)
+ {
+ MutableECPoint P = MakeZero();
+ uint good = P.DecodeCT(G);
+ good &= ~P.IsInfinityCT;
+ good &= P.MulSpecCT(x);
+ good &= P.Encode(D, compressed);
+ return good;
+ }
+
+ /*
+ * Given points A and B, and scalar x and y, return x*A+y*B. This
+ * is used for ECDSA. Scalars use big-endian encoding and must be
+ * non-zero and lower than the subgroup order.
+ *
+ * The result is written in the provided D[] array, using either
+ * compressed or uncompressed format (for some curves, output is
+ * always compressed). The array shall have the appropriate length.
+ * Returned value is -1 on success, 0 on error. If 0 is returned
+ * then the array contents are indeterminate.
+ *
+ * Not all curves support this operation; if the curve does not,
+ * then an exception is thrown.
+ *
+ * A, B and D need not be distinct arrays.
+ */
+ public uint MulAdd(byte[] A, byte[] x, byte[] B, byte[] y,
+ byte[] D, bool compressed)
+ {
+ MutableECPoint P = MakeZero();
+ MutableECPoint Q = MakeZero();
+
+ /*
+ * Decode both points.
+ */
+ uint good = P.DecodeCT(A);
+ good &= Q.DecodeCT(B);
+ good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+ /*
+ * Perform both point multiplications.
+ */
+ good &= P.MulSpecCT(x);
+ good &= Q.MulSpecCT(y);
+ good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+ /*
+ * Perform addition. The AddCT() function may fail if
+ * P = Q, in which case we must compute 2Q and use that
+ * value instead.
+ */
+ uint z = P.AddCT(Q);
+ Q.DoubleCT();
+ P.Set(Q, ~z);
+
+ /*
+ * Encode the result. The Encode() function will report
+ * an error if the addition result is infinity.
+ */
+ good &= P.Encode(D, compressed);
+ return good;
+ }
+
+ /*
+ * Generate a new random secret value appropriate for an ECDH
+ * key exchange (WARNING: this might not be sufficiently uniform
+ * for the generation of the per-signature secret value 'k' for
+ * ECDSA).
+ *
+ * The value is returned in unsigned big-endian order, in an array
+ * of the same size of the subgroup order.
+ */
+ public abstract byte[] MakeRandomSecret();
+
+ /* ============================================================= */
+
+ byte[] subgroupOrder;
+ byte[] cofactor;
+
+ internal ECCurve(byte[] subgroupOrder, byte[] cofactor)
+ {
+ this.subgroupOrder = subgroupOrder;
+ this.cofactor = cofactor;
+ }
+
+ /*
+ * Create a new mutable point instance, initialized to the point
+ * at infinity.
+ *
+ * (On some curves whose implementations do not support generic
+ * point addition, this method may return a non-infinity point
+ * which serves as placeholder to obtain MutableECPoint instances.)
+ */
+ internal abstract MutableECPoint MakeZero();
+
+ /*
+ * Create a new mutable point instance, initialized to the
+ * defined subgroup generator.
+ */
+ internal abstract MutableECPoint MakeGenerator();
+
+ /*
+ * Create a new mutable point instance by decoding the provided
+ * value.
+ */
+ internal abstract MutableECPoint Decode(byte[] enc);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation for Curve25519.
+ */
+
+internal class ECCurve25519 : ECCurve {
+
+ public override string Name {
+ get {
+ return "Curve25519";
+ }
+ }
+
+ public override ECCurveType CurveType {
+ get {
+ return ECCurveType.Montgomery;
+ }
+ }
+
+ public override int EncodedLength {
+ get {
+ return 32;
+ }
+ }
+
+ public override int EncodedLengthCompressed {
+ get {
+ return 32;
+ }
+ }
+
+ public override byte[] GetGenerator(bool compressed)
+ {
+ byte[] G = new byte[32];
+ G[0] = 9;
+ return G;
+ }
+
+ private static byte[] ORDER = {
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x14, 0xDE, 0xF9, 0xDE, 0xA2, 0xF7, 0x9C, 0xD6,
+ 0x58, 0x12, 0x63, 0x1A, 0x5C, 0xF5, 0xD3, 0xED
+ };
+
+ private static byte[] COFACTOR = {
+ 0x08
+ };
+
+ private static byte[] MOD = {
+ 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED
+ };
+
+ internal ModInt mp;
+ internal ModInt ma24;
+
+ internal ECCurve25519() : base(ORDER, COFACTOR)
+ {
+ mp = new ModInt(MOD);
+ ma24 = mp.Dup();
+ ma24.Set(121665);
+ ma24.ToMonty();
+ }
+
+ public override void CheckValid()
+ {
+ /* Nothing to do, the curve is valid by construction. */
+ }
+
+ public override int GetXoff(out int len)
+ {
+ len = 32;
+ return 0;
+ }
+
+ /* obsolete
+ public override uint Mul(byte[] G, byte[] x, byte[] D, bool compressed)
+ {
+ if (G.Length != 32 || D.Length != 32 || x.Length > 32) {
+ return 0;
+ }
+
+ byte[] k = new byte[32];
+ Array.Copy(x, 0, k, 32 - x.Length, x.Length);
+
+ k[31] &= 0xF8;
+ k[0] &= 0x7F;
+ k[0] |= 0x40;
+
+ byte[] u = new byte[32];
+ for (int i = 0; i < 32; i ++) {
+ u[i] = G[31 - i];
+ }
+ u[0] &= 0x7F;
+
+ ModInt x1 = mp.Dup();
+ x1.DecodeReduce(u);
+ x1.ToMonty();
+ ModInt x2 = mp.Dup();
+ x2.SetMonty(0xFFFFFFFF);
+ ModInt z2 = mp.Dup();
+ ModInt x3 = x1.Dup();
+ ModInt z3 = x2.Dup();
+ uint swap = 0;
+
+ ModInt a = mp.Dup();
+ ModInt aa = mp.Dup();
+ ModInt b = mp.Dup();
+ ModInt bb = mp.Dup();
+ ModInt c = mp.Dup();
+ ModInt d = mp.Dup();
+ ModInt e = mp.Dup();
+
+ for (int t = 254; t >= 0; t --) {
+ uint kt = (uint)-((k[31 - (t >> 3)] >> (t & 7)) & 1);
+ swap ^= kt;
+ x2.CondSwap(x3, swap);
+ z2.CondSwap(z3, swap);
+ swap = kt;
+
+ a.Set(x2);
+ a.Add(z2);
+ aa.Set(a);
+ aa.MontySquare();
+ b.Set(x2);
+ b.Sub(z2);
+ bb.Set(b);
+ bb.MontySquare();
+ e.Set(aa);
+ e.Sub(bb);
+ c.Set(x3);
+ c.Add(z3);
+ d.Set(x3);
+ d.Sub(z3);
+ d.MontyMul(a);
+ c.MontyMul(b);
+ x3.Set(d);
+ x3.Add(c);
+ x3.MontySquare();
+ z3.Set(d);
+ z3.Sub(c);
+ z3.MontySquare();
+ z3.MontyMul(x1);
+ x2.Set(aa);
+ x2.MontyMul(bb);
+ z2.Set(e);
+ z2.MontyMul(ma24);
+ z2.Add(aa);
+ z2.MontyMul(e);
+ }
+ x2.CondSwap(x3, swap);
+ z2.CondSwap(z3, swap);
+
+ z2.FromMonty();
+ z2.Invert();
+ x2.MontyMul(z2);
+
+ x2.Encode(u);
+ for (int i = 0; i < 32; i ++) {
+ D[i] = u[31 - i];
+ }
+ return 0xFFFFFFFF;
+ }
+
+ public override uint MulAdd(byte[] A, byte[] x, byte[] B, byte[] y,
+ byte[] D, bool compressed)
+ {
+ throw new CryptoException(
+ "Operation not supported for Curve25519");
+ }
+ */
+
+ public override bool Equals(object obj)
+ {
+ return (obj as ECCurve25519) != null;
+ }
+
+ public override int GetHashCode()
+ {
+ return 0x3E96D5F6;
+ }
+
+ public override byte[] MakeRandomSecret()
+ {
+ /*
+ * For Curve25519, we simply generate a random 32-byte
+ * array, to which we apply the "clamping" that will
+ * be done for point multiplication anyway.
+ */
+ byte[] x = new byte[32];
+ RNG.GetBytes(x);
+ x[0] &= 0x7F;
+ x[0] |= 0x40;
+ x[31] &= 0xF8;
+ return x;
+ }
+
+ internal override MutableECPoint MakeZero()
+ {
+ return new MutableECPointCurve25519();
+ }
+
+ internal override MutableECPoint MakeGenerator()
+ {
+ MutableECPointCurve25519 G = new MutableECPointCurve25519();
+ G.Decode(GetGenerator(false));
+ return G;
+ }
+
+ internal override MutableECPoint Decode(byte[] enc)
+ {
+ MutableECPointCurve25519 P = new MutableECPointCurve25519();
+ P.Decode(enc);
+ return P;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation for elliptic curves in a prime field.
+ */
+
+internal class ECCurvePrime : ECCurve {
+
+ public override string Name {
+ get {
+ return name;
+ }
+ }
+
+ public override ECCurveType CurveType {
+ get {
+ return ECCurveType.Prime;
+ }
+ }
+
+ public override int EncodedLength {
+ get {
+ return 1 + (flen << 1);
+ }
+ }
+
+ public override int EncodedLengthCompressed {
+ get {
+ return 1 + flen;
+ }
+ }
+
+ string name;
+
+ /*
+ * 'mp' always contains 0 modulo p.
+ * 'ma' contains a.
+ * 'mb' contains b.
+ * pMod4 is p modulo 4.
+ */
+ internal ModInt mp;
+ internal ModInt ma;
+ internal ModInt mb;
+ internal int pMod4;
+
+ /*
+ * a and b are in unsigned big-endian notation.
+ * aIsM3 is true when a == -3 modulo p.
+ */
+ internal byte[] a;
+ internal byte[] b;
+ internal bool aIsM3;
+
+ internal byte[] mod;
+ internal int flen;
+ byte[] gx;
+ byte[] gy;
+ int hashCode;
+
+ /*
+ * Checks enforced by the constructor:
+ * -- modulus is odd and at least 80-bit long
+ * -- subgroup order is odd and at least 30-bit long
+ * -- parameters a[] and b[] are lower than modulus
+ * -- coordinates gx and gy are lower than modulus
+ * -- coordinates gx and gy match curve equation
+ */
+ internal ECCurvePrime(string name, byte[] mod, byte[] a, byte[] b,
+ byte[] gx, byte[] gy, byte[] subgroupOrder, byte[] cofactor)
+ : base(subgroupOrder, cofactor)
+ {
+ this.mod = mod = BigInt.NormalizeBE(mod);
+ int modLen = BigInt.BitLength(mod);
+ if (modLen < 80) {
+ throw new CryptoException(
+ "Invalid curve: modulus is too small");
+ }
+ if ((mod[mod.Length - 1] & 0x01) == 0) {
+ throw new CryptoException(
+ "Invalid curve: modulus is even");
+ }
+ int sgLen = BigInt.BitLength(subgroupOrder);
+ if (sgLen < 30) {
+ throw new CryptoException(
+ "Invalid curve: subgroup is too small");
+ }
+ if ((subgroupOrder[subgroupOrder.Length - 1] & 0x01) == 0) {
+ throw new CryptoException(
+ "Invalid curve: subgroup order is even");
+ }
+
+ mp = new ModInt(mod);
+ flen = (modLen + 7) >> 3;
+ pMod4 = mod[mod.Length - 1] & 3;
+
+ this.a = a = BigInt.NormalizeBE(a);
+ this.b = b = BigInt.NormalizeBE(b);
+ if (BigInt.CompareCT(a, mod) >= 0
+ || BigInt.CompareCT(b, mod) >= 0)
+ {
+ throw new CryptoException(
+ "Invalid curve: out-of-range parameter");
+ }
+ ma = mp.Dup();
+ ma.Decode(a);
+ ma.Add(3);
+ aIsM3 = ma.IsZero;
+ ma.Sub(3);
+ mb = mp.Dup();
+ mb.Decode(b);
+
+ this.gx = gx = BigInt.NormalizeBE(gx);
+ this.gy = gy = BigInt.NormalizeBE(gy);
+ if (BigInt.CompareCT(gx, mod) >= 0
+ || BigInt.CompareCT(gy, mod) >= 0)
+ {
+ throw new CryptoException(
+ "Invalid curve: out-of-range coordinates");
+ }
+ MutableECPointPrime G = new MutableECPointPrime(this);
+ G.Set(gx, gy, true);
+
+ hashCode = (int)(BigInt.HashInt(mod)
+ ^ BigInt.HashInt(a) ^ BigInt.HashInt(b)
+ ^ BigInt.HashInt(gx) ^ BigInt.HashInt(gy));
+
+ if (name == null) {
+ name = string.Format("generic prime {0}/{1}",
+ modLen, sgLen);
+ }
+ this.name = name;
+ }
+
+ /*
+ * Extra checks:
+ * -- modulus is prime
+ * -- subgroup order is prime
+ * -- generator indeed generates subgroup
+ */
+ public override void CheckValid()
+ {
+ /*
+ * Check that the modulus is prime.
+ */
+ if (!BigInt.IsPrime(mod)) {
+ throw new CryptoException(
+ "Invalid curve: modulus is not prime");
+ }
+
+ /*
+ * Check that the subgroup order is prime.
+ */
+ if (!BigInt.IsPrime(SubgroupOrder)) {
+ throw new CryptoException(
+ "Invalid curve: subgroup order is not prime");
+ }
+
+ /*
+ * Check that the G point is indeed a generator of the
+ * subgroup. Note that since it has explicit coordinates,
+ * it cannot be the point at infinity; it suffices to
+ * verify that, when multiplied by the subgroup order,
+ * it yields infinity.
+ */
+ MutableECPointPrime G = new MutableECPointPrime(this);
+ G.Set(gx, gy, false);
+ if (G.MulSpecCT(SubgroupOrder) == 0 || !G.IsInfinity) {
+ throw new CryptoException(
+ "Invalid curve: generator does not match"
+ + " subgroup order");
+ }
+
+ /*
+ * TODO: check cofactor.
+ *
+ * If the cofactor is small, then we can simply compute
+ * the complete curve order by multiplying the cofactor
+ * with the subgroup order, and see whether it is in the
+ * proper range with regards to the field cardinal (by
+ * using Hasse's theorem). However, if the cofactor is
+ * larger than the subgroup order, then detecting a
+ * wrong cofactor value is a bit more complex. We could
+ * generate a few random points and multiply them by
+ * the computed order, but this may be expensive.
+ */
+ }
+
+ public override int GetXoff(out int len)
+ {
+ len = flen;
+ return 1;
+ }
+
+ /* obsolete
+ public override uint Mul(byte[] G, byte[] x, byte[] D, bool compressed)
+ {
+ MutableECPointPrime P = new MutableECPointPrime(this);
+ uint good = P.DecodeCT(G);
+ good &= ~P.IsInfinityCT;
+ good &= P.MulSpecCT(x);
+ good &= P.Encode(D, compressed);
+ return good;
+ }
+
+ public override uint MulAdd(byte[] A, byte[] x, byte[] B, byte[] y,
+ byte[] D, bool compressed)
+ {
+ MutableECPointPrime P = new MutableECPointPrime(this);
+ MutableECPointPrime Q = new MutableECPointPrime(this);
+
+ uint good = P.DecodeCT(A);
+ good &= Q.DecodeCT(B);
+ good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+ good &= P.MulSpecCT(x);
+ good &= Q.MulSpecCT(y);
+ good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+ uint z = P.AddCT(Q);
+ Q.DoubleCT();
+ P.Set(Q, ~z);
+
+ good &= P.Encode(D, compressed);
+ return good;
+ }
+ */
+
+ public override byte[] MakeRandomSecret()
+ {
+ /*
+ * We force the top bits to 0 to guarantee that the value
+ * is less than the subgroup order; and we force the
+ * least significant bit to 0 so that the value is not null.
+ * This is good enough for ECDH.
+ */
+ byte[] q = SubgroupOrder;
+ byte[] x = new byte[q.Length];
+ int mask = 0xFF;
+ while (mask >= q[0]) {
+ mask >>= 1;
+ }
+ RNG.GetBytes(x);
+ x[0] &= (byte)mask;
+ x[x.Length - 1] |= (byte)0x01;
+ return x;
+ }
+
+ internal override MutableECPoint MakeZero()
+ {
+ return new MutableECPointPrime(this);
+ }
+
+ internal override MutableECPoint MakeGenerator()
+ {
+ /*
+ * We do not have to check the generator, since
+ * it was already done in the constructor.
+ */
+ MutableECPointPrime G = new MutableECPointPrime(this);
+ G.Set(gx, gy, false);
+ return G;
+ }
+
+ internal override MutableECPoint Decode(byte[] enc)
+ {
+ MutableECPointPrime P = new MutableECPointPrime(this);
+ P.Decode(enc);
+ return P;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as ECCurvePrime);
+ }
+
+ internal bool Equals(ECCurvePrime curve)
+ {
+ if (this == curve) {
+ return true;
+ }
+ return BigInt.Compare(mod, curve.mod) == 0
+ && BigInt.Compare(a, curve.a) == 0
+ && BigInt.Compare(b, curve.b) == 0
+ && BigInt.Compare(gx, curve.gx) == 0
+ && BigInt.Compare(gy, curve.gy) == 0;
+ }
+
+ public override int GetHashCode()
+ {
+ return hashCode;
+ }
+
+ /*
+ * Given a value X in sx, this method computes X^3+aX+b into sd.
+ * 'sx' is unmodified. 'st' is modified (it receives a*X).
+ * The sx, sd and st instances MUST be distinct.
+ */
+ internal void RebuildY2(ModInt sx, ModInt sd, ModInt st)
+ {
+ sd.Set(sx);
+ sd.ToMonty();
+ st.Set(sd);
+ sd.MontySquare();
+ sd.MontyMul(sx);
+ st.MontyMul(ma);
+ sd.Add(st);
+ sd.Add(mb);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Symbolic identifiers for curve types.
+ */
+
+public enum ECCurveType {
+
+ Prime,
+ BinaryTrinomial,
+ BinaryPentanomial,
+ Montgomery,
+ Edwards
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace Crypto {
+
+/*
+ * This class implements the ECDSA signature algorithm. The static methods
+ * provide access to the algorithm primitives. The hash of the signed data
+ * must be provided externally.
+ *
+ * Signatures may be encoded in either "ASN.1" or "raw" formats; "ASN.1"
+ * is normally used (e.g. in SSL/TLS) but some protocols and API expect
+ * the raw format (e.g. PKCS#11 and OpenPGP). An ECDSA signature consists
+ * in two integers r and s, which are values modulo the subgroup order q.
+ * In ASN.1 format, the signature is the DER encoding of the ASN.1
+ * structure:
+ *
+ * ECDSA-signature ::= SEQUENCE {
+ * r INTEGER,
+ * s INTEGER
+ * }
+ *
+ * In raw format, the two integers r and s are encoded with unsigned
+ * big-endian encoding (with the same encoding length) and concatenated.
+ *
+ * The Sign() and Verify() methods use the ASN.1 format. The SignRaw()
+ * and VerifyRaw() use the raw format. The SigRawToAsn1() and SigAsn1ToRaw()
+ * allow for converting between the two formats.
+ */
+
+public class ECDSA : DSAUtils {
+
+ /*
+ * Verify an ECDSA signature (ASN.1 format). Returned value is true
+ * on success. If the signature is invalid, then false is returned.
+ * Internal exceptions (due to an incorrect public key) are also
+ * converted to a returned value of false.
+ *
+ * There are four methods, depending on the source operands.
+ */
+
+ public static bool Verify(ECPublicKey pk,
+ byte[] hash, byte[] sig)
+ {
+ return Verify(pk, hash, 0, hash.Length, sig, 0, sig.Length);
+ }
+
+ public static bool Verify(ECPublicKey pk,
+ byte[] hash, int hashOff, int hashLen, byte[] sig)
+ {
+ return Verify(pk, hash, hashOff, hashLen, sig, 0, sig.Length);
+ }
+
+ public static bool Verify(ECPublicKey pk,
+ byte[] hash, byte[] sig, int sigOff, int sigLen)
+ {
+ return Verify(pk, hash, 0, hash.Length, sig, sigOff, sigLen);
+ }
+
+ public static bool Verify(ECPublicKey pk,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] sig, int sigOff, int sigLen)
+ {
+ byte[] raw = SigAsn1ToRaw(sig, sigOff, sigLen);
+ if (raw == null) {
+ return false;
+ }
+ return VerifyRaw(pk,
+ hash, hashOff, hashLen, raw, 0, raw.Length);
+ }
+
+ /*
+ * Verify an ECDSA signature (raw format). Returned value is true
+ * on success. If the signature is invalid, then false is returned.
+ * Internal exceptions (due to an incorrect public key) are also
+ * converted to a returned value of false.
+ *
+ * There are four methods, depending on the source operands.
+ */
+
+ public static bool VerifyRaw(ECPublicKey pk,
+ byte[] hash, byte[] sig)
+ {
+ return VerifyRaw(pk,
+ hash, 0, hash.Length, sig, 0, sig.Length);
+ }
+
+ public static bool VerifyRaw(ECPublicKey pk,
+ byte[] hash, int hashOff, int hashLen, byte[] sig)
+ {
+ return VerifyRaw(pk,
+ hash, hashOff, hashLen, sig, 0, sig.Length);
+ }
+
+ public static bool VerifyRaw(ECPublicKey pk,
+ byte[] hash, byte[] sig, int sigOff, int sigLen)
+ {
+ return VerifyRaw(pk,
+ hash, 0, hash.Length, sig, sigOff, sigLen);
+ }
+
+ public static bool VerifyRaw(ECPublicKey pk,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] sig, int sigOff, int sigLen)
+ {
+ try {
+ /*
+ * Get the curve.
+ */
+ ECCurve curve = pk.Curve;
+
+ /*
+ * Get r and s from signature. This also verifies
+ * that they do not exceed the subgroup order.
+ */
+ if (sigLen == 0 || (sigLen & 1) != 0) {
+ return false;
+ }
+ int tlen = sigLen >> 1;
+ ModInt oneQ = new ModInt(curve.SubgroupOrder);
+ oneQ.Set(1);
+ ModInt r = oneQ.Dup();
+ ModInt s = oneQ.Dup();
+ r.Decode(sig, sigOff, tlen);
+ s.Decode(sig, sigOff + tlen, tlen);
+
+ /*
+ * If either r or s was too large, it got set to
+ * zero. We also don't want real zeros.
+ */
+ if (r.IsZero || s.IsZero) {
+ return false;
+ }
+
+ /*
+ * Convert the hash value to an integer modulo q.
+ * As per FIPS 186-4, if the hash value is larger
+ * than q, then we keep the qlen leftmost bits of
+ * the hash value.
+ */
+ int qBitLength = oneQ.ModBitLength;
+ int hBitLength = hashLen << 3;
+ byte[] hv;
+ if (hBitLength <= qBitLength) {
+ hv = new byte[hashLen];
+ Array.Copy(hash, hashOff, hv, 0, hashLen);
+ } else {
+ int qlen = (qBitLength + 7) >> 3;
+ hv = new byte[qlen];
+ Array.Copy(hash, hashOff, hv, 0, qlen);
+ int rs = (8 - (qBitLength & 7)) & 7;
+ BigInt.RShift(hv, rs);
+ }
+ ModInt z = oneQ.Dup();
+ z.DecodeReduce(hv);
+
+ /*
+ * Apply the verification algorithm:
+ * w = 1/s mod q
+ * u = z*w mod q
+ * v = r*w mod q
+ * T = u*G + v*Pub
+ * test whether T.x mod q == r.
+ */
+ /*
+ * w = 1/s mod q
+ */
+ ModInt w = s.Dup();
+ w.Invert();
+
+ /*
+ * u = z*w mod q
+ */
+ w.ToMonty();
+ ModInt u = w.Dup();
+ u.MontyMul(z);
+
+ /*
+ * v = r*w mod q
+ */
+ ModInt v = w.Dup();
+ v.MontyMul(r);
+
+ /*
+ * Compute u*G
+ */
+ MutableECPoint T = curve.MakeGenerator();
+ uint good = T.MulSpecCT(u.Encode());
+
+ /*
+ * Compute v*iPub
+ */
+ MutableECPoint M = pk.iPub.Dup();
+ good &= M.MulSpecCT(v.Encode());
+
+ /*
+ * Compute T = u*G+v*iPub
+ */
+ uint nd = T.AddCT(M);
+ M.DoubleCT();
+ T.Set(M, ~nd);
+ good &= ~T.IsInfinityCT;
+
+ /*
+ * Get T.x, reduced modulo q.
+ * Signature is valid if and only if we get
+ * the same value as r (and we did not encounter
+ * an error previously).
+ */
+ s.DecodeReduce(T.X);
+ return (good & r.EqCT(s)) != 0;
+
+ } catch (CryptoException) {
+ /*
+ * Exceptions may occur if the key or signature
+ * have invalid values (non invertible, out of
+ * range...). Any such occurrence means that the
+ * signature is not valid.
+ */
+ return false;
+ }
+ }
+
+ /*
+ * Compute an ECDSA signature (ASN.1 format). On error (e.g. due
+ * to an invalid private key), an exception is thrown.
+ *
+ * Internally, the process described in RFC 6979 is used to
+ * compute the per-signature random value 'k'. If 'rfc6979Hash'
+ * is not null, then a clone of that function is used for that
+ * process, and signatures are fully deterministic and should
+ * match RFC 6979 test vectors; if 'rfc6979Hash' is null, then
+ * the engine uses SHA-256 with additional randomness, resulting
+ * in randomized signatures. The systematic use of RFC 6979 in
+ * both cases ensures the safety of the private key even if the
+ * system RNG is predictible.
+ *
+ * There are four methods, depending on the source operands.
+ */
+
+ public static byte[] Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash)
+ {
+ return Sign(sk, rfc6979Hash, hash, 0, hash.Length);
+ }
+
+ public static byte[] Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash, int hashOff, int hashLen)
+ {
+ return SigRawToAsn1(SignRaw(sk, rfc6979Hash,
+ hash, hashOff, hashLen));
+ }
+
+ public static int Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash, byte[] outBuf, int outOff)
+ {
+ return Sign(sk, rfc6979Hash,
+ hash, 0, hash.Length, outBuf, outOff);
+ }
+
+ public static int Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] outBuf, int outOff)
+ {
+ byte[] sig = Sign(sk, rfc6979Hash, hash, hashOff, hashLen);
+ Array.Copy(sig, 0, outBuf, outOff, sig.Length);
+ return sig.Length;
+ }
+
+ /*
+ * Compute an ECDSA signature (raw format). On error (e.g. due
+ * to an invalid private key), an exception is thrown.
+ *
+ * Internally, the process described in RFC 6979 is used to
+ * compute the per-signature random value 'k'. If 'rfc6979Hash'
+ * is not null, then a clone of that function is used for that
+ * process, and signatures are fully deterministic and should
+ * match RFC 6979 test vectors; if 'rfc6979Hash' is null, then
+ * the engine uses SHA-256 with additional randomness, resulting
+ * in randomized signatures. The systematic use of RFC 6979 in
+ * both cases ensures the safety of the private key even if the
+ * system RNG is predictible.
+ *
+ * The signature returned by these methods always has length
+ * exactly twice that of the encoded subgroup order (they are
+ * not minimalized). Use SigRawMinimalize() to reduce the
+ * signature size to its minimum length.
+ *
+ * There are four methods, depending on the source operands.
+ */
+
+ public static byte[] SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash)
+ {
+ return SignRaw(sk, rfc6979Hash, hash, 0, hash.Length);
+ }
+
+ public static int SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash, byte[] outBuf, int outOff)
+ {
+ return SignRaw(sk, rfc6979Hash,
+ hash, 0, hash.Length, outBuf, outOff);
+ }
+
+ public static int SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] outBuf, int outOff)
+ {
+ byte[] sig = SignRaw(sk, rfc6979Hash, hash, hashOff, hashLen);
+ Array.Copy(sig, 0, outBuf, outOff, sig.Length);
+ return sig.Length;
+ }
+
+ public static byte[] SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+ byte[] hash, int hashOff, int hashLen)
+ {
+ ECCurve curve = sk.Curve;
+ byte[] q = curve.SubgroupOrder;
+ RFC6979 rf = new RFC6979(rfc6979Hash, q, sk.X,
+ hash, hashOff, hashLen, rfc6979Hash != null);
+ ModInt mh = rf.GetHashMod();
+ ModInt mx = mh.Dup();
+ mx.Decode(sk.X);
+
+ /*
+ * Compute DSA signature. We use a loop to enumerate
+ * candidates for k until a proper one is found (it
+ * is VERY improbable that we may have to loop).
+ */
+ ModInt mr = mh.Dup();
+ ModInt ms = mh.Dup();
+ ModInt mk = mh.Dup();
+ byte[] k = new byte[q.Length];
+ for (;;) {
+ rf.NextK(k);
+ MutableECPoint G = curve.MakeGenerator();
+ if (G.MulSpecCT(k) == 0) {
+ /*
+ * We may get an error here only if the
+ * curve is invalid (generator does not
+ * produce the expected subgroup).
+ */
+ throw new CryptoException(
+ "Invalid EC private key / curve");
+ }
+ mr.DecodeReduce(G.X);
+ if (mr.IsZero) {
+ continue;
+ }
+ ms.Set(mx);
+ ms.ToMonty();
+ ms.MontyMul(mr);
+ ms.Add(mh);
+ mk.Decode(k);
+ mk.Invert();
+ ms.ToMonty();
+ ms.MontyMul(mk);
+
+ byte[] sig = new byte[q.Length << 1];
+ mr.Encode(sig, 0, q.Length);
+ ms.Encode(sig, q.Length, q.Length);
+ return sig;
+ }
+ }
+
+ /*
+ * Generate a new EC key pair in the specified curve.
+ */
+ public static ECPrivateKey Generate(ECCurve curve)
+ {
+ return new ECPrivateKey(curve,
+ BigInt.RandIntNZ(curve.SubgroupOrder));
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains an EC private key, consisting of two elements:
+ * -- an elliptic curve
+ * -- the private integer (X)
+ *
+ * The private integer is always handled as an integer modulo the
+ * curve subbroup order. Its binary representation is unsigned big-endian
+ * with exactly the same length as the subgroup order.
+ */
+
+public class ECPrivateKey : IPrivateKey {
+
+ public ECCurve Curve {
+ get {
+ return curve;
+ }
+ }
+
+ public byte[] X {
+ get {
+ return priv;
+ }
+ }
+
+ public int KeySizeBits {
+ get {
+ return BigInt.BitLength(curve.SubgroupOrder);
+ }
+ }
+
+ public string AlgorithmName {
+ get {
+ return "EC";
+ }
+ }
+
+ IPublicKey IPrivateKey.PublicKey {
+ get {
+ return this.PublicKey;
+ }
+ }
+
+ public ECPublicKey PublicKey {
+ get {
+ if (dpk == null) {
+ MutableECPoint G = curve.MakeGenerator();
+ G.MulSpecCT(priv);
+ dpk = new ECPublicKey(curve, G.Encode(false));
+ }
+ return dpk;
+ }
+ }
+
+ ECCurve curve;
+ byte[] priv;
+ ECPublicKey dpk;
+
+ /*
+ * Create a new instance with the provided elements. The
+ * constructor verifies that the provided private integer
+ * is non-zero and is less than the subgroup order.
+ */
+ public ECPrivateKey(ECCurve curve, byte[] X)
+ {
+ this.curve = curve;
+ ModInt ms = new ModInt(curve.SubgroupOrder);
+ uint good = ms.Decode(X);
+ good &= ~ms.IsZeroCT;
+ if (good == 0) {
+ throw new CryptoException("Invalid private key");
+ }
+ priv = ms.Encode();
+ dpk = null;
+ }
+
+ /*
+ * CheckValid() runs the validity tests on the curve.
+ */
+ public void CheckValid()
+ {
+ curve.CheckValid();
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains an EC public key, consisting of two elements:
+ * -- an elliptic curve
+ * -- a non-zero point on that curve (called "Pub")
+ */
+
+public class ECPublicKey : IPublicKey {
+
+ public ECCurve Curve {
+ get {
+ return curve;
+ }
+ }
+
+ public byte[] Pub {
+ get {
+ return pub;
+ }
+ }
+
+ public int KeySizeBits {
+ get {
+ return BigInt.BitLength(curve.SubgroupOrder);
+ }
+ }
+
+ public string AlgorithmName {
+ get {
+ return "EC";
+ }
+ }
+
+ ECCurve curve;
+ byte[] pub;
+ int hashCode;
+
+ internal MutableECPoint iPub;
+
+ /*
+ * Create a new instance with the provided elements. The
+ * constructor verifies that the provided point is part of
+ * the curve.
+ */
+ public ECPublicKey(ECCurve curve, byte[] Pub)
+ {
+ this.curve = curve;
+ this.pub = Pub;
+ iPub = curve.Decode(Pub);
+ if (iPub.IsInfinity) {
+ throw new CryptoException(
+ "Public key point is infinity");
+ }
+ hashCode = curve.GetHashCode()
+ ^ (int)BigInt.HashInt(iPub.X)
+ ^ (int)BigInt.HashInt(iPub.Y);
+ }
+
+ /*
+ * CheckValid() runs the validity tests on the curve, and
+ * verifies that provided point is part of a subgroup with
+ * the advertised subgroup order.
+ */
+ public void CheckValid()
+ {
+ curve.CheckValid();
+ MutableECPoint P = iPub.Dup();
+ if (P.MulSpecCT(curve.SubgroupOrder) == 0
+ || !P.IsInfinity)
+ {
+ throw new CryptoException(
+ "Public key point not on the defined subgroup");
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ ECPublicKey pk = obj as ECPublicKey;
+ if (pk == null) {
+ return false;
+ }
+ if (hashCode != pk.hashCode) {
+ return false;
+ }
+ return iPub.Eq(pk.iPub);
+ }
+
+ public override int GetHashCode()
+ {
+ return hashCode;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * GHASH implementation using integer multiplications (32->64). It is
+ * constant-time, provided that the platform multiplication opcode is
+ * constant-time.
+ */
+
+public sealed class GHASH {
+
+ /*
+ * Compute GHASH over the provided data. The y[] array is
+ * updated, using the h[] secret value. If the data length
+ * is not a multiple of 16 bytes, then it is right-padded
+ * with zeros.
+ */
+ public static void Run(byte[] y, byte[] h, byte[] data)
+ {
+ Run(y, h, data, 0, data.Length);
+ }
+
+ /*
+ * Compute GHASH over the provided data. The y[] array is
+ * updated, using the h[] secret value. If the data length
+ * is not a multiple of 16 bytes, then it is right-padded
+ * with zeros.
+ */
+ public static void Run(byte[] y, byte[] h,
+ byte[] data, int off, int len)
+ {
+ uint y0, y1, y2, y3;
+ uint h0, h1, h2, h3;
+ y3 = Dec32be(y, 0);
+ y2 = Dec32be(y, 4);
+ y1 = Dec32be(y, 8);
+ y0 = Dec32be(y, 12);
+ h3 = Dec32be(h, 0);
+ h2 = Dec32be(h, 4);
+ h1 = Dec32be(h, 8);
+ h0 = Dec32be(h, 12);
+
+ while (len > 0) {
+ /*
+ * Decode the next block and add it (XOR) into the
+ * current state.
+ */
+ if (len >= 16) {
+ y3 ^= Dec32be(data, off);
+ y2 ^= Dec32be(data, off + 4);
+ y1 ^= Dec32be(data, off + 8);
+ y0 ^= Dec32be(data, off + 12);
+ } else {
+ y3 ^= Dec32bePartial(data, off + 0, len - 0);
+ y2 ^= Dec32bePartial(data, off + 4, len - 4);
+ y1 ^= Dec32bePartial(data, off + 8, len - 8);
+ y0 ^= Dec32bePartial(data, off + 12, len - 12);
+ }
+ off += 16;
+ len -= 16;
+
+ /*
+ * We multiply two 128-bit field elements with
+ * two Karatsuba levels, to get down to nine
+ * 32->64 multiplications.
+ */
+ uint a0 = y0;
+ uint b0 = h0;
+ uint a1 = y1;
+ uint b1 = h1;
+ uint a2 = a0 ^ a1;
+ uint b2 = b0 ^ b1;
+
+ uint a3 = y2;
+ uint b3 = h2;
+ uint a4 = y3;
+ uint b4 = h3;
+ uint a5 = a3 ^ a4;
+ uint b5 = b3 ^ b4;
+
+ uint a6 = a0 ^ a3;
+ uint b6 = b0 ^ b3;
+ uint a7 = a1 ^ a4;
+ uint b7 = b1 ^ b4;
+ uint a8 = a6 ^ a7;
+ uint b8 = b6 ^ b7;
+
+ ulong z0 = BMul(a0, b0);
+ ulong z1 = BMul(a1, b1);
+ ulong z2 = BMul(a2, b2);
+ ulong z3 = BMul(a3, b3);
+ ulong z4 = BMul(a4, b4);
+ ulong z5 = BMul(a5, b5);
+ ulong z6 = BMul(a6, b6);
+ ulong z7 = BMul(a7, b7);
+ ulong z8 = BMul(a8, b8);
+
+ z2 ^= z0 ^ z1;
+ z0 ^= z2 << 32;
+ z1 ^= z2 >> 32;
+
+ z5 ^= z3 ^ z4;
+ z3 ^= z5 << 32;
+ z4 ^= z5 >> 32;
+
+ z8 ^= z6 ^ z7;
+ z6 ^= z8 << 32;
+ z7 ^= z8 >> 32;
+
+ z6 ^= z0 ^ z3;
+ z7 ^= z1 ^ z4;
+ z1 ^= z6;
+ z3 ^= z7;
+
+ /*
+ * 255-bit product is now in z4:z3:z1:z0. Since
+ * the GHASH specification uses a "reversed"
+ * notation, our 255-bit result must be shifted
+ * by 1 bit to the left.
+ */
+ z4 = (z4 << 1) | (z3 >> 63);
+ z3 = (z3 << 1) | (z1 >> 63);
+ z1 = (z1 << 1) | (z0 >> 63);
+ z0 = (z0 << 1);
+
+ /*
+ * Apply reduction modulo the degree-128
+ * polynomial that defines the field.
+ */
+ z3 ^= z0 ^ (z0 >> 1) ^ (z0 >> 2) ^ (z0 >> 7);
+ z1 ^= (z0 << 63) ^ (z0 << 62) ^ (z0 << 57);
+ z4 ^= z1 ^ (z1 >> 1) ^ (z1 >> 2) ^ (z1 >> 7);
+ z3 ^= (z1 << 63) ^ (z1 << 62) ^ (z1 << 57);
+
+ /*
+ * The reduced result is the new "y" state.
+ */
+ y0 = (uint)z3;
+ y1 = (uint)(z3 >> 32);
+ y2 = (uint)z4;
+ y3 = (uint)(z4 >> 32);
+ }
+
+ Enc32be(y3, y, 0);
+ Enc32be(y2, y, 4);
+ Enc32be(y1, y, 8);
+ Enc32be(y0, y, 12);
+ }
+
+ static ulong BMul(uint x, uint y)
+ {
+ ulong x0, x1, x2, x3;
+ ulong y0, y1, y2, y3;
+ ulong z0, z1, z2, z3;
+ x0 = (ulong)(x & 0x11111111);
+ x1 = (ulong)(x & 0x22222222);
+ x2 = (ulong)(x & 0x44444444);
+ x3 = (ulong)(x & 0x88888888);
+ y0 = (ulong)(y & 0x11111111);
+ y1 = (ulong)(y & 0x22222222);
+ y2 = (ulong)(y & 0x44444444);
+ y3 = (ulong)(y & 0x88888888);
+ z0 = (x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1);
+ z1 = (x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2);
+ z2 = (x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3);
+ z3 = (x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0);
+ z0 &= 0x1111111111111111;
+ z1 &= 0x2222222222222222;
+ z2 &= 0x4444444444444444;
+ z3 &= 0x8888888888888888;
+ return z0 | z1 | z2 | z3;
+ }
+
+ static uint Dec32be(byte[] buf, int off)
+ {
+ return ((uint)buf[off + 0] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ }
+
+ static void Enc32be(uint x, byte[] buf, int off)
+ {
+ buf[off + 0] = (byte)(x >> 24);
+ buf[off + 1] = (byte)(x >> 16);
+ buf[off + 2] = (byte)(x >> 8);
+ buf[off + 3] = (byte)x;
+ }
+
+ static uint Dec32bePartial(byte[] buf, int off, int len)
+ {
+ if (len >= 4) {
+ return ((uint)buf[off + 0] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ } else if (len >= 3) {
+ return ((uint)buf[off + 0] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8);
+ } else if (len >= 2) {
+ return ((uint)buf[off + 0] << 24)
+ | ((uint)buf[off + 1] << 16);
+ } else if (len >= 1) {
+ return ((uint)buf[off + 0] << 24);
+ } else {
+ return 0;
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of HMAC (RFC 2104).
+ */
+
+public sealed class HMAC {
+
+ IDigest h;
+ byte[] key;
+ int keyLen;
+ byte[] tmp, tmpCT;
+ ulong dataLen;
+
+ /*
+ * Create a new instance, using the provided hash function. The
+ * hash function instance will be used internally.
+ */
+ public HMAC(IDigest h) : this(h, true)
+ {
+ }
+
+ private HMAC(IDigest h, bool doReset)
+ {
+ this.h = h;
+ int n = h.BlockSize;
+ if (n < h.DigestSize) {
+ throw new ArgumentException(
+ "invalid hash function for HMAC");
+ }
+ key = new byte[n];
+ keyLen = 0;
+ tmp = new byte[h.DigestSize];
+ tmpCT = new byte[h.DigestSize];
+ if (doReset) {
+ Reset();
+ }
+ }
+
+ /*
+ * Duplicate this engine. The returned instance inherits the
+ * current state (key, data already processed...) but is
+ * thereafter independent.
+ */
+ public HMAC Dup()
+ {
+ HMAC hm = new HMAC(h.Dup(), false);
+ Array.Copy(key, 0, hm.key, 0, keyLen);
+ hm.keyLen = keyLen;
+ hm.dataLen = dataLen;
+ return hm;
+ }
+
+ /*
+ * Get the HMAC output size (in bytes).
+ */
+ public int MACSize {
+ get {
+ return h.DigestSize;
+ }
+ }
+
+ /*
+ * Set the HMAC key. This implies a call to Reset() (thus,
+ * the key must be set before data begins to be inserted).
+ */
+ public void SetKey(byte[] key)
+ {
+ SetKey(key, 0, key.Length);
+ }
+
+ /*
+ * Set the HMAC key ('len' bytes, starting at offset 'off' in key[]).
+ */
+ public void SetKey(byte[] key, int off, int len)
+ {
+ h.Reset();
+ if (len > h.BlockSize) {
+ h.Update(key, off, len);
+ h.DoFinal(this.key, 0);
+ keyLen = h.DigestSize;
+ } else {
+ Array.Copy(key, off, this.key, 0, len);
+ keyLen = len;
+ }
+ Reset();
+ }
+
+ /*
+ * Add one byte to the input to HMAC.
+ */
+ public void Update(byte b)
+ {
+ h.Update(b);
+ dataLen ++;
+ }
+
+ /*
+ * Add some bytes to the input to HMAC.
+ */
+ public void Update(byte[] buf)
+ {
+ h.Update(buf, 0, buf.Length);
+ }
+
+ /*
+ * Add some bytes to the input to HMAC ('len' bytes from buf[],
+ * starting at offset 'off').
+ */
+ public void Update(byte[] buf, int off, int len)
+ {
+ h.Update(buf, off, len);
+ dataLen += (ulong)len;
+ }
+
+ /*
+ * Compute the HMAC value; it is written in outBuf[] at
+ * offset 'off'. The engine is also reset (as if Reset()
+ * was called).
+ */
+ public void DoFinal(byte[] outBuf, int off)
+ {
+ h.DoFinal(tmp, 0);
+ ProcessKey(0x5C);
+ h.Update(tmp);
+ h.DoFinal(outBuf, off);
+ Reset();
+ }
+
+ /*
+ * Compute the HMAC value; it is written to a newly allocated
+ * array, which is returned. The engine is also reset (as if
+ * Reset() was called).
+ */
+ public byte[] DoFinal()
+ {
+ byte[] r = new byte[h.DigestSize];
+ DoFinal(r, 0);
+ return r;
+ }
+
+ /*
+ * Reset the HMAC engine. This forgets all previously input
+ * data, but reuses the currently set key.
+ */
+ public void Reset()
+ {
+ h.Reset();
+ ProcessKey(0x36);
+ dataLen = 0;
+ }
+
+ /*
+ * Process some bytes, then compute the output.
+ *
+ * This function is supposed to implement the processing in
+ * constant time (and thus constant memory access pattern) for
+ * all values of 'len' between 'minLen' and 'maxLen'
+ * (inclusive). This function works only for the supported
+ * underlying hash functions (MD5, SHA-1 and the SHA-2
+ * functions).
+ *
+ * The source array (buf[]) must contain at least maxLen bytes
+ * (starting at offset 'off'); they will all be read.
+ */
+ public void ComputeCT(byte[] buf, int off, int len,
+ int minLen, int maxLen, byte[] outBuf, int outOff)
+ {
+ /*
+ * Padding is 0x80, followed by 0 to 63 bytes of value
+ * 0x00 (up to 127 bytes for SHA-384 and SHA-512), then
+ * the input bit length expressed over 64 bits
+ * (little-endian for MD5, big-endian for
+ * the SHA-* functions)(for SHA-384 and SHA-512, this is
+ * 128 bits).
+ *
+ * Note that we only support bit lengths that fit on
+ * 64 bits, so we can handle SHA-384/SHA-512 padding
+ * almost as if it was the same as SHA-256; we just have
+ * to take care of the larger blocks (128 bytes instead
+ * of 64) and the larger minimal overhead (17 bytes
+ * instead of 9 bytes).
+ *
+ * be true for big-endian length encoding
+ * bs block size, in bytes (must be a power of 2)
+ * po padding overhead (0x80 byte and length encoding)
+ */
+ bool be;
+ int bs, po;
+ if (h is MD5) {
+ be = false;
+ bs = 64;
+ po = 9;
+ } else if ((h is SHA1) || (h is SHA2Small)) {
+ be = true;
+ bs = 64;
+ po = 9;
+ } else if (h is SHA2Big) {
+ be = true;
+ bs = 128;
+ po = 17;
+ } else {
+ throw new NotSupportedException();
+ }
+
+ /*
+ * Method implemented here is inspired from the one
+ * described there:
+ * https://www.imperialviolet.org/2013/02/04/luckythirteen.html
+ */
+
+ /*
+ * We compute the data bit length; let's not forget
+ * the initial first block (the one with the HMAC key).
+ */
+ ulong bitLen = ((ulong)bs + dataLen + (ulong)len) << 3;
+
+ /*
+ * All complete blocks before minLen can be processed
+ * efficiently.
+ */
+ ulong nDataLen = (dataLen + (ulong)minLen) & ~(ulong)(bs - 1);
+ if (nDataLen > dataLen) {
+ int zlen = (int)(nDataLen - dataLen);
+ h.Update(buf, off, (int)(nDataLen - dataLen));
+ dataLen = nDataLen;
+ off += zlen;
+ len -= zlen;
+ maxLen -= zlen;
+ }
+
+ /*
+ * At that point:
+ * -- dataLen contains the number of bytes already processed
+ * (in total, not counting the initial key block).
+ * -- We must input 'len' bytes, which may be up to 'maxLen'
+ * (inclusive).
+ *
+ * We compute kr, kl, kz and km:
+ * kr number of input bytes already in the current block
+ * km index of the first byte after the end of the last
+ * padding block, if 'len' is equal to 'maxLen'
+ * kz index of the last byte of the actual last padding
+ * block
+ * kl index of the start of the encoded length
+ */
+ int kr = (int)dataLen & (bs - 1);
+ int kz = ((kr + len + po + bs - 1) & ~(bs - 1)) - 1 - kr;
+ int kl = kz - 7;
+ int km = ((kr + maxLen + po + bs - 1) & ~(bs - 1)) - kr;
+
+ /*
+ * We must process km bytes. For index i from 0 to km-1:
+ * d is from data[] if i < maxLen, 0x00 otherwise
+ * e is an encoded length byte or 0x00, depending on i
+ * These tests do not depend on the actual length, so
+ * they need not be constant-time.
+ *
+ * Actual input byte is:
+ * d if i < len
+ * 0x80 if i == len
+ * 0x00 if i > len and i < kl
+ * e if i >= kl
+ *
+ * We extract hash state whenever we reach a full block;
+ * we keep it only if i == kz.
+ */
+ int hlen = h.DigestSize;
+ for (int k = 0; k < hlen; k ++) {
+ tmp[k] = 0;
+ }
+ for (int i = 0; i < km; i ++) {
+ int d = (i < maxLen) ? buf[off + i] : 0x00;
+ int e;
+ int j = (kr + i) & (bs - 1);
+ if (j >= (bs - 8)) {
+ int k = (j - (bs - 8)) << 3;
+ if (be) {
+ e = (int)(bitLen >> (56 - k));
+ } else {
+ e = (int)(bitLen >> k);
+ }
+ e &= 0xFF;
+ } else {
+ e = 0x00;
+ }
+
+ /*
+ * x0 is 0x80 if i == len; otherwise it is d.
+ */
+ int z = i - len;
+ int x0 = 0x80 ^ (((z | -z) >> 31) & (0x80 ^ d));
+
+ /*
+ * x1 is e if i >= kl; otherwise it is 0x00.
+ */
+ int x1 = e & ~((i - kl) >> 31);
+
+ /*
+ * We input x0 if i <= len, x1 otherwise.
+ */
+ h.Update((byte)(x0 ^ (((len - i) >> 31) & (x0 ^ x1))));
+
+ /*
+ * Get current state if we are at the end of a block,
+ * and keep it if i == kz.
+ */
+ if (j == (bs - 1)) {
+ h.CurrentState(tmpCT, 0);
+ z = i - kz;
+ z = ~((z | -z) >> 31);
+ for (int k = 0; k < hlen; k ++) {
+ tmp[k] |= (byte)(z & tmpCT[k]);
+ }
+ }
+ }
+
+ /*
+ * We got the hash output in tmp[]; we must complete
+ * the HMAC computation.
+ */
+ h.Reset();
+ ProcessKey(0x5C);
+ h.Update(tmp);
+ h.DoFinal(outBuf, outOff);
+ Reset();
+ }
+
+ void ProcessKey(byte pad)
+ {
+ for (int i = 0; i < keyLen; i ++) {
+ h.Update((byte)(key[i] ^ pad));
+ }
+ for (int i = h.BlockSize - keyLen; i > 0; i --) {
+ h.Update(pad);
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of HMAC_DRBG (NIST SP800-90A).
+ *
+ * This class provides HMAC_DRBG as a deterministic PRNG from a given
+ * seed. Once a seed is set, chunks of data are obtained with
+ * GetBytes(). The GetBytes() methods can be called several times;
+ * the internal state is updated after each call. Setting a new seed
+ * resets the internal state.
+ */
+
+public sealed class HMAC_DRBG {
+
+ HMAC hm;
+ byte[] K, V;
+ bool seeded;
+
+ /*
+ * Create the instance over the provided hash function
+ * implementation. The digest instance is linked in and will
+ * be used repeatedly. The engine is not seeded yet.
+ */
+ public HMAC_DRBG(IDigest h)
+ {
+ hm = new HMAC(h.Dup());
+ int len = h.DigestSize;
+ K = new byte[len];
+ V = new byte[len];
+ seeded = false;
+ Reset();
+ }
+
+ /*
+ * Reset the engine. A seed will have to be provided before
+ * generating pseudorandom bytes.
+ */
+ public void Reset()
+ {
+ for (int i = 0; i < K.Length; i ++) {
+ K[i] = 0x00;
+ V[i] = 0x01;
+ }
+ hm.SetKey(K);
+ }
+
+ /*
+ * Reset the engine with the provided seed.
+ */
+ public void SetSeed(byte[] seed)
+ {
+ Reset();
+ Update(seed);
+ }
+
+ /*
+ * Reset the engine with the provided seed.
+ */
+ public void SetSeed(byte[] seed, int off, int len)
+ {
+ Reset();
+ Update(seed, off, len);
+ }
+
+ /*
+ * Inject an additional seed. This may be null, in which case
+ * the state is modified but the engine is not marked as "seeded"
+ * (if it was not already marked so).
+ */
+ public void Update(byte[] seed)
+ {
+ if (seed != null) {
+ Update(seed, 0, seed.Length);
+ } else {
+ Update(null, 0, 0);
+ }
+ }
+
+ /*
+ * Inject an additional seed. If the seed length is 0, then the
+ * state is modified, but the engine is not marked as "seeded"
+ * (if it was not already marked so).
+ */
+ public void Update(byte[] seed, int off, int len)
+ {
+ /* K = HMAC_K(V || 0x00 || seed) */
+ hm.Update(V);
+ hm.Update((byte)0x00);
+ hm.Update(seed, off, len);
+ hm.DoFinal(K, 0);
+ hm.SetKey(K);
+
+ /* V = HMAC_K(V) */
+ hm.Update(V);
+ hm.DoFinal(V, 0);
+
+ /*
+ * Stop there if the additional seed is empty.
+ */
+ if (len == 0) {
+ return;
+ }
+
+ /* K = HMAC_K(V || 0x01 || seed) */
+ hm.Update(V);
+ hm.Update((byte)0x01);
+ hm.Update(seed, off, len);
+ hm.DoFinal(K, 0);
+ hm.SetKey(K);
+
+ /* V = HMAC_K(V) */
+ hm.Update(V);
+ hm.DoFinal(V, 0);
+
+ /*
+ * We get there only if a non-empty seed is used.
+ */
+ seeded = true;
+ }
+
+ /*
+ * Generate some pseudorandom bytes. The engine MUST have been
+ * seeded.
+ */
+ public void GetBytes(byte[] buf)
+ {
+ GetBytes(buf, 0, buf.Length);
+ }
+
+ /*
+ * Generate some pseudorandom bytes. The engine MUST have been
+ * seeded.
+ */
+ public void GetBytes(byte[] buf, int off, int len)
+ {
+ if (!seeded) {
+ throw new CryptoException(
+ "HMAC_DRBG engine was not seeded");
+ }
+ while (len > 0) {
+ /* V = HMAC_K(V) */
+ hm.Update(V);
+ hm.DoFinal(V, 0);
+ int clen = Math.Min(V.Length, len);
+ Array.Copy(V, 0, buf, off, clen);
+ off += clen;
+ len -= clen;
+ }
+
+ /* K = HMAC_K(V || 0x00) */
+ hm.Update(V);
+ hm.Update((byte)0x00);
+ hm.DoFinal(K, 0);
+ hm.SetKey(K);
+
+ /* V = HMAC_K(V) */
+ hm.Update(V);
+ hm.DoFinal(V, 0);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Interface for a block cipher implementation. Each instance has a
+ * state, which contains (at least) the current secret key, but may also
+ * have other internal values. Thereby, instances are not thread-safe.
+ * The Dup() method may be used to "clone" an instance into a new,
+ * independent instance that starts its life configured with the same
+ * secret key.
+ */
+
+public interface IBlockCipher {
+
+ /*
+ * Get the block size in bytes.
+ */
+ int BlockSize { get; }
+
+ /*
+ * Set the key.
+ */
+ void SetKey(byte[] key);
+
+ /*
+ * Set the key.
+ */
+ void SetKey(byte[] key, int off, int len);
+
+ /*
+ * Encrypt one block.
+ */
+ void BlockEncrypt(byte[] buf);
+
+ /*
+ * Encrypt one block.
+ */
+ void BlockEncrypt(byte[] buf, int off);
+
+ /*
+ * Decrypt one block.
+ */
+ void BlockDecrypt(byte[] buf);
+
+ /*
+ * Encrypt one block.
+ */
+ void BlockDecrypt(byte[] buf, int off);
+
+ /*
+ * Do CBC encryption. There is no padding; the source array
+ * must already have a length multiple of the block size.
+ * The provided iv[] array must have the same length as a
+ * block. The data is encrypted in-place. The iv[] array is
+ * unmodified.
+ */
+ void CBCEncrypt(byte[] iv, byte[] data);
+
+ /*
+ * Do CBC encryption. There is no padding; the source array
+ * must already have a length multiple of the block size.
+ * The provided iv[] array must have the same length as a
+ * block. The data is encrypted in-place. The iv[] array is
+ * unmodified.
+ */
+ void CBCEncrypt(byte[] iv, byte[] data, int off, int len);
+
+ /*
+ * Do CBC decryption. The source array must have a length
+ * multiple of the block size; no attempt at padding removal is
+ * performed. The provided iv[] array must have the same length
+ * as a block. The data is decrypted in-place. The iv[] array is
+ * unmodified.
+ */
+ void CBCDecrypt(byte[] iv, byte[] data);
+
+ /*
+ * Do CBC decryption. The source array must have a length
+ * multiple of the block size; no attempt at padding removal is
+ * performed. The provided iv[] array must have the same length
+ * as a block. The data is decrypted in-place. The iv[] array is
+ * unmodified.
+ */
+ void CBCDecrypt(byte[] iv, byte[] data, int off, int len);
+
+ /*
+ * Do CTR encryption or decryption. This implements the variant
+ * used in GCM:
+ * - IV length is 4 bytes less than the block length
+ * - The block counter is used for the last 4 bytes of the
+ * block input; big-endian encoding is used.
+ * - Counter arithmetic is done modulo 2^32.
+ *
+ * The starting counter value is provided as parameter; the new
+ * counter value is returned. This allows computing a long CTR
+ * run in several chunks, as long as all chunks (except possibly
+ * the last one) have a length which is multiple of the block size.
+ */
+ uint CTRRun(byte[] iv, uint cc, byte[] data);
+
+ /*
+ * Do CTR encryption or decryption. This implements the variant
+ * used in GCM:
+ * - IV length is 4 bytes less than the block length
+ * - The block counter is used for the last 4 bytes of the
+ * block input; big-endian encoding is used.
+ * - Counter arithmetic is done modulo 2^32.
+ *
+ * The starting counter value is provided as parameter; the new
+ * counter value is returned. This allows computing a long CTR
+ * run in several chunks, as long as all chunks (except possibly
+ * the last one) have a length which is multiple of the block size.
+ */
+ uint CTRRun(byte[] iv, uint cc, byte[] data, int off, int len);
+
+ /*
+ * Duplicate this engine. This creates a new, independent
+ * instance that implements the same function, and starts with
+ * the same currently set key.
+ */
+ IBlockCipher Dup();
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This interface qualifies a hash function implementation.
+ */
+
+public interface IDigest {
+
+ /*
+ * Get the hash function symbolic name.
+ */
+ string Name { get; }
+
+ /*
+ * Get the hash function output size, in bytes.
+ */
+ int DigestSize { get; }
+
+ /*
+ * Get the hash function block size, in bytes (the block
+ * size is used in the definition of HMAC).
+ */
+ int BlockSize { get; }
+
+ /*
+ * Add one byte to the current input.
+ */
+ void Update(byte b);
+
+ /*
+ * Add some bytes to the current input.
+ */
+ void Update(byte[] buf);
+
+ /*
+ * Add some bytes to the current input ('len' bytes from buf[],
+ * starting at offset 'off').
+ */
+ void Update(byte[] buf, int off, int len);
+
+ /*
+ * Finalize the hash computation and write the output in the
+ * provided outBuf[] array (starting at offset 'off'). This
+ * instance is also automatically reset (as if by a Reset() call).
+ */
+ void DoFinal(byte[] outBuf, int off);
+
+ /*
+ * Finalize the hash computation and write the output into a
+ * newly allocated buffer, which is returned. This instance
+ * is also automatically reset (as if by a Reset() call).
+ */
+ byte[] DoFinal();
+
+ /*
+ * Finalize the current hash computation but keep it active;
+ * this thus returns the hash value computed over the input
+ * bytes injected so far, but new bytes may be added afterwards.
+ * The output is written in outBuf[] at offset 'off'.
+ */
+ void DoPartial(byte[] outBuf, int off);
+
+ /*
+ * Finalize the current hash computation but keep it active;
+ * this thus returns the hash value computed over the input
+ * bytes injected so far, but new bytes may be added afterwards.
+ * The output is written into a newly allocated array, which
+ * is returned.
+ */
+ byte[] DoPartial();
+
+ /*
+ * Encode the current running state into the provided buffer.
+ * This is defined for functions that employ an internal padding
+ * but no special finalization step (e.g. MD5, SHA-1, SHA-256);
+ * the running state is the one resulting from the last
+ * processed block.
+ *
+ * Note: for SHA-224, SHA-384 and similar functions, the current
+ * state as returned by this method will be truncated to the
+ * actual hash output length.
+ */
+ void CurrentState(byte[] outBuf, int off);
+
+ /*
+ * Reset the internal state, to start a new computation. This
+ * can be called at any time.
+ */
+ void Reset();
+
+ /*
+ * Duplicate this engine. This creates a new, independent
+ * instance that implements the same function, and starts with
+ * the current state.
+ */
+ IDigest Dup();
+
+ /*
+ * Compute the hash of a given input in one call; this does NOT
+ * change the internal state.
+ */
+ byte[] Hash(byte[] buf);
+
+ /*
+ * Compute the hash of a given input in one call; this does NOT
+ * change the internal state.
+ */
+ byte[] Hash(byte[] buf, int off, int len);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Interface of an asymmetric private key.
+ */
+
+public interface IPrivateKey {
+
+ /*
+ * Private key size, expressed in bits. Exact definition of the
+ * "size" depends on the key type.
+ */
+ int KeySizeBits { get; }
+
+ /*
+ * Algorithm name (for display).
+ */
+ string AlgorithmName { get; }
+
+ /*
+ * Get (or compute) the public key for this private key.
+ */
+ IPublicKey PublicKey { get; }
+
+ /*
+ * Perform algorithm-dependent consistency checks. These
+ * checks may be relatively expensive. On any error, a
+ * CryptoException is thrown.
+ */
+ void CheckValid();
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Interface for an asymmetric public key.
+ */
+
+public interface IPublicKey {
+
+ /*
+ * Public key size, expressed in bits. Exact definition of the
+ * "size" depends on the key type.
+ */
+ int KeySizeBits { get; }
+
+ /*
+ * Algorithm name (for display).
+ */
+ string AlgorithmName { get; }
+
+ /*
+ * Perform algorithm-dependent consistency checks. These
+ * checks may be relatively expensive. On any error, a
+ * CryptoException is thrown.
+ */
+ void CheckValid();
+
+ /*
+ * Public keys ought to be comparable for equality.
+ */
+ bool Equals(object obj);
+ int GetHashCode();
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * MD5 implementation. MD5 is described in RFC 1321.
+ *
+ * WARNING: as a cryptographic hash function, MD5 turned out to be
+ * very weak with regards to collisions. Use with care.
+ */
+
+public sealed class MD5 : DigestCore {
+
+ const int BLOCK_LEN = 64;
+
+ uint A, B, C, D;
+ byte[] block, saveBlock;
+ int ptr;
+ ulong byteCount;
+
+ /*
+ * Create a new instance. It is ready to process data bytes.
+ */
+ public MD5()
+ {
+ block = new byte[BLOCK_LEN];
+ saveBlock = new byte[BLOCK_LEN];
+ Reset();
+ }
+
+ /* see IDigest */
+ public override string Name {
+ get {
+ return "MD5";
+ }
+ }
+
+ /* see IDigest */
+ public override int DigestSize {
+ get {
+ return 16;
+ }
+ }
+
+ /* see IDigest */
+ public override int BlockSize {
+ get {
+ return 64;
+ }
+ }
+
+ /* see IDigest */
+ public override void Reset()
+ {
+ A = 0x67452301;
+ B = 0xEFCDAB89;
+ C = 0x98BADCFE;
+ D = 0x10325476;
+ byteCount = 0;
+ ptr = 0;
+ }
+
+ /* see IDigest */
+ public override void Update(byte b)
+ {
+ block[ptr ++] = b;
+ byteCount ++;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+
+ /* see IDigest */
+ public override void Update(byte[] buf, int off, int len)
+ {
+ if (len < 0) {
+ throw new ArgumentException("negative chunk length");
+ }
+ byteCount += (ulong)len;
+ while (len > 0) {
+ int clen = Math.Min(len, BLOCK_LEN - ptr);
+ Array.Copy(buf, off, block, ptr, clen);
+ off += clen;
+ len -= clen;
+ ptr += clen;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+ }
+
+ /* see IDigest */
+ public override void DoPartial(byte[] outBuf, int off)
+ {
+ /*
+ * Save current state.
+ */
+ uint saveA = A;
+ uint saveB = B;
+ uint saveC = C;
+ uint saveD = D;
+ int savePtr = ptr;
+ Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+ /*
+ * Add padding. This may involve processing an extra block.
+ */
+ block[ptr ++] = 0x80;
+ if (ptr > BLOCK_LEN - 8) {
+ for (int j = ptr; j < BLOCK_LEN; j ++) {
+ block[j] = 0;
+ }
+ ProcessBlock();
+ }
+ for (int j = ptr; j < (BLOCK_LEN - 8); j ++) {
+ block[j] = 0;
+ }
+ ulong x = byteCount << 3;
+ Enc32le((uint)x, block, BLOCK_LEN - 8);
+ Enc32le((uint)(x >> 32), block, BLOCK_LEN - 4);
+
+ /*
+ * Process final block and encode result.
+ */
+ ProcessBlock();
+ Enc32le(A, outBuf, off);
+ Enc32le(B, outBuf, off + 4);
+ Enc32le(C, outBuf, off + 8);
+ Enc32le(D, outBuf, off + 12);
+
+ /*
+ * Restore current state.
+ */
+ Array.Copy(saveBlock, 0, block, 0, savePtr);
+ A = saveA;
+ B = saveB;
+ C = saveC;
+ D = saveD;
+ ptr = savePtr;
+ }
+
+ /* see IDigest */
+ public override IDigest Dup()
+ {
+ MD5 h = new MD5();
+ h.A = A;
+ h.B = B;
+ h.C = C;
+ h.D = D;
+ h.ptr = ptr;
+ h.byteCount = byteCount;
+ Array.Copy(block, 0, h.block, 0, ptr);
+ return h;
+ }
+
+ /* see IDigest */
+ public override void CurrentState(byte[] outBuf, int off)
+ {
+ Enc32le(A, outBuf, off);
+ Enc32le(B, outBuf, off + 4);
+ Enc32le(C, outBuf, off + 8);
+ Enc32le(D, outBuf, off + 12);
+ }
+
+ void ProcessBlock()
+ {
+ /*
+ * Decode input block (sixteen 32-bit words).
+ */
+ uint x0 = Dec32le(block, 0);
+ uint x1 = Dec32le(block, 4);
+ uint x2 = Dec32le(block, 8);
+ uint x3 = Dec32le(block, 12);
+ uint x4 = Dec32le(block, 16);
+ uint x5 = Dec32le(block, 20);
+ uint x6 = Dec32le(block, 24);
+ uint x7 = Dec32le(block, 28);
+ uint x8 = Dec32le(block, 32);
+ uint x9 = Dec32le(block, 36);
+ uint xA = Dec32le(block, 40);
+ uint xB = Dec32le(block, 44);
+ uint xC = Dec32le(block, 48);
+ uint xD = Dec32le(block, 52);
+ uint xE = Dec32le(block, 56);
+ uint xF = Dec32le(block, 60);
+
+ /*
+ * Read state words.
+ */
+ uint wa = A;
+ uint wb = B;
+ uint wc = C;
+ uint wd = D;
+ uint tmp;
+
+ /*
+ * Rounds 0 to 15.
+ */
+ tmp = wa + (wd ^ (wb & (wc ^ wd))) + x0 + 0xD76AA478;
+ wa = wb + ((tmp << 7) | (tmp >> 25));
+ tmp = wd + (wc ^ (wa & (wb ^ wc))) + x1 + 0xE8C7B756;
+ wd = wa + ((tmp << 12) | (tmp >> 20));
+ tmp = wc + (wb ^ (wd & (wa ^ wb))) + x2 + 0x242070DB;
+ wc = wd + ((tmp << 17) | (tmp >> 15));
+ tmp = wb + (wa ^ (wc & (wd ^ wa))) + x3 + 0xC1BDCEEE;
+ wb = wc + ((tmp << 22) | (tmp >> 10));
+ tmp = wa + (wd ^ (wb & (wc ^ wd))) + x4 + 0xF57C0FAF;
+ wa = wb + ((tmp << 7) | (tmp >> 25));
+ tmp = wd + (wc ^ (wa & (wb ^ wc))) + x5 + 0x4787C62A;
+ wd = wa + ((tmp << 12) | (tmp >> 20));
+ tmp = wc + (wb ^ (wd & (wa ^ wb))) + x6 + 0xA8304613;
+ wc = wd + ((tmp << 17) | (tmp >> 15));
+ tmp = wb + (wa ^ (wc & (wd ^ wa))) + x7 + 0xFD469501;
+ wb = wc + ((tmp << 22) | (tmp >> 10));
+ tmp = wa + (wd ^ (wb & (wc ^ wd))) + x8 + 0x698098D8;
+ wa = wb + ((tmp << 7) | (tmp >> 25));
+ tmp = wd + (wc ^ (wa & (wb ^ wc))) + x9 + 0x8B44F7AF;
+ wd = wa + ((tmp << 12) | (tmp >> 20));
+ tmp = wc + (wb ^ (wd & (wa ^ wb))) + xA + 0xFFFF5BB1;
+ wc = wd + ((tmp << 17) | (tmp >> 15));
+ tmp = wb + (wa ^ (wc & (wd ^ wa))) + xB + 0x895CD7BE;
+ wb = wc + ((tmp << 22) | (tmp >> 10));
+ tmp = wa + (wd ^ (wb & (wc ^ wd))) + xC + 0x6B901122;
+ wa = wb + ((tmp << 7) | (tmp >> 25));
+ tmp = wd + (wc ^ (wa & (wb ^ wc))) + xD + 0xFD987193;
+ wd = wa + ((tmp << 12) | (tmp >> 20));
+ tmp = wc + (wb ^ (wd & (wa ^ wb))) + xE + 0xA679438E;
+ wc = wd + ((tmp << 17) | (tmp >> 15));
+ tmp = wb + (wa ^ (wc & (wd ^ wa))) + xF + 0x49B40821;
+ wb = wc + ((tmp << 22) | (tmp >> 10));
+
+ /*
+ * Rounds 16 to 31.
+ */
+ tmp = wa + (wc ^ (wd & (wb ^ wc))) + x1 + 0xF61E2562;
+ wa = wb + ((tmp << 5) | (tmp >> 27));
+ tmp = wd + (wb ^ (wc & (wa ^ wb))) + x6 + 0xC040B340;
+ wd = wa + ((tmp << 9) | (tmp >> 23));
+ tmp = wc + (wa ^ (wb & (wd ^ wa))) + xB + 0x265E5A51;
+ wc = wd + ((tmp << 14) | (tmp >> 18));
+ tmp = wb + (wd ^ (wa & (wc ^ wd))) + x0 + 0xE9B6C7AA;
+ wb = wc + ((tmp << 20) | (tmp >> 12));
+ tmp = wa + (wc ^ (wd & (wb ^ wc))) + x5 + 0xD62F105D;
+ wa = wb + ((tmp << 5) | (tmp >> 27));
+ tmp = wd + (wb ^ (wc & (wa ^ wb))) + xA + 0x02441453;
+ wd = wa + ((tmp << 9) | (tmp >> 23));
+ tmp = wc + (wa ^ (wb & (wd ^ wa))) + xF + 0xD8A1E681;
+ wc = wd + ((tmp << 14) | (tmp >> 18));
+ tmp = wb + (wd ^ (wa & (wc ^ wd))) + x4 + 0xE7D3FBC8;
+ wb = wc + ((tmp << 20) | (tmp >> 12));
+ tmp = wa + (wc ^ (wd & (wb ^ wc))) + x9 + 0x21E1CDE6;
+ wa = wb + ((tmp << 5) | (tmp >> 27));
+ tmp = wd + (wb ^ (wc & (wa ^ wb))) + xE + 0xC33707D6;
+ wd = wa + ((tmp << 9) | (tmp >> 23));
+ tmp = wc + (wa ^ (wb & (wd ^ wa))) + x3 + 0xF4D50D87;
+ wc = wd + ((tmp << 14) | (tmp >> 18));
+ tmp = wb + (wd ^ (wa & (wc ^ wd))) + x8 + 0x455A14ED;
+ wb = wc + ((tmp << 20) | (tmp >> 12));
+ tmp = wa + (wc ^ (wd & (wb ^ wc))) + xD + 0xA9E3E905;
+ wa = wb + ((tmp << 5) | (tmp >> 27));
+ tmp = wd + (wb ^ (wc & (wa ^ wb))) + x2 + 0xFCEFA3F8;
+ wd = wa + ((tmp << 9) | (tmp >> 23));
+ tmp = wc + (wa ^ (wb & (wd ^ wa))) + x7 + 0x676F02D9;
+ wc = wd + ((tmp << 14) | (tmp >> 18));
+ tmp = wb + (wd ^ (wa & (wc ^ wd))) + xC + 0x8D2A4C8A;
+ wb = wc + ((tmp << 20) | (tmp >> 12));
+
+ /*
+ * Rounds 32 to 47.
+ */
+ tmp = wa + (wb ^ wc ^ wd) + x5 + 0xFFFA3942;
+ wa = wb + ((tmp << 4) | (tmp >> 28));
+ tmp = wd + (wa ^ wb ^ wc) + x8 + 0x8771F681;
+ wd = wa + ((tmp << 11) | (tmp >> 21));
+ tmp = wc + (wd ^ wa ^ wb) + xB + 0x6D9D6122;
+ wc = wd + ((tmp << 16) | (tmp >> 16));
+ tmp = wb + (wc ^ wd ^ wa) + xE + 0xFDE5380C;
+ wb = wc + ((tmp << 23) | (tmp >> 9));
+ tmp = wa + (wb ^ wc ^ wd) + x1 + 0xA4BEEA44;
+ wa = wb + ((tmp << 4) | (tmp >> 28));
+ tmp = wd + (wa ^ wb ^ wc) + x4 + 0x4BDECFA9;
+ wd = wa + ((tmp << 11) | (tmp >> 21));
+ tmp = wc + (wd ^ wa ^ wb) + x7 + 0xF6BB4B60;
+ wc = wd + ((tmp << 16) | (tmp >> 16));
+ tmp = wb + (wc ^ wd ^ wa) + xA + 0xBEBFBC70;
+ wb = wc + ((tmp << 23) | (tmp >> 9));
+ tmp = wa + (wb ^ wc ^ wd) + xD + 0x289B7EC6;
+ wa = wb + ((tmp << 4) | (tmp >> 28));
+ tmp = wd + (wa ^ wb ^ wc) + x0 + 0xEAA127FA;
+ wd = wa + ((tmp << 11) | (tmp >> 21));
+ tmp = wc + (wd ^ wa ^ wb) + x3 + 0xD4EF3085;
+ wc = wd + ((tmp << 16) | (tmp >> 16));
+ tmp = wb + (wc ^ wd ^ wa) + x6 + 0x04881D05;
+ wb = wc + ((tmp << 23) | (tmp >> 9));
+ tmp = wa + (wb ^ wc ^ wd) + x9 + 0xD9D4D039;
+ wa = wb + ((tmp << 4) | (tmp >> 28));
+ tmp = wd + (wa ^ wb ^ wc) + xC + 0xE6DB99E5;
+ wd = wa + ((tmp << 11) | (tmp >> 21));
+ tmp = wc + (wd ^ wa ^ wb) + xF + 0x1FA27CF8;
+ wc = wd + ((tmp << 16) | (tmp >> 16));
+ tmp = wb + (wc ^ wd ^ wa) + x2 + 0xC4AC5665;
+ wb = wc + ((tmp << 23) | (tmp >> 9));
+
+ /*
+ * Rounds 48 to 63.
+ */
+ tmp = wa + (wc ^ (wb | ~wd)) + x0 + 0xF4292244;
+ wa = wb + ((tmp << 6) | (tmp >> 26));
+ tmp = wd + (wb ^ (wa | ~wc)) + x7 + 0x432AFF97;
+ wd = wa + ((tmp << 10) | (tmp >> 22));
+ tmp = wc + (wa ^ (wd | ~wb)) + xE + 0xAB9423A7;
+ wc = wd + ((tmp << 15) | (tmp >> 17));
+ tmp = wb + (wd ^ (wc | ~wa)) + x5 + 0xFC93A039;
+ wb = wc + ((tmp << 21) | (tmp >> 11));
+ tmp = wa + (wc ^ (wb | ~wd)) + xC + 0x655B59C3;
+ wa = wb + ((tmp << 6) | (tmp >> 26));
+ tmp = wd + (wb ^ (wa | ~wc)) + x3 + 0x8F0CCC92;
+ wd = wa + ((tmp << 10) | (tmp >> 22));
+ tmp = wc + (wa ^ (wd | ~wb)) + xA + 0xFFEFF47D;
+ wc = wd + ((tmp << 15) | (tmp >> 17));
+ tmp = wb + (wd ^ (wc | ~wa)) + x1 + 0x85845DD1;
+ wb = wc + ((tmp << 21) | (tmp >> 11));
+ tmp = wa + (wc ^ (wb | ~wd)) + x8 + 0x6FA87E4F;
+ wa = wb + ((tmp << 6) | (tmp >> 26));
+ tmp = wd + (wb ^ (wa | ~wc)) + xF + 0xFE2CE6E0;
+ wd = wa + ((tmp << 10) | (tmp >> 22));
+ tmp = wc + (wa ^ (wd | ~wb)) + x6 + 0xA3014314;
+ wc = wd + ((tmp << 15) | (tmp >> 17));
+ tmp = wb + (wd ^ (wc | ~wa)) + xD + 0x4E0811A1;
+ wb = wc + ((tmp << 21) | (tmp >> 11));
+ tmp = wa + (wc ^ (wb | ~wd)) + x4 + 0xF7537E82;
+ wa = wb + ((tmp << 6) | (tmp >> 26));
+ tmp = wd + (wb ^ (wa | ~wc)) + xB + 0xBD3AF235;
+ wd = wa + ((tmp << 10) | (tmp >> 22));
+ tmp = wc + (wa ^ (wd | ~wb)) + x2 + 0x2AD7D2BB;
+ wc = wd + ((tmp << 15) | (tmp >> 17));
+ tmp = wb + (wd ^ (wc | ~wa)) + x9 + 0xEB86D391;
+ wb = wc + ((tmp << 21) | (tmp >> 11));
+
+ /*
+ * Update state words and reset block pointer.
+ */
+ A += wa;
+ B += wb;
+ C += wc;
+ D += wd;
+ ptr = 0;
+ }
+
+ static uint Dec32le(byte[] buf, int off)
+ {
+ return (uint)buf[off]
+ | ((uint)buf[off + 1] << 8)
+ | ((uint)buf[off + 2] << 16)
+ | ((uint)buf[off + 3] << 24);
+ }
+
+ static void Enc32le(uint x, byte[] buf, int off)
+ {
+ buf[off] = (byte)x;
+ buf[off + 1] = (byte)(x >> 8);
+ buf[off + 2] = (byte)(x >> 16);
+ buf[off + 3] = (byte)(x >> 24);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Mutable container for a modular integer.
+ *
+ * Rules:
+ *
+ * - Each instance is initialised over a given modulus, or by
+ * duplicating an existing instance.
+ *
+ * - All operands for a given operation must share the same modulus,
+ * i.e. the instances must have been created with Dup() calls from
+ * a common ancestor.
+ *
+ * - Modulus must be odd and greater than 1.
+ */
+
+public class ModInt {
+
+ /*
+ * Dedicated class for a modulus. It maintains some useful
+ * values that depend only on the modulus.
+ */
+ class ZMod {
+
+ /*
+ * val = modulus value, 31 bits per word, little-endian
+ * bitLen = modulus bit length (exact)
+ * n0i is such that n0i * val[0] == -1 mod 2^31
+ * R = 2^(31*val.Length) (modular)
+ * R2 = 2^(31*(val.Length*2)) (modular)
+ * Rx[i] = 2^(31*(val.Length+(2^i))) (modular)
+ */
+ internal uint[] val;
+ internal int bitLen;
+ internal uint n0i;
+ internal uint[] R;
+ internal uint[] R2;
+ internal uint[][] Rx;
+ internal byte[] vm2;
+
+ internal ZMod(byte[] bmod, int off, int len)
+ {
+ bitLen = BigInt.BitLength(bmod, off, len);
+ if (bitLen <= 1 || (bmod[off + len - 1] & 1) == 0) {
+ throw new CryptoException("invalid modulus");
+ }
+ int n = (bitLen + 30) / 31;
+ val = new uint[n];
+ DecodeBE(bmod, off, len, val);
+ uint x = val[0];
+ uint y = 2 - x;
+ y *= 2 - y * x;
+ y *= 2 - y * x;
+ y *= 2 - y * x;
+ y *= 2 - y * x;
+ n0i = (1 + ~y) & 0x7FFFFFFF;
+
+ /*
+ * Compute the Rx[] values.
+ */
+ int zk = 0;
+ while ((n >> zk) != 0) {
+ zk ++;
+ }
+ R = new uint[n];
+ R[n - 1] = 1;
+ for (int i = 0; i < 31; i ++) {
+ ModMul2(R, val);
+ }
+ Rx = new uint[zk][];
+ Rx[0] = new uint[n];
+ Array.Copy(R, 0, Rx[0], 0, n);
+ for (int i = 0; i < 31; i ++) {
+ ModMul2(Rx[0], val);
+ }
+ for (int k = 1; k < zk; k ++) {
+ Rx[k] = new uint[n];
+ MontyMul(Rx[k - 1], Rx[k - 1], Rx[k], val, n0i);
+ }
+
+ /*
+ * Compute R2 by multiplying the relevant Rx[]
+ * values.
+ */
+ R2 = null;
+ uint[] tt = new uint[n];
+ for (int k = 0; k < zk; k ++) {
+ if (((n >> k) & 1) == 0) {
+ continue;
+ }
+ if (R2 == null) {
+ R2 = new uint[n];
+ Array.Copy(Rx[k], 0, R2, 0, n);
+ } else {
+ MontyMul(Rx[k], R2, tt, val, n0i);
+ Array.Copy(tt, 0, R2, 0, n);
+ }
+ }
+
+ /*
+ * Compute N-2; used as an exponent for modular
+ * inversion. Since modulus N is odd and greater
+ * than 1, N-2 is necessarily positive.
+ */
+ vm2 = new byte[(bitLen + 7) >> 3];
+ int cc = 2;
+ for (int i = 0; i < vm2.Length; i ++) {
+ int b = bmod[off + len - 1 - i];
+ b -= cc;
+ vm2[vm2.Length - 1 - i] = (byte)b;
+ cc = (b >> 8) & 1;
+ }
+ }
+ }
+
+ ZMod mod;
+ uint[] val;
+ uint[] tmp1, tmp2;
+
+ /*
+ * Get the modulus bit length.
+ */
+ public int ModBitLength {
+ get {
+ return mod.bitLen;
+ }
+ }
+
+ /*
+ * Test whether this value is zero.
+ */
+ public bool IsZero {
+ get {
+ return IsZeroCT != 0;
+ }
+ }
+
+ public uint IsZeroCT {
+ get {
+ int z = (int)val[0];
+ for (int i = 1; i < val.Length; i ++) {
+ z |= (int)val[i];
+ }
+ return ~(uint)((z | -z) >> 31);
+ }
+ }
+
+ /*
+ * Test whether this value is one.
+ */
+ public bool IsOne {
+ get {
+ return IsOneCT != 0;
+ }
+ }
+
+ public uint IsOneCT {
+ get {
+ int z = (int)val[0] ^ 1;
+ for (int i = 1; i < val.Length; i ++) {
+ z |= (int)val[i];
+ }
+ return ~(uint)((z | -z) >> 31);
+ }
+ }
+
+ ModInt(ZMod m)
+ {
+ Init(m);
+ }
+
+ /*
+ * Create a new instance by decoding the provided modulus
+ * (unsigned big-endian). Value is zero.
+ */
+ public ModInt(byte[] modulus)
+ : this(modulus, 0, modulus.Length)
+ {
+ }
+
+ /*
+ * Create a new instance by decoding the provided modulus
+ * (unsigned big-endian). Value is zero.
+ */
+ public ModInt(byte[] modulus, int off, int len)
+ {
+ Init(new ZMod(modulus, off, len));
+ }
+
+ void Init(ZMod mod)
+ {
+ this.mod = mod;
+ int n = mod.val.Length;
+ val = new uint[n];
+ tmp1 = new uint[n];
+ tmp2 = new uint[n];
+ }
+
+ /*
+ * Duplicate this instance. The new instance uses the same
+ * modulus, and its value is initialized to the same value as
+ * this instance.
+ */
+ public ModInt Dup()
+ {
+ ModInt m = new ModInt(mod);
+ Array.Copy(val, 0, m.val, 0, val.Length);
+ return m;
+ }
+
+ /*
+ * Set the value in this instance to a copy of the value in
+ * the provided instance. The 'm' instance may use a different
+ * modulus, in which case the value may incur modular reduction.
+ */
+ public void Set(ModInt m)
+ {
+ /*
+ * If the instances use the same modulus array, then
+ * the value can simply be copied as is.
+ */
+ if (mod == m.mod) {
+ Array.Copy(m.val, 0, val, 0, val.Length);
+ return;
+ }
+ Reduce(m.val);
+ }
+
+ /*
+ * Set the value in this instance to a copy of the value in
+ * the provided instance, but do so only conditionally. If
+ * 'ctl' is -1, then the copy is done. If 'ctl' is 0, then the
+ * copy is NOT done. This instance and the source instance
+ * MUST use the same modulus.
+ */
+ public void CondCopy(ModInt m, uint ctl)
+ {
+ if (mod != m.mod) {
+ throw new CryptoException("Not same modulus");
+ }
+ for (int i = 0; i < val.Length; i ++) {
+ uint w = val[i];
+ val[i] = w ^ (ctl & (m.val[i] ^ w));
+ }
+ }
+
+ /*
+ * Set the value in this instance to a copy of either 'a'
+ * (if ctl is -1) or 'b' (if ctl is 0).
+ */
+ public void CopyMux(uint ctl, ModInt a, ModInt b)
+ {
+ if (mod != a.mod || mod != b.mod) {
+ throw new CryptoException("Not same modulus");
+ }
+ for (int i = 0; i < val.Length; i ++) {
+ uint aw = a.val[i];
+ uint bw = b.val[i];
+ val[i] = bw ^ (ctl & (aw ^ bw));
+ }
+ }
+
+ /*
+ * Set the value in this instance to the provided integer value
+ * (which MUST be nonnegative).
+ */
+ public void Set(int x)
+ {
+ val[0] = (uint)x;
+ for (int i = 1; i < val.Length; i ++) {
+ val[i] = 0;
+ }
+ }
+
+ /*
+ * Set this value to either 0 (if ctl is 0) or 1 (if ctl is -1),
+ * in Montgomery representation.
+ */
+ public void SetMonty(uint ctl)
+ {
+ for (int i = 0; i < val.Length; i ++) {
+ val[i] = ctl & mod.R[i];
+ }
+ }
+
+ /*
+ * Set this value by decoding the provided integer (big-endian
+ * unsigned encoding). If the source value does not fit (it is
+ * not lower than the modulus), then this method return 0, and
+ * this instance is set to 0. Otherwise, it returns -1.
+ */
+ public uint Decode(byte[] buf)
+ {
+ return Decode(buf, 0, buf.Length);
+ }
+
+ /*
+ * Set this value by decoding the provided integer (big-endian
+ * unsigned encoding). If the source value does not fit (it is
+ * not lower than the modulus), then this method return 0, and
+ * this instance is set to 0. Otherwise, it returns -1.
+ */
+ public uint Decode(byte[] buf, int off, int len)
+ {
+ /*
+ * Decode into val[]; if the truncation loses some
+ * non-zero bytes, then this returns 0.
+ */
+ uint x = DecodeBE(buf, off, len, val);
+
+ /*
+ * Compare with modulus. We want to get a 0 if the
+ * subtraction would not yield a carry, i.e. the
+ * source value is not lower than the modulus.
+ */
+ x &= Sub(val, mod.val, 0);
+
+ /*
+ * At that point, x is -1 if the value fits, 0
+ * otherwise.
+ */
+ for (int i = 0; i < val.Length; i ++) {
+ val[i] &= x;
+ }
+ return x;
+ }
+
+ /*
+ * Set this value by decoding the provided integer (big-endian
+ * unsigned encoding). The source value is reduced if necessary.
+ */
+ public void DecodeReduce(byte[] buf)
+ {
+ DecodeReduce(buf, 0, buf.Length);
+ }
+
+ /*
+ * Set this value by decoding the provided integer (big-endian
+ * unsigned encoding). The source value is reduced if necessary.
+ */
+ public void DecodeReduce(byte[] buf, int off, int len)
+ {
+ uint[] x = new uint[((len << 3) + 30) / 31];
+ DecodeBE(buf, off, len, x);
+ Reduce(x);
+ }
+
+ /*
+ * Set the value from the provided source array, with modular
+ * reduction.
+ */
+ void Reduce(uint[] b)
+ {
+ /*
+ * If the modulus uses only one word then we must use
+ * a special code path.
+ */
+ if (mod.val.Length == 1) {
+ ReduceSmallMod(b);
+ return;
+ }
+
+ /*
+ * Fast copy of words that do not incur modular
+ * reduction.
+ */
+ int aLen = mod.val.Length;
+ int bLen = b.Length;
+ int cLen = Math.Min(aLen - 1, bLen);
+ Array.Copy(b, bLen - cLen, val, 0, cLen);
+ for (int i = cLen; i < aLen; i ++) {
+ val[i] = 0;
+ }
+
+ /*
+ * Inject extra words. We use the pre-computed
+ * Rx[] values to do shifts.
+ */
+ for (int j = bLen - cLen; j > 0;) {
+ /*
+ * We can add power-of-2 words, but less
+ * than the modulus size. Note that the modulus
+ * uses at least two words, so this process works.
+ */
+ int k;
+ for (k = 0;; k ++) {
+ int nk = 1 << (k + 1);
+ if (nk >= aLen || nk > j) {
+ break;
+ }
+ }
+ int num = 1 << k;
+ MontyMul(val, mod.Rx[k], tmp1, mod.val, mod.n0i);
+ j -= num;
+ Array.Copy(b, j, tmp2, 0, num);
+ for (int i = num; i < tmp2.Length; i ++) {
+ tmp2[i] = 0;
+ }
+ ModAdd(tmp1, tmp2, val, mod.val, 0);
+ }
+ }
+
+ /*
+ * Modular reduction in case the modulus fits on a single
+ * word.
+ */
+ void ReduceSmallMod(uint[] b)
+ {
+ uint x = 0;
+ uint n = mod.val[0];
+ int nlen = mod.bitLen;
+ uint n0i = mod.n0i;
+ uint r2 = mod.R2[0];
+ for (int i = b.Length - 1; i >= 0; i --) {
+ /*
+ * Multiply x by R (Montgomery multiplication by R^2).
+ */
+ ulong z = (ulong)x * (ulong)r2;
+ uint u = ((uint)z * n0i) & 0x7FFFFFFF;
+ z += (ulong)u * (ulong)n;
+ x = (uint)(z >> 31);
+
+ /*
+ * Ensure x fits on 31 bits (it may be up to twice
+ * the modulus at that point). If x >= 2^31 then,
+ * necessarily, x is greater than the modulus, and
+ * the subtraction is sound; moreover, in that case,
+ * subtracting the modulus brings back x to less
+ * than the modulus, hence fitting on 31 bits.
+ */
+ x -= (uint)((int)x >> 31) & n;
+
+ /*
+ * Add the next word, then reduce. The addition
+ * does not overflow since both operands fit on
+ * 31 bits.
+ *
+ * Since the modulus could be much smaller than
+ * 31 bits, we need a full remainder operation here.
+ */
+ x += b[i];
+
+ /*
+ * Constant-time modular reduction.
+ * We first perform two subtraction of the
+ * shifted modulus to ensure that the high bit
+ * is cleared. This allows the loop to work
+ * properly.
+ */
+ x -= (uint)((int)x >> 31) & (n << (31 - nlen));
+ x -= (uint)((int)x >> 31) & (n << (31 - nlen));
+ for (int j = 31 - nlen; j >= 0; j --) {
+ x -= (n << j);
+ x += (uint)((int)x >> 31) & (n << j);
+ }
+ }
+ val[0] = x;
+ }
+
+ /*
+ * Encode into bytes. Big-endian unsigned encoding is used, the
+ * returned array having the minimal length to encode the modulus.
+ */
+ public byte[] Encode()
+ {
+ return Encode(false);
+ }
+
+ /*
+ * Encode into bytes. Big-endian encoding is used; if 'signed' is
+ * true, then signed encoding is used: returned value will have a
+ * leading bit set to 0. Returned array length is the minimal size
+ * for encoding the modulus (with a sign bit if using signed
+ * encoding).
+ */
+ public byte[] Encode(bool signed)
+ {
+ int x = mod.bitLen;
+ if (signed) {
+ x ++;
+ }
+ byte[] buf = new byte[(x + 7) >> 3];
+ Encode(buf, 0, buf.Length);
+ return buf;
+ }
+
+ /*
+ * Encode into bytes. The provided array is fully set; big-endian
+ * encoding is used, and extra leading bytes of value 0 are added
+ * if necessary. If the destination array is too small, then the
+ * value is silently truncated.
+ */
+ public void Encode(byte[] buf)
+ {
+ Encode(buf, 0, buf.Length);
+ }
+
+ /*
+ * Encode into bytes. The provided array chunk is fully set;
+ * big-endian encoding is used, and extra leading bytes of value
+ * 0 are added if necessary. If the destination array is too
+ * small, then the value is silently truncated.
+ */
+ public void Encode(byte[] buf, int off, int len)
+ {
+ EncodeBE(val, buf, off, len);
+ }
+
+ /*
+ * Get the least significant bit of the value (0 or 1).
+ */
+ public uint GetLSB()
+ {
+ return val[0] & (uint)1;
+ }
+
+ /*
+ * Add a small integer to this instance. The small integer
+ * 'x' MUST be lower than 2^31 and MUST be lower than the modulus.
+ */
+ public void Add(uint x)
+ {
+ tmp1[0] = x;
+ for (int i = 1; i < tmp1.Length; i ++) {
+ tmp1[i] = 0;
+ }
+ ModAdd(val, tmp1, val, mod.val, 0);
+ }
+
+ /*
+ * Add another value to this instance. The operand 'b' may
+ * be the same as this instance.
+ */
+ public void Add(ModInt b)
+ {
+ if (mod != b.mod) {
+ throw new CryptoException("Not same modulus");
+ }
+ ModAdd(val, b.val, val, mod.val, 0);
+ }
+
+ /*
+ * Subtract a small integer from this instance. The small integer
+ * 'x' MUST be lower than 2^31 and MUST be lower than the modulus.
+ */
+ public void Sub(uint x)
+ {
+ tmp1[0] = x;
+ for (int i = 1; i < tmp1.Length; i ++) {
+ tmp1[i] = 0;
+ }
+ ModSub(val, tmp1, val, mod.val);
+ }
+
+ /*
+ * Subtract another value from this instance. The operand 'b'
+ * may be the same as this instance.
+ */
+ public void Sub(ModInt b)
+ {
+ if (mod != b.mod) {
+ throw new CryptoException("Not same modulus");
+ }
+ ModSub(val, b.val, val, mod.val);
+ }
+
+ /*
+ * Negate this value.
+ */
+ public void Negate()
+ {
+ ModSub(null, val, val, mod.val);
+ }
+
+ /*
+ * Convert this instance to Montgomery representation.
+ */
+ public void ToMonty()
+ {
+ MontyMul(val, mod.R2, tmp1, mod.val, mod.n0i);
+ Array.Copy(tmp1, 0, val, 0, val.Length);
+ }
+
+ /*
+ * Convert this instance back from Montgomery representation to
+ * normal representation.
+ */
+ public void FromMonty()
+ {
+ tmp1[0] = 1;
+ for (int i = 1; i < tmp1.Length; i ++) {
+ tmp1[i] = 0;
+ }
+ MontyMul(val, tmp1, tmp2, mod.val, mod.n0i);
+ Array.Copy(tmp2, 0, val, 0, val.Length);
+ }
+
+ /*
+ * Compute a Montgomery multiplication with the provided
+ * value. The other operand may be this instance.
+ */
+ public void MontyMul(ModInt b)
+ {
+ if (mod != b.mod) {
+ throw new CryptoException("Not same modulus");
+ }
+ MontyMul(val, b.val, tmp1, mod.val, mod.n0i);
+ Array.Copy(tmp1, 0, val, 0, val.Length);
+ }
+
+ /*
+ * Montgomery-square this instance.
+ */
+ public void MontySquare()
+ {
+ MontyMul(val, val, tmp1, mod.val, mod.n0i);
+ Array.Copy(tmp1, 0, val, 0, val.Length);
+ }
+
+ /*
+ * Perform modular exponentiation. Exponent is in big-endian
+ * unsigned encoding.
+ */
+ public void Pow(byte[] exp)
+ {
+ Pow(exp, 0, exp.Length);
+ }
+
+ /*
+ * Perform modular exponentiation. Exponent is in big-endian
+ * unsigned encoding.
+ */
+ public void Pow(byte[] exp, int off, int len)
+ {
+ MontyMul(val, mod.R2, tmp1, mod.val, mod.n0i);
+ val[0] = 1;
+ for (int i = 1; i < val.Length; i ++) {
+ val[i] = 0;
+ }
+ for (int i = 0; i < len; i ++) {
+ int x = exp[off + len - 1 - i];
+ for (int j = 0; j < 8; j ++) {
+ MontyMul(val, tmp1, tmp2, mod.val, mod.n0i);
+ uint ctl = (uint)-((x >> j) & 1);
+ for (int k = 0; k < val.Length; k ++) {
+ val[k] = (tmp2[k] & ctl)
+ | (val[k] & ~ctl);
+ }
+ MontyMul(tmp1, tmp1, tmp2, mod.val, mod.n0i);
+ Array.Copy(tmp2, 0, tmp1, 0, tmp2.Length);
+ }
+ }
+ }
+
+ /*
+ * Compute modular inverse of this value. If this instance is
+ * zero, then it remains equal to zero. If the modulus is not
+ * prime, then this function computes wrong values.
+ */
+ public void Invert()
+ {
+ Pow(mod.vm2);
+ }
+
+ /*
+ * Compute the square root for this value. This method assumes
+ * that the modulus is prime, greater than or equal to 7, and
+ * equal to 3 modulo 4; if it is not, then the returned value
+ * and the contents of this instance are indeterminate.
+ *
+ * Returned value is -1 if the value was indeed a square, 0
+ * otherwise. In the latter case, array contents are the square
+ * root of the opposite of the original value.
+ */
+ public uint SqrtBlum()
+ {
+ /*
+ * We suppose that p = 3 mod 4; we raise to the power
+ * (p-3)/4, then do an extra multiplication to go to
+ * power (p+1)/4.
+ *
+ * Since we know the modulus bit length, we can do
+ * the exponentiation from the high bits downwards.
+ */
+ ToMonty();
+ Array.Copy(val, 0, tmp1, 0, val.Length);
+ int k = (mod.bitLen - 2) / 31;
+ int j = mod.bitLen - 2 - k * 31;
+ uint ew = mod.val[k];
+ for (int i = mod.bitLen - 2; i >= 2; i --) {
+ uint ctl = ~(uint)-((int)(ew >> j) & 1);
+ MontyMul(tmp1, tmp1, tmp2, mod.val, mod.n0i);
+ MontyMul(val, tmp2, tmp1, mod.val, mod.n0i);
+ for (int m = 0; m < tmp1.Length; m ++) {
+ uint w = tmp1[m];
+ tmp1[m] = w ^ (ctl & (w ^ tmp2[m]));
+ }
+ if (-- j < 0) {
+ j = 30;
+ ew = mod.val[-- k];
+ }
+ }
+
+ /*
+ * The extra multiplication. Square root is written in
+ * tmp2 (in Montgomery representation).
+ */
+ MontyMul(val, tmp1, tmp2, mod.val, mod.n0i);
+
+ /*
+ * Square it back in tmp1, to see if it indeed yields
+ * val.
+ */
+ MontyMul(tmp2, tmp2, tmp1, mod.val, mod.n0i);
+ int z = 0;
+ for (int i = 0; i < val.Length; i ++) {
+ z |= (int)(val[i] ^ tmp1[i]);
+ }
+ uint good = ~(uint)((z | -z) >> 31);
+
+ /*
+ * Convert back the result to normal representation.
+ */
+ Array.Copy(tmp2, 0, val, 0, val.Length);
+ FromMonty();
+ return good;
+ }
+
+ /*
+ * Conditionally swap this instance with the one provided in
+ * parameter. The swap is performed if ctl is -1, not performed
+ * if ctl is 0.
+ */
+ public void CondSwap(ModInt b, uint ctl)
+ {
+ if (mod != b.mod) {
+ throw new CryptoException("Not same modulus");
+ }
+ for (int i = 0; i < val.Length; i ++) {
+ uint x = val[i];
+ uint y = b.val[i];
+ uint m = ctl & (x ^ y);
+ val[i] = x ^ m;
+ b.val[i] = y ^ m;
+ }
+ }
+
+ /*
+ * Compare for equality this value with another. Comparison still
+ * works if the two values use distinct moduli.
+ */
+ public bool Eq(ModInt b)
+ {
+ return EqCT(b) != 0;
+ }
+
+ /*
+ * Compare for equality this value with another. Comparison still
+ * works if the two values use distinct moduli. Returned value is
+ * -1 on equality, 0 otherwise.
+ */
+ public uint EqCT(ModInt b)
+ {
+ uint z = 0;
+ if (b.val.Length > val.Length) {
+ for (int i = 0; i < val.Length; i ++) {
+ z |= val[i] ^ b.val[i];
+ }
+ for (int i = val.Length; i < b.val.Length; i ++) {
+ z |= b.val[i];
+ }
+ } else {
+ for (int i = 0; i < b.val.Length; i ++) {
+ z |= val[i] ^ b.val[i];
+ }
+ for (int i = b.val.Length; i < val.Length; i ++) {
+ z |= b.val[i];
+ }
+ }
+ int x = (int)z;
+ return ~(uint)((x | -x) >> 31);
+ }
+
+ /* ============================================================== */
+
+ /*
+ * Decode value (unsigned big-endian) into dst[] (little-endian,
+ * 31 bits per word). All words of dst[] are initialised.
+ * Returned value is -1 on success, 0 if some non-zero source
+ * bits had to be ignored.
+ */
+ static uint DecodeBE(byte[] buf, int off, int len, uint[] dst)
+ {
+ int i = 0;
+ uint acc = 0;
+ int accLen = 0;
+ off += len;
+ while (i < dst.Length) {
+ uint b;
+ if (len > 0) {
+ b = buf[-- off];
+ len --;
+ } else {
+ b = 0;
+ }
+ acc |= (b << accLen);
+ accLen += 8;
+ if (accLen >= 31) {
+ dst[i ++] = acc & 0x7FFFFFFF;
+ accLen -= 31;
+ acc = b >> (8 - accLen);
+ }
+ }
+ while (len -- > 0) {
+ acc |= buf[-- off];
+ }
+ int x = (int)acc;
+ return ~(uint)((x | -x) >> 31);
+ }
+
+ /*
+ * Encode an integer (array of words, little-endian, 31 bits per
+ * word) into big-endian encoding, with the provided length (in
+ * bytes).
+ */
+ static byte[] EncodeBE(uint[] x, int len)
+ {
+ byte[] val = new byte[len];
+ EncodeBE(x, val, 0, len);
+ return val;
+ }
+
+ /*
+ * Encode an integer (array of words, little-endian, 31 bits per
+ * word) into big-endian encoding, with the provided length (in
+ * bytes).
+ */
+ static void EncodeBE(uint[] x, byte[] val)
+ {
+ EncodeBE(x, val, 0, val.Length);
+ }
+
+ /*
+ * Encode an integer (array of words, little-endian, 31 bits per
+ * word) into big-endian encoding, with the provided length (in
+ * bytes).
+ */
+ static void EncodeBE(uint[] x, byte[] val, int off, int len)
+ {
+ uint acc = 0;
+ int accLen = 0;
+ int j = 0;
+ for (int i = len - 1; i >= 0; i --) {
+ uint b;
+ if (accLen < 8) {
+ uint z = (j < x.Length) ? x[j ++] : 0;
+ b = acc | (z << accLen);
+ acc = z >> (8 - accLen);
+ accLen += 23;
+ } else {
+ b = acc;
+ accLen -= 8;
+ acc >>= 8;
+ }
+ val[off + i] = (byte)b;
+ }
+ }
+
+ /*
+ * Subtract b from a; the carry is returned (-1 if carry, 0
+ * otherwise). The operation is done only if ctl is -1; if
+ * ctl is 0, then a[] is unmodified, but the carry is still
+ * computed and returned.
+ *
+ * The two operand arrays MUST have the same size.
+ */
+ static uint Sub(uint[] a, uint[] b, uint ctl)
+ {
+ int n = a.Length;
+ int cc = 0;
+ ctl >>= 1;
+ for (int i = 0; i < n; i ++) {
+ uint aw = a[i];
+ uint bw = b[i];
+ uint cw = (uint)cc + aw - bw;
+ cc = (int)cw >> 31;
+ a[i] = (aw & ~ctl) | (cw & ctl);
+ }
+ return (uint)cc;
+ }
+
+ /*
+ * Left-shift value by one bit; the extra bit (carry) is
+ * return as -1 or 0.
+ */
+ static uint LShift(uint[] a)
+ {
+ int n = a.Length;
+ uint cc = 0;
+ for (int i = 0; i < n; i ++) {
+ uint aw = a[i];
+ a[i] = (cc | (aw << 1)) & 0x7FFFFFFF;
+ cc = aw >> 30;
+ }
+ return (uint)-(int)cc;
+ }
+
+ /*
+ * Modular left-shift value by one bit. Value and modulus MUST
+ * have the same length. Value MUST be lower than modulus.
+ */
+ static void ModMul2(uint[] a, uint[] mod)
+ {
+ int n = a.Length;
+
+ /*
+ * First pass: compute 2*a-mod, but don't keep the
+ * result, only the final carry (0 or -1).
+ */
+ uint cc1 = 0;
+ int cc2 = 0;
+ for (int i = 0; i < n; i ++) {
+ uint aw = a[i];
+ uint aws = ((aw << 1) | cc1) & 0x7FFFFFFF;
+ cc1 = aw >> 30;
+ uint z = aws - mod[i] + (uint)cc2;
+ cc2 = (int)z >> 31;
+ }
+ cc2 += (int)cc1;
+
+ /*
+ * If cc2 is 0, then the subtraction yields no carry and
+ * must be done. Otherwise, cc2 is -1, and the subtraction
+ * must not be done.
+ */
+ uint ctl = ~(uint)cc2;
+ cc1 = 0;
+ cc2 = 0;
+ for (int i = 0; i < n; i ++) {
+ uint aw = a[i];
+ uint aws = ((aw << 1) | cc1) & 0x7FFFFFFF;
+ cc1 = aw >> 30;
+ uint z = aws - (mod[i] & ctl) + (uint)cc2;
+ cc2 = (int)z >> 31;
+ a[i] = z & 0x7FFFFFFF;
+ }
+ }
+
+ /*
+ * Modular addition.
+ *
+ * If 'hi' is zero, then this computes a+b-n if a+b >= n,
+ * a+b otherwise.
+ *
+ * If 'hi' is non-zero, then this computes 2^k+a+b-n, where
+ * k = n.Length*31.
+ *
+ * Result is written in d[]. The same array may be used in
+ * several operands.
+ */
+ static void ModAdd(uint[] a, uint[] b, uint[] d, uint[] n, uint hi)
+ {
+ /*
+ * Set ctl to -1 if hi is non-zero, 0 otherwise.
+ * 'ctl' computes whether the subtraction with n[]
+ * is needed in the second pass.
+ */
+ int x = (int)hi;
+ uint ctl = (uint)((x | -x) >> 31);
+
+ for (int pass = 0; pass < 2; pass ++) {
+ /*
+ * cc1 is the carry for a+b (0 or 1)
+ *
+ * cc2 is the carry for the modulus
+ * subtraction (0 or -1)
+ */
+ uint cc1 = 0;
+ int cc2 = 0;
+ for (int i = 0; i < n.Length; i ++) {
+ uint aw = a[i];
+ uint bw = b[i];
+ uint nw = n[i];
+ uint sw = aw + bw + cc1;
+ cc1 = sw >> 31;
+ sw &= 0x7FFFFFFF;
+ uint dw = sw - nw + (uint)cc2;
+ cc2 = (int)dw >> 31;
+ if (pass == 1) {
+ dw &= 0x7FFFFFFF;
+ d[i] = (dw & ctl) | (sw & ~ctl);
+ }
+ }
+
+ /*
+ * Compute aggregate subtraction carry. This should
+ * not be 1 if the operands are correct (it would
+ * mean that a+b-n overflows, so both a and b are
+ * larger than n). If it is 0, then a+b-n >= 0,
+ * so the subtraction must be done; otherwise, the
+ * aggregate carry will be -1, and the subtraction
+ * should not be done (unless forced by a non-zero
+ * 'hi' value).
+ */
+ cc2 += (int)cc1;
+ ctl |= ~(uint)(cc2 >> 31);
+ }
+ }
+
+ /*
+ * Modular subtraction.
+ *
+ * This computes a-b, then adds n if the result is negative.
+ * If a is null, then it is assumed to be all-zeros.
+ *
+ * Result is written in d[]. The same array may be used in
+ * several operands.
+ */
+ static void ModSub(uint[] a, uint[] b, uint[] d, uint[] n)
+ {
+ uint ctl = 0;
+ for (int pass = 0; pass < 2; pass ++) {
+ /*
+ * cc1 = carry for a-b (0 or -1)
+ * cc2 = carry for modulus addition (0 or 1)
+ */
+ int cc1 = 0;
+ uint cc2 = 0;
+ for (int i = 0; i < n.Length; i ++) {
+ uint aw = (a == null) ? 0 : a[i];
+ uint bw = b[i];
+ uint nw = n[i];
+ uint sw = aw - bw + (uint)cc1;
+ cc1 = (int)sw >> 31;
+ sw &= 0x7FFFFFFF;
+ uint dw = sw + nw + cc2;
+ cc2 = dw >> 31;
+ if (pass == 1) {
+ dw &= 0x7FFFFFFF;
+ d[i] = (dw & ctl) | (sw & ~ctl);
+ }
+ }
+
+ /*
+ * Modulus addition must be done if and only if
+ * a-b had a final carry.
+ */
+ ctl = (uint)cc1;
+ }
+ }
+
+ /*
+ * Compute Montgomery multiplication of a[] by b[], result in
+ * d[], with modulus n[]. All arrays must have the same length.
+ * d[] must be distinct from a[], b[] and n[]. Modulus must be
+ * odd. n0i must be such that n[0]*n0i = -1 mod 2^31. Values a[]
+ * and b[] must be lower than n[] (so, in particular, a[] and
+ * b[] cannot be the same array as n[]).
+ */
+ static void MontyMul(uint[] a, uint[] b, uint[] d, uint[] n, uint n0i)
+ {
+ int len = n.Length;
+ for (int i = 0; i < len; i ++) {
+ d[i] = 0;
+ }
+ ulong dh = 0;
+ for (int i = 0; i < len; i ++) {
+ uint ai = a[i];
+ uint u = ((d[0] + ai * b[0]) * n0i) & 0x7FFFFFFF;
+ ulong cc = 0;
+ for (int j = 0; j < len; j ++) {
+ ulong z = (ulong)d[j]
+ + (ulong)ai * (ulong)b[j]
+ + (ulong)u * (ulong)n[j] + cc;
+ cc = z >> 31;
+ if (j > 0) {
+ d[j - 1] = (uint)z & 0x7FFFFFFF;
+ } else {
+ // DEBUG
+ if (((uint)z & 0x7FFFFFFF) != 0) {
+ throw new Exception("BAD!");
+ }
+ }
+ }
+ dh += cc;
+ d[len - 1] = (uint)dh & 0x7FFFFFFF;
+ dh >>= 31;
+ }
+ int x = (int)dh;
+ uint ctl = (uint)((x | -x) >> 31);
+ Sub(d, n, ctl | ~Sub(d, n, 0));
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * A MutableECPoint instance contains an elliptic curve point, in a
+ * given curve. It may be modified to contain another point, but not
+ * on another curve.
+ *
+ * Constant-time guarantees: IsInfinityCT, DoubleCT(), NegCT() and
+ * AddCT() are constant-time with regards to the represented curve
+ * point. Execution time may vary depending on the sequence of calls,
+ * but not on the point data. In particular, points may be internally
+ * "normalized" or not, and operations involving normalized points can
+ * be faster; however, normalization happens only upon an explicit call.
+ * The normalization process itself (Normalize()) is constant-time.
+ */
+
+internal abstract class MutableECPoint {
+
+ internal MutableECPoint()
+ {
+ }
+
+ internal abstract ECCurve Curve {
+ get;
+ }
+
+ /*
+ * Test whether this point is the point at infinity.
+ */
+ internal bool IsInfinity {
+ get {
+ return IsInfinityCT != 0;
+ }
+ }
+
+ /*
+ * Test whether this point is the point at infinity (returns
+ * 0xFFFFFFFF or 0x00000000).
+ */
+ internal abstract uint IsInfinityCT {
+ get;
+ }
+
+ /*
+ * Normalize this instance. What this entails depends on the
+ * curve type, but it will typically means computing affine
+ * coordinates in case this instance was using some sort of
+ * projective system.
+ */
+ internal abstract void Normalize();
+
+ /*
+ * Encode this point into some bytes. If "compressed" is true,
+ * then a compressed format will be used.
+ *
+ * This call may entail normalization. If the point is not the
+ * infinity point, then this method is constant-time.
+ */
+ internal abstract byte[] Encode(bool compressed);
+
+ /*
+ * Encode this point into some bytes. If "compressed is true,
+ * then a compressed format will be used. The destination array
+ * must have the proper length for the requested point format.
+ *
+ * This call may entail normalization. If the point is invalid
+ * or is the point at infinity, then the returned value is 0
+ * and what gets written in the array is indeterminate. Otherwise,
+ * the encoded point is written and -1 is returned. Either way,
+ * this call is constant-time.
+ */
+ internal abstract uint Encode(byte[] dst, bool compressed);
+
+ /*
+ * Set this point by decoding the provided value. An invalid
+ * encoding sets this point to 0 (infinity) and triggers an
+ * exception.
+ */
+ internal void Decode(byte[] enc)
+ {
+ if (DecodeCT(enc) == 0) {
+ throw new CryptoException("Invalid encoded point");
+ }
+ }
+
+ /*
+ * Set this point by decoding the provided value. This is
+ * constant-time (up to the encoded point length). Returned
+ * value is 0xFFFFFFFF if the encoded point was valid,
+ * 0x00000000 otherwise. If the decoding failed, then this
+ * value is set to 0 (infinity).
+ */
+ internal abstract uint DecodeCT(byte[] enc);
+
+ /*
+ * Get the X coordinate for this point. This implies
+ * normalization. If the point is the point at infinity,
+ * then the returned array contains the encoding of 0.
+ * This is constant-time.
+ */
+ internal abstract byte[] X {
+ get;
+ }
+
+ /*
+ * Get the Y coordinate for this point. This implies
+ * normalization. If the point is the point at infinity,
+ * then the returned array contains the encoding of 0.
+ * This is constant-time.
+ */
+ internal abstract byte[] Y {
+ get;
+ }
+
+ /*
+ * Create a new instance that starts with the same contents as
+ * this point.
+ */
+ internal abstract MutableECPoint Dup();
+
+ /*
+ * Set this instance to the point at infinity.
+ */
+ internal abstract void SetZero();
+
+ /*
+ * Set this instance to the same contents as the provided point.
+ * The operand Q must be part of the same curve.
+ */
+ internal abstract void Set(MutableECPoint Q);
+
+ /*
+ * Set this instance to the same contents as the provided point,
+ * but only if ctl == 0xFFFFFFFFF. If ctl == 0x00000000, then
+ * this instance is unmodified. The operand Q must be part of
+ * the same curve.
+ */
+ internal abstract void Set(MutableECPoint Q, uint ctl);
+
+ /*
+ * Set this instance to the same contents as point P1 if
+ * ctl == 0xFFFFFFFF, or point P2 if ctl == 0x00000000.
+ * Both operands must use the same curve as this instance.
+ */
+ internal abstract void SetMux(uint ctl,
+ MutableECPoint P1, MutableECPoint P2);
+
+ /*
+ * DoubleCT() is constant-time. It works for all points
+ * (including points of order 2 and the infinity point).
+ */
+ internal abstract void DoubleCT();
+
+ /*
+ * AddCT() computes P+Q (P is this instance, Q is the operand).
+ * It may assume that P != Q. If P = Q and the method could not
+ * compute the correct result, then it shall set this instance to
+ * 0 (infinity) and return 0x00000000. In all other cases, it must
+ * compute the correct point and return 0xFFFFFFFF. In particular,
+ * it should properly handle cases where P = 0 or Q = 0. This
+ * function is allowed to handle doubling cases as well, if it
+ * can.
+ *
+ * This method may be more efficient if the operand is
+ * normalized. Execution time and memory access may depend on
+ * whether this instance or the other operand is normalized,
+ * but not on the actual point values (including if the points
+ * do not fulfill the properties above).
+ */
+ internal abstract uint AddCT(MutableECPoint Q);
+
+ /*
+ * Negate this point. It also works on the point at infinity,
+ * and it is constant-time.
+ */
+ internal abstract void NegCT();
+
+ /*
+ * Multiply this point by the provided integer (unsigned
+ * big-endian representation). This is constant-time. This
+ * method assumes that:
+ * -- the point on which we are operating is part of the curve
+ * defined subgroup;
+ * -- the defined subgroup has a prime order which is no less
+ * than 17;
+ * -- the point is not the point at infinity;
+ * -- the multiplier operand is no more than the subgroup order.
+ * If these conditions are met, then the resulting point will
+ * be the proper element of the defined subgroup (it will be
+ * the point at infinity only if the multiplier is 0 or is
+ * equal to the subgroup order). If they are NOT met, then the
+ * resulting point is undefined (but will still be part of the
+ * curve).
+ *
+ * This method is constant-time.
+ *
+ * Returned value is 0xFFFFFFFF if none of the internal
+ * operations reached a problematic state (i.e. that we tried to
+ * perform an addition and the two operands turned out to be
+ * equal to each other). If the conditions above are met, then
+ * this is always the case. If a problematic state was reached,
+ * then the returned value is 0x00000000. Callers MUST be very
+ * cautious about using that reported error state, since it is
+ * not guaranteed that all invalid points would be reported as
+ * such. There thus is potential for leakage of secret data.
+ */
+ internal abstract uint MulSpecCT(byte[] n);
+
+ /*
+ * Compare this point to another. This method throws an
+ * exception if the provided point is not on the same curve as
+ * this instance. It otherwise returns 0xFFFFFFFF if both points
+ * are equal, 0x00000000 otherwise. This method is constant-time
+ * (its execution time may depend on whether this and/or the
+ * other point is normalized or not, but not on the actual
+ * values).
+ */
+ internal abstract uint EqCT(MutableECPoint Q);
+
+ internal bool Eq(MutableECPoint Q)
+ {
+ return EqCT(Q) != 0;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * An implementation of MutableECPoint for Curve25519.
+ * This is a partial implementation that only supports multiplication
+ * by a scalar, not general addition.
+ */
+
+internal class MutableECPointCurve25519 : MutableECPoint {
+
+ ModInt x;
+ ModInt x1, x2, z2, x3, z3;
+ ModInt a, aa, b, bb, c, d, e;
+ byte[] u;
+
+ /*
+ * Create a new instance. It is initialized to the point at
+ * infinity (represented with a 0).
+ */
+ internal MutableECPointCurve25519()
+ {
+ ECCurve25519 ccc = (ECCurve25519)EC.Curve25519;
+ x = ccc.mp.Dup();
+ x1 = ccc.mp.Dup();
+ x2 = ccc.mp.Dup();
+ z2 = ccc.mp.Dup();
+ x3 = ccc.mp.Dup();
+ z3 = ccc.mp.Dup();
+ a = ccc.mp.Dup();
+ aa = ccc.mp.Dup();
+ b = ccc.mp.Dup();
+ bb = ccc.mp.Dup();
+ c = ccc.mp.Dup();
+ d = ccc.mp.Dup();
+ e = ccc.mp.Dup();
+ u = new byte[32];
+ }
+
+ internal override ECCurve Curve {
+ get {
+ return EC.Curve25519;
+ }
+ }
+
+ internal override uint IsInfinityCT {
+ get {
+ return 0;
+ }
+ }
+
+ internal override void Normalize()
+ {
+ }
+
+ internal override byte[] Encode(bool compressed)
+ {
+ byte[] r = new byte[32];
+ Encode(r, false);
+ return r;
+ }
+
+ internal override uint Encode(byte[] dst, bool compressed)
+ {
+ if (dst.Length != 32) {
+ throw new CryptoException("invalid output length");
+ }
+ x.Encode(u, 0, 32);
+ for (int i = 0; i < 32; i ++) {
+ dst[i] = u[31 - i];
+ }
+ return 0xFFFFFFFF;
+ }
+
+ internal override uint DecodeCT(byte[] enc)
+ {
+ if (enc.Length != 32) {
+ return 0;
+ }
+ for (int i = 0; i < 32; i ++) {
+ u[i] = enc[31 - i];
+ }
+ u[0] &= 0x7F;
+ x.DecodeReduce(u);
+ return 0xFFFFFFFF;
+ }
+
+ internal override byte[] X {
+ get {
+ return x.Encode();
+ }
+ }
+
+ internal override byte[] Y {
+ get {
+ throw new CryptoException(
+ "Not implemented for Curve25519");
+ }
+ }
+
+ internal override MutableECPoint Dup()
+ {
+ MutableECPointCurve25519 Q = new MutableECPointCurve25519();
+ Q.Set(this);
+ return Q;
+ }
+
+ internal void Set(byte[] X, byte[] Y, bool check)
+ {
+ throw new CryptoException("Not implemented for Curve25519");
+ }
+
+ internal void Set(ModInt X, ModInt Y, bool check)
+ {
+ throw new CryptoException("Not implemented for Curve25519");
+ }
+
+ internal override void SetZero()
+ {
+ throw new CryptoException("Not implemented for Curve25519");
+ }
+
+ internal override void Set(MutableECPoint Q)
+ {
+ MutableECPointCurve25519 R = SameCurve(Q);
+ x.Set(R.x);
+ }
+
+ internal override void Set(MutableECPoint Q, uint ctl)
+ {
+ MutableECPointCurve25519 R = SameCurve(Q);
+ x.CondCopy(R.x, ctl);
+ }
+
+ internal override void SetMux(uint ctl,
+ MutableECPoint P1, MutableECPoint P2)
+ {
+ SetMuxInner(ctl, SameCurve(P1), SameCurve(P2));
+ }
+
+ void SetMuxInner(uint ctl,
+ MutableECPointCurve25519 P1, MutableECPointCurve25519 P2)
+ {
+ x.CopyMux(ctl, P1.x, P2.x);
+ }
+
+ internal override void DoubleCT()
+ {
+ throw new CryptoException("Not implemented for Curve25519");
+ }
+
+ internal override uint AddCT(MutableECPoint Q)
+ {
+ throw new CryptoException("Not implemented for Curve25519");
+ }
+
+ internal override void NegCT()
+ {
+ throw new CryptoException("Not implemented for Curve25519");
+ }
+
+ internal override uint MulSpecCT(byte[] n)
+ {
+ /*
+ * Copy scalar into a temporary array (u[]) for
+ * normalisation to 32 bytes and clamping.
+ */
+ if (n.Length > 32) {
+ return 0;
+ }
+ Array.Copy(n, 0, u, 32 - n.Length, n.Length);
+ for (int i = 0; i < 32 - n.Length; i ++) {
+ u[i] = 0;
+ }
+ u[31] &= 0xF8;
+ u[0] &= 0x7F;
+ u[0] |= 0x40;
+
+ x1.Set(x);
+ x1.ToMonty();
+ x2.SetMonty(0xFFFFFFFF);
+ z2.Set(0);
+ x3.Set(x1);
+ z3.Set(x2);
+ uint swap = 0;
+ ModInt ma24 = ((ECCurve25519)EC.Curve25519).ma24;
+
+ for (int t = 254; t >= 0; t --) {
+ uint kt = (uint)-((u[31 - (t >> 3)] >> (t & 7)) & 1);
+ swap ^= kt;
+ x2.CondSwap(x3, swap);
+ z2.CondSwap(z3, swap);
+ swap = kt;
+
+ a.Set(x2);
+ a.Add(z2);
+ aa.Set(a);
+ aa.MontySquare();
+ b.Set(x2);
+ b.Sub(z2);
+ bb.Set(b);
+ bb.MontySquare();
+ e.Set(aa);
+ e.Sub(bb);
+ c.Set(x3);
+ c.Add(z3);
+ d.Set(x3);
+ d.Sub(z3);
+ d.MontyMul(a);
+ c.MontyMul(b);
+ x3.Set(d);
+ x3.Add(c);
+ x3.MontySquare();
+ z3.Set(d);
+ z3.Sub(c);
+ z3.MontySquare();
+ z3.MontyMul(x1);
+ x2.Set(aa);
+ x2.MontyMul(bb);
+ z2.Set(e);
+ z2.MontyMul(ma24);
+ z2.Add(aa);
+ z2.MontyMul(e);
+ }
+ x2.CondSwap(x3, swap);
+ z2.CondSwap(z3, swap);
+
+ /*
+ * We need to restore z2 to normal representation before
+ * inversion. Then the final Montgomery multiplication
+ * will cancel out with x2, which is still in Montgomery
+ * representation.
+ */
+ z2.FromMonty();
+ z2.Invert();
+ x2.MontyMul(z2);
+
+ /*
+ * x2 now contains the result.
+ */
+ x.Set(x2);
+ return 0xFFFFFFFF;
+ }
+
+ internal override uint EqCT(MutableECPoint Q)
+ {
+ MutableECPointCurve25519 R = SameCurve(Q);
+ return x.EqCT(R.x);
+ }
+
+ MutableECPointCurve25519 SameCurve(MutableECPoint Q)
+ {
+ MutableECPointCurve25519 R = Q as MutableECPointCurve25519;
+ if (R == null) {
+ throw new CryptoException("Mixed curves");
+ }
+ return R;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * An implementation of MutableECPoint for curves in a prime field.
+ * Jacobian coordinates are used.
+ */
+
+internal class MutableECPointPrime : MutableECPoint {
+
+ /*
+ * Internal representation:
+ * x = X / (Z^2)
+ * y = Y / (Z^3)
+ * For the point at infinity, Z == 0.
+ *
+ * When 'affine' is true (0xFFFFFFFF), then mx and my are in
+ * normal representation, and mz is either 0 (point at infinity)
+ * or 1 (otherwise). When 'affine' is false (0x00000000), then
+ * mx, my and mz are in Montgomery representation.
+ *
+ * Note that in affine coordinates, the point at infinity admits
+ * several equivalent representations. In non-affine
+ * coordinates, all points have several equivalent
+ * representations.
+ */
+
+ ECCurvePrime curve;
+ ModInt mx, my, mz;
+ uint affine;
+ ModInt mt1, mt2, mt3, mt4, mt5;
+ ModInt ms1, ms2, ms3, ms4, ms5, ms6;
+
+ /*
+ * Create a new instance. It is initialized to the point at
+ * infinity.
+ */
+ internal MutableECPointPrime(ECCurvePrime curve)
+ {
+ this.curve = curve;
+ mx = curve.mp.Dup();
+ my = curve.mp.Dup();
+ mz = curve.mp.Dup();
+ mt1 = curve.mp.Dup();
+ mt2 = curve.mp.Dup();
+ mt3 = curve.mp.Dup();
+ mt4 = curve.mp.Dup();
+ mt5 = curve.mp.Dup();
+ ms1 = curve.mp.Dup();
+ ms2 = curve.mp.Dup();
+ ms3 = curve.mp.Dup();
+ ms4 = curve.mp.Dup();
+ ms5 = curve.mp.Dup();
+ ms6 = curve.mp.Dup();
+ affine = 0xFFFFFFFF;
+ }
+
+ internal override ECCurve Curve {
+ get {
+ return curve;
+ }
+ }
+
+ internal override uint IsInfinityCT {
+ get {
+ return mz.IsZeroCT;
+ }
+ }
+
+ internal override void Normalize()
+ {
+ ToAffine();
+ }
+
+ internal override byte[] Encode(bool compressed)
+ {
+ ToAffine();
+ if (IsInfinity) {
+ return new byte[1];
+ }
+ if (compressed) {
+ byte[] enc = new byte[curve.EncodedLengthCompressed];
+ enc[0] = (byte)(0x02 + my.GetLSB());
+ mx.Encode(enc, 1, enc.Length - 1);
+ return enc;
+ } else {
+ byte[] enc = new byte[curve.EncodedLength];
+ int flen = (enc.Length - 1) >> 1;
+ enc[0] = 0x04;
+ mx.Encode(enc, 1, flen);
+ my.Encode(enc, 1 + flen, flen);
+ return enc;
+ }
+ }
+
+ internal override uint Encode(byte[] dst, bool compressed)
+ {
+ ToAffine();
+ if (compressed) {
+ int len = curve.EncodedLengthCompressed;
+ if (dst.Length != len) {
+ throw new CryptoException(
+ "invalid output length");
+ }
+ dst[0] = (byte)(0x02 + my.GetLSB());
+ mx.Encode(dst, 1, len - 1);
+ } else {
+ int len = curve.EncodedLength;
+ if (dst.Length != len) {
+ throw new CryptoException(
+ "invalid output length");
+ }
+ int flen = (len - 1) >> 1;
+ dst[0] = 0x04;
+ mx.Encode(dst, 1, flen);
+ my.Encode(dst, 1 + flen, flen);
+ }
+ return ~IsInfinityCT;
+ }
+
+ internal override uint DecodeCT(byte[] enc)
+ {
+ /*
+ * Format (specified in IEEE P1363, annex E):
+ *
+ * 0x00 point at infinity
+ * 0x02+b <X> compressed, b = lsb of Y
+ * 0x04 <X> <Y> uncompressed
+ * 0x06+b <X> <Y> uncompressed, b = lsb of Y
+ *
+ * Coordinates X and Y are in unsigned big-endian
+ * notation with exactly the length of the modulus.
+ *
+ * We want constant-time decoding, up to the encoded
+ * length. This means that the four following situations
+ * can be differentiated:
+ * -- Point is zero (length = 1)
+ * -- Point is compressed (length = 1 + flen)
+ * -- Point is uncompressed or hybrid (length = 1 + 2*flen)
+ * -- Length is neither 1, 1+flen or 1+2*flen.
+ */
+
+ int flen = curve.flen;
+ uint good = 0xFFFFFFFF;
+ if (enc.Length == 1) {
+ /*
+ * 1-byte encoding is point at infinity; the
+ * byte shall have value 0.
+ */
+ int z = enc[0];
+ good &= ~(uint)((z | -z) >> 31);
+ SetZero();
+ } else if (enc.Length == 1 + flen) {
+ /*
+ * Compressed encoding. Leading byte is 0x02 or
+ * 0x03.
+ */
+ int z = (enc[0] & 0xFE) - 0x02;
+ good &= ~(uint)((z | -z) >> 31);
+ uint lsbValue = (uint)(enc[0] & 1);
+ good &= mx.Decode(enc, 1, flen);
+ RebuildY2();
+ if (curve.pMod4 == 3) {
+ good &= my.SqrtBlum();
+ } else {
+ /*
+ * Square roots modulo a non-Blum prime
+ * are a bit more complex. We do not
+ * support them yet (TODO).
+ */
+ good = 0x00000000;
+ }
+
+ /*
+ * Adjust Y depending on LSB.
+ */
+ mt1.Set(my);
+ mt1.Negate();
+ uint dn = (uint)-(int)(my.GetLSB() ^ lsbValue);
+ my.CondCopy(mt1, dn);
+
+ /*
+ * A corner case: LSB adjustment works only if
+ * Y != 0. If Y is 0 and requested LSB is 1,
+ * then the decoding fails. Note that this case
+ * cannot happen with usual prime curves, because
+ * they have a prime order, implying that there is
+ * no valid point such that Y = 0 (that would be
+ * a point of order 2).
+ */
+ good &= ~(uint)-(int)(my.GetLSB() ^ lsbValue);
+
+ mz.Set(1);
+ } else if (enc.Length == 1 + (flen << 1)) {
+ /*
+ * Uncompressed or hybrid. Leading byte is either
+ * 0x04, 0x06 or 0x07. We verify that the X and
+ * Y coordinates fulfill the curve equation.
+ */
+ int fb = enc[0];
+ int z = (fb & 0xFC) - 0x04;
+ good &= ~(uint)((z | -z) >> 31);
+ z = fb - 0x05;
+ good &= (uint)((z | -z) >> 31);
+ good &= mx.Decode(enc, 1, flen);
+ RebuildY2();
+ mt1.Set(my);
+ mt1.FromMonty();
+ good &= my.Decode(enc, 1 + flen, flen);
+ mt2.Set(my);
+ mt2.MontySquare();
+ good &= mt1.EqCT(mt2);
+
+ /*
+ * We must check the LSB for hybrid encoding.
+ * The check fails if the encoding is marked as
+ * hybrid AND the LSB does not match.
+ */
+ int lm = (fb >> 1) & ((int)my.GetLSB() ^ fb) & 1;
+ good &= ~(uint)-lm;
+
+ mz.Set(1);
+ } else {
+ good = 0x00000000;
+ }
+
+ /*
+ * If decoding failed, then we force the value to 0.
+ * Otherwise, we got a value. Either way, this uses
+ * affine coordinates.
+ */
+ mx.CondCopy(curve.mp, ~good);
+ my.CondCopy(curve.mp, ~good);
+ mz.CondCopy(curve.mp, ~good);
+ affine = 0xFFFFFFFF;
+ return good;
+ }
+
+ internal override byte[] X {
+ get {
+ ToAffine();
+ return mx.Encode();
+ }
+ }
+
+ internal override byte[] Y {
+ get {
+ ToAffine();
+ return my.Encode();
+ }
+ }
+
+ internal override MutableECPoint Dup()
+ {
+ MutableECPointPrime Q = new MutableECPointPrime(curve);
+ Q.Set(this);
+ return Q;
+ }
+
+ internal void Set(byte[] X, byte[] Y, bool check)
+ {
+ mx.Decode(X);
+ my.Decode(Y);
+ mz.Set(1);
+ affine = 0xFFFFFFFF;
+ if (check) {
+ CheckEquation();
+ }
+ }
+
+ internal void Set(ModInt X, ModInt Y, bool check)
+ {
+ mx.Set(X);
+ my.Set(Y);
+ mz.Set(1);
+ affine = 0xFFFFFFFF;
+ if (check) {
+ CheckEquation();
+ }
+ }
+
+ void CheckEquation()
+ {
+ curve.RebuildY2(mx, mt1, mt2);
+ mt2.Set(my);
+ mt2.ToMonty();
+ mt2.MontyMul(my);
+ if (!mt1.Eq(mt2)) {
+ throw new CryptoException(
+ "Point is not on the curve");
+ }
+ }
+
+ internal override void SetZero()
+ {
+ mx.Set(0);
+ my.Set(0);
+ mz.Set(0);
+ affine = 0xFFFFFFFF;
+ }
+
+ internal override void Set(MutableECPoint Q)
+ {
+ MutableECPointPrime R = SameCurve(Q);
+ mx.Set(R.mx);
+ my.Set(R.my);
+ mz.Set(R.mz);
+ affine = R.affine;
+ }
+
+ internal override void Set(MutableECPoint Q, uint ctl)
+ {
+ MutableECPointPrime R = SameCurve(Q);
+ mx.CondCopy(R.mx, ctl);
+ my.CondCopy(R.my, ctl);
+ mz.CondCopy(R.mz, ctl);
+ affine ^= ctl & (affine ^ R.affine);
+ }
+
+ internal override void SetMux(uint ctl,
+ MutableECPoint P1, MutableECPoint P2)
+ {
+ SetMuxInner(ctl, SameCurve(P1), SameCurve(P2));
+ }
+
+ void SetMuxInner(uint ctl,
+ MutableECPointPrime P1, MutableECPointPrime P2)
+ {
+ mx.CopyMux(ctl, P1.mx, P2.mx);
+ my.CopyMux(ctl, P1.my, P2.my);
+ mz.CopyMux(ctl, P1.mz, P2.mz);
+ affine = P2.affine ^ (ctl & (P1.affine ^ P2.affine));
+ }
+
+ internal override void DoubleCT()
+ {
+ ToJacobian();
+
+ /*
+ * Formulas are:
+ * S = 4*X*Y^2
+ * M = 3*X^2 + a*Z^4
+ * X' = M^2 - 2*S
+ * Y' = M*(S - X') - 8*Y^4
+ * Z' = 2*Y*Z
+ *
+ * These formulas also happen to work properly (with our
+ * chosen representation) when the source point has
+ * order 2 (Y = 0 implies Z' = 0) and when the source
+ * point is already the point at infinity (Z = 0 implies
+ * Z' = 0).
+ *
+ * When a = -3, the value of M can be computed with the
+ * more efficient formula:
+ * M = 3*(X+Z^2)*(X-Z^2)
+ */
+
+ /*
+ * Compute M in t1.
+ */
+ if (curve.aIsM3) {
+ /*
+ * Set t1 = Z^2.
+ */
+ mt1.Set(mz);
+ mt1.MontySquare();
+
+ /*
+ * Set t2 = X-Z^2 and then t1 = X+Z^2.
+ */
+ mt2.Set(mx);
+ mt2.Sub(mt1);
+ mt1.Add(mx);
+
+ /*
+ * Set t1 = 3*(X+Z^2)*(X-Z^2).
+ */
+ mt1.MontyMul(mt2);
+ mt2.Set(mt1);
+ mt1.Add(mt2);
+ mt1.Add(mt2);
+ } else {
+ /*
+ * Set t1 = 3*X^2.
+ */
+ mt1.Set(mx);
+ mt1.MontySquare();
+ mt2.Set(mt1);
+ mt1.Add(mt2);
+ mt1.Add(mt2);
+
+ /*
+ * Set t2 = a*Z^4.
+ */
+ mt2.Set(mz);
+ mt2.MontySquare();
+ mt2.MontySquare();
+ mt2.MontyMul(curve.ma);
+
+ /*
+ * Set t1 = 3*X^2 + a*Z^4.
+ */
+ mt1.Add(mt2);
+ }
+
+ /*
+ * Compute S = 4*X*Y^2 in t2. We also save 2*Y^2 in mt3.
+ */
+ mt2.Set(my);
+ mt2.MontySquare();
+ mt2.Add(mt2);
+ mt3.Set(mt2);
+ mt2.Add(mt2);
+ mt2.MontyMul(mx);
+
+ /*
+ * Compute X' = M^2 - 2*S.
+ */
+ mx.Set(mt1);
+ mx.MontySquare();
+ mx.Sub(mt2);
+ mx.Sub(mt2);
+
+ /*
+ * Compute Z' = 2*Y*Z.
+ */
+ mz.MontyMul(my);
+ mz.Add(mz);
+
+ /*
+ * Compute Y' = M*(S - X') - 8*Y^4. We already have
+ * 4*Y^2 in t3.
+ */
+ mt2.Sub(mx);
+ mt2.MontyMul(mt1);
+ mt3.MontySquare();
+ mt3.Add(mt3);
+ my.Set(mt2);
+ my.Sub(mt3);
+ }
+
+ internal override uint AddCT(MutableECPoint Q)
+ {
+ MutableECPointPrime P2 = SameCurve(Q);
+
+ if (P2.affine != 0) {
+ ms4.Set(P2.mx);
+ ms5.Set(P2.my);
+ ms6.Set(P2.mz);
+ ms4.ToMonty();
+ ms5.ToMonty();
+ ms6.SetMonty(~ms6.IsZeroCT);
+ return AddCTInner(ms4, ms5, ms6, true);
+ } else {
+ return AddCTInner(P2.mx, P2.my, P2.mz, false);
+ }
+ }
+
+ /*
+ * Inner function for addition. The Jacobian coordinates for
+ * the operand are provided in Montogomery representation. If
+ * p2affine is true, then it is guaranteed that p2z is 1
+ * (converted to Montogomery).
+ */
+ uint AddCTInner(ModInt p2x, ModInt p2y, ModInt p2z, bool p2affine)
+ {
+ /*
+ * In this comment, the two operands are called P1 and
+ * P2. P1 is this instance; P2 is the operand. Coordinates
+ * of P1 are (X1,Y1,Z1). Coordinates of P2 are (X2,Y2,Z2).
+ *
+ * Formulas:
+ * U1 = X1 * Z2^2
+ * U2 = X2 * Z1^2
+ * S1 = Y1 * Z2^3
+ * S2 = Y2 * Z1^3
+ * H = U2 - U1
+ * R = S2 - S1
+ * X3 = R^2 - H^3 - 2*U1*H^2
+ * Y3 = R*(U1*H^2 - X3) - S1*H^3
+ * Z3 = H*Z1*Z2
+ *
+ * If both P1 and P2 are 0, then the formulas yield 0,
+ * which is fine. If one of P1 and P2 is 0 (but not both),
+ * then we get 0 as result, which is wrong and must be
+ * fixed at the end.
+ *
+ * If U1 == U2 and S1 == S2 then this means that either
+ * P1 or P2 is 0 (or both), or P1 == P2. In the latter
+ * case, the formulas are wrong and we must report
+ * an error.
+ *
+ * If U1 == U2 and S1 != S2 then P1 + P2 = 0. We get H = 0,
+ * which implies that we obtain the point at infinity,
+ * which is fine.
+ */
+
+ uint P1IsZero = mz.IsZeroCT;
+ uint P2IsZero = p2z.IsZeroCT;
+
+ ToJacobian();
+
+ /*
+ * Save this value, in case the operand turns out to
+ * be the point at infinity.
+ */
+ ms1.Set(mx);
+ ms2.Set(my);
+ ms3.Set(mz);
+
+ /*
+ * Compute U1 = X1*Z2^2 in t1, and S1 = Y1*Z2^3 in t3.
+ */
+ if (p2affine) {
+ mt1.Set(mx);
+ mt3.Set(my);
+ } else {
+ mt3.Set(p2z);
+ mt3.MontySquare();
+ mt1.Set(mx);
+ mt1.MontyMul(mt3);
+ mt3.MontyMul(p2z);
+ mt3.MontyMul(my);
+ }
+ //PrintMR(" u1 = x1*z2^2", mt1);
+ //PrintMR(" s1 = y1*z2^3", mt3);
+
+ /*
+ * Compute U2 = X2*Z1^2 in t2, and S2 = Y2*Z1^3 in t4.
+ */
+ mt4.Set(mz);
+ mt4.MontySquare();
+ mt2.Set(p2x);
+ mt2.MontyMul(mt4);
+ mt4.MontyMul(mz);
+ mt4.MontyMul(p2y);
+ //PrintMR(" u2 = x2*z1^2", mt2);
+ //PrintMR(" s2 = y2*z1^3", mt4);
+
+ /*
+ * Compute H = U2 - U1 in t2, and R = S2 - S1 in t4.
+ */
+ mt2.Sub(mt1);
+ mt4.Sub(mt3);
+ //PrintMR(" h = u2-u1", mt2);
+ //PrintMR(" r = s2-s1", mt4);
+
+ /*
+ * If both H and R are 0, then we may have a problem
+ * (either P1 == P2, or P1 == 0, or P2 == 0).
+ */
+ uint formProb = mt2.IsZeroCT & mt4.IsZeroCT;
+
+ /*
+ * Compute U1*H^2 in t1 and H^3 in t5.
+ */
+ mt5.Set(mt2);
+ mt5.MontySquare();
+ mt1.MontyMul(mt5);
+ mt5.MontyMul(mt2);
+ //PrintMR(" u1*h^2", mt1);
+ //PrintMR(" h^3", mt5);
+
+ /*
+ * Compute X3 = R^2 - H^3 - 2*U1*H^2.
+ */
+ mx.Set(mt4);
+ mx.MontySquare();
+ mx.Sub(mt5);
+ mx.Sub(mt1);
+ mx.Sub(mt1);
+ //PrintMR(" x3 = r^2-h^3-2*u1*h^2", mx);
+
+ /*
+ * Compute Y3 = R*(U1*H^2 - X3) - S1*H^3.
+ */
+ mt1.Sub(mx);
+ mt1.MontyMul(mt4);
+ mt5.MontyMul(mt3);
+ mt1.Sub(mt5);
+ my.Set(mt1);
+ //PrintMR(" y3 = r*(u1*h^2-x3)-s1*h^3", my);
+
+ /*
+ * Compute Z3 = H*Z1*Z2.
+ */
+ mz.MontyMul(mt2);
+ if (!p2affine) {
+ mz.MontyMul(p2z);
+ }
+ //PrintMR(" z3 = h*z1*z2", mz);
+
+ /*
+ * Fixup: handle the cases where P1 = 0 or P2 = 0.
+ */
+ mx.CondCopy(ms1, P2IsZero);
+ my.CondCopy(ms2, P2IsZero);
+ mz.CondCopy(ms3, P2IsZero);
+ mx.CondCopy(p2x, P1IsZero);
+ my.CondCopy(p2y, P1IsZero);
+ mz.CondCopy(p2z, P1IsZero);
+
+ /*
+ * Report failure when P1 == P2, except when one of
+ * the points was zero (or both) because that case
+ * was properly handled.
+ */
+ return (~formProb) | P1IsZero | P2IsZero;
+ }
+
+ internal override void NegCT()
+ {
+ my.Negate();
+ }
+
+ internal override uint MulSpecCT(byte[] n)
+ {
+ uint good = 0xFFFFFFFF;
+
+ /*
+ * Create and populate window.
+ *
+ * If this instance is 0, then we only add 0 to 0 and
+ * double 0, for which DoubleCT() and AddCT() work
+ * properly.
+ *
+ * If this instance (P) is non-zero, then x*P for all
+ * x in the 1..16 range shall be non-zero and distinct,
+ * since the subgroup order is prime and at least 17.
+ * Thus, we never add two equal points together in the
+ * window construction.
+ *
+ * We MUST ensure that all points are in the same
+ * coordinate convention (affine or Jacobian) to ensure
+ * constant-time execution. TODO: measure to see which
+ * is best: all affine or all Jacobian. All affine implies
+ * 14 or 15 extra divisions, but saves a few hundreds of
+ * multiplications.
+ */
+ MutableECPointPrime[] w = new MutableECPointPrime[16];
+ w[0] = new MutableECPointPrime(curve);
+ w[0].ToJacobian();
+ w[1] = new MutableECPointPrime(curve);
+ w[1].Set(this);
+ w[1].ToJacobian();
+ for (int i = 2; (i + 1) < w.Length; i += 2) {
+ w[i] = new MutableECPointPrime(curve);
+ w[i].Set(w[i >> 1]);
+ w[i].DoubleCT();
+ w[i + 1] = new MutableECPointPrime(curve);
+ w[i + 1].Set(w[i]);
+ good &= w[i + 1].AddCT(this);
+ }
+
+ /* obsolete
+ for (int i = 0; i < w.Length; i ++) {
+ w[i].ToAffine();
+ w[i].Print("Win " + i);
+ w[i].ToJacobian();
+ }
+ Console.WriteLine("good = {0}", (int)good);
+ */
+
+ /*
+ * Set this value to 0. We also set it already to
+ * Jacobian coordinates, since it will be done that
+ * way anyway. This instance will serve as accumulator.
+ */
+ mx.Set(0);
+ my.Set(0);
+ mz.Set(0);
+ affine = 0x00000000;
+
+ /*
+ * We process the multiplier by 4-bit nibbles, starting
+ * with the most-significant one (the high nibble of the
+ * first byte, since we use big-endian notation).
+ *
+ * For each nibble, we perform a constant-time lookup
+ * in the window, to obtain the point to add to the
+ * current value of the accumulator. Thanks to the
+ * conditions on the operands (prime subgroup order and
+ * so on), all the additions below must work.
+ */
+ MutableECPointPrime t = new MutableECPointPrime(curve);
+ for (int i = (n.Length << 1) - 1; i >= 0; i --) {
+ int b = n[n.Length - 1 - (i >> 1)];
+ int j = (b >> ((i & 1) << 2)) & 0x0F;
+ for (int k = 0; k < 16; k ++) {
+ t.Set(w[k], ~(uint)(((j - k) | (k - j)) >> 31));
+ }
+ good &= AddCT(t);
+ if (i > 0) {
+ DoubleCT();
+ DoubleCT();
+ DoubleCT();
+ DoubleCT();
+ }
+ }
+
+ return good;
+ }
+
+ internal override uint EqCT(MutableECPoint Q)
+ {
+ MutableECPointPrime R = SameCurve(Q);
+ if (affine != 0) {
+ if (R.affine != 0) {
+ return mx.EqCT(R.mx)
+ & my.EqCT(R.my)
+ & mz.EqCT(R.mz);
+ } else {
+ return EqCTMixed(R, this);
+ }
+ } else if (R.affine != 0) {
+ return EqCTMixed(this, R);
+ }
+
+ /*
+ * Both points are in Jacobian coordinates.
+ * If Z1 and Z2 are non-zero, then equality is
+ * achieved if and only if both following equations
+ * are true:
+ * X1*(Z2^2) = X2*(Z1^2)
+ * Y1*(Z2^3) = Y2*(Z1^3)
+ * If Z1 or Z2 is zero, then equality is achieved
+ * if and only if both are zero.
+ */
+ mt1.Set(mz);
+ mt1.MontySquare();
+ mt2.Set(R.mz);
+ mt2.MontySquare();
+ mt3.Set(mx);
+ mt3.MontyMul(mt2);
+ mt4.Set(R.mx);
+ mt4.MontyMul(mt1);
+ uint r = mt3.EqCT(mt4);
+ mt1.MontyMul(mz);
+ mt2.MontyMul(R.mz);
+ mt3.Set(my);
+ mt3.MontyMul(mt2);
+ mt4.Set(R.my);
+ mt4.MontyMul(mt1);
+ r &= mt3.EqCT(mt4);
+ uint z1z = mz.IsZeroCT;
+ uint z2z = R.mz.IsZeroCT;
+ return (r & ~(z1z | z2z)) ^ (z1z & z2z);
+ }
+
+ /*
+ * Mixed comparison: P1 is in Jacobian coordinates, P2 is in
+ * affine coordinates.
+ */
+ uint EqCTMixed(MutableECPointPrime P1, MutableECPointPrime P2)
+ {
+ /*
+ * If either P1 or P2 is infinity, then they are equal
+ * if and only if they both are infinity.
+ *
+ * If neither is infinity, then we must check the following:
+ * X1 = X2*(Z1^2)
+ * Y1 = Y2*(Z1^3)
+ * Beware that X1, Y1 and Z1 are in Montgomery representation,
+ * while X2 and Y2 are not.
+ */
+ mt1.Set(P1.mz);
+ mt1.MontySquare();
+ mt2.Set(P2.mx);
+ mt2.MontyMul(mt1);
+ mt3.Set(P1.mx);
+ mt3.FromMonty();
+ uint r = mt2.EqCT(mt3);
+ mt1.MontyMul(P1.mz);
+ mt1.MontyMul(P2.my);
+ mt2.Set(P1.my);
+ mt2.FromMonty();
+ r &= mt1.EqCT(mt2);
+ uint z1z = P1.mz.IsZeroCT;
+ uint z2z = P2.mz.IsZeroCT;
+ return (r & ~(z1z | z2z)) ^ (z1z & z2z);
+ }
+
+ MutableECPointPrime SameCurve(MutableECPoint Q)
+ {
+ MutableECPointPrime R = Q as MutableECPointPrime;
+ if (R == null || !curve.Equals(R.curve)) {
+ throw new CryptoException("Mixed curves");
+ }
+ return R;
+ }
+
+ /*
+ * Convert to Jabobian coordinates (if not already done).
+ */
+ void ToJacobian()
+ {
+ if (affine == 0) {
+ return;
+ }
+
+ /*
+ * Since Z = 0 or 1 in affine coordinates, we can
+ * use SetMonty().
+ */
+ mx.ToMonty();
+ my.ToMonty();
+ mz.SetMonty(~mz.IsZeroCT);
+ affine = 0x00000000;
+ }
+
+ /*
+ * Convert to affine coordinates (if not already done).
+ */
+ void ToAffine()
+ {
+ if (affine != 0) {
+ return;
+ }
+
+ /*
+ * Divisions are expensive, so we want to make only one,
+ * not two. This involves some games with Montgomery
+ * representation.
+ *
+ * A number a in Montgomery representation means that
+ * the value we have is equal to aR. Montgomery
+ * multiplication of a by b yields ab/R (so, if we
+ * apply it to aR and bR, we get abR).
+ */
+
+ /* Save Z*R in mt1. */
+ mt1.Set(mz);
+
+ /* Compute Z^3 in mz. */
+ mz.MontySquare();
+ mz.MontyMul(mt1);
+ mz.FromMonty();
+
+ /* Compute t2 = 1/Z^3. */
+ mt2.Set(mz);
+ mt2.Invert();
+ uint cc = ~mt2.IsZeroCT;
+
+ /* Compute y. */
+ my.MontyMul(mt2);
+
+ /* Compute t2 = 1/Z^2. */
+ mt2.MontyMul(mt1);
+
+ /* Compute x. */
+ mx.MontyMul(mt2);
+
+ /*
+ * If the point is infinity (division by Z^2 failed),
+ * then set all coordinates to 0. Otherwise, set mz
+ * to exactly 1.
+ */
+ mx.CondCopy(curve.mp, ~cc);
+ my.CondCopy(curve.mp, ~cc);
+ mz.Set((int)cc & 1);
+
+ affine = 0xFFFFFFFF;
+ }
+
+ /*
+ * Compute Y^2 into my, using the value in mx as X. Both values
+ * are in normal (non-Montgomery) representation.
+ */
+ void RebuildY2()
+ {
+ my.Set(mx);
+ my.ToMonty();
+ mt1.Set(my);
+ my.MontySquare();
+ my.MontyMul(mx);
+ mt1.MontyMul(curve.ma);
+ my.Add(mt1);
+ my.Add(curve.mb);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains static definitions for the NIST elliptic
+ * curves.
+ */
+
+public class NIST {
+
+ // public static ECCurve P192;
+ // public static ECCurve P224;
+ public static ECCurve P256;
+ public static ECCurve P384;
+ public static ECCurve P521;
+
+ static NIST()
+ {
+ P256 = MakePrime(
+ "P-256",
+ "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
+ "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
+ "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
+ "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
+ "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
+ "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
+ "01");
+ P384 = MakePrime(
+ "P-384",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
+ "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
+ "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
+ "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
+ "01");
+ P521 = MakePrime(
+ "P-521",
+ "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+ "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
+ "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
+ "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
+ "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
+ "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
+ "01");
+ }
+
+ static ECCurvePrime MakePrime(string name, string smod,
+ string sa, string sb, string sgx, string sgy,
+ string sso, string scf)
+ {
+ return new ECCurvePrime(
+ name,
+ ToBytes(smod), ToBytes(sa), ToBytes(sb),
+ ToBytes(sgx), ToBytes(sgy),
+ ToBytes(sso), ToBytes(scf));
+ }
+
+ static byte[] ToBytes(string s)
+ {
+ int len = ToBytes(s, null);
+ byte[] dst = new byte[len];
+ ToBytes(s, dst);
+ return dst;
+ }
+
+ static int ToBytes(string str, byte[] dst)
+ {
+ int off = 0;
+ bool z = true;
+ int acc = 0;
+ foreach (char c in str) {
+ int d;
+ if (c >= '0' && c <= '9') {
+ d = c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ d = c - ('A' - 10);
+ } else if (c >= 'a' && c <= 'f') {
+ d = c - ('a' - 10);
+ } else if (c == ' ' || c == '\t' || c == ':') {
+ continue;
+ } else {
+ throw new ArgumentException(String.Format(
+ "not hex: U+{0:X4}", (int)c));
+ }
+ if (z) {
+ acc = d;
+ } else {
+ if (dst != null) {
+ dst[off] = (byte)((acc << 4) + d);
+ }
+ off ++;
+ }
+ z = !z;
+ }
+ if (!z) {
+ throw new ArgumentException("final half byte");
+ }
+ return off;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class implements Poly1305, when used with ChaCha20. The
+ * ChaCha20 instance (already set with the secret key) is passed as
+ * parameter. Instances are not thread-safe.
+ */
+
+public sealed class Poly1305 {
+
+ /*
+ * The ChaCha20 instance to use for encryption and decryption.
+ * That instance MUST be set, and it must have been initialised
+ * with the key to use.
+ */
+ public ChaCha20 ChaCha {
+ get; set;
+ }
+
+ byte[] pkey;
+ uint[] r;
+ uint[] acc;
+ byte[] foot;
+ byte[] tmp;
+
+ /*
+ * Create a new instance. The ChaCha20 instance to use MUST be
+ * set in the 'ChaCha' property.
+ */
+ public Poly1305()
+ {
+ pkey = new byte[32];
+ r = new uint[5];
+ acc = new uint[5];
+ foot = new byte[16];
+ tmp = new byte[16];
+ }
+
+ /*
+ * Run Poly1305 and ChaCha20 on the provided elements.
+ *
+ * iv Nonce for ChaCha20, exactly 12 bytes
+ * data data to encrypt or decrypt (buffer, off + len)
+ * aad additional authenticated data (buffer, offAAD + lenAAD)
+ * tag destination for computed authentication tag
+ * encrypt true to encrypt, false to decrypt
+ */
+ public void Run(byte[] iv,
+ byte[] data, int off, int len,
+ byte[] aad, int offAAD, int lenAAD,
+ byte[] tag, bool encrypt)
+ {
+ /*
+ * Compute Poly1305 key.
+ */
+ for (int i = 0; i < pkey.Length; i ++) {
+ pkey[i] = 0;
+ }
+ ChaCha.Run(iv, 0, pkey);
+
+ /*
+ * If encrypting, ChaCha20 must run first (the MAC is
+ * computed on the ciphertext).
+ */
+ if (encrypt) {
+ ChaCha.Run(iv, 1, data, off, len);
+ }
+
+ /*
+ * Decode the 'r' value into 26-bit words, with the
+ * "clamping" operation applied.
+ */
+ r[0] = Dec32le(pkey, 0) & 0x03FFFFFF;
+ r[1] = (Dec32le(pkey, 3) >> 2) & 0x03FFFF03;
+ r[2] = (Dec32le(pkey, 6) >> 4) & 0x03FFC0FF;
+ r[3] = (Dec32le(pkey, 9) >> 6) & 0x03F03FFF;
+ r[4] = (Dec32le(pkey, 12) >> 8) & 0x000FFFFF;
+
+ /*
+ * Accumulator is 0.
+ */
+ acc[0] = 0;
+ acc[1] = 0;
+ acc[2] = 0;
+ acc[3] = 0;
+ acc[4] = 0;
+
+ /*
+ * Process AAD, ciphertext and footer.
+ */
+ Enc32le(lenAAD, foot, 0);
+ Enc32le(0, foot, 4);
+ Enc32le(len, foot, 8);
+ Enc32le(0, foot, 12);
+ RunInner(aad, offAAD, lenAAD);
+ RunInner(data, off, len);
+ RunInner(foot, 0, 16);
+
+ /*
+ * Finalize modular reduction. The output of RunInner() is
+ * already mostly reduced: only acc[1] may be (very slightly)
+ * above 2^26. Thus, we only need one loop, back to acc[1].
+ */
+ uint cc = 0;
+ for (int i = 1; i <= 6; i ++) {
+ int j;
+
+ j = (i >= 5) ? i - 5 : i;
+ acc[j] += cc;
+ cc = acc[j] >> 26;
+ acc[j] &= 0x03FFFFFF;
+ }
+
+ /*
+ * The final value may still be in the 2^130-5..2^130-1
+ * range, in which case an additional subtraction must be
+ * performed, with constant-time code.
+ */
+ cc = (uint)((int)(0x03FFFFFA - acc[0]) >> 31);
+ for (int i = 1; i < 5; i ++) {
+ int z = (int)(acc[i] - 0x03FFFFFF);
+ cc &= ~(uint)((z | -z) >> 31);
+ }
+ cc &= 5;
+ for (int i = 0; i < 5; i ++) {
+ uint t = acc[i] + cc;
+ cc = t >> 26;
+ acc[i] = t & 0x03FFFFFF;
+ }
+
+ /*
+ * The tag is the sum of the 's' value (second half of
+ * the pkey[] array, little-endian encoding) and the
+ * current accumulator value. This addition is done modulo
+ * 2^128, i.e. with a simple truncation.
+ */
+ cc = 0;
+ uint aw = 0;
+ int awLen = 0;
+ for (int i = 0, j = 0; i < 16; i ++) {
+ if (awLen < 8) {
+ /*
+ * We "refill" our running byte buffer with
+ * a new extra accumulator word. Note that
+ * 'awLen' is always even, so at this point
+ * it must be 6 or less; since accumulator
+ * words fit on 32 bits, the operation
+ * below does not lose any bit.
+ */
+ aw |= acc[j ++] << awLen;
+ awLen += 26;
+ }
+ uint tb = (aw & 0xFF) + cc + pkey[16 + i];
+ aw >>= 8;
+ awLen -= 8;
+ tag[i] = (byte)tb;
+ cc = tb >> 8;
+ }
+
+ /*
+ * If decrypting, then we still have the ciphertext at
+ * this point, and we must perform the decryption.
+ */
+ if (!encrypt) {
+ ChaCha.Run(iv, 1, data, off, len);
+ }
+ }
+
+ /*
+ * Inner processing of the provided data. The accumulator and 'r'
+ * value are set in the instance fields, and must be updated.
+ * All accumulator words fit on 26 bits each, except the second
+ * (acc[1]) which may be very slightly above 2^26.
+ */
+ void RunInner(byte[] data, int off, int len)
+ {
+ /*
+ * Implementation is inspired from the public-domain code
+ * available there:
+ * https://github.com/floodyberry/poly1305-donna
+ */
+ uint r0 = r[0];
+ uint r1 = r[1];
+ uint r2 = r[2];
+ uint r3 = r[3];
+ uint r4 = r[4];
+
+ uint u1 = r1 * 5;
+ uint u2 = r2 * 5;
+ uint u3 = r3 * 5;
+ uint u4 = r4 * 5;
+
+ uint a0 = acc[0];
+ uint a1 = acc[1];
+ uint a2 = acc[2];
+ uint a3 = acc[3];
+ uint a4 = acc[4];
+
+ while (len > 0) {
+ if (len < 16) {
+ Array.Copy(data, off, tmp, 0, len);
+ for (int i = len; i < 16; i ++) {
+ tmp[i] = 0;
+ }
+ data = tmp;
+ off = 0;
+ }
+
+ /*
+ * Decode next block, with the "high bit" applied,
+ * and add that value to the accumulator.
+ */
+ a0 += Dec32le(data, off) & 0x03FFFFFF;
+ a1 += (Dec32le(data, off + 3) >> 2) & 0x03FFFFFF;
+ a2 += (Dec32le(data, off + 6) >> 4) & 0x03FFFFFF;
+ a3 += (Dec32le(data, off + 9) >> 6) & 0x03FFFFFF;
+ a4 += (Dec32le(data, off + 12) >> 8) | 0x01000000;
+
+ /*
+ * Compute multiplication. All elementary
+ * multiplications are 32x32->64.
+ */
+ ulong w0 = (ulong)a0 * r0
+ + (ulong)a1 * u4
+ + (ulong)a2 * u3
+ + (ulong)a3 * u2
+ + (ulong)a4 * u1;
+ ulong w1 = (ulong)a0 * r1
+ + (ulong)a1 * r0
+ + (ulong)a2 * u4
+ + (ulong)a3 * u3
+ + (ulong)a4 * u2;
+ ulong w2 = (ulong)a0 * r2
+ + (ulong)a1 * r1
+ + (ulong)a2 * r0
+ + (ulong)a3 * u4
+ + (ulong)a4 * u3;
+ ulong w3 = (ulong)a0 * r3
+ + (ulong)a1 * r2
+ + (ulong)a2 * r1
+ + (ulong)a3 * r0
+ + (ulong)a4 * u4;
+ ulong w4 = (ulong)a0 * r4
+ + (ulong)a1 * r3
+ + (ulong)a2 * r2
+ + (ulong)a3 * r1
+ + (ulong)a4 * r0;
+
+ /*
+ * Most of the modular reduction was done by using
+ * the 'u*' multipliers. We still need to do some
+ * carry propagation.
+ */
+ ulong c;
+ c = w0 >> 26;
+ a0 = (uint)w0 & 0x03FFFFFF;
+ w1 += c;
+ c = w1 >> 26;
+ a1 = (uint)w1 & 0x03FFFFFF;
+ w2 += c;
+ c = w2 >> 26;
+ a2 = (uint)w2 & 0x03FFFFFF;
+ w3 += c;
+ c = w3 >> 26;
+ a3 = (uint)w3 & 0x03FFFFFF;
+ w4 += c;
+ c = w4 >> 26;
+ a4 = (uint)w4 & 0x03FFFFFF;
+ a0 += (uint)c * 5;
+ a1 += a0 >> 26;
+ a0 &= 0x03FFFFFF;
+
+ off += 16;
+ len -= 16;
+ }
+
+ acc[0] = a0;
+ acc[1] = a1;
+ acc[2] = a2;
+ acc[3] = a3;
+ acc[4] = a4;
+ }
+
+ static uint Dec32le(byte[] buf, int off)
+ {
+ return (uint)buf[off]
+ | ((uint)buf[off + 1] << 8)
+ | ((uint)buf[off + 2] << 16)
+ | ((uint)buf[off + 3] << 24);
+ }
+
+ static void Enc32le(int x, byte[] buf, int off)
+ {
+ buf[off] = (byte)x;
+ buf[off + 1] = (byte)(x >> 8);
+ buf[off + 2] = (byte)(x >> 16);
+ buf[off + 3] = (byte)(x >> 24);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class implements the computation of the transient secret value
+ * "k" for DSA and ECDSA, using the method described in RFC 6979. It
+ * can perform the deterministic computation, and optionally inject
+ * extra random bytes when randomized signatures are needed.
+ */
+
+class RFC6979 {
+
+ HMAC_DRBG drbg;
+ byte[] q;
+ int qlen;
+ ModInt mh;
+
+ internal RFC6979(IDigest h, byte[] q, byte[] x,
+ byte[] hv, bool deterministic)
+ : this(h, q, x, hv, 0, hv.Length, deterministic)
+ {
+ }
+
+ internal RFC6979(IDigest h, byte[] q, byte[] x,
+ byte[] hv, int hvOff, int hvLen, bool deterministic)
+ {
+ if (h == null) {
+ h = new SHA256();
+ } else {
+ h = h.Dup();
+ h.Reset();
+ }
+ drbg = new HMAC_DRBG(h);
+ mh = new ModInt(q);
+ qlen = mh.ModBitLength;
+ int qolen = (qlen + 7) >> 3;
+ this.q = new byte[qolen];
+ Array.Copy(q, q.Length - qolen, this.q, 0, qolen);
+ int hlen = hvLen << 3;
+ if (hlen > qlen) {
+ byte[] htmp = new byte[hvLen];
+ Array.Copy(hv, hvOff, htmp, 0, hv.Length);
+ BigInt.RShift(htmp, hlen - qlen);
+ hv = htmp;
+ hvOff = 0;
+ }
+ mh.DecodeReduce(hv, hvOff, hvLen);
+ ModInt mx = mh.Dup();
+ mx.Decode(x);
+
+ byte[] seed = new byte[(qolen << 1) + (deterministic ? 0 : 32)];
+ mx.Encode(seed, 0, qolen);
+ mh.Encode(seed, qolen, qolen);
+ if (!deterministic) {
+ RNG.GetBytes(seed, qolen << 1,
+ seed.Length - (qolen << 1));
+ }
+ drbg.SetSeed(seed);
+ }
+
+ internal ModInt GetHashMod()
+ {
+ return mh.Dup();
+ }
+
+ internal void NextK(byte[] k)
+ {
+ for (;;) {
+ drbg.GetBytes(k);
+ BigInt.RShift(k, (k.Length << 3) - qlen);
+ if (!BigInt.IsZero(k) && BigInt.CompareCT(k, q) < 0) {
+ return;
+ }
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using NC = System.Security.Cryptography;
+
+namespace Crypto {
+
+/*
+ * Random generator.
+ */
+
+public sealed class RNG {
+
+ /*
+ * To ensure efficient generation of random numbers, we use
+ * our own PRNG, seeded with a strong value (from the operating
+ * system), and based on AES-CTR. We obtain a random 128-bit
+ * key from the OS (RNGCryptoServiceProvider); then we use it
+ * to encrypt successive values for a 128-bit counter (also
+ * initialized from RNGCryptoServiceProvider). This is AES-CTR
+ * mode and thus provably as strong as AES-128 encryption as it
+ * is practiced in SSL/TLS.
+ *
+ * A mutex is used to ensure safe access in a multi-threaded
+ * context. Once initialized, random generation proceeds at
+ * the same speed as AES encryption, i.e. fast enough for
+ * our purposes.
+ *
+ * As a special action for debugging, it is possible to reset
+ * the state to an explicit seed value. Of course, this tends
+ * to kill security, so it should be used only to make actions
+ * reproducible, as part of systematic tests.
+ */
+
+ static object rngMutex = new object();
+ static IBlockCipher rngAES = null;
+ static byte[] counter, rblock;
+
+ static void Init()
+ {
+ if (rngAES == null) {
+ NC.RNGCryptoServiceProvider srng =
+ new NC.RNGCryptoServiceProvider();
+ byte[] key = new byte[16];
+ byte[] iv = new byte[16];
+ srng.GetBytes(key);
+ srng.GetBytes(iv);
+ Init(key, iv);
+ }
+ }
+
+ static void Init(byte[] key, byte[] iv)
+ {
+ if (rngAES == null) {
+ rngAES = new AES();
+ counter = new byte[16];
+ rblock = new byte[16];
+ }
+ rngAES.SetKey(key);
+ Array.Copy(iv, 0, rblock, 0, 16);
+ }
+
+ static void NextBlock()
+ {
+ int len = counter.Length;
+ int carry = 1;
+ for (int i = 0; i < len; i ++) {
+ int v = counter[i] + carry;
+ counter[i] = (byte)v;
+ carry = v >> 8;
+ }
+ Array.Copy(counter, 0, rblock, 0, len);
+ rngAES.BlockEncrypt(rblock);
+ }
+
+ /*
+ * Set or reset the state to the provided seed. All subsequent
+ * output will depend only on that seed value. This function shall
+ * be used ONLY for debug/test purposes, since it replaces the
+ * automatic seeding that uses OS-provided entropy.
+ */
+ public static void SetSeed(byte[] seed)
+ {
+ byte[] s32 = new SHA256().Hash(seed);
+ byte[] key = new byte[16];
+ byte[] iv = new byte[16];
+ Array.Copy(s32, 0, key, 0, 16);
+ Array.Copy(s32, 16, iv, 0, 16);
+ lock (rngMutex) {
+ Init(key, iv);
+ }
+ }
+
+ /*
+ * Fill the provided array with random bytes.
+ */
+ public static void GetBytes(byte[] buf)
+ {
+ GetBytes(buf, 0, buf.Length);
+ }
+
+ /*
+ * Fill the provided array chunk with random bytes.
+ */
+ public static void GetBytes(byte[] buf, int off, int len)
+ {
+ lock (rngMutex) {
+ Init();
+ while (len > 0) {
+ NextBlock();
+ int clen = Math.Min(len, rblock.Length);
+ Array.Copy(rblock, 0, buf, off, clen);
+ off += clen;
+ len -= clen;
+ }
+ }
+ }
+
+ /*
+ * Get a new random 32-bit integer (uniform generation).
+ */
+ public static uint U32()
+ {
+ lock (rngMutex) {
+ Init();
+ NextBlock();
+ return (uint)rblock[0]
+ | ((uint)rblock[1] << 8)
+ | ((uint)rblock[2] << 16)
+ | ((uint)rblock[3] << 24);
+ }
+ }
+
+ /*
+ * Convert integer value x (0 to 15) to an hexadecimal character
+ * (lowercase).
+ */
+ static char ToHex(int x)
+ {
+ int hi = -(((x + 6) >> 4) & 1);
+ return (char)(x + 48 + (hi & 39));
+ }
+
+ /*
+ * Get a string of random hexadecimal characters. The 'len'
+ * parameter specifies the string length in characters (it
+ * may be odd).
+ */
+ public static string GetHex(int len)
+ {
+ byte[] buf = new byte[(len + 1) >> 1];
+ GetBytes(buf);
+ StringBuilder sb = new StringBuilder();
+ foreach (byte b in buf) {
+ sb.Append(b >> 4);
+ sb.Append(b & 15);
+ }
+ string s = sb.ToString();
+ if (s.Length > len) {
+ s = s.Substring(0, len);
+ }
+ return s;
+ }
+
+ /*
+ * Get a sequence of random non-zero bytes.
+ */
+ public static void GetBytesNonZero(byte[] buf)
+ {
+ GetBytesNonZero(buf, 0, buf.Length);
+ }
+
+ /*
+ * Get a sequence of random non-zero bytes.
+ */
+ public static void GetBytesNonZero(byte[] buf, int off, int len)
+ {
+ if (len <= 0) {
+ return;
+ }
+ lock (rngMutex) {
+ Init();
+ for (;;) {
+ NextBlock();
+ for (int i = 0; i < rblock.Length; i ++) {
+ byte x = rblock[i];
+ if (x == 0) {
+ continue;
+ }
+ buf[off ++] = x;
+ if (-- len == 0) {
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class implements the RSA encryption and signature algorithms.
+ * The static methods provide access to the algorithm primitives. For
+ * signatures, the hash of the signed data must be provided externally.
+ */
+
+public static class RSA {
+
+ /*
+ * Get the maximum length (in bytes) of an RSA-encrypted value
+ * with the specified public key.
+ */
+ public static int GetMaxEncryptedLength(RSAPublicKey pk)
+ {
+ return pk.Modulus.Length;
+ }
+
+ /*
+ * Encrypt a message with a public key. This applied PKCS#1 v1.5
+ * "type 2" padding. If the message length exceeds the maximum
+ * that can be processed with that public key, an exception is
+ * thrown.
+ *
+ * There are four methods, depending on the kind of source
+ * operand, and how the destination is to be obtained.
+ */
+
+ public static byte[] Encrypt(RSAPublicKey pk, byte[] buf)
+ {
+ return Encrypt(pk, buf, 0, buf.Length);
+ }
+
+ public static byte[] Encrypt(RSAPublicKey pk,
+ byte[] buf, int off, int len)
+ {
+ byte[] n = pk.Modulus;
+ int modLen = n.Length;
+ byte[] x = DoPKCS1Padding(modLen, true, null, buf, off, len);
+ return BigInt.ModPow(x, pk.Exponent, n);
+ }
+
+ public static int Encrypt(RSAPublicKey pk,
+ byte[] buf, byte[] outBuf, int outOff)
+ {
+ return Encrypt(pk, buf, 0, buf.Length, outBuf, outOff);
+ }
+
+ public static int Encrypt(RSAPublicKey pk,
+ byte[] buf, int off, int len, byte[] outBuf, int outOff)
+ {
+ byte[] r = Encrypt(pk, buf, off, len);
+ Array.Copy(r, 0, outBuf, outOff, r.Length);
+ return r.Length;
+ }
+
+ /*
+ * Perform a RSA decryption. A PKCS#1 v1.5 "type 2" padding is
+ * expected, and removed. An exception is thrown on any error.
+ *
+ * WARNING: potentially vulnerable to Bleichenbacher's attack.
+ * Use with care.
+ *
+ * There are four methods, depending on input and output
+ * operands.
+ */
+ public static byte[] Decrypt(RSAPrivateKey sk, byte[] buf)
+ {
+ return Decrypt(sk, buf, 0, buf.Length);
+ }
+
+ public static byte[] Decrypt(RSAPrivateKey sk,
+ byte[] buf, int off, int len)
+ {
+ byte[] tmp = new byte[sk.N.Length];
+ int outLen = Decrypt(sk, buf, off, len, tmp, 0);
+ byte[] outBuf = new byte[outLen];
+ Array.Copy(tmp, 0, outBuf, 0, outLen);
+ return outBuf;
+ }
+
+ public static int Decrypt(RSAPrivateKey sk,
+ byte[] buf, byte[] outBuf, int outOff)
+ {
+ return Decrypt(sk, buf, 0, buf.Length, outBuf, outOff);
+ }
+
+ public static int Decrypt(RSAPrivateKey sk,
+ byte[] buf, int off, int len, byte[] outBuf, int outOff)
+ {
+ if (len != sk.N.Length) {
+ throw new CryptoException(
+ "Invalid RSA-encrypted message length");
+ }
+
+ /*
+ * Note: since RSAPrivateKey refuses a modulus of less
+ * than 64 bytes, we know that len >= 64 here.
+ */
+ byte[] x = new byte[len];
+ Array.Copy(buf, off, x, 0, len);
+ DoPrivate(sk, x);
+ if (x[0] != 0x00 || x[1] != 0x02) {
+ throw new CryptoException(
+ "Invalid PKCS#1 v1.5 encryption padding");
+ }
+ int i;
+ for (i = 2; i < len && x[i] != 0x00; i ++);
+ if (i < 10 || i >= len) {
+ throw new CryptoException(
+ "Invalid PKCS#1 v1.5 encryption padding");
+ }
+ i ++;
+ int olen = len - i;
+ Array.Copy(x, i, outBuf, outOff, olen);
+ return olen;
+ }
+
+ /*
+ * Perform a RSA private key operation (modular exponentiation
+ * with the private exponent). The source array MUST have the
+ * same length as the modulus, and it is modified "in place".
+ *
+ * This function is constant-time, except if the source x[] does
+ * not have the proper length (it should be identical to the
+ * modulus length). If the source array has the proper length
+ * but the numerical value is not in the proper range, then it
+ * is first reduced modulo N.
+ */
+ public static void DoPrivate(RSAPrivateKey sk, byte[] x)
+ {
+ DoPrivate(sk, x, 0, x.Length);
+ }
+
+ public static void DoPrivate(RSAPrivateKey sk,
+ byte[] x, int off, int len)
+ {
+ /*
+ * Check that the source array has the proper length
+ * (identical to the length of the modulus).
+ */
+ if (len != sk.N.Length) {
+ throw new CryptoException(
+ "Invalid source length for RSA private");
+ }
+
+ /*
+ * Reduce the source value to the proper range.
+ */
+ ModInt mx = new ModInt(sk.N);
+ mx.DecodeReduce(x, off, len);
+
+ /*
+ * Compute m1 = x^dp mod p.
+ */
+ ModInt m1 = new ModInt(sk.P);
+ m1.Set(mx);
+ m1.Pow(sk.DP);
+
+ /*
+ * Compute m2 = x^dq mod q.
+ */
+ ModInt m2 = new ModInt(sk.Q);
+ m2.Set(mx);
+ m2.Pow(sk.DQ);
+
+ /*
+ * Compute h = (m1 - m2) / q mod p.
+ * (Result goes in m1.)
+ */
+ ModInt m3 = m1.Dup();
+ m3.Set(m2);
+ m1.Sub(m3);
+ m3.Decode(sk.IQ);
+ m1.ToMonty();
+ m1.MontyMul(m3);
+
+ /*
+ * Compute m_2 + q*h. This works on plain integers, but
+ * we have efficient and constant-time code for modular
+ * integers, so we will do it modulo n.
+ */
+ m3 = mx;
+ m3.Set(m1);
+ m1 = m3.Dup();
+ m1.Decode(sk.Q);
+ m1.ToMonty();
+ m3.MontyMul(m1);
+ m1.Set(m2);
+ m3.Add(m1);
+
+ /*
+ * Write result back in x[].
+ */
+ m3.Encode(x, off, len);
+ }
+
+ /*
+ * Constant headers for PKCS#1 v1.5 "type 1" padding. There are
+ * two versions for each header, because of the PKCS#1 ambiguity
+ * with regards to hash function parameters (ASN.1 NULL value,
+ * or omitted).
+ */
+
+ // PKCS#1 with no explicit digest function (special for SSL/TLS)
+ public static byte[] PKCS1_ND = new byte[] { };
+
+ // PKCS#1 with MD5
+ public static byte[] PKCS1_MD5 = new byte[] {
+ 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00,
+ 0x04, 0x10
+ };
+
+ // PKCS#1 with MD5 (alt)
+ public static byte[] PKCS1_MD5_ALT = new byte[] {
+ 0x30, 0x1E, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86,
+ 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x04, 0x10
+ };
+
+ // PKCS#1 with SHA-1
+ public static byte[] PKCS1_SHA1 = new byte[] {
+ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E,
+ 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14
+ };
+
+ // PKCS#1 with SHA-1 (alt)
+ public static byte[] PKCS1_SHA1_ALT = new byte[] {
+ 0x30, 0x1F, 0x30, 0x07, 0x06, 0x05, 0x2B, 0x0E,
+ 0x03, 0x02, 0x1A, 0x04, 0x14
+ };
+
+ // PKCS#1 with SHA-224
+ public static byte[] PKCS1_SHA224 = new byte[] {
+ 0x30, 0x2D, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
+ 0x00, 0x04, 0x1C
+ };
+
+ // PKCS#1 with SHA-224 (alt)
+ public static byte[] PKCS1_SHA224_ALT = new byte[] {
+ 0x30, 0x2B, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04,
+ 0x1C
+ };
+
+ // PKCS#1 with SHA-256
+ public static byte[] PKCS1_SHA256 = new byte[] {
+ 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20
+ };
+
+ // PKCS#1 with SHA-256 (alt)
+ public static byte[] PKCS1_SHA256_ALT = new byte[] {
+ 0x30, 0x2F, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04,
+ 0x20
+ };
+
+ // PKCS#1 with SHA-384
+ public static byte[] PKCS1_SHA384 = new byte[] {
+ 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
+ 0x00, 0x04, 0x30
+ };
+
+ // PKCS#1 with SHA-384 (alt)
+ public static byte[] PKCS1_SHA384_ALT = new byte[] {
+ 0x30, 0x3F, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04,
+ 0x30
+ };
+
+ // PKCS#1 with SHA-512
+ public static byte[] PKCS1_SHA512 = new byte[] {
+ 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+ 0x00, 0x04, 0x40
+ };
+
+ // PKCS#1 with SHA-512 (alt)
+ public static byte[] PKCS1_SHA512_ALT = new byte[] {
+ 0x30, 0x4F, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04,
+ 0x40
+ };
+
+ /*
+ * Verify an "ND" signature (no digest header: this is for
+ * signatures in SSL/TLS up to TLS-1.1). The hash value
+ * (normally the 36-byte concatenation of MD5 and SHA-1 in
+ * the case of SSL/TLS) and the signature are provided.
+ * Returned value is true on success, false otherwise. No
+ * exception is thrown even if the public key is invalid.
+ */
+
+ public static bool VerifyND(RSAPublicKey pk,
+ byte[] hash, byte[] sig)
+ {
+ return Verify(pk, PKCS1_ND, null,
+ hash, 0, hash.Length,
+ sig, 0, sig.Length);
+ }
+
+ public static bool VerifyND(RSAPublicKey pk,
+ byte[] hash, int hashOff, int hashLen, byte[] sig)
+ {
+ return Verify(pk, PKCS1_ND, null,
+ hash, hashOff, hashLen,
+ sig, 0, sig.Length);
+ }
+
+ public static bool VerifyND(RSAPublicKey pk,
+ byte[] hash, byte[] sig, int sigOff, int sigLen)
+ {
+ return Verify(pk, PKCS1_ND, null,
+ hash, 0, hash.Length,
+ sig, sigOff, sigLen);
+ }
+
+ public static bool VerifyND(RSAPublicKey pk,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] sig, int sigOff, int sigLen)
+ {
+ return Verify(pk, PKCS1_ND, null,
+ hash, hashOff, hashLen,
+ sig, sigOff, sigLen);
+ }
+
+ /*
+ * Verify a RSA signature, with PKCS#1 v1.5 "type 1" padding.
+ * The digest header, digest alternative header, hashed data,
+ * and signature value are provided. On any error (including
+ * an invalid RSA public key), false is returned.
+ *
+ * If 'headerAlt' is null, then the signature MUST use the
+ * header value provided in 'header'. Otherwise, the signature
+ * MUST use either 'header' or 'headerAlt'.
+ */
+
+ public static bool Verify(RSAPublicKey pk,
+ byte[] header, byte[] headerAlt,
+ byte[] hash, byte[] sig)
+ {
+ return Verify(pk, header, headerAlt,
+ hash, 0, hash.Length,
+ sig, 0, sig.Length);
+ }
+
+ public static bool Verify(RSAPublicKey pk,
+ byte[] header, byte[] headerAlt,
+ byte[] hash, int hashOff, int hashLen, byte[] sig)
+ {
+ return Verify(pk, header, headerAlt,
+ hash, hashOff, hashLen,
+ sig, 0, sig.Length);
+ }
+
+ public static bool Verify(RSAPublicKey pk,
+ byte[] header, byte[] headerAlt,
+ byte[] hash, byte[] sig, int sigOff, int sigLen)
+ {
+ return Verify(pk, header, headerAlt,
+ hash, 0, hash.Length,
+ sig, sigOff, sigLen);
+ }
+
+ public static bool Verify(RSAPublicKey pk,
+ byte[] header, byte[] headerAlt,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] sig, int sigOff, int sigLen)
+ {
+ /*
+ * Signature must be an integer less than the modulus,
+ * but encoded over exactly the same size as the modulus.
+ */
+ byte[] n = pk.Modulus;
+ int modLen = n.Length;
+ if (sigLen != modLen) {
+ return false;
+ }
+ byte[] x = new byte[modLen];
+ Array.Copy(sig, sigOff, x, 0, modLen);
+ if (BigInt.Compare(x, n) >= 0) {
+ return false;
+ }
+
+ /*
+ * Do the RSA exponentation, then verify and remove the
+ * "Type 1" padding (00 01 FF...FF 00 with at least
+ * eight bytes of value FF).
+ */
+ x = BigInt.ModPow(x, pk.Exponent, n);
+ if (x.Length < 11 || x[0] != 0x00 || x[1] != 0x01) {
+ return false;
+ }
+ int k = 2;
+ while (k < x.Length && x[k] == 0xFF) {
+ k ++;
+ }
+ if (k < 10 || k == x.Length || x[k] != 0x00) {
+ return false;
+ }
+ k ++;
+
+ /*
+ * Check that the remaining byte end with the provided
+ * hash value.
+ */
+ int len = modLen - k;
+ if (len < hashLen) {
+ return false;
+ }
+ for (int i = 0; i < hashLen; i ++) {
+ if (x[modLen - hashLen + i] != hash[hashOff + i]) {
+ return false;
+ }
+ }
+ len -= hashLen;
+
+ /*
+ * Header is at offset 'k', and length 'len'. Compare
+ * with the provided header(s).
+ */
+ if (Eq(header, 0, header.Length, x, k, len)) {
+ return true;
+ }
+ if (headerAlt != null) {
+ if (Eq(headerAlt, 0, headerAlt.Length, x, k, len)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Compute a RSA signature (PKCS#1 v1.5 "type 1" padding).
+ * The digest header and the hashed data are provided. The
+ * header should be one of the standard PKCS#1 header; it
+ * may also be an empty array or null for a "ND" signature
+ * (this is normally used only in SSL/TLS up to TLS-1.1).
+ */
+
+ public static byte[] Sign(RSAPrivateKey sk, byte[] header,
+ byte[] hash)
+ {
+ return Sign(sk, header, hash, 0, hash.Length);
+ }
+
+ public static byte[] Sign(RSAPrivateKey sk, byte[] header,
+ byte[] hash, int hashOff, int hashLen)
+ {
+ byte[] sig = new byte[sk.N.Length];
+ Sign(sk, header, hash, hashOff, hashLen, sig, 0);
+ return sig;
+ }
+
+ public static int Sign(RSAPrivateKey sk, byte[] header,
+ byte[] hash, byte[] outBuf, int outOff)
+ {
+ return Sign(sk, header, hash, 0, hash.Length, outBuf, outOff);
+ }
+
+ public static int Sign(RSAPrivateKey sk, byte[] header,
+ byte[] hash, int hashOff, int hashLen,
+ byte[] outBuf, int outOff)
+ {
+ int modLen = sk.N.Length;
+ byte[] x = DoPKCS1Padding(modLen, false,
+ header, hash, hashOff, hashLen);
+ DoPrivate(sk, x);
+ Array.Copy(x, 0, outBuf, outOff, x.Length);
+ return x.Length;
+ }
+
+ /*
+ * Apply PKCS#1 v1.5 padding. The data to pad is the concatenation
+ * of head[] and the chunk of val[] beginning at valOff and of
+ * length valLen. If 'head' is null, then an empty array is used.
+ *
+ * The returned array has length modLen (the modulus size, in bytes).
+ * Padding type 2 (random bytes, for encryption) is used if type2
+ * is true; otherwise, padding type 1 is applied (bytes 0xFF, for
+ * signatures).
+ */
+ public static byte[] DoPKCS1Padding(int modLen, bool type2,
+ byte[] head, byte[] val, int valOff, int valLen)
+ {
+ if (head == null) {
+ head = PKCS1_ND;
+ }
+ int len = head.Length + valLen;
+ int padLen = modLen - len - 3;
+ if (padLen < 8) {
+ throw new Exception(
+ "modulus too short for PKCS#1 padding");
+ }
+ byte[] x = new byte[modLen];
+ x[0] = 0x00;
+ x[1] = (byte)(type2 ? 0x02 : 0x01);
+ if (type2) {
+ RNG.GetBytesNonZero(x, 2, padLen);
+ } else {
+ for (int i = 0; i < padLen; i ++) {
+ x[i + 2] = 0xFF;
+ }
+ }
+ x[padLen + 2] = 0x00;
+ Array.Copy(head, 0, x, padLen + 3, head.Length);
+ Array.Copy(val, valOff, x, modLen - valLen, valLen);
+ return x;
+ }
+
+ /*
+ * Compare two byte chunks for equality.
+ */
+ static bool Eq(byte[] a, int aoff, int alen,
+ byte[] b, int boff, int blen)
+ {
+ if (alen != blen) {
+ return false;
+ }
+ for (int i = 0; i < alen; i ++) {
+ if (a[aoff + i] != b[boff + i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains a RSA private key. The private key elements can
+ * be exported as arrays of bytes (minimal unsigned big-endian
+ * representation).
+ *
+ * Private key elements are:
+ * n modulus
+ * e public exponent
+ * d private exponent
+ * p first modulus factor
+ * q second modulus factor
+ * dp d mod (p-1)
+ * dq d mod (q-1)
+ * iq (1/q) mod p
+ */
+
+public class RSAPrivateKey : IPrivateKey {
+
+ public byte[] N {
+ get {
+ return n;
+ }
+ }
+
+ public byte[] E {
+ get {
+ return e;
+ }
+ }
+
+ public byte[] D {
+ get {
+ return d;
+ }
+ }
+
+ public byte[] P {
+ get {
+ return p;
+ }
+ }
+
+ public byte[] Q {
+ get {
+ return q;
+ }
+ }
+
+ public byte[] DP {
+ get {
+ return dp;
+ }
+ }
+
+ public byte[] DQ {
+ get {
+ return dq;
+ }
+ }
+
+ public byte[] IQ {
+ get {
+ return iq;
+ }
+ }
+
+ public int KeySizeBits {
+ get {
+ return ((n.Length - 1) << 3)
+ + BigInt.BitLength(n[0]);
+ }
+ }
+
+ public string AlgorithmName {
+ get {
+ return "RSA";
+ }
+ }
+
+ IPublicKey IPrivateKey.PublicKey {
+ get {
+ return this.PublicKey;
+ }
+ }
+
+ public RSAPublicKey PublicKey {
+ get {
+ return new RSAPublicKey(n, e);
+ }
+ }
+
+ byte[] n, e, d, p, q, dp, dq, iq;
+
+ /*
+ * Create a new instance with the provided elements. Values are
+ * in unsigned big-endian representation.
+ *
+ * n modulus
+ * e public exponent
+ * d private exponent
+ * p first modulus factor
+ * q second modulus factor
+ * dp d mod (p-1)
+ * dq d mod (q-1)
+ * iq (1/q) mod p
+ *
+ * Rules verified by this constructor:
+ * n must be odd and at least 512 bits
+ * e must be odd
+ * p must be odd
+ * q must be odd
+ * p and q are greater than 1
+ * n is equal to p*q
+ * dp must be non-zero and lower than p-1
+ * dq must be non-zero and lower than q-1
+ * iq must be non-zero and lower than p
+ *
+ * This constructor does NOT verify that:
+ * p and q are prime
+ * d is equal to dp modulo p-1
+ * d is equal to dq modulo q-1
+ * dp is the inverse of e modulo p-1
+ * dq is the inverse of e modulo q-1
+ * iq is the inverse of q modulo p
+ */
+ public RSAPrivateKey(byte[] n, byte[] e, byte[] d,
+ byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] iq)
+ {
+ n = BigInt.NormalizeBE(n);
+ e = BigInt.NormalizeBE(e);
+ d = BigInt.NormalizeBE(d);
+ p = BigInt.NormalizeBE(p);
+ q = BigInt.NormalizeBE(q);
+ dp = BigInt.NormalizeBE(dp);
+ dq = BigInt.NormalizeBE(dq);
+ iq = BigInt.NormalizeBE(iq);
+
+ if (n.Length < 64 || (n.Length == 64 && n[0] < 0x80)) {
+ throw new CryptoException(
+ "Invalid RSA private key (less than 512 bits)");
+ }
+ if (!BigInt.IsOdd(n)) {
+ throw new CryptoException(
+ "Invalid RSA private key (even modulus)");
+ }
+ if (!BigInt.IsOdd(e)) {
+ throw new CryptoException(
+ "Invalid RSA private key (even exponent)");
+ }
+ if (!BigInt.IsOdd(p) || !BigInt.IsOdd(q)) {
+ throw new CryptoException(
+ "Invalid RSA private key (even factor)");
+ }
+ if (BigInt.IsOne(p) || BigInt.IsOne(q)) {
+ throw new CryptoException(
+ "Invalid RSA private key (trivial factor)");
+ }
+ if (BigInt.Compare(n, BigInt.Mul(p, q)) != 0) {
+ throw new CryptoException(
+ "Invalid RSA private key (bad factors)");
+ }
+ if (dp.Length == 0 || dq.Length == 0) {
+ throw new CryptoException(
+ "Invalid RSA private key"
+ + " (null reduced private exponent)");
+ }
+
+ /*
+ * We can temporarily modify p[] and q[] (to compute
+ * p-1 and q-1) since these are freshly produced copies.
+ */
+ p[p.Length - 1] --;
+ q[q.Length - 1] --;
+ if (BigInt.Compare(dp, p) >= 0 || BigInt.Compare(dq, q) >= 0) {
+ throw new CryptoException(
+ "Invalid RSA private key"
+ + " (oversized reduced private exponent)");
+ }
+ p[p.Length - 1] ++;
+ q[q.Length - 1] ++;
+ if (iq.Length == 0 || BigInt.Compare(iq, p) >= 0) {
+ throw new CryptoException(
+ "Invalid RSA private key"
+ + " (out of range CRT coefficient)");
+ }
+ this.n = n;
+ this.e = e;
+ this.d = d;
+ this.p = p;
+ this.q = q;
+ this.dp = dp;
+ this.dq = dq;
+ this.iq = iq;
+ }
+
+ /*
+ * Create a new instance with the provided elements: the two
+ * factors, and the public exponent. The other elements are
+ * computed. Values are in unsigned big-endian representation.
+ * Rules verified by this constructor:
+ * p must be odd
+ * q must be odd
+ * e must be relatively prime to both p-1 and q-1
+ * e must be greater than 1
+ * p*q must have size at least 512 bits
+ * TODO: not implemented yet.
+ */
+ public RSAPrivateKey(byte[] p, byte[] q, byte[] e)
+ {
+ throw new Exception("NYI");
+ }
+
+ /*
+ * Create a new instance with the provided elements: the two
+ * factors, and the public exponent. The other elements are
+ * computed. The factors are in unsigned big-endian
+ * representation; the public exponent is a small integer.
+ * Rules verified by this constructor:
+ * p must be odd
+ * q must be odd
+ * e must be relatively prime to both p-1 and q-1
+ * p*q must have size at least 512 bits
+ * TODO: not implemented yet.
+ */
+ public RSAPrivateKey(byte[] p, byte[] q, uint e)
+ : this(p, q, ToBytes(e))
+ {
+ }
+
+ static byte[] ToBytes(uint x)
+ {
+ byte[] r = new byte[4];
+ r[0] = (byte)(x >> 24);
+ r[1] = (byte)(x >> 16);
+ r[2] = (byte)(x >> 8);
+ r[3] = (byte)x;
+ return r;
+ }
+
+ /*
+ * CheckValid() will verify that the prime factors are indeed
+ * prime, and that all other values are correct.
+ */
+ public void CheckValid()
+ {
+ /*
+ * Factors ought to be prime.
+ */
+ if (!BigInt.IsPrime(p) || !BigInt.IsPrime(q)) {
+ throw new CryptoException("Invalid RSA private key"
+ + " (non-prime factor)");
+ }
+
+ /*
+ * FIXME: Verify that:
+ * dp = d mod p-1
+ * e*dp = 1 mod p-1
+ * dq = d mod q-1
+ * e*dq = 1 mod q-1
+ * (This is not easy with existing code because p-1 and q-1
+ * are even, but ModInt tolerates only odd moduli.)
+ *
+ CheckExp(p, d, dp, e);
+ CheckExp(q, d, dq, e);
+ */
+
+ /*
+ * Verify that:
+ * q*iq = 1 mod p
+ */
+ ModInt x = new ModInt(p);
+ ModInt y = x.Dup();
+ x.DecodeReduce(q);
+ x.ToMonty();
+ y.Decode(iq);
+ x.MontyMul(y);
+ if (!x.IsOne) {
+ throw new CryptoException("Invalid RSA private key"
+ + " (wrong CRT coefficient)");
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains a RSA public key, defined as a modulus and an
+ * exponent. Both use big-endian representation. This class normalizes
+ * the parameters provided to the constructor so that modulus and
+ * exponent use their minimal unsigned big-endian representation.
+ *
+ * Modulus must have length at least 512 bits. Modulus and exponent
+ * must be odd integers.
+ */
+
+public class RSAPublicKey : IPublicKey {
+
+ public byte[] Modulus {
+ get {
+ return mod;
+ }
+ }
+
+ public byte[] Exponent {
+ get {
+ return e;
+ }
+ }
+
+ public int KeySizeBits {
+ get {
+ return ((mod.Length - 1) << 3)
+ + BigInt.BitLength(mod[0]);
+ }
+ }
+
+ public string AlgorithmName {
+ get {
+ return "RSA";
+ }
+ }
+
+ byte[] mod;
+ byte[] e;
+ int hashCode;
+
+ /*
+ * Create a new instance with the provided element (unsigned,
+ * big-endian). This constructor checks the following rules:
+ *
+ * the modulus size must be at least 512 bits
+ * the modulus must be odd
+ * the exponent must be odd and greater than 1
+ */
+ public RSAPublicKey(byte[] modulus, byte[] exponent)
+ {
+ mod = BigInt.NormalizeBE(modulus, false);
+ e = BigInt.NormalizeBE(exponent, false);
+ if (mod.Length < 64 || (mod.Length == 64 && mod[0] < 0x80)) {
+ throw new CryptoException(
+ "Invalid RSA public key (less than 512 bits)");
+ }
+ if ((mod[mod.Length - 1] & 0x01) == 0) {
+ throw new CryptoException(
+ "Invalid RSA public key (even modulus)");
+ }
+ if (BigInt.IsZero(e)) {
+ throw new CryptoException(
+ "Invalid RSA public key (exponent is zero)");
+ }
+ if (BigInt.IsOne(e)) {
+ throw new CryptoException(
+ "Invalid RSA public key (exponent is one)");
+ }
+ if ((e[e.Length - 1] & 0x01) == 0) {
+ throw new CryptoException(
+ "Invalid RSA public key (even exponent)");
+ }
+
+ /*
+ * A simple hash code that will work well because RSA
+ * keys are in practice quite randomish.
+ */
+ hashCode = (int)(BigInt.HashInt(modulus)
+ ^ BigInt.HashInt(exponent));
+ }
+
+ /*
+ * For a RSA public key, we cannot, in all generality, check
+ * any more things than we already did in the constructor.
+ * Notably, we cannot check whether the public exponent (e)
+ * is indeed relatively prime to phi(n) (the order of the
+ * invertible group modulo n).
+ */
+ public void CheckValid()
+ {
+ /*
+ * We cannot check more than what we already checked in
+ * the constructor.
+ */
+ }
+
+ public override bool Equals(object obj)
+ {
+ RSAPublicKey p = obj as RSAPublicKey;
+ if (p == null) {
+ return false;
+ }
+ return BigInt.Compare(mod, p.mod) == 0
+ && BigInt.Compare(e, p.e) == 0;
+ }
+
+ public override int GetHashCode()
+ {
+ return hashCode;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-1 implementation. SHA-1 is described in FIPS 180-4. Note that
+ * SHA-1 collisions can be computed more efficiently than what would be
+ * expected from an ideal hash function with the same output size; and
+ * this was actually done at least once. Use with care.
+ */
+
+public sealed class SHA1 : DigestCore {
+
+ const int BLOCK_LEN = 64;
+
+ uint A, B, C, D, E;
+ byte[] block, saveBlock;
+ int ptr;
+ ulong byteCount;
+
+ /*
+ * Create a new instance. It is ready to process data bytes.
+ */
+ public SHA1()
+ {
+ block = new byte[BLOCK_LEN];
+ saveBlock = new byte[BLOCK_LEN];
+ Reset();
+ }
+
+ /* see IDigest */
+ public override string Name {
+ get {
+ return "SHA-1";
+ }
+ }
+
+ /* see IDigest */
+ public override int DigestSize {
+ get {
+ return 20;
+ }
+ }
+
+ /* see IDigest */
+ public override int BlockSize {
+ get {
+ return 64;
+ }
+ }
+
+ /* see IDigest */
+ public override void Reset()
+ {
+ A = 0x67452301;
+ B = 0xEFCDAB89;
+ C = 0x98BADCFE;
+ D = 0x10325476;
+ E = 0xC3D2E1F0;
+ byteCount = 0;
+ ptr = 0;
+ }
+
+ /* see IDigest */
+ public override void Update(byte b)
+ {
+ block[ptr ++] = b;
+ byteCount ++;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+
+ /* see IDigest */
+ public override void Update(byte[] buf, int off, int len)
+ {
+ if (len < 0) {
+ throw new ArgumentException("negative chunk length");
+ }
+ byteCount += (ulong)len;
+ while (len > 0) {
+ int clen = Math.Min(len, BLOCK_LEN - ptr);
+ Array.Copy(buf, off, block, ptr, clen);
+ off += clen;
+ len -= clen;
+ ptr += clen;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+ }
+
+ /* see IDigest */
+ public override void DoPartial(byte[] outBuf, int off)
+ {
+ /*
+ * Save current state.
+ */
+ uint saveA = A;
+ uint saveB = B;
+ uint saveC = C;
+ uint saveD = D;
+ uint saveE = E;
+ int savePtr = ptr;
+ Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+ /*
+ * Add padding. This may involve processing an extra block.
+ */
+ block[ptr ++] = 0x80;
+ if (ptr > BLOCK_LEN - 8) {
+ for (int j = ptr; j < BLOCK_LEN; j ++) {
+ block[j] = 0;
+ }
+ ProcessBlock();
+ }
+ for (int j = ptr; j < (BLOCK_LEN - 8); j ++) {
+ block[j] = 0;
+ }
+ ulong x = byteCount << 3;
+ Enc32be((uint)(x >> 32), block, BLOCK_LEN - 8);
+ Enc32be((uint)x, block, BLOCK_LEN - 4);
+
+ /*
+ * Process final block and encode result.
+ */
+ ProcessBlock();
+ Enc32be(A, outBuf, off);
+ Enc32be(B, outBuf, off + 4);
+ Enc32be(C, outBuf, off + 8);
+ Enc32be(D, outBuf, off + 12);
+ Enc32be(E, outBuf, off + 16);
+
+ /*
+ * Restore current state.
+ */
+ Array.Copy(saveBlock, 0, block, 0, savePtr);
+ A = saveA;
+ B = saveB;
+ C = saveC;
+ D = saveD;
+ E = saveE;
+ ptr = savePtr;
+ }
+
+ /* see IDigest */
+ public override IDigest Dup()
+ {
+ SHA1 h = new SHA1();
+ h.A = A;
+ h.B = B;
+ h.C = C;
+ h.D = D;
+ h.E = E;
+ h.ptr = ptr;
+ h.byteCount = byteCount;
+ Array.Copy(block, 0, h.block, 0, ptr);
+ return h;
+ }
+
+ /* see IDigest */
+ public override void CurrentState(byte[] outBuf, int off)
+ {
+ Enc32be(A, outBuf, off);
+ Enc32be(B, outBuf, off + 4);
+ Enc32be(C, outBuf, off + 8);
+ Enc32be(D, outBuf, off + 12);
+ Enc32be(E, outBuf, off + 16);
+ }
+
+ const uint K1 = 0x5A827999;
+ const uint K2 = 0x6ED9EBA1;
+ const uint K3 = 0x8F1BBCDC;
+ const uint K4 = 0xCA62C1D6;
+
+ void ProcessBlock()
+ {
+ /*
+ * Read state words.
+ */
+ uint wa = A;
+ uint wb = B;
+ uint wc = C;
+ uint wd = D;
+ uint we = E;
+
+ /*
+ * Rounds 0 to 19.
+ */
+ uint x0 = Dec32be(block, 0);
+ we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + x0 + K1;
+ wb = (wb << 30) | (wb >> 2);
+ uint x1 = Dec32be(block, 4);
+ wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + x1 + K1;
+ wa = (wa << 30) | (wa >> 2);
+ uint x2 = Dec32be(block, 8);
+ wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + x2 + K1;
+ we = (we << 30) | (we >> 2);
+ uint x3 = Dec32be(block, 12);
+ wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + x3 + K1;
+ wd = (wd << 30) | (wd >> 2);
+ uint x4 = Dec32be(block, 16);
+ wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + x4 + K1;
+ wc = (wc << 30) | (wc >> 2);
+ uint x5 = Dec32be(block, 20);
+ we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + x5 + K1;
+ wb = (wb << 30) | (wb >> 2);
+ uint x6 = Dec32be(block, 24);
+ wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + x6 + K1;
+ wa = (wa << 30) | (wa >> 2);
+ uint x7 = Dec32be(block, 28);
+ wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + x7 + K1;
+ we = (we << 30) | (we >> 2);
+ uint x8 = Dec32be(block, 32);
+ wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + x8 + K1;
+ wd = (wd << 30) | (wd >> 2);
+ uint x9 = Dec32be(block, 36);
+ wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + x9 + K1;
+ wc = (wc << 30) | (wc >> 2);
+ uint xA = Dec32be(block, 40);
+ we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + xA + K1;
+ wb = (wb << 30) | (wb >> 2);
+ uint xB = Dec32be(block, 44);
+ wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + xB + K1;
+ wa = (wa << 30) | (wa >> 2);
+ uint xC = Dec32be(block, 48);
+ wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + xC + K1;
+ we = (we << 30) | (we >> 2);
+ uint xD = Dec32be(block, 52);
+ wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + xD + K1;
+ wd = (wd << 30) | (wd >> 2);
+ uint xE = Dec32be(block, 56);
+ wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + xE + K1;
+ wc = (wc << 30) | (wc >> 2);
+ uint xF = Dec32be(block, 60);
+ we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + xF + K1;
+ wb = (wb << 30) | (wb >> 2);
+ x0 ^= xD ^ x8 ^ x2;
+ x0 = (x0 << 1) | (x0 >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + x0 + K1;
+ wa = (wa << 30) | (wa >> 2);
+ x1 ^= xE ^ x9 ^ x3;
+ x1 = (x1 << 1) | (x1 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + x1 + K1;
+ we = (we << 30) | (we >> 2);
+ x2 ^= xF ^ xA ^ x4;
+ x2 = (x2 << 1) | (x2 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + x2 + K1;
+ wd = (wd << 30) | (wd >> 2);
+ x3 ^= x0 ^ xB ^ x5;
+ x3 = (x3 << 1) | (x3 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + x3 + K1;
+ wc = (wc << 30) | (wc >> 2);
+
+ /*
+ * Rounds 20 to 39.
+ */
+ x4 ^= x1 ^ xC ^ x6;
+ x4 = (x4 << 1) | (x4 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x4 + K2;
+ wb = (wb << 30) | (wb >> 2);
+ x5 ^= x2 ^ xD ^ x7;
+ x5 = (x5 << 1) | (x5 >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x5 + K2;
+ wa = (wa << 30) | (wa >> 2);
+ x6 ^= x3 ^ xE ^ x8;
+ x6 = (x6 << 1) | (x6 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x6 + K2;
+ we = (we << 30) | (we >> 2);
+ x7 ^= x4 ^ xF ^ x9;
+ x7 = (x7 << 1) | (x7 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x7 + K2;
+ wd = (wd << 30) | (wd >> 2);
+ x8 ^= x5 ^ x0 ^ xA;
+ x8 = (x8 << 1) | (x8 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x8 + K2;
+ wc = (wc << 30) | (wc >> 2);
+ x9 ^= x6 ^ x1 ^ xB;
+ x9 = (x9 << 1) | (x9 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x9 + K2;
+ wb = (wb << 30) | (wb >> 2);
+ xA ^= x7 ^ x2 ^ xC;
+ xA = (xA << 1) | (xA >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xA + K2;
+ wa = (wa << 30) | (wa >> 2);
+ xB ^= x8 ^ x3 ^ xD;
+ xB = (xB << 1) | (xB >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + xB + K2;
+ we = (we << 30) | (we >> 2);
+ xC ^= x9 ^ x4 ^ xE;
+ xC = (xC << 1) | (xC >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + xC + K2;
+ wd = (wd << 30) | (wd >> 2);
+ xD ^= xA ^ x5 ^ xF;
+ xD = (xD << 1) | (xD >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + xD + K2;
+ wc = (wc << 30) | (wc >> 2);
+ xE ^= xB ^ x6 ^ x0;
+ xE = (xE << 1) | (xE >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + xE + K2;
+ wb = (wb << 30) | (wb >> 2);
+ xF ^= xC ^ x7 ^ x1;
+ xF = (xF << 1) | (xF >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xF + K2;
+ wa = (wa << 30) | (wa >> 2);
+ x0 ^= xD ^ x8 ^ x2;
+ x0 = (x0 << 1) | (x0 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x0 + K2;
+ we = (we << 30) | (we >> 2);
+ x1 ^= xE ^ x9 ^ x3;
+ x1 = (x1 << 1) | (x1 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x1 + K2;
+ wd = (wd << 30) | (wd >> 2);
+ x2 ^= xF ^ xA ^ x4;
+ x2 = (x2 << 1) | (x2 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x2 + K2;
+ wc = (wc << 30) | (wc >> 2);
+ x3 ^= x0 ^ xB ^ x5;
+ x3 = (x3 << 1) | (x3 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x3 + K2;
+ wb = (wb << 30) | (wb >> 2);
+ x4 ^= x1 ^ xC ^ x6;
+ x4 = (x4 << 1) | (x4 >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x4 + K2;
+ wa = (wa << 30) | (wa >> 2);
+ x5 ^= x2 ^ xD ^ x7;
+ x5 = (x5 << 1) | (x5 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x5 + K2;
+ we = (we << 30) | (we >> 2);
+ x6 ^= x3 ^ xE ^ x8;
+ x6 = (x6 << 1) | (x6 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x6 + K2;
+ wd = (wd << 30) | (wd >> 2);
+ x7 ^= x4 ^ xF ^ x9;
+ x7 = (x7 << 1) | (x7 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x7 + K2;
+ wc = (wc << 30) | (wc >> 2);
+
+ /*
+ * Rounds 40 to 59.
+ */
+ x8 ^= x5 ^ x0 ^ xA;
+ x8 = (x8 << 1) | (x8 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + x8 + K3;
+ wb = (wb << 30) | (wb >> 2);
+ x9 ^= x6 ^ x1 ^ xB;
+ x9 = (x9 << 1) | (x9 >> 31);
+ wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + x9 + K3;
+ wa = (wa << 30) | (wa >> 2);
+ xA ^= x7 ^ x2 ^ xC;
+ xA = (xA << 1) | (xA >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + xA + K3;
+ we = (we << 30) | (we >> 2);
+ xB ^= x8 ^ x3 ^ xD;
+ xB = (xB << 1) | (xB >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + xB + K3;
+ wd = (wd << 30) | (wd >> 2);
+ xC ^= x9 ^ x4 ^ xE;
+ xC = (xC << 1) | (xC >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + xC + K3;
+ wc = (wc << 30) | (wc >> 2);
+ xD ^= xA ^ x5 ^ xF;
+ xD = (xD << 1) | (xD >> 31);
+ we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + xD + K3;
+ wb = (wb << 30) | (wb >> 2);
+ xE ^= xB ^ x6 ^ x0;
+ xE = (xE << 1) | (xE >> 31);
+ wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + xE + K3;
+ wa = (wa << 30) | (wa >> 2);
+ xF ^= xC ^ x7 ^ x1;
+ xF = (xF << 1) | (xF >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + xF + K3;
+ we = (we << 30) | (we >> 2);
+ x0 ^= xD ^ x8 ^ x2;
+ x0 = (x0 << 1) | (x0 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + x0 + K3;
+ wd = (wd << 30) | (wd >> 2);
+ x1 ^= xE ^ x9 ^ x3;
+ x1 = (x1 << 1) | (x1 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + x1 + K3;
+ wc = (wc << 30) | (wc >> 2);
+ x2 ^= xF ^ xA ^ x4;
+ x2 = (x2 << 1) | (x2 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + x2 + K3;
+ wb = (wb << 30) | (wb >> 2);
+ x3 ^= x0 ^ xB ^ x5;
+ x3 = (x3 << 1) | (x3 >> 31);
+ wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + x3 + K3;
+ wa = (wa << 30) | (wa >> 2);
+ x4 ^= x1 ^ xC ^ x6;
+ x4 = (x4 << 1) | (x4 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + x4 + K3;
+ we = (we << 30) | (we >> 2);
+ x5 ^= x2 ^ xD ^ x7;
+ x5 = (x5 << 1) | (x5 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + x5 + K3;
+ wd = (wd << 30) | (wd >> 2);
+ x6 ^= x3 ^ xE ^ x8;
+ x6 = (x6 << 1) | (x6 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + x6 + K3;
+ wc = (wc << 30) | (wc >> 2);
+ x7 ^= x4 ^ xF ^ x9;
+ x7 = (x7 << 1) | (x7 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + x7 + K3;
+ wb = (wb << 30) | (wb >> 2);
+ x8 ^= x5 ^ x0 ^ xA;
+ x8 = (x8 << 1) | (x8 >> 31);
+ wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + x8 + K3;
+ wa = (wa << 30) | (wa >> 2);
+ x9 ^= x6 ^ x1 ^ xB;
+ x9 = (x9 << 1) | (x9 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + x9 + K3;
+ we = (we << 30) | (we >> 2);
+ xA ^= x7 ^ x2 ^ xC;
+ xA = (xA << 1) | (xA >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + xA + K3;
+ wd = (wd << 30) | (wd >> 2);
+ xB ^= x8 ^ x3 ^ xD;
+ xB = (xB << 1) | (xB >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + xB + K3;
+ wc = (wc << 30) | (wc >> 2);
+
+ /*
+ * Rounds 60 to 79.
+ */
+ xC ^= x9 ^ x4 ^ xE;
+ xC = (xC << 1) | (xC >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + xC + K4;
+ wb = (wb << 30) | (wb >> 2);
+ xD ^= xA ^ x5 ^ xF;
+ xD = (xD << 1) | (xD >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xD + K4;
+ wa = (wa << 30) | (wa >> 2);
+ xE ^= xB ^ x6 ^ x0;
+ xE = (xE << 1) | (xE >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + xE + K4;
+ we = (we << 30) | (we >> 2);
+ xF ^= xC ^ x7 ^ x1;
+ xF = (xF << 1) | (xF >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + xF + K4;
+ wd = (wd << 30) | (wd >> 2);
+ x0 ^= xD ^ x8 ^ x2;
+ x0 = (x0 << 1) | (x0 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x0 + K4;
+ wc = (wc << 30) | (wc >> 2);
+ x1 ^= xE ^ x9 ^ x3;
+ x1 = (x1 << 1) | (x1 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x1 + K4;
+ wb = (wb << 30) | (wb >> 2);
+ x2 ^= xF ^ xA ^ x4;
+ x2 = (x2 << 1) | (x2 >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x2 + K4;
+ wa = (wa << 30) | (wa >> 2);
+ x3 ^= x0 ^ xB ^ x5;
+ x3 = (x3 << 1) | (x3 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x3 + K4;
+ we = (we << 30) | (we >> 2);
+ x4 ^= x1 ^ xC ^ x6;
+ x4 = (x4 << 1) | (x4 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x4 + K4;
+ wd = (wd << 30) | (wd >> 2);
+ x5 ^= x2 ^ xD ^ x7;
+ x5 = (x5 << 1) | (x5 >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x5 + K4;
+ wc = (wc << 30) | (wc >> 2);
+ x6 ^= x3 ^ xE ^ x8;
+ x6 = (x6 << 1) | (x6 >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x6 + K4;
+ wb = (wb << 30) | (wb >> 2);
+ x7 ^= x4 ^ xF ^ x9;
+ x7 = (x7 << 1) | (x7 >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x7 + K4;
+ wa = (wa << 30) | (wa >> 2);
+ x8 ^= x5 ^ x0 ^ xA;
+ x8 = (x8 << 1) | (x8 >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x8 + K4;
+ we = (we << 30) | (we >> 2);
+ x9 ^= x6 ^ x1 ^ xB;
+ x9 = (x9 << 1) | (x9 >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x9 + K4;
+ wd = (wd << 30) | (wd >> 2);
+ xA ^= x7 ^ x2 ^ xC;
+ xA = (xA << 1) | (xA >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + xA + K4;
+ wc = (wc << 30) | (wc >> 2);
+ xB ^= x8 ^ x3 ^ xD;
+ xB = (xB << 1) | (xB >> 31);
+ we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + xB + K4;
+ wb = (wb << 30) | (wb >> 2);
+ xC ^= x9 ^ x4 ^ xE;
+ xC = (xC << 1) | (xC >> 31);
+ wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xC + K4;
+ wa = (wa << 30) | (wa >> 2);
+ xD ^= xA ^ x5 ^ xF;
+ xD = (xD << 1) | (xD >> 31);
+ wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + xD + K4;
+ we = (we << 30) | (we >> 2);
+ xE ^= xB ^ x6 ^ x0;
+ xE = (xE << 1) | (xE >> 31);
+ wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + xE + K4;
+ wd = (wd << 30) | (wd >> 2);
+ xF ^= xC ^ x7 ^ x1;
+ xF = (xF << 1) | (xF >> 31);
+ wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + xF + K4;
+ wc = (wc << 30) | (wc >> 2);
+
+ /*
+ * Update state words and reset block pointer.
+ */
+ A += wa;
+ B += wb;
+ C += wc;
+ D += wd;
+ E += we;
+ ptr = 0;
+ }
+
+ static uint Dec32be(byte[] buf, int off)
+ {
+ return ((uint)buf[off] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ }
+
+ static void Enc32be(uint x, byte[] buf, int off)
+ {
+ buf[off] = (byte)(x >> 24);
+ buf[off + 1] = (byte)(x >> 16);
+ buf[off + 2] = (byte)(x >> 8);
+ buf[off + 3] = (byte)x;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-224 implementation. SHA-224 is described in FIPS 180-4.
+ */
+
+public sealed class SHA224 : SHA2Small {
+
+ /*
+ * Create a new instance, ready to process data bytes.
+ */
+ public SHA224()
+ {
+ }
+
+ /* see IDigest */
+ public override string Name {
+ get {
+ return "SHA-224";
+ }
+ }
+
+ /* see IDigest */
+ public override int DigestSize {
+ get {
+ return 28;
+ }
+ }
+
+ internal override uint[] IV {
+ get {
+ return IV224;
+ }
+ }
+
+ internal override SHA2Small DupInner()
+ {
+ return new SHA224();
+ }
+
+ static uint[] IV224 = {
+ 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939,
+ 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4
+ };
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-256 implementation. SHA-256 is described in FIPS 180-4.
+ */
+
+public sealed class SHA256 : SHA2Small {
+
+ /*
+ * Create a new instance, ready to process data bytes.
+ */
+ public SHA256()
+ {
+ }
+
+ /* see IDigest */
+ public override string Name {
+ get {
+ return "SHA-256";
+ }
+ }
+
+ /* see IDigest */
+ public override int DigestSize {
+ get {
+ return 32;
+ }
+ }
+
+ internal override uint[] IV {
+ get {
+ return IV256;
+ }
+ }
+
+ internal override SHA2Small DupInner()
+ {
+ return new SHA256();
+ }
+
+ static uint[] IV256 = {
+ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
+ 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
+ };
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of SHA-384 and SHA-512, as described in FIPS 180-4.
+ */
+
+public abstract class SHA2Big : DigestCore {
+
+ const int BLOCK_LEN = 128;
+
+ ulong[] state;
+ byte[] block, saveBlock;
+ int ptr;
+ ulong byteCount;
+ ulong[] W;
+
+ /*
+ * Create a new instance, ready to process data bytes. The
+ * output length (in bytes) and initial value must be specified.
+ */
+ internal SHA2Big()
+ {
+ state = new ulong[8];
+ block = new byte[BLOCK_LEN];
+ saveBlock = new byte[BLOCK_LEN];
+ W = new ulong[80];
+ Reset();
+ }
+
+ internal abstract ulong[] IV { get; }
+
+ internal abstract SHA2Big DupInner();
+
+ /* see IDigest */
+ public override int BlockSize {
+ get {
+ return BLOCK_LEN;
+ }
+ }
+
+ /* see IDigest */
+ public override void Reset()
+ {
+ Array.Copy(IV, 0, state, 0, state.Length);
+ byteCount = 0;
+ ptr = 0;
+ }
+
+ /* see IDigest */
+ public override void Update(byte b)
+ {
+ block[ptr ++] = b;
+ byteCount ++;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+
+ /* see IDigest */
+ public override void Update(byte[] buf, int off, int len)
+ {
+ if (len < 0) {
+ throw new ArgumentException("negative chunk length");
+ }
+ byteCount += (ulong)len;
+ while (len > 0) {
+ int clen = Math.Min(len, BLOCK_LEN - ptr);
+ Array.Copy(buf, off, block, ptr, clen);
+ off += clen;
+ len -= clen;
+ ptr += clen;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+ }
+
+ /* see IDigest */
+ public override void DoPartial(byte[] outBuf, int off)
+ {
+ /*
+ * Save current state.
+ */
+ ulong A = state[0];
+ ulong B = state[1];
+ ulong C = state[2];
+ ulong D = state[3];
+ ulong E = state[4];
+ ulong F = state[5];
+ ulong G = state[6];
+ ulong H = state[7];
+ int savePtr = ptr;
+ Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+ /*
+ * Add padding. This may involve processing an extra block.
+ */
+ block[ptr ++] = 0x80;
+ if (ptr > BLOCK_LEN - 16) {
+ for (int j = ptr; j < BLOCK_LEN; j ++) {
+ block[j] = 0;
+ }
+ ProcessBlock();
+ }
+ for (int j = ptr; j < (BLOCK_LEN - 16); j ++) {
+ block[j] = 0;
+ }
+ Enc64be(byteCount >> 61, block, BLOCK_LEN - 16);
+ Enc64be(byteCount << 3, block, BLOCK_LEN - 8);
+
+ /*
+ * Process final block and encode result.
+ */
+ ProcessBlock();
+ int n = DigestSize >> 3;
+ for (int i = 0; i < n; i ++) {
+ Enc64be(state[i], outBuf, off + (i << 3));
+ }
+
+ /*
+ * Restore current state.
+ */
+ Array.Copy(saveBlock, 0, block, 0, savePtr);
+ state[0] = A;
+ state[1] = B;
+ state[2] = C;
+ state[3] = D;
+ state[4] = E;
+ state[5] = F;
+ state[6] = G;
+ state[7] = H;
+ ptr = savePtr;
+ }
+
+ /* see IDigest */
+ public override IDigest Dup()
+ {
+ SHA2Big h = DupInner();
+ Array.Copy(state, 0, h.state, 0, state.Length);
+ h.ptr = ptr;
+ h.byteCount = byteCount;
+ Array.Copy(block, 0, h.block, 0, ptr);
+ return h;
+ }
+
+ /* see IDigest */
+ public override void CurrentState(byte[] outBuf, int off)
+ {
+ int n = DigestSize >> 3;
+ for (int i = 0; i < n; i ++) {
+ Enc64be(state[i], outBuf, off + (i << 3));
+ }
+ }
+
+ static ulong[] K = {
+ 0x428A2F98D728AE22, 0x7137449123EF65CD,
+ 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC,
+ 0x3956C25BF348B538, 0x59F111F1B605D019,
+ 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118,
+ 0xD807AA98A3030242, 0x12835B0145706FBE,
+ 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2,
+ 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1,
+ 0x9BDC06A725C71235, 0xC19BF174CF692694,
+ 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3,
+ 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65,
+ 0x2DE92C6F592B0275, 0x4A7484AA6EA6E483,
+ 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5,
+ 0x983E5152EE66DFAB, 0xA831C66D2DB43210,
+ 0xB00327C898FB213F, 0xBF597FC7BEEF0EE4,
+ 0xC6E00BF33DA88FC2, 0xD5A79147930AA725,
+ 0x06CA6351E003826F, 0x142929670A0E6E70,
+ 0x27B70A8546D22FFC, 0x2E1B21385C26C926,
+ 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF,
+ 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8,
+ 0x81C2C92E47EDAEE6, 0x92722C851482353B,
+ 0xA2BFE8A14CF10364, 0xA81A664BBC423001,
+ 0xC24B8B70D0F89791, 0xC76C51A30654BE30,
+ 0xD192E819D6EF5218, 0xD69906245565A910,
+ 0xF40E35855771202A, 0x106AA07032BBD1B8,
+ 0x19A4C116B8D2D0C8, 0x1E376C085141AB53,
+ 0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8,
+ 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB,
+ 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3,
+ 0x748F82EE5DEFB2FC, 0x78A5636F43172F60,
+ 0x84C87814A1F0AB72, 0x8CC702081A6439EC,
+ 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9,
+ 0xBEF9A3F7B2C67915, 0xC67178F2E372532B,
+ 0xCA273ECEEA26619C, 0xD186B8C721C0C207,
+ 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178,
+ 0x06F067AA72176FBA, 0x0A637DC5A2C898A6,
+ 0x113F9804BEF90DAE, 0x1B710B35131C471B,
+ 0x28DB77F523047D84, 0x32CAAB7B40C72493,
+ 0x3C9EBE0A15C9BEBC, 0x431D67C49C100D4C,
+ 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A,
+ 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817
+ };
+
+ void ProcessBlock()
+ {
+ /*
+ * Read state words.
+ */
+ ulong A = state[0];
+ ulong B = state[1];
+ ulong C = state[2];
+ ulong D = state[3];
+ ulong E = state[4];
+ ulong F = state[5];
+ ulong G = state[6];
+ ulong H = state[7];
+
+ ulong T1, T2;
+ ulong[] W = this.W;
+ byte[] block = this.block;
+
+ for (int i = 0, j = 0; i < 16; i ++, j += 8) {
+ W[i] = Dec64be(block, j);
+ }
+ for (int i = 16; i < 80; i ++) {
+ ulong w2 = W[i - 2];
+ ulong w15 = W[i - 15];
+ W[i] = (((w2 << 45) | (w2 >> 19))
+ ^ ((w2 << 3) | (w2 >> 61))
+ ^ (w2 >> 6))
+ + W[i - 7]
+ + (((w15 << 63) | (w15 >> 1))
+ ^ ((w15 << 56) | (w15 >> 8))
+ ^ (w15 >> 7))
+ + W[i - 16];
+ }
+ for (int i = 0; i < 80; i += 8) {
+ T1 = H + (((E << 50) | (E >> 14))
+ ^ ((E << 46) | (E >> 18))
+ ^ ((E << 23) | (E >> 41)))
+ + (G ^ (E & (F ^ G)))
+ + K[i + 0] + W[i + 0];
+ T2 = (((A << 36) | (A >> 28))
+ ^ ((A << 30) | (A >> 34))
+ ^ ((A << 25) | (A >> 39)))
+ + ((A & B) ^ (C & (A ^ B)));
+ D += T1;
+ H = T1 + T2;
+ T1 = G + (((D << 50) | (D >> 14))
+ ^ ((D << 46) | (D >> 18))
+ ^ ((D << 23) | (D >> 41)))
+ + (F ^ (D & (E ^ F)))
+ + K[i + 1] + W[i + 1];
+ T2 = (((H << 36) | (H >> 28))
+ ^ ((H << 30) | (H >> 34))
+ ^ ((H << 25) | (H >> 39)))
+ + ((H & A) ^ (B & (H ^ A)));
+ C += T1;
+ G = T1 + T2;
+ T1 = F + (((C << 50) | (C >> 14))
+ ^ ((C << 46) | (C >> 18))
+ ^ ((C << 23) | (C >> 41)))
+ + (E ^ (C & (D ^ E)))
+ + K[i + 2] + W[i + 2];
+ T2 = (((G << 36) | (G >> 28))
+ ^ ((G << 30) | (G >> 34))
+ ^ ((G << 25) | (G >> 39)))
+ + ((G & H) ^ (A & (G ^ H)));
+ B += T1;
+ F = T1 + T2;
+ T1 = E + (((B << 50) | (B >> 14))
+ ^ ((B << 46) | (B >> 18))
+ ^ ((B << 23) | (B >> 41)))
+ + (D ^ (B & (C ^ D)))
+ + K[i + 3] + W[i + 3];
+ T2 = (((F << 36) | (F >> 28))
+ ^ ((F << 30) | (F >> 34))
+ ^ ((F << 25) | (F >> 39)))
+ + ((F & G) ^ (H & (F ^ G)));
+ A += T1;
+ E = T1 + T2;
+ T1 = D + (((A << 50) | (A >> 14))
+ ^ ((A << 46) | (A >> 18))
+ ^ ((A << 23) | (A >> 41)))
+ + (C ^ (A & (B ^ C)))
+ + K[i + 4] + W[i + 4];
+ T2 = (((E << 36) | (E >> 28))
+ ^ ((E << 30) | (E >> 34))
+ ^ ((E << 25) | (E >> 39)))
+ + ((E & F) ^ (G & (E ^ F)));
+ H += T1;
+ D = T1 + T2;
+ T1 = C + (((H << 50) | (H >> 14))
+ ^ ((H << 46) | (H >> 18))
+ ^ ((H << 23) | (H >> 41)))
+ + (B ^ (H & (A ^ B)))
+ + K[i + 5] + W[i + 5];
+ T2 = (((D << 36) | (D >> 28))
+ ^ ((D << 30) | (D >> 34))
+ ^ ((D << 25) | (D >> 39)))
+ + ((D & E) ^ (F & (D ^ E)));
+ G += T1;
+ C = T1 + T2;
+ T1 = B + (((G << 50) | (G >> 14))
+ ^ ((G << 46) | (G >> 18))
+ ^ ((G << 23) | (G >> 41)))
+ + (A ^ (G & (H ^ A)))
+ + K[i + 6] + W[i + 6];
+ T2 = (((C << 36) | (C >> 28))
+ ^ ((C << 30) | (C >> 34))
+ ^ ((C << 25) | (C >> 39)))
+ + ((C & D) ^ (E & (C ^ D)));
+ F += T1;
+ B = T1 + T2;
+ T1 = A + (((F << 50) | (F >> 14))
+ ^ ((F << 46) | (F >> 18))
+ ^ ((F << 23) | (F >> 41)))
+ + (H ^ (F & (G ^ H)))
+ + K[i + 7] + W[i + 7];
+ T2 = (((B << 36) | (B >> 28))
+ ^ ((B << 30) | (B >> 34))
+ ^ ((B << 25) | (B >> 39)))
+ + ((B & C) ^ (D & (B ^ C)));
+ E += T1;
+ A = T1 + T2;
+ }
+
+ /*
+ * Update state words and reset block pointer.
+ */
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+ state[4] += E;
+ state[5] += F;
+ state[6] += G;
+ state[7] += H;
+ ptr = 0;
+ }
+
+ static ulong Dec64be(byte[] buf, int off)
+ {
+ return ((ulong)buf[off] << 56)
+ | ((ulong)buf[off + 1] << 48)
+ | ((ulong)buf[off + 2] << 40)
+ | ((ulong)buf[off + 3] << 32)
+ | ((ulong)buf[off + 4] << 24)
+ | ((ulong)buf[off + 5] << 16)
+ | ((ulong)buf[off + 6] << 8)
+ | (ulong)buf[off + 7];
+ }
+
+ static void Enc64be(ulong x, byte[] buf, int off)
+ {
+ buf[off] = (byte)(x >> 56);
+ buf[off + 1] = (byte)(x >> 48);
+ buf[off + 2] = (byte)(x >> 40);
+ buf[off + 3] = (byte)(x >> 32);
+ buf[off + 4] = (byte)(x >> 24);
+ buf[off + 5] = (byte)(x >> 16);
+ buf[off + 6] = (byte)(x >> 8);
+ buf[off + 7] = (byte)x;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of SHA-224 and SHA-256, as described in FIPS 180-4.
+ */
+
+public abstract class SHA2Small : DigestCore {
+
+ const int BLOCK_LEN = 64;
+
+ uint[] state;
+ byte[] block, saveBlock;
+ int ptr;
+ ulong byteCount;
+ uint[] W;
+
+ /*
+ * Create a new instance, ready to process data bytes. The
+ * output length (in bytes) and initial value must be specified.
+ */
+ internal SHA2Small()
+ {
+ state = new uint[8];
+ block = new byte[BLOCK_LEN];
+ saveBlock = new byte[BLOCK_LEN];
+ W = new uint[64];
+ Reset();
+ }
+
+ internal abstract uint[] IV { get; }
+
+ internal abstract SHA2Small DupInner();
+
+ /* see IDigest */
+ public override int BlockSize {
+ get {
+ return BLOCK_LEN;
+ }
+ }
+
+ /* see IDigest */
+ public override void Reset()
+ {
+ Array.Copy(IV, 0, state, 0, state.Length);
+ byteCount = 0;
+ ptr = 0;
+ }
+
+ /* see IDigest */
+ public override void Update(byte b)
+ {
+ block[ptr ++] = b;
+ byteCount ++;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+
+ /* see IDigest */
+ public override void Update(byte[] buf, int off, int len)
+ {
+ if (len < 0) {
+ throw new ArgumentException("negative chunk length");
+ }
+ byteCount += (ulong)len;
+ while (len > 0) {
+ int clen = Math.Min(len, BLOCK_LEN - ptr);
+ Array.Copy(buf, off, block, ptr, clen);
+ off += clen;
+ len -= clen;
+ ptr += clen;
+ if (ptr == BLOCK_LEN) {
+ ProcessBlock();
+ }
+ }
+ }
+
+ /* see IDigest */
+ public override void DoPartial(byte[] outBuf, int off)
+ {
+ /*
+ * Save current state.
+ */
+ uint A = state[0];
+ uint B = state[1];
+ uint C = state[2];
+ uint D = state[3];
+ uint E = state[4];
+ uint F = state[5];
+ uint G = state[6];
+ uint H = state[7];
+ int savePtr = ptr;
+ Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+ /*
+ * Add padding. This may involve processing an extra block.
+ */
+ block[ptr ++] = 0x80;
+ if (ptr > BLOCK_LEN - 8) {
+ for (int j = ptr; j < BLOCK_LEN; j ++) {
+ block[j] = 0;
+ }
+ ProcessBlock();
+ }
+ for (int j = ptr; j < (BLOCK_LEN - 8); j ++) {
+ block[j] = 0;
+ }
+ ulong x = byteCount << 3;
+ Enc32be((uint)(x >> 32), block, BLOCK_LEN - 8);
+ Enc32be((uint)x, block, BLOCK_LEN - 4);
+
+ /*
+ * Process final block and encode result.
+ */
+ ProcessBlock();
+ int n = DigestSize >> 2;
+ for (int i = 0; i < n; i ++) {
+ Enc32be(state[i], outBuf, off + (i << 2));
+ }
+
+ /*
+ * Restore current state.
+ */
+ Array.Copy(saveBlock, 0, block, 0, savePtr);
+ state[0] = A;
+ state[1] = B;
+ state[2] = C;
+ state[3] = D;
+ state[4] = E;
+ state[5] = F;
+ state[6] = G;
+ state[7] = H;
+ ptr = savePtr;
+ }
+
+ /* see IDigest */
+ public override IDigest Dup()
+ {
+ SHA2Small h = DupInner();
+ Array.Copy(state, 0, h.state, 0, state.Length);
+ h.ptr = ptr;
+ h.byteCount = byteCount;
+ Array.Copy(block, 0, h.block, 0, ptr);
+ return h;
+ }
+
+ /* see IDigest */
+ public override void CurrentState(byte[] outBuf, int off)
+ {
+ int n = DigestSize >> 2;
+ for (int i = 0; i < n; i ++) {
+ Enc32be(state[i], outBuf, off + (i << 2));
+ }
+ }
+
+ static uint[] K = {
+ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
+ 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
+ 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
+ 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
+ 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
+ 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
+ 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
+ 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
+ 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
+ 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
+ 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
+ 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
+ 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
+ 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
+ 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
+ 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2
+ };
+
+ void ProcessBlock()
+ {
+ /*
+ * Read state words.
+ */
+ uint A = state[0];
+ uint B = state[1];
+ uint C = state[2];
+ uint D = state[3];
+ uint E = state[4];
+ uint F = state[5];
+ uint G = state[6];
+ uint H = state[7];
+
+ uint T1, T2;
+ uint[] W = this.W;
+ byte[] block = this.block;
+
+ for (int i = 0, j = 0; i < 16; i ++, j += 4) {
+ W[i] = Dec32be(block, j);
+ }
+ for (int i = 16; i < 64; i ++) {
+ uint w2 = W[i - 2];
+ uint w15 = W[i - 15];
+ W[i] = (((w2 << 15) | (w2 >> 17))
+ ^ ((w2 << 13) | (w2 >> 19))
+ ^ (w2 >> 10))
+ + W[i - 7]
+ + (((w15 << 25) | (w15 >> 7))
+ ^ ((w15 << 14) | (w15 >> 18))
+ ^ (w15 >> 3))
+ + W[i - 16];
+ }
+ for (int i = 0; i < 64; i += 8) {
+ T1 = H + (((E << 26) | (E >> 6))
+ ^ ((E << 21) | (E >> 11))
+ ^ ((E << 7) | (E >> 25)))
+ + (G ^ (E & (F ^ G)))
+ + K[i + 0] + W[i + 0];
+ T2 = (((A << 30) | (A >> 2))
+ ^ ((A << 19) | (A >> 13))
+ ^ ((A << 10) | (A >> 22)))
+ + ((A & B) ^ (C & (A ^ B)));
+ D += T1;
+ H = T1 + T2;
+ T1 = G + (((D << 26) | (D >> 6))
+ ^ ((D << 21) | (D >> 11))
+ ^ ((D << 7) | (D >> 25)))
+ + (F ^ (D & (E ^ F)))
+ + K[i + 1] + W[i + 1];
+ T2 = (((H << 30) | (H >> 2))
+ ^ ((H << 19) | (H >> 13))
+ ^ ((H << 10) | (H >> 22)))
+ + ((H & A) ^ (B & (H ^ A)));
+ C += T1;
+ G = T1 + T2;
+ T1 = F + (((C << 26) | (C >> 6))
+ ^ ((C << 21) | (C >> 11))
+ ^ ((C << 7) | (C >> 25)))
+ + (E ^ (C & (D ^ E)))
+ + K[i + 2] + W[i + 2];
+ T2 = (((G << 30) | (G >> 2))
+ ^ ((G << 19) | (G >> 13))
+ ^ ((G << 10) | (G >> 22)))
+ + ((G & H) ^ (A & (G ^ H)));
+ B += T1;
+ F = T1 + T2;
+ T1 = E + (((B << 26) | (B >> 6))
+ ^ ((B << 21) | (B >> 11))
+ ^ ((B << 7) | (B >> 25)))
+ + (D ^ (B & (C ^ D)))
+ + K[i + 3] + W[i + 3];
+ T2 = (((F << 30) | (F >> 2))
+ ^ ((F << 19) | (F >> 13))
+ ^ ((F << 10) | (F >> 22)))
+ + ((F & G) ^ (H & (F ^ G)));
+ A += T1;
+ E = T1 + T2;
+ T1 = D + (((A << 26) | (A >> 6))
+ ^ ((A << 21) | (A >> 11))
+ ^ ((A << 7) | (A >> 25)))
+ + (C ^ (A & (B ^ C)))
+ + K[i + 4] + W[i + 4];
+ T2 = (((E << 30) | (E >> 2))
+ ^ ((E << 19) | (E >> 13))
+ ^ ((E << 10) | (E >> 22)))
+ + ((E & F) ^ (G & (E ^ F)));
+ H += T1;
+ D = T1 + T2;
+ T1 = C + (((H << 26) | (H >> 6))
+ ^ ((H << 21) | (H >> 11))
+ ^ ((H << 7) | (H >> 25)))
+ + (B ^ (H & (A ^ B)))
+ + K[i + 5] + W[i + 5];
+ T2 = (((D << 30) | (D >> 2))
+ ^ ((D << 19) | (D >> 13))
+ ^ ((D << 10) | (D >> 22)))
+ + ((D & E) ^ (F & (D ^ E)));
+ G += T1;
+ C = T1 + T2;
+ T1 = B + (((G << 26) | (G >> 6))
+ ^ ((G << 21) | (G >> 11))
+ ^ ((G << 7) | (G >> 25)))
+ + (A ^ (G & (H ^ A)))
+ + K[i + 6] + W[i + 6];
+ T2 = (((C << 30) | (C >> 2))
+ ^ ((C << 19) | (C >> 13))
+ ^ ((C << 10) | (C >> 22)))
+ + ((C & D) ^ (E & (C ^ D)));
+ F += T1;
+ B = T1 + T2;
+ T1 = A + (((F << 26) | (F >> 6))
+ ^ ((F << 21) | (F >> 11))
+ ^ ((F << 7) | (F >> 25)))
+ + (H ^ (F & (G ^ H)))
+ + K[i + 7] + W[i + 7];
+ T2 = (((B << 30) | (B >> 2))
+ ^ ((B << 19) | (B >> 13))
+ ^ ((B << 10) | (B >> 22)))
+ + ((B & C) ^ (D & (B ^ C)));
+ E += T1;
+ A = T1 + T2;
+ }
+
+ /* obsolete
+ for (int i = 0; i < 64; i ++) {
+ uint T1 = H + (((E << 26) | (E >> 6))
+ ^ ((E << 21) | (E >> 11))
+ ^ ((E << 7) | (E >> 25)))
+ + (G ^ (E & (F ^ G)))
+ + K[i] + W[i];
+ uint T2 = (((A << 30) | (A >> 2))
+ ^ ((A << 19) | (A >> 13))
+ ^ ((A << 10) | (A >> 22)))
+ + ((A & B) ^ (C & (A ^ B)));
+ H = G; G = F; F = E; E = D + T1;
+ D = C; C = B; B = A; A = T1 + T2;
+ }
+ */
+
+ /*
+ * Update state words and reset block pointer.
+ */
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+ state[4] += E;
+ state[5] += F;
+ state[6] += G;
+ state[7] += H;
+ ptr = 0;
+ }
+
+ static uint Dec32be(byte[] buf, int off)
+ {
+ return ((uint)buf[off] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ }
+
+ static void Enc32be(uint x, byte[] buf, int off)
+ {
+ buf[off] = (byte)(x >> 24);
+ buf[off + 1] = (byte)(x >> 16);
+ buf[off + 2] = (byte)(x >> 8);
+ buf[off + 3] = (byte)x;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-384 implementation. SHA-384 is described in FIPS 180-4.
+ */
+
+public sealed class SHA384 : SHA2Big {
+
+ /*
+ * Create a new instance, ready to process data bytes.
+ */
+ public SHA384()
+ {
+ }
+
+ /* see IDigest */
+ public override string Name {
+ get {
+ return "SHA-384";
+ }
+ }
+
+ /* see IDigest */
+ public override int DigestSize {
+ get {
+ return 48;
+ }
+ }
+
+ internal override ulong[] IV {
+ get {
+ return IV384;
+ }
+ }
+
+ internal override SHA2Big DupInner()
+ {
+ return new SHA384();
+ }
+
+ static ulong[] IV384 = {
+ 0xCBBB9D5DC1059ED8, 0x629A292A367CD507,
+ 0x9159015A3070DD17, 0x152FECD8F70E5939,
+ 0x67332667FFC00B31, 0x8EB44A8768581511,
+ 0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4
+ };
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-512 implementation. SHA-512 is described in FIPS 180-4.
+ */
+
+public sealed class SHA512 : SHA2Big {
+
+ /*
+ * Create a new instance, ready to process data bytes.
+ */
+ public SHA512()
+ {
+ }
+
+ /* see IDigest */
+ public override string Name {
+ get {
+ return "SHA-512";
+ }
+ }
+
+ /* see IDigest */
+ public override int DigestSize {
+ get {
+ return 64;
+ }
+ }
+
+ internal override ulong[] IV {
+ get {
+ return IV512;
+ }
+ }
+
+ internal override SHA2Big DupInner()
+ {
+ return new SHA512();
+ }
+
+ static ulong[] IV512 = {
+ 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,
+ 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,
+ 0x510E527FADE682D1, 0x9B05688C2B3E6C1F,
+ 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179
+ };
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+internal class IO {
+
+ internal static void Enc16be(int x, byte[] buf, int off)
+ {
+ buf[off + 0] = (byte)(x >> 8);
+ buf[off + 1] = (byte)x;
+ }
+
+ internal static void Enc24be(int x, byte[] buf, int off)
+ {
+ buf[off + 0] = (byte)(x >> 16);
+ buf[off + 1] = (byte)(x >> 8);
+ buf[off + 2] = (byte)x;
+ }
+
+ internal static void Enc32be(uint x, byte[] buf, int off)
+ {
+ buf[off + 0] = (byte)(x >> 24);
+ buf[off + 1] = (byte)(x >> 16);
+ buf[off + 2] = (byte)(x >> 8);
+ buf[off + 3] = (byte)x;
+ }
+
+ internal static void Enc64be(ulong x, byte[] buf, int off)
+ {
+ for (int i = 0; i < 8; i ++) {
+ buf[off + 7 - i] = (byte)x;
+ x >>= 8;
+ }
+ }
+
+ internal static int Dec16be(byte[] buf, int off)
+ {
+ return (buf[off + 0] << 8)
+ | buf[off + 1];
+ }
+
+ internal static int Dec24be(byte[] buf, int off)
+ {
+ return (buf[off + 0] << 16)
+ | (buf[off + 1] << 8)
+ | buf[off + 2];
+ }
+
+ internal static uint Dec32be(byte[] buf, int off)
+ {
+ return ((uint)buf[off + 0] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ }
+
+ internal static ulong Dec64be(byte[] buf, int off)
+ {
+ ulong x = 0;
+ for (int i = 0; i < 8; i ++) {
+ x = (x << 8) | buf[off + i];
+ }
+ return x;
+ }
+
+ internal static void Write16(Stream s, int x)
+ {
+ s.WriteByte((byte)(x >> 8));
+ s.WriteByte((byte)x);
+ }
+
+ internal static void Write24(Stream s, int x)
+ {
+ s.WriteByte((byte)(x >> 16));
+ s.WriteByte((byte)(x >> 8));
+ s.WriteByte((byte)x);
+ }
+
+ /*
+ * Write a 5-byte record header at the specified offset.
+ */
+ internal static void WriteHeader(int recordType, int version,
+ int length, byte[] buf, int off)
+ {
+ buf[off] = (byte)recordType;
+ IO.Enc16be(version, buf, off + 1);
+ IO.Enc16be(length, buf, off + 3);
+ }
+
+ /*
+ * Read all requested bytes. Fail on unexpected EOF, unless
+ * 'eof' is true, in which case an EOF is acceptable at the
+ * very beginning (i.e. no byte read at all).
+ *
+ * Returned value is true, unless there was an EOF at the
+ * start and 'eof' is true, in which case returned value is
+ * false.
+ */
+ internal static bool ReadAll(Stream s, byte[] buf, bool eof)
+ {
+ return ReadAll(s, buf, 0, buf.Length, eof);
+ }
+
+ /*
+ * Read all requested bytes. Fail on unexpected EOF, unless
+ * 'eof' is true, in which case an EOF is acceptable at the
+ * very beginning (i.e. no byte read at all).
+ *
+ * Returned value is true, unless there was an EOF at the
+ * start and 'eof' is true, in which case returned value is
+ * false.
+ */
+ internal static bool ReadAll(Stream s,
+ byte[] buf, int off, int len, bool eof)
+ {
+ int tlen = 0;
+ while (tlen < len) {
+ int rlen = s.Read(buf, off + tlen, len - tlen);
+ if (rlen <= 0) {
+ if (eof && tlen == 0) {
+ return false;
+ }
+ throw new SSLException("Unexpected EOF");
+ }
+ tlen += rlen;
+ }
+ return true;
+ }
+
+ /*
+ * Compare two arrays of bytes for equality.
+ */
+ internal static bool Eq(byte[] a, byte[] b)
+ {
+ return EqCT(a, b) != 0;
+ }
+
+ /*
+ * Compare two arrays of bytes for equality. This is constant-time
+ * if both arrays have the same length. Returned value is 0xFFFFFFFF
+ * on equality, 0 otherwise.
+ */
+ internal static uint EqCT(byte[] a, byte[] b)
+ {
+ int n = a.Length;
+ if (n != b.Length) {
+ return 0;
+ }
+ int z = 0;
+ for (int i = 0; i < n; i ++) {
+ z |= a[i] ^ b[i];
+ }
+ return ~(uint)((z | -z) >> 31);
+ }
+
+ /*
+ * Duplicate an array of bytes. null is duplicated into null.
+ */
+ internal static byte[] CopyBlob(byte[] x)
+ {
+ if (x == null) {
+ return null;
+ }
+ byte[] y = new byte[x.Length];
+ Array.Copy(x, 0, y, 0, x.Length);
+ return y;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * An IServerChoices instance represents the server policy choices for
+ * a given connection. It returns the certificate chain and cipher suite
+ * to send to the client, and it computes private key operations.
+ */
+
+public interface IServerChoices {
+
+ /*
+ * Get the selected cipher suite.
+ */
+ int GetCipherSuite();
+
+ /*
+ * Get the certificate chain to send to the client.
+ */
+ byte[][] GetCertificateChain();
+
+ /*
+ * Compute the key exchange, based on the value sent by the
+ * client. Returned value is the premaster secret. This method
+ * is invoked only if the selected cipher suite is of type
+ * RSA or static ECDH.
+ */
+ byte[] DoKeyExchange(byte[] cke);
+
+ /*
+ * Compute the signature on the provided ServerKeyExchange
+ * message. The 'hashAlgo' and 'sigAlgo' values are set to
+ * the symbolic values corresponding to the used signature
+ * algorithm. This method is invoked only if the selected cipher
+ * suite is of type ECDHE.
+ */
+ byte[] DoSign(byte[] ske, out int hashAlgo, out int sigAlgo);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * An IServerPolicy instance is a callback object that selects the
+ * cipher suite and certificate chain to send to the client, and
+ * implements the private key operation (signature or key exchange).
+ */
+
+public interface IServerPolicy {
+
+ /*
+ * Get the server policy choices for the provided connection.
+ * In the 'server' object, the following are already set:
+ *
+ * Version Selected protocol version.
+ *
+ * CommonCipherSuites Common cipher suites.
+ *
+ * ClientCurves Elliptic curve supported by the client.
+ *
+ * CommonCurves Common supported curves.
+ *
+ * ClientHashAndSign Common hash and signature algorithms.
+ *
+ * Returned value is a callback object that embodies the choices
+ * and will perform private key operations.
+ */
+ IServerChoices Apply(SSLServer server);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * A session cache instance is able to cache and remember session
+ * parameters; this is typically used on a SSL server.
+ */
+
+public interface ISessionCache {
+
+ /*
+ * Retrieve session parameters by session ID. If the client sent
+ * an intended server name (SNI extension), then that name is
+ * also provided as parameter (printable ASCII, normalised to
+ * lowercase, no space); otherwise, that parameter is null.
+ * Session cache implementations are free to use the server name
+ * or not; if the client specified a target name, and the cache
+ * returns parameters with a different, non-null name, then the
+ * session resumption will be rejected by the engine.
+ *
+ * If no parameters are found for that ID (and optional server
+ * name), then this method shall return null.
+ */
+ SSLSessionParameters Retrieve(byte[] id, string serverName);
+
+ /*
+ * Record new session parameters. These should be internally
+ * indexed by their ID.
+ */
+ void Store(SSLSessionParameters sp);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+internal class InputRecord {
+
+ Stream sub;
+ byte[] buffer;
+ int recordPtr, recordEnd;
+ int recordType;
+ int recordVersion;
+ int expectedVersion;
+ RecordDecrypt rdec;
+
+ /*
+ * Get the length (in bytes) of the data that remains to be
+ * read from the received buffer.
+ */
+ internal int BufferedLength {
+ get {
+ return recordEnd - recordPtr;
+ }
+ }
+
+ /*
+ * Get the current record type (-1 if no record was read yet).
+ */
+ internal int RecordType {
+ get {
+ return recordType;
+ }
+ }
+
+ /*
+ * Get the current record version (-1 if no record was read yet).
+ */
+ internal int RecordVersion {
+ get {
+ return recordVersion;
+ }
+ }
+
+ internal InputRecord(Stream sub)
+ {
+ this.sub = sub;
+ buffer = new byte[16384 + 500];
+ recordPtr = 0;
+ recordEnd = 0;
+ recordType = -1;
+ recordVersion = -1;
+ expectedVersion = -1;
+ rdec = new RecordDecryptPlain();
+ }
+
+ /*
+ * Set the expected version. If this value is nonnegative, then
+ * all subsequent records are expected to match this version; a
+ * version mismatch will trigger an exception.
+ *
+ * If not initially set explicitly, then this value is automatically
+ * set to the version of the first incoming record.
+ */
+ internal void SetExpectedVersion(int expectedVersion)
+ {
+ this.expectedVersion = expectedVersion;
+ }
+
+ /*
+ * Set the new decryption engine. This is possible only if the
+ * end of the current record was reached.
+ */
+ internal void SetDecryption(RecordDecrypt rdec)
+ {
+ if (recordPtr != recordEnd) {
+ throw new SSLException(
+ "Cannot switch encryption: buffered data");
+ }
+ this.rdec = rdec;
+ }
+
+ /*
+ * Get next record. Returned value is false if EOF was reached
+ * before obtaining the first record header byte.
+ */
+ internal bool NextRecord()
+ {
+ if (!IO.ReadAll(sub, buffer, 0, 5, true)) {
+ return false;
+ }
+ recordType = buffer[0];
+ recordVersion = IO.Dec16be(buffer, 1);
+ int len = IO.Dec16be(buffer, 3);
+ if (expectedVersion >= 0 && expectedVersion != recordVersion) {
+ throw new SSLException(string.Format(
+ "Wrong record version: 0x{0:X4}"
+ + " (expected: 0x{1:X4})",
+ recordVersion, expectedVersion));
+ } else {
+ if ((recordVersion >> 8) != 0x03) {
+ throw new SSLException(string.Format(
+ "Unsupported record version: 0x{0:X4}",
+ recordVersion));
+ }
+ if (expectedVersion < 0) {
+ expectedVersion = recordVersion;
+ }
+ }
+ if (!rdec.CheckLength(len)) {
+ throw new SSLException("Wrong record length: " + len);
+ }
+ IO.ReadAll(sub, buffer, 0, len, false);
+ int off = 0;
+ if (!rdec.Decrypt(recordType, recordVersion,
+ buffer, ref off, ref len))
+ {
+ throw new SSLException("Decryption failure");
+ }
+ recordPtr = off;
+ recordEnd = off + len;
+ return true;
+ }
+
+ /*
+ * Read the next byte from the current record. -1 is returned if
+ * the current record is finished.
+ */
+ internal int Read()
+ {
+ if (recordPtr == recordEnd) {
+ return -1;
+ } else {
+ return buffer[recordPtr ++];
+ }
+ }
+
+ /*
+ * Read some bytes from the current record. The number of
+ * obtained bytes is returned; a short count (including 0)
+ * is possible only if the end of the current record was
+ * reached.
+ */
+ internal int Read(byte[] buf)
+ {
+ return Read(buf, 0, buf.Length);
+ }
+
+ /*
+ * Read some bytes from the current record. The number of
+ * obtained bytes is returned; a short count (including 0)
+ * is possible only if the end of the current record was
+ * reached.
+ */
+ internal int Read(byte[] buf, int off, int len)
+ {
+ int clen = Math.Min(len, recordEnd - recordPtr);
+ Array.Copy(buffer, recordPtr, buf, off, clen);
+ recordPtr += clen;
+ return clen;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace SSLTLS {
+
+/*
+ * Symbolic constants for acceptable usages of a public key:
+ *
+ * EncryptOnly public key may be used for asymmetric encryption or
+ * key exchange (TLS_RSA_* and TLS_ECDH_*), but not for
+ * signatures
+ *
+ * SignOnly public key may be used for signatures only
+ * (TLS_ECDHE_*), but not for asymmetric encryption or
+ * key exchange
+ *
+ * EncryptAndSign public key may be used for asymmetric encryption,
+ * key exchange and signatures
+ */
+
+public enum KeyUsage {
+ EncryptOnly,
+ SignOnly,
+ EncryptAndSign
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+internal class OutputRecord {
+
+ /*
+ * Splitting modes are for debugging and tests. Note that the
+ * automatic 1/n-1 split for CBC cipher suites in TLS 1.0 is
+ * handled independently in RecordEncryptCBC.
+ */
+
+ /* No split. */
+ internal const int MODE_NORMAL = 0;
+
+ /* Each record is split into two, of approximately the same size. */
+ internal const int MODE_SPLIT_HALF = 1;
+
+ /* Each record is preceded with an extra record of size 0. */
+ internal const int MODE_SPLIT_ZERO_BEFORE = 2;
+
+ /* Each record is split into two records (like SPLIT_HALF), and
+ an extra zero-length record is added between the two halves. */
+ internal const int MODE_SPLIT_ZERO_HALF = 3;
+
+ /* The first byte of each record is separated into its own record. */
+ internal const int MODE_SPLIT_ONE_START = 4;
+
+ /* The last byte of each record is separated into its own record. */
+ internal const int MODE_SPLIT_ONE_END = 5;
+
+ /* The record is split into records of length 1 byte each. */
+ internal const int MODE_SPLIT_MULTI_ONE = 6;
+
+ /*
+ * Spliting modes are only applied on the specified record types
+ * (these are bit flags that can be combined).
+ */
+ internal const int MODE_MT_CCS = 1 << SSL.CHANGE_CIPHER_SPEC;
+ internal const int MODE_MT_ALERT = 1 << SSL.ALERT;
+ internal const int MODE_MT_HANDSHAKE = 1 << SSL.HANDSHAKE;
+ internal const int MODE_MT_APPLICATION_DATA = 1 << SSL.APPLICATION_DATA;
+
+ const int MODE_MASK = 0xFFFF;
+
+ Stream sub;
+ byte[] buffer;
+ int ptr, basePtr, maxPtr;
+ int version;
+ int recordType;
+ RecordEncrypt renc;
+
+ int splitMode;
+ byte[] extra;
+
+ long countHandshake;
+ long countAppData;
+ long thresholdZeroHandshake;
+ long thresholdZeroAppData;
+ byte[] extra2;
+
+ internal OutputRecord(Stream sub)
+ {
+ this.sub = sub;
+ buffer = new byte[16384 + 500];
+ version = 0;
+ recordType = -1;
+ splitMode = MODE_NORMAL;
+ extra = null;
+ countHandshake = 0;
+ countAppData = 0;
+ thresholdZeroHandshake = 0;
+ thresholdZeroAppData = 0;
+ extra2 = new byte[500];
+ renc = new RecordEncryptPlain();
+ PrepNew();
+ }
+
+ /*
+ * If set, then all I/O errors while writing on the underlying
+ * stream will be converted to a generic SSLException with message
+ * "Unexpected transport closure". This helps test code that
+ * expects the peer to abort asynchronously, so the error may
+ * be detected during both reading or writing.
+ */
+ internal bool NormalizeIOError {
+ get; set;
+ }
+
+ internal void SetVersion(int version)
+ {
+ if (version != this.version) {
+ if (ptr != basePtr) {
+ FlushInner();
+ }
+ this.version = version;
+ PrepNew();
+ }
+ }
+
+ internal int RecordType {
+ get {
+ return recordType;
+ }
+ set {
+ if (value != recordType) {
+ if (ptr != basePtr) {
+ FlushInner();
+ }
+ recordType = value;
+ }
+ }
+ }
+
+ internal void SetEncryption(RecordEncrypt renc)
+ {
+ if (ptr != basePtr) {
+ FlushInner();
+ }
+ this.renc = renc;
+ PrepNew();
+ }
+
+ internal void SetSplitMode(int splitMode)
+ {
+ this.splitMode = splitMode;
+ if ((splitMode & MODE_MASK) != MODE_NORMAL && extra == null) {
+ extra = new byte[buffer.Length];
+ }
+ }
+
+ internal void SetThresholdZeroAppData(long t)
+ {
+ thresholdZeroAppData = t;
+ }
+
+ internal void SetThresholdZeroHandshake(long t)
+ {
+ thresholdZeroAppData = t;
+ }
+
+ void PrepNew()
+ {
+ int start = 0;
+ int end = buffer.Length;
+ renc.GetMaxPlaintext(ref start, ref end);
+ ptr = start;
+ basePtr = start;
+ maxPtr = end;
+ }
+
+ internal void Flush()
+ {
+ if (ptr == basePtr) {
+ return;
+ }
+ FlushInner();
+ sub.Flush();
+ }
+
+ internal void SendZeroLength(int type)
+ {
+ Flush();
+ int rt = RecordType;
+ RecordType = type;
+ FlushInner();
+ RecordType = rt;
+ sub.Flush();
+ }
+
+ void FlushInner()
+ {
+ int off = basePtr;
+ int len = ptr - basePtr;
+ if (version == 0) {
+ throw new Exception("Record version is not set");
+ }
+ int m = splitMode & MODE_MASK;
+ if (m == MODE_NORMAL || (splitMode & (1 << recordType)) == 0) {
+ EncryptAndWrite(off, len);
+ } else {
+ Array.Copy(buffer, off, extra, off, len);
+ switch (m) {
+ case MODE_SPLIT_HALF:
+ case MODE_SPLIT_ZERO_HALF:
+ int hlen = (len >> 1);
+ if (hlen > 0) {
+ EncryptAndWrite(off, hlen);
+ }
+ if (m == MODE_SPLIT_ZERO_HALF) {
+ EncryptAndWrite(off, 0);
+ }
+ Array.Copy(extra, off + hlen,
+ buffer, off, len - hlen);
+ hlen = len - hlen;
+ if (hlen > 0) {
+ EncryptAndWrite(off, hlen);
+ }
+ break;
+ case MODE_SPLIT_ZERO_BEFORE:
+ EncryptAndWrite(off, 0);
+ Array.Copy(extra, off, buffer, off, len);
+ if (len > 0) {
+ EncryptAndWrite(off, len);
+ }
+ break;
+ case MODE_SPLIT_ONE_START:
+ if (len > 0) {
+ EncryptAndWrite(off, 1);
+ }
+ if (len > 1) {
+ Array.Copy(extra, off + 1,
+ buffer, off, len - 1);
+ EncryptAndWrite(off, len - 1);
+ }
+ break;
+ case MODE_SPLIT_ONE_END:
+ if (len > 1) {
+ EncryptAndWrite(off, len - 1);
+ }
+ if (len > 0) {
+ buffer[off] = extra[off + len - 1];
+ EncryptAndWrite(off, 1);
+ }
+ break;
+ case MODE_SPLIT_MULTI_ONE:
+ for (int i = 0; i < len; i ++) {
+ buffer[off] = extra[off + i];
+ EncryptAndWrite(off, 1);
+ }
+ break;
+ default:
+ throw new SSLException(string.Format(
+ "Bad record splitting value: {0}", m));
+ }
+ }
+ PrepNew();
+ }
+
+ void EncryptAndWrite(int off, int len)
+ {
+ try {
+ EncryptAndWriteInner(off, len);
+ } catch {
+ if (NormalizeIOError) {
+ throw new SSLException(
+ "Unexpected transport closure");
+ } else {
+ throw;
+ }
+ }
+ }
+
+ void EncryptAndWriteInner(int off, int len)
+ {
+ if (recordType == SSL.HANDSHAKE) {
+ countHandshake ++;
+ if (countHandshake == thresholdZeroAppData) {
+ int start = 0;
+ int end = extra2.Length;
+ renc.GetMaxPlaintext(ref start, ref end);
+ int zoff = start;
+ int zlen = 0;
+ renc.Encrypt(SSL.APPLICATION_DATA, version,
+ extra2, ref zoff, ref zlen);
+ sub.Write(extra2, zoff, zlen);
+ }
+ } else if (recordType == SSL.APPLICATION_DATA) {
+ countAppData ++;
+ if (countAppData == thresholdZeroHandshake) {
+ int start = 0;
+ int end = extra2.Length;
+ renc.GetMaxPlaintext(ref start, ref end);
+ int zoff = start;
+ int zlen = 0;
+ renc.Encrypt(SSL.HANDSHAKE, version,
+ extra2, ref zoff, ref zlen);
+ sub.Write(extra2, zoff, zlen);
+ }
+ }
+
+ renc.Encrypt(recordType, version, buffer, ref off, ref len);
+ sub.Write(buffer, off, len);
+ }
+
+ internal void Write(byte x)
+ {
+ buffer[ptr ++] = x;
+ if (ptr == maxPtr) {
+ FlushInner();
+ }
+ }
+
+ internal void Write(byte[] data)
+ {
+ Write(data, 0, data.Length);
+ }
+
+ internal void Write(byte[] data, int off, int len)
+ {
+ while (len > 0) {
+ int clen = Math.Min(len, maxPtr - ptr);
+ Array.Copy(data, off, buffer, ptr, clen);
+ ptr += clen;
+ off += clen;
+ len -= clen;
+ if (ptr == maxPtr) {
+ FlushInner();
+ }
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Implementation of the TLS PRF function. This class implements both the
+ * PRF for TLS 1.0 and 1.1 (based on MD5 and SHA-1), and the PRF for
+ * TLS 1.2 (based on a provided hash function).
+ */
+
+public sealed class PRF {
+
+ public static byte[] LABEL_MASTER_SECRET =
+ Encoding.UTF8.GetBytes("master secret");
+ public static byte[] LABEL_KEY_EXPANSION =
+ Encoding.UTF8.GetBytes("key expansion");
+ public static byte[] LABEL_CLIENT_FINISHED =
+ Encoding.UTF8.GetBytes("client finished");
+ public static byte[] LABEL_SERVER_FINISHED =
+ Encoding.UTF8.GetBytes("server finished");
+
+ HMAC hm1, hm2;
+ byte[] bufa1, bufa2;
+ byte[] bufb1, bufb2;
+
+ /*
+ * Create a PRF instance, using both MD5 and SHA-1 (for TLS 1.0
+ * and TLS 1.1).
+ */
+ public PRF() : this(new MD5(), new SHA1())
+ {
+ }
+
+ /*
+ * Create a PRF instance, using the provided hash function (for
+ * TLS 1.2). The 'h' instance will be used internally.
+ */
+ public PRF(IDigest h) : this(h, null)
+ {
+ }
+
+ /*
+ * Get the "natural" output length; this is the output size,
+ * or sum of output sizes, of the underlying hash function(s).
+ */
+ public int NaturalOutputSize {
+ get {
+ int len = hm1.MACSize;
+ if (hm2 != null) {
+ len += hm2.MACSize;
+ }
+ return len;
+ }
+ }
+
+ PRF(IDigest h1, IDigest h2)
+ {
+ hm1 = new HMAC(h1);
+ bufa1 = new byte[hm1.MACSize];
+ bufb1 = new byte[hm1.MACSize];
+ if (h2 == null) {
+ hm2 = null;
+ bufa2 = null;
+ bufb2 = null;
+ } else {
+ hm2 = new HMAC(h2);
+ bufa2 = new byte[hm2.MACSize];
+ bufb2 = new byte[hm2.MACSize];
+ }
+ }
+
+ /*
+ * Compute the PRF, result in outBuf[].
+ */
+ public void GetBytes(byte[] secret, byte[] label, byte[] seed,
+ byte[] outBuf)
+ {
+ GetBytes(secret, label, seed, outBuf, 0, outBuf.Length);
+ }
+
+ /*
+ * Compute the PRF, result in outBuf[] (at offset 'off', producing
+ * exactly 'len' bytes).
+ */
+ public void GetBytes(byte[] secret, byte[] label, byte[] seed,
+ byte[] outBuf, int off, int len)
+ {
+ for (int i = 0; i < len; i ++) {
+ outBuf[off + i] = 0;
+ }
+ if (hm2 == null) {
+ Phash(hm1, secret, 0, secret.Length,
+ bufa1, bufb1,
+ label, seed, outBuf, off, len);
+ } else {
+ int n = (secret.Length + 1) >> 1;
+ Phash(hm1, secret, 0, n,
+ bufa1, bufb1,
+ label, seed, outBuf, off, len);
+ Phash(hm2, secret, secret.Length - n, n,
+ bufa2, bufb2,
+ label, seed, outBuf, off, len);
+ }
+ }
+
+ /*
+ * Compute the PRF, result is written in a newly allocated
+ * array (of length 'outLen' bytes).
+ */
+ public byte[] GetBytes(byte[] secret, byte[] label, byte[] seed,
+ int outLen)
+ {
+ byte[] r = new byte[outLen];
+ GetBytes(secret, label, seed, r, 0, outLen);
+ return r;
+ }
+
+ /*
+ * This function computes Phash with the specified HMAC
+ * engine, XORing the output with the current contents of
+ * the outBuf[] buffer.
+ */
+ static void Phash(HMAC hm, byte[] s, int soff, int slen,
+ byte[] bufa, byte[] bufb,
+ byte[] label, byte[] seed,
+ byte[] outBuf, int outOff, int outLen)
+ {
+ /*
+ * Set the key for HMAC.
+ */
+ hm.SetKey(s, soff, slen);
+
+ /*
+ * Compute A(1) = HMAC(secret, seed).
+ */
+ hm.Update(label);
+ hm.Update(seed);
+ hm.DoFinal(bufa, 0);
+ while (outLen > 0) {
+ /*
+ * Next chunk: HMAC(secret, A(i) + label + seed)
+ */
+ hm.Update(bufa);
+ hm.Update(label);
+ hm.Update(seed);
+ hm.DoFinal(bufb, 0);
+ int clen = Math.Min(hm.MACSize, outLen);
+ for (int i = 0; i < clen; i ++) {
+ outBuf[outOff ++] ^= bufb[i];
+ }
+ outLen -= clen;
+
+ /*
+ * If we are not finished, then compute:
+ * A(i+1) = HMAC(secret, A(i))
+ */
+ if (outLen > 0) {
+ hm.Update(bufa);
+ hm.DoFinal(bufa, 0);
+ }
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal abstract class RecordDecrypt {
+
+ /*
+ * Check whether an incoming record length is valid.
+ */
+ internal abstract bool CheckLength(int len);
+
+ /*
+ * Decrypt the incoming record. This method assumes that the
+ * record length has already been tested with CheckLength().
+ * The 'off' and 'len' values point to the record contents (not
+ * including the header), and are adjusted to point at the
+ * decrypted plaintext. On error, false is returned.
+ */
+ internal abstract bool Decrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptCBC : RecordDecrypt {
+
+ IBlockCipher bc;
+ HMAC hm;
+ byte[] iv, ivTmp;
+ bool explicitIV;
+ ulong seq;
+ byte[] tmp1, tmp2;
+
+ internal RecordDecryptCBC(IBlockCipher bc, HMAC hm, byte[] iv)
+ {
+ this.bc = bc;
+ this.hm = hm;
+ this.iv = new byte[bc.BlockSize];
+ this.ivTmp = new byte[bc.BlockSize];
+ if (iv == null) {
+ explicitIV = true;
+ } else {
+ Array.Copy(iv, 0, this.iv, 0, iv.Length);
+ explicitIV = false;
+ }
+ seq = 0;
+ tmp1 = new byte[Math.Max(13, hm.MACSize)];
+ tmp2 = new byte[Math.Max(13, hm.MACSize)];
+ }
+
+ internal override bool CheckLength(int len)
+ {
+ /*
+ * Record length (not counting the header) must be
+ * a multiple of the block size, and have enough room
+ * for the MAC and the padding-length byte. With
+ * TLS 1.1+, there must also be an explicit IV.
+ */
+ int blen = bc.BlockSize;
+ int hlen = hm.MACSize;
+ if ((len & (blen - 1)) != 0) {
+ return false;
+ }
+ int minLen = hlen + 1;
+ int maxLen = (16384 + 256 + hlen) & ~(blen - 1);
+ if (explicitIV) {
+ minLen += blen;
+ maxLen += blen;
+ }
+ return len >= minLen && len <= maxLen;
+ }
+
+ internal override bool Decrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ int blen = bc.BlockSize;
+ int hlen = hm.MACSize;
+
+ /*
+ * Grab a copy of the last encrypted block; this is
+ * the "saved IV" for the next record.
+ */
+ Array.Copy(data, off + len - blen, ivTmp, 0, blen);
+
+ /*
+ * Decrypt the data. The length has already been
+ * checked. If there is an explicit IV, it gets
+ * "decrypted" as well, which is not a problem.
+ */
+ bc.CBCDecrypt(iv, data, off, len);
+ Array.Copy(ivTmp, 0, iv, 0, blen);
+ if (explicitIV) {
+ off += blen;
+ len -= blen;
+ }
+
+ /*
+ * Compute minimum and maximum length of plaintext + MAC.
+ * These can be inferred from the observable record length,
+ * and thus are not secret.
+ */
+ int minLen = (hlen + 256 < len) ? len - 256 : hlen;
+ int maxLen = len - 1;
+
+ /*
+ * Get the actual padding length and check padding. The
+ * padding length must match the minLen/maxLen range.
+ */
+ int padLen = data[off + len - 1];
+ int good = ~(((maxLen - minLen) - padLen) >> 31);
+ int lenWithMAC = minLen ^ (good & (minLen ^ (maxLen - padLen)));
+ int dbb = 0;
+ for (int i = minLen; i < maxLen; i ++) {
+ dbb |= ~((i - lenWithMAC) >> 31)
+ & (data[off + i] ^ padLen);
+ }
+ good &= ~((dbb | -dbb) >> 31);
+
+ /*
+ * Extract the MAC value; this is done in one pass, but
+ * results in a "rotate" MAC value. The rotation count
+ * is kept in 'rotCount': this is the offset of the
+ * first MAC value byte in tmp1[].
+ */
+ int lenNoMAC = lenWithMAC - hlen;
+ minLen -= hlen;
+ int rotCount = 0;
+ for (int i = 0; i < hlen; i ++) {
+ tmp1[i] = 0;
+ }
+ int v = 0;
+ for (int i = minLen; i < maxLen; i ++) {
+ int m = ~((i - lenNoMAC) >> 31)
+ & ((i - lenWithMAC) >> 31);
+ tmp1[v] |= (byte)(m & data[off + i]);
+ m = i - lenNoMAC;
+ rotCount |= ~((m | -m) >> 31) & v;
+ if (++ v == hlen) {
+ v = 0;
+ }
+ }
+ maxLen -= hlen;
+
+ /*
+ * Rotate back the MAC value. We do it bit by bit, with
+ * 6 iterations; this is good for all MAC value up to
+ * and including 64 bytes.
+ */
+ for (int i = 5; i >= 0; i --) {
+ int rc = 1 << i;
+ if (rc >= hlen) {
+ continue;
+ }
+ int ctl = -((rotCount >> i) & 1);
+ for (int j = 0, k = rc; j < hlen; j ++) {
+ int b1 = tmp1[j];
+ int b2 = tmp1[k];
+ tmp2[j] = (byte)(b1 ^ (ctl & (b1 ^ b2)));
+ if (++ k == hlen) {
+ k = 0;
+ }
+ }
+ Array.Copy(tmp2, 0, tmp1, 0, hlen);
+ rotCount &= ~rc;
+ }
+
+ /*
+ * Recompute the HMAC value. At that point, minLen and
+ * maxLen have been adjusted to match the plaintext
+ * without the MAC.
+ */
+ IO.Enc64be(seq ++, tmp2, 0);
+ IO.WriteHeader(recordType, version, lenNoMAC, tmp2, 8);
+ hm.Update(tmp2, 0, 13);
+ hm.ComputeCT(data, off, lenNoMAC, minLen, maxLen, tmp2, 0);
+
+ /*
+ * Compare MAC values.
+ */
+ dbb = 0;
+ for (int i = 0; i < hlen; i ++) {
+ dbb |= tmp1[i] ^ tmp2[i];
+ }
+ good &= ~((dbb | -dbb) >> 31);
+
+ /*
+ * We must also check that the plaintext length fits in
+ * the maximum allowed by the standard (previous check
+ * was on the encrypted length).
+ */
+ good &= (lenNoMAC - 16385) >> 31;
+ len = lenNoMAC;
+ return good != 0;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptChaPol : RecordDecrypt {
+
+ Poly1305 pp;
+ byte[] iv;
+ byte[] nonce;
+ byte[] tmp;
+ byte[] tag;
+ ulong seq;
+
+ internal RecordDecryptChaPol(Poly1305 pp, byte[] iv)
+ {
+ this.pp = pp;
+ this.iv = new byte[12];
+ Array.Copy(iv, 0, this.iv, 0, 12);
+ nonce = new byte[12];
+ tmp = new byte[13];
+ tag = new byte[16];
+ seq = 0;
+ }
+
+ internal override bool CheckLength(int len)
+ {
+ return len >= 16 && len <= (16384 + 16);
+ }
+
+ internal override bool Decrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ /*
+ * Make the "additional data" for the MAC:
+ * -- sequence number (8 bytes, big-endian)
+ * -- header with plaintext length (5 bytes)
+ */
+ len -= 16;
+ IO.Enc64be(seq, tmp, 0);
+ IO.WriteHeader(recordType, version, len, tmp, 8);
+
+ /*
+ * The ChaCha20+Poly1305 IV consists in the
+ * implicit IV (12 bytes), with the sequence number
+ * "XORed" in the last 8 bytes (big-endian).
+ */
+ Array.Copy(iv, 0, nonce, 0, 12);
+ for (int i = 0; i < 8; i ++) {
+ nonce[i + 4] ^= tmp[i];
+ }
+
+ /*
+ * Do encryption and compute tag.
+ */
+ pp.Run(nonce, data, off, len, tmp, 0, 13, tag, false);
+
+ /*
+ * Each record has its own sequence number.
+ */
+ seq ++;
+
+ /*
+ * Compare the tag value.
+ */
+ int z = 0;
+ for (int i = 0; i < 16; i ++) {
+ z |= tag[i] ^ data[off + len + i];
+ }
+ return z == 0;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptGCM : RecordDecrypt {
+
+ IBlockCipher bc;
+ byte[] iv;
+ byte[] h;
+ ulong seq;
+ byte[] tmp, tag;
+
+ internal RecordDecryptGCM(IBlockCipher bc, byte[] iv)
+ {
+ this.bc = bc;
+ this.iv = new byte[12];
+ Array.Copy(iv, 0, this.iv, 0, 4);
+ h = new byte[16];
+ bc.BlockEncrypt(h);
+ seq = 0;
+ tag = new byte[16];
+ tmp = new byte[29];
+ }
+
+ internal override bool CheckLength(int len)
+ {
+ return len >= 24 && len <= (16384 + 24);
+ }
+
+ internal override bool Decrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ off += 8;
+ len -= 24;
+ IO.Enc64be(seq, tmp, 0);
+ IO.WriteHeader(recordType, version, len, tmp, 8);
+ IO.Enc64be(13 << 3, tmp, 13);
+ IO.Enc64be((ulong)len << 3, tmp, 21);
+ for (int i = 0; i < 16; i ++) {
+ tag[i] = 0;
+ }
+ GHASH.Run(tag, h, tmp, 0, 13);
+ GHASH.Run(tag, h, data, off, len);
+ GHASH.Run(tag, h, tmp, 13, 16);
+ seq ++;
+
+ Array.Copy(data, off - 8, iv, 4, 8);
+ bc.CTRRun(iv, 2, data, off, len);
+ bc.CTRRun(iv, 1, data, off + len, 16);
+
+ int z = 0;
+ for (int i = 0; i < 16; i ++) {
+ z |= tag[i] ^ data[off + len + i];
+ }
+ return z == 0;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptPlain : RecordDecrypt {
+
+ internal override bool CheckLength(int len)
+ {
+ return len <= 16384;
+ }
+
+ internal override bool Decrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ return true;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal abstract class RecordEncrypt {
+
+ /*
+ * Adjust index for plaintext. This function must reserve room
+ * for header, MAC, padding...
+ */
+ internal abstract void GetMaxPlaintext(ref int start, ref int end);
+
+ /*
+ * Perform encryption. The plaintext offset and length are provided;
+ * upon exit, they are modified to designate the complete record
+ * (including the header).
+ */
+ internal abstract void Encrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len);
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptCBC : RecordEncrypt {
+
+ IBlockCipher bc;
+ HMAC hm;
+ byte[] iv;
+ bool explicitIV;
+ ulong seq;
+ byte[] tmp;
+
+ internal RecordEncryptCBC(IBlockCipher bc, HMAC hm, byte[] iv)
+ {
+ this.bc = bc;
+ this.hm = hm;
+ this.iv = new byte[bc.BlockSize];
+ if (iv == null) {
+ explicitIV = true;
+ } else {
+ Array.Copy(iv, 0, this.iv, 0, iv.Length);
+ explicitIV = false;
+ }
+ seq = 0;
+ tmp = new byte[Math.Max(13, hm.MACSize)];
+ }
+
+ internal override void GetMaxPlaintext(ref int start, ref int end)
+ {
+ /*
+ * Add room for the record header.
+ */
+ start += 5;
+
+ int blen = bc.BlockSize;
+ if (explicitIV) {
+ start += blen;
+ } else {
+ /*
+ * We reserve room for an automatic 1/n-1 split.
+ */
+ start += 4 + ((hm.MACSize + blen + 1) & ~(blen - 1));
+ }
+ int len = (end - start) & ~(blen - 1);
+ len -= 1 + hm.MACSize;
+
+ /*
+ * We keep a bit of extra room to try out overlong padding.
+ */
+ len -= blen;
+
+ if (len > 16384) {
+ len = 16384;
+ }
+ end = start + len;
+ }
+
+ internal override void Encrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ if (explicitIV
+ || recordType != SSL.APPLICATION_DATA
+ || len <= 1)
+ {
+ EncryptInner(recordType, version,
+ data, ref off, ref len);
+ return;
+ }
+
+ /*
+ * Automatic 1/n-1 split. We do it only when there is
+ * no explicit IV (i.e. TLS 1.0, not TLS 1.1+), and there
+ * are at least two plaintext bytes in the record.
+ */
+ int blen = bc.BlockSize;
+ int off1 = off - (4 + ((hm.MACSize + blen + 1) & ~(blen - 1)));
+ int len1 = 1;
+ data[off1] = data[off];
+ EncryptInner(recordType, version, data, ref off1, ref len1);
+ int off2 = off + 1;
+ int len2 = len - 1;
+ EncryptInner(recordType, version, data, ref off2, ref len2);
+ if (off1 + len1 != off2) {
+ throw new Exception("Split gone wrong");
+ }
+ off = off1;
+ len = len1 + len2;
+ }
+
+ void EncryptInner(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ int blen = bc.BlockSize;
+ int mlen = hm.MACSize;
+ int doff = off;
+ int dlen = len;
+
+ if (explicitIV) {
+ /*
+ * To make pseudorandom IV, we reuse HMAC, computed
+ * over the encoded sequence number. Since this
+ * input is distinct from all other HMAC inputs with
+ * the same key, this should be randomish enough
+ * (assuming HMAC is a good imitation of a random
+ * oracle).
+ */
+ IO.Enc64be(seq, tmp, 0);
+ hm.Update(tmp, 0, 8);
+ hm.DoFinal(tmp, 0);
+ Array.Copy(tmp, 0, data, off - blen, blen);
+ off -= blen;
+ len += blen;
+ }
+
+ /*
+ * Compute HMAC.
+ */
+ IO.Enc64be(seq, tmp, 0);
+ IO.WriteHeader(recordType, version, dlen, tmp, 8);
+ hm.Update(tmp, 0, 13);
+ hm.Update(data, doff, dlen);
+ hm.DoFinal(data, off + len);
+ len += mlen;
+ seq ++;
+
+ /*
+ * Add padding.
+ */
+ int plen = blen - (len & (blen - 1));
+ for (int i = 0; i < plen; i ++) {
+ data[off + len + i] = (byte)(plen - 1);
+ }
+ len += plen;
+
+ /*
+ * Perform CBC encryption. We use our saved IV. If there is
+ * an explicit IV, then it gets encrypted, which is fine
+ * (CBC encryption of randomness is equally good randomness).
+ */
+ bc.CBCEncrypt(iv, data, off, len);
+ Array.Copy(data, off + len - blen, iv, 0, blen);
+
+ /*
+ * Add the header.
+ */
+ off -= 5;
+ IO.WriteHeader(recordType, version, len, data, off);
+ len += 5;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptChaPol : RecordEncrypt {
+
+ Poly1305 pp;
+ byte[] iv;
+ byte[] nonce;
+ byte[] tmp;
+ byte[] tag;
+ ulong seq;
+
+ internal RecordEncryptChaPol(Poly1305 pp, byte[] iv)
+ {
+ this.pp = pp;
+ this.iv = new byte[12];
+ Array.Copy(iv, 0, this.iv, 0, 12);
+ nonce = new byte[12];
+ tmp = new byte[13];
+ tag = new byte[16];
+ seq = 0;
+ }
+
+ internal override void GetMaxPlaintext(ref int start, ref int end)
+ {
+ /*
+ * We need room at the start for the record header (5 bytes)
+ * and some at the end for the MAC (16 bytes).
+ */
+ start += 5;
+ end -= 16;
+ int len = Math.Min(end - start, 16384);
+ end = start + len;
+ }
+
+ internal override void Encrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ /*
+ * Make the "additional data" for the MAC:
+ * -- sequence number (8 bytes, big-endian)
+ * -- header with plaintext length (5 bytes)
+ */
+ IO.Enc64be(seq, tmp, 0);
+ IO.WriteHeader(recordType, version, len, tmp, 8);
+
+ /*
+ * The ChaCha20+Poly1305 IV consists in the
+ * implicit IV (12 bytes), with the sequence number
+ * "XORed" in the last 8 bytes (big-endian).
+ */
+ Array.Copy(iv, 0, nonce, 0, 12);
+ for (int i = 0; i < 8; i ++) {
+ nonce[i + 4] ^= tmp[i];
+ }
+
+ /*
+ * Do encryption and compute tag.
+ */
+ pp.Run(nonce, data, off, len, tmp, 0, 13, tag, true);
+
+ /*
+ * Copy back tag where appropriate and add header.
+ */
+ Array.Copy(tag, 0, data, off + len, 16);
+ off -= 5;
+ len += 16;
+ IO.WriteHeader(recordType, version, len, data, off);
+ len += 5;
+
+ /*
+ * Each record has its own sequence number.
+ */
+ seq ++;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptGCM : RecordEncrypt {
+
+ IBlockCipher bc;
+ byte[] iv;
+ byte[] h;
+ ulong seq;
+ byte[] tmp, tag;
+
+ internal RecordEncryptGCM(IBlockCipher bc, byte[] iv)
+ {
+ this.bc = bc;
+ this.iv = new byte[12];
+ Array.Copy(iv, 0, this.iv, 0, 4);
+ h = new byte[16];
+ bc.BlockEncrypt(h);
+ seq = 0;
+ tag = new byte[16];
+ tmp = new byte[29];
+ }
+
+ internal override void GetMaxPlaintext(ref int start, ref int end)
+ {
+ /*
+ * We need room at the start for the record header (5 bytes)
+ * and the explicit nonce (8 bytes). We need room at the end
+ * for the MAC (16 bytes).
+ */
+ start += 13;
+ end -= 16;
+ int len = Math.Min(end - start, 16384);
+ end = start + len;
+ }
+
+ internal override void Encrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ /*
+ * Explicit nonce is the encoded sequence number. We
+ * encrypt the data itself; the counter starts at 2
+ * (value 1 is for the authentication tag).
+ */
+ IO.Enc64be(seq, data, off - 8);
+ Array.Copy(data, off - 8, iv, 4, 8);
+ bc.CTRRun(iv, 2, data, off, len);
+
+ /*
+ * For the authentication tag:
+ * header = sequence + 5-byte "plain" header
+ * footer = the two relevant lengths (in bits)
+ */
+ IO.Enc64be(seq, tmp, 0);
+ IO.WriteHeader(recordType, version, len, tmp, 8);
+ IO.Enc64be(13 << 3, tmp, 13);
+ IO.Enc64be((ulong)len << 3, tmp, 21);
+
+ /*
+ * Compute clear authentication tag.
+ */
+ for (int i = 0; i < 16; i ++) {
+ tag[i] = 0;
+ }
+ GHASH.Run(tag, h, tmp, 0, 13);
+ GHASH.Run(tag, h, data, off, len);
+ GHASH.Run(tag, h, tmp, 13, 16);
+
+ /*
+ * Copy authentication tag and apply final encryption on it.
+ */
+ Array.Copy(tag, 0, data, off + len, 16);
+ bc.CTRRun(iv, 1, data, off + len, 16);
+
+ /*
+ * Each record uses one sequence number.
+ */
+ seq ++;
+
+ /*
+ * Write encrypted header and return adjusted offset/length.
+ */
+ off -= 13;
+ len += 24;
+ IO.WriteHeader(recordType, version, len, data, off);
+ len += 5;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptPlain : RecordEncrypt {
+
+ internal RecordEncryptPlain()
+ {
+ }
+
+ internal override void GetMaxPlaintext(ref int start, ref int end)
+ {
+ start += 5;
+ end = Math.Min(end, start + 16384);
+ }
+
+ internal override void Encrypt(int recordType, int version,
+ byte[] data, ref int off, ref int len)
+ {
+ off -= 5;
+ IO.WriteHeader(recordType, version, len, data, off);
+ len += 5;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+using Asn1;
+using Crypto;
+using XKeys;
+
+namespace SSLTLS {
+
+/*
+ * A fake class that serves as container for various constants.
+ */
+
+public sealed class SSL {
+
+ /*
+ * Protocol versions.
+ */
+ public const int SSL30 = 0x0300;
+ public const int TLS10 = 0x0301;
+ public const int TLS11 = 0x0302;
+ public const int TLS12 = 0x0303;
+
+ /*
+ * Record types.
+ */
+ public const int CHANGE_CIPHER_SPEC = 20;
+ public const int ALERT = 21;
+ public const int HANDSHAKE = 22;
+ public const int APPLICATION_DATA = 23;
+
+ /*
+ * Alert levels.
+ */
+ public const int WARNING = 1;
+ public const int FATAL = 2;
+
+ /*
+ * Alert messages.
+ */
+ public const int CLOSE_NOTIFY = 0;
+ public const int UNEXPECTED_MESSAGE = 10;
+ public const int BAD_RECORD_MAC = 20;
+ public const int DECRYPTION_FAILED = 21;
+ public const int RECORD_OVERFLOW = 22;
+ public const int DECOMPRESSION_FAILURE = 30;
+ public const int HANDSHAKE_FAILURE = 40;
+ public const int BAD_CERTIFICATE = 42;
+ public const int UNSUPPORTED_CERTIFICATE = 43;
+ public const int CERTIFICATE_REVOKED = 44;
+ public const int CERTIFICATE_EXPIRED = 45;
+ public const int CERTIFICATE_UNKNOWN = 46;
+ public const int ILLEGAL_PARAMETER = 47;
+ public const int UNKNOWN_CA = 48;
+ public const int ACCESS_DENIED = 49;
+ public const int DECODE_ERROR = 50;
+ public const int DECRYPT_ERROR = 51;
+ public const int EXPORT_RESTRICTION = 60;
+ public const int PROTOCOL_VERSION = 70;
+ public const int INSUFFICIENT_SECURITY = 71;
+ public const int INTERNAL_ERROR = 80;
+ public const int USER_CANCELED = 90;
+ public const int NO_RENEGOTIATION = 100;
+
+ /*
+ * Handshake message types.
+ */
+ public const int HELLO_REQUEST = 0;
+ public const int CLIENT_HELLO = 1;
+ public const int SERVER_HELLO = 2;
+ public const int CERTIFICATE = 11;
+ public const int SERVER_KEY_EXCHANGE = 12;
+ public const int CERTIFICATE_REQUEST = 13;
+ public const int SERVER_HELLO_DONE = 14;
+ public const int CERTIFICATE_VERIFY = 15;
+ public const int CLIENT_KEY_EXCHANGE = 16;
+ public const int FINISHED = 20;
+
+ /*
+ * Cipher suites.
+ */
+
+ /* From RFC 5246 */
+ public const int NULL_WITH_NULL_NULL = 0x0000;
+ public const int RSA_WITH_NULL_MD5 = 0x0001;
+ public const int RSA_WITH_NULL_SHA = 0x0002;
+ public const int RSA_WITH_NULL_SHA256 = 0x003B;
+ public const int RSA_WITH_RC4_128_MD5 = 0x0004;
+ public const int RSA_WITH_RC4_128_SHA = 0x0005;
+ public const int RSA_WITH_3DES_EDE_CBC_SHA = 0x000A;
+ public const int RSA_WITH_AES_128_CBC_SHA = 0x002F;
+ public const int RSA_WITH_AES_256_CBC_SHA = 0x0035;
+ public const int RSA_WITH_AES_128_CBC_SHA256 = 0x003C;
+ public const int RSA_WITH_AES_256_CBC_SHA256 = 0x003D;
+ public const int DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D;
+ public const int DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010;
+ public const int DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013;
+ public const int DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016;
+ public const int DH_DSS_WITH_AES_128_CBC_SHA = 0x0030;
+ public const int DH_RSA_WITH_AES_128_CBC_SHA = 0x0031;
+ public const int DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032;
+ public const int DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033;
+ public const int DH_DSS_WITH_AES_256_CBC_SHA = 0x0036;
+ public const int DH_RSA_WITH_AES_256_CBC_SHA = 0x0037;
+ public const int DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038;
+ public const int DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039;
+ public const int DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E;
+ public const int DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F;
+ public const int DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040;
+ public const int DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067;
+ public const int DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068;
+ public const int DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069;
+ public const int DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A;
+ public const int DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B;
+ public const int DH_anon_WITH_RC4_128_MD5 = 0x0018;
+ public const int DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B;
+ public const int DH_anon_WITH_AES_128_CBC_SHA = 0x0034;
+ public const int DH_anon_WITH_AES_256_CBC_SHA = 0x003A;
+ public const int DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C;
+ public const int DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D;
+
+ /* From RFC 4492 */
+ public const int ECDH_ECDSA_WITH_NULL_SHA = 0xC001;
+ public const int ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002;
+ public const int ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003;
+ public const int ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004;
+ public const int ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005;
+ public const int ECDHE_ECDSA_WITH_NULL_SHA = 0xC006;
+ public const int ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007;
+ public const int ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008;
+ public const int ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009;
+ public const int ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A;
+ public const int ECDH_RSA_WITH_NULL_SHA = 0xC00B;
+ public const int ECDH_RSA_WITH_RC4_128_SHA = 0xC00C;
+ public const int ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D;
+ public const int ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E;
+ public const int ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F;
+ public const int ECDHE_RSA_WITH_NULL_SHA = 0xC010;
+ public const int ECDHE_RSA_WITH_RC4_128_SHA = 0xC011;
+ public const int ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012;
+ public const int ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013;
+ public const int ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014;
+ public const int ECDH_anon_WITH_NULL_SHA = 0xC015;
+ public const int ECDH_anon_WITH_RC4_128_SHA = 0xC016;
+ public const int ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017;
+ public const int ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018;
+ public const int ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019;
+
+ /* From RFC 5288 */
+ public const int RSA_WITH_AES_128_GCM_SHA256 = 0x009C;
+ public const int RSA_WITH_AES_256_GCM_SHA384 = 0x009D;
+ public const int DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E;
+ public const int DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F;
+ public const int DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0;
+ public const int DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1;
+ public const int DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2;
+ public const int DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3;
+ public const int DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4;
+ public const int DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5;
+ public const int DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6;
+ public const int DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7;
+
+ /* From RFC 5289 */
+ public const int ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023;
+ public const int ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024;
+ public const int ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025;
+ public const int ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026;
+ public const int ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027;
+ public const int ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028;
+ public const int ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029;
+ public const int ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A;
+ public const int ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B;
+ public const int ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C;
+ public const int ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D;
+ public const int ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E;
+ public const int ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F;
+ public const int ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030;
+ public const int ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031;
+ public const int ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032;
+
+ /* From RFC 7905 */
+ public const int ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8;
+ public const int ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9;
+ public const int DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAA;
+ public const int PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAB;
+ public const int ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAC;
+ public const int DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAD;
+ public const int RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAE;
+
+ /* From RFC 7507 */
+ public const int FALLBACK_SCSV = 0x5600;
+
+ /* From RFC 5746 */
+ public const int EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF;
+
+ /*
+ * Client certificate types.
+ */
+ public const int RSA_SIGN = 1;
+ public const int DSS_SIGN = 2;
+ public const int RSA_FIXED_DH = 3;
+ public const int DSS_FIXED_DH = 4;
+
+ /*
+ * Hash algorithm identifiers. The special "MD5SHA1" is for use
+ * with RSA signatures in TLS 1.0 and 1.1 only.
+ */
+ public const int MD5SHA1 = 0;
+ public const int MD5 = 1;
+ public const int SHA1 = 2;
+ public const int SHA224 = 3;
+ public const int SHA256 = 4;
+ public const int SHA384 = 5;
+ public const int SHA512 = 6;
+
+ /*
+ * Signature algorithm identifiers.
+ */
+ public const int RSA = 1;
+ public const int DSA = 2;
+ public const int ECDSA = 3;
+
+ /*
+ * Combined hash-and-sign algorithms.
+ */
+ public const int RSA_MD5SHA1 = (MD5SHA1 << 8) + RSA;
+ public const int RSA_MD5 = (MD5 << 8) + RSA;
+ public const int RSA_SHA1 = (SHA1 << 8) + RSA;
+ public const int RSA_SHA224 = (SHA224 << 8) + RSA;
+ public const int RSA_SHA256 = (SHA256 << 8) + RSA;
+ public const int RSA_SHA384 = (SHA384 << 8) + RSA;
+ public const int RSA_SHA512 = (SHA512 << 8) + RSA;
+ public const int ECDSA_MD5 = (MD5 << 8) + ECDSA;
+ public const int ECDSA_SHA1 = (SHA1 << 8) + ECDSA;
+ public const int ECDSA_SHA224 = (SHA224 << 8) + ECDSA;
+ public const int ECDSA_SHA256 = (SHA256 << 8) + ECDSA;
+ public const int ECDSA_SHA384 = (SHA384 << 8) + ECDSA;
+ public const int ECDSA_SHA512 = (SHA512 << 8) + ECDSA;
+
+ /*
+ * Symbolic identifiers for named curves.
+ */
+ public const int NIST_P256 = 23;
+ public const int NIST_P384 = 24;
+ public const int NIST_P521 = 25;
+ public const int Curve25519 = 29;
+
+ /*
+ * Get a human-readable name for a version.
+ */
+ public static string VersionName(int version)
+ {
+ switch (version) {
+ case SSL30: return "SSL 3.0";
+ case TLS10: return "TLS 1.0";
+ case TLS11: return "TLS 1.1";
+ case TLS12: return "TLS 1.2";
+ }
+ if ((version >> 8) == 3) {
+ return String.Format("TLS 1.{0}", (version & 0xFF) - 1);
+ }
+ return String.Format("UNKNOWN:0x{0:X4}", version);
+ }
+
+ /*
+ * Get a human-readable name for a cipher suite.
+ */
+ public static string CipherSuiteName(int cipherSuite)
+ {
+ switch (cipherSuite) {
+ case NULL_WITH_NULL_NULL:
+ return "NULL_WITH_NULL_NULL";
+ case RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+ case DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+ case DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+ case DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+ case ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+ case RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+ case ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+ case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+ case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
+ case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ return "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256";
+ case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return "DHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
+ case PSK_WITH_CHACHA20_POLY1305_SHA256:
+ return "PSK_WITH_CHACHA20_POLY1305_SHA256";
+ case ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+ return "ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256";
+ case DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+ return "DHE_PSK_WITH_CHACHA20_POLY1305_SHA256";
+ case RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+ return "RSA_PSK_WITH_CHACHA20_POLY1305_SHA256";
+ case FALLBACK_SCSV:
+ return "FALLBACK_SCSV";
+ case EMPTY_RENEGOTIATION_INFO_SCSV:
+ return "EMPTY_RENEGOTIATION_INFO_SCSV";
+ default:
+ return String.Format("UNKNOWN:0x{0:X4}", cipherSuite);
+ }
+ }
+
+ /*
+ * Get a human-readable name for a hash-and-sign algorithm.
+ */
+ public static string HashAndSignName(int hs)
+ {
+ switch (hs) {
+ case RSA_MD5: return "RSA_MD5";
+ case RSA_SHA1: return "RSA_SHA1";
+ case RSA_SHA224: return "RSA_SHA224";
+ case RSA_SHA256: return "RSA_SHA256";
+ case RSA_SHA384: return "RSA_SHA384";
+ case RSA_SHA512: return "RSA_SHA512";
+ case ECDSA_MD5: return "ECDSA_MD5";
+ case ECDSA_SHA1: return "ECDSA_SHA1";
+ case ECDSA_SHA224: return "ECDSA_SHA224";
+ case ECDSA_SHA256: return "ECDSA_SHA256";
+ case ECDSA_SHA384: return "ECDSA_SHA384";
+ case ECDSA_SHA512: return "ECDSA_SHA512";
+ default:
+ return String.Format("UNKNOWN:0x{0:X4}", hs);
+ }
+ }
+
+ /*
+ * Get a human-readable name for a curve.
+ */
+ public static string CurveName(int id)
+ {
+ switch (id) {
+ case Curve25519: return "Curve25519";
+ case NIST_P256: return "NIST_P256";
+ case NIST_P384: return "NIST_P384";
+ case NIST_P521: return "NIST_P521";
+ default:
+ return String.Format("UNKNOWN:0x{0:X4}", id);
+ }
+ }
+
+ /*
+ * Extract the public key from an encoded X.509 certificate.
+ * This does NOT make any attempt at validating the certificate.
+ */
+ internal static IPublicKey GetKeyFromCert(byte[] cert)
+ {
+ AsnElt ae = AsnElt.Decode(cert);
+ ae.CheckTag(AsnElt.SEQUENCE);
+ ae.CheckNumSub(3);
+ ae = ae.GetSub(0);
+ ae.CheckTag(AsnElt.SEQUENCE);
+ ae.CheckNumSubMin(6);
+ int off = 5;
+ if (ae.GetSub(0).TagValue != AsnElt.INTEGER) {
+ ae.CheckNumSubMin(7);
+ off ++;
+ }
+ return KF.DecodePublicKey(ae.GetSub(off));
+ }
+
+ internal static bool IsRSA(int cs)
+ {
+ switch (cs) {
+ case RSA_WITH_RC4_128_MD5:
+ case RSA_WITH_RC4_128_SHA:
+ case RSA_WITH_3DES_EDE_CBC_SHA:
+ case RSA_WITH_AES_128_CBC_SHA:
+ case RSA_WITH_AES_256_CBC_SHA:
+ case RSA_WITH_AES_128_CBC_SHA256:
+ case RSA_WITH_AES_256_CBC_SHA256:
+ case RSA_WITH_AES_128_GCM_SHA256:
+ case RSA_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsDH_DSA(int cs)
+ {
+ switch (cs) {
+ case DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case DH_DSS_WITH_AES_128_CBC_SHA:
+ case DH_DSS_WITH_AES_256_CBC_SHA:
+ case DH_DSS_WITH_AES_128_CBC_SHA256:
+ case DH_DSS_WITH_AES_256_CBC_SHA256:
+ case DH_DSS_WITH_AES_128_GCM_SHA256:
+ case DH_DSS_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsDH_RSA(int cs)
+ {
+ switch (cs) {
+ case DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case DH_RSA_WITH_AES_128_CBC_SHA:
+ case DH_RSA_WITH_AES_256_CBC_SHA:
+ case DH_RSA_WITH_AES_128_CBC_SHA256:
+ case DH_RSA_WITH_AES_256_CBC_SHA256:
+ case DH_RSA_WITH_AES_128_GCM_SHA256:
+ case DH_RSA_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsDH(int cs)
+ {
+ return IsDH_DSA(cs) || IsDH_RSA(cs);
+ }
+
+ internal static bool IsDHE_DSS(int cs)
+ {
+ switch (cs) {
+ case DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case DHE_DSS_WITH_AES_128_CBC_SHA:
+ case DHE_DSS_WITH_AES_256_CBC_SHA:
+ case DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsDHE_RSA(int cs)
+ {
+ switch (cs) {
+ case DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case DHE_RSA_WITH_AES_128_CBC_SHA:
+ case DHE_RSA_WITH_AES_256_CBC_SHA:
+ case DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsECDH_ECDSA(int cs)
+ {
+ switch (cs) {
+ case ECDH_ECDSA_WITH_NULL_SHA:
+ case ECDH_ECDSA_WITH_RC4_128_SHA:
+ case ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsECDH_RSA(int cs)
+ {
+ switch (cs) {
+ case ECDH_RSA_WITH_NULL_SHA:
+ case ECDH_RSA_WITH_RC4_128_SHA:
+ case ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsECDH(int cs)
+ {
+ return IsECDH_ECDSA(cs) || IsECDH_RSA(cs);
+ }
+
+ internal static bool IsECDHE_ECDSA(int cs)
+ {
+ switch (cs) {
+ case ECDHE_ECDSA_WITH_NULL_SHA:
+ case ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsECDHE_RSA(int cs)
+ {
+ switch (cs) {
+ case ECDHE_RSA_WITH_NULL_SHA:
+ case ECDHE_RSA_WITH_RC4_128_SHA:
+ case ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsECDHE(int cs)
+ {
+ return IsECDHE_RSA(cs) || IsECDHE_ECDSA(cs);
+ }
+
+ internal static bool IsSHA384(int cs)
+ {
+ switch (cs) {
+ case RSA_WITH_AES_256_GCM_SHA384:
+ case DH_DSS_WITH_AES_256_GCM_SHA384:
+ case DH_RSA_WITH_AES_256_GCM_SHA384:
+ case DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static bool IsTLS12(int cs)
+ {
+ switch (cs) {
+ case RSA_WITH_NULL_SHA256:
+ case RSA_WITH_AES_128_CBC_SHA256:
+ case RSA_WITH_AES_256_CBC_SHA256:
+ case DH_DSS_WITH_AES_128_CBC_SHA256:
+ case DH_RSA_WITH_AES_128_CBC_SHA256:
+ case DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case DH_DSS_WITH_AES_256_CBC_SHA256:
+ case DH_RSA_WITH_AES_256_CBC_SHA256:
+ case DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case DH_anon_WITH_AES_128_CBC_SHA256:
+ case DH_anon_WITH_AES_256_CBC_SHA256:
+ case RSA_WITH_AES_128_GCM_SHA256:
+ case RSA_WITH_AES_256_GCM_SHA384:
+ case DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case DH_RSA_WITH_AES_128_GCM_SHA256:
+ case DH_RSA_WITH_AES_256_GCM_SHA384:
+ case DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case DH_DSS_WITH_AES_128_GCM_SHA256:
+ case DH_DSS_WITH_AES_256_GCM_SHA384:
+ case DH_anon_WITH_AES_128_GCM_SHA256:
+ case DH_anon_WITH_AES_256_GCM_SHA384:
+ case ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case PSK_WITH_CHACHA20_POLY1305_SHA256:
+ case ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+ case DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+ case RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal static PRF GetPRFForTLS12(int cs)
+ {
+ return new PRF(IsSHA384(cs)
+ ? (IDigest)new SHA384()
+ : (IDigest)new SHA256());
+ }
+
+ internal static ECCurve GetCurveByID(int id)
+ {
+ switch (id) {
+ case NIST_P256: return EC.P256;
+ case NIST_P384: return EC.P384;
+ case NIST_P521: return EC.P521;
+ case Curve25519: return EC.Curve25519;
+ default:
+ throw new SSLException("Unknown curve: " + id);
+ }
+ }
+
+ /*
+ * Get ID for a curve. This returns -1 if the curve is not
+ * recognised.
+ */
+ internal static int CurveToID(ECCurve curve)
+ {
+ switch (curve.Name) {
+ case "P-256": return SSL.NIST_P256;
+ case "P-384": return SSL.NIST_P384;
+ case "P-521": return SSL.NIST_P521;
+ case "Curve25519": return SSL.Curve25519;
+ default:
+ return -1;
+ }
+ }
+
+ internal static IDigest GetHashByID(int id)
+ {
+ switch (id) {
+ case 1: return new MD5();
+ case 2: return new SHA1();
+ case 3: return new SHA224();
+ case 4: return new SHA256();
+ case 5: return new SHA384();
+ case 6: return new SHA512();
+ default:
+ throw new SSLException("Unknown hash: " + id);
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Class for a SSL client connection.
+ *
+ * An instance is created over a specified transport stream. SSL session
+ * parameters from a previous connection are optionally specified, to
+ * attempt session resumption. The instance handles the connection but
+ * cannot be "revived" after the connection was closed (the session
+ * parameters, though, can be extracted and used with another instance).
+ */
+
+public class SSLClient : SSLEngine {
+
+ /*
+ * Create the client over the provided stream. No attempt at
+ * session resumption will be made.
+ */
+ public SSLClient(Stream sub) : this(sub, null)
+ {
+ }
+
+ /*
+ * Create the client over the provided stream. If the
+ * 'sessionParameters' are not null, then the client will try to
+ * resume that session. Note that session parameters may include
+ * a "target server name", in which case the ServerName
+ * property will be set to that name, which will be included
+ * in the ClientHello as the Server Name Indication extension.
+ */
+ public SSLClient(Stream sub, SSLSessionParameters sessionParameters)
+ : base(sub)
+ {
+ sentExtensions = new List<int>();
+ resumeParams = sessionParameters;
+ if (resumeParams != null) {
+ string name = resumeParams.ServerName;
+ if (name != null) {
+ ServerName = name;
+ }
+ }
+ }
+
+ /*
+ * Validator for the server certificate. This callback is
+ * responsible for obtaining the server's public key and making
+ * sure it is the right one. A normal, standard-compliant
+ * implementation should do the following:
+ *
+ * -- Validate the certificate as X.509 mandates (building
+ * a path to a trust anchor, and verifying all signatures, names
+ * and appropriate certificate extensions; also obtaining
+ * proper CRL or OCSP response for a fresh revocation status).
+ *
+ * -- Check that the intended server name (provided in the
+ * 'serverName' parameter) matches that which is found in the
+ * certificate (see RFC 2818 section 3.1 for details; also
+ * consider RFC 6125 section 6.4).
+ *
+ * -- Return the public key found in the server's certificate,
+ * along with its allowed usages (which may depend on the
+ * KeyUsage extensions found in the certificate).
+ *
+ * The certificate chain, as received from the server, is
+ * provided as parameter; it is non-empty (it contains at least
+ * one certificate). The server's certificate is the first one
+ * in the chain.
+ *
+ * The 'serverName' parameter is the intended server name, to
+ * match against the names found in the certificate. If it is
+ * null, then no matching is expected (this correspond to the
+ * ServerName property in this SSLClient instance).
+ *
+ * The 'usage' variable shall be set to a value that qualifies
+ * whether the key may be used for encryption and/or signatures.
+ */
+ public delegate IPublicKey CertValidator(
+ byte[][] chain, string serverName, out KeyUsage usage);
+ public CertValidator ServerCertValidator {
+ get; set;
+ }
+
+ /*
+ * A simple INSECURE certificate "validator" that does not validate
+ * anything: the public key is extracted and returned, with no
+ * other checks. THIS IS FOR TESTS ONLY. Using this validator
+ * basically voids all security properties of SSL.
+ */
+ public static IPublicKey InsecureCertValidator(
+ byte[][] chain, string serverName, out KeyUsage usage)
+ {
+ usage = KeyUsage.EncryptAndSign;
+ return SSL.GetKeyFromCert(chain[0]);
+ }
+
+ List<int> sentExtensions;
+ SSLSessionParameters resumeParams;
+
+ internal override bool IsClient {
+ get {
+ return true;
+ }
+ }
+
+ internal override bool DoHandshake()
+ {
+ CheckConfigHashAndSign();
+
+ ResetHashes();
+ MakeRandom(clientRandom);
+
+ SendClientHello();
+ FlushSub();
+
+ bool resume;
+ if (!ParseServerHello(out resume)) {
+ return false;
+ }
+ HandshakeCount ++;
+ if (resume) {
+ /*
+ * Abbreviated handshake.
+ */
+ ParseCCSAndFinished();
+ SendCCSAndFinished();
+ FlushSub();
+ SetAppData();
+ IsResume = true;
+ return true;
+ }
+
+ KeyUsage usage;
+ IPublicKey pkey = ParseCertificate(out usage);
+
+ ECCurve curve;
+ byte[] serverPoint;
+ if (SSL.IsECDHE_RSA(CipherSuite)) {
+ if (!(pkey is RSAPublicKey)) {
+ throw new SSLException(
+ "ECDHE_RSA needs a RSA public key");
+ }
+ if (usage != KeyUsage.SignOnly
+ && usage != KeyUsage.EncryptAndSign)
+ {
+ throw new SSLException("Server public key"
+ + " unfit for signatures");
+ }
+ serverPoint = ParseServerKeyExchange(out curve, pkey);
+ } else if (SSL.IsECDHE_ECDSA(CipherSuite)) {
+ if (!(pkey is ECPublicKey)) {
+ throw new SSLException(
+ "ECDHE_ECDSA needs an EC public key");
+ }
+ if (usage != KeyUsage.SignOnly
+ && usage != KeyUsage.EncryptAndSign)
+ {
+ throw new SSLException("Server public key"
+ + " unfit for signatures");
+ }
+ serverPoint = ParseServerKeyExchange(out curve, pkey);
+ } else {
+ curve = null;
+ serverPoint = null;
+ }
+
+ bool reqClientCert = false;
+ int mt;
+ byte[] msg = ReadHandshakeMessage(out mt);
+ if (mt == SSL.CERTIFICATE_REQUEST) {
+ /*
+ * FIXME: parse message and select a client
+ * certificate.
+ */
+ reqClientCert = true;
+ msg = ReadHandshakeMessage(out mt);
+ }
+ if (mt != SSL.SERVER_HELLO_DONE) {
+ throw new SSLException(string.Format("Unexpected"
+ + " handshake message {0}"
+ + " (expected: ServerHelloDone)", mt));
+ }
+ if (msg.Length != 0) {
+ throw new SSLException(
+ "Invalid ServerHelloDone (not empty)");
+ }
+
+ if (reqClientCert) {
+ /*
+ * FIXME: right now, we send an empty Certificate
+ * message if the server asks for a client
+ * certificate; i.e., we claim we have none.
+ */
+ MemoryStream ms =
+ StartHandshakeMessage(SSL.CERTIFICATE);
+ EndHandshakeMessage(ms);
+ }
+
+ if (SSL.IsRSA(CipherSuite)) {
+ if (!(pkey is RSAPublicKey)) {
+ throw new SSLException(
+ "Server public key is not RSA");
+ }
+ if (usage != KeyUsage.EncryptOnly
+ && usage != KeyUsage.EncryptAndSign)
+ {
+ throw new SSLException("Server public key is"
+ + " not allowed for encryption");
+ }
+ SendClientKeyExchangeRSA(pkey as RSAPublicKey);
+ } else if (SSL.IsECDH(CipherSuite)) {
+ if (!(pkey is ECPublicKey)) {
+ throw new SSLException(
+ "Server public key is not EC");
+ }
+ if (usage != KeyUsage.EncryptOnly
+ && usage != KeyUsage.EncryptAndSign)
+ {
+ throw new SSLException("Server public key is"
+ + " not allowed for key exchange");
+ }
+ SendClientKeyExchangeECDH(pkey as ECPublicKey);
+ } else if (serverPoint != null) {
+ SendClientKeyExchangeECDH(curve, serverPoint);
+ } else {
+ /*
+ * TODO: Maybe support DHE cipher suites?
+ */
+ throw new Exception("NYI");
+ }
+
+ /*
+ * FIXME: when client certificates are supported, we
+ * will need to send a CertificateVerify message here.
+ */
+
+ SendCCSAndFinished();
+ FlushSub();
+
+ ParseCCSAndFinished();
+ SetAppData();
+ IsResume = false;
+
+ return true;
+ }
+
+ void SendClientHello()
+ {
+ MemoryStream ms = StartHandshakeMessage(SSL.CLIENT_HELLO);
+
+ // Maximum supported protocol version.
+ IO.Write16(ms, VersionMax);
+
+ // Client random.
+ ms.Write(clientRandom, 0, clientRandom.Length);
+
+ // Session ID.
+ if (resumeParams != null) {
+ byte[] id = resumeParams.SessionID;
+ ms.WriteByte((byte)id.Length);
+ ms.Write(id, 0, id.Length);
+ } else {
+ ms.WriteByte(0x00);
+ }
+
+ // List of supported cipher suites.
+ int csLen = SupportedCipherSuites.Length << 1;
+ int extraCS = GetQuirkInt("sendExtraCipherSuite", -1);
+ if (extraCS >= 0) {
+ csLen += 2;
+ }
+ IO.Write16(ms, csLen);
+ foreach (int cs in SupportedCipherSuites) {
+ IO.Write16(ms, cs);
+ }
+ if (extraCS >= 0) {
+ IO.Write16(ms, extraCS);
+ }
+
+ // List of supported compression algorithms.
+ ms.WriteByte(0x01);
+ ms.WriteByte(0x00);
+
+ // Extensions
+ sentExtensions.Clear();
+ MemoryStream chExt = new MemoryStream();
+
+ // Server Name Indication
+ if (ServerName != null && ServerName.Length > 0) {
+ byte[] encName = Encoding.UTF8.GetBytes(ServerName);
+ int elen = encName.Length;
+ if (elen > 65530) {
+ throw new SSLException("Oversized server name");
+ }
+ sentExtensions.Add(0x0000);
+ IO.Write16(chExt, 0x0000); // extension type
+ IO.Write16(chExt, elen + 5); // extension length
+ IO.Write16(chExt, elen + 3); // name list length
+ chExt.WriteByte(0x00); // name type
+ IO.Write16(chExt, elen); // name length
+ chExt.Write(encName, 0, elen);
+ }
+
+ // Supported Curves and Supported Point Formats
+ if (SupportedCurves != null && SupportedCurves.Length > 0) {
+ int len = SupportedCurves.Length;
+ sentExtensions.Add(0x000A);
+ IO.Write16(chExt, 0x000A);
+ IO.Write16(chExt, (len << 1) + 2);
+ IO.Write16(chExt, len << 1);
+ foreach (int cc in SupportedCurves) {
+ IO.Write16(chExt, cc);
+ }
+
+ sentExtensions.Add(0x000B);
+ IO.Write16(chExt, 0x000B);
+ IO.Write16(chExt, 2);
+ chExt.WriteByte(1);
+ chExt.WriteByte(0x00);
+ }
+
+ // Supported Signatures
+ if (VersionMax >= SSL.TLS12 && SupportedHashAndSign != null
+ && SupportedHashAndSign.Length > 0)
+ {
+ sentExtensions.Add(0x000D);
+ IO.Write16(chExt, 0x000D);
+ int num = SupportedHashAndSign.Length;
+ IO.Write16(chExt, 2 + (num << 1));
+ IO.Write16(chExt, num << 1);
+ foreach (int hs in SupportedHashAndSign) {
+ IO.Write16(chExt, hs);
+ }
+ }
+
+ // Secure renegotiation
+ if (!GetQuirkBool("noSecureReneg")) {
+ sentExtensions.Add(0xFF01);
+ IO.Write16(chExt, 0xFF01);
+ byte[] exv;
+ if (renegSupport > 0) {
+ exv = savedClientFinished;
+ } else {
+ exv = new byte[0];
+ }
+
+ if (GetQuirkBool("forceEmptySecureReneg")) {
+ exv = new byte[0];
+ } else if (GetQuirkBool("forceNonEmptySecureReneg")) {
+ exv = new byte[12];
+ } else if (GetQuirkBool("alterNonEmptySecureReneg")) {
+ if (exv.Length > 0) {
+ exv[exv.Length - 1] ^= 0x01;
+ }
+ } else if (GetQuirkBool("oversizedSecureReneg")) {
+ exv = new byte[255];
+ }
+
+ IO.Write16(chExt, exv.Length + 1);
+ chExt.WriteByte((byte)exv.Length);
+ chExt.Write(exv, 0, exv.Length);
+ }
+
+ // Extra extension with random contents.
+ int extraExt = GetQuirkInt("sendExtraExtension", -1);
+ if (extraExt >= 0) {
+ byte[] exv = new byte[extraExt >> 16];
+ RNG.GetBytes(exv);
+ IO.Write16(chExt, extraExt & 0xFFFF);
+ IO.Write16(chExt, exv.Length);
+ chExt.Write(exv, 0, exv.Length);
+ }
+
+ // Max Fragment Length
+ // ALPN
+ // FIXME
+
+ byte[] encExt = chExt.ToArray();
+ if (encExt.Length > 0) {
+ if (encExt.Length > 65535) {
+ throw new SSLException("Oversized extensions");
+ }
+ IO.Write16(ms, encExt.Length);
+ ms.Write(encExt, 0, encExt.Length);
+ }
+
+ EndHandshakeMessage(ms);
+ }
+
+ bool ParseServerHello(out bool resume)
+ {
+ resume = false;
+
+ int mt;
+ byte[] msg = ReadHandshakeMessage(out mt, FirstHandshakeDone);
+ if (msg == null) {
+ /*
+ * Server denies attempt explicitly.
+ */
+ return false;
+ }
+ if (mt != SSL.SERVER_HELLO) {
+ throw new SSLException(string.Format("Unexpected"
+ + " handshake message {0} (expecting a"
+ + " ServerHello)", mt));
+ }
+
+ if (msg.Length < 38) {
+ throw new SSLException("Truncated ServerHello");
+ }
+ Version = IO.Dec16be(msg, 0);
+ if (Version < VersionMin || Version > VersionMax) {
+ throw new SSLException(string.Format(
+ "Unsupported version: 0x{0:X4}", Version));
+ }
+ Array.Copy(msg, 2, serverRandom, 0, 32);
+ int idLen = msg[34];
+ if (idLen > 32) {
+ throw new SSLException("Invalid session ID length");
+ }
+ if (idLen + 38 > msg.Length) {
+ throw new SSLException("Truncated ServerHello");
+ }
+ sessionID = new byte[idLen];
+ Array.Copy(msg, 35, sessionID, 0, idLen);
+ int off = 35 + idLen;
+
+ /*
+ * Cipher suite. It must be one of the suites we sent.
+ */
+ CipherSuite = IO.Dec16be(msg, off);
+ off += 2;
+ bool found = false;
+ foreach (int cs in SupportedCipherSuites) {
+ if (cs == SSL.FALLBACK_SCSV
+ || cs == SSL.EMPTY_RENEGOTIATION_INFO_SCSV)
+ {
+ continue;
+ }
+ if (cs == CipherSuite) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new SSLException(string.Format(
+ "Server selected cipher suite 0x{0:X4}"
+ + " which we did not advertise", CipherSuite));
+ }
+
+ /*
+ * Compression. Must be 0, since we do not support it.
+ */
+ int comp = msg[off ++];
+ if (comp != 0x00) {
+ throw new SSLException(string.Format(
+ "Server selected compression {0}"
+ + " which we did not advertise", comp));
+ }
+
+ /*
+ * Extensions. Each extension from the server should
+ * correspond to an extension sent in the ClientHello.
+ */
+ bool secReneg = false;
+ if (msg.Length > off) {
+ if (msg.Length == off + 1) {
+ throw new SSLException("Truncated ServerHello");
+ }
+ int tlen = IO.Dec16be(msg, off);
+ off += 2;
+ if (tlen != msg.Length - off) {
+ throw new SSLException(
+ "Invalid extension list length");
+ }
+ while (off < msg.Length) {
+ if ((off + 4) > msg.Length) {
+ throw new SSLException(
+ "Truncated extention");
+ }
+ int etype = IO.Dec16be(msg, off);
+ int elen = IO.Dec16be(msg, off + 2);
+ off += 4;
+ if (elen > msg.Length - off) {
+ throw new SSLException(
+ "Truncated extention");
+ }
+ if (!sentExtensions.Contains(etype)) {
+ throw new SSLException(string.Format(
+ "Server send unadvertised"
+ + " extenstion 0x{0:X4}",
+ etype));
+ }
+
+ /*
+ * We have some processing to do on some
+ * specific server-side extensions.
+ */
+ switch (etype) {
+
+ case 0xFF01:
+ secReneg = true;
+ ParseExtSecureReneg(msg, off, elen);
+ break;
+ }
+
+ off += elen;
+ }
+ }
+
+ /*
+ * Renegotiation support: if we did not get the extension
+ * from the server, then secure renegotiation is
+ * definitely not supported. If it _was_ known as
+ * being supported (from a previous handshake) then this
+ * is a fatal error.
+ */
+ if (!secReneg) {
+ if (renegSupport > 0) {
+ throw new SSLException("Missing Secure"
+ + " Renegotiation extension");
+ }
+ renegSupport = -1;
+ }
+
+ /*
+ * Check whether this is a session resumption: a session
+ * is resumed if we sent a non-empty session ID, and the
+ * ServerHello contained the same session ID.
+ *
+ * In case of resumption, the ServerHello must use the
+ * same version and cipher suite than in the saved
+ * parameters.
+ */
+ if (resumeParams != null) {
+ byte[] id = resumeParams.SessionID;
+ if (id.Length > 0 && IO.Eq(id, sessionID)) {
+ if (Version != resumeParams.Version) {
+ throw new SSLException(
+ "Resume version mismatch");
+ }
+ if (CipherSuite != resumeParams.CipherSuite) {
+ throw new SSLException(
+ "Resume cipher suite mismatch");
+ }
+ SetMasterSecret(resumeParams.MasterSecret);
+ resume = true;
+ }
+ }
+
+ return true;
+ }
+
+ void ParseExtSecureReneg(byte[] buf, int off, int len)
+ {
+ if (len < 1 || len != 1 + buf[off]) {
+ throw new SSLException(
+ "Invalid Secure Renegotiation extension");
+ }
+ len --;
+ off ++;
+
+ if (renegSupport == 0) {
+ /*
+ * Initial handshake: extension MUST be empty.
+ */
+ if (len != 0) {
+ throw new SSLException(
+ "Non-empty Secure Renegotation"
+ + " on initial handshake");
+ }
+ renegSupport = 1;
+ } else {
+ /*
+ * Renegotiation: extension MUST contain the
+ * concatenation of the saved client and
+ * server Finished messages (in that order).
+ */
+ if (len != 24) {
+ throw new SSLException(
+ "Wrong Secure Renegotiation value");
+ }
+ int z = 0;
+ for (int i = 0; i < 12; i ++) {
+ z |= savedClientFinished[i] ^ buf[off + i];
+ z |= savedServerFinished[i] ^ buf[off + 12 + i];
+ }
+ if (z != 0) {
+ throw new SSLException(
+ "Wrong Secure Renegotiation value");
+ }
+ }
+ }
+
+ IPublicKey ParseCertificate(out KeyUsage usage)
+ {
+ byte[] msg = ReadHandshakeMessageExpected(SSL.CERTIFICATE);
+ if (msg.Length < 3) {
+ throw new SSLException("Invalid Certificate message");
+ }
+ int tlen = IO.Dec24be(msg, 0);
+ int off = 3;
+ if (tlen != msg.Length - off) {
+ throw new SSLException("Invalid Certificate message");
+ }
+ List<byte[]> certs = new List<byte[]>();
+ while (off < msg.Length) {
+ if (msg.Length - off < 3) {
+ throw new SSLException(
+ "Invalid Certificate message");
+ }
+ int clen = IO.Dec24be(msg, off);
+ off += 3;
+ if (clen > msg.Length - off) {
+ throw new SSLException(
+ "Invalid Certificate message");
+ }
+ byte[] ec = new byte[clen];
+ Array.Copy(msg, off, ec, 0, clen);
+ off += clen;
+ certs.Add(ec);
+ }
+
+ return ServerCertValidator(
+ certs.ToArray(), ServerName, out usage);
+ }
+
+ byte[] ParseServerKeyExchange(out ECCurve curve, IPublicKey pkey)
+ {
+ byte[] msg = ReadHandshakeMessageExpected(
+ SSL.SERVER_KEY_EXCHANGE);
+ if (msg.Length < 4) {
+ throw new SSLException(
+ "Invalid ServerKeyExchange message");
+ }
+ if (msg[0] != 0x03) {
+ throw new SSLException("Unsupported unnamed curve");
+ }
+ curve = SSL.GetCurveByID(IO.Dec16be(msg, 1));
+ int plen = msg[3];
+ int off = 4;
+ if (msg.Length - off < plen) {
+ throw new SSLException(
+ "Invalid ServerKeyExchange message");
+ }
+ byte[] point = new byte[plen];
+ Array.Copy(msg, off, point, 0, plen);
+ off += plen;
+ int slen = off;
+
+ int hashId, sigId;
+ if (Version >= SSL.TLS12) {
+ if (msg.Length - off < 2) {
+ throw new SSLException(
+ "Invalid ServerKeyExchange message");
+ }
+ hashId = msg[off ++];
+ if (hashId == 0) {
+ throw new SSLException(
+ "Invalid hash identifier");
+ }
+ sigId = msg[off ++];
+ } else {
+ if (pkey is RSAPublicKey) {
+ hashId = 0;
+ sigId = 1;
+ } else if (pkey is ECPublicKey) {
+ hashId = 2;
+ sigId = 3;
+ } else {
+ throw new SSLException(
+ "Unsupported signature key type");
+ }
+ }
+
+ if (msg.Length - off < 2) {
+ throw new SSLException(
+ "Invalid ServerKeyExchange message");
+ }
+ int sigLen = IO.Dec16be(msg, off);
+ off += 2;
+ if (sigLen != msg.Length - off) {
+ throw new SSLException(
+ "Invalid ServerKeyExchange message");
+ }
+ byte[] sig = new byte[sigLen];
+ Array.Copy(msg, off, sig, 0, sigLen);
+
+ byte[] hv;
+ if (hashId == 0) {
+ MD5 md5 = new MD5();
+ SHA1 sha1 = new SHA1();
+ md5.Update(clientRandom);
+ md5.Update(serverRandom);
+ md5.Update(msg, 0, slen);
+ sha1.Update(clientRandom);
+ sha1.Update(serverRandom);
+ sha1.Update(msg, 0, slen);
+ hv = new byte[36];
+ md5.DoFinal(hv, 0);
+ sha1.DoFinal(hv, 16);
+ } else {
+ IDigest h = SSL.GetHashByID(hashId);
+ h.Update(clientRandom);
+ h.Update(serverRandom);
+ h.Update(msg, 0, slen);
+ hv = h.DoFinal();
+ }
+
+ bool ok;
+ if (sigId == 1) {
+ RSAPublicKey rpk = pkey as RSAPublicKey;
+ if (rpk == null) {
+ throw new SSLException(
+ "Wrong public key type for RSA");
+ }
+ if (hashId == 0) {
+ ok = RSA.VerifyND(rpk, hv, sig);
+ } else {
+ byte[] head1, head2;
+
+ switch (hashId) {
+ case 1:
+ head1 = RSA.PKCS1_MD5;
+ head2 = RSA.PKCS1_MD5_ALT;
+ break;
+ case 2:
+ head1 = RSA.PKCS1_SHA1;
+ head2 = RSA.PKCS1_SHA1_ALT;
+ break;
+ case 3:
+ head1 = RSA.PKCS1_SHA224;
+ head2 = RSA.PKCS1_SHA224_ALT;
+ break;
+ case 4:
+ head1 = RSA.PKCS1_SHA256;
+ head2 = RSA.PKCS1_SHA256_ALT;
+ break;
+ case 5:
+ head1 = RSA.PKCS1_SHA384;
+ head2 = RSA.PKCS1_SHA384_ALT;
+ break;
+ case 6:
+ head1 = RSA.PKCS1_SHA512;
+ head2 = RSA.PKCS1_SHA512_ALT;
+ break;
+ default:
+ throw new SSLException(
+ "Unsupported hash algorithm: "
+ + hashId);
+ }
+ ok = RSA.Verify(rpk, head1, head2, hv, sig);
+ }
+ } else if (sigId == 3) {
+ ECPublicKey epk = pkey as ECPublicKey;
+ if (epk == null) {
+ throw new SSLException(
+ "Wrong public key type for ECDSA");
+ }
+ ok = ECDSA.Verify(epk, hv, sig);
+ } else {
+ throw new SSLException(
+ "Unsupported signature type: " + sigId);
+ }
+
+ if (!ok) {
+ throw new SSLException(
+ "Invalid signature on ServerKeyExchange");
+ }
+ return point;
+ }
+
+ void SendClientKeyExchangeRSA(RSAPublicKey pkey)
+ {
+ byte[] pms = new byte[48];
+ IO.Enc16be(Version, pms, 0);
+ RNG.GetBytes(pms, 2, pms.Length - 2);
+ byte[] epms = RSA.Encrypt(pkey, pms);
+ MemoryStream ms =
+ StartHandshakeMessage(SSL.CLIENT_KEY_EXCHANGE);
+ IO.Write16(ms, epms.Length);
+ ms.Write(epms, 0, epms.Length);
+ EndHandshakeMessage(ms);
+ ComputeMaster(pms);
+ }
+
+ void SendClientKeyExchangeECDH(ECPublicKey pkey)
+ {
+ ECCurve curve = pkey.Curve;
+ SendClientKeyExchangeECDH(curve, pkey.Pub);
+ }
+
+ void SendClientKeyExchangeECDH(ECCurve curve, byte[] pub)
+ {
+ byte[] k = curve.MakeRandomSecret();
+
+ /*
+ * Compute the point P = k*G that we will send.
+ */
+ byte[] P = curve.GetGenerator(false);
+ uint good = curve.Mul(P, k, P, false);
+
+ /*
+ * Compute the shared secret Q = k*Pub.
+ */
+ byte[] Q = new byte[P.Length];
+ good &= curve.Mul(pub, k, Q, false);
+
+ if (good == 0) {
+ /*
+ * This might happen only if the server's public
+ * key is not part of the proper subgroup. This
+ * cannot happen with NIST's "P" curves.
+ */
+ throw new SSLException("ECDH failed");
+ }
+
+ /*
+ * Send message.
+ */
+ MemoryStream ms =
+ StartHandshakeMessage(SSL.CLIENT_KEY_EXCHANGE);
+ ms.WriteByte((byte)P.Length);
+ ms.Write(P, 0, P.Length);
+ EndHandshakeMessage(ms);
+
+ /*
+ * Extract premaster secret.
+ */
+ int xlen;
+ int xoff = curve.GetXoff(out xlen);
+ byte[] pms = new byte[xlen];
+ Array.Copy(Q, xoff, pms, 0, xlen);
+ ComputeMaster(pms);
+ }
+
+ internal override void ProcessExtraHandshake()
+ {
+ /*
+ * If we receive a non-empty handshake message, then
+ * it should be an HelloRequest. Note that we expect
+ * the request not to be mixed with application data
+ * records (though it could be split).
+ */
+ ReadHelloRequests();
+
+ /*
+ * We accept to renegotiate only if the server supports
+ * the Secure Renegotiation extension.
+ */
+ if (renegSupport != 1) {
+ SendWarning(SSL.NO_RENEGOTIATION);
+ SetAppData();
+ return;
+ }
+
+ /*
+ * We do a new handshake. We explicitly refuse to reuse
+ * session parameters, because there is no point in
+ * renegotiation if this resumes the same session.
+ */
+ resumeParams = null;
+ DoHandshake();
+ }
+
+ internal override void PrepareRenegotiate()
+ {
+ /*
+ * Nothing to do to trigger a new handshake, the client
+ * just has to send the ClientHello right away.
+ */
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * This is the base class common to SSLClient and SSLServer.
+ */
+
+public abstract class SSLEngine : Stream {
+
+ Stream sub;
+ InputRecord inRec;
+ OutputRecord outRec;
+ int deferredAlert;
+ int state;
+ int versionMin;
+ int actualVersion;
+ bool receivedNoReneg;
+
+ /*
+ * Functions used to hash handshake messages.
+ */
+ MD5 md5;
+ SHA1 sha1;
+ SHA256 sha256;
+ SHA384 sha384;
+
+ /*
+ * State:
+ * STATE_HANDSHAKE expecting handshake message only
+ * STATE_CCS expecting Change Cipher Spec message only
+ * STATE_APPDATA expecting application data or handshake
+ * STATE_CLOSING expecting only alert (close_notify)
+ * STATE_CLOSED closed
+ */
+ internal const int STATE_CLOSED = 0;
+ internal const int STATE_HANDSHAKE = 1;
+ internal const int STATE_CCS = 2;
+ internal const int STATE_APPDATA = 3;
+ internal const int STATE_CLOSING = 4;
+
+ /*
+ * Default cipher suites.
+ */
+ static int[] DEFAULT_CIPHER_SUITES = {
+ SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
+ SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+ SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
+ SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+ SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA,
+
+ SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
+ SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256,
+ SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
+ SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384,
+ SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
+ SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256,
+ SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
+ SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384,
+ SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ SSL.ECDH_RSA_WITH_AES_128_CBC_SHA,
+ SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ SSL.ECDH_RSA_WITH_AES_256_CBC_SHA,
+
+ SSL.RSA_WITH_AES_128_GCM_SHA256,
+ SSL.RSA_WITH_AES_256_GCM_SHA384,
+ SSL.RSA_WITH_AES_128_CBC_SHA256,
+ SSL.RSA_WITH_AES_256_CBC_SHA256,
+ SSL.RSA_WITH_AES_128_CBC_SHA,
+ SSL.RSA_WITH_AES_256_CBC_SHA,
+
+ SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+ SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ SSL.RSA_WITH_3DES_EDE_CBC_SHA
+ };
+
+ /*
+ * Default curves.
+ */
+ static int[] DEFAULT_CURVES = {
+ SSL.Curve25519,
+ SSL.NIST_P256,
+ SSL.NIST_P384,
+ SSL.NIST_P521
+ };
+
+ /*
+ * Default hash and sign algorithms.
+ */
+ static int[] DEFAULT_HASHANDSIGN = {
+ SSL.ECDSA_SHA256,
+ SSL.RSA_SHA256,
+ SSL.ECDSA_SHA224,
+ SSL.RSA_SHA224,
+ SSL.ECDSA_SHA384,
+ SSL.RSA_SHA384,
+ SSL.ECDSA_SHA512,
+ SSL.RSA_SHA512,
+ SSL.ECDSA_SHA1,
+ SSL.RSA_SHA1
+ };
+
+ internal byte[] clientRandom;
+ internal byte[] serverRandom;
+ internal byte[] sessionID;
+
+ /*
+ * 'renegSupport' is one of:
+ * 0 initial handshake not done yet
+ * 1 peer supports secure renegotiation
+ * -1 peer does not support secure renegotiation
+ */
+ internal int renegSupport;
+ internal byte[] savedClientFinished;
+ internal byte[] savedServerFinished;
+
+ byte[] masterSecret;
+
+ /*
+ * Create a new engine over the provided transport stream.
+ */
+ public SSLEngine(Stream sub)
+ {
+ this.sub = sub;
+ inRec = new InputRecord(sub);
+ outRec = new OutputRecord(sub);
+ md5 = new MD5();
+ sha1 = new SHA1();
+ sha256 = new SHA256();
+ sha384 = new SHA384();
+ state = STATE_HANDSHAKE;
+ deferredAlert = -1;
+ AutoFlush = true;
+ CloseSub = true;
+ OnClose = null;
+ NoCloseNotify = false;
+ ClosedWithoutNotify = false;
+ MaximumHandshakeMessageLength = 65536;
+ SupportedCipherSuites = DEFAULT_CIPHER_SUITES;
+ SupportedCurves = DEFAULT_CURVES;
+ SupportedHashAndSign = DEFAULT_HASHANDSIGN;
+ VersionMin = SSL.TLS10;
+ VersionMax = SSL.TLS12;
+ AllowRenegotiation = true;
+ receivedNoReneg = false;
+ clientRandom = new byte[32];
+ serverRandom = new byte[32];
+ masterSecret = new byte[48];
+ HandshakeCount = 0;
+ }
+
+ /*
+ * If 'NormalizeIOError' is true, then I/O errors while writing
+ * on the underlying stream will be reported as a generic
+ * SSLException with message "Unexpected transport closure".
+ * This helps test code that expects an asynchronous abort that
+ * may be detected during a read of a write operation, depending
+ * on the exact timing. Default is false.
+ */
+ public bool NormalizeIOError {
+ get {
+ return outRec.NormalizeIOError;
+ }
+ set {
+ outRec.NormalizeIOError = value;
+ }
+ }
+
+ /*
+ * If 'AutoFlush' is true, then after every Write() or WriteByte()
+ * call, the current record is assembled and sent, leaving no
+ * buffered data yet to be sent. Default value is true.
+ */
+ public bool AutoFlush {
+ get; set;
+ }
+
+ /*
+ * If 'CloseSub' is true, then the underlying transport stream
+ * will be closed on normal closure or protocol failure. Default
+ * value is true. If a closure callback is set in 'OnClose',
+ * then this flag is ignored.
+ */
+ public bool CloseSub {
+ get; set;
+ }
+
+ /*
+ * A generic callback to be invoked when the SSL connection is
+ * closed. If a callback is specified here, then the 'CloseSub'
+ * flag is ignored; the callback is supposed to handle the
+ * closing of the transport stream, if necessary.
+ */
+ public delegate void CloseGen(Stream sub);
+ public CloseGen OnClose {
+ get; set;
+ }
+
+ /*
+ * If 'NoCloseNotify' is true, then lack of a close_notify from
+ * the peer before closing the transport stream will NOT be
+ * considered erroneous; i.e. it won't trigger an exception.
+ * If that situation arises, the ClosedWithoutNotify flag will
+ * return true, so the caller may still test for it.
+ *
+ * Not sending close_notify alerts before closing is a widespread
+ * practice, since it simplifies timeout management. It is unsafe,
+ * in that it allows truncation attacks, unless the application
+ * protocol is self-terminated (e.g. HTTP/1.1 is self-terminated,
+ * HTTP/0.9 is not).
+ *
+ * Default value is false: lack of close_notify triggers an
+ * exception.
+ */
+ public bool NoCloseNotify {
+ get; set;
+ }
+
+ /*
+ * The 'ClosedWithoutNotify' flag is set to true if the connection
+ * was closed abruptly (no close_notify alert), but this engine
+ * was configured to tolerate that situation ('NoCloseNotify' was
+ * set to true).
+ */
+ public bool ClosedWithoutNotify {
+ get; private set;
+ }
+
+ /*
+ * Maximum allowed size for a handshake message. The protocol
+ * allows for messages up to 16 megabytes, but these hardly
+ * make sense in practice. In the interest of avoiding
+ * memory-based denials of service, a lower limit can be set.
+ * If an incoming handshake message exceeds that size, then
+ * an exception is thrown. Default value is 65536.
+ */
+ public int MaximumHandshakeMessageLength {
+ get; set;
+ }
+
+ /*
+ * Set the cipher suites supported by this engine, in preference
+ * order (most preferred comes first). Default value should ensure
+ * maximum interoperability and good security and performance.
+ */
+ public int[] SupportedCipherSuites {
+ get; set;
+ }
+
+ /*
+ * Set the elliptic curves supported by this engine, for ECDH
+ * and ECDHE. The list is in preference order (most preferred
+ * comes first). Default list is Curve25519 followed by the
+ * usual NIST curves (P-256, P-384 and P-521, in that order).
+ */
+ public int[] SupportedCurves {
+ get; set;
+ }
+
+ /*
+ * Set the supported hash and sign algorithm combinations. Each
+ * value is a 16-bit integer: high byte is the hash algorithm
+ * identifier, while low byte is the signature algorithm identifier.
+ * List is ordered by preference order. Default list applies the
+ * following rules:
+ *
+ * - Hash function order is:
+ * SHA-256, SHA-224, SHA-384, SHA-512, SHA-1
+ *
+ * - For the same hash function, ECDSA is preferred over RSA.
+ *
+ * Note that the special RSA with MD5+SHA-1 shall not be part of
+ * this list. Its use is implicit when using TLS 1.0 or 1.1 with
+ * a cipher suite or client authentication that uses RSA signatures.
+ */
+ public int[] SupportedHashAndSign {
+ get; set;
+ }
+
+ /*
+ * Set the minimum supported protocol version. Default is TLS 1.0.
+ */
+ public int VersionMin {
+ get {
+ return versionMin;
+ }
+ set {
+ SetOutputRecordVersion(value);
+ versionMin = value;
+ }
+ }
+
+ /*
+ * Set the maximum supported protocol version. Default is TLS 1.2.
+ */
+ public int VersionMax {
+ get; set;
+ }
+
+ /*
+ * Get the actual protocol version. This is set only when the
+ * version is chosen, within the handshake.
+ */
+ public int Version {
+ get {
+ return actualVersion;
+ }
+ internal set {
+ actualVersion = value;
+ SetOutputRecordVersion(value);
+ }
+ }
+
+ /*
+ * Get the actual cipher suite. This is set only when it is chosen,
+ * within the handshake.
+ */
+ public int CipherSuite {
+ get; internal set;
+ }
+
+ /*
+ * Get or set the server name associated with this connection.
+ *
+ * On a client, the caller shall set that name; if non-null and
+ * non-empty, then it will be sent to the server as a SNI
+ * extension; it will moreover be matched against the names
+ * found in the server's certificate.
+ *
+ * On a server, this value is set to the host name received from
+ * the client as a SNI extension (if any).
+ */
+ public string ServerName {
+ get; set;
+ }
+
+ /*
+ * Get the current session parameters. If the initial handshake
+ * was not performed yet, then the handshake is performed now
+ * (thus, this call may trigger an exception in case of I/O or
+ * protocol error).
+ *
+ * The returned object is a freshly allocated copy, which is not
+ * impacted by further activity on this engine.
+ */
+ public SSLSessionParameters SessionParameters {
+ get {
+ if (state == STATE_HANDSHAKE) {
+ DoHandshakeWrapper();
+ }
+ return new SSLSessionParameters(sessionID, Version,
+ CipherSuite, ServerName, masterSecret);
+ }
+ }
+
+ /*
+ * Renegotiation support: if true, then renegotiations will be
+ * accepted (both explicit calls to Renegotiate(), and requests
+ * from the peer); if false, then all renegotiation attempts will
+ * be rejected.
+ *
+ * Default value is true. Regardless of the value of this flag,
+ * renegotiation attempts will be denied if the peer does not
+ * support the "Secure Renegotiation" extension (RFC 5746).
+ */
+ public bool AllowRenegotiation {
+ get; set;
+ }
+
+ /*
+ * Set "quirks" to alter engine behaviour. When null (which is the
+ * default), normal behaviour occurs.
+ */
+ public SSLQuirks Quirks {
+ get; set;
+ }
+
+ /*
+ * Get the current handshake count. This starts at 0 (before
+ * the initial handshake) and is incremented for each handshake.
+ * The increment occurs at the start of the handshake (after
+ * confirmation that a handshake will be indeed attempted, when
+ * doing a renegotiation).
+ */
+ public long HandshakeCount {
+ get; internal set;
+ }
+
+ /*
+ * Tell whether the last handshake was a session resumption or not.
+ * This flag is set at the end of the handshake.
+ */
+ public bool IsResume {
+ get; internal set;
+ }
+
+ /*
+ * Trigger a new handshake. If the initial handshake was not done
+ * yet, then it is performed at that point. Otherwise, this is
+ * a renegotiation attempt.
+ *
+ * Returned value is true if a new handshake happened, false
+ * otherwise. For the initial handshake, true is always returned
+ * (handshake failures trigger exceptions). For a renegotiation,
+ * a 'false' value may be returned if one of the following holds:
+ * - This engine was configured not to use renegotiations.
+ * - The peer does not support secure renegotiation.
+ * - A renegotiation was attempted, but denied by the peer.
+ */
+ public bool Renegotiate()
+ {
+ if (!FirstHandshakeDone) {
+ DoHandshakeWrapper();
+ return true;
+ }
+
+ if (!AllowRenegotiation) {
+ return false;
+ }
+ if (!GetQuirkBool("noSecureReneg") && renegSupport < 0) {
+ return false;
+ }
+ int rt = outRec.RecordType;
+ try {
+ PrepareRenegotiate();
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ if (!DoHandshakeWrapper()) {
+ outRec.RecordType = rt;
+ return false;
+ }
+ return true;
+ }
+
+ /* ============================================================ */
+ /*
+ * Stream standard API.
+ */
+
+ public override int ReadByte()
+ {
+ if (state == STATE_CLOSED) {
+ return -1;
+ }
+ CheckAppData();
+ try {
+ return ZRead();
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ public override int Read(byte[] buf, int off, int len)
+ {
+ if (state == STATE_CLOSED) {
+ return -1;
+ }
+ CheckAppData();
+ try {
+ return ZRead(buf, off, len);
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ public override void WriteByte(byte x)
+ {
+ CheckAppData();
+ try {
+ outRec.Write(x);
+ if (AutoFlush) {
+ outRec.Flush();
+ }
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ public override void Write(byte[] buf, int off, int len)
+ {
+ CheckAppData();
+ try {
+ outRec.Write(buf, off, len);
+ if (AutoFlush) {
+ outRec.Flush();
+ }
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ public override void Flush()
+ {
+ CheckAppData();
+ try {
+ outRec.Flush();
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ public override void Close()
+ {
+ Close(true);
+ }
+
+ void Close(bool expectCloseNotify)
+ {
+ if (state == STATE_CLOSED) {
+ return;
+ }
+ try {
+ if (state == STATE_APPDATA) {
+ SendWarning(SSL.CLOSE_NOTIFY);
+ state = STATE_CLOSING;
+ if (expectCloseNotify) {
+ if (!NextRecord()) {
+ return;
+ }
+ throw new SSLException(
+ "Peer does not want to close");
+ }
+ }
+ } catch {
+ // ignored
+ } finally {
+ MarkFailed();
+ }
+ }
+
+ public override long Seek(long off, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long len)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool CanRead {
+ get {
+ return state != STATE_CLOSED;
+ }
+ }
+
+ public override bool CanWrite {
+ get {
+ return state != STATE_CLOSED;
+ }
+ }
+
+ public override bool CanSeek {
+ get {
+ return false;
+ }
+ }
+
+ public override long Length {
+ get {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override long Position {
+ get {
+ throw new NotSupportedException();
+ }
+ set {
+ throw new NotSupportedException();
+ }
+ }
+
+ /* ============================================================ */
+
+ /*
+ * Test whether this engine is a client or a server.
+ */
+ internal abstract bool IsClient {
+ get;
+ }
+
+ /*
+ * Test the configuration of hash-and-sign with regards to
+ * cipher suites: if the list of cipher suites includes an
+ * ECDHE suite, then there must be at least one supported
+ * hash-and-sign with the corresponding signature type.
+ */
+ internal void CheckConfigHashAndSign()
+ {
+ /*
+ * The hash-and-sign are only for TLS 1.2.
+ */
+ if (VersionMax < SSL.TLS12) {
+ return;
+ }
+
+ /*
+ * If the list is empty then we will work over the default
+ * list inferred by the peer (no extension sent).
+ */
+ if (SupportedHashAndSign == null
+ || SupportedHashAndSign.Length == 0)
+ {
+ return;
+ }
+
+ bool needRSA = false;
+ bool needECDSA = false;
+ foreach (int cs in SupportedCipherSuites) {
+ if (SSL.IsECDHE_RSA(cs)) {
+ needRSA = true;
+ }
+ if (SSL.IsECDHE_ECDSA(cs)) {
+ needECDSA = true;
+ }
+ }
+ foreach (int hs in SupportedHashAndSign) {
+ int sa = hs & 0xFF;
+ if (needRSA && sa == SSL.RSA) {
+ needRSA = false;
+ }
+ if (needECDSA && sa == SSL.ECDSA) {
+ needECDSA = false;
+ }
+ }
+ if (needRSA) {
+ throw new SSLException("Incoherent configuration:"
+ + " supports ECDHE_RSA but no RSA signature"
+ + " (for TLS 1.2)");
+ }
+ if (needECDSA) {
+ throw new SSLException("Incoherent configuration:"
+ + " supports ECDHE_ECDSA but no ECDSA signature"
+ + " (for TLS 1.2)");
+ }
+ }
+
+ internal bool HasQuirk(string name)
+ {
+ return Quirks != null && Quirks.GetString(name, null) != null;
+ }
+
+ internal bool GetQuirkBool(string name)
+ {
+ return GetQuirkBool(name, false);
+ }
+
+ internal bool GetQuirkBool(string name, bool defaultValue)
+ {
+ if (Quirks == null) {
+ return false;
+ }
+ return Quirks.GetBoolean(name, defaultValue);
+ }
+
+ internal int GetQuirkInt(string name)
+ {
+ return GetQuirkInt(name, 0);
+ }
+
+ internal int GetQuirkInt(string name, int defaultValue)
+ {
+ if (Quirks == null) {
+ return defaultValue;
+ }
+ return Quirks.GetInteger(name, defaultValue);
+ }
+
+ internal string GetQuirkString(string name)
+ {
+ return GetQuirkString(name, null);
+ }
+
+ internal string GetQuirkString(string name, string defaultValue)
+ {
+ if (Quirks == null) {
+ return defaultValue;
+ }
+ return Quirks.GetString(name, defaultValue);
+ }
+
+ /*
+ * Test whether the first handshake has been done or not.
+ */
+ internal bool FirstHandshakeDone {
+ get {
+ return savedClientFinished != null;
+ }
+ }
+
+ /*
+ * Close the engine. No I/O may happen beyond this call.
+ */
+ internal void MarkFailed()
+ {
+ if (sub != null) {
+ try {
+ if (OnClose != null) {
+ OnClose(sub);
+ } else if (CloseSub) {
+ sub.Close();
+ }
+ } catch {
+ // ignored
+ }
+ sub = null;
+ }
+ state = STATE_CLOSED;
+ }
+
+ /*
+ * Check that the current state is not closed.
+ */
+ void CheckNotClosed()
+ {
+ if (state == STATE_CLOSED) {
+ throw new SSLException("Connection is closed");
+ }
+ }
+
+ /*
+ * Check that we are ready to exchange application data. A
+ * handshake is performed if necessary.
+ */
+ void CheckAppData()
+ {
+ CheckNotClosed();
+ if (!FirstHandshakeDone) {
+ DoHandshakeWrapper();
+ } else if (state != STATE_APPDATA) {
+ throw new SSLException(
+ "Connection not ready for application data");
+ }
+ }
+
+ /*
+ * Set the version for outgoing records.
+ */
+ internal void SetOutputRecordVersion(int version)
+ {
+ outRec.SetVersion(version);
+ }
+
+ /*
+ * Set the expected version for incoming records. This should be
+ * used by the server code just after parsing the ClientHello,
+ * because the client is supposed to send records matching the
+ * protocol version decided by the server and sent in the
+ * ServerHello.
+ *
+ * For a SSL client, this call in unnecessary because default
+ * behaviour is to look at the version of the first incoming
+ * record (containing the ServerHello for the server) and expect
+ * all subsequent records to have the same version.
+ */
+ internal void SetInputRecordVersion(int version)
+ {
+ inRec.SetExpectedVersion(version);
+ }
+
+ /*
+ * Flush the underlying record engine.
+ */
+ internal void FlushSub()
+ {
+ outRec.Flush();
+ }
+
+ /*
+ * Get next record. This returns false only if the connection
+ * turned out to be ended "properly".
+ *
+ * In all other cases, a record is obtained, and true is
+ * returned. It is possible that the record contains no unread
+ * data (it could be an empty record, or it could be an alert
+ * record whose contents are automatically processed).
+ */
+ internal bool NextRecord()
+ {
+ if (!inRec.NextRecord()) {
+ if (NoCloseNotify && (state == STATE_APPDATA
+ || state == STATE_CLOSING))
+ {
+ /*
+ * No close_notify, but we have been set
+ * to tolerate it.
+ */
+ ClosedWithoutNotify = true;
+ MarkFailed();
+ return false;
+ }
+ throw new SSLException("Unexpected transport closure");
+ }
+
+ /*
+ * We basically ignore empty records, regardless of state.
+ */
+ if (inRec.BufferedLength == 0) {
+ return true;
+ }
+
+ int rt = inRec.RecordType;
+ switch (rt) {
+
+ case SSL.ALERT:
+ /*
+ * Fatal alerts trigger an exception. Warnings are
+ * ignored, except close_notify and no_renegotiation.
+ */
+ while (inRec.BufferedLength > 0) {
+ int level = deferredAlert;
+ deferredAlert = -1;
+ if (level < 0) {
+ level = inRec.Read();
+ if (inRec.BufferedLength == 0) {
+ deferredAlert = level;
+ break;
+ }
+ }
+ int desc = inRec.Read();
+ if (level == SSL.FATAL) {
+ throw new SSLException(desc);
+ }
+ if (level != SSL.WARNING) {
+ throw new SSLException("Unknown"
+ + " alert level: " + level);
+ }
+ if (desc == SSL.CLOSE_NOTIFY) {
+ if (state == STATE_CLOSING) {
+ MarkFailed();
+ return false;
+ }
+ if (state != STATE_APPDATA) {
+ throw new SSLException(
+ "Unexpected closure");
+ }
+ Close(false);
+ return false;
+ } else if (desc == SSL.NO_RENEGOTIATION) {
+ receivedNoReneg = true;
+ }
+ }
+ return true;
+
+ case SSL.HANDSHAKE:
+ switch (state) {
+ case STATE_HANDSHAKE:
+ return true;
+ case STATE_APPDATA:
+ ProcessExtraHandshakeWrapper();
+ return true;
+ }
+ throw new SSLException("Unexpected handshake message");
+
+ case SSL.CHANGE_CIPHER_SPEC:
+ if (state == STATE_CCS) {
+ return true;
+ }
+ throw new SSLException("Unexpected Change Cipher Spec");
+
+ case SSL.APPLICATION_DATA:
+ if (state == STATE_APPDATA) {
+ return true;
+ }
+ throw new SSLException("Unexpected application data");
+
+ default:
+ throw new SSLException("Invalid record type: " + rt);
+
+ }
+ }
+
+ /*
+ * ZRead() reads the next byte, possibly obtaining further records
+ * to do so. It may return -1 only if the end-of-stream was reached,
+ * which can happen only in "application data" state (after a
+ * successful handshake).
+ */
+ int ZRead()
+ {
+ int x = ZReadNoHash();
+ if (x >= 0 && inRec.RecordType == SSL.HANDSHAKE) {
+ HashExtra((byte)x);
+ }
+ return x;
+ }
+
+ /*
+ * ZReadNoHash() is similar to ZRead() except that it skips
+ * the automatic hashing of handshake messages.
+ */
+ int ZReadNoHash()
+ {
+ while (inRec.BufferedLength == 0) {
+ if (!NextRecord()) {
+ return -1;
+ }
+ }
+ return inRec.Read();
+ }
+
+ /*
+ * ZReadNoHashNoReneg() is similar to ZReadNoHash() except
+ * that it may return -1 while being in state STATE_HANDSHAKE
+ * in case a no_renegotiation alert is received.
+ */
+ int ZReadNoHashNoReneg()
+ {
+ while (inRec.BufferedLength == 0) {
+ receivedNoReneg = false;
+ if (!NextRecord() || receivedNoReneg) {
+ return -1;
+ }
+ }
+ return inRec.Read();
+ }
+
+ /*
+ * Read some bytes. At least one byte will be obtained, unless
+ * EOF is reached. Extra records are obtained if necessary.
+ */
+ int ZRead(byte[] buf)
+ {
+ return ZRead(buf, 0, buf.Length);
+ }
+
+ /*
+ * Read some bytes. At least one byte will be obtained, unless
+ * EOF is reached. Extra records are obtained if necessary.
+ */
+ int ZRead(byte[] buf, int off, int len)
+ {
+ while (inRec.BufferedLength == 0) {
+ if (!NextRecord()) {
+ return 0;
+ }
+ }
+ int rlen = inRec.Read(buf, off, len);
+ if (rlen > 0 && inRec.RecordType == SSL.HANDSHAKE) {
+ md5.Update(buf, off, rlen);
+ sha1.Update(buf, off, rlen);
+ sha256.Update(buf, off, rlen);
+ sha384.Update(buf, off, rlen);
+ }
+ return rlen;
+ }
+
+ bool DoHandshakeWrapper()
+ {
+ /*
+ * Record split mode syntax: name:[types]
+ *
+ * 'name' is a symbolic name.
+ *
+ * 'types' is a comma-separated list of record types on
+ * which the splitting mode applies. Record types are
+ * numeric values (in decimal).
+ */
+ string splitMode = GetQuirkString("recordSplitMode");
+ if (splitMode != null) {
+ splitMode = splitMode.Trim();
+ int j = splitMode.IndexOf(':');
+ int m = 0;
+ if (j >= 0) {
+ string w = splitMode.Substring(j + 1);
+ foreach (string s in w.Split(',')) {
+ m |= 1 << Int32.Parse(s.Trim());
+ }
+ splitMode = splitMode.Substring(0, j).Trim();
+ }
+ switch (splitMode.ToLowerInvariant()) {
+ case "half":
+ m |= OutputRecord.MODE_SPLIT_HALF;
+ break;
+ case "zero_before":
+ m |= OutputRecord.MODE_SPLIT_ZERO_BEFORE;
+ break;
+ case "zero_half":
+ m |= OutputRecord.MODE_SPLIT_ZERO_HALF;
+ break;
+ case "one_start":
+ m |= OutputRecord.MODE_SPLIT_ONE_START;
+ break;
+ case "one_end":
+ m |= OutputRecord.MODE_SPLIT_ONE_END;
+ break;
+ case "multi_one":
+ m |= OutputRecord.MODE_SPLIT_MULTI_ONE;
+ break;
+ default:
+ throw new SSLException(string.Format(
+ "Bad recordSplitMode name: '{0}'",
+ splitMode));
+ }
+ outRec.SetSplitMode(m);
+ }
+
+ /*
+ * Triggers for extra empty records.
+ */
+ outRec.SetThresholdZeroHandshake(
+ GetQuirkInt("thresholdZeroHandshake"));
+ outRec.SetThresholdZeroAppData(
+ GetQuirkInt("thresholdZeroAppData"));
+
+ try {
+ for (;;) {
+ bool ret = DoHandshake();
+ if (!ret) {
+ return false;
+ }
+ /*
+ * There could be some extra handshake
+ * data lingering in the input buffer, in
+ * which case we must process it right away.
+ */
+ if (HasBufferedHandshake) {
+ ProcessExtraHandshakeWrapper();
+ }
+ return true;
+ }
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ void ProcessExtraHandshakeWrapper()
+ {
+ try {
+ while (HasBufferedHandshake) {
+ ProcessExtraHandshake();
+ }
+ } catch {
+ MarkFailed();
+ throw;
+ }
+ }
+
+ /*
+ * Set the state to the provided value.
+ */
+ internal void SetState(int state)
+ {
+ this.state = state;
+ }
+
+ /*
+ * Reset running hashes for handshake messages.
+ */
+ internal void ResetHashes()
+ {
+ md5.Reset();
+ sha1.Reset();
+ sha256.Reset();
+ sha384.Reset();
+ }
+
+ /*
+ * Inject a specific byte value in the hash functions for handshake
+ * messages.
+ */
+ void HashExtra(byte b)
+ {
+ md5.Update(b);
+ sha1.Update(b);
+ sha256.Update(b);
+ sha384.Update(b);
+ }
+
+ /*
+ * Run a handshake. This function normally returns true; it returns
+ * false only if the call was a renegotiation attempt AND it was
+ * denied by the peer.
+ */
+ internal abstract bool DoHandshake();
+
+ /*
+ * A non-empty handshake record has been received while we
+ * were in post-handshake "application data" state. This
+ * method should handle that message with the necessary
+ * actions; if it returns false then the caller will fail
+ * the connection with an exception.
+ */
+ internal abstract void ProcessExtraHandshake();
+
+ /*
+ * Perform the preparatory steps for a renegotiation. For a client,
+ * there are no such steps, so this call is a no-op. For a server,
+ * an HelloRequest should be sent.
+ */
+ internal abstract void PrepareRenegotiate();
+
+ /*
+ * Read the next handshake message. This also sets the state
+ * to STATE_HANDSHAKE.
+ */
+ internal byte[] ReadHandshakeMessage(out int msgType)
+ {
+ return ReadHandshakeMessage(out msgType, false);
+ }
+
+ /*
+ * Read the next handshake message. This also sets the state
+ * to STATE_HANDSHAKE. If tolerateNoReneg is true, then a
+ * received no_renegotiation alert interrupts the reading, in
+ * which case this function sets the state back to its previous
+ * value and returns null.
+ */
+ internal byte[] ReadHandshakeMessage(
+ out int msgType, bool tolerateNoReneg)
+ {
+ int oldState = state;
+ state = STATE_HANDSHAKE;
+
+ /*
+ * In STATE_HANDSHAKE, an unexpected closure is never
+ * tolerated, so ZRead() won't return -1.
+ */
+ for (;;) {
+ msgType = ZReadNoHashNoReneg();
+ if (msgType < 0) {
+ if (tolerateNoReneg) {
+ state = oldState;
+ return null;
+ }
+ continue;
+ }
+ if (msgType == SSL.HELLO_REQUEST && IsClient) {
+ /*
+ * Extra HelloRequest messages are ignored
+ * (as long as they are properly empty), and
+ * they don't contribute to the running hashes.
+ */
+ if (ZReadNoHash() != 0
+ || ZReadNoHash() != 0
+ || ZReadNoHash() != 0)
+ {
+ throw new SSLException(
+ "Non-empty HelloRequest");
+ }
+ continue;
+ }
+ HashExtra((byte)msgType);
+ int len = ZRead();
+ len = (len << 8) + ZRead();
+ len = (len << 8) + ZRead();
+ if (len > MaximumHandshakeMessageLength) {
+ throw new SSLException(
+ "Oversized handshake message: len="
+ + len);
+ }
+ byte[] buf = new byte[len];
+ int off = 0;
+ while (off < len) {
+ off += ZRead(buf, off, len - off);
+ }
+ return buf;
+ }
+ }
+
+ /*
+ * Read the next handshake message; fail (with an exception) if
+ * it does not have the specified type. This also sets the state
+ * to STATE_HANDSHAKE.
+ */
+ internal byte[] ReadHandshakeMessageExpected(int msgType)
+ {
+ int rmt;
+ byte[] msg = ReadHandshakeMessage(out rmt);
+ if (rmt != msgType) {
+ throw new SSLException(string.Format("Unexpected"
+ + " handshake message {0} (expected: {1})",
+ rmt, msgType));
+ }
+ return msg;
+ }
+
+ /*
+ * Read an HelloRequest message. If, after reading an HelloRequest,
+ * the record is not empty, then other HelloRequest messages are
+ * read.
+ *
+ * This method shall be called only when a non-empty record of type
+ * handshake is buffered. It switches the state to STATE_HANDSHAKE.
+ */
+ internal void ReadHelloRequests()
+ {
+ state = STATE_HANDSHAKE;
+ while (inRec.BufferedLength > 0) {
+ int x = ZReadNoHash();
+ if (x != SSL.HELLO_REQUEST) {
+ throw new SSLException(
+ "Unexpected handshake message");
+ }
+ if (ZReadNoHash() != 0x00
+ || ZReadNoHash() != 0x00
+ || ZReadNoHash() != 0x00)
+ {
+ throw new SSLException(
+ "Non-empty HelloRequest");
+ }
+ }
+ }
+
+ /*
+ * Test whether there is some buffered handshake data.
+ */
+ internal bool HasBufferedHandshake {
+ get {
+ return inRec.RecordType == SSL.HANDSHAKE
+ && inRec.BufferedLength > 0;
+ }
+ }
+
+ static DateTime EPOCH =
+ new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ /*
+ * Create a new client or server random.
+ */
+ internal void MakeRandom(byte[] dst)
+ {
+ uint utc = (uint)((DateTime.UtcNow - EPOCH).Ticks / 10000000);
+ IO.Enc32be(utc, dst, 0);
+ RNG.GetBytes(dst, 4, dst.Length - 4);
+ }
+
+ /*
+ * Create a MemoryStream with preloaded 4-byte handshake message
+ * header.
+ */
+ internal MemoryStream StartHandshakeMessage(int type)
+ {
+ MemoryStream ms = new MemoryStream();
+ ms.WriteByte((byte)type);
+ IO.Write24(ms, 0);
+ return ms;
+ }
+
+ /*
+ * Finalise a handshake message, and send it.
+ */
+ internal void EndHandshakeMessage(MemoryStream ms)
+ {
+ byte[] buf = ms.ToArray();
+ IO.Enc24be(buf.Length - 4, buf, 1);
+ outRec.RecordType = SSL.HANDSHAKE;
+ outRec.Write(buf);
+ md5.Update(buf);
+ sha1.Update(buf);
+ sha256.Update(buf);
+ sha384.Update(buf);
+ }
+
+ /*
+ * Get the PRF corresponding to the negotiated protocol version
+ * and cipher suite.
+ */
+ internal PRF GetPRF()
+ {
+ if (Version <= SSL.TLS11) {
+ return new PRF();
+ } else {
+ return SSL.GetPRFForTLS12(CipherSuite);
+ }
+ }
+
+ /*
+ * Compute the master secret from the provided premaster secret.
+ */
+ internal void ComputeMaster(byte[] pms)
+ {
+ PRF prf = GetPRF();
+ byte[] seed = new byte[64];
+ Array.Copy(clientRandom, 0, seed, 0, 32);
+ Array.Copy(serverRandom, 0, seed, 32, 32);
+ prf.GetBytes(pms, PRF.LABEL_MASTER_SECRET, seed, masterSecret);
+ }
+
+ /*
+ * Set the master secret to the provided value. This is used when
+ * resuming a session.
+ */
+ internal void SetMasterSecret(byte[] rms)
+ {
+ Array.Copy(rms, 0, masterSecret, 0, rms.Length);
+ }
+
+ /*
+ * Switch to new security parameters.
+ * 'write' is true if we switch encryption for our sending channel,
+ * false for our receiving channel.
+ */
+ internal void SwitchEncryption(bool write)
+ {
+ int macLen, encLen, ivLen;
+ IBlockCipher block = null;
+ IDigest hash = null;
+ Poly1305 pp = null;
+ switch (CipherSuite) {
+ case SSL.RSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case SSL.DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case SSL.DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.DH_anon_WITH_3DES_EDE_CBC_SHA:
+ case SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case SSL.ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ macLen = 20;
+ encLen = 24;
+ ivLen = 8;
+ block = new DES();
+ hash = new SHA1();
+ break;
+
+ case SSL.RSA_WITH_AES_128_CBC_SHA:
+ case SSL.DH_DSS_WITH_AES_128_CBC_SHA:
+ case SSL.DH_RSA_WITH_AES_128_CBC_SHA:
+ case SSL.DHE_DSS_WITH_AES_128_CBC_SHA:
+ case SSL.DHE_RSA_WITH_AES_128_CBC_SHA:
+ case SSL.DH_anon_WITH_AES_128_CBC_SHA:
+ case SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case SSL.ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case SSL.ECDH_anon_WITH_AES_128_CBC_SHA:
+ macLen = 20;
+ encLen = 16;
+ ivLen = 16;
+ block = new AES();
+ hash = new SHA1();
+ break;
+
+ case SSL.RSA_WITH_AES_256_CBC_SHA:
+ case SSL.DH_DSS_WITH_AES_256_CBC_SHA:
+ case SSL.DH_RSA_WITH_AES_256_CBC_SHA:
+ case SSL.DHE_DSS_WITH_AES_256_CBC_SHA:
+ case SSL.DHE_RSA_WITH_AES_256_CBC_SHA:
+ case SSL.DH_anon_WITH_AES_256_CBC_SHA:
+ case SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case SSL.ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case SSL.ECDH_anon_WITH_AES_256_CBC_SHA:
+ macLen = 20;
+ encLen = 32;
+ ivLen = 16;
+ block = new AES();
+ hash = new SHA1();
+ break;
+
+ case SSL.RSA_WITH_AES_128_CBC_SHA256:
+ case SSL.DH_DSS_WITH_AES_128_CBC_SHA256:
+ case SSL.DH_RSA_WITH_AES_128_CBC_SHA256:
+ case SSL.DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case SSL.DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case SSL.DH_anon_WITH_AES_128_CBC_SHA256:
+ case SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ macLen = 32;
+ encLen = 16;
+ ivLen = 16;
+ block = new AES();
+ hash = new SHA256();
+ break;
+
+ case SSL.RSA_WITH_AES_256_CBC_SHA256:
+ case SSL.DH_DSS_WITH_AES_256_CBC_SHA256:
+ case SSL.DH_RSA_WITH_AES_256_CBC_SHA256:
+ case SSL.DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case SSL.DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case SSL.DH_anon_WITH_AES_256_CBC_SHA256:
+ macLen = 32;
+ encLen = 32;
+ ivLen = 16;
+ block = new AES();
+ hash = new SHA256();
+ break;
+
+ case SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ macLen = 48;
+ encLen = 32;
+ ivLen = 16;
+ block = new AES();
+ hash = new SHA384();
+ break;
+
+ case SSL.RSA_WITH_AES_128_GCM_SHA256:
+ case SSL.DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case SSL.DH_RSA_WITH_AES_128_GCM_SHA256:
+ case SSL.DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case SSL.DH_DSS_WITH_AES_128_GCM_SHA256:
+ case SSL.DH_anon_WITH_AES_128_GCM_SHA256:
+ case SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ macLen = 0;
+ encLen = 16;
+ ivLen = 4;
+ block = new AES();
+ break;
+
+ case SSL.RSA_WITH_AES_256_GCM_SHA384:
+ case SSL.DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case SSL.DH_RSA_WITH_AES_256_GCM_SHA384:
+ case SSL.DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case SSL.DH_DSS_WITH_AES_256_GCM_SHA384:
+ case SSL.DH_anon_WITH_AES_256_GCM_SHA384:
+ case SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ macLen = 0;
+ encLen = 32;
+ ivLen = 4;
+ block = new AES();
+ break;
+
+ case SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ case SSL.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ macLen = 0;
+ encLen = 32;
+ ivLen = 12;
+ pp = new Poly1305();
+ pp.ChaCha = new ChaCha20();
+ break;
+
+ default:
+ throw new SSLException("Unsupported cipher suite");
+ }
+
+ /*
+ * Normally we don't need IV when using CBC+HMAC with
+ * TLS 1.1+.
+ */
+ if (Version >= SSL.TLS11 && hash != null) {
+ ivLen = 0;
+ }
+
+ byte[] seed = new byte[64];
+ Array.Copy(serverRandom, 0, seed, 0, 32);
+ Array.Copy(clientRandom, 0, seed, 32, 32);
+ byte[] kb = GetPRF().GetBytes(masterSecret,
+ PRF.LABEL_KEY_EXPANSION, seed,
+ (macLen + encLen + ivLen) << 1);
+
+ /*
+ * Test whether we need the client write keys, or the
+ * server write keys.
+ */
+ bool clientWrite = (IsClient == write);
+ HMAC hm = null;
+ if (macLen > 0) {
+ hm = new HMAC(hash);
+ hm.SetKey(kb, clientWrite ? 0 : macLen, macLen);
+ }
+ if (block != null) {
+ block.SetKey(kb, (macLen << 1)
+ + (clientWrite ? 0 : encLen), encLen);
+ } else if (pp != null) {
+ pp.ChaCha.SetKey(kb, (macLen << 1)
+ + (clientWrite ? 0 : encLen), encLen);
+ }
+ byte[] iv = null;
+ if (ivLen > 0) {
+ iv = new byte[ivLen];
+ Array.Copy(kb, ((macLen + encLen) << 1)
+ + (clientWrite ? 0 : ivLen), iv, 0, ivLen);
+ }
+
+ if (hm != null) {
+ /*
+ * CBC+HMAC cipher suite.
+ */
+ if (write) {
+ outRec.SetEncryption(
+ new RecordEncryptCBC(block, hm, iv));
+ } else {
+ inRec.SetDecryption(
+ new RecordDecryptCBC(block, hm, iv));
+ }
+ } else if (block != null) {
+ /*
+ * GCM cipher suite.
+ */
+ if (write) {
+ outRec.SetEncryption(
+ new RecordEncryptGCM(block, iv));
+ } else {
+ inRec.SetDecryption(
+ new RecordDecryptGCM(block, iv));
+ }
+ } else if (pp != null) {
+ /*
+ * ChaCha20 + Poly1305 cipher suite.
+ */
+ if (write) {
+ outRec.SetEncryption(
+ new RecordEncryptChaPol(pp, iv));
+ } else {
+ inRec.SetDecryption(
+ new RecordDecryptChaPol(pp, iv));
+ }
+ } else {
+ throw new Exception("NYI");
+ }
+ }
+
+ /*
+ * Compute Finished message. The 'client' flag is set to true
+ * for the Finished message sent by the client, false for the
+ * Finished message sent by the server.
+ */
+ internal byte[] ComputeFinished(bool client)
+ {
+ PRF prf;
+ byte[] seed;
+ if (Version <= SSL.TLS11) {
+ seed = new byte[36];
+ md5.DoPartial(seed, 0);
+ sha1.DoPartial(seed, 16);
+ prf = new PRF();
+ } else if (SSL.IsSHA384(CipherSuite)) {
+ seed = sha384.DoPartial();
+ prf = new PRF(new SHA384());
+ } else {
+ seed = sha256.DoPartial();
+ prf = new PRF(new SHA256());
+ }
+ byte[] label = client
+ ? PRF.LABEL_CLIENT_FINISHED
+ : PRF.LABEL_SERVER_FINISHED;
+ return prf.GetBytes(masterSecret, label, seed, 12);
+ }
+
+ /*
+ * Send a ChangeCipherSpec, then a Finished message. This
+ * call implies switching to the new encryption parameters for
+ * the sending channel.
+ */
+ internal void SendCCSAndFinished()
+ {
+ outRec.RecordType = SSL.CHANGE_CIPHER_SPEC;
+ outRec.Write(0x01);
+ outRec.RecordType = SSL.HANDSHAKE;
+ SwitchEncryption(true);
+ byte[] fin = ComputeFinished(IsClient);
+ if (IsClient) {
+ savedClientFinished = fin;
+ } else {
+ savedServerFinished = fin;
+ }
+ MemoryStream ms = StartHandshakeMessage(SSL.FINISHED);
+ ms.Write(fin, 0, fin.Length);
+ EndHandshakeMessage(ms);
+ }
+
+ /*
+ * Receive a ChangeCipherSpec, then a Finished message. This
+ * call implies switching to the new encryption parameters for
+ * the receiving channel.
+ */
+ internal void ParseCCSAndFinished()
+ {
+ if (inRec.BufferedLength > 0) {
+ throw new SSLException(
+ "Buffered data while expecting CCS");
+ }
+ state = STATE_CCS;
+ int x = ZRead();
+ if (x != 0x01 || inRec.BufferedLength > 0) {
+ throw new SSLException("Invalid CCS contents");
+ }
+ SwitchEncryption(false);
+ state = STATE_HANDSHAKE;
+ byte[] fin = ComputeFinished(!IsClient);
+ byte[] msg = ReadHandshakeMessageExpected(SSL.FINISHED);
+ if (!Eq(fin, msg)) {
+ throw new SSLException("Wrong Finished value");
+ }
+ if (inRec.BufferedLength > 0) {
+ throw new SSLException(
+ "Extra handshake data after Finished message");
+ }
+ if (IsClient) {
+ savedServerFinished = fin;
+ } else {
+ savedClientFinished = fin;
+ }
+ }
+
+ /*
+ * Switch to "application data" state just after the handshake.
+ */
+ internal void SetAppData()
+ {
+ SetState(STATE_APPDATA);
+ outRec.RecordType = SSL.APPLICATION_DATA;
+ }
+
+ /*
+ * Send an alert of level "warning".
+ */
+ internal void SendWarning(int type)
+ {
+ int rt = outRec.RecordType;
+ outRec.RecordType = SSL.ALERT;
+ outRec.Write(SSL.WARNING);
+ outRec.Write((byte)type);
+ outRec.Flush();
+ outRec.RecordType = rt;
+ }
+
+ static bool Eq(byte[] a, byte[] b)
+ {
+ int n = a.Length;
+ if (n != b.Length) {
+ return false;
+ }
+ int z = 0;
+ for (int i = 0; i < n; i ++) {
+ z |= a[i] ^ b[i];
+ }
+ return z == 0;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * All SSL failures (invalid messages, failed decryption or MAC checks,
+ * received fatal alerts...) trigger an SSLException. Since SSLEngine
+ * is a Stream, this exception is made an extension of IOException.
+ */
+
+public class SSLException : IOException {
+
+ public SSLException(string msg) : base(msg)
+ {
+ }
+
+ public SSLException(string msg, Exception cause) : base(msg, cause)
+ {
+ }
+
+ public SSLException(Exception cause) : base(cause.Message, cause)
+ {
+ }
+
+ public SSLException(int alert)
+ : base(String.Format(
+ "Fatal alert {0} received from peer", alert))
+ {
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * An SSLQuirks instance is a set of named parameters that can be
+ * applied to an SSL engine (client or server) and alter its behaviour.
+ * Some of these variants are rarely used options permitted by the
+ * standard; others are downright invalid, and are meant to verify that
+ * a peer implementation is properly reacting to malformed messages
+ * and other anomalous conditions.
+ *
+ * Parameter names and values are strings. Parameter names are
+ * case-sensitive. As a matter of convention:
+ *
+ * - Boolean values are set as "true" or "false" string values.
+ *
+ * - Integers are encoded in decimal or hexadecimal; hexadecimal
+ * values have a leading "0x" header (or "-0x" for a negative
+ * hexadecimal value).
+ */
+
+public class SSLQuirks {
+
+ /*
+ * When reading a parameter value, and the parameter is not
+ * defined, null is returned.
+ */
+ public string this[string name] {
+ get {
+ string v;
+ if (d.TryGetValue(name, out v)) {
+ return v;
+ } else {
+ return null;
+ }
+ }
+ set {
+ d[name] = value;
+ }
+ }
+
+ IDictionary<string, string> d;
+
+ public SSLQuirks()
+ {
+ d = new SortedDictionary<string, string>(
+ StringComparer.Ordinal);
+ }
+
+ /*
+ * Get a boolean quirk. If defined, then the boolean value is
+ * written in 'val' and true is returned; otherwise, 'val' is
+ * set to false, and false is returned.
+ */
+ public bool TryGetBoolean(string name, out bool val)
+ {
+ string s;
+ if (!d.TryGetValue(name, out s)) {
+ val = false;
+ return false;
+ }
+ switch (s) {
+ case "true": val = true; return true;
+ case "false": val = false; return true;
+ }
+ s = s.ToLowerInvariant();
+ switch (s) {
+ case "true": val = true; return true;
+ case "false": val = false; return true;
+ }
+ throw new Exception("Quirk value is not a boolean");
+ }
+
+ /*
+ * Get a boolean quirk. If undefined, the provided default value
+ * is returned.
+ */
+ public bool GetBoolean(string name, bool defaultValue)
+ {
+ bool val;
+ if (TryGetBoolean(name, out val)) {
+ return val;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /*
+ * Get an integer quirk. If defined, then the integer value is
+ * written in 'val' and true is returned; otherwise, 'val' is
+ * set to 0, and false is returned.
+ */
+ public bool TryGetInteger(string name, out int val)
+ {
+ string s;
+ if (!d.TryGetValue(name, out s)) {
+ val = 0;
+ return false;
+ }
+ bool neg = false;
+ if (s.StartsWith("-")) {
+ neg = true;
+ s = s.Substring(1);
+ }
+ int radix;
+ if (s.StartsWith("0x")) {
+ radix = 16;
+ s = s.Substring(2);
+ } else {
+ radix = 10;
+ }
+ int acc = 0;
+ if (s.Length == 0) {
+ throw new Exception("Quirk value is not an integer");
+ }
+ foreach (char c in s) {
+ int x;
+ if (c >= '0' && c <= '9') {
+ x = c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ x = c - ('A' - 10);
+ } else if (c >= 'a' && c <= 'f') {
+ x = c - ('a' - 10);
+ } else {
+ throw new Exception(
+ "Quirk value is not an integer");
+ }
+ if (x >= radix) {
+ throw new Exception(
+ "Quirk value is not an integer");
+ }
+ acc = (acc * radix) + x;
+ }
+ if (neg) {
+ acc = -acc;
+ }
+ val = acc;
+ return true;
+ }
+
+ /*
+ * Get an integer quirk. If undefined, the provided default value
+ * is returned.
+ */
+ public int GetInteger(string name, int defaultValue)
+ {
+ int val;
+ if (TryGetInteger(name, out val)) {
+ return val;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /*
+ * Get a string quirk. If undefined, the provided default value
+ * is returned.
+ */
+ public string GetString(string name, string defaultValue)
+ {
+ string s;
+ if (d.TryGetValue(name, out s)) {
+ return s;
+ } else {
+ return defaultValue;
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Class for a SSL server connection.
+ *
+ * An instance is created over a specified transport stream. An optional
+ * cache for session parameters can be provided, to support session
+ * resumption. The instance handles the connection but cannot be
+ * "revived" after the connection was closed (the session parameters,
+ * though, can be extracted and used with another instance).
+ */
+
+public class SSLServer : SSLEngine {
+
+ /*
+ * If true, then the server will enforce its own preference
+ * order for cipher suite selection; otherwise, it will follow
+ * the client's preferences. Default value is false.
+ */
+ public bool EnforceServerOrder {
+ get; set;
+ }
+
+ /*
+ * Server policy object, that selects cipher suite and certificate
+ * chain to send to client. Such a policy object MUST be set
+ * before the initial handshake takes place. This property is
+ * initialised to the value provided as second argument to the
+ * SSLServer constructor.
+ */
+ public IServerPolicy ServerPolicy {
+ get; set;
+ }
+
+ /*
+ * Optional session cache for SSL sessions. If null, then no
+ * cache is used. Default value is null.
+ */
+ public ISessionCache SessionCache {
+ get; set;
+ }
+
+ /*
+ * If this flag is set to true, then session resumption will be
+ * rejected; all handshakes will be full handshakes. Main
+ * intended usage is when a server wants to renegotiate and ask
+ * for a client certificate. Note that even if that flag is set,
+ * each session resulting from a full handshake is still pushed
+ * to the session cache (if configured in SessionCache).
+ * Default value is false, meaning that session resumption is
+ * allowed (but won't happen anyway if no session cache was
+ * set in SessionCache).
+ */
+ public bool NoResume {
+ get; set;
+ }
+
+ /*
+ * Get the maximum supported version announced by the client
+ * in its ClientHello message.
+ */
+ public int ClientVersionMax {
+ get; private set;
+ }
+
+ /*
+ * Get the list of hash and signature algorithms supported by the
+ * client. Each value is a 16-bit integer, with the high byte
+ * being the hash algorithm, and the low byte the signature
+ * algorithm.
+ *
+ * The list is trimmed to include only hash and signature algorithms
+ * that are supported by both client and server. It is ordered
+ * by client or server preference, depending on the value of
+ * the EnforceServerOrder flag.
+ *
+ * If the client did not send the dedicated extension, then the
+ * list is inferred from the sent cipher suite, as specified
+ * by RFC 5246, section 7.4.1.4.1.
+ */
+ public List<int> ClientHashAndSign {
+ get; internal set;
+ }
+
+ /*
+ * Get the list of elliptic curves supported by the client. Each
+ * entry is a 16-bit integer that identifies a named curve. The
+ * list is ordered by client preferences.
+ *
+ * If the client did not send the Supported Curves extension,
+ * then the list will be inferred to contain NIST P-256 only
+ * (if the client supports at least one ECDH, ECDHE or ECDSA
+ * cipher suite), or to be empty (if the client does not support
+ * any EC-based cipher suite).
+ */
+ public List<int> ClientCurves {
+ get; internal set;
+ }
+
+ /*
+ * Get the list of elliptic curves supported by both client and
+ * server. Each entry is a 16-bit integer that identifies a
+ * named curve. The list is ordered by preference (client or
+ * server, depending on configuration). This list is the one
+ * used for curve selection for ECDHE.
+ */
+ public List<int> CommonCurves {
+ get; internal set;
+ }
+
+ /*
+ * Get the list of cipher suites supported by the client. The
+ * order matches the configured preferences (client or server
+ * preference order, depending on the EnforceServerOrder flag).
+ * Moreover, the list is trimmed:
+ *
+ * - Signalling cipher suites ("SCSV") have been removed.
+ *
+ * - Only suites supported by both client and server are kept.
+ *
+ * - Suites that require TLS 1.2 are omitted if the selected
+ * protocol version is TLS 1.0 or 1.1.
+ *
+ * - Suites that require client support for RSA signatures are
+ * removed if there is no common support for RSA signatures.
+ *
+ * - Suites that require client support for ECDSA signatures
+ * are removed if there is no common support for ECDSA
+ * signatures.
+ *
+ * - ECDHE suites are removed if there is no common support for
+ * elliptic curves.
+ */
+ public List<int> CommonCipherSuites {
+ get; internal set;
+ }
+
+ IServerChoices serverChoices;
+ ECCurve ecdheCurve;
+ byte[] ecdheSecret;
+
+ /*
+ * Create an SSL server instance, over the provided stream.
+ * The 'serverPolicy' parameter is used as initial value to
+ * the ServerPolicy property.
+ */
+ public SSLServer(Stream sub, IServerPolicy serverPolicy)
+ : base(sub)
+ {
+ EnforceServerOrder = false;
+ ServerPolicy = serverPolicy;
+ }
+
+ internal override bool IsClient {
+ get {
+ return false;
+ }
+ }
+
+ internal override bool DoHandshake()
+ {
+ CheckConfigHashAndSign();
+
+ ResetHashes();
+ MakeRandom(serverRandom);
+
+ bool resume;
+ if (!ParseClientHello(out resume)) {
+ return false;
+ }
+ HandshakeCount ++;
+ SetOutputRecordVersion(Version);
+ SetInputRecordVersion(Version);
+
+ if (resume) {
+ SendServerHello();
+ SendCCSAndFinished();
+ FlushSub();
+ ParseCCSAndFinished();
+ SetAppData();
+ IsResume = true;
+ return true;
+ }
+
+ SendServerHello();
+ SendCertificate();
+ if (SSL.IsECDHE(CipherSuite)) {
+ SendServerKeyExchange();
+ }
+ SendServerHelloDone();
+ FlushSub();
+
+ ParseClientKeyExchange();
+ ParseCCSAndFinished();
+ SendCCSAndFinished();
+ FlushSub();
+ SetAppData();
+ IsResume = false;
+ if (SessionCache != null) {
+ SessionCache.Store(SessionParameters);
+ }
+ return true;
+ }
+
+ bool ParseClientHello(out bool resume)
+ {
+ resume = false;
+ int mt;
+ byte[] msg = ReadHandshakeMessage(out mt, FirstHandshakeDone);
+ if (msg == null) {
+ /*
+ * Client rejected renegotiation attempt. This cannot
+ * happen if we are invoked from
+ * ProcessExtraHandshake() because that method is
+ * invoked only when there is buffered handshake
+ * data.
+ */
+ return false;
+ }
+ if (mt != SSL.CLIENT_HELLO) {
+ throw new SSLException(string.Format("Unexpected"
+ + " handshake message {0} (expecting a"
+ + " ClientHello)", mt));
+ }
+
+ /*
+ * Maximum protocol version supported by the client.
+ */
+ if (msg.Length < 35) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ ClientVersionMax = IO.Dec16be(msg, 0);
+ if (ClientVersionMax < VersionMin) {
+ throw new SSLException(string.Format(
+ "No acceptable version (client max = 0x{0:X4})",
+ ClientVersionMax));
+ }
+
+ /*
+ * Client random (32 bytes).
+ */
+ Array.Copy(msg, 2, clientRandom, 0, 32);
+
+ /*
+ * Session ID sent by the client: at most 32 bytes.
+ */
+ int idLen = msg[34];
+ int off = 35;
+ if (idLen > 32 || (off + idLen) > msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ byte[] clientSessionID = new byte[idLen];
+ Array.Copy(msg, off, clientSessionID, 0, idLen);
+ off += idLen;
+
+ /*
+ * List of client cipher suites.
+ */
+ if ((off + 2) > msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ int csLen = IO.Dec16be(msg, off);
+ off += 2;
+ if ((off + csLen) > msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ List<int> clientSuites = new List<int>();
+ bool seenReneg = false;
+ while (csLen > 0) {
+ int cs = IO.Dec16be(msg, off);
+ off += 2;
+ csLen -= 2;
+ if (cs == SSL.FALLBACK_SCSV) {
+ if (ClientVersionMax < VersionMax) {
+ throw new SSLException(
+ "Undue fallback detected");
+ }
+ } else if (cs == SSL.EMPTY_RENEGOTIATION_INFO_SCSV) {
+ if (FirstHandshakeDone) {
+ throw new SSLException(
+ "Reneg SCSV in renegotiation");
+ }
+ seenReneg = true;
+ } else {
+ clientSuites.Add(cs);
+ }
+ }
+
+ /*
+ * List of compression methods. We only accept method 0
+ * (no compression).
+ */
+ if ((off + 1) > msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ int compLen = msg[off ++];
+ if ((off + compLen) > msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ bool foundUncompressed = false;
+ while (compLen -- > 0) {
+ if (msg[off ++] == 0x00) {
+ foundUncompressed = true;
+ }
+ }
+ if (!foundUncompressed) {
+ throw new SSLException("No common compression support");
+ }
+
+ /*
+ * Extensions.
+ */
+ ClientHashAndSign = null;
+ ClientCurves = null;
+ if (off < msg.Length) {
+ if ((off + 2) > msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ int tlen = IO.Dec16be(msg, off);
+ off += 2;
+ if ((off + tlen) != msg.Length) {
+ throw new SSLException("Invalid ClientHello");
+ }
+ while (off < msg.Length) {
+ if ((off + 4) > msg.Length) {
+ throw new SSLException(
+ "Invalid ClientHello");
+ }
+ int etype = IO.Dec16be(msg, off);
+ int elen = IO.Dec16be(msg, off + 2);
+ off += 4;
+ if ((off + elen) > msg.Length) {
+ throw new SSLException(
+ "Invalid ClientHello");
+ }
+ switch (etype) {
+
+ case 0x0000:
+ ParseExtSNI(msg, off, elen);
+ break;
+
+ case 0x000D:
+ ParseExtSignatures(msg, off, elen);
+ break;
+
+ case 0x000A:
+ ParseExtCurves(msg, off, elen);
+ break;
+
+ case 0xFF01:
+ ParseExtSecureReneg(msg, off, elen);
+ seenReneg = true;
+ break;
+
+ // Max Frag Length
+ // ALPN
+ // FIXME
+ }
+
+ off += elen;
+ }
+ }
+
+ /*
+ * If we are renegotiating and we did not see the
+ * Secure Renegotiation extension, then this is an error.
+ */
+ if (FirstHandshakeDone && !seenReneg) {
+ throw new SSLException(
+ "Missing Secure Renegotiation extension");
+ }
+
+ /*
+ * Use prescribed default values for supported algorithms
+ * and curves, when not otherwise advertised by the client.
+ */
+ if (ClientCurves == null) {
+ ClientCurves = new List<int>();
+ foreach (int cs in clientSuites) {
+ if (SSL.IsECDH(cs) || SSL.IsECDHE(cs)) {
+ ClientCurves.Add(SSL.NIST_P256);
+ break;
+ }
+ }
+ }
+ if (ClientHashAndSign == null) {
+ bool withRSA = false;
+ bool withECDSA = false;
+ foreach (int cs in clientSuites) {
+ if (SSL.IsRSA(cs)
+ || SSL.IsECDH_RSA(cs)
+ || SSL.IsECDHE_RSA(cs))
+ {
+ withRSA = true;
+ }
+ if (SSL.IsECDH_ECDSA(cs)
+ || SSL.IsECDHE_ECDSA(cs))
+ {
+ withECDSA = true;
+ }
+ }
+ ClientHashAndSign = new List<int>();
+ if (withRSA) {
+ ClientHashAndSign.Add(SSL.RSA_SHA1);
+ }
+ if (withECDSA) {
+ ClientHashAndSign.Add(SSL.ECDSA_SHA1);
+ }
+ }
+
+ /*
+ * Filter curves and algorithms with regards to our own
+ * configuration.
+ */
+ CommonCurves = FilterList(ClientCurves,
+ SupportedCurves, EnforceServerOrder);
+ ClientHashAndSign = FilterList(ClientHashAndSign,
+ SupportedHashAndSign, EnforceServerOrder);
+
+ /*
+ * Selected protocol version (can be overridden by
+ * resumption).
+ */
+ Version = Math.Min(ClientVersionMax, VersionMax);
+
+ /*
+ * Recompute list of acceptable cipher suites. We keep
+ * only suites which are common to the client and server,
+ * with some extra filters.
+ *
+ * Note that when using static ECDH, it is up to the
+ * policy callback to determine whether the curves match
+ * the contents of the certificate.
+ *
+ * We also build a list of common suites for session
+ * resumption: this one may include suites whose
+ * asymmetric crypto is not supported, because session
+ * resumption uses only symmetric crypto.
+ */
+ CommonCipherSuites = new List<int>();
+ List<int> commonSuitesResume = new List<int>();
+ bool canTLS12 = Version >= SSL.TLS12;
+ bool canSignRSA;
+ bool canSignECDSA;
+ if (Version >= SSL.TLS12) {
+ canSignRSA = false;
+ canSignECDSA = false;
+ foreach (int alg in ClientHashAndSign) {
+ int sa = alg & 0xFF;
+ switch (sa) {
+ case SSL.RSA: canSignRSA = true; break;
+ case SSL.ECDSA: canSignECDSA = true; break;
+ }
+ }
+ } else {
+ /*
+ * For pre-1.2, the hash-and-sign configuration does
+ * not matter, only the cipher suites themselves. So
+ * we claim support of both RSA and ECDSA signatures
+ * to avoid trimming the list too much.
+ */
+ canSignRSA = true;
+ canSignECDSA = true;
+ }
+ bool canECDHE = CommonCurves.Count > 0;
+
+ foreach (int cs in clientSuites) {
+ if (!canTLS12 && SSL.IsTLS12(cs)) {
+ continue;
+ }
+ commonSuitesResume.Add(cs);
+ if (!canECDHE && SSL.IsECDHE(cs)) {
+ continue;
+ }
+ if (!canSignRSA && SSL.IsECDHE_RSA(cs)) {
+ continue;
+ }
+ if (!canSignECDSA && SSL.IsECDHE_ECDSA(cs)) {
+ continue;
+ }
+ CommonCipherSuites.Add(cs);
+ }
+ CommonCipherSuites = FilterList(CommonCipherSuites,
+ SupportedCipherSuites, EnforceServerOrder);
+ commonSuitesResume = FilterList(commonSuitesResume,
+ SupportedCipherSuites, EnforceServerOrder);
+
+ /*
+ * If resuming, then use the remembered session parameters,
+ * but only if they are compatible with what the client
+ * sent AND what we currently support.
+ */
+ SSLSessionParameters sp = null;
+ if (idLen > 0 && !NoResume && SessionCache != null) {
+ sp = SessionCache.Retrieve(
+ clientSessionID, ServerName);
+ if (sp != null && sp.ServerName != null
+ && ServerName != null)
+ {
+ /*
+ * When resuming a session, if there is
+ * an explicit name sent by the client,
+ * and the cached parameters also include
+ * an explicit name, then both names
+ * shall match.
+ */
+ string s1 = sp.ServerName.ToLowerInvariant();
+ string s2 = ServerName.ToLowerInvariant();
+ if (s1 != s2) {
+ sp = null;
+ }
+ }
+ }
+ if (sp != null) {
+ bool resumeOK = true;
+ if (sp.Version < VersionMin
+ || sp.Version > VersionMax
+ || sp.Version > ClientVersionMax)
+ {
+ resumeOK = false;
+ }
+ if (!commonSuitesResume.Contains(sp.CipherSuite)) {
+ resumeOK = false;
+ }
+
+ if (resumeOK) {
+ /*
+ * Session resumption is acceptable.
+ */
+ resume = true;
+ sessionID = clientSessionID;
+ Version = sp.Version;
+ CipherSuite = sp.CipherSuite;
+ sessionID = clientSessionID;
+ SetMasterSecret(sp.MasterSecret);
+ return true;
+ }
+ }
+
+ /*
+ * Not resuming. Let's select parameters.
+ * Protocol version was already set.
+ */
+ if (CommonCipherSuites.Count == 0) {
+ throw new SSLException("No common cipher suite");
+ }
+ serverChoices = ServerPolicy.Apply(this);
+ CipherSuite = serverChoices.GetCipherSuite();
+
+ /*
+ * We create a new session ID, even if we don't have a
+ * session cache, because the session parameters could
+ * be extracted manually by the application.
+ */
+ sessionID = new byte[32];
+ RNG.GetBytes(sessionID);
+
+ return true;
+ }
+
+ void ParseExtSNI(byte[] buf, int off, int len)
+ {
+ if (len < 2) {
+ throw new SSLException("Invalid SNI extension");
+ }
+ int tlen = IO.Dec16be(buf, off);
+ off += 2;
+ if ((tlen + 2) != len) {
+ throw new SSLException("Invalid SNI extension");
+ }
+ int lim = off + tlen;
+ bool found = false;
+ while (off < lim) {
+ if ((off + 3) > lim) {
+ throw new SSLException("Invalid SNI extension");
+ }
+ int ntype = buf[off ++];
+ int nlen = IO.Dec16be(buf, off);
+ off += 2;
+ if ((off + nlen) > lim) {
+ throw new SSLException("Invalid SNI extension");
+ }
+ if (ntype == 0) {
+ /*
+ * Name type is "host name". There shall be
+ * only one (at most) in the extension.
+ */
+ if (found) {
+ throw new SSLException("Several host"
+ + " names in SNI extension");
+ }
+ found = true;
+
+ /*
+ * Verify that the name contains only
+ * printable non-space ASCII, and normalise
+ * it to lowercase.
+ */
+ char[] tc = new char[nlen];
+ for (int i = 0; i < nlen; i ++) {
+ int x = buf[off + i];
+ if (x <= 32 || x >= 126) {
+ throw new SSLException(
+ "Invalid SNI hostname");
+ }
+ if (x >= 'A' && x <= 'Z') {
+ x += ('a' - 'A');
+ }
+ tc[i] = (char)x;
+ }
+ ServerName = new string(tc);
+ }
+ off += nlen;
+ }
+ }
+
+ void ParseExtSignatures(byte[] buf, int off, int len)
+ {
+ if (len < 2) {
+ throw new SSLException("Invalid signatures extension");
+ }
+ int tlen = IO.Dec16be(buf, off);
+ off += 2;
+ if (len != (tlen + 2)) {
+ throw new SSLException("Invalid signatures extension");
+ }
+ if ((tlen & 1) != 0) {
+ throw new SSLException("Invalid signatures extension");
+ }
+ ClientHashAndSign = new List<int>();
+ while (tlen > 0) {
+ ClientHashAndSign.Add(IO.Dec16be(buf, off));
+ off += 2;
+ tlen -= 2;
+ }
+ }
+
+ void ParseExtCurves(byte[] buf, int off, int len)
+ {
+ if (len < 2) {
+ throw new SSLException("Invalid curves extension");
+ }
+ int tlen = IO.Dec16be(buf, off);
+ off += 2;
+ if (len != (tlen + 2)) {
+ throw new SSLException("Invalid curves extension");
+ }
+ if ((tlen & 1) != 0) {
+ throw new SSLException("Invalid curves extension");
+ }
+ ClientCurves = new List<int>();
+ while (tlen > 0) {
+ ClientCurves.Add(IO.Dec16be(buf, off));
+ off += 2;
+ tlen -= 2;
+ }
+ }
+
+ void ParseExtSecureReneg(byte[] buf, int off, int len)
+ {
+ if (len < 1 || len != 1 + buf[off]) {
+ throw new SSLException(
+ "Invalid Secure Renegotiation extension");
+ }
+ len --;
+ off ++;
+
+ if (renegSupport == 0) {
+ /*
+ * Initial handshake: extension MUST be empty.
+ */
+ if (len != 0) {
+ throw new SSLException(
+ "Non-empty Secure Renegotation"
+ + " on initial handshake");
+ }
+ renegSupport = 1;
+ } else {
+ /*
+ * Renegotiation: extension MUST contain the
+ * saved client Finished message.
+ */
+ if (len != 12) {
+ throw new SSLException(
+ "Wrong Secure Renegotiation value");
+ }
+ int z = 0;
+ for (int i = 0; i < 12; i ++) {
+ z |= savedClientFinished[i] ^ buf[off + i];
+ }
+ if (z != 0) {
+ throw new SSLException(
+ "Wrong Secure Renegotiation value");
+ }
+ }
+ }
+
+ void SendServerHello()
+ {
+ MemoryStream ms = StartHandshakeMessage(SSL.SERVER_HELLO);
+
+ // Protocol version
+ IO.Write16(ms, Version);
+
+ // Server random
+ ms.Write(serverRandom, 0, serverRandom.Length);
+
+ // Session ID
+ ms.WriteByte((byte)sessionID.Length);
+ ms.Write(sessionID, 0, sessionID.Length);
+
+ // Cipher suite
+ IO.Write16(ms, CipherSuite);
+
+ // Compression
+ ms.WriteByte(0x00);
+
+ // Extensions
+ MemoryStream chExt = new MemoryStream();
+
+ // Secure renegotiation
+ if (!GetQuirkBool("noSecureReneg")) {
+ byte[] exv = null;
+ if (renegSupport > 0) {
+ if (FirstHandshakeDone) {
+ exv = new byte[24];
+ Array.Copy(savedClientFinished, 0,
+ exv, 0, 12);
+ Array.Copy(savedServerFinished, 0,
+ exv, 12, 12);
+ } else {
+ exv = new byte[0];
+ }
+ }
+ if (GetQuirkBool("forceEmptySecureReneg")) {
+ exv = new byte[0];
+ } else if (GetQuirkBool("forceNonEmptySecureReneg")) {
+ exv = new byte[24];
+ } else if (GetQuirkBool("alterNonEmptySecureReneg")) {
+ if (exv.Length > 0) {
+ exv[exv.Length - 1] ^= 0x01;
+ }
+ } else if (GetQuirkBool("oversizedSecureReneg")) {
+ exv = new byte[255];
+ }
+
+ if (exv != null) {
+ IO.Write16(chExt, 0xFF01);
+ IO.Write16(chExt, exv.Length + 1);
+ chExt.WriteByte((byte)exv.Length);
+ chExt.Write(exv, 0, exv.Length);
+ }
+ }
+
+ // Extra extension with random contents.
+ int extraExt = GetQuirkInt("sendExtraExtension", -1);
+ if (extraExt >= 0) {
+ byte[] exv = new byte[extraExt >> 16];
+ RNG.GetBytes(exv);
+ IO.Write16(chExt, extraExt & 0xFFFF);
+ IO.Write16(chExt, exv.Length);
+ chExt.Write(exv, 0, exv.Length);
+ }
+
+ // Max Fragment Length
+ // ALPN
+ // FIXME
+
+ byte[] encExt = chExt.ToArray();
+ if (encExt.Length > 0) {
+ if (encExt.Length > 65535) {
+ throw new SSLException("Oversized extensions");
+ }
+ IO.Write16(ms, encExt.Length);
+ ms.Write(encExt, 0, encExt.Length);
+ }
+
+ EndHandshakeMessage(ms);
+ }
+
+ void SendCertificate()
+ {
+ MemoryStream ms = StartHandshakeMessage(SSL.CERTIFICATE);
+ byte[][] chain = serverChoices.GetCertificateChain();
+ int tlen = 0;
+ foreach (byte[] ec in chain) {
+ tlen += 3 + ec.Length;
+ }
+ if (tlen > 0xFFFFFC) {
+ throw new SSLException("Oversized certificate chain");
+ }
+ IO.Write24(ms, tlen);
+ foreach (byte[] ec in chain) {
+ IO.Write24(ms, ec.Length);
+ ms.Write(ec, 0, ec.Length);
+ }
+ EndHandshakeMessage(ms);
+ }
+
+ void SendServerKeyExchange()
+ {
+ if (CommonCurves.Count == 0) {
+ /*
+ * Since we filter cipher suites when parsing the
+ * ClientHello, this situation may happen only if
+ * the IServerPolicy callback goofed up.
+ */
+ throw new SSLException("No curve for ECDHE");
+ }
+ int curveID = CommonCurves[0];
+ ecdheCurve = SSL.GetCurveByID(curveID);
+
+ /*
+ * Generate our ephemeral ECDH secret and the point to
+ * send to the peer.
+ */
+ ecdheSecret = ecdheCurve.MakeRandomSecret();
+ byte[] P = ecdheCurve.GetGenerator(false);
+ ecdheCurve.Mul(P, ecdheSecret, P, false);
+
+ /*
+ * Generate to-be-signed:
+ * clientRandom 32 bytes
+ * serverRandom 32 bytes
+ * 0x03 curve is a "named curve"
+ * id curve identifier (two bytes)
+ * point public point (one-byte length + value)
+ */
+ byte[] tbs = new byte[64 + 4 + P.Length];
+ Array.Copy(clientRandom, 0, tbs, 0, 32);
+ Array.Copy(serverRandom, 0, tbs, 32, 32);
+ tbs[64] = 0x03;
+ IO.Enc16be(curveID, tbs, 65);
+ tbs[67] = (byte)P.Length;
+ Array.Copy(P, 0, tbs, 68, P.Length);
+
+ /*
+ * Obtain server signature.
+ */
+ int hashAlgo, sigAlgo;
+ byte[] sig = serverChoices.DoSign(tbs,
+ out hashAlgo, out sigAlgo);
+
+ /*
+ * Encode message.
+ */
+ MemoryStream ms = StartHandshakeMessage(
+ SSL.SERVER_KEY_EXCHANGE);
+ ms.Write(tbs, 64, tbs.Length - 64);
+ if (Version >= SSL.TLS12) {
+ ms.WriteByte((byte)hashAlgo);
+ ms.WriteByte((byte)sigAlgo);
+ }
+ IO.Write16(ms, sig.Length);
+ ms.Write(sig, 0, sig.Length);
+ EndHandshakeMessage(ms);
+ }
+
+ void SendServerHelloDone()
+ {
+ MemoryStream ms = StartHandshakeMessage(SSL.SERVER_HELLO_DONE);
+ EndHandshakeMessage(ms);
+ }
+
+ void ParseClientKeyExchange()
+ {
+ byte[] msg = ReadHandshakeMessageExpected(
+ SSL.CLIENT_KEY_EXCHANGE);
+ byte[] pms;
+ if (SSL.IsECDHE(CipherSuite)) {
+ /*
+ * Expecting a curve point; we are doing the
+ * ECDH ourselves.
+ */
+ if (msg.Length < 1 || msg.Length != 1 + msg[0]) {
+ throw new SSLException(
+ "Invalid ClientKeyExchange");
+ }
+ byte[] P = new byte[msg.Length - 1];
+ byte[] D = new byte[ecdheCurve.EncodedLength];
+ Array.Copy(msg, 1, P, 0, P.Length);
+ if (ecdheCurve.Mul(P, ecdheSecret, D, false) == 0) {
+ throw new SSLException(
+ "Invalid ClientKeyExchange");
+ }
+ int xlen;
+ int xoff = ecdheCurve.GetXoff(out xlen);
+ pms = new byte[xlen];
+ Array.Copy(D, xoff, pms, 0, xlen);
+
+ /*
+ * Memory wiping is out of scope for this library,
+ * and is unreliable anyway in the presence of
+ * a moving garbage collector. So we just unlink
+ * the secret array.
+ */
+ ecdheSecret = null;
+ } else {
+ /*
+ * RSA or static ECDH. The crypto operation is done
+ * by the relevant callback.
+ */
+ if (msg.Length < 2) {
+ throw new SSLException(
+ "Invalid ClientKeyExchange");
+ }
+ int off, len;
+ if (SSL.IsRSA(CipherSuite)) {
+ len = IO.Dec16be(msg, 0);
+ off = 2;
+ } else if (SSL.IsECDH(CipherSuite)) {
+ len = msg[0];
+ off = 1;
+ } else {
+ throw new Exception("NYI");
+ }
+ if (msg.Length != off + len) {
+ throw new SSLException(
+ "Invalid ClientKeyExchange");
+ }
+ byte[] cke = new byte[len];
+ Array.Copy(msg, off, cke, 0, len);
+ pms = serverChoices.DoKeyExchange(cke);
+ }
+
+ ComputeMaster(pms);
+ }
+
+ internal override void ProcessExtraHandshake()
+ {
+ /*
+ * If Secure Renegotiation is supported, then we accept
+ * to do a new handshake.
+ */
+ if (renegSupport > 0) {
+ DoHandshake();
+ return;
+ }
+
+ /*
+ * We must read and discard an incoming ClientHello,
+ * then politely refuse.
+ */
+ ReadHandshakeMessageExpected(SSL.CLIENT_HELLO);
+ SendWarning(SSL.NO_RENEGOTIATION);
+ SetAppData();
+ }
+
+ internal override void PrepareRenegotiate()
+ {
+ MemoryStream ms = StartHandshakeMessage(SSL.HELLO_REQUEST);
+ EndHandshakeMessage(ms);
+ FlushSub();
+ }
+
+ /*
+ * Compute the intersection of two lists of integers (the second
+ * list is provided as an array). The intersection is returned
+ * as a new List<int> instance. If enforceV2 is true, then the
+ * order of items in the returned list will be that of v2; otherwise,
+ * it will be that of v1. Duplicates are removed.
+ */
+ static List<int> FilterList(List<int> v1, int[] v2, bool enforceV2)
+ {
+ List<int> r = new List<int>();
+ if (enforceV2) {
+ foreach (int x in v2) {
+ if (v1.Contains(x) && !r.Contains(x)) {
+ r.Add(x);
+ }
+ }
+ } else {
+ foreach (int x in v1) {
+ foreach (int y in v2) {
+ if (x == y) {
+ if (!r.Contains(x)) {
+ r.Add(x);
+ }
+ break;
+ }
+ }
+ }
+ }
+ return r;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Basic implementation of IServerPolicy: it uses a single certificate
+ * chain and a software private key.
+ */
+
+public class SSLServerPolicyBasic : IServerPolicy {
+
+ byte[][] chain;
+ IPrivateKey skey;
+ bool canSign, canEncrypt;
+
+ /*
+ * Create the policy instance, with the provided certificate chain,
+ * private key, and allowed key usages.
+ */
+ public SSLServerPolicyBasic(byte[][] chain,
+ IPrivateKey skey, KeyUsage usage)
+ {
+ this.chain = chain;
+ this.skey = skey;
+ canEncrypt = false;
+ canSign = false;
+ switch (usage) {
+ case KeyUsage.EncryptOnly:
+ canEncrypt = true;
+ break;
+ case KeyUsage.SignOnly:
+ canSign = true;
+ break;
+ case KeyUsage.EncryptAndSign:
+ canEncrypt = true;
+ canSign = true;
+ break;
+ }
+ }
+
+ public IServerChoices Apply(SSLServer server)
+ {
+ /*
+ * Conditions for selecting a cipher suite:
+ *
+ * RSA canEncrypt, key is RSA
+ *
+ * ECDH canEncrypt, key is EC, curve supported
+ *
+ * ECDHE_RSA canSign, key is RSA, have hash+RSA algo
+ *
+ * ECDHE_ECDSA canSign, key is EC, have hash+ECDSA algo,
+ * curve supported
+ *
+ * The engine already filtered things, so we know that:
+ *
+ * - if an ECDHE suite is present, then there is a common
+ * supported curve;
+ *
+ * - if an ECDHE_RSA suite is present, then there is a
+ * common hash+RSA algorithm;
+ *
+ * - if an ECDHE_ECDSA suite is present, then there is a
+ * common hash+ECDSA algorithm.
+ *
+ * We must still walk the list of algorithm to determine the
+ * proper hash to use for signatures; we also need to check
+ * that our EC curve is supported by the client.
+ */
+ int curveID = -1;
+ if (skey is ECPrivateKey) {
+ curveID = SSL.CurveToID(((ECPrivateKey)skey).Curve);
+ }
+ bool canRSA = canEncrypt && (skey is RSAPrivateKey);
+ bool canECDH = canEncrypt && (skey is ECPrivateKey)
+ && server.ClientCurves.Contains(curveID);
+ bool canECDHE_RSA = canSign && (skey is RSAPrivateKey);
+ bool canECDHE_ECDSA = canSign && (skey is ECPrivateKey);
+
+ foreach (int cs in server.CommonCipherSuites) {
+ if (SSL.IsRSA(cs)) {
+ if (!canRSA) {
+ continue;
+ }
+ return new ChoicesRSA(server.ClientVersionMax,
+ cs, chain, skey as RSAPrivateKey);
+ } else if (SSL.IsECDH(cs)) {
+ if (!canECDH) {
+ continue;
+ }
+ return new ChoicesECDH(cs, chain,
+ skey as ECPrivateKey);
+ } else if (SSL.IsECDHE_RSA(cs)) {
+ if (!canECDHE_RSA) {
+ continue;
+ }
+ int hashAlgo;
+ if (server.Version <= SSL.TLS11) {
+ hashAlgo = SSL.MD5SHA1;
+ } else {
+ hashAlgo = SelectHash(
+ server.ClientHashAndSign,
+ SSL.RSA);
+ }
+ return new ChoicesSign(cs, chain,
+ hashAlgo, skey);
+ } else if (SSL.IsECDHE_ECDSA(cs)) {
+ if (!canECDHE_ECDSA) {
+ continue;
+ }
+ int hashAlgo;
+ if (server.Version <= SSL.TLS11) {
+ hashAlgo = SSL.SHA1;
+ } else {
+ hashAlgo = SelectHash(
+ server.ClientHashAndSign,
+ SSL.ECDSA);
+ }
+ return new ChoicesSign(cs, chain,
+ hashAlgo, skey);
+ }
+ }
+
+ throw new SSLException("No suitable cipher suite");
+ }
+
+ static int SelectHash(List<int> hsl, int sigAlg)
+ {
+ foreach (int x in hsl) {
+ if ((x & 0xFF) == sigAlg) {
+ return x >> 8;
+ }
+ }
+
+ /*
+ * This should never happen, because the offending
+ * cipher suites would have been filtered by the engine.
+ */
+ throw new Exception();
+ }
+}
+
+class ChoicesBase {
+
+ int cipherSuite;
+ byte[][] chain;
+
+ internal ChoicesBase(int cipherSuite, byte[][] chain)
+ {
+ this.cipherSuite = cipherSuite;
+ this.chain = chain;
+ }
+
+ public int GetCipherSuite()
+ {
+ return cipherSuite;
+ }
+
+ public byte[][] GetCertificateChain()
+ {
+ return chain;
+ }
+
+ public virtual byte[] DoKeyExchange(byte[] cke)
+ {
+ throw new Exception();
+ }
+
+ public virtual byte[] DoSign(byte[] ske,
+ out int hashAlgo, out int sigAlgo)
+ {
+ throw new Exception();
+ }
+}
+
+class ChoicesRSA : ChoicesBase, IServerChoices {
+
+ int clientVersionMax;
+ RSAPrivateKey rk;
+
+ internal ChoicesRSA(int clientVersionMax, int cipherSuite,
+ byte[][] chain, RSAPrivateKey rk)
+ : base(cipherSuite, chain)
+ {
+ this.clientVersionMax = clientVersionMax;
+ this.rk = rk;
+ }
+
+ public override byte[] DoKeyExchange(byte[] cke)
+ {
+ if (cke.Length < 59) {
+ throw new CryptoException(
+ "Invalid ClientKeyExchange (too short)");
+ }
+ RSA.DoPrivate(rk, cke);
+
+ /*
+ * Constant-time check for PKCS#1 v1.5 padding. z is set
+ * to -1 if the padding is correct, 0 otherwise. We also
+ * check the two first PMS byte (they should be equal to
+ * the maximum protocol version announced by the client
+ * in its ClientHello).
+ */
+ int z = 0;
+ z |= cke[0];
+ z |= cke[1] ^ 0x02;
+ for (int i = 2; i < cke.Length - 49; i ++) {
+ int y = cke[i];
+ z |= ~((y | -y) >> 31);
+ }
+ z |= cke[cke.Length - 49];
+ z |= cke[cke.Length - 48] ^ (clientVersionMax >> 8);
+ z |= cke[cke.Length - 47] ^ (clientVersionMax & 0xFF);
+ z = ~((z | -z) >> 31);
+
+ /*
+ * Get a random premaster, then overwrite it with the
+ * decrypted value, but only if the padding was correct.
+ */
+ byte[] pms = new byte[48];
+ RNG.GetBytes(pms);
+ for (int i = 0; i < 48; i ++) {
+ int x = pms[i];
+ int y = cke[cke.Length - 48 + i];
+ pms[i] = (byte)(x ^ (z & (x ^ y)));
+ }
+
+ return pms;
+ }
+}
+
+class ChoicesECDH : ChoicesBase, IServerChoices {
+
+ ECPrivateKey ek;
+
+ internal ChoicesECDH(int cipherSuite, byte[][] chain, ECPrivateKey ek)
+ : base(cipherSuite, chain)
+ {
+ this.ek = ek;
+ }
+
+ public override byte[] DoKeyExchange(byte[] cke)
+ {
+ ECCurve curve = ek.Curve;
+ byte[] tmp = new byte[curve.EncodedLength];
+ if (curve.Mul(cke, ek.X, tmp, false) == 0) {
+ throw new SSLException(
+ "Invalid ClientKeyExchange EC point value");
+ }
+ int xlen;
+ int xoff = curve.GetXoff(out xlen);
+ byte[] pms = new byte[xlen];
+ Array.Copy(tmp, xoff, pms, 0, xlen);
+ return pms;
+ }
+}
+
+class ChoicesSign : ChoicesBase, IServerChoices {
+
+ int hashAlgo;
+ IPrivateKey skey;
+
+ internal ChoicesSign(int cipherSuite, byte[][] chain,
+ int hashAlgo, IPrivateKey skey)
+ : base(cipherSuite, chain)
+ {
+ this.hashAlgo = hashAlgo;
+ this.skey = skey;
+ }
+
+ public override byte[] DoSign(byte[] ske,
+ out int hashAlgo, out int signAlgo)
+ {
+ hashAlgo = this.hashAlgo;
+ byte[] hv = Hash(hashAlgo, ske);
+ if (skey is RSAPrivateKey) {
+ RSAPrivateKey rk = skey as RSAPrivateKey;
+ signAlgo = SSL.RSA;
+ byte[] head;
+ switch (hashAlgo) {
+ case SSL.MD5SHA1: head = null; break;
+ case SSL.SHA1: head = RSA.PKCS1_SHA1; break;
+ case SSL.SHA224: head = RSA.PKCS1_SHA224; break;
+ case SSL.SHA256: head = RSA.PKCS1_SHA256; break;
+ case SSL.SHA384: head = RSA.PKCS1_SHA384; break;
+ case SSL.SHA512: head = RSA.PKCS1_SHA512; break;
+ default:
+ throw new Exception();
+ }
+ return RSA.Sign(rk, head, hv);
+ } else if (skey is ECPrivateKey) {
+ ECPrivateKey ek = skey as ECPrivateKey;
+ signAlgo = SSL.ECDSA;
+ return ECDSA.Sign(ek, null, hv);
+ } else {
+ throw new Exception("NYI");
+ }
+ }
+
+ static byte[] Hash(int hashAlgo, byte[] data)
+ {
+ switch (hashAlgo) {
+ case SSL.MD5SHA1:
+ byte[] hv = new byte[36];
+ MD5 md5 = new MD5();
+ SHA1 sha1 = new SHA1();
+ md5.Update(data);
+ md5.DoFinal(hv, 0);
+ sha1.Update(data);
+ sha1.DoFinal(hv, 16);
+ return hv;
+ case SSL.MD5:
+ return new MD5().Hash(data);
+ case SSL.SHA1:
+ return new SHA1().Hash(data);
+ case SSL.SHA224:
+ return new SHA224().Hash(data);
+ case SSL.SHA256:
+ return new SHA256().Hash(data);
+ case SSL.SHA384:
+ return new SHA384().Hash(data);
+ case SSL.SHA512:
+ return new SHA512().Hash(data);
+ default:
+ throw new Exception("NYI");
+ }
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SSLTLS {
+
+/*
+ * A basic implementation of an SSL session cache. It stores up to a
+ * predetermined number of sessions, and uses a least-recently-used
+ * eviction policy. Sessions are kept in RAM.
+ *
+ * Instances use locking to be thread-safe, so that multiple SSL server
+ * engines managed in different threads may share the same cache (though
+ * each engine, individually, is not thread-safe).
+ */
+
+public class SSLSessionCacheLRU : ISessionCache {
+
+ /*
+ * TODO: use a doubly-linked list for LRU policy. Right now,
+ * values are indexed in an array, and eviction/moving implies
+ * shifting many pointers in that array. This is fine for small
+ * caches, up to a few thousands of entries.
+ */
+
+ object mutex;
+ IDictionary<string, int> spx;
+ SSLSessionParameters[] data;
+ int count, maxCount;
+
+ public SSLSessionCacheLRU(int maxCount)
+ {
+ spx = new Dictionary<string, int>();
+ data = new SSLSessionParameters[maxCount];
+ count = 0;
+ this.maxCount = maxCount;
+ mutex = new object();
+ }
+
+ /* see ISessionCache */
+ public SSLSessionParameters Retrieve(byte[] id, string serverName)
+ {
+ lock (mutex) {
+ int x;
+ if (!spx.TryGetValue(IDToString(id), out x)) {
+ return null;
+ }
+ SSLSessionParameters sp = data[x];
+ if ((x + 1) < count) {
+ Array.Copy(data, x + 1,
+ data, x, count - x - 1);
+ data[count - 1] = sp;
+ }
+ return sp;
+ }
+ }
+
+ /* see ISessionCache */
+ public void Store(SSLSessionParameters sp)
+ {
+ lock (mutex) {
+ string ids = IDToString(sp.SessionID);
+ int x;
+ if (spx.TryGetValue(ids, out x)) {
+ if ((x + 1) < count) {
+ Array.Copy(data, x + 1,
+ data, x, count - x - 1);
+ }
+ spx[ids] = count - 1;
+ data[count - 1] = sp;
+ return;
+ }
+ if (count == maxCount) {
+ SSLSessionParameters esp = data[0];
+ Array.Copy(data, 1, data, 0, count - 1);
+ count --;
+ spx.Remove(IDToString(esp.SessionID));
+ }
+ spx[ids] = count;
+ data[count] = sp;
+ count ++;
+ }
+ }
+
+ static string IDToString(byte[] id)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (byte b in id) {
+ sb.AppendFormat("{0:x2}", b);
+ }
+ return sb.ToString();
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace SSLTLS {
+
+/*
+ * An SSLSessionParameters instance contains the "security parameters"
+ * for a SSL session. Instances are obtained from an open instance
+ * (after the handshake) and can be used to initialise new instances
+ * so as to attempt a session resumption.
+ */
+
+public class SSLSessionParameters {
+
+ /*
+ * Session ID (at most 32 bytes).
+ */
+ public byte[] SessionID {
+ get; set;
+ }
+
+ /*
+ * Protocol version.
+ */
+ public int Version {
+ get; set;
+ }
+
+ /*
+ * Used cipher suite.
+ */
+ public int CipherSuite {
+ get; set;
+ }
+
+ /*
+ * Server name attached to this session; it may be null.
+ */
+ public string ServerName {
+ get; set;
+ }
+
+ /*
+ * Negotiated master secret.
+ */
+ public byte[] MasterSecret {
+ get; set;
+ }
+
+ /*
+ * Create a new instance. The provided sessionID and masterSecret
+ * arrays are internally copied into new instances.
+ */
+ public SSLSessionParameters(byte[] sessionID, int version,
+ int cipherSuite, string serverName, byte[] masterSecret)
+ {
+ SessionID = IO.CopyBlob(sessionID);
+ Version = version;
+ CipherSuite = cipherSuite;
+ ServerName = serverName;
+ MasterSecret = IO.CopyBlob(masterSecret);
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+using Crypto;
+
+/*
+ * This is a "reference" implementation of Poly1305 that uses the
+ * generic ZInt code for computations. It is not constant-time, and
+ * it is very slow; it is meant to test other implementations.
+ *
+ * API is identical to the Poly1305 class.
+ */
+
+public class Poly1305Ref {
+
+ public ChaCha20 ChaCha {
+ get; set;
+ }
+
+ public Poly1305Ref()
+ {
+ }
+
+ static ZInt p = ((ZInt)1 << 130) - (ZInt)5;
+ static ZInt rmask = ((ZInt)1 << 124) - (ZInt)1
+ - ((ZInt)15 << 28) - ((ZInt)15 << 60) - ((ZInt)15 << 92)
+ - ((ZInt)3 << 32) - ((ZInt)3 << 64) - ((ZInt)3 << 96);
+
+ public void Run(byte[] iv,
+ byte[] data, int off, int len,
+ byte[] aad, int offAAD, int lenAAD,
+ byte[] tag, bool encrypt)
+ {
+ byte[] pkey = new byte[32];
+ ChaCha.Run(iv, 0, pkey);
+ if (encrypt) {
+ ChaCha.Run(iv, 1, data, off, len);
+ }
+
+ ByteSwap(pkey, 0, 16);
+ ZInt r = ZInt.DecodeUnsignedBE(pkey, 0, 16);
+ r &= rmask;
+ ZInt a = (ZInt)0;
+
+ a = RunInner(a, r, aad, offAAD, lenAAD);
+ a = RunInner(a, r, data, off, len);
+ byte[] foot = new byte[16];
+ foot[ 0] = (byte)lenAAD;
+ foot[ 1] = (byte)(lenAAD >> 8);
+ foot[ 2] = (byte)(lenAAD >> 16);
+ foot[ 3] = (byte)(lenAAD >> 24);
+ foot[ 8] = (byte)len;
+ foot[ 9] = (byte)(len >> 8);
+ foot[10] = (byte)(len >> 16);
+ foot[11] = (byte)(len >> 24);
+ a = RunInner(a, r, foot, 0, 16);
+
+ ByteSwap(pkey, 16, 16);
+ ZInt s = ZInt.DecodeUnsignedBE(pkey, 16, 16);
+ a += s;
+ a.ToBytesLE(tag, 0, 16);
+
+ if (!encrypt) {
+ ChaCha.Run(iv, 1, data, off, len);
+ }
+ }
+
+ ZInt RunInner(ZInt a, ZInt r, byte[] data, int off, int len)
+ {
+ byte[] tmp = new byte[16];
+ while (len > 0) {
+ if (len >= 16) {
+ Array.Copy(data, off, tmp, 0, 16);
+ } else {
+ Array.Copy(data, off, tmp, 0, len);
+ for (int i = len; i < 16; i ++) {
+ tmp[i] = 0;
+ }
+ }
+ ByteSwap(tmp, 0, 16);
+ ZInt v = ZInt.DecodeUnsignedBE(tmp) | ((ZInt)1 << 128);
+ a = ((a + v) * r) % p;
+ off += 16;
+ len -= 16;
+ }
+ return a;
+ }
+
+ static void ByteSwap(byte[] buf, int off, int len)
+ {
+ for (int i = 0; (i + i) < len; i ++) {
+ byte t = buf[off + i];
+ buf[off + i] = buf[off + len - 1 - i];
+ buf[off + len - 1 - i] = t;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+public class TestCrypto {
+
+ public static void Main(string[] args)
+ {
+ IDictionary<string, bool> d =
+ new SortedDictionary<string, bool>(
+ StringComparer.OrdinalIgnoreCase);
+ foreach (string a in args) {
+ StringBuilder sb = new StringBuilder();
+ foreach (char c in a.ToLowerInvariant()) {
+ if ((c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9'))
+ {
+ sb.Append(c);
+ }
+ }
+ d[sb.ToString()] = true;
+ }
+ bool all = (d.Count == 0);
+ try {
+ if (all || d.ContainsKey("md5")) {
+ TestMD5();
+ }
+ if (all || d.ContainsKey("sha1")) {
+ TestSHA1();
+ }
+ if (all || d.ContainsKey("sha224")) {
+ TestSHA224();
+ }
+ if (all || d.ContainsKey("sha256")) {
+ TestSHA256();
+ }
+ if (all || d.ContainsKey("sha384")) {
+ TestSHA384();
+ }
+ if (all || d.ContainsKey("sha512")) {
+ TestSHA512();
+ }
+ if (all || d.ContainsKey("hmac")) {
+ TestHMAC();
+ }
+ if (all || d.ContainsKey("hmacdrbg")) {
+ TestHMAC_DRBG();
+ }
+ if (all || d.ContainsKey("aes")) {
+ TestAES();
+ }
+ if (all || d.ContainsKey("des")) {
+ TestDES();
+ }
+ if (all || d.ContainsKey("chacha20")) {
+ TestChaCha20();
+ }
+ if (all || d.ContainsKey("poly1305")) {
+ TestPoly1305();
+ }
+ if (all || d.ContainsKey("ghash")) {
+ TestGHASH();
+ }
+ if (all || d.ContainsKey("int")) {
+ TestMath.TestModInt();
+ }
+ if (all || d.ContainsKey("rsa")) {
+ TestRSA();
+ }
+ if (all || d.ContainsKey("ec")) {
+ TestEC.TestECInt();
+ }
+ if (all || d.ContainsKey("ecdsa")) {
+ TestECDSA();
+ }
+ } catch (Exception e) {
+ Console.WriteLine(e.ToString());
+ Environment.Exit(1);
+ }
+ }
+
+ static void TestMD5()
+ {
+ Console.Write("Testing MD5... ");
+ DoKATHash(new MD5(), KAT_MD5);
+ Console.WriteLine("done.");
+ }
+
+ static void TestSHA1()
+ {
+ Console.Write("Testing SHA-1... ");
+ DoKATHash(new SHA1(), KAT_SHA1);
+ DoKATHashLong(new SHA1(), "34aa973cd4c4daa4f61eeb2bdbad27316534016f");
+ Console.WriteLine("done.");
+ }
+
+ static void TestSHA224()
+ {
+ Console.Write("Testing SHA-224... ");
+ DoKATHash(new SHA224(), KAT_SHA224);
+ DoKATHashLong(new SHA224(), "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67");
+ Console.WriteLine("done.");
+ }
+
+ static void TestSHA256()
+ {
+ Console.Write("Testing SHA-256... ");
+ DoKATHash(new SHA256(), KAT_SHA256);
+ DoKATHashLong(new SHA256(), "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
+ Console.WriteLine("done.");
+ }
+
+ static void TestSHA384()
+ {
+ Console.Write("Testing SHA-384... ");
+ DoKATHash(new SHA384(), KAT_SHA384);
+ DoKATHashLong(new SHA384(), "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985");
+ Console.WriteLine("done.");
+ }
+
+ static void TestSHA512()
+ {
+ Console.Write("Testing SHA-512... ");
+ DoKATHash(new SHA512(), KAT_SHA512);
+ DoKATHashLong(new SHA512(), "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b");
+ Console.WriteLine("done.");
+ }
+
+ static void DoKATHash(IDigest h, string[] katTab)
+ {
+ for (int i = 0; i < katTab.Length; i += 2) {
+ DoKATHash(h,
+ Encoding.UTF8.GetBytes(katTab[i]),
+ ToBin(katTab[i + 1]));
+ }
+ }
+
+ static void TestHMAC()
+ {
+ Console.Write("Testing HMAC... ");
+
+ DoKATHMAC(new HMAC(new MD5()),
+ ToBin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ Encoding.UTF8.GetBytes("Hi There"),
+ ToBin("9294727a3638bb1c13f48ef8158bfc9d"));
+ DoKATHMAC(new HMAC(new MD5()),
+ Encoding.UTF8.GetBytes("Jefe"),
+ Encoding.UTF8.GetBytes("what do ya want for nothing?"),
+ ToBin("750c783e6ab0b503eaa86e310a5db738"));
+ DoKATHMAC(new HMAC(new MD5()),
+ ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+ ToBin("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"),
+ ToBin("56be34521d144c88dbb8c733f0e8b3f6"));
+ DoKATHMAC(new HMAC(new MD5()),
+ ToBin("0102030405060708090a0b0c0d0e0f10111213141516171819"),
+ ToBin("CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD"),
+ ToBin("697eaf0aca3a3aea3a75164746ffaa79"));
+ DoKATHMAC(new HMAC(new MD5()),
+ ToBin("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+ Encoding.UTF8.GetBytes("Test With Truncation"),
+ ToBin("56461ef2342edc00f9bab995690efd4c"));
+ DoKATHMAC(new HMAC(new MD5()),
+ ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+ Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key - Hash Key First"),
+ ToBin("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd"));
+ DoKATHMAC(new HMAC(new MD5()),
+ ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+ Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"),
+ ToBin("6f630fad67cda0ee1fb1f562db3aa53e"));
+
+ DoKATHMAC(new HMAC(new SHA1()),
+ ToBin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ Encoding.UTF8.GetBytes("Hi There"),
+ ToBin("b617318655057264e28bc0b6fb378c8ef146be00"));
+ DoKATHMAC(new HMAC(new SHA1()),
+ Encoding.UTF8.GetBytes("Jefe"),
+ Encoding.UTF8.GetBytes("what do ya want for nothing?"),
+ ToBin("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"));
+ DoKATHMAC(new HMAC(new SHA1()),
+ ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+ ToBin("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"),
+ ToBin("125d7342b9ac11cd91a39af48aa17b4f63f175d3"));
+ DoKATHMAC(new HMAC(new SHA1()),
+ ToBin("0102030405060708090a0b0c0d0e0f10111213141516171819"),
+ ToBin("CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD"),
+ ToBin("4c9007f4026250c6bc8414f9bf50c86c2d7235da"));
+ DoKATHMAC(new HMAC(new SHA1()),
+ ToBin("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+ Encoding.UTF8.GetBytes("Test With Truncation"),
+ ToBin("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"));
+ DoKATHMAC(new HMAC(new SHA1()),
+ ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+ Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key - Hash Key First"),
+ ToBin("aa4ae5e15272d00e95705637ce8a3b55ed402112"));
+ DoKATHMAC(new HMAC(new SHA1()),
+ ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+ Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"),
+ ToBin("e8e99d0f45237d786d6bbaa7965c7808bbff1a91"));
+
+ TestHMAC_CT(new MD5());
+ TestHMAC_CT(new SHA1());
+ TestHMAC_CT(new SHA256());
+ TestHMAC_CT(new SHA512());
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestHMAC_CT(IDigest h)
+ {
+ Console.Write("[{0}]", h.Name);
+ int hlen = h.DigestSize;
+ HMAC hm = new HMAC(h);
+ byte[] key = new byte[hlen];
+ RNG.GetBytes(key);
+ hm.SetKey(key);
+ byte[] data = new byte[384];
+ RNG.GetBytes(data);
+ byte[] tmp1 = new byte[hlen];
+ byte[] tmp2 = new byte[hlen];
+ for (int i = 0; i < 256; i ++) {
+ int i1 = i >> 1;
+ int i2 = i - i1;
+ for (int j = 0; j <= 128; j ++) {
+ hm.Update(data, 0, i1);
+ hm.ComputeCT(data, i1, i2 + j, i2, i2 + j,
+ tmp1, 0);
+ hm.Update(data, 0, i + j);
+ hm.DoFinal(tmp2, 0);
+ CheckEq(tmp1, tmp2, "CT");
+ }
+ Console.Write(".");
+ }
+ }
+
+ static void TestHMAC_DRBG()
+ {
+ Console.Write("Testing HMAC_DRBG... ");
+ byte[] tmp = new byte[30];
+
+ HMAC_DRBG drbg = new HMAC_DRBG(new SHA256());
+ drbg.Update(ToBin("009A4D6792295A7F730FC3F2B49CBC0F62E862272F01795EDF0D54DB760F156D0DAC04C0322B3A204224"));
+ drbg.GetBytes(tmp);
+ CheckEq(tmp, ToBin("9305A46DE7FF8EB107194DEBD3FD48AA20D5E7656CBE0EA69D2A8D4E7C67"), "KAT 1");
+ drbg.GetBytes(tmp);
+ CheckEq(tmp, ToBin("C70C78608A3B5BE9289BE90EF6E81A9E2C1516D5751D2F75F50033E45F73"), "KAT 2");
+ drbg.GetBytes(tmp);
+ CheckEq(tmp, ToBin("475E80E992140567FCC3A50DAB90FE84BCD7BB03638E9C4656A06F37F650"), "KAT 3");
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestAES()
+ {
+ Console.Write("Testing AES... ");
+
+ AES bc = new AES();
+ DoKATBlockCipherRaw(bc, KAT_AES_RAW);
+ DoKATBlockCipherCBC(bc, KAT_AES_CBC);
+ DoKATBlockCipherCTR(bc, KAT_AES_CTR);
+
+ DoMonteCarloAESEncrypt(bc,
+ "139a35422f1d61de3c91787fe0507afd",
+ "b9145a768b7dc489a096b546f43b231f",
+ "fb2649694783b551eacd9d5db6126d47");
+ DoMonteCarloAESDecrypt(bc,
+ "0c60e7bf20ada9baa9e1ddf0d1540726",
+ "b08a29b11a500ea3aca42c36675b9785",
+ "d1d2bfdc58ffcad2341b095bce55221e");
+
+ DoMonteCarloAESEncrypt(bc,
+ "b9a63e09e1dfc42e93a90d9bad739e5967aef672eedd5da9",
+ "85a1f7a58167b389cddc8a9ff175ee26",
+ "5d1196da8f184975e240949a25104554");
+ DoMonteCarloAESDecrypt(bc,
+ "4b97585701c03fbebdfa8555024f589f1482c58a00fdd9fd",
+ "d0bd0e02ded155e4516be83f42d347a4",
+ "b63ef1b79507a62eba3dafcec54a6328");
+
+ DoMonteCarloAESEncrypt(bc,
+ "f9e8389f5b80712e3886cc1fa2d28a3b8c9cd88a2d4a54c6aa86ce0fef944be0",
+ "b379777f9050e2a818f2940cbbd9aba4",
+ "c5d2cb3d5b7ff0e23e308967ee074825");
+ DoMonteCarloAESDecrypt(bc,
+ "2b09ba39b834062b9e93f48373b8dd018dedf1e5ba1b8af831ebbacbc92a2643",
+ "89649bd0115f30bd878567610223a59d",
+ "e3d3868f578caf34e36445bf14cefc68");
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestDES()
+ {
+ Console.Write("Testing DES... ");
+
+ DES bc = new DES();
+ DoKATBlockCipherRaw(bc, KAT_DES_RAW);
+ DoKATBlockCipherCBC(bc, KAT_DES_CBC);
+ DoMonteCarloDESEncrypt(bc);
+ DoMonteCarloDESDecrypt(bc);
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestChaCha20()
+ {
+ Console.Write("Testing ChaCha20... ");
+
+ for (int i = 0; i < KAT_CHACHA20.Length; i += 5) {
+ byte[] key = ToBin(KAT_CHACHA20[i + 0]);
+ byte[] iv = ToBin(KAT_CHACHA20[i + 1]);
+ uint cc = UInt32.Parse(KAT_CHACHA20[i + 2]);
+ byte[] plain = ToBin(KAT_CHACHA20[i + 3]);
+ byte[] cipher = ToBin(KAT_CHACHA20[i + 4]);
+
+ ChaCha20 chacha = new ChaCha20();
+ chacha.SetKey(key);
+ byte[] tmp = new byte[plain.Length];
+
+ for (int j = 0; j <= plain.Length; j ++) {
+ for (int k = 0; k < tmp.Length; k ++) {
+ tmp[k] = 0;
+ }
+ Array.Copy(plain, 0, tmp, 0, j);
+ if (chacha.Run(iv, cc, tmp, 0, j)
+ != cc + (uint)((j + 63) >> 6))
+ {
+ throw new Exception(
+ "ChaCha20: wrong counter");
+ }
+ CheckEq(tmp, 0, cipher, 0, j, "KAT 1");
+ for (int k = j; k < tmp.Length; k ++) {
+ if (tmp[k] != 0) {
+ throw new Exception(
+ "ChaCha20: overrun");
+ }
+ }
+
+ uint cc2 = cc;
+ for (int k = 0; k < j; k += 64) {
+ int clen = Math.Min(64, j - k);
+ cc2 = chacha.Run(iv, cc2, tmp, k, clen);
+ }
+ CheckEq(tmp, 0, plain, 0, j, "KAT 2");
+ }
+ }
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestPoly1305()
+ {
+ Console.Write("Testing Poly1305... ");
+
+ byte[] plain = ToBin("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e");
+ byte[] aad = ToBin("50515253c0c1c2c3c4c5c6c7");
+ byte[] key = ToBin("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");
+ byte[] iv = ToBin("070000004041424344454647");
+ byte[] cipher = ToBin("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116");
+ byte[] tag = ToBin("1ae10b594f09e26a7e902ecbd0600691");
+
+ byte[] data = new byte[plain.Length];
+ Array.Copy(plain, 0, data, 0, plain.Length);
+ byte[] tt = new byte[16];
+
+ ChaCha20 cc = new ChaCha20();
+ cc.SetKey(key);
+ Poly1305 pp = new Poly1305();
+ pp.ChaCha = cc;
+ pp.Run(iv, data, 0, data.Length, aad, 0, aad.Length, tt, true);
+ CheckEq(data, cipher, "KAT 1");
+ CheckEq(tt, tag, "KAT 2");
+ for (int i = 0; i < tt.Length; i ++) {
+ tt[i] = 0;
+ }
+ pp.Run(iv, data, 0, data.Length, aad, 0, aad.Length, tt, false);
+ CheckEq(data, plain, "KAT 3");
+ CheckEq(tt, tag, "KAT 4");
+
+ /*
+ * Make random messages and keys, and test the implementation
+ * against the ZInt-based reference code.
+ */
+ Poly1305Ref ppref = new Poly1305Ref();
+ ppref.ChaCha = new ChaCha20();
+ data = new byte[100];
+ aad = new byte[data.Length];
+ byte[] tmp = new byte[data.Length];
+ key = new byte[32];
+ iv = new byte[12];
+ byte[] tag1 = new byte[16];
+ byte[] tag2 = new byte[16];
+ for (int i = 0; i < data.Length; i ++) {
+ RNG.GetBytes(key);
+ RNG.GetBytes(iv);
+ RNG.GetBytes(data, 0, i);
+ RNG.GetBytes(aad, 0, i);
+
+ Array.Copy(data, 0, tmp, 0, i);
+ for (int j = i; j < tmp.Length; j ++) {
+ tmp[j] = 0xFF;
+ }
+ pp.ChaCha.SetKey(key);
+ pp.Run(iv, tmp, 0, i, aad, 0, i, tag1, true);
+
+ for (int j = i; j < tmp.Length; j ++) {
+ tmp[j] = 0x00;
+ }
+ ppref.ChaCha.SetKey(key);
+ ppref.Run(iv, tmp, 0, i, aad, 0, i, tag2, false);
+
+ CheckEq(data, 0, tmp, 0, i, "cross enc/dec");
+ CheckEq(tag1, tag2, "cross MAC");
+ Console.Write(".");
+ }
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestGHASH()
+ {
+ Console.Write("Testing GHASH... ");
+
+ for (int i = 0; i < KAT_GHASH.Length; i += 4) {
+ byte[] h = ToBin(KAT_GHASH[i]);
+ byte[] a = ToBin(KAT_GHASH[i + 1]);
+ byte[] c = ToBin(KAT_GHASH[i + 2]);
+ byte[] r = ToBin(KAT_GHASH[i + 3]);
+ byte[] y = new byte[16];
+ byte[] p = new byte[16];
+ GHASH.Run(y, h, a);
+ GHASH.Run(y, h, c);
+ uint alen = (uint)a.Length << 3;
+ uint clen = (uint)c.Length << 3;
+ p[ 4] = (byte)(alen >> 24);
+ p[ 5] = (byte)(alen >> 16);
+ p[ 6] = (byte)(alen >> 8);
+ p[ 7] = (byte)alen;
+ p[12] = (byte)(clen >> 24);
+ p[13] = (byte)(clen >> 16);
+ p[14] = (byte)(clen >> 8);
+ p[15] = (byte)clen;
+ GHASH.Run(y, h, p);
+ CheckEq(y, r, "KAT " + (i / 4 + 1));
+ }
+
+ Console.WriteLine("done.");
+ }
+
+ static void TestRSA()
+ {
+ Console.Write("Testing RSA... ");
+
+ DoRSASelfTests(1024);
+ DoRSASelfTests(1231);
+ DoRSASelfTests(2048);
+
+ Console.WriteLine("done.");
+ }
+
+ static void PrintInt(string name, byte[] x)
+ {
+ Console.Write("{0} = 0x", name);
+ foreach (byte b in x) {
+ Console.Write("{0:X2}", b);
+ }
+ Console.WriteLine();
+ }
+
+ static ZInt MakePrime(int size)
+ {
+ ZInt m = ((ZInt)3 << (size - 2)) + (ZInt)3;
+ for (;;) {
+ ZInt r = ZInt.MakeRand(size - 4);
+ ZInt p = (r << 2) + m;
+ if (p % (ZInt)65537 != 1 && p.IsPrime) {
+ return p;
+ }
+ }
+ }
+
+ static ZInt ExtendedGCD(ZInt a, ZInt b, out ZInt u, out ZInt v)
+ {
+ ZInt s = 0, sp = 1;
+ ZInt t = 1, tp = 0;
+ while (b != 0) {
+ ZInt q = a / b;
+ ZInt bn = a - q * b;
+ ZInt sn = sp - q * s;
+ ZInt tn = tp - q * t;
+ a = b;
+ sp = s;
+ tp = t;
+ b = bn;
+ s = sn;
+ t = tn;
+ }
+ u = sp;
+ v = tp;
+ return a;
+ }
+
+ static ZInt ModInverse(ZInt x, ZInt n)
+ {
+ ZInt u, v;
+ ZInt d = ExtendedGCD(x, n, out u, out v);
+ if (x * u + v * n != d) {
+ throw new Exception("bad extended GCD");
+ }
+ if (d != 1) {
+ throw new Exception("not invertible");
+ }
+ return u.Mod(n);
+ }
+
+ static void DoRSASelfTests(int size)
+ {
+ Console.Write(" key gen ({0}): ", size);
+ long begin = DateTime.UtcNow.Ticks;
+
+ /*
+ * Use ZInt to generate new RSA key.
+ */
+ ZInt p = MakePrime(size - (size >> 1));
+ ZInt q;
+ do {
+ q = MakePrime(size >> 1);
+ } while (p == q);
+ if (p < q) {
+ ZInt t = p;
+ p = q;
+ q = t;
+ }
+ ZInt n = p * q;
+ ZInt e = 65537;
+ ZInt d = ModInverse(65537, (p - 1) * (q - 1));
+ ZInt dp = d % (p - 1);
+ ZInt dq = d % (q - 1);
+ ZInt iq = ModInverse(q, p);
+
+ long end = DateTime.UtcNow.Ticks;
+ Console.WriteLine("done in {0} ms.", (end - begin) / 10000);
+
+ /*
+ Console.WriteLine("n = {0}", n);
+ Console.WriteLine("e = {0}", e);
+ Console.WriteLine("d = {0}", d);
+ Console.WriteLine("p = {0}", p);
+ Console.WriteLine("q = {0}", q);
+ Console.WriteLine("dp = {0}", dp);
+ Console.WriteLine("dq = {0}", dq);
+ Console.WriteLine("iq = {0}", iq);
+ */
+
+ RSAPrivateKey sk = new RSAPrivateKey(
+ n.ToBytesUnsignedBE(),
+ e.ToBytesUnsignedBE(),
+ d.ToBytesUnsignedBE(),
+ p.ToBytesUnsignedBE(),
+ q.ToBytesUnsignedBE(),
+ dp.ToBytesUnsignedBE(),
+ dq.ToBytesUnsignedBE(),
+ iq.ToBytesUnsignedBE());
+
+ /*
+ Console.Write(" key gen ({0}): ", size);
+ long begin = DateTime.UtcNow.Ticks;
+ RSAPrivateKey sk = RSA.Generate(size);
+ long end = DateTime.UtcNow.Ticks;
+ Console.WriteLine("done in {0} ms.", (end - begin) / 10000);
+ */
+
+ /*
+ PrintInt("n ", sk.N);
+ PrintInt("e ", sk.E);
+ PrintInt("d ", sk.D);
+ PrintInt("p ", sk.P);
+ PrintInt("q ", sk.Q);
+ PrintInt("dp", sk.DP);
+ PrintInt("dq", sk.DQ);
+ PrintInt("iq", sk.IQ);
+ */
+
+ RSAPublicKey pk = (RSAPublicKey)sk.PublicKey;
+ for (int i = 0; i < ((size + 7) >> 3) - 11; i ++) {
+ byte[] msg = new byte[i];
+ RNG.GetBytes(msg);
+ IDigest h = new SHA256();
+ h.Update(msg);
+ byte[] hv = h.DoFinal();
+ byte[] sig = RSA.Sign(sk, RSA.PKCS1_SHA256, hv);
+ if (!RSA.Verify(pk, RSA.PKCS1_SHA256, null, hv, sig)) {
+ throw new Exception(String.Format(
+ "RSA sign/verify 1 (len = {0})", i));
+ }
+ if (!RSA.Verify(pk, RSA.PKCS1_SHA256_ALT,
+ RSA.PKCS1_SHA256, hv, sig))
+ {
+ throw new Exception(String.Format(
+ "RSA sign/verify 2 (len = {0})", i));
+ }
+ if (RSA.Verify(pk, RSA.PKCS1_SHA1, null, hv, sig)) {
+ throw new Exception(String.Format(
+ "RSA sign/verify 3 (len = {0})", i));
+ }
+ hv[21] ^= 0x01;
+ if (RSA.Verify(pk, RSA.PKCS1_SHA256, null, hv, sig)) {
+ throw new Exception(String.Format(
+ "RSA sign/verify 4 (len = {0})", i));
+ }
+
+ byte[] enc = RSA.Encrypt(pk, msg);
+ byte[] dec = RSA.Decrypt(sk, enc);
+ if (!Eq(msg, dec)) {
+ throw new Exception(String.Format(
+ "RSA encrypt/decrypt (len = {0})", i));
+ }
+ }
+ }
+
+ static void TestECDSA()
+ {
+ Console.WriteLine("Testing ECDSA... ");
+
+ DoKATECDSA(NIST.P256, ECDSA_K_P256, ECDSA_SIGS_P256);
+ DoKATECDSA(NIST.P384, ECDSA_K_P384, ECDSA_SIGS_P384);
+ DoKATECDSA(NIST.P521, ECDSA_K_P521, ECDSA_SIGS_P521);
+ DoECDSASelfTests(NIST.P256);
+ DoECDSASelfTests(NIST.P384);
+ DoECDSASelfTests(NIST.P521);
+
+ Console.WriteLine("done.");
+ }
+
+ static void DoKATECDSA(ECCurve curve, string[] ks, string[] sigs)
+ {
+ Console.Write(" KAT ({0}): ", curve.Name);
+ ECPublicKey pk = new ECPublicKey(curve, ToBin(ks[0]));
+ pk.CheckValid();
+ Console.Write("<valid pub> ");
+ ECPrivateKey sk = new ECPrivateKey(curve, ToBin(ks[1]));
+ sk.CheckValid();
+ Console.Write("<valid priv> ");
+ IPublicKey pk2 = sk.PublicKey;
+ if (!pk.Equals(pk2) || !pk2.Equals(pk)) {
+ throw new Exception("ECDSA mismatch public/private");
+ }
+ if (!pk.Equals(pk2) || !pk2.Equals(pk)) {
+ throw new Exception("ECDSA mismatch public/private");
+ }
+ for (int i = 0; i < 10; i ++) {
+ byte[] r = ToBin(sigs[i << 1]);
+ byte[] s = ToBin(sigs[(i << 1) + 1]);
+ byte[] msg = Encoding.UTF8.GetBytes(
+ (i < 5) ? "sample" : "test");
+ IDigest dig;
+ switch (i % 5) {
+ case 0: dig = new SHA1(); break;
+ case 1: dig = new SHA224(); break;
+ case 2: dig = new SHA256(); break;
+ case 3: dig = new SHA384(); break;
+ default: dig = new SHA512(); break;
+ }
+ dig.Update(msg);
+ byte[] hv = dig.DoFinal();
+ DoKATECDSA(pk, sk, dig, r, s, hv);
+ Console.Write(".");
+ }
+ Console.WriteLine();
+ }
+
+ static void DoKATECDSA(ECPublicKey pk, ECPrivateKey sk,
+ IDigest dig, byte[] r, byte[] s, byte[] hv)
+ {
+ if (r.Length != s.Length) {
+ throw new ArgumentException();
+ }
+ byte[] sig1 = new byte[r.Length + s.Length];
+ Array.Copy(r, 0, sig1, 0, r.Length);
+ Array.Copy(s, 0, sig1, r.Length, s.Length);
+ byte[] sig2 = ECDSA.SigRawToAsn1(sig1);
+ byte[] sig3 = ECDSA.SigRawToAsn1(ECDSA.SigAsn1ToRaw(sig2));
+ if (!Eq(sig2, sig3)) {
+ throw new Exception("ECDSA sig enc/dec");
+ }
+
+ if (!ECDSA.VerifyRaw(pk, hv, sig1)) {
+ throw new Exception("ECDSA verify 1");
+ }
+ if (!ECDSA.Verify(pk, hv, sig2)) {
+ throw new Exception("ECDSA verify 2");
+ }
+ byte[] hv2 = new byte[hv.Length + 2];
+ Array.Copy(hv, 0, hv2, 1, hv.Length);
+ if (!ECDSA.VerifyRaw(pk, hv2, 1, hv.Length, sig1)) {
+ throw new Exception("ECDSA verify 3");
+ }
+ hv2[1] ^= (byte)0x02;
+ if (ECDSA.VerifyRaw(pk, hv2, 1, hv.Length, sig1)) {
+ throw new Exception("ECDSA verify 4");
+ }
+
+ byte[] sig4 = ECDSA.SignRaw(sk, null, hv);
+ if (Eq(sig1, sig4)) {
+ throw new Exception("ECDSA sig randomized");
+ }
+ if (!ECDSA.VerifyRaw(pk, hv, sig4)) {
+ throw new Exception("ECDSA verify 5");
+ }
+ byte[] sig5 = ECDSA.SignRaw(sk, dig, hv);
+ if (!Eq(sig1, sig5)) {
+ throw new Exception("ECDSA sig deterministic");
+ }
+ }
+
+ static void DoECDSASelfTests(ECCurve curve)
+ {
+ Console.Write(" self ({0}): ", curve.Name);
+ ECPrivateKey sk = ECDSA.Generate(curve);
+ sk.CheckValid();
+ Console.Write("<valid> ");
+ ECPublicKey pk = sk.PublicKey;
+ IDigest h = new SHA256();
+ byte[] msg = new byte[32];
+ for (int i = 0; i < 10; i ++) {
+ RNG.GetBytes(msg);
+ byte[] hv = h.Hash(msg);
+ byte[] sig = ECDSA.Sign(sk, null, hv);
+ if (!ECDSA.Verify(pk, hv, sig)) {
+ throw new Exception("ECDSA sign/verify");
+ }
+ Console.Write(".");
+ }
+ Console.WriteLine();
+ }
+
+ static void DoKATHash(IDigest h, byte[] data, byte[] refOut)
+ {
+ h.Update(data);
+ CheckEq(h.DoFinal(), refOut, "KAT 1");
+ h.Update(data, 0, data.Length);
+ byte[] tmp = new byte[h.DigestSize];
+ h.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 2");
+ foreach (byte b in data) {
+ h.Update(b);
+ }
+ CheckEq(h.DoFinal(), refOut, "KAT 3");
+ for (int t = 0; t < data.Length; t ++) {
+ h.Update(data, 0, t);
+ h.Update(data, t, data.Length - t);
+ h.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 4." + t);
+ }
+ foreach (byte b in data) {
+ h.Update(b);
+ h.DoPartial(tmp, 0);
+ }
+ CheckEq(tmp, refOut, "KAT 5");
+ h.Reset();
+ h.Update(data);
+ CheckEq(h.DoFinal(), refOut, "KAT 6");
+ for (int t = 0; t < data.Length; t ++) {
+ h.Update(data, 0, t);
+ IDigest h2 = h.Dup();
+ h.Update(data, t, data.Length - t);
+ h.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 7." + t);
+ h2.Update(data, t, data.Length - t);
+ h2.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 8." + t);
+ }
+ }
+
+ static void DoKATHashLong(IDigest h, string refOut)
+ {
+ byte[] buf = new byte[1000];
+ for (int i = 0; i < buf.Length; i ++) {
+ buf[i] = (byte)'a';
+ }
+ for (int i = 0; i < 1000; i ++) {
+ h.Update(buf);
+ }
+ CheckEq(h.DoFinal(), ToBin(refOut), "KAT Long");
+ }
+
+ static void DoKATHMAC(HMAC hm, byte[] key, byte[] data, byte[] refOut)
+ {
+ hm.SetKey(key);
+ hm.Update(data);
+ CheckEq(hm.DoFinal(), refOut, "KAT 1");
+ hm.Update(data, 0, data.Length);
+ byte[] tmp = new byte[hm.MACSize];
+ hm.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 2");
+ foreach (byte b in data) {
+ hm.Update(b);
+ }
+ CheckEq(hm.DoFinal(), refOut, "KAT 3");
+ for (int t = 0; t < data.Length; t ++) {
+ hm.Update(data, 0, t);
+ hm.Update(data, t, data.Length - t);
+ hm.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 4." + t);
+ }
+ for (int t = 0; t < data.Length; t ++) {
+ hm.Update(data, 0, t);
+ HMAC hm2 = hm.Dup();
+ hm.Update(data, t, data.Length - t);
+ hm.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 5." + t);
+ hm2.Update(data, t, data.Length - t);
+ hm2.DoFinal(tmp, 0);
+ CheckEq(tmp, refOut, "KAT 6." + t);
+ }
+ }
+
+ static void DoKATBlockCipherRaw(IBlockCipher bc, string[] kat)
+ {
+ for (int i = 0; i < kat.Length; i += 3) {
+ byte[] key = ToBin(kat[i]);
+ byte[] plain = ToBin(kat[i + 1]);
+ byte[] cipher = ToBin(kat[i + 2]);
+ int blen = bc.BlockSize;
+ if (blen != plain.Length || blen != cipher.Length) {
+ throw new Exception(string.Format(
+ "block size mismatch: {0} / {1},{2}",
+ blen, plain.Length, cipher.Length));
+ }
+ bc.SetKey(key);
+ byte[] tmp = new byte[blen];
+ Array.Copy(plain, 0, tmp, 0, blen);
+ bc.BlockEncrypt(tmp, 0);
+ CheckEq(tmp, cipher, "KAT encrypt");
+ bc.BlockDecrypt(tmp, 0);
+ CheckEq(tmp, plain, "KAT decrypt");
+ }
+ }
+
+ static void DoKATBlockCipherCBC(IBlockCipher bc, string[] kat)
+ {
+ for (int i = 0; i < kat.Length; i += 4) {
+ byte[] key = ToBin(kat[i]);
+ byte[] iv = ToBin(kat[i + 1]);
+ byte[] plain = ToBin(kat[i + 2]);
+ byte[] cipher = ToBin(kat[i + 3]);
+ int blen = bc.BlockSize;
+ if (blen != iv.Length
+ || (plain.Length % blen) != 0
+ || (cipher.Length % blen) != 0
+ || plain.Length != cipher.Length)
+ {
+ throw new Exception(string.Format(
+ "block size mismatch:"
+ + " {0} / {1},{2},{3}",
+ blen, iv.Length,
+ plain.Length, cipher.Length));
+ }
+ bc.SetKey(key);
+ byte[] tmp = new byte[plain.Length];
+ Array.Copy(plain, 0, tmp, 0, tmp.Length);
+ bc.CBCEncrypt(iv, tmp);
+ CheckEq(tmp, cipher, "KAT CBC encrypt (1)");
+ bc.CBCDecrypt(iv, tmp);
+ CheckEq(tmp, plain, "KAT CBC decrypt (1)");
+
+ byte[] iv2 = new byte[blen];
+ Array.Copy(iv, 0, iv2, 0, blen);
+ for (int j = 0; j < tmp.Length; j += blen) {
+ bc.CBCEncrypt(iv2, tmp, j, blen);
+ Array.Copy(tmp, j, iv2, 0, blen);
+ }
+ CheckEq(tmp, cipher, "KAT CBC encrypt (2)");
+ byte[] iv3 = new byte[blen];
+ Array.Copy(iv, 0, iv2, 0, blen);
+ for (int j = 0; j < tmp.Length; j += blen) {
+ Array.Copy(tmp, j, iv3, 0, blen);
+ bc.CBCDecrypt(iv2, tmp, j, blen);
+ Array.Copy(iv3, 0, iv2, 0, blen);
+ }
+ CheckEq(tmp, plain, "KAT CBC decrypt (2)");
+ }
+ }
+
+ static void DoKATBlockCipherCTR(IBlockCipher bc, string[] kat)
+ {
+ for (int i = 0; i < kat.Length; i += 4) {
+ byte[] key = ToBin(kat[i]);
+ byte[] iv = ToBin(kat[i + 1]);
+ byte[] plain = ToBin(kat[i + 2]);
+ byte[] cipher = ToBin(kat[i + 3]);
+ int blen = bc.BlockSize;
+ if (blen != (iv.Length + 4)
+ || plain.Length != cipher.Length)
+ {
+ throw new Exception(string.Format(
+ "block size mismatch:"
+ + " {0} / {1},{2},{3}",
+ blen, iv.Length,
+ plain.Length, cipher.Length));
+ }
+ bc.SetKey(key);
+ byte[] tmp = new byte[plain.Length];
+ Array.Copy(plain, 0, tmp, 0, tmp.Length);
+ uint cc;
+ cc = bc.CTRRun(iv, 1, tmp);
+ CheckEq(tmp, cipher, "KAT CTR encrypt (1)");
+ if (cc != 1 + ((tmp.Length + blen - 1) / blen)) {
+ throw new Exception(string.Format(
+ "wrong CTR counter: {0} / {1}",
+ cc, tmp.Length));
+ }
+ cc = bc.CTRRun(iv, 1, tmp);
+ CheckEq(tmp, plain, "KAT CTR decrypt (1)");
+ if (cc != 1 + ((tmp.Length + blen - 1) / blen)) {
+ throw new Exception(string.Format(
+ "wrong CTR counter: {0} / {1}",
+ cc, tmp.Length));
+ }
+
+ cc = 1;
+ for (int j = 0; j < tmp.Length; j += blen) {
+ int clen = Math.Min(blen, tmp.Length - j);
+ uint cc2 = bc.CTRRun(iv, cc, tmp, j, clen);
+ if (cc2 != cc + 1) {
+ throw new Exception(
+ "wrong CTR counter update");
+ }
+ cc = cc2;
+ }
+ CheckEq(tmp, cipher, "KAT CTR encrypt (2)");
+ }
+ }
+
+ static void DoMonteCarloAESEncrypt(IBlockCipher bc,
+ string skey, string splain, string scipher)
+ {
+ byte[] key = ToBin(skey);
+ byte[] buf = ToBin(splain);
+ byte[] pbuf = new byte[buf.Length];
+ byte[] cipher = ToBin(scipher);
+ for (int i = 0; i < 100; i ++) {
+ bc.SetKey(key);
+ for (int j = 0; j < 1000; j ++) {
+ Array.Copy(buf, 0, pbuf, 0, pbuf.Length);
+ bc.BlockEncrypt(buf);
+ }
+ switch (key.Length) {
+ case 16:
+ for (int k = 0; k < 16; k ++) {
+ key[k] ^= buf[k];
+ }
+ break;
+ case 24:
+ for (int k = 0; k < 8; k ++) {
+ key[k] ^= pbuf[8 + k];
+ }
+ for (int k = 0; k < 16; k ++) {
+ key[8 + k] ^= buf[k];
+ }
+ break;
+ case 32:
+ for (int k = 0; k < 16; k ++) {
+ key[k] ^= pbuf[k];
+ }
+ for (int k = 0; k < 16; k ++) {
+ key[16 + k] ^= buf[k];
+ }
+ break;
+ }
+ Console.Write(".");
+ }
+ Console.Write(" ");
+ CheckEq(buf, cipher, "MC AES encrypt");
+ }
+
+ static void DoMonteCarloAESDecrypt(IBlockCipher bc,
+ string skey, string splain, string scipher)
+ {
+ byte[] key = ToBin(skey);
+ byte[] buf = ToBin(splain);
+ byte[] pbuf = new byte[buf.Length];
+ byte[] cipher = ToBin(scipher);
+ for (int i = 0; i < 100; i ++) {
+ bc.SetKey(key);
+ for (int j = 0; j < 1000; j ++) {
+ Array.Copy(buf, 0, pbuf, 0, pbuf.Length);
+ bc.BlockDecrypt(buf);
+ }
+ switch (key.Length) {
+ case 16:
+ for (int k = 0; k < 16; k ++) {
+ key[k] ^= buf[k];
+ }
+ break;
+ case 24:
+ for (int k = 0; k < 8; k ++) {
+ key[k] ^= pbuf[8 + k];
+ }
+ for (int k = 0; k < 16; k ++) {
+ key[8 + k] ^= buf[k];
+ }
+ break;
+ case 32:
+ for (int k = 0; k < 16; k ++) {
+ key[k] ^= pbuf[k];
+ }
+ for (int k = 0; k < 16; k ++) {
+ key[16 + k] ^= buf[k];
+ }
+ break;
+ }
+ Console.Write(".");
+ }
+ Console.Write(" ");
+ CheckEq(buf, cipher, "MC AES decrypt");
+ }
+
+ static void DoMonteCarloDESEncrypt(IBlockCipher bc)
+ {
+ byte[] k1 = ToBin("9ec2372c86379df4");
+ byte[] k2 = ToBin("ad7ac4464f73805d");
+ byte[] k3 = ToBin("20c4f87564527c91");
+ byte[] buf = ToBin("b624d6bd41783ab1");
+ byte[] cipher = ToBin("eafd97b190b167fe");
+ byte[] key = new byte[24];
+ for (int i = 0; i < 400; i ++) {
+ Array.Copy(k1, 0, key, 0, 8);
+ Array.Copy(k2, 0, key, 8, 8);
+ Array.Copy(k3, 0, key, 16, 8);
+ bc.SetKey(key);
+ for (int j = 0; j < 10000; j ++) {
+ bc.BlockEncrypt(buf, 0);
+ switch (j) {
+ case 9997:
+ for (int n = 0; n < 8; n ++) {
+ k3[n] ^= buf[n];
+ }
+ break;
+ case 9998:
+ for (int n = 0; n < 8; n ++) {
+ k2[n] ^= buf[n];
+ }
+ break;
+ case 9999:
+ for (int n = 0; n < 8; n ++) {
+ k1[n] ^= buf[n];
+ }
+ break;
+ }
+ }
+ Console.Write(".");
+ }
+ Console.Write(" ");
+ CheckEq(buf, cipher, "MC DES encrypt");
+ }
+
+ static void DoMonteCarloDESDecrypt(IBlockCipher bc)
+ {
+ byte[] k1 = ToBin("79b63486e0ce37e0");
+ byte[] k2 = ToBin("08e65231abae3710");
+ byte[] k3 = ToBin("1f5eb69e925ef185");
+ byte[] buf = ToBin("2783aa729432fe96");
+ byte[] cipher = ToBin("44937ca532cdbf98");
+ byte[] key = new byte[24];
+ for (int i = 0; i < 400; i ++) {
+ Array.Copy(k1, 0, key, 0, 8);
+ Array.Copy(k2, 0, key, 8, 8);
+ Array.Copy(k3, 0, key, 16, 8);
+ bc.SetKey(key);
+ for (int j = 0; j < 10000; j ++) {
+ bc.BlockDecrypt(buf, 0);
+ switch (j) {
+ case 9997:
+ for (int n = 0; n < 8; n ++) {
+ k3[n] ^= buf[n];
+ }
+ break;
+ case 9998:
+ for (int n = 0; n < 8; n ++) {
+ k2[n] ^= buf[n];
+ }
+ break;
+ case 9999:
+ for (int n = 0; n < 8; n ++) {
+ k1[n] ^= buf[n];
+ }
+ break;
+ }
+ }
+ Console.Write(".");
+ }
+ Console.Write(" ");
+ CheckEq(buf, cipher, "MC DES decrypt");
+ }
+
+ static bool Eq(byte[] a1, byte[] a2)
+ {
+ if (a1 == a2) {
+ return true;
+ }
+ if (a1 == null || a2 == null) {
+ return false;
+ }
+ int n = a1.Length;
+ if (n != a2.Length) {
+ return false;
+ }
+ for (int i = 0; i < n; i ++) {
+ if (a1[i] != a2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static bool Eq(byte[] a1, int off1, byte[] a2, int off2, int len)
+ {
+ for (int i = 0; i < len; i ++) {
+ if (a1[off1 + i] != a2[off1 + i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static void CheckEq(byte[] a1, byte[] a2, string msg)
+ {
+ if (Eq(a1, a2)) {
+ return;
+ }
+ throw new Exception(string.Format(
+ "Not equal ({0}):\nv1 = {1}\nv2 = {2}",
+ msg, ToHex(a1), ToHex(a2)));
+ }
+
+ static void CheckEq(byte[] a1, int off1,
+ byte[] a2, int off2, int len, string msg)
+ {
+ if (Eq(a1, off1, a2, off2, len)) {
+ return;
+ }
+ throw new Exception(string.Format(
+ "Not equal ({0}):\nv1 = {1}\nv2 = {2}",
+ msg, ToHex(a1, off1, len), ToHex(a2, off2, len)));
+ }
+
+ static byte[] ToBin(string str)
+ {
+ MemoryStream ms = new MemoryStream();
+ bool z = true;
+ int acc = 0;
+ foreach (char c in str) {
+ int d;
+ if (c >= '0' && c <= '9') {
+ d = c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ d = c - ('A' - 10);
+ } else if (c >= 'a' && c <= 'f') {
+ d = c - ('a' - 10);
+ } else if (c == ' ' || c == '\t' || c == ':') {
+ continue;
+ } else {
+ throw new ArgumentException(String.Format(
+ "not hex: U+{0:X4}", (int)c));
+ }
+ if (z) {
+ acc = d;
+ } else {
+ ms.WriteByte((byte)((acc << 4) + d));
+ }
+ z = !z;
+ }
+ if (!z) {
+ throw new ArgumentException("final half byte");
+ }
+ return ms.ToArray();
+ }
+
+ static string ToHex(byte[] buf)
+ {
+ return ToHex(buf, 0, buf.Length);
+ }
+
+ static string ToHex(byte[] buf, int off, int len)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < len; i ++) {
+ sb.AppendFormat("{0:X2}", buf[off + i]);
+ }
+ return sb.ToString();
+ }
+
+ static string[] KAT_MD5 = {
+ "",
+ "d41d8cd98f00b204e9800998ecf8427e",
+ "a",
+ "0cc175b9c0f1b6a831c399e269772661",
+ "abc",
+ "900150983cd24fb0d6963f7d28e17f72",
+ "message digest",
+ "f96b697d7cb7938d525a2f31aaf161d0",
+ "abcdefghijklmnopqrstuvwxyz",
+ "c3fcd3d76192e4007dfb496cca67e13b",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+ "d174ab98d277d9f5a5611c2c9f419d9f",
+ "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+ "57edf4a22be3c955ac49da2e2107b67a"
+ };
+
+ static string[] KAT_SHA1 = {
+ "abc",
+ "a9993e364706816aba3e25717850c26c9cd0d89d",
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "84983e441c3bd26ebaae4aa1f95129e5e54670f1"
+ };
+
+ static string[] KAT_SHA224 = {
+ "abc",
+ "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"
+ };
+
+ static string[] KAT_SHA256 = {
+ "abc",
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
+ };
+
+ static string[] KAT_SHA384 = {
+ "abc",
+ "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039"
+ };
+
+ static string[] KAT_SHA512 = {
+ "abc",
+ "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
+ };
+
+ /*
+ * AES known-answer tests.
+ * Order: key, plaintext, ciphertext.
+ */
+ static string[] KAT_AES_RAW = {
+ /*
+ * From FIPS-197.
+ */
+ "000102030405060708090a0b0c0d0e0f",
+ "00112233445566778899aabbccddeeff",
+ "69c4e0d86a7b0430d8cdb78070b4c55a",
+
+ "000102030405060708090a0b0c0d0e0f1011121314151617",
+ "00112233445566778899aabbccddeeff",
+ "dda97ca4864cdfe06eaf70a0ec0d7191",
+
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "00112233445566778899aabbccddeeff",
+ "8ea2b7ca516745bfeafc49904b496089",
+
+ /*
+ * From NIST validation suite (ECBVarTxt128.rsp).
+ */
+ "00000000000000000000000000000000",
+ "80000000000000000000000000000000",
+ "3ad78e726c1ec02b7ebfe92b23d9ec34",
+
+ "00000000000000000000000000000000",
+ "c0000000000000000000000000000000",
+ "aae5939c8efdf2f04e60b9fe7117b2c2",
+
+ "00000000000000000000000000000000",
+ "e0000000000000000000000000000000",
+ "f031d4d74f5dcbf39daaf8ca3af6e527",
+
+ "00000000000000000000000000000000",
+ "f0000000000000000000000000000000",
+ "96d9fd5cc4f07441727df0f33e401a36",
+
+ "00000000000000000000000000000000",
+ "f8000000000000000000000000000000",
+ "30ccdb044646d7e1f3ccea3dca08b8c0",
+
+ "00000000000000000000000000000000",
+ "fc000000000000000000000000000000",
+ "16ae4ce5042a67ee8e177b7c587ecc82",
+
+ "00000000000000000000000000000000",
+ "fe000000000000000000000000000000",
+ "b6da0bb11a23855d9c5cb1b4c6412e0a",
+
+ "00000000000000000000000000000000",
+ "ff000000000000000000000000000000",
+ "db4f1aa530967d6732ce4715eb0ee24b",
+
+ "00000000000000000000000000000000",
+ "ff800000000000000000000000000000",
+ "a81738252621dd180a34f3455b4baa2f",
+
+ "00000000000000000000000000000000",
+ "ffc00000000000000000000000000000",
+ "77e2b508db7fd89234caf7939ee5621a",
+
+ "00000000000000000000000000000000",
+ "ffe00000000000000000000000000000",
+ "b8499c251f8442ee13f0933b688fcd19",
+
+ "00000000000000000000000000000000",
+ "fff00000000000000000000000000000",
+ "965135f8a81f25c9d630b17502f68e53",
+
+ "00000000000000000000000000000000",
+ "fff80000000000000000000000000000",
+ "8b87145a01ad1c6cede995ea3670454f",
+
+ "00000000000000000000000000000000",
+ "fffc0000000000000000000000000000",
+ "8eae3b10a0c8ca6d1d3b0fa61e56b0b2",
+
+ "00000000000000000000000000000000",
+ "fffe0000000000000000000000000000",
+ "64b4d629810fda6bafdf08f3b0d8d2c5",
+
+ "00000000000000000000000000000000",
+ "ffff0000000000000000000000000000",
+ "d7e5dbd3324595f8fdc7d7c571da6c2a",
+
+ "00000000000000000000000000000000",
+ "ffff8000000000000000000000000000",
+ "f3f72375264e167fca9de2c1527d9606",
+
+ "00000000000000000000000000000000",
+ "ffffc000000000000000000000000000",
+ "8ee79dd4f401ff9b7ea945d86666c13b",
+
+ "00000000000000000000000000000000",
+ "ffffe000000000000000000000000000",
+ "dd35cea2799940b40db3f819cb94c08b",
+
+ "00000000000000000000000000000000",
+ "fffff000000000000000000000000000",
+ "6941cb6b3e08c2b7afa581ebdd607b87",
+
+ "00000000000000000000000000000000",
+ "fffff800000000000000000000000000",
+ "2c20f439f6bb097b29b8bd6d99aad799",
+
+ "00000000000000000000000000000000",
+ "fffffc00000000000000000000000000",
+ "625d01f058e565f77ae86378bd2c49b3",
+
+ "00000000000000000000000000000000",
+ "fffffe00000000000000000000000000",
+ "c0b5fd98190ef45fbb4301438d095950",
+
+ "00000000000000000000000000000000",
+ "ffffff00000000000000000000000000",
+ "13001ff5d99806efd25da34f56be854b",
+
+ "00000000000000000000000000000000",
+ "ffffff80000000000000000000000000",
+ "3b594c60f5c8277a5113677f94208d82",
+
+ "00000000000000000000000000000000",
+ "ffffffc0000000000000000000000000",
+ "e9c0fc1818e4aa46bd2e39d638f89e05",
+
+ "00000000000000000000000000000000",
+ "ffffffe0000000000000000000000000",
+ "f8023ee9c3fdc45a019b4e985c7e1a54",
+
+ "00000000000000000000000000000000",
+ "fffffff0000000000000000000000000",
+ "35f40182ab4662f3023baec1ee796b57",
+
+ "00000000000000000000000000000000",
+ "fffffff8000000000000000000000000",
+ "3aebbad7303649b4194a6945c6cc3694",
+
+ "00000000000000000000000000000000",
+ "fffffffc000000000000000000000000",
+ "a2124bea53ec2834279bed7f7eb0f938",
+
+ "00000000000000000000000000000000",
+ "fffffffe000000000000000000000000",
+ "b9fb4399fa4facc7309e14ec98360b0a",
+
+ "00000000000000000000000000000000",
+ "ffffffff000000000000000000000000",
+ "c26277437420c5d634f715aea81a9132",
+
+ "00000000000000000000000000000000",
+ "ffffffff800000000000000000000000",
+ "171a0e1b2dd424f0e089af2c4c10f32f",
+
+ "00000000000000000000000000000000",
+ "ffffffffc00000000000000000000000",
+ "7cadbe402d1b208fe735edce00aee7ce",
+
+ "00000000000000000000000000000000",
+ "ffffffffe00000000000000000000000",
+ "43b02ff929a1485af6f5c6d6558baa0f",
+
+ "00000000000000000000000000000000",
+ "fffffffff00000000000000000000000",
+ "092faacc9bf43508bf8fa8613ca75dea",
+
+ "00000000000000000000000000000000",
+ "fffffffff80000000000000000000000",
+ "cb2bf8280f3f9742c7ed513fe802629c",
+
+ "00000000000000000000000000000000",
+ "fffffffffc0000000000000000000000",
+ "215a41ee442fa992a6e323986ded3f68",
+
+ "00000000000000000000000000000000",
+ "fffffffffe0000000000000000000000",
+ "f21e99cf4f0f77cea836e11a2fe75fb1",
+
+ "00000000000000000000000000000000",
+ "ffffffffff0000000000000000000000",
+ "95e3a0ca9079e646331df8b4e70d2cd6",
+
+ "00000000000000000000000000000000",
+ "ffffffffff8000000000000000000000",
+ "4afe7f120ce7613f74fc12a01a828073",
+
+ "00000000000000000000000000000000",
+ "ffffffffffc000000000000000000000",
+ "827f000e75e2c8b9d479beed913fe678",
+
+ "00000000000000000000000000000000",
+ "ffffffffffe000000000000000000000",
+ "35830c8e7aaefe2d30310ef381cbf691",
+
+ "00000000000000000000000000000000",
+ "fffffffffff000000000000000000000",
+ "191aa0f2c8570144f38657ea4085ebe5",
+
+ "00000000000000000000000000000000",
+ "fffffffffff800000000000000000000",
+ "85062c2c909f15d9269b6c18ce99c4f0",
+
+ "00000000000000000000000000000000",
+ "fffffffffffc00000000000000000000",
+ "678034dc9e41b5a560ed239eeab1bc78",
+
+ "00000000000000000000000000000000",
+ "fffffffffffe00000000000000000000",
+ "c2f93a4ce5ab6d5d56f1b93cf19911c1",
+
+ "00000000000000000000000000000000",
+ "ffffffffffff00000000000000000000",
+ "1c3112bcb0c1dcc749d799743691bf82",
+
+ "00000000000000000000000000000000",
+ "ffffffffffff80000000000000000000",
+ "00c55bd75c7f9c881989d3ec1911c0d4",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffc0000000000000000000",
+ "ea2e6b5ef182b7dff3629abd6a12045f",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffe0000000000000000000",
+ "22322327e01780b17397f24087f8cc6f",
+
+ "00000000000000000000000000000000",
+ "fffffffffffff0000000000000000000",
+ "c9cacb5cd11692c373b2411768149ee7",
+
+ "00000000000000000000000000000000",
+ "fffffffffffff8000000000000000000",
+ "a18e3dbbca577860dab6b80da3139256",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffc000000000000000000",
+ "79b61c37bf328ecca8d743265a3d425c",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffe000000000000000000",
+ "d2d99c6bcc1f06fda8e27e8ae3f1ccc7",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffff000000000000000000",
+ "1bfd4b91c701fd6b61b7f997829d663b",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffff800000000000000000",
+ "11005d52f25f16bdc9545a876a63490a",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffc00000000000000000",
+ "3a4d354f02bb5a5e47d39666867f246a",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffe00000000000000000",
+ "d451b8d6e1e1a0ebb155fbbf6e7b7dc3",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffff00000000000000000",
+ "6898d4f42fa7ba6a10ac05e87b9f2080",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffff80000000000000000",
+ "b611295e739ca7d9b50f8e4c0e754a3f",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffc0000000000000000",
+ "7d33fc7d8abe3ca1936759f8f5deaf20",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffe0000000000000000",
+ "3b5e0f566dc96c298f0c12637539b25c",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffff0000000000000000",
+ "f807c3e7985fe0f5a50e2cdb25c5109e",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffff8000000000000000",
+ "41f992a856fb278b389a62f5d274d7e9",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffc000000000000000",
+ "10d3ed7a6fe15ab4d91acbc7d0767ab1",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffe000000000000000",
+ "21feecd45b2e675973ac33bf0c5424fc",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffff000000000000000",
+ "1480cb3955ba62d09eea668f7c708817",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffff800000000000000",
+ "66404033d6b72b609354d5496e7eb511",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffc00000000000000",
+ "1c317a220a7d700da2b1e075b00266e1",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffe00000000000000",
+ "ab3b89542233f1271bf8fd0c0f403545",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffff00000000000000",
+ "d93eae966fac46dca927d6b114fa3f9e",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffff80000000000000",
+ "1bdec521316503d9d5ee65df3ea94ddf",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffc0000000000000",
+ "eef456431dea8b4acf83bdae3717f75f",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffe0000000000000",
+ "06f2519a2fafaa596bfef5cfa15c21b9",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffff0000000000000",
+ "251a7eac7e2fe809e4aa8d0d7012531a",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffff8000000000000",
+ "3bffc16e4c49b268a20f8d96a60b4058",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffc000000000000",
+ "e886f9281999c5bb3b3e8862e2f7c988",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffe000000000000",
+ "563bf90d61beef39f48dd625fcef1361",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffff000000000000",
+ "4d37c850644563c69fd0acd9a049325b",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffff800000000000",
+ "b87c921b91829ef3b13ca541ee1130a6",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffc00000000000",
+ "2e65eb6b6ea383e109accce8326b0393",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffe00000000000",
+ "9ca547f7439edc3e255c0f4d49aa8990",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffff00000000000",
+ "a5e652614c9300f37816b1f9fd0c87f9",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffff80000000000",
+ "14954f0b4697776f44494fe458d814ed",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffc0000000000",
+ "7c8d9ab6c2761723fe42f8bb506cbcf7",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffe0000000000",
+ "db7e1932679fdd99742aab04aa0d5a80",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffff0000000000",
+ "4c6a1c83e568cd10f27c2d73ded19c28",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffff8000000000",
+ "90ecbe6177e674c98de412413f7ac915",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffc000000000",
+ "90684a2ac55fe1ec2b8ebd5622520b73",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffe000000000",
+ "7472f9a7988607ca79707795991035e6",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffff000000000",
+ "56aff089878bf3352f8df172a3ae47d8",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffff800000000",
+ "65c0526cbe40161b8019a2a3171abd23",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffc00000000",
+ "377be0be33b4e3e310b4aabda173f84f",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffe00000000",
+ "9402e9aa6f69de6504da8d20c4fcaa2f",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffff00000000",
+ "123c1f4af313ad8c2ce648b2e71fb6e1",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffff80000000",
+ "1ffc626d30203dcdb0019fb80f726cf4",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffc0000000",
+ "76da1fbe3a50728c50fd2e621b5ad885",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffe0000000",
+ "082eb8be35f442fb52668e16a591d1d6",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffff0000000",
+ "e656f9ecf5fe27ec3e4a73d00c282fb3",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffff8000000",
+ "2ca8209d63274cd9a29bb74bcd77683a",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffc000000",
+ "79bf5dce14bb7dd73a8e3611de7ce026",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffe000000",
+ "3c849939a5d29399f344c4a0eca8a576",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffff000000",
+ "ed3c0a94d59bece98835da7aa4f07ca2",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffff800000",
+ "63919ed4ce10196438b6ad09d99cd795",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffc00000",
+ "7678f3a833f19fea95f3c6029e2bc610",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffe00000",
+ "3aa426831067d36b92be7c5f81c13c56",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffff00000",
+ "9272e2d2cdd11050998c845077a30ea0",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffff80000",
+ "088c4b53f5ec0ff814c19adae7f6246c",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffc0000",
+ "4010a5e401fdf0a0354ddbcc0d012b17",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffe0000",
+ "a87a385736c0a6189bd6589bd8445a93",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffff0000",
+ "545f2b83d9616dccf60fa9830e9cd287",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffff8000",
+ "4b706f7f92406352394037a6d4f4688d",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffc000",
+ "b7972b3941c44b90afa7b264bfba7387",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffe000",
+ "6f45732cf10881546f0fd23896d2bb60",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffff000",
+ "2e3579ca15af27f64b3c955a5bfc30ba",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffff800",
+ "34a2c5a91ae2aec99b7d1b5fa6780447",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffc00",
+ "a4d6616bd04f87335b0e53351227a9ee",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffe00",
+ "7f692b03945867d16179a8cefc83ea3f",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffff00",
+ "3bd141ee84a0e6414a26e7a4f281f8a2",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffff80",
+ "d1788f572d98b2b16ec5d5f3922b99bc",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffc0",
+ "0833ff6f61d98a57b288e8c3586b85a6",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffe0",
+ "8568261797de176bf0b43becc6285afb",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffff0",
+ "f9b0fda0c4a898f5b9e6f661c4ce4d07",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffff8",
+ "8ade895913685c67c5269f8aae42983e",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffffc",
+ "39bde67d5c8ed8a8b1c37eb8fa9f5ac0",
+
+ "00000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffffe",
+ "5c005e72c1418c44f569f2ea33ba54f3",
+
+ "00000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffff",
+ "3f5b8cc9ea855a0afa7347d23e8d664e",
+
+ /*
+ * From NIST validation suite (ECBVarTxt192.rsp).
+ */
+ "000000000000000000000000000000000000000000000000",
+ "80000000000000000000000000000000",
+ "6cd02513e8d4dc986b4afe087a60bd0c",
+
+ "000000000000000000000000000000000000000000000000",
+ "c0000000000000000000000000000000",
+ "2ce1f8b7e30627c1c4519eada44bc436",
+
+ "000000000000000000000000000000000000000000000000",
+ "e0000000000000000000000000000000",
+ "9946b5f87af446f5796c1fee63a2da24",
+
+ "000000000000000000000000000000000000000000000000",
+ "f0000000000000000000000000000000",
+ "2a560364ce529efc21788779568d5555",
+
+ "000000000000000000000000000000000000000000000000",
+ "f8000000000000000000000000000000",
+ "35c1471837af446153bce55d5ba72a0a",
+
+ "000000000000000000000000000000000000000000000000",
+ "fc000000000000000000000000000000",
+ "ce60bc52386234f158f84341e534cd9e",
+
+ "000000000000000000000000000000000000000000000000",
+ "fe000000000000000000000000000000",
+ "8c7c27ff32bcf8dc2dc57c90c2903961",
+
+ "000000000000000000000000000000000000000000000000",
+ "ff000000000000000000000000000000",
+ "32bb6a7ec84499e166f936003d55a5bb",
+
+ "000000000000000000000000000000000000000000000000",
+ "ff800000000000000000000000000000",
+ "a5c772e5c62631ef660ee1d5877f6d1b",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffc00000000000000000000000000000",
+ "030d7e5b64f380a7e4ea5387b5cd7f49",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffe00000000000000000000000000000",
+ "0dc9a2610037009b698f11bb7e86c83e",
+
+ "000000000000000000000000000000000000000000000000",
+ "fff00000000000000000000000000000",
+ "0046612c766d1840c226364f1fa7ed72",
+
+ "000000000000000000000000000000000000000000000000",
+ "fff80000000000000000000000000000",
+ "4880c7e08f27befe78590743c05e698b",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffc0000000000000000000000000000",
+ "2520ce829a26577f0f4822c4ecc87401",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffe0000000000000000000000000000",
+ "8765e8acc169758319cb46dc7bcf3dca",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffff0000000000000000000000000000",
+ "e98f4ba4f073df4baa116d011dc24a28",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffff8000000000000000000000000000",
+ "f378f68c5dbf59e211b3a659a7317d94",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffc000000000000000000000000000",
+ "283d3b069d8eb9fb432d74b96ca762b4",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffe000000000000000000000000000",
+ "a7e1842e8a87861c221a500883245c51",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffff000000000000000000000000000",
+ "77aa270471881be070fb52c7067ce732",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffff800000000000000000000000000",
+ "01b0f476d484f43f1aeb6efa9361a8ac",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffc00000000000000000000000000",
+ "1c3a94f1c052c55c2d8359aff2163b4f",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffe00000000000000000000000000",
+ "e8a067b604d5373d8b0f2e05a03b341b",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffff00000000000000000000000000",
+ "a7876ec87f5a09bfea42c77da30fd50e",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffff80000000000000000000000000",
+ "0cf3e9d3a42be5b854ca65b13f35f48d",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffc0000000000000000000000000",
+ "6c62f6bbcab7c3e821c9290f08892dda",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffe0000000000000000000000000",
+ "7f5e05bd2068738196fee79ace7e3aec",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffff0000000000000000000000000",
+ "440e0d733255cda92fb46e842fe58054",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffff8000000000000000000000000",
+ "aa5d5b1c4ea1b7a22e5583ac2e9ed8a7",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffc000000000000000000000000",
+ "77e537e89e8491e8662aae3bc809421d",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffe000000000000000000000000",
+ "997dd3e9f1598bfa73f75973f7e93b76",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffff000000000000000000000000",
+ "1b38d4f7452afefcb7fc721244e4b72e",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffff800000000000000000000000",
+ "0be2b18252e774dda30cdda02c6906e3",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffc00000000000000000000000",
+ "d2695e59c20361d82652d7d58b6f11b2",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffe00000000000000000000000",
+ "902d88d13eae52089abd6143cfe394e9",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffff00000000000000000000000",
+ "d49bceb3b823fedd602c305345734bd2",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffff80000000000000000000000",
+ "707b1dbb0ffa40ef7d95def421233fae",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffc0000000000000000000000",
+ "7ca0c1d93356d9eb8aa952084d75f913",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffe0000000000000000000000",
+ "f2cbf9cb186e270dd7bdb0c28febc57d",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffff0000000000000000000000",
+ "c94337c37c4e790ab45780bd9c3674a0",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffff8000000000000000000000",
+ "8e3558c135252fb9c9f367ed609467a1",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffc000000000000000000000",
+ "1b72eeaee4899b443914e5b3a57fba92",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffe000000000000000000000",
+ "011865f91bc56868d051e52c9efd59b7",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffff000000000000000000000",
+ "e4771318ad7a63dd680f6e583b7747ea",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffff800000000000000000000",
+ "61e3d194088dc8d97e9e6db37457eac5",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffc00000000000000000000",
+ "36ff1ec9ccfbc349e5d356d063693ad6",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffe00000000000000000000",
+ "3cc9e9a9be8cc3f6fb2ea24088e9bb19",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffff00000000000000000000",
+ "1ee5ab003dc8722e74905d9a8fe3d350",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffff80000000000000000000",
+ "245339319584b0a412412869d6c2eada",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffc0000000000000000000",
+ "7bd496918115d14ed5380852716c8814",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffe0000000000000000000",
+ "273ab2f2b4a366a57d582a339313c8b1",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffff0000000000000000000",
+ "113365a9ffbe3b0ca61e98507554168b",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffff8000000000000000000",
+ "afa99c997ac478a0dea4119c9e45f8b1",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffc000000000000000000",
+ "9216309a7842430b83ffb98638011512",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffe000000000000000000",
+ "62abc792288258492a7cb45145f4b759",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffff000000000000000000",
+ "534923c169d504d7519c15d30e756c50",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffff800000000000000000",
+ "fa75e05bcdc7e00c273fa33f6ee441d2",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffc00000000000000000",
+ "7d350fa6057080f1086a56b17ec240db",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffe00000000000000000",
+ "f34e4a6324ea4a5c39a661c8fe5ada8f",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffff00000000000000000",
+ "0882a16f44088d42447a29ac090ec17e",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffff80000000000000000",
+ "3a3c15bfc11a9537c130687004e136ee",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffc0000000000000000",
+ "22c0a7678dc6d8cf5c8a6d5a9960767c",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffe0000000000000000",
+ "b46b09809d68b9a456432a79bdc2e38c",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffff0000000000000000",
+ "93baaffb35fbe739c17c6ac22eecf18f",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffff8000000000000000",
+ "c8aa80a7850675bc007c46df06b49868",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffc000000000000000",
+ "12c6f3877af421a918a84b775858021d",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffe000000000000000",
+ "33f123282c5d633924f7d5ba3f3cab11",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffff000000000000000",
+ "a8f161002733e93ca4527d22c1a0c5bb",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffff800000000000000",
+ "b72f70ebf3e3fda23f508eec76b42c02",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffc00000000000000",
+ "6a9d965e6274143f25afdcfc88ffd77c",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffe00000000000000",
+ "a0c74fd0b9361764ce91c5200b095357",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffff00000000000000",
+ "091d1fdc2bd2c346cd5046a8c6209146",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffff80000000000000",
+ "e2a37580116cfb71856254496ab0aca8",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffc0000000000000",
+ "e0b3a00785917c7efc9adba322813571",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffe0000000000000",
+ "733d41f4727b5ef0df4af4cf3cffa0cb",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffff0000000000000",
+ "a99ebb030260826f981ad3e64490aa4f",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffff8000000000000",
+ "73f34c7d3eae5e80082c1647524308ee",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffc000000000000",
+ "40ebd5ad082345b7a2097ccd3464da02",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffe000000000000",
+ "7cc4ae9a424b2cec90c97153c2457ec5",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffff000000000000",
+ "54d632d03aba0bd0f91877ebdd4d09cb",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffff800000000000",
+ "d3427be7e4d27cd54f5fe37b03cf0897",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffc00000000000",
+ "b2099795e88cc158fd75ea133d7e7fbe",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffe00000000000",
+ "a6cae46fb6fadfe7a2c302a34242817b",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffff00000000000",
+ "026a7024d6a902e0b3ffccbaa910cc3f",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffff80000000000",
+ "156f07767a85a4312321f63968338a01",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffc0000000000",
+ "15eec9ebf42b9ca76897d2cd6c5a12e2",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffe0000000000",
+ "db0d3a6fdcc13f915e2b302ceeb70fd8",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffff0000000000",
+ "71dbf37e87a2e34d15b20e8f10e48924",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffff8000000000",
+ "c745c451e96ff3c045e4367c833e3b54",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffc000000000",
+ "340da09c2dd11c3b679d08ccd27dd595",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffe000000000",
+ "8279f7c0c2a03ee660c6d392db025d18",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffff000000000",
+ "a4b2c7d8eba531ff47c5041a55fbd1ec",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffff800000000",
+ "74569a2ca5a7bd5131ce8dc7cbfbf72f",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffc00000000",
+ "3713da0c0219b63454035613b5a403dd",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffe00000000",
+ "8827551ddcc9df23fa72a3de4e9f0b07",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffff00000000",
+ "2e3febfd625bfcd0a2c06eb460da1732",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffff80000000",
+ "ee82e6ba488156f76496311da6941deb",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffc0000000",
+ "4770446f01d1f391256e85a1b30d89d3",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffe0000000",
+ "af04b68f104f21ef2afb4767cf74143c",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffff0000000",
+ "cf3579a9ba38c8e43653173e14f3a4c6",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffff8000000",
+ "b3bba904f4953e09b54800af2f62e7d4",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffc000000",
+ "fc4249656e14b29eb9c44829b4c59a46",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffe000000",
+ "9b31568febe81cfc2e65af1c86d1a308",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffff000000",
+ "9ca09c25f273a766db98a480ce8dfedc",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffff800000",
+ "b909925786f34c3c92d971883c9fbedf",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffc00000",
+ "82647f1332fe570a9d4d92b2ee771d3b",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffe00000",
+ "3604a7e80832b3a99954bca6f5b9f501",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffff00000",
+ "884607b128c5de3ab39a529a1ef51bef",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffff80000",
+ "670cfa093d1dbdb2317041404102435e",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffc0000",
+ "7a867195f3ce8769cbd336502fbb5130",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffe0000",
+ "52efcf64c72b2f7ca5b3c836b1078c15",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffff0000",
+ "4019250f6eefb2ac5ccbcae044e75c7e",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffff8000",
+ "022c4f6f5a017d292785627667ddef24",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffc000",
+ "e9c21078a2eb7e03250f71000fa9e3ed",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffe000",
+ "a13eaeeb9cd391da4e2b09490b3e7fad",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffff000",
+ "c958a171dca1d4ed53e1af1d380803a9",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffff800",
+ "21442e07a110667f2583eaeeee44dc8c",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffc00",
+ "59bbb353cf1dd867a6e33737af655e99",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffe00",
+ "43cd3b25375d0ce41087ff9fe2829639",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffff00",
+ "6b98b17e80d1118e3516bd768b285a84",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffff80",
+ "ae47ed3676ca0c08deea02d95b81db58",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffc0",
+ "34ec40dc20413795ed53628ea748720b",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffe0",
+ "4dc68163f8e9835473253542c8a65d46",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffff0",
+ "2aabb999f43693175af65c6c612c46fb",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffff8",
+ "e01f94499dac3547515c5b1d756f0f58",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffffc",
+ "9d12435a46480ce00ea349f71799df9a",
+
+ "000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffffe",
+ "cef41d16d266bdfe46938ad7884cc0cf",
+
+ "000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffff",
+ "b13db4da1f718bc6904797c82bcf2d32",
+
+ /*
+ * From NIST validation suite (ECBVarTxt256.rsp).
+ */
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "80000000000000000000000000000000",
+ "ddc6bf790c15760d8d9aeb6f9a75fd4e",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "c0000000000000000000000000000000",
+ "0a6bdc6d4c1e6280301fd8e97ddbe601",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "e0000000000000000000000000000000",
+ "9b80eefb7ebe2d2b16247aa0efc72f5d",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "f0000000000000000000000000000000",
+ "7f2c5ece07a98d8bee13c51177395ff7",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "f8000000000000000000000000000000",
+ "7818d800dcf6f4be1e0e94f403d1e4c2",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fc000000000000000000000000000000",
+ "e74cd1c92f0919c35a0324123d6177d3",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fe000000000000000000000000000000",
+ "8092a4dcf2da7e77e93bdd371dfed82e",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ff000000000000000000000000000000",
+ "49af6b372135acef10132e548f217b17",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ff800000000000000000000000000000",
+ "8bcd40f94ebb63b9f7909676e667f1e7",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffc00000000000000000000000000000",
+ "fe1cffb83f45dcfb38b29be438dbd3ab",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffe00000000000000000000000000000",
+ "0dc58a8d886623705aec15cb1e70dc0e",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fff00000000000000000000000000000",
+ "c218faa16056bd0774c3e8d79c35a5e4",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fff80000000000000000000000000000",
+ "047bba83f7aa841731504e012208fc9e",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffc0000000000000000000000000000",
+ "dc8f0e4915fd81ba70a331310882f6da",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffe0000000000000000000000000000",
+ "1569859ea6b7206c30bf4fd0cbfac33c",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffff0000000000000000000000000000",
+ "300ade92f88f48fa2df730ec16ef44cd",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffff8000000000000000000000000000",
+ "1fe6cc3c05965dc08eb0590c95ac71d0",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffc000000000000000000000000000",
+ "59e858eaaa97fec38111275b6cf5abc0",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffe000000000000000000000000000",
+ "2239455e7afe3b0616100288cc5a723b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffff000000000000000000000000000",
+ "3ee500c5c8d63479717163e55c5c4522",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffff800000000000000000000000000",
+ "d5e38bf15f16d90e3e214041d774daa8",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffc00000000000000000000000000",
+ "b1f4066e6f4f187dfe5f2ad1b17819d0",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffe00000000000000000000000000",
+ "6ef4cc4de49b11065d7af2909854794a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffff00000000000000000000000000",
+ "ac86bc606b6640c309e782f232bf367f",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffff80000000000000000000000000",
+ "36aff0ef7bf3280772cf4cac80a0d2b2",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffc0000000000000000000000000",
+ "1f8eedea0f62a1406d58cfc3ecea72cf",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffe0000000000000000000000000",
+ "abf4154a3375a1d3e6b1d454438f95a6",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffff0000000000000000000000000",
+ "96f96e9d607f6615fc192061ee648b07",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffff8000000000000000000000000",
+ "cf37cdaaa0d2d536c71857634c792064",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffc000000000000000000000000",
+ "fbd6640c80245c2b805373f130703127",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffe000000000000000000000000",
+ "8d6a8afe55a6e481badae0d146f436db",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffff000000000000000000000000",
+ "6a4981f2915e3e68af6c22385dd06756",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffff800000000000000000000000",
+ "42a1136e5f8d8d21d3101998642d573b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffc00000000000000000000000",
+ "9b471596dc69ae1586cee6158b0b0181",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffe00000000000000000000000",
+ "753665c4af1eff33aa8b628bf8741cfd",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffff00000000000000000000000",
+ "9a682acf40be01f5b2a4193c9a82404d",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffff80000000000000000000000",
+ "54fafe26e4287f17d1935f87eb9ade01",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffc0000000000000000000000",
+ "49d541b2e74cfe73e6a8e8225f7bd449",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffe0000000000000000000000",
+ "11a45530f624ff6f76a1b3826626ff7b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffff0000000000000000000000",
+ "f96b0c4a8bc6c86130289f60b43b8fba",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffff8000000000000000000000",
+ "48c7d0e80834ebdc35b6735f76b46c8b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffc000000000000000000000",
+ "2463531ab54d66955e73edc4cb8eaa45",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffe000000000000000000000",
+ "ac9bd8e2530469134b9d5b065d4f565b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffff000000000000000000000",
+ "3f5f9106d0e52f973d4890e6f37e8a00",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffff800000000000000000000",
+ "20ebc86f1304d272e2e207e59db639f0",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffc00000000000000000000",
+ "e67ae6426bf9526c972cff072b52252c",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffe00000000000000000000",
+ "1a518dddaf9efa0d002cc58d107edfc8",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffff00000000000000000000",
+ "ead731af4d3a2fe3b34bed047942a49f",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffff80000000000000000000",
+ "b1d4efe40242f83e93b6c8d7efb5eae9",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffc0000000000000000000",
+ "cd2b1fec11fd906c5c7630099443610a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffe0000000000000000000",
+ "a1853fe47fe29289d153161d06387d21",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffff0000000000000000000",
+ "4632154179a555c17ea604d0889fab14",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffff8000000000000000000",
+ "dd27cac6401a022e8f38f9f93e774417",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffc000000000000000000",
+ "c090313eb98674f35f3123385fb95d4d",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffe000000000000000000",
+ "cc3526262b92f02edce548f716b9f45c",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffff000000000000000000",
+ "c0838d1a2b16a7c7f0dfcc433c399c33",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffff800000000000000000",
+ "0d9ac756eb297695eed4d382eb126d26",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffc00000000000000000",
+ "56ede9dda3f6f141bff1757fa689c3e1",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffe00000000000000000",
+ "768f520efe0f23e61d3ec8ad9ce91774",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffff00000000000000000",
+ "b1144ddfa75755213390e7c596660490",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffff80000000000000000",
+ "1d7c0c4040b355b9d107a99325e3b050",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffc0000000000000000",
+ "d8e2bb1ae8ee3dcf5bf7d6c38da82a1a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffe0000000000000000",
+ "faf82d178af25a9886a47e7f789b98d7",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffff0000000000000000",
+ "9b58dbfd77fe5aca9cfc190cd1b82d19",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffff8000000000000000",
+ "77f392089042e478ac16c0c86a0b5db5",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffc000000000000000",
+ "19f08e3420ee69b477ca1420281c4782",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffe000000000000000",
+ "a1b19beee4e117139f74b3c53fdcb875",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffff000000000000000",
+ "a37a5869b218a9f3a0868d19aea0ad6a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffff800000000000000",
+ "bc3594e865bcd0261b13202731f33580",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffc00000000000000",
+ "811441ce1d309eee7185e8c752c07557",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffe00000000000000",
+ "959971ce4134190563518e700b9874d1",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffff00000000000000",
+ "76b5614a042707c98e2132e2e805fe63",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffff80000000000000",
+ "7d9fa6a57530d0f036fec31c230b0cc6",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffc0000000000000",
+ "964153a83bf6989a4ba80daa91c3e081",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffe0000000000000",
+ "a013014d4ce8054cf2591d06f6f2f176",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffff0000000000000",
+ "d1c5f6399bf382502e385eee1474a869",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffff8000000000000",
+ "0007e20b8298ec354f0f5fe7470f36bd",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffc000000000000",
+ "b95ba05b332da61ef63a2b31fcad9879",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffe000000000000",
+ "4620a49bd967491561669ab25dce45f4",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffff000000000000",
+ "12e71214ae8e04f0bb63d7425c6f14d5",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffff800000000000",
+ "4cc42fc1407b008fe350907c092e80ac",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffc00000000000",
+ "08b244ce7cbc8ee97fbba808cb146fda",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffe00000000000",
+ "39b333e8694f21546ad1edd9d87ed95b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffff00000000000",
+ "3b271f8ab2e6e4a20ba8090f43ba78f3",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffff80000000000",
+ "9ad983f3bf651cd0393f0a73cccdea50",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffc0000000000",
+ "8f476cbff75c1f725ce18e4bbcd19b32",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffe0000000000",
+ "905b6267f1d6ab5320835a133f096f2a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffff0000000000",
+ "145b60d6d0193c23f4221848a892d61a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffff8000000000",
+ "55cfb3fb6d75cad0445bbc8dafa25b0f",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffc000000000",
+ "7b8e7098e357ef71237d46d8b075b0f5",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffe000000000",
+ "2bf27229901eb40f2df9d8398d1505ae",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffff000000000",
+ "83a63402a77f9ad5c1e931a931ecd706",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffff800000000",
+ "6f8ba6521152d31f2bada1843e26b973",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffc00000000",
+ "e5c3b8e30fd2d8e6239b17b44bd23bbd",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffe00000000",
+ "1ac1f7102c59933e8b2ddc3f14e94baa",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffff00000000",
+ "21d9ba49f276b45f11af8fc71a088e3d",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffff80000000",
+ "649f1cddc3792b4638635a392bc9bade",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffc0000000",
+ "e2775e4b59c1bc2e31a2078c11b5a08c",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffe0000000",
+ "2be1fae5048a25582a679ca10905eb80",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffff0000000",
+ "da86f292c6f41ea34fb2068df75ecc29",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffff8000000",
+ "220df19f85d69b1b562fa69a3c5beca5",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffc000000",
+ "1f11d5d0355e0b556ccdb6c7f5083b4d",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffe000000",
+ "62526b78be79cb384633c91f83b4151b",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffff000000",
+ "90ddbcb950843592dd47bbef00fdc876",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffff800000",
+ "2fd0e41c5b8402277354a7391d2618e2",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffc00000",
+ "3cdf13e72dee4c581bafec70b85f9660",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffe00000",
+ "afa2ffc137577092e2b654fa199d2c43",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffff00000",
+ "8d683ee63e60d208e343ce48dbc44cac",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffff80000",
+ "705a4ef8ba2133729c20185c3d3a4763",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffc0000",
+ "0861a861c3db4e94194211b77ed761b9",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffe0000",
+ "4b00c27e8b26da7eab9d3a88dec8b031",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffff0000",
+ "5f397bf03084820cc8810d52e5b666e9",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffff8000",
+ "63fafabb72c07bfbd3ddc9b1203104b8",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffc000",
+ "683e2140585b18452dd4ffbb93c95df9",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffe000",
+ "286894e48e537f8763b56707d7d155c8",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffff000",
+ "a423deabc173dcf7e2c4c53e77d37cd1",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffff800",
+ "eb8168313e1cfdfdb5e986d5429cf172",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffc00",
+ "27127daafc9accd2fb334ec3eba52323",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffe00",
+ "ee0715b96f72e3f7a22a5064fc592f4c",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffff00",
+ "29ee526770f2a11dcfa989d1ce88830f",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffff80",
+ "0493370e054b09871130fe49af730a5a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffc0",
+ "9b7b940f6c509f9e44a4ee140448ee46",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffe0",
+ "2915be4a1ecfdcbe3e023811a12bb6c7",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffff0",
+ "7240e524bc51d8c4d440b1be55d1062c",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffff8",
+ "da63039d38cb4612b2dc36ba26684b93",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffffc",
+ "0f59cb5a4b522e2ac56c1a64f558ad9a",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "fffffffffffffffffffffffffffffffe",
+ "7bfe9d876c6d63c1d035da8fe21c409d",
+
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "ffffffffffffffffffffffffffffffff",
+ "acdace8078a32b1a182bfa4987ca1347"
+ };
+
+ /*
+ * AES known-answer tests for CBC.
+ * Order: key, IV, plaintext, ciphertext.
+ */
+ static string[] KAT_AES_CBC = {
+ /*
+ * From NIST validation suite "Multiblock Message Test"
+ * (cbcmmt128.rsp).
+ */
+ "1f8e4973953f3fb0bd6b16662e9a3c17",
+ "2fe2b333ceda8f98f4a99b40d2cd34a8",
+ "45cf12964fc824ab76616ae2f4bf0822",
+ "0f61c4d44c5147c03c195ad7e2cc12b2",
+
+ "0700d603a1c514e46b6191ba430a3a0c",
+ "aad1583cd91365e3bb2f0c3430d065bb",
+ "068b25c7bfb1f8bdd4cfc908f69dffc5ddc726a197f0e5f720f730393279be91",
+ "c4dc61d9725967a3020104a9738f23868527ce839aab1752fd8bdb95a82c4d00",
+
+ "3348aa51e9a45c2dbe33ccc47f96e8de",
+ "19153c673160df2b1d38c28060e59b96",
+ "9b7cee827a26575afdbb7c7a329f887238052e3601a7917456ba61251c214763d5e1847a6ad5d54127a399ab07ee3599",
+ "d5aed6c9622ec451a15db12819952b6752501cf05cdbf8cda34a457726ded97818e1f127a28d72db5652749f0c6afee5",
+
+ "b7f3c9576e12dd0db63e8f8fac2b9a39",
+ "c80f095d8bb1a060699f7c19974a1aa0",
+ "9ac19954ce1319b354d3220460f71c1e373f1cd336240881160cfde46ebfed2e791e8d5a1a136ebd1dc469dec00c4187722b841cdabcb22c1be8a14657da200e",
+ "19b9609772c63f338608bf6eb52ca10be65097f89c1e0905c42401fd47791ae2c5440b2d473116ca78bd9ff2fb6015cfd316524eae7dcb95ae738ebeae84a467",
+
+ "b6f9afbfe5a1562bba1368fc72ac9d9c",
+ "3f9d5ebe250ee7ce384b0d00ee849322",
+ "db397ec22718dbffb9c9d13de0efcd4611bf792be4fce0dc5f25d4f577ed8cdbd4eb9208d593dda3d4653954ab64f05676caa3ce9bfa795b08b67ceebc923fdc89a8c431188e9e482d8553982cf304d1",
+ "10ea27b19e16b93af169c4a88e06e35c99d8b420980b058e34b4b8f132b13766f72728202b089f428fecdb41c79f8aa0d0ef68f5786481cca29e2126f69bc14160f1ae2187878ba5c49cf3961e1b7ee9",
+
+ "bbe7b7ba07124ff1ae7c3416fe8b465e",
+ "7f65b5ee3630bed6b84202d97fb97a1e",
+ "2aad0c2c4306568bad7447460fd3dac054346d26feddbc9abd9110914011b4794be2a9a00a519a51a5b5124014f4ed2735480db21b434e99a911bb0b60fe0253763725b628d5739a5117b7ee3aefafc5b4c1bf446467e7bf5f78f31ff7caf187",
+ "3b8611bfc4973c5cd8e982b073b33184cd26110159172e44988eb5ff5661a1e16fad67258fcbfee55469267a12dc374893b4e3533d36f5634c3095583596f135aa8cd1138dc898bc5651ee35a92ebf89ab6aeb5366653bc60a70e0074fc11efe",
+
+ "89a553730433f7e6d67d16d373bd5360",
+ "f724558db3433a523f4e51a5bea70497",
+ "807bc4ea684eedcfdcca30180680b0f1ae2814f35f36d053c5aea6595a386c1442770f4d7297d8b91825ee7237241da8925dd594ccf676aecd46ca2068e8d37a3a0ec8a7d5185a201e663b5ff36ae197110188a23503763b8218826d23ced74b31e9f6e2d7fbfa6cb43420c7807a8625",
+ "406af1429a478c3d07e555c5287a60500d37fc39b68e5bbb9bafd6ddb223828561d6171a308d5b1a4551e8a5e7d572918d25c968d3871848d2f16635caa9847f38590b1df58ab5efb985f2c66cfaf86f61b3f9c0afad6c963c49cee9b8bc81a2ddb06c967f325515a4849eec37ce721a",
+
+ "c491ca31f91708458e29a925ec558d78",
+ "9ef934946e5cd0ae97bd58532cb49381",
+ "cb6a787e0dec56f9a165957f81af336ca6b40785d9e94093c6190e5152649f882e874d79ac5e167bd2a74ce5ae088d2ee854f6539e0a94796b1e1bd4c9fcdbc79acbef4d01eeb89776d18af71ae2a4fc47dd66df6c4dbe1d1850e466549a47b636bcc7c2b3a62495b56bb67b6d455f1eebd9bfefecbca6c7f335cfce9b45cb9d",
+ "7b2931f5855f717145e00f152a9f4794359b1ffcb3e55f594e33098b51c23a6c74a06c1d94fded7fd2ae42c7db7acaef5844cb33aeddc6852585ed0020a6699d2cb53809cefd169148ce42292afab063443978306c582c18b9ce0da3d084ce4d3c482cfd8fcf1a85084e89fb88b40a084d5e972466d07666126fb761f84078f2",
+
+ "f6e87d71b0104d6eb06a68dc6a71f498",
+ "1c245f26195b76ebebc2edcac412a2f8",
+ "f82bef3c73a6f7f80db285726d691db6bf55eec25a859d3ba0e0445f26b9bb3b16a3161ed1866e4dd8f2e5f8ecb4e46d74a7a78c20cdfc7bcc9e479ba7a0caba9438238ad0c01651d5d98de37f03ddce6e6b4bd4ab03cf9e8ed818aedfa1cf963b932067b97d776dce1087196e7e913f7448e38244509f0caf36bd8217e15336d35c149fd4e41707893fdb84014f8729",
+ "b09512f3eff9ed0d85890983a73dadbb7c3678d52581be64a8a8fc586f490f2521297a478a0598040ebd0f5509fafb0969f9d9e600eaef33b1b93eed99687b167f89a5065aac439ce46f3b8d22d30865e64e45ef8cd30b6984353a844a11c8cd60dba0e8866b3ee30d24b3fa8a643b328353e06010fa8273c8fd54ef0a2b6930e5520aae5cd5902f9b86a33592ca4365",
+
+ "2c14413751c31e2730570ba3361c786b",
+ "1dbbeb2f19abb448af849796244a19d7",
+ "40d930f9a05334d9816fe204999c3f82a03f6a0457a8c475c94553d1d116693adc618049f0a769a2eed6a6cb14c0143ec5cccdbc8dec4ce560cfd206225709326d4de7948e54d603d01b12d7fed752fb23f1aa4494fbb00130e9ded4e77e37c079042d828040c325b1a5efd15fc842e44014ca4374bf38f3c3fc3ee327733b0c8aee1abcd055772f18dc04603f7b2c1ea69ff662361f2be0a171bbdcea1e5d3f",
+ "6be8a12800455a320538853e0cba31bd2d80ea0c85164a4c5c261ae485417d93effe2ebc0d0a0b51d6ea18633d210cf63c0c4ddbc27607f2e81ed9113191ef86d56f3b99be6c415a4150299fb846ce7160b40b63baf1179d19275a2e83698376d28b92548c68e06e6d994e2c1501ed297014e702cdefee2f656447706009614d801de1caaf73f8b7fa56cf1ba94b631933bbe577624380850f117435a0355b2b",
+
+ /*
+ * From NIST validation suite "Multiblock Message Test"
+ * (cbcmmt192.rsp).
+ */
+ "ba75f4d1d9d7cf7f551445d56cc1a8ab2a078e15e049dc2c",
+ "531ce78176401666aa30db94ec4a30eb",
+ "c51fc276774dad94bcdc1d2891ec8668",
+ "70dd95a14ee975e239df36ff4aee1d5d",
+
+ "eab3b19c581aa873e1981c83ab8d83bbf8025111fb2e6b21",
+ "f3d6667e8d4d791e60f7505ba383eb05",
+ "9d4e4cccd1682321856df069e3f1c6fa391a083a9fb02d59db74c14081b3acc4",
+ "51d44779f90d40a80048276c035cb49ca2a47bcb9b9cf7270b9144793787d53f",
+
+ "16c93bb398f1fc0cf6d68fc7a5673cdf431fa147852b4a2d",
+ "eaaeca2e07ddedf562f94df63f0a650f",
+ "c5ce958613bf741718c17444484ebaf1050ddcacb59b9590178cbe69d7ad7919608cb03af13bbe04f3506b718a301ea0",
+ "ed6a50e0c6921d52d6647f75d67b4fd56ace1fedb8b5a6a997b4d131640547d22c5d884a75e6752b5846b5b33a5181f4",
+
+ "067bb17b4df785697eaccf961f98e212cb75e6797ce935cb",
+ "8b59c9209c529ca8391c9fc0ce033c38",
+ "db3785a889b4bd387754da222f0e4c2d2bfe0d79e05bc910fba941beea30f1239eacf0068f4619ec01c368e986fca6b7c58e490579d29611bd10087986eff54f",
+ "d5f5589760bf9c762228fde236de1fa2dd2dad448db3fa9be0c4196efd46a35c84dd1ac77d9db58c95918cb317a6430a08d2fb6a8e8b0f1c9b72c7a344dc349f",
+
+ "0fd39de83e0be77a79c8a4a612e3dd9c8aae2ce35e7a2bf8",
+ "7e1d629b84f93b079be51f9a5f5cb23c",
+ "38fbda37e28fa86d9d83a4345e419dea95d28c7818ff25925db6ac3aedaf0a86154e20a4dfcc5b1b4192895393e5eb5846c88bdbd41ecf7af3104f410eaee470f5d9017ed460475f626953035a13db1f",
+ "edadae2f9a45ff3473e02d904c94d94a30a4d92da4deb6bcb4b0774472694571842039f21c496ef93fd658842c735f8a81fcd0aa578442ab893b18f606aed1bab11f81452dd45e9b56adf2eccf4ea095",
+
+ "e3fecc75f0075a09b383dfd389a3d33cc9b854b3b254c0f4",
+ "36eab883afef936cc38f63284619cd19",
+ "931b2f5f3a5820d53a6beaaa6431083a3488f4eb03b0f5b57ef838e1579623103bd6e6800377538b2e51ef708f3c4956432e8a8ee6a34e190642b26ad8bdae6c2af9a6c7996f3b6004d2671e41f1c9f40ee03d1c4a52b0a0654a331f15f34dce",
+ "75395974bd32b3665654a6c8e396b88ae34b123575872a7ab687d8e76b46df911a8a590cd01d2f5c330be3a6626e9dd3aa5e10ed14e8ff829811b6fed50f3f533ca4385a1cbca78f5c4744e50f2f8359165c2485d1324e76c3eae76a0ccac629",
+
+ "f9c27565eb07947c8cb51b79248430f7b1066c3d2fdc3d13",
+ "2bd67cc89ab7948d644a49672843cbd9",
+ "6abcc270173cf114d44847e911a050db57ba7a2e2c161c6f37ccb6aaa4677bddcaf50cad0b5f8758fcf7c0ebc650ceb5cd52cafb8f8dd3edcece55d9f1f08b9fa8f54365cf56e28b9596a7e1dd1d3418e4444a7724add4cf79d527b183ec88de4be4eeff29c80a97e54f85351cb189ee",
+ "ca282924a61187feb40520979106e5cc861957f23828dcb7285e0eaac8a0ca2a6b60503d63d6039f4693dba32fa1f73ae2e709ca94911f28a5edd1f30eaddd54680c43acc9c74cd90d8bb648b4e544275f47e514daa20697f66c738eb30337f017fca1a26da4d1a0cc0a0e98e2463070",
+
+ "fb09cf9e00dbf883689d079c920077c0073c31890b55bab5",
+ "e3c89bd097c3abddf64f4881db6dbfe2",
+ "c1a37683fb289467dd1b2c89efba16bbd2ee24cf18d19d44596ded2682c79a2f711c7a32bf6a24badd32a4ee637c73b7a41da6258635650f91fb9ffa45bdfc3cb122136241b3deced8996aa51ea8d3e81c9d70e006a44bc0571ed48623a0d622a93fa9da290baaedf5d9e876c94620945ff8ecc83f27379ed55cf490c5790f27",
+ "8158e21420f25b59d6ae943fa1cbf21f02e979f419dab0126a721b7eef55bee9ad97f5ccff7d239057bbc19a8c378142f7672f1d5e7e17d7bebcb0070e8355cace6660171a53b61816ae824a6ef69ce470b6ffd3b5bb4b438874d91d27854d3b6f25860d3868958de3307d62b1339bdddb8a318c0ce0f33c17caf0e9f6040820",
+
+ "bca6fa3c67fd294e958f66fe8bd64f45f428f5bc8e9733a7",
+ "92a47f2833f1450d1da41717bdc6e83c",
+ "5becbc31d8bead6d36ae014a5863d14a431e6b55d29ea6baaa417271716db3a33b2e506b452086dfe690834ac2de30bc41254ec5401ec47d064237c7792fdcd7914d8af20eb114756642d519021a8c75a92f6bc53d326ae9a5b7e1b10a9756574692934d9939fc399e0c203f7edf8e7e6482eadd31a0400770e897b48c6bca2b404593045080e93377358c42a0f4dede",
+ "926db248cc1ba20f0c57631a7c8aef094f791937b905949e3460240e8bfa6fa483115a1b310b6e4369caebc5262888377b1ddaa5800ea496a2bdff0f9a1031e7129c9a20e35621e7f0b8baca0d87030f2ae7ca8593c8599677a06fd4b26009ead08fecac24caa9cf2cad3b470c8227415a7b1e0f2eab3fad96d70a209c8bb26c627677e2531b9435ca6e3c444d195b5f",
+
+ "162ad50ee64a0702aa551f571dedc16b2c1b6a1e4d4b5eee",
+ "24408038161a2ccae07b029bb66355c1",
+ "be8abf00901363987a82cc77d0ec91697ba3857f9e4f84bd79406c138d02698f003276d0449120bef4578d78fecabe8e070e11710b3f0a2744bd52434ec70015884c181ebdfd51c604a71c52e4c0e110bc408cd462b248a80b8a8ac06bb952ac1d7faed144807f1a731b7febcaf7835762defe92eccfc7a9944e1c702cffe6bc86733ed321423121085ac02df8962bcbc1937092eebf0e90a8b20e3dd8c244ae",
+ "c82cf2c476dea8cb6a6e607a40d2f0391be82ea9ec84a537a6820f9afb997b76397d005424faa6a74dc4e8c7aa4a8900690f894b6d1dca80675393d2243adac762f159301e357e98b724762310cd5a7bafe1c2a030dba46fd93a9fdb89cc132ca9c17dc72031ec6822ee5a9d99dbca66c784c01b0885cbb62e29d97801927ec415a5d215158d325f9ee689437ad1b7684ad33c0d92739451ac87f39ff8c31b84",
+
+ /*
+ * From NIST validation suite "Multiblock Message Test"
+ * (cbcmmt256.rsp).
+ */
+ "6ed76d2d97c69fd1339589523931f2a6cff554b15f738f21ec72dd97a7330907",
+ "851e8764776e6796aab722dbb644ace8",
+ "6282b8c05c5c1530b97d4816ca434762",
+ "6acc04142e100a65f51b97adf5172c41",
+
+ "dce26c6b4cfb286510da4eecd2cffe6cdf430f33db9b5f77b460679bd49d13ae",
+ "fdeaa134c8d7379d457175fd1a57d3fc",
+ "50e9eee1ac528009e8cbcd356975881f957254b13f91d7c6662d10312052eb00",
+ "2fa0df722a9fd3b64cb18fb2b3db55ff2267422757289413f8f657507412a64c",
+
+ "fe8901fecd3ccd2ec5fdc7c7a0b50519c245b42d611a5ef9e90268d59f3edf33",
+ "bd416cb3b9892228d8f1df575692e4d0",
+ "8d3aa196ec3d7c9b5bb122e7fe77fb1295a6da75abe5d3a510194d3a8a4157d5c89d40619716619859da3ec9b247ced9",
+ "608e82c7ab04007adb22e389a44797fed7de090c8c03ca8a2c5acd9e84df37fbc58ce8edb293e98f02b640d6d1d72464",
+
+ "0493ff637108af6a5b8e90ac1fdf035a3d4bafd1afb573be7ade9e8682e663e5",
+ "c0cd2bebccbb6c49920bd5482ac756e8",
+ "8b37f9148df4bb25956be6310c73c8dc58ea9714ff49b643107b34c9bff096a94fedd6823526abc27a8e0b16616eee254ab4567dd68e8ccd4c38ac563b13639c",
+ "05d5c77729421b08b737e41119fa4438d1f570cc772a4d6c3df7ffeda0384ef84288ce37fc4c4c7d1125a499b051364c389fd639bdda647daa3bdadab2eb5594",
+
+ "9adc8fbd506e032af7fa20cf5343719de6d1288c158c63d6878aaf64ce26ca85",
+ "11958dc6ab81e1c7f01631e9944e620f",
+ "c7917f84f747cd8c4b4fedc2219bdbc5f4d07588389d8248854cf2c2f89667a2d7bcf53e73d32684535f42318e24cd45793950b3825e5d5c5c8fcd3e5dda4ce9246d18337ef3052d8b21c5561c8b660e",
+ "9c99e68236bb2e929db1089c7750f1b356d39ab9d0c40c3e2f05108ae9d0c30b04832ccdbdc08ebfa426b7f5efde986ed05784ce368193bb3699bc691065ac62e258b9aa4cc557e2b45b49ce05511e65",
+
+ "73b8faf00b3302ac99855cf6f9e9e48518690a5906a4869d4dcf48d282faae2a",
+ "b3cb97a80a539912b8c21f450d3b9395",
+ "3adea6e06e42c4f041021491f2775ef6378cb08824165edc4f6448e232175b60d0345b9f9c78df6596ec9d22b7b9e76e8f3c76b32d5d67273f1d83fe7a6fc3dd3c49139170fa5701b3beac61b490f0a9e13f844640c4500f9ad3087adfb0ae10",
+ "ac3d6dbafe2e0f740632fd9e820bf6044cd5b1551cbb9cc03c0b25c39ccb7f33b83aacfca40a3265f2bbff879153448acacb88fcfb3bb7b10fe463a68c0109f028382e3e557b1adf02ed648ab6bb895df0205d26ebbfa9a5fd8cebd8e4bee3dc",
+
+ "9ddf3745896504ff360a51a3eb49c01b79fccebc71c3abcb94a949408b05b2c9",
+ "e79026639d4aa230b5ccffb0b29d79bc",
+ "cf52e5c3954c51b94c9e38acb8c9a7c76aebdaa9943eae0a1ce155a2efdb4d46985d935511471452d9ee64d2461cb2991d59fc0060697f9a671672163230f367fed1422316e52d29eceacb8768f56d9b80f6d278093c9a8acd3cfd7edd8ebd5c293859f64d2f8486ae1bd593c65bc014",
+ "34df561bd2cfebbcb7af3b4b8d21ca5258312e7e2e4e538e35ad2490b6112f0d7f148f6aa8d522a7f3c61d785bd667db0e1dc4606c318ea4f26af4fe7d11d4dcff0456511b4aed1a0d91ba4a1fd6cd9029187bc5881a5a07fe02049d39368e83139b12825bae2c7be81e6f12c61bb5c5",
+
+ "458b67bf212d20f3a57fce392065582dcefbf381aa22949f8338ab9052260e1d",
+ "4c12effc5963d40459602675153e9649",
+ "256fd73ce35ae3ea9c25dd2a9454493e96d8633fe633b56176dce8785ce5dbbb84dbf2c8a2eeb1e96b51899605e4f13bbc11b93bf6f39b3469be14858b5b720d4a522d36feed7a329c9b1e852c9280c47db8039c17c4921571a07d1864128330e09c308ddea1694e95c84500f1a61e614197e86a30ecc28df64ccb3ccf5437aa",
+ "90b7b9630a2378f53f501ab7beff039155008071bc8438e789932cfd3eb1299195465e6633849463fdb44375278e2fdb1310821e6492cf80ff15cb772509fb426f3aeee27bd4938882fd2ae6b5bd9d91fa4a43b17bb439ebbe59c042310163a82a5fe5388796eee35a181a1271f00be29b852d8fa759bad01ff4678f010594cd",
+
+ "d2412db0845d84e5732b8bbd642957473b81fb99ca8bff70e7920d16c1dbec89",
+ "51c619fcf0b23f0c7925f400a6cacb6d",
+ "026006c4a71a180c9929824d9d095b8faaa86fc4fa25ecac61d85ff6de92dfa8702688c02a282c1b8af4449707f22d75e91991015db22374c95f8f195d5bb0afeb03040ff8965e0e1339dba5653e174f8aa5a1b39fe3ac839ce307a4e44b4f8f1b0063f738ec18acdbff2ebfe07383e734558723e741f0a1836dafdf9de82210a9248bc113b3c1bc8b4e252ca01bd803",
+ "0254b23463bcabec5a395eb74c8fb0eb137a07bc6f5e9f61ec0b057de305714f8fa294221c91a159c315939b81e300ee902192ec5f15254428d8772f79324ec43298ca21c00b370273ee5e5ed90e43efa1e05a5d171209fe34f9f29237dba2a6726650fd3b1321747d1208863c6c3c6b3e2d879ab5f25782f08ba8f2abbe63e0bedb4a227e81afb36bb6645508356d34",
+
+ "48be597e632c16772324c8d3fa1d9c5a9ecd010f14ec5d110d3bfec376c5532b",
+ "d6d581b8cf04ebd3b6eaa1b53f047ee1",
+ "0c63d413d3864570e70bb6618bf8a4b9585586688c32bba0a5ecc1362fada74ada32c52acfd1aa7444ba567b4e7daaecf7cc1cb29182af164ae5232b002868695635599807a9a7f07a1f137e97b1e1c9dabc89b6a5e4afa9db5855edaa575056a8f4f8242216242bb0c256310d9d329826ac353d715fa39f80cec144d6424558f9f70b98c920096e0f2c855d594885a00625880e9dfb734163cecef72cf030b8",
+ "fc5873e50de8faf4c6b84ba707b0854e9db9ab2e9f7d707fbba338c6843a18fc6facebaf663d26296fb329b4d26f18494c79e09e779647f9bafa87489630d79f4301610c2300c19dbf3148b7cac8c4f4944102754f332e92b6f7c5e75bc6179eb877a078d4719009021744c14f13fd2a55a2b9c44d18000685a845a4f632c7c56a77306efa66a24d05d088dcd7c13fe24fc447275965db9e4d37fbc9304448cd"
+ };
+
+ /*
+ * AES known-answer tests for CTR.
+ * Order: key, IV, plaintext, ciphertext.
+ */
+ static string[] KAT_AES_CTR = {
+ /*
+ * From RFC 3686.
+ */
+ "ae6852f8121067cc4bf7a5765577f39e",
+ "000000300000000000000000",
+ "53696e676c6520626c6f636b206d7367",
+ "e4095d4fb7a7b3792d6175a3261311b8",
+
+ "7e24067817fae0d743d6ce1f32539163",
+ "006cb6dbc0543b59da48d90b",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "5104a106168a72d9790d41ee8edad388eb2e1efc46da57c8fce630df9141be28",
+
+ "7691be035e5020a8ac6e618529f9a0dc",
+ "00e0017b27777f3f4a1786f0",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223",
+ "c1cf48a89f2ffdd9cf4652e9efdb72d74540a42bde6d7836d59a5ceaaef3105325b2072f",
+
+ "16af5b145fc9f579c175f93e3bfb0eed863d06ccfdb78515",
+ "0000004836733c147d6d93cb",
+ "53696e676c6520626c6f636b206d7367",
+ "4b55384fe259c9c84e7935a003cbe928",
+
+ "7c5cb2401b3dc33c19e7340819e0f69c678c3db8e6f6a91a",
+ "0096b03b020c6eadc2cb500d",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "453243fc609b23327edfaafa7131cd9f8490701c5ad4a79cfc1fe0ff42f4fb00",
+
+ "02bf391ee8ecb159b959617b0965279bf59b60a786d3e0fe",
+ "0007bdfd5cbd60278dcc0912",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223",
+ "96893fc55e5c722f540b7dd1ddf7e758d288bc95c69165884536c811662f2188abee0935",
+
+ "776beff2851db06f4c8a0542c8696f6c6a81af1eec96b4d37fc1d689e6c1c104",
+ "00000060db5672c97aa8f0b2",
+ "53696e676c6520626c6f636b206d7367",
+ "145ad01dbf824ec7560863dc71e3e0c0",
+
+ "f6d66d6bd52d59bb0796365879eff886c66dd51a5b6a99744b50590c87a23884",
+ "00faac24c1585ef15a43d875",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "f05e231b3894612c49ee000b804eb2a9b8306b508f839d6a5530831d9344af1c",
+
+ "ff7a617ce69148e4f1726e2f43581de2aa62d9f805532edff1eed687fb54153d",
+ "001cc5b751a51d70a1c11148",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223",
+ "eb6c52821d0bbbf7ce7594462aca4faab407df866569fd07f48cc0b583d6071f1ec0e6b8"
+ };
+
+ /*
+ * DES known-answer tests.
+ * Order: plaintext, key, ciphertext.
+ * (mostly from NIST SP 800-20).
+ */
+ static string[] KAT_DES_RAW = {
+ "10316E028C8F3B4A", "0000000000000000", "82DCBAFBDEAB6602",
+ "8000000000000000", "0000000000000000", "95A8D72813DAA94D",
+ "4000000000000000", "0000000000000000", "0EEC1487DD8C26D5",
+ "2000000000000000", "0000000000000000", "7AD16FFB79C45926",
+ "1000000000000000", "0000000000000000", "D3746294CA6A6CF3",
+ "0800000000000000", "0000000000000000", "809F5F873C1FD761",
+ "0400000000000000", "0000000000000000", "C02FAFFEC989D1FC",
+ "0200000000000000", "0000000000000000", "4615AA1D33E72F10",
+ "0100000000000000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0080000000000000", "0000000000000000", "2055123350C00858",
+ "0040000000000000", "0000000000000000", "DF3B99D6577397C8",
+ "0020000000000000", "0000000000000000", "31FE17369B5288C9",
+ "0010000000000000", "0000000000000000", "DFDD3CC64DAE1642",
+ "0008000000000000", "0000000000000000", "178C83CE2B399D94",
+ "0004000000000000", "0000000000000000", "50F636324A9B7F80",
+ "0002000000000000", "0000000000000000", "A8468EE3BC18F06D",
+ "0001000000000000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000800000000000", "0000000000000000", "A2DC9E92FD3CDE92",
+ "0000400000000000", "0000000000000000", "CAC09F797D031287",
+ "0000200000000000", "0000000000000000", "90BA680B22AEB525",
+ "0000100000000000", "0000000000000000", "CE7A24F350E280B6",
+ "0000080000000000", "0000000000000000", "882BFF0AA01A0B87",
+ "0000040000000000", "0000000000000000", "25610288924511C2",
+ "0000020000000000", "0000000000000000", "C71516C29C75D170",
+ "0000010000000000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000008000000000", "0000000000000000", "5199C29A52C9F059",
+ "0000004000000000", "0000000000000000", "C22F0A294A71F29F",
+ "0000002000000000", "0000000000000000", "EE371483714C02EA",
+ "0000001000000000", "0000000000000000", "A81FBD448F9E522F",
+ "0000000800000000", "0000000000000000", "4F644C92E192DFED",
+ "0000000400000000", "0000000000000000", "1AFA9A66A6DF92AE",
+ "0000000200000000", "0000000000000000", "B3C1CC715CB879D8",
+ "0000000100000000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000000080000000", "0000000000000000", "19D032E64AB0BD8B",
+ "0000000040000000", "0000000000000000", "3CFAA7A7DC8720DC",
+ "0000000020000000", "0000000000000000", "B7265F7F447AC6F3",
+ "0000000010000000", "0000000000000000", "9DB73B3C0D163F54",
+ "0000000008000000", "0000000000000000", "8181B65BABF4A975",
+ "0000000004000000", "0000000000000000", "93C9B64042EAA240",
+ "0000000002000000", "0000000000000000", "5570530829705592",
+ "0000000001000000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000000000800000", "0000000000000000", "8638809E878787A0",
+ "0000000000400000", "0000000000000000", "41B9A79AF79AC208",
+ "0000000000200000", "0000000000000000", "7A9BE42F2009A892",
+ "0000000000100000", "0000000000000000", "29038D56BA6D2745",
+ "0000000000080000", "0000000000000000", "5495C6ABF1E5DF51",
+ "0000000000040000", "0000000000000000", "AE13DBD561488933",
+ "0000000000020000", "0000000000000000", "024D1FFA8904E389",
+ "0000000000010000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000000000008000", "0000000000000000", "D1399712F99BF02E",
+ "0000000000004000", "0000000000000000", "14C1D7C1CFFEC79E",
+ "0000000000002000", "0000000000000000", "1DE5279DAE3BED6F",
+ "0000000000001000", "0000000000000000", "E941A33F85501303",
+ "0000000000000800", "0000000000000000", "DA99DBBC9A03F379",
+ "0000000000000400", "0000000000000000", "B7FC92F91D8E92E9",
+ "0000000000000200", "0000000000000000", "AE8E5CAA3CA04E85",
+ "0000000000000100", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000000000000080", "0000000000000000", "9CC62DF43B6EED74",
+ "0000000000000040", "0000000000000000", "D863DBB5C59A91A0",
+ "0000000000000020", "0000000000000000", "A1AB2190545B91D7",
+ "0000000000000010", "0000000000000000", "0875041E64C570F7",
+ "0000000000000008", "0000000000000000", "5A594528BEBEF1CC",
+ "0000000000000004", "0000000000000000", "FCDB3291DE21F0C0",
+ "0000000000000002", "0000000000000000", "869EFD7F9F265A09",
+ "0000000000000001", "0000000000000000", "8CA64DE9C1B123A7",
+ "0000000000000000", "8000000000000000", "95F8A5E5DD31D900",
+ "0000000000000000", "4000000000000000", "DD7F121CA5015619",
+ "0000000000000000", "2000000000000000", "2E8653104F3834EA",
+ "0000000000000000", "1000000000000000", "4BD388FF6CD81D4F",
+ "0000000000000000", "0800000000000000", "20B9E767B2FB1456",
+ "0000000000000000", "0400000000000000", "55579380D77138EF",
+ "0000000000000000", "0200000000000000", "6CC5DEFAAF04512F",
+ "0000000000000000", "0100000000000000", "0D9F279BA5D87260",
+ "0000000000000000", "0080000000000000", "D9031B0271BD5A0A",
+ "0000000000000000", "0040000000000000", "424250B37C3DD951",
+ "0000000000000000", "0020000000000000", "B8061B7ECD9A21E5",
+ "0000000000000000", "0010000000000000", "F15D0F286B65BD28",
+ "0000000000000000", "0008000000000000", "ADD0CC8D6E5DEBA1",
+ "0000000000000000", "0004000000000000", "E6D5F82752AD63D1",
+ "0000000000000000", "0002000000000000", "ECBFE3BD3F591A5E",
+ "0000000000000000", "0001000000000000", "F356834379D165CD",
+ "0000000000000000", "0000800000000000", "2B9F982F20037FA9",
+ "0000000000000000", "0000400000000000", "889DE068A16F0BE6",
+ "0000000000000000", "0000200000000000", "E19E275D846A1298",
+ "0000000000000000", "0000100000000000", "329A8ED523D71AEC",
+ "0000000000000000", "0000080000000000", "E7FCE22557D23C97",
+ "0000000000000000", "0000040000000000", "12A9F5817FF2D65D",
+ "0000000000000000", "0000020000000000", "A484C3AD38DC9C19",
+ "0000000000000000", "0000010000000000", "FBE00A8A1EF8AD72",
+ "0000000000000000", "0000008000000000", "750D079407521363",
+ "0000000000000000", "0000004000000000", "64FEED9C724C2FAF",
+ "0000000000000000", "0000002000000000", "F02B263B328E2B60",
+ "0000000000000000", "0000001000000000", "9D64555A9A10B852",
+ "0000000000000000", "0000000800000000", "D106FF0BED5255D7",
+ "0000000000000000", "0000000400000000", "E1652C6B138C64A5",
+ "0000000000000000", "0000000200000000", "E428581186EC8F46",
+ "0000000000000000", "0000000100000000", "AEB5F5EDE22D1A36",
+ "0000000000000000", "0000000080000000", "E943D7568AEC0C5C",
+ "0000000000000000", "0000000040000000", "DF98C8276F54B04B",
+ "0000000000000000", "0000000020000000", "B160E4680F6C696F",
+ "0000000000000000", "0000000010000000", "FA0752B07D9C4AB8",
+ "0000000000000000", "0000000008000000", "CA3A2B036DBC8502",
+ "0000000000000000", "0000000004000000", "5E0905517BB59BCF",
+ "0000000000000000", "0000000002000000", "814EEB3B91D90726",
+ "0000000000000000", "0000000001000000", "4D49DB1532919C9F",
+ "0000000000000000", "0000000000800000", "25EB5FC3F8CF0621",
+ "0000000000000000", "0000000000400000", "AB6A20C0620D1C6F",
+ "0000000000000000", "0000000000200000", "79E90DBC98F92CCA",
+ "0000000000000000", "0000000000100000", "866ECEDD8072BB0E",
+ "0000000000000000", "0000000000080000", "8B54536F2F3E64A8",
+ "0000000000000000", "0000000000040000", "EA51D3975595B86B",
+ "0000000000000000", "0000000000020000", "CAFFC6AC4542DE31",
+ "0000000000000000", "0000000000010000", "8DD45A2DDF90796C",
+ "0000000000000000", "0000000000008000", "1029D55E880EC2D0",
+ "0000000000000000", "0000000000004000", "5D86CB23639DBEA9",
+ "0000000000000000", "0000000000002000", "1D1CA853AE7C0C5F",
+ "0000000000000000", "0000000000001000", "CE332329248F3228",
+ "0000000000000000", "0000000000000800", "8405D1ABE24FB942",
+ "0000000000000000", "0000000000000400", "E643D78090CA4207",
+ "0000000000000000", "0000000000000200", "48221B9937748A23",
+ "0000000000000000", "0000000000000100", "DD7C0BBD61FAFD54",
+ "0000000000000000", "0000000000000080", "2FBC291A570DB5C4",
+ "0000000000000000", "0000000000000040", "E07C30D7E4E26E12",
+ "0000000000000000", "0000000000000020", "0953E2258E8E90A1",
+ "0000000000000000", "0000000000000010", "5B711BC4CEEBF2EE",
+ "0000000000000000", "0000000000000008", "CC083F1E6D9E85F6",
+ "0000000000000000", "0000000000000004", "D2FD8867D50D2DFE",
+ "0000000000000000", "0000000000000002", "06E7EA22CE92708F",
+ "0000000000000000", "0000000000000001", "166B40B44ABA4BD6",
+ "0000000000000000", "0000000000000000", "8CA64DE9C1B123A7",
+ "0101010101010101", "0101010101010101", "994D4DC157B96C52",
+ "0202020202020202", "0202020202020202", "E127C2B61D98E6E2",
+ "0303030303030303", "0303030303030303", "984C91D78A269CE3",
+ "0404040404040404", "0404040404040404", "1F4570BB77550683",
+ "0505050505050505", "0505050505050505", "3990ABF98D672B16",
+ "0606060606060606", "0606060606060606", "3F5150BBA081D585",
+ "0707070707070707", "0707070707070707", "C65242248C9CF6F2",
+ "0808080808080808", "0808080808080808", "10772D40FAD24257",
+ "0909090909090909", "0909090909090909", "F0139440647A6E7B",
+ "0A0A0A0A0A0A0A0A", "0A0A0A0A0A0A0A0A", "0A288603044D740C",
+ "0B0B0B0B0B0B0B0B", "0B0B0B0B0B0B0B0B", "6359916942F7438F",
+ "0C0C0C0C0C0C0C0C", "0C0C0C0C0C0C0C0C", "934316AE443CF08B",
+ "0D0D0D0D0D0D0D0D", "0D0D0D0D0D0D0D0D", "E3F56D7F1130A2B7",
+ "0E0E0E0E0E0E0E0E", "0E0E0E0E0E0E0E0E", "A2E4705087C6B6B4",
+ "0F0F0F0F0F0F0F0F", "0F0F0F0F0F0F0F0F", "D5D76E09A447E8C3",
+ "1010101010101010", "1010101010101010", "DD7515F2BFC17F85",
+ "1111111111111111", "1111111111111111", "F40379AB9E0EC533",
+ "1212121212121212", "1212121212121212", "96CD27784D1563E5",
+ "1313131313131313", "1313131313131313", "2911CF5E94D33FE1",
+ "1414141414141414", "1414141414141414", "377B7F7CA3E5BBB3",
+ "1515151515151515", "1515151515151515", "701AA63832905A92",
+ "1616161616161616", "1616161616161616", "2006E716C4252D6D",
+ "1717171717171717", "1717171717171717", "452C1197422469F8",
+ "1818181818181818", "1818181818181818", "C33FD1EB49CB64DA",
+ "1919191919191919", "1919191919191919", "7572278F364EB50D",
+ "1A1A1A1A1A1A1A1A", "1A1A1A1A1A1A1A1A", "69E51488403EF4C3",
+ "1B1B1B1B1B1B1B1B", "1B1B1B1B1B1B1B1B", "FF847E0ADF192825",
+ "1C1C1C1C1C1C1C1C", "1C1C1C1C1C1C1C1C", "521B7FB3B41BB791",
+ "1D1D1D1D1D1D1D1D", "1D1D1D1D1D1D1D1D", "26059A6A0F3F6B35",
+ "1E1E1E1E1E1E1E1E", "1E1E1E1E1E1E1E1E", "F24A8D2231C77538",
+ "1F1F1F1F1F1F1F1F", "1F1F1F1F1F1F1F1F", "4FD96EC0D3304EF6",
+ "2020202020202020", "2020202020202020", "18A9D580A900B699",
+ "2121212121212121", "2121212121212121", "88586E1D755B9B5A",
+ "2222222222222222", "2222222222222222", "0F8ADFFB11DC2784",
+ "2323232323232323", "2323232323232323", "2F30446C8312404A",
+ "2424242424242424", "2424242424242424", "0BA03D9E6C196511",
+ "2525252525252525", "2525252525252525", "3E55E997611E4B7D",
+ "2626262626262626", "2626262626262626", "B2522FB5F158F0DF",
+ "2727272727272727", "2727272727272727", "2109425935406AB8",
+ "2828282828282828", "2828282828282828", "11A16028F310FF16",
+ "2929292929292929", "2929292929292929", "73F0C45F379FE67F",
+ "2A2A2A2A2A2A2A2A", "2A2A2A2A2A2A2A2A", "DCAD4338F7523816",
+ "2B2B2B2B2B2B2B2B", "2B2B2B2B2B2B2B2B", "B81634C1CEAB298C",
+ "2C2C2C2C2C2C2C2C", "2C2C2C2C2C2C2C2C", "DD2CCB29B6C4C349",
+ "2D2D2D2D2D2D2D2D", "2D2D2D2D2D2D2D2D", "7D07A77A2ABD50A7",
+ "2E2E2E2E2E2E2E2E", "2E2E2E2E2E2E2E2E", "30C1B0C1FD91D371",
+ "2F2F2F2F2F2F2F2F", "2F2F2F2F2F2F2F2F", "C4427B31AC61973B",
+ "3030303030303030", "3030303030303030", "F47BB46273B15EB5",
+ "3131313131313131", "3131313131313131", "655EA628CF62585F",
+ "3232323232323232", "3232323232323232", "AC978C247863388F",
+ "3333333333333333", "3333333333333333", "0432ED386F2DE328",
+ "3434343434343434", "3434343434343434", "D254014CB986B3C2",
+ "3535353535353535", "3535353535353535", "B256E34BEDB49801",
+ "3636363636363636", "3636363636363636", "37F8759EB77E7BFC",
+ "3737373737373737", "3737373737373737", "5013CA4F62C9CEA0",
+ "3838383838383838", "3838383838383838", "8940F7B3EACA5939",
+ "3939393939393939", "3939393939393939", "E22B19A55086774B",
+ "3A3A3A3A3A3A3A3A", "3A3A3A3A3A3A3A3A", "B04A2AAC925ABB0B",
+ "3B3B3B3B3B3B3B3B", "3B3B3B3B3B3B3B3B", "8D250D58361597FC",
+ "3C3C3C3C3C3C3C3C", "3C3C3C3C3C3C3C3C", "51F0114FB6A6CD37",
+ "3D3D3D3D3D3D3D3D", "3D3D3D3D3D3D3D3D", "9D0BB4DB830ECB73",
+ "3E3E3E3E3E3E3E3E", "3E3E3E3E3E3E3E3E", "E96089D6368F3E1A",
+ "3F3F3F3F3F3F3F3F", "3F3F3F3F3F3F3F3F", "5C4CA877A4E1E92D",
+ "4040404040404040", "4040404040404040", "6D55DDBC8DEA95FF",
+ "4141414141414141", "4141414141414141", "19DF84AC95551003",
+ "4242424242424242", "4242424242424242", "724E7332696D08A7",
+ "4343434343434343", "4343434343434343", "B91810B8CDC58FE2",
+ "4444444444444444", "4444444444444444", "06E23526EDCCD0C4",
+ "4545454545454545", "4545454545454545", "EF52491D5468D441",
+ "4646464646464646", "4646464646464646", "48019C59E39B90C5",
+ "4747474747474747", "4747474747474747", "0544083FB902D8C0",
+ "4848484848484848", "4848484848484848", "63B15CADA668CE12",
+ "4949494949494949", "4949494949494949", "EACC0C1264171071",
+ "4A4A4A4A4A4A4A4A", "4A4A4A4A4A4A4A4A", "9D2B8C0AC605F274",
+ "4B4B4B4B4B4B4B4B", "4B4B4B4B4B4B4B4B", "C90F2F4C98A8FB2A",
+ "4C4C4C4C4C4C4C4C", "4C4C4C4C4C4C4C4C", "03481B4828FD1D04",
+ "4D4D4D4D4D4D4D4D", "4D4D4D4D4D4D4D4D", "C78FC45A1DCEA2E2",
+ "4E4E4E4E4E4E4E4E", "4E4E4E4E4E4E4E4E", "DB96D88C3460D801",
+ "4F4F4F4F4F4F4F4F", "4F4F4F4F4F4F4F4F", "6C69E720F5105518",
+ "5050505050505050", "5050505050505050", "0D262E418BC893F3",
+ "5151515151515151", "5151515151515151", "6AD84FD7848A0A5C",
+ "5252525252525252", "5252525252525252", "C365CB35B34B6114",
+ "5353535353535353", "5353535353535353", "1155392E877F42A9",
+ "5454545454545454", "5454545454545454", "531BE5F9405DA715",
+ "5555555555555555", "5555555555555555", "3BCDD41E6165A5E8",
+ "5656565656565656", "5656565656565656", "2B1FF5610A19270C",
+ "5757575757575757", "5757575757575757", "D90772CF3F047CFD",
+ "5858585858585858", "5858585858585858", "1BEA27FFB72457B7",
+ "5959595959595959", "5959595959595959", "85C3E0C429F34C27",
+ "5A5A5A5A5A5A5A5A", "5A5A5A5A5A5A5A5A", "F9038021E37C7618",
+ "5B5B5B5B5B5B5B5B", "5B5B5B5B5B5B5B5B", "35BC6FF838DBA32F",
+ "5C5C5C5C5C5C5C5C", "5C5C5C5C5C5C5C5C", "4927ACC8CE45ECE7",
+ "5D5D5D5D5D5D5D5D", "5D5D5D5D5D5D5D5D", "E812EE6E3572985C",
+ "5E5E5E5E5E5E5E5E", "5E5E5E5E5E5E5E5E", "9BB93A89627BF65F",
+ "5F5F5F5F5F5F5F5F", "5F5F5F5F5F5F5F5F", "EF12476884CB74CA",
+ "6060606060606060", "6060606060606060", "1BF17E00C09E7CBF",
+ "6161616161616161", "6161616161616161", "29932350C098DB5D",
+ "6262626262626262", "6262626262626262", "B476E6499842AC54",
+ "6363636363636363", "6363636363636363", "5C662C29C1E96056",
+ "6464646464646464", "6464646464646464", "3AF1703D76442789",
+ "6565656565656565", "6565656565656565", "86405D9B425A8C8C",
+ "6666666666666666", "6666666666666666", "EBBF4810619C2C55",
+ "6767676767676767", "6767676767676767", "F8D1CD7367B21B5D",
+ "6868686868686868", "6868686868686868", "9EE703142BF8D7E2",
+ "6969696969696969", "6969696969696969", "5FDFFFC3AAAB0CB3",
+ "6A6A6A6A6A6A6A6A", "6A6A6A6A6A6A6A6A", "26C940AB13574231",
+ "6B6B6B6B6B6B6B6B", "6B6B6B6B6B6B6B6B", "1E2DC77E36A84693",
+ "6C6C6C6C6C6C6C6C", "6C6C6C6C6C6C6C6C", "0F4FF4D9BC7E2244",
+ "6D6D6D6D6D6D6D6D", "6D6D6D6D6D6D6D6D", "A4C9A0D04D3280CD",
+ "6E6E6E6E6E6E6E6E", "6E6E6E6E6E6E6E6E", "9FAF2C96FE84919D",
+ "6F6F6F6F6F6F6F6F", "6F6F6F6F6F6F6F6F", "115DBC965E6096C8",
+ "7070707070707070", "7070707070707070", "AF531E9520994017",
+ "7171717171717171", "7171717171717171", "B971ADE70E5C89EE",
+ "7272727272727272", "7272727272727272", "415D81C86AF9C376",
+ "7373737373737373", "7373737373737373", "8DFB864FDB3C6811",
+ "7474747474747474", "7474747474747474", "10B1C170E3398F91",
+ "7575757575757575", "7575757575757575", "CFEF7A1C0218DB1E",
+ "7676767676767676", "7676767676767676", "DBAC30A2A40B1B9C",
+ "7777777777777777", "7777777777777777", "89D3BF37052162E9",
+ "7878787878787878", "7878787878787878", "80D9230BDAEB67DC",
+ "7979797979797979", "7979797979797979", "3440911019AD68D7",
+ "7A7A7A7A7A7A7A7A", "7A7A7A7A7A7A7A7A", "9626FE57596E199E",
+ "7B7B7B7B7B7B7B7B", "7B7B7B7B7B7B7B7B", "DEA0B796624BB5BA",
+ "7C7C7C7C7C7C7C7C", "7C7C7C7C7C7C7C7C", "E9E40542BDDB3E9D",
+ "7D7D7D7D7D7D7D7D", "7D7D7D7D7D7D7D7D", "8AD99914B354B911",
+ "7E7E7E7E7E7E7E7E", "7E7E7E7E7E7E7E7E", "6F85B98DD12CB13B",
+ "7F7F7F7F7F7F7F7F", "7F7F7F7F7F7F7F7F", "10130DA3C3A23924",
+ "8080808080808080", "8080808080808080", "EFECF25C3C5DC6DB",
+ "8181818181818181", "8181818181818181", "907A46722ED34EC4",
+ "8282828282828282", "8282828282828282", "752666EB4CAB46EE",
+ "8383838383838383", "8383838383838383", "161BFABD4224C162",
+ "8484848484848484", "8484848484848484", "215F48699DB44A45",
+ "8585858585858585", "8585858585858585", "69D901A8A691E661",
+ "8686868686868686", "8686868686868686", "CBBF6EEFE6529728",
+ "8787878787878787", "8787878787878787", "7F26DCF425149823",
+ "8888888888888888", "8888888888888888", "762C40C8FADE9D16",
+ "8989898989898989", "8989898989898989", "2453CF5D5BF4E463",
+ "8A8A8A8A8A8A8A8A", "8A8A8A8A8A8A8A8A", "301085E3FDE724E1",
+ "8B8B8B8B8B8B8B8B", "8B8B8B8B8B8B8B8B", "EF4E3E8F1CC6706E",
+ "8C8C8C8C8C8C8C8C", "8C8C8C8C8C8C8C8C", "720479B024C397EE",
+ "8D8D8D8D8D8D8D8D", "8D8D8D8D8D8D8D8D", "BEA27E3795063C89",
+ "8E8E8E8E8E8E8E8E", "8E8E8E8E8E8E8E8E", "468E5218F1A37611",
+ "8F8F8F8F8F8F8F8F", "8F8F8F8F8F8F8F8F", "50ACE16ADF66BFE8",
+ "9090909090909090", "9090909090909090", "EEA24369A19F6937",
+ "9191919191919191", "9191919191919191", "6050D369017B6E62",
+ "9292929292929292", "9292929292929292", "5B365F2FB2CD7F32",
+ "9393939393939393", "9393939393939393", "F0B00B264381DDBB",
+ "9494949494949494", "9494949494949494", "E1D23881C957B96C",
+ "9595959595959595", "9595959595959595", "D936BF54ECA8BDCE",
+ "9696969696969696", "9696969696969696", "A020003C5554F34C",
+ "9797979797979797", "9797979797979797", "6118FCEBD407281D",
+ "9898989898989898", "9898989898989898", "072E328C984DE4A2",
+ "9999999999999999", "9999999999999999", "1440B7EF9E63D3AA",
+ "9A9A9A9A9A9A9A9A", "9A9A9A9A9A9A9A9A", "79BFA264BDA57373",
+ "9B9B9B9B9B9B9B9B", "9B9B9B9B9B9B9B9B", "C50E8FC289BBD876",
+ "9C9C9C9C9C9C9C9C", "9C9C9C9C9C9C9C9C", "A399D3D63E169FA9",
+ "9D9D9D9D9D9D9D9D", "9D9D9D9D9D9D9D9D", "4B8919B667BD53AB",
+ "9E9E9E9E9E9E9E9E", "9E9E9E9E9E9E9E9E", "D66CDCAF3F6724A2",
+ "9F9F9F9F9F9F9F9F", "9F9F9F9F9F9F9F9F", "E40E81FF3F618340",
+ "A0A0A0A0A0A0A0A0", "A0A0A0A0A0A0A0A0", "10EDB8977B348B35",
+ "A1A1A1A1A1A1A1A1", "A1A1A1A1A1A1A1A1", "6446C5769D8409A0",
+ "A2A2A2A2A2A2A2A2", "A2A2A2A2A2A2A2A2", "17ED1191CA8D67A3",
+ "A3A3A3A3A3A3A3A3", "A3A3A3A3A3A3A3A3", "B6D8533731BA1318",
+ "A4A4A4A4A4A4A4A4", "A4A4A4A4A4A4A4A4", "CA439007C7245CD0",
+ "A5A5A5A5A5A5A5A5", "A5A5A5A5A5A5A5A5", "06FC7FDE1C8389E7",
+ "A6A6A6A6A6A6A6A6", "A6A6A6A6A6A6A6A6", "7A3C1F3BD60CB3D8",
+ "A7A7A7A7A7A7A7A7", "A7A7A7A7A7A7A7A7", "E415D80048DBA848",
+ "A8A8A8A8A8A8A8A8", "A8A8A8A8A8A8A8A8", "26F88D30C0FB8302",
+ "A9A9A9A9A9A9A9A9", "A9A9A9A9A9A9A9A9", "D4E00A9EF5E6D8F3",
+ "AAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAA", "C4322BE19E9A5A17",
+ "ABABABABABABABAB", "ABABABABABABABAB", "ACE41A06BFA258EA",
+ "ACACACACACACACAC", "ACACACACACACACAC", "EEAAC6D17880BD56",
+ "ADADADADADADADAD", "ADADADADADADADAD", "3C9A34CA4CB49EEB",
+ "AEAEAEAEAEAEAEAE", "AEAEAEAEAEAEAEAE", "9527B0287B75F5A3",
+ "AFAFAFAFAFAFAFAF", "AFAFAFAFAFAFAFAF", "F2D9D1BE74376C0C",
+ "B0B0B0B0B0B0B0B0", "B0B0B0B0B0B0B0B0", "939618DF0AEFAAE7",
+ "B1B1B1B1B1B1B1B1", "B1B1B1B1B1B1B1B1", "24692773CB9F27FE",
+ "B2B2B2B2B2B2B2B2", "B2B2B2B2B2B2B2B2", "38703BA5E2315D1D",
+ "B3B3B3B3B3B3B3B3", "B3B3B3B3B3B3B3B3", "FCB7E4B7D702E2FB",
+ "B4B4B4B4B4B4B4B4", "B4B4B4B4B4B4B4B4", "36F0D0B3675704D5",
+ "B5B5B5B5B5B5B5B5", "B5B5B5B5B5B5B5B5", "62D473F539FA0D8B",
+ "B6B6B6B6B6B6B6B6", "B6B6B6B6B6B6B6B6", "1533F3ED9BE8EF8E",
+ "B7B7B7B7B7B7B7B7", "B7B7B7B7B7B7B7B7", "9C4EA352599731ED",
+ "B8B8B8B8B8B8B8B8", "B8B8B8B8B8B8B8B8", "FABBF7C046FD273F",
+ "B9B9B9B9B9B9B9B9", "B9B9B9B9B9B9B9B9", "B7FE63A61C646F3A",
+ "BABABABABABABABA", "BABABABABABABABA", "10ADB6E2AB972BBE",
+ "BBBBBBBBBBBBBBBB", "BBBBBBBBBBBBBBBB", "F91DCAD912332F3B",
+ "BCBCBCBCBCBCBCBC", "BCBCBCBCBCBCBCBC", "46E7EF47323A701D",
+ "BDBDBDBDBDBDBDBD", "BDBDBDBDBDBDBDBD", "8DB18CCD9692F758",
+ "BEBEBEBEBEBEBEBE", "BEBEBEBEBEBEBEBE", "E6207B536AAAEFFC",
+ "BFBFBFBFBFBFBFBF", "BFBFBFBFBFBFBFBF", "92AA224372156A00",
+ "C0C0C0C0C0C0C0C0", "C0C0C0C0C0C0C0C0", "A3B357885B1E16D2",
+ "C1C1C1C1C1C1C1C1", "C1C1C1C1C1C1C1C1", "169F7629C970C1E5",
+ "C2C2C2C2C2C2C2C2", "C2C2C2C2C2C2C2C2", "62F44B247CF1348C",
+ "C3C3C3C3C3C3C3C3", "C3C3C3C3C3C3C3C3", "AE0FEEB0495932C8",
+ "C4C4C4C4C4C4C4C4", "C4C4C4C4C4C4C4C4", "72DAF2A7C9EA6803",
+ "C5C5C5C5C5C5C5C5", "C5C5C5C5C5C5C5C5", "4FB5D5536DA544F4",
+ "C6C6C6C6C6C6C6C6", "C6C6C6C6C6C6C6C6", "1DD4E65AAF7988B4",
+ "C7C7C7C7C7C7C7C7", "C7C7C7C7C7C7C7C7", "76BF084C1535A6C6",
+ "C8C8C8C8C8C8C8C8", "C8C8C8C8C8C8C8C8", "AFEC35B09D36315F",
+ "C9C9C9C9C9C9C9C9", "C9C9C9C9C9C9C9C9", "C8078A6148818403",
+ "CACACACACACACACA", "CACACACACACACACA", "4DA91CB4124B67FE",
+ "CBCBCBCBCBCBCBCB", "CBCBCBCBCBCBCBCB", "2DABFEB346794C3D",
+ "CCCCCCCCCCCCCCCC", "CCCCCCCCCCCCCCCC", "FBCD12C790D21CD7",
+ "CDCDCDCDCDCDCDCD", "CDCDCDCDCDCDCDCD", "536873DB879CC770",
+ "CECECECECECECECE", "CECECECECECECECE", "9AA159D7309DA7A0",
+ "CFCFCFCFCFCFCFCF", "CFCFCFCFCFCFCFCF", "0B844B9D8C4EA14A",
+ "D0D0D0D0D0D0D0D0", "D0D0D0D0D0D0D0D0", "3BBD84CE539E68C4",
+ "D1D1D1D1D1D1D1D1", "D1D1D1D1D1D1D1D1", "CF3E4F3E026E2C8E",
+ "D2D2D2D2D2D2D2D2", "D2D2D2D2D2D2D2D2", "82F85885D542AF58",
+ "D3D3D3D3D3D3D3D3", "D3D3D3D3D3D3D3D3", "22D334D6493B3CB6",
+ "D4D4D4D4D4D4D4D4", "D4D4D4D4D4D4D4D4", "47E9CB3E3154D673",
+ "D5D5D5D5D5D5D5D5", "D5D5D5D5D5D5D5D5", "2352BCC708ADC7E9",
+ "D6D6D6D6D6D6D6D6", "D6D6D6D6D6D6D6D6", "8C0F3BA0C8601980",
+ "D7D7D7D7D7D7D7D7", "D7D7D7D7D7D7D7D7", "EE5E9FD70CEF00E9",
+ "D8D8D8D8D8D8D8D8", "D8D8D8D8D8D8D8D8", "DEF6BDA6CABF9547",
+ "D9D9D9D9D9D9D9D9", "D9D9D9D9D9D9D9D9", "4DADD04A0EA70F20",
+ "DADADADADADADADA", "DADADADADADADADA", "C1AA16689EE1B482",
+ "DBDBDBDBDBDBDBDB", "DBDBDBDBDBDBDBDB", "F45FC26193E69AEE",
+ "DCDCDCDCDCDCDCDC", "DCDCDCDCDCDCDCDC", "D0CFBB937CEDBFB5",
+ "DDDDDDDDDDDDDDDD", "DDDDDDDDDDDDDDDD", "F0752004EE23D87B",
+ "DEDEDEDEDEDEDEDE", "DEDEDEDEDEDEDEDE", "77A791E28AA464A5",
+ "DFDFDFDFDFDFDFDF", "DFDFDFDFDFDFDFDF", "E7562A7F56FF4966",
+ "E0E0E0E0E0E0E0E0", "E0E0E0E0E0E0E0E0", "B026913F2CCFB109",
+ "E1E1E1E1E1E1E1E1", "E1E1E1E1E1E1E1E1", "0DB572DDCE388AC7",
+ "E2E2E2E2E2E2E2E2", "E2E2E2E2E2E2E2E2", "D9FA6595F0C094CA",
+ "E3E3E3E3E3E3E3E3", "E3E3E3E3E3E3E3E3", "ADE4804C4BE4486E",
+ "E4E4E4E4E4E4E4E4", "E4E4E4E4E4E4E4E4", "007B81F520E6D7DA",
+ "E5E5E5E5E5E5E5E5", "E5E5E5E5E5E5E5E5", "961AEB77BFC10B3C",
+ "E6E6E6E6E6E6E6E6", "E6E6E6E6E6E6E6E6", "8A8DD870C9B14AF2",
+ "E7E7E7E7E7E7E7E7", "E7E7E7E7E7E7E7E7", "3CC02E14B6349B25",
+ "E8E8E8E8E8E8E8E8", "E8E8E8E8E8E8E8E8", "BAD3EE68BDDB9607",
+ "E9E9E9E9E9E9E9E9", "E9E9E9E9E9E9E9E9", "DFF918E93BDAD292",
+ "EAEAEAEAEAEAEAEA", "EAEAEAEAEAEAEAEA", "8FE559C7CD6FA56D",
+ "EBEBEBEBEBEBEBEB", "EBEBEBEBEBEBEBEB", "C88480835C1A444C",
+ "ECECECECECECECEC", "ECECECECECECECEC", "D6EE30A16B2CC01E",
+ "EDEDEDEDEDEDEDED", "EDEDEDEDEDEDEDED", "6932D887B2EA9C1A",
+ "EEEEEEEEEEEEEEEE", "EEEEEEEEEEEEEEEE", "0BFC865461F13ACC",
+ "EFEFEFEFEFEFEFEF", "EFEFEFEFEFEFEFEF", "228AEA0D403E807A",
+ "F0F0F0F0F0F0F0F0", "F0F0F0F0F0F0F0F0", "2A2891F65BB8173C",
+ "F1F1F1F1F1F1F1F1", "F1F1F1F1F1F1F1F1", "5D1B8FAF7839494B",
+ "F2F2F2F2F2F2F2F2", "F2F2F2F2F2F2F2F2", "1C0A9280EECF5D48",
+ "F3F3F3F3F3F3F3F3", "F3F3F3F3F3F3F3F3", "6CBCE951BBC30F74",
+ "F4F4F4F4F4F4F4F4", "F4F4F4F4F4F4F4F4", "9CA66E96BD08BC70",
+ "F5F5F5F5F5F5F5F5", "F5F5F5F5F5F5F5F5", "F5D779FCFBB28BF3",
+ "F6F6F6F6F6F6F6F6", "F6F6F6F6F6F6F6F6", "0FEC6BBF9B859184",
+ "F7F7F7F7F7F7F7F7", "F7F7F7F7F7F7F7F7", "EF88D2BF052DBDA8",
+ "F8F8F8F8F8F8F8F8", "F8F8F8F8F8F8F8F8", "39ADBDDB7363090D",
+ "F9F9F9F9F9F9F9F9", "F9F9F9F9F9F9F9F9", "C0AEAF445F7E2A7A",
+ "FAFAFAFAFAFAFAFA", "FAFAFAFAFAFAFAFA", "C66F54067298D4E9",
+ "FBFBFBFBFBFBFBFB", "FBFBFBFBFBFBFBFB", "E0BA8F4488AAF97C",
+ "FCFCFCFCFCFCFCFC", "FCFCFCFCFCFCFCFC", "67B36E2875D9631C",
+ "FDFDFDFDFDFDFDFD", "FDFDFDFDFDFDFDFD", "1ED83D49E267191D",
+ "FEFEFEFEFEFEFEFE", "FEFEFEFEFEFEFEFE", "66B2B23EA84693AD",
+ "FFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "7359B2163E4EDC58",
+ "0001020304050607", "0011223344556677", "3EF0A891CF8ED990",
+ "2BD6459F82C5B300", "EA024714AD5C4D84", "126EFE8ED312190A"
+ };
+
+ /*
+ * Known-answer tests for DES/3DES in CBC mode.
+ * Order: key, IV, plaintext, ciphertext.
+ */
+ static string[] KAT_DES_CBC = {
+ /*
+ * From NIST validation suite (tdesmmt.zip).
+ */
+ "34a41a8c293176c1b30732ecfe38ae8a34a41a8c293176c1",
+ "f55b4855228bd0b4",
+ "7dd880d2a9ab411c",
+ "c91892948b6cadb4",
+
+ "70a88fa1dfb9942fa77f40157ffef2ad70a88fa1dfb9942f",
+ "ece08ce2fdc6ce80",
+ "bc225304d5a3a5c9918fc5006cbc40cc",
+ "27f67dc87af7ddb4b68f63fa7c2d454a",
+
+ "e091790be55be0bc0780153861a84adce091790be55be0bc",
+ "fd7d430f86fbbffe",
+ "03c7fffd7f36499c703dedc9df4de4a92dd4382e576d6ae9",
+ "053aeba85dd3a23bfbe8440a432f9578f312be60fb9f0035",
+
+ "857feacd16157c58e5347a70e56e578a857feacd16157c58",
+ "002dcb6d46ef0969",
+ "1f13701c7f0d7385307507a18e89843ebd295bd5e239ef109347a6898c6d3fd5",
+ "a0e4edde34f05bd8397ce279e49853e9387ba04be562f5fa19c3289c3f5a3391",
+
+ "a173545b265875ba852331fbb95b49a8a173545b265875ba",
+ "ab385756391d364c",
+ "d08894c565608d9ae51dda63b85b3b33b1703bb5e4f1abcbb8794e743da5d6f3bf630f2e9b6d5b54",
+ "370b47acf89ac6bdbb13c9a7336787dc41e1ad8beead32281d0609fb54968404bdf2894892590658",
+
+ "26376bcb2f23df1083cd684fe00ed3c726376bcb2f23df10",
+ "33acfb0f3d240ea6",
+ "903a1911da1e6877f23c1985a9b61786ef438e0ce1240885035ad60fc916b18e5d71a1fb9c5d1eff61db75c0076f6efb",
+ "7a4f7510f6ec0b93e2495d21a8355684d303a770ebda2e0e51ff33d72b20cb73e58e2e3de2ef6b2e12c504c0f181ba63",
+
+ "3e1f98135d027cec752f67765408a7913e1f98135d027cec",
+ "11f5f2304b28f68b",
+ "7c022f5af24f7925d323d4d0e20a2ce49272c5e764b22c806f4b6ddc406d864fe5bd1c3f45556d3eb30c8676c2f8b54a5a32423a0bd95a07",
+ "2bb4b131fa4ae0b4f0378a2cdb68556af6eee837613016d7ea936f3931f25f8b3ae351d5e9d00be665676e2400408b5db9892d95421e7f1a",
+
+ "13b9d549cd136ec7bf9e9810ef2cdcbf13b9d549cd136ec7",
+ "a82c1b1057badcc8",
+ "1fff1563bc1645b55cb23ea34a0049dfc06607150614b621dedcb07f20433402a2d869c95ac4a070c7a3da838c928a385f899c5d21ecb58f4e5cbdad98d39b8c",
+ "75f804d4a2c542a31703e23df26cc38861a0729090e6eae5672c1db8c0b09fba9b125bbca7d6c7d330b3859e6725c6d26de21c4e3af7f5ea94df3cde2349ce37",
+
+ "20320dfdad579bb57c6e4acd769dbadf20320dfdad579bb5",
+ "879201b5857ccdea",
+ "0431283cc8bb4dc7750a9d5c68578486932091632a12d0a79f2c54e3d122130881fff727050f317a40fcd1a8d13793458b99fc98254ba6a233e3d95b55cf5a3faff78809999ea4bf",
+ "85d17840eb2af5fc727027336bfd71a2b31bd14a1d9eb64f8a08bfc4f56eaa9ca7654a5ae698287869cc27324813730de4f1384e0b8cfbc472ff5470e3c5e4bd8ceb23dc2d91988c",
+
+ "23abb073a2df34cb3d1fdce6b092582c23abb073a2df34cb",
+ "7d7fbf19e8562d32",
+ "31e718fd95e6d7ca4f94763191add2674ab07c909d88c486916c16d60a048a0cf8cdb631cebec791362cd0c202eb61e166b65c1f65d0047c8aec57d3d84b9e17032442dce148e1191b06a12c284cc41e",
+ "c9a3f75ab6a7cd08a7fd53ca540aafe731d257ee1c379fadcc4cc1a06e7c12bddbeb7562c436d1da849ed072629e82a97b56d9becc25ff4f16f21c5f2a01911604f0b5c49df96cb641faee662ca8aa68",
+
+ "b5cb1504802326c73df186e3e352a20de643b0d63ee30e37",
+ "43f791134c5647ba",
+ "dcc153cef81d6f24",
+ "92538bd8af18d3ba",
+
+ "a49d7564199e97cb529d2c9d97bf2f98d35edf57ba1f7358",
+ "c2e999cb6249023c",
+ "c689aee38a301bb316da75db36f110b5",
+ "e9afaba5ec75ea1bbe65506655bb4ecb",
+
+ "1a5d4c0825072a15a8ad9dfdaeda8c048adffb85bc4fced0",
+ "7fcfa736f7548b6f",
+ "983c3edacd939406010e1bc6ff9e12320ac5008117fa8f84",
+ "d84fa24f38cf451ca2c9adc960120bd8ff9871584fe31cee",
+
+ "d98aadc76d4a3716158c32866efbb9ce834af2297379a49d",
+ "3c5220327c502b44",
+ "6174079dda53ca723ebf00a66837f8d5ce648c08acaa5ee45ffe62210ef79d3e",
+ "f5bd4d600bed77bec78409e3530ebda1d815506ed53103015b87e371ae000958",
+
+ "ef6d3e54266d978ffb0b8ce6689d803e2cd34cc802fd0252",
+ "38bae5bce06d0ad9",
+ "c4f228b537223cd01c0debb5d9d4e12ba71656618d119b2f8f0af29d23efa3a9e43c4c458a1b79a0",
+ "9e3289fb18379f55aa4e45a7e0e6df160b33b75f8627ad0954f8fdcb78cee55a4664caeda1000fe5",
+
+ "625bc19b19df83abfb2f5bec9d4f2062017525a75bc26e70",
+ "bd0cff364ff69a91",
+ "8152d2ab876c3c8201403a5a406d3feaf27319dbea6ad01e24f4d18203704b86de70da6bbb6d638e5aba3ff576b79b28",
+ "706fe7a973fac40e25b2b4499ce527078944c70e976d017b6af86a3a7a6b52943a72ba18a58000d2b61fdc3bfef2bc4a",
+
+ "b6383176046e6880a1023bf45768b5bf5119022fe054bfe5",
+ "ec13ca541c43401e",
+ "cd5a886e9af011346c4dba36a424f96a78a1ddf28aaa4188bf65451f4efaffc7179a6dd237c0ae35d9b672314e5cb032612597f7e462c6f3",
+ "b030f976f46277ee211c4a324d5c87555d1084513a1223d3b84416b52bbc28f4b77f3a9d8d0d91dc37d3dbe8af8be98f74674b02f9a38527",
+
+ "3d8cf273d343b9aedccddacb91ad86206737adc86b4a49a7",
+ "bb3a9a0c71c62ef0",
+ "1fde3991c32ce220b5b6666a9234f2fd7bd24b921829fd9cdc6eb4218be9eac9faa9c2351777349128086b6d58776bc86ff2f76ee1b3b2850a318462b8983fa1",
+ "422ce705a46bb52ad928dab6c863166d617c6fc24003633120d91918314bbf464cea7345c3c35f2042f2d6929735d74d7728f22fea618a0b9cf5b1281acb13fb",
+
+ "fbceb5cb646b925be0b92f7f6b493d5e5b16e9159732732a",
+ "2e17b3c7025ae86b",
+ "4c309bc8e1e464fdd2a2b8978645d668d455f7526bd8d7b6716a722f6a900b815c4a73cc30e788065c1dfca7bf5958a6cc5440a5ebe7f8691c20278cde95db764ff8ce8994ece89c",
+ "c02129bdf4bbbd75e71605a00b12c80db6b4e05308e916615011f09147ed915dd1bc67f27f9e027e4e13df36b55464a31c11b4d1fe3d855d89df492e1a7201b995c1ba16a8dbabee",
+
+ "9b162a0df8ad9b61c88676e3d586434570b902f12a2046e0",
+ "ebd6fefe029ad54b",
+ "f4c1c918e77355c8156f0fd778da52bff121ae5f2f44eaf4d2754946d0e10d1f18ce3a0176e69c18b7d20b6e0d0bee5eb5edfe4bd60e4d92adcd86bce72e76f94ee5cbcaa8b01cfddcea2ade575e66ac",
+ "1ff3c8709f403a8eff291aedf50c010df5c5ff64a8b205f1fce68564798897a390db16ee0d053856b75898009731da290fcc119dad987277aacef694872e880c4bb41471063fae05c89f25e4bd0cad6a"
+ };
+
+ /*
+ * From RFC 7539. Each vector consists in 5 values:
+ * key (hex)
+ * iv (hex)
+ * counter (decimal)
+ * plain (hex)
+ * cipher (hex)
+ */
+ static string[] KAT_CHACHA20 = {
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "000000000000000000000000",
+ "0",
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586",
+
+ "0000000000000000000000000000000000000000000000000000000000000001",
+ "000000000000000000000002",
+ "1",
+ "416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e7472696275746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e20224945544620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c2073746174656d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207768696368206172652061646472657373656420746f",
+ "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221",
+
+ "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
+ "000000000000000000000002",
+ "42",
+ "2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e",
+ "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1"
+ };
+
+ /*
+ * From: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+ */
+ static string[] KAT_GHASH = {
+
+ "66e94bd4ef8a2c3b884cfa59ca342b2e",
+ "",
+ "",
+ "00000000000000000000000000000000",
+
+ "66e94bd4ef8a2c3b884cfa59ca342b2e",
+ "",
+ "0388dace60b6a392f328c2b971b2fe78",
+ "f38cbb1ad69223dcc3457ae5b6b0f885",
+
+ "b83b533708bf535d0aa6e52980d53b78",
+ "",
+ "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
+ "7f1b32b81b820d02614f8895ac1d4eac",
+
+ "b83b533708bf535d0aa6e52980d53b78",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
+ "698e57f70e6ecc7fd9463b7260a9ae5f",
+
+ "b83b533708bf535d0aa6e52980d53b78",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
+ "df586bb4c249b92cb6922877e444d37b",
+
+ "b83b533708bf535d0aa6e52980d53b78",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5",
+ "1c5afe9760d3932f3c9a878aac3dc3de",
+
+ "aae06992acbf52a3e8f4a96ec9300bd7",
+ "",
+ "98e7247c07f0fe411c267e4384b0f600",
+ "e2c63f0ac44ad0e02efa05ab6743d4ce",
+
+ "466923ec9ae682214f2c082badb39249",
+ "",
+ "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256",
+ "51110d40f6c8fff0eb1ae33445a889f0",
+
+ "466923ec9ae682214f2c082badb39249",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710",
+ "ed2ce3062e4a8ec06db8b4c490e8a268",
+
+ "466923ec9ae682214f2c082badb39249",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "0f10f599ae14a154ed24b36e25324db8c566632ef2bbb34f8347280fc4507057fddc29df9a471f75c66541d4d4dad1c9e93a19a58e8b473fa0f062f7",
+ "1e6a133806607858ee80eaf237064089",
+
+ "466923ec9ae682214f2c082badb39249",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "d27e88681ce3243c4830165a8fdcf9ff1de9a1d8e6b447ef6ef7b79828666e4581e79012af34ddd9e2f037589b292db3e67c036745fa22e7e9b7373b",
+ "82567fb0b4cc371801eadec005968e94",
+
+ "dc95c078a2408989ad48a21492842087",
+ "",
+ "cea7403d4d606b6e074ec5d3baf39d18",
+ "83de425c5edc5d498f382c441041ca92",
+
+ "acbef20579b4b8ebce889bac8732dad7",
+ "",
+ "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
+ "4db870d37cb75fcb46097c36230d1612",
+
+ "acbef20579b4b8ebce889bac8732dad7",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662",
+ "8bd0c4d8aacd391e67cca447e8c38f65",
+
+ "acbef20579b4b8ebce889bac8732dad7",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "c3762df1ca787d32ae47c13bf19844cbaf1ae14d0b976afac52ff7d79bba9de0feb582d33934a4f0954cc2363bc73f7862ac430e64abe499f47c9b1f",
+ "75a34288b8c68f811c52b2e9a2f97f63",
+
+ "acbef20579b4b8ebce889bac8732dad7",
+ "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+ "5a8def2f0c9e53f1f75d7853659e2a20eeb2b22aafde6419a058ab4f6f746bf40fc0c3b780f244452da3ebf1c5d82cdea2418997200ef82e44ae7e3f",
+ "d5ffcf6fc5ac4d69722187421a7f170b"
+ };
+
+ static string[] ECDSA_K_P256 = {
+ "04"
++ "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"
++ "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
+
+ "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721"
+ };
+
+ static string[] ECDSA_SIGS_P256 = {
+ "61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32",
+ "6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB",
+
+ "53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F",
+ "B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C",
+
+ "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
+ "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8",
+
+ "0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719",
+ "4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954",
+
+ "8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00",
+ "2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE",
+
+ "0CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89",
+ "01B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1",
+
+ "C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692",
+ "C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D",
+
+ "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
+ "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083",
+
+ "83910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB6",
+ "8DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C",
+
+ "461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04",
+ "39AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55"
+ };
+
+ static string[] ECDSA_K_P384 = {
+ "04"
+ + "EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E"
+ + "06AAE5286B300C64DEF8F0EA9055866064A254515480BC13"
+ + "8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9"
+ + "F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
+
+ "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA"
+ + "9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5"
+ };
+
+ static string[] ECDSA_SIGS_P384 = {
+ "EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF7201454"
+ + "0C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2",
+ "A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC"
+ + "9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443",
+
+ "42356E76B55A6D9B4631C865445DBE54E056D3B3431766D0"
+ + "509244793C3F9366450F76EE3DE43F5A125333A6BE060122",
+ "9DA0C81787064021E78DF658F2FBB0B042BF304665DB721F"
+ + "077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D",
+
+ "21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E"
+ + "354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD",
+ "F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D4"
+ + "5DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0",
+
+ "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA7"
+ + "3D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46",
+ "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526"
+ + "203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8",
+
+ "ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047"
+ + "C0046861DA4A799CFE30F35CC900056D7C99CD7882433709",
+ "512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA913"
+ + "5329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5",
+
+ "4BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F"
+ + "7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7",
+ "D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8"
+ + "C4B7186201A2991695BA1C84541327E966FA7B50F7382282",
+
+ "E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE8"
+ + "88FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72",
+ "07041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3"
+ + "286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66",
+
+ "6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5"
+ + "B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B",
+ "2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C"
+ + "8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265",
+
+ "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36"
+ + "AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB",
+ "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B"
+ + "827C2F13173923E06A739F040649A667BF3B828246BAA5A5",
+
+ "A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314"
+ + "AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277",
+ "976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFA"
+ + "F576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736"
+ };
+
+ static string[] ECDSA_K_P521 = {
+ "04"
++ "01894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD37"
++ "1123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4"
++ "00493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28"
++ "A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
+
+ "00FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CA"
++ "A896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538"
+ };
+
+ static string[] ECDSA_SIGS_P521 = {
+ "00343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910F"
++ "E092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D",
+ "00E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5"
++ "694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16",
+
+ "01776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30"
++ "715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E",
+ "0050CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA"
++ "41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F",
+
+ "01511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D"
++ "16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7",
+ "004A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E"
++ "4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC",
+
+ "01EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B"
++ "576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451",
+ "01F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5F"
++ "DE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61",
+
+ "00C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F17"
++ "4E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA",
+ "00617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF28"
++ "2623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A",
+
+ "013BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B06"
++ "93F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367",
+ "01E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F"
++ "717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF",
+
+ "01C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086B"
++ "FE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB",
+ "0177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5B"
++ "EC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4",
+
+ "000E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042"
++ "EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8",
+ "00CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9F"
++ "DE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86",
+
+ "014BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C8"
++ "9DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C",
+ "0133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED"
++ "94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979",
+
+ "013E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10C"
++ "DB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D",
+ "01FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A"
++ "19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3"
+ };
+}
--- /dev/null
+using System;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+internal class TestEC {
+
+ internal static void Main(string[] args)
+ {
+ try {
+ TestECInt();
+ } catch (Exception e) {
+ Console.WriteLine(e.ToString());
+ Environment.Exit(1);
+ }
+ }
+
+ internal static void TestECInt()
+ {
+ TestCurve25519();
+ SpeedCurve(EC.Curve25519);
+
+ TestCurve(NIST.P256, KAT_P256);
+ TestCurve(NIST.P384, KAT_P384);
+ TestCurve(NIST.P521, KAT_P521);
+
+ SpeedCurve(NIST.P256);
+ SpeedCurve(NIST.P384);
+ SpeedCurve(NIST.P521);
+ }
+
+ static void TestCurve25519()
+ {
+ Console.Write("Test Curve25519: ");
+
+ TestCurve25519KAT(
+ "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4",
+ "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c",
+ "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552");
+ TestCurve25519KAT(
+ "4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d",
+ "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493",
+ "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957");
+
+ byte[] u = EC.Curve25519.GetGenerator(false);
+ byte[] k = new byte[u.Length];
+ Array.Copy(u, 0, k, 0, u.Length);
+ Byteswap(k);
+ byte[] nk = new byte[u.Length];
+
+ for (int i = 1; i <= 1000; i ++) {
+ EC.Curve25519.Mul(u, k, nk, false);
+ Array.Copy(k, 0, u, 0, u.Length);
+ Byteswap(u);
+ Array.Copy(nk, 0, k, 0, u.Length);
+ Byteswap(k);
+ if (i == 1) {
+ byte[] z = ToBin(C25519_MC_1);
+ Byteswap(z);
+ if (!Eq(k, z)) {
+ throw new Exception(
+ "Curve25519 MC 1");
+ }
+ } else if (i == 1000) {
+ byte[] z = ToBin(C25519_MC_1000);
+ Byteswap(z);
+ if (!Eq(k, z)) {
+ throw new Exception(
+ "Curve25519 MC 1000");
+ }
+ }
+ if (i % 1000 == 0) {
+ Console.Write(".");
+ }
+ }
+
+ /*
+ Byteswap(k);
+ if (!Eq(k, ToBin(C25519_MC_1000000))) {
+ throw new Exception("Curve25519 MC 1000");
+ }
+ */
+
+ Console.WriteLine(" done.");
+ }
+
+ static string C25519_MC_1 = "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079";
+ static string C25519_MC_1000 = "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51";
+ /*
+ static string C25519_MC_1000000 = "7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424";
+ */
+
+ static void Byteswap(byte[] t)
+ {
+ for (int i = 0; i < (t.Length >> 1); i ++) {
+ byte x = t[i];
+ t[i] = t[t.Length - 1 - i];
+ t[t.Length - 1 - i] = x;
+ }
+ }
+
+ static void TestCurve25519KAT(string sscalar, string s_in, string s_out)
+ {
+ byte[] tmp = ToBin(sscalar);
+ byte[] scalar = new byte[tmp.Length];
+ for (int i = 0; i < tmp.Length; i ++) {
+ scalar[i] = tmp[tmp.Length - 1 - i];
+ }
+ byte[] A = ToBin(s_in);
+ byte[] B = new byte[A.Length];
+ if (EC.Curve25519.Mul(A, scalar, B, false) != 0xFFFFFFFF) {
+ throw new Exception("Curve25519 multiplication failed");
+ }
+ byte[] C = ToBin(s_out);
+ if (!Eq(B, C)) {
+ throw new Exception("Curve25519 KAT failed");
+ }
+ Console.Write(".");
+ }
+
+ static void TestCurve(ECCurve curve, string[] kat)
+ {
+ Console.Write("Test {0}: ", curve.Name);
+
+ // ====================================================
+
+ /* obsolete -- DEBUG
+ Console.WriteLine();
+ ZInt p = ZInt.DecodeUnsignedBE(((ECCurvePrime)curve).mod);
+ ZInt a = ZInt.DecodeUnsignedBE(((ECCurvePrime)curve).a);
+ ZInt b = ZInt.DecodeUnsignedBE(((ECCurvePrime)curve).b);
+ Console.WriteLine("p = {0}", p.ToHexString());
+ Console.WriteLine("a = {0}", a.ToHexString());
+ Console.WriteLine("b = {0}", b.ToHexString());
+ MutableECPoint F1 = curve.MakeGenerator();
+ byte[] enc = F1.Encode(false);
+ int flen = enc.Length >> 1;
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ byte[] X = new byte[flen];
+ byte[] Y = new byte[flen];
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x1 = ZInt.DecodeUnsignedBE(X);
+ ZInt y1 = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X1 = {0}", x1.ToHexString());
+ Console.WriteLine("Y1 = {0}", y1.ToHexString());
+ MutableECPoint F2 = F1.Dup();
+ F2.DoubleCT();
+ MutableECPoint F3 = F2.Dup();
+ MutableECPoint F4 = F2.Dup();
+ enc = F2.Encode(false);
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x2 = ZInt.DecodeUnsignedBE(X);
+ ZInt y2 = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X2 = {0}", x2.ToHexString());
+ Console.WriteLine("Y2 = {0}", y2.ToHexString());
+ if ((x1 * x1 * x1 + a * x1 + b - y1 * y1) % p != 0) {
+ throw new Exception("Generator not on curve");
+ }
+ if ((x2 * x2 * x2 + a * x2 + b - y2 * y2) % p != 0) {
+ throw new Exception("Double not on curve");
+ }
+
+ if (F3.AddCT(F1) == 0) {
+ throw new Exception("Addition failed");
+ }
+ MutableECPoint F5 = F3.Dup();
+ enc = F3.Encode(false);
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x3 = ZInt.DecodeUnsignedBE(X);
+ ZInt y3 = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X3 = {0}", x3.ToHexString());
+ Console.WriteLine("Y3 = {0}", y3.ToHexString());
+ if ((x3 * x3 * x3 + a * x3 + b - y3 * y3) % p != 0) {
+ throw new Exception("Triple not on curve");
+ }
+ ZInt l3 = ((p + y2 - y1)
+ * ZInt.ModPow(p + x2 - x1, p - 2, p)) % p;
+ ZInt x3p = (l3 * l3 + p + p - x1 - x2) % p;
+ ZInt y3p = (l3 * (p + x1 - x3p) + p - y1) % p;
+ Console.WriteLine("X3p = {0}", x3p.ToHexString());
+ Console.WriteLine("Y3p = {0}", y3p.ToHexString());
+ Console.WriteLine("[X:{0}, Y:{1}]", x3 == x3p, y3 == y3p);
+
+ if (F5.AddCT(F4) == 0) {
+ throw new Exception("Addition failed");
+ }
+ enc = F5.Encode(false);
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x5 = ZInt.DecodeUnsignedBE(X);
+ ZInt y5 = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X5 = {0}", x5.ToHexString());
+ Console.WriteLine("Y5 = {0}", y5.ToHexString());
+ if ((x5 * x5 * x5 + a * x5 + b - y5 * y5) % p != 0) {
+ throw new Exception("Quintuple not on curve");
+ }
+ ZInt l5 = ((p + y3 - y2)
+ * ZInt.ModPow(p + x3 - x2, p - 2, p)) % p;
+ ZInt x5p = (l5 * l5 + p + p - x2 - x3) % p;
+ ZInt y5p = (l5 * (p + x2 - x5p) + p - y2) % p;
+ Console.WriteLine("X5p = {0}", x5p.ToHexString());
+ Console.WriteLine("Y5p = {0}", y5p.ToHexString());
+ Console.WriteLine("[X:{0}, Y:{1}]", x5 == x5p, y5 == y5p);
+
+ F1.Set(curve.MakeGenerator());
+ if (F1.MulSpecCT(new byte[] { 0x05 }) == 0) {
+ throw new Exception("Multiplication failed");
+ }
+ enc = F1.Encode(false);
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x5t = ZInt.DecodeUnsignedBE(X);
+ ZInt y5t = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X5t = {0}", x5t.ToHexString());
+ Console.WriteLine("Y5t = {0}", y5t.ToHexString());
+ if ((x5t * x5t * x5t + a * x5t + b - y5t * y5t) % p != 0) {
+ throw new Exception("Quintuple not on curve (2)");
+ }
+ Console.WriteLine("[X:{0}, Y:{1}]", x5t == x5p, y5t == y5p);
+
+ F1.Set(F5);
+ F2.SetZero();
+ if (F1.AddCT(F2) == 0) {
+ throw new Exception("Addition failed (+0)");
+ }
+ enc = F1.Encode(false);
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x5q = ZInt.DecodeUnsignedBE(X);
+ ZInt y5q = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X5q = {0}", x5q.ToHexString());
+ Console.WriteLine("Y5q = {0}", y5q.ToHexString());
+ Console.WriteLine("[X:{0}, Y:{1}]", x5q == x5p, y5q == y5p);
+
+ F2.SetZero();
+ if (F2.AddCT(F1) == 0) {
+ throw new Exception("Addition failed (0+)");
+ }
+ enc = F2.Encode(false);
+ for (int i = 0; i < enc.Length; i ++) {
+ if (i == 1 || i == 1 + (enc.Length >> 1)) {
+ Console.Write(" ");
+ }
+ Console.Write("{0:X2}", enc[i]);
+ }
+ Console.WriteLine();
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ ZInt x5r = ZInt.DecodeUnsignedBE(X);
+ ZInt y5r = ZInt.DecodeUnsignedBE(Y);
+ Console.WriteLine("X5r = {0}", x5r.ToHexString());
+ Console.WriteLine("Y5r = {0}", y5r.ToHexString());
+ Console.WriteLine("[X:{0}, Y:{1}]", x5r == x5p, y5r == y5p);
+
+ EC rG = EC.Make(p.ToBytesUnsignedBE(),
+ a.ToBytesUnsignedBE(), b.ToBytesUnsignedBE());
+ rG.Set(x1.ToBytesUnsignedBE(), y1.ToBytesUnsignedBE());
+ for (int i = 1; i <= 30; i ++) {
+ Console.Write(".");
+ ZInt n = ZInt.MakeRand(i);
+ byte[] nb = n.ToBytesUnsignedBE();
+ F1 = curve.MakeGenerator();
+ if (F1.MulSpecCT(nb) == 0) {
+ throw new Exception("Multiplication error");
+ }
+ enc = F1.Encode(false);
+ ZInt xp, yp;
+ if (enc.Length == 1) {
+ xp = 0;
+ yp = 0;
+ } else {
+ Array.Copy(enc, 1, X, 0, flen);
+ Array.Copy(enc, 1 + flen, Y, 0, flen);
+ xp = ZInt.DecodeUnsignedBE(X);
+ yp = ZInt.DecodeUnsignedBE(Y);
+ }
+ EC rH = rG.Dup();
+ rH.Mul(nb);
+ ZInt xh = ZInt.DecodeUnsignedBE(rH.X);
+ ZInt yh = ZInt.DecodeUnsignedBE(rH.Y);
+ if (xp != xh || yp != yh) {
+ Console.WriteLine();
+ Console.WriteLine("n = {0}", n);
+ Console.WriteLine("xp = {0}", xp.ToHexString());
+ Console.WriteLine("yp = {0}", yp.ToHexString());
+ Console.WriteLine("xh = {0}", xh.ToHexString());
+ Console.WriteLine("yh = {0}", yh.ToHexString());
+ throw new Exception("Bad mult result");
+ }
+ }
+ Console.WriteLine();
+ */
+
+ // ====================================================
+
+ curve.CheckValid();
+ MutableECPoint G = curve.MakeGenerator();
+ if (G.IsInfinity) {
+ throw new Exception("Generator is infinity");
+ }
+ MutableECPoint P = G.Dup();
+ MutableECPoint Q = G.Dup();
+ MutableECPoint R = G.Dup();
+ MutableECPoint S = G.Dup();
+ MutableECPoint T = G.Dup();
+
+ for (int i = 0; i < 10; i ++) {
+ Console.Write(".");
+ byte[] u, v, w;
+ u = MakeRandPoint(P);
+ do {
+ v = MakeRandPoint(Q);
+ } while (BigInt.Compare(u, v) == 0);
+ // byte[] s = BigInt.Add(u, v);
+ byte[] t;
+ do {
+ w = MakeRandPoint(R);
+ t = BigInt.Add(v, w);
+ } while (BigInt.Compare(u, w) == 0
+ || BigInt.Compare(v, w) == 0
+ || BigInt.Compare(u, t) == 0);
+ if (P.Eq(Q) || P.Eq(R) || Q.Eq(R)) {
+ throw new Exception("Equal points");
+ }
+ S.Set(P);
+ Add(S, Q);
+ Add(S, R);
+ T.Set(Q);
+ Add(T, R);
+ Add(T, P);
+ if (!S.Eq(T) || !T.Eq(S)) {
+ throw new Exception("Associativity error");
+ }
+ S.Normalize();
+ if (!S.Eq(T) || !T.Eq(S)) {
+ throw new Exception("Normalization error (1)");
+ }
+ T.Normalize();
+ if (!S.Eq(T) || !T.Eq(S)) {
+ throw new Exception("Normalization error (2)");
+ }
+
+ byte[] enc1 = P.Encode(false);
+ byte[] enc2 = P.Encode(true);
+ byte[] enc3 = new byte[enc1.Length];
+ Array.Copy(enc1, 1, enc3, 1, enc1.Length - 1);
+ enc3[0] = (byte)(enc2[0] | 0x04);
+ Q.Decode(enc1);
+ if (!P.Eq(Q) || !Q.Eq(P)) {
+ throw new Exception("Encode/decode error 1");
+ }
+ Q.Decode(enc2);
+ if (!P.Eq(Q) || !Q.Eq(P)) {
+ throw new Exception("Encode/decode error 2");
+ }
+ Q.Decode(enc3);
+ if (!P.Eq(Q) || !Q.Eq(P)) {
+ throw new Exception("Encode/decode error 3");
+ }
+ }
+
+ Console.Write(" ");
+ for (int i = 0; i < kat.Length; i += 2) {
+ P.Set(G);
+ byte[] n = ToBin(kat[i]);
+ if (P.MulSpecCT(n) == 0) {
+ throw new Exception("Multiplication error");
+ }
+ byte[] er = ToBin(kat[i + 1]);
+ if (!Eq(er, P.Encode(false))) {
+ throw new Exception("KAT failed");
+ }
+ byte[] eg = curve.GetGenerator(false);
+ byte[] ed = new byte[eg.Length];
+ curve.Mul(eg, n, ed, false);
+ if (!Eq(ed, er)) {
+ throw new Exception("KAT failed (API 2)");
+ }
+ Console.Write(".");
+ }
+
+ Console.WriteLine();
+ }
+
+ static void SpeedCurve(ECCurve curve)
+ {
+ byte[][] nn = new byte[100][];
+ for (int i = 0; i < nn.Length; i ++) {
+ nn[i] = BigInt.RandIntNZ(curve.SubgroupOrder);
+ }
+ MutableECPoint G = curve.MakeGenerator();
+ MutableECPoint P = G.Dup();
+ int num = 1;
+ for (;;) {
+ long orig = DateTime.Now.Ticks;
+ for (int i = 0, j = 0; i < num; i ++) {
+ P.MulSpecCT(nn[j]);
+ if (++ j == nn.Length) {
+ j = 0;
+ }
+ }
+ long end = DateTime.Now.Ticks;
+ double tt = (double)(end - orig) / 10000000.0;
+ if (tt < 2.0) {
+ num <<= 1;
+ continue;
+ }
+ double f = (double)num / tt;
+ Console.WriteLine("{0,10} {1,9:f3} mul/s",
+ curve.Name, f);
+ return;
+ }
+ }
+
+ /*
+ * Create a random non-infinity point by multiplying the
+ * curve subgroup generator with a random non-zero integer
+ * modulo the subgroup order. The multiplier is returned.
+ */
+ static byte[] MakeRandPoint(MutableECPoint P)
+ {
+ ECCurve curve = P.Curve;
+ P.Set(curve.MakeGenerator());
+ byte[] n = BigInt.RandIntNZ(curve.SubgroupOrder);
+ if (P.MulSpecCT(n) == 0) {
+ throw new Exception("Multiplication failed");
+ }
+ return n;
+ }
+
+ static void Add(MutableECPoint P, MutableECPoint Q)
+ {
+ if (P.Eq(Q)) {
+ P.DoubleCT();
+ } else {
+ if (P.AddCT(Q) == 0) {
+ throw new Exception("Addition failed");
+ }
+ }
+ }
+
+ static bool Eq(byte[] a1, byte[] a2)
+ {
+ if (a1 == a2) {
+ return true;
+ }
+ if (a1 == null || a2 == null) {
+ return false;
+ }
+ int n = a1.Length;
+ if (n != a2.Length) {
+ return false;
+ }
+ for (int i = 0; i < n; i ++) {
+ if (a1[i] != a2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static byte[] ToBin(string str)
+ {
+ MemoryStream ms = new MemoryStream();
+ bool z = true;
+ int acc = 0;
+ foreach (char c in str) {
+ int d;
+ if (c >= '0' && c <= '9') {
+ d = c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ d = c - ('A' - 10);
+ } else if (c >= 'a' && c <= 'f') {
+ d = c - ('a' - 10);
+ } else if (c == ' ' || c == '\t' || c == ':') {
+ continue;
+ } else {
+ throw new ArgumentException(String.Format(
+ "not hex: U+{0:X4}", (int)c));
+ }
+ if (z) {
+ acc = d;
+ } else {
+ ms.WriteByte((byte)((acc << 4) + d));
+ }
+ z = !z;
+ }
+ if (!z) {
+ throw new ArgumentException("final half byte");
+ }
+ return ms.ToArray();
+ }
+
+ static string[] KAT_P256 = {
+ "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
+ "0460FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB67903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299"
+ };
+
+ static string[] KAT_P384 = {
+ "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
+ "04EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC138015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720"
+ };
+
+ static string[] KAT_P521 = {
+ "00FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
+ "0401894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A400493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5"
+ };
+}
--- /dev/null
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+
+using Crypto;
+
+internal class TestMath {
+
+ internal static void Main(string[] args)
+ {
+ try {
+ TestModInt();
+ } catch (Exception e) {
+ Console.WriteLine(e.ToString());
+ Environment.Exit(1);
+ }
+ }
+
+ static ZInt RandPrime(int k)
+ {
+ if (k < 2) {
+ throw new ArgumentException();
+ }
+ ZInt min = ZInt.One << (k - 1);
+ ZInt max = ZInt.One << k;
+ for (;;) {
+ ZInt p = ZInt.MakeRand(min, max) | 1;
+ if (p.IsPrime) {
+ return p;
+ }
+ }
+ }
+
+ internal static void TestModInt()
+ {
+ Console.Write("Test ModInt: ");
+ for (int k = 2; k <= 128; k ++) {
+ for (int i = 0; i < 10; i ++) {
+ int kwlen = (k + 30) / 31;
+ int kwb = 31 * kwlen;
+
+ ZInt p;
+ if (k >= 9) {
+ p = ZInt.DecodeUnsignedBE(
+ BigInt.RandPrime(k));
+ if (p.BitLength != k) {
+ throw new Exception(
+ "wrong prime size");
+ }
+ if (!p.IsPrime) {
+ throw new Exception(
+ "not prime");
+ }
+ } else {
+ p = RandPrime(k);
+ }
+
+ ZInt a = ZInt.MakeRand(p);
+ ZInt b = ZInt.MakeRand(p);
+ ZInt v = ZInt.MakeRand(k + 60);
+ if (b == ZInt.Zero) {
+ b = ZInt.One;
+ }
+ byte[] ea = a.ToBytesBE();
+ byte[] eb = b.ToBytesBE();
+ byte[] ev = v.ToBytesBE();
+ ModInt mz = new ModInt(p.ToBytesBE());
+ ModInt ma = mz.Dup();
+ ModInt mb = mz.Dup();
+
+ ma.Decode(ea);
+ CheckEq(ma, a);
+
+ ma.Decode(ea);
+ mb.Decode(eb);
+ ma.Add(mb);
+ CheckEq(ma, (a + b).Mod(p));
+
+ ma.Decode(ea);
+ mb.Decode(eb);
+ ma.Sub(mb);
+ CheckEq(ma, (a - b).Mod(p));
+
+ ma.Decode(ea);
+ ma.Negate();
+ CheckEq(ma, (-a).Mod(p));
+
+ ma.Decode(ea);
+ mb.Decode(eb);
+ ma.MontyMul(mb);
+ CheckEq((ZInt.DecodeUnsignedBE(ma.Encode())
+ << kwb).Mod(p), (a * b).Mod(p));
+
+ ma.Decode(ea);
+ ma.ToMonty();
+ CheckEq(ma, (a << kwb).Mod(p));
+ ma.FromMonty();
+ CheckEq(ma, a);
+
+ ma.Decode(ea);
+ mb.Decode(eb);
+ ma.ToMonty();
+ mb.ToMonty();
+ ma.MontyMul(mb);
+ ma.FromMonty();
+ CheckEq(ma, (a * b).Mod(p));
+
+ mb.Decode(eb);
+ mb.Invert();
+ ZInt r = ZInt.DecodeUnsignedBE(mb.Encode());
+ CheckEq(ZInt.One, (r * b).Mod(p));
+
+ ma.Decode(ea);
+ ma.Pow(ev);
+ CheckEq(ma, ZInt.ModPow(a, v, p));
+
+ ma.DecodeReduce(ev);
+ CheckEq(ma, v.Mod(p));
+
+ mb.Decode(eb);
+ ma.Set(mb);
+ CheckEq(ma, b);
+
+ ModInt mv = new ModInt(
+ ((p << 61) + 1).ToBytesBE());
+ mv.Decode(ev);
+ ma.Set(mv);
+ CheckEq(ma, v.Mod(p));
+
+ if (k >= 9) {
+ ma.Decode(ea);
+ mb.Set(ma);
+ mb.ToMonty();
+ mb.MontyMul(ma);
+ if ((int)mb.SqrtBlum() != -1) {
+ throw new CryptoException(
+ "square root failed");
+ }
+ if (!mb.Eq(ma)) {
+ mb.Negate();
+ }
+ CheckEq(mb, a);
+
+ mb.Decode(eb);
+ mb.ToMonty();
+ mb.MontySquare();
+ mb.FromMonty();
+ mb.Negate();
+ if (mb.SqrtBlum() != 0) {
+ throw new CryptoException(
+ "square root should"
+ + " have failed");
+ }
+ }
+ }
+ Console.Write(".");
+ }
+ Console.WriteLine(" done.");
+ }
+
+ static void CheckEq(ModInt m, ZInt z)
+ {
+ CheckEq(ZInt.DecodeUnsignedBE(m.Encode()), z);
+ }
+
+ static void CheckEq(ZInt x, ZInt z)
+ {
+ if (x != z) {
+ throw new Exception(String.Format(
+ "mismatch: x={0} z={1}", x, z));
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+/*
+ * A simple JSON parser.
+ *
+ * A JSON value is returned as:
+ *
+ * - null, if the value is a JSON null;
+ *
+ * - a string, if the value is a JSON string, a JSON number or a
+ * JSON boolean;
+ *
+ * - an IDictionary<string, object>, if the value is a JSON object;
+ *
+ * - an array (object[]), if the value is a JSON array.
+ *
+ * This parser is lenient with numbers, in that it will gleefully
+ * accumulate digits, dots, minus sign, plus sign, lowercase 'e'
+ * and uppercase 'E' characters in any order.
+ */
+
+public static class JSON {
+
+ /*
+ * Parse a source stream as a JSON object.
+ */
+ public static object Parse(Stream src)
+ {
+ return Parse(new StreamReader(src));
+ }
+
+ /*
+ * Parse a source stream as a JSON object.
+ */
+ public static object Parse(TextReader tr)
+ {
+ int cp = NextNonWS(tr, ' ');
+ object val;
+ cp = ReadValue(tr, cp, out val);
+ while (cp >= 0) {
+ if (!IsWS(cp)) {
+ throw new Exception(
+ "Trailing garbage after JSON value");
+ }
+ cp = tr.Read();
+ }
+ return val;
+ }
+
+ /*
+ * Encode a JSON object onto a stream.
+ */
+ public static void Encode(object obj, Stream dst)
+ {
+ TextWriter tw = new StreamWriter(dst);
+ Encode(obj, tw);
+ tw.Flush();
+ }
+
+ /*
+ * Encode a JSON object onto a stream.
+ */
+ public static void Encode(object obj, TextWriter tw)
+ {
+ EncodeValue(0, obj, tw);
+ tw.WriteLine();
+ }
+
+ /*
+ * Get a value by path. If the value is present, then 'val'
+ * is set to that value (which may be null) and true is returned;
+ * otherwise, 'val' is set to null and false is written.
+ *
+ * An exception is still thrown if one of the upper path elements
+ * does not have the expected type.
+ */
+ public static bool TryGet(object obj, string path, out object val)
+ {
+ int n = path.Length;
+ int p = 0;
+ while (p < n) {
+ int q = path.IndexOf('/', p);
+ if (q < 0) {
+ q = n;
+ }
+ IDictionary<string, object> d =
+ obj as IDictionary<string, object>;
+ if (d == null) {
+ throw new Exception(string.Format(
+ "Path '{0}': not an object",
+ path.Substring(0, p)));
+ }
+ string name = path.Substring(p, q - p);
+ if (!d.ContainsKey(name)) {
+ val = null;
+ return false;
+ }
+ obj = d[name];
+ p = q + 1;
+ }
+ val = obj;
+ return true;
+ }
+
+ /*
+ * Get a value by path.
+ */
+ public static object Get(object obj, string path)
+ {
+ object val;
+ if (!TryGet(obj, path, out val)) {
+ throw new Exception("No such value: " + path);
+ }
+ return val;
+ }
+
+ /*
+ * Try to get a value by path; value (if present) should be a
+ * string.
+ */
+ public static bool TryGetString(object obj, string path, out string val)
+ {
+ object gv;
+ if (!TryGet(obj, path, out gv)) {
+ val = null;
+ return false;
+ }
+ if (!(gv is string)) {
+ throw new Exception("Value at " + path
+ + " is not a string");
+ }
+ val = gv as string;
+ return true;
+ }
+
+ /*
+ * Get a value by path; value should be a string.
+ */
+ public static string GetString(object obj, string path)
+ {
+ string str;
+ if (!TryGetString(obj, path, out str)) {
+ throw new Exception("No such value: " + path);
+ }
+ return str;
+ }
+
+ /*
+ * Try to get a value by path; value should be an array.
+ */
+ public static bool TryGetArray(object obj, string path,
+ out object[] val)
+ {
+ object gv;
+ if (!TryGet(obj, path, out gv)) {
+ val = null;
+ return false;
+ }
+ val = gv as object[];
+ if (val == null) {
+ throw new Exception("Value at " + path
+ + " is not an array");
+ }
+ return true;
+ }
+
+ /*
+ * Get a value by path; value should be an array.
+ */
+ public static object[] GetArray(object obj, string path)
+ {
+ object[] a;
+ if (!TryGetArray(obj, path, out a)) {
+ throw new Exception("No such value: " + path);
+ }
+ return a;
+ }
+
+ /*
+ * Try to get a value by path; if present, value should be an
+ * array, whose elements are all strings. A new, properly typed
+ * array is returned, containing the strings.
+ */
+ public static bool TryGetStringArray(object obj, string path,
+ out string[] a)
+ {
+ object[] g;
+ if (!TryGetArray(obj, path, out g)) {
+ a = null;
+ return false;
+ }
+ string[] r = new string[g.Length];
+ for (int i = 0; i < g.Length; i ++) {
+ string s = g[i] as string;
+ if (s == null) {
+ throw new Exception(string.Format("Element {0}"
+ + " in array {1} is not a string",
+ i, path));
+ }
+ r[i] = s;
+ }
+ a = r;
+ return true;
+ }
+
+ /*
+ * Get a value by path; value should be an array, whose
+ * elements are all strings. A new, properly typed array is
+ * returned, containing the strings.
+ */
+ public static string[] GetStringArray(object obj, string path)
+ {
+ string[] a;
+ if (!TryGetStringArray(obj, path, out a)) {
+ throw new Exception("No such value: " + path);
+ }
+ return a;
+ }
+
+ /*
+ * Try to get a value by path; value should a boolean.
+ */
+ public static bool TryGetBool(object obj, string path, out bool val)
+ {
+ object gv;
+ if (!TryGet(obj, path, out gv)) {
+ val = false;
+ return false;
+ }
+ if (gv is bool) {
+ val = (bool)gv;
+ return true;
+ } else if (gv is string) {
+ switch (gv as string) {
+ case "true": val = true; return true;
+ case "false": val = false; return true;
+ }
+ }
+ throw new Exception("Value at " + path + " is not a boolean");
+ }
+
+ /*
+ * Get a value by path; value should a boolean.
+ */
+ public static bool GetBool(object obj, string path)
+ {
+ bool v;
+ if (!TryGetBool(obj, path, out v)) {
+ throw new Exception("No such value: " + path);
+ }
+ return v;
+ }
+
+ /*
+ * Try to get a value by path; value should an integer.
+ */
+ public static bool TryGetInt32(object obj, string path, out int val)
+ {
+ object gv;
+ if (!TryGet(obj, path, out gv)) {
+ val = 0;
+ return false;
+ }
+ if (gv is int) {
+ val = (int)gv;
+ return true;
+ } else if (gv is uint) {
+ uint x = (uint)gv;
+ if (x <= (uint)Int32.MaxValue) {
+ val = (int)x;
+ return true;
+ }
+ } else if (gv is long) {
+ long x = (long)gv;
+ if (x >= (long)Int32.MinValue
+ && x <= (long)Int32.MaxValue)
+ {
+ val = (int)x;
+ return true;
+ }
+ } else if (gv is ulong) {
+ ulong x = (ulong)gv;
+ if (x <= (ulong)Int32.MaxValue) {
+ val = (int)x;
+ return true;
+ }
+ } else if (gv is string) {
+ int x;
+ if (Int32.TryParse((string)gv, out x)) {
+ val = x;
+ return true;
+ }
+ }
+ throw new Exception("Value at " + path + " is not a boolean");
+ }
+
+ /*
+ * Get a value by path; value should an integer.
+ */
+ public static int GetInt32(object obj, string path)
+ {
+ int v;
+ if (!TryGetInt32(obj, path, out v)) {
+ throw new Exception("No such value: " + path);
+ }
+ return v;
+ }
+
+ /*
+ * Try to get a value by path; value should be an object map.
+ */
+ public static bool TryGetObjectMap(object obj, string path,
+ out IDictionary<string, object> val)
+ {
+ object gv;
+ if (!TryGet(obj, path, out gv)) {
+ val = null;
+ return false;
+ }
+ val = gv as IDictionary<string, object>;
+ if (val == null) {
+ throw new Exception("Value at " + path
+ + " is not an object map");
+ }
+ return true;
+ }
+
+ /*
+ * Get a value by path; value should be an object map.
+ */
+ public static IDictionary<string, object> GetObjectMap(
+ object obj, string path)
+ {
+ IDictionary<string, object> v;
+ if (!TryGetObjectMap(obj, path, out v)) {
+ throw new Exception("No such value: " + path);
+ }
+ return v;
+ }
+
+ static void EncodeValue(int indent, object obj, TextWriter tw)
+ {
+ if (obj == null) {
+ tw.Write("null");
+ return;
+ }
+ if (obj is bool) {
+ tw.Write((bool)obj ? "true" : "false");
+ return;
+ }
+ if (obj is string) {
+ EncodeString((string)obj, tw);
+ return;
+ }
+ if (obj is int || obj is uint || obj is long || obj is ulong) {
+ tw.Write(obj.ToString());
+ return;
+ }
+ if (obj is Array) {
+ tw.Write("[");
+ Array a = (Array)obj;
+ for (int i = 0; i < a.Length; i ++) {
+ if (i != 0) {
+ tw.Write(",");
+ }
+ tw.WriteLine();
+ Indent(indent + 1, tw);
+ EncodeValue(indent + 1, a.GetValue(i), tw);
+ }
+ tw.WriteLine();
+ Indent(indent, tw);
+ tw.Write("]");
+ return;
+ }
+ if (obj is IDictionary<string, object>) {
+ tw.Write("{");
+ IDictionary<string, object> d =
+ (IDictionary<string, object>)obj;
+ bool first = true;
+ foreach (string name in d.Keys) {
+ if (first) {
+ first = false;
+ } else {
+ tw.Write(",");
+ }
+ tw.WriteLine();
+ Indent(indent + 1, tw);
+ EncodeString(name, tw);
+ tw.Write(" : ");
+ EncodeValue(indent + 1, d[name], tw);
+ }
+ tw.WriteLine();
+ Indent(indent, tw);
+ tw.Write("}");
+ return;
+ }
+ throw new Exception("Unknown value type: "
+ + obj.GetType().FullName);
+ }
+
+ static void Indent(int indent, TextWriter tw)
+ {
+ while (indent -- > 0) {
+ tw.Write(" ");
+ }
+ }
+
+ static void EncodeString(string str, TextWriter tw)
+ {
+ tw.Write('\"');
+ foreach (char c in str) {
+ if (c >= 32 && c <= 126) {
+ if (c == '\\' || c == '"') {
+ tw.Write('\\');
+ }
+ tw.Write(c);
+ } else {
+ switch (c) {
+ case '\b':
+ tw.Write("\\b");
+ break;
+ case '\f':
+ tw.Write("\\f");
+ break;
+ case '\n':
+ tw.Write("\\n");
+ break;
+ case '\r':
+ tw.Write("\\r");
+ break;
+ case '\t':
+ tw.Write("\\t");
+ break;
+ default:
+ tw.Write("\\u{0:X4}", (int)c);
+ break;
+ }
+ }
+ }
+ tw.Write('\"');
+ }
+
+ /*
+ * Read a value, that starts with the provided character. The
+ * value is written in 'val'. Returned value is the next
+ * character in the stream, or a synthetic space if the next
+ * character was not read.
+ */
+ static int ReadValue(TextReader tr, int cp, out object val)
+ {
+ switch (cp) {
+ case '"':
+ val = ReadString(tr);
+ return ' ';
+ case '{':
+ val = ReadObject(tr);
+ return ' ';
+ case '[':
+ val = ReadArray(tr);
+ return ' ';
+ case 't':
+ CheckKeyword(tr, "true");
+ val = "true";
+ return ' ';
+ case 'f':
+ CheckKeyword(tr, "false");
+ val = "false";
+ return ' ';
+ case 'n':
+ CheckKeyword(tr, "null");
+ val = null;
+ return ' ';
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ StringBuilder sb = new StringBuilder();
+ sb.Append((char)cp);
+ cp = ReadNumber(tr, sb);
+ val = sb.ToString();
+ return cp;
+ default:
+ throw Unexpected(cp);
+ }
+ }
+
+ static string ReadString(TextReader tr)
+ {
+ StringBuilder sb = new StringBuilder();
+ bool lcwb = false;
+ for (;;) {
+ int cp = Next(tr);
+ if (lcwb) {
+ lcwb = false;
+ switch (cp) {
+ case '"': case '\\': case '/':
+ sb.Append((char)cp);
+ break;
+ case 'b':
+ sb.Append('\b');
+ break;
+ case 'f':
+ sb.Append('\f');
+ break;
+ case 'n':
+ sb.Append('\n');
+ break;
+ case 'r':
+ sb.Append('\r');
+ break;
+ case 't':
+ sb.Append('\t');
+ break;
+ case 'u':
+ sb.Append(ReadUnicodeEscape(tr));
+ break;
+ default:
+ throw Unexpected(cp);
+ }
+ } else {
+ if (cp == '\\') {
+ lcwb = true;
+ } else if (cp == '"') {
+ return sb.ToString();
+ } else if (cp <= 0x1F) {
+ throw Unexpected(cp);
+ } else {
+ sb.Append((char)cp);
+ }
+ }
+ }
+ }
+
+ static char ReadUnicodeEscape(TextReader tr)
+ {
+ int acc = 0;
+ for (int i = 0; i < 4; i ++) {
+ int cp = Next(tr);
+ if (cp >= '0' && cp <= '9') {
+ cp -= '0';
+ } else if (cp >= 'A' && cp <= 'F') {
+ cp -= 'A' - 10;
+ } else if (cp >= 'a' && cp <= 'f') {
+ cp -= 'a' - 10;
+ } else {
+ throw Unexpected(cp);
+ }
+ acc = (acc << 4) + cp;
+ }
+ return (char)acc;
+ }
+
+ static IDictionary<string, object> ReadObject(TextReader tr)
+ {
+ IDictionary<string, object> r =
+ new SortedDictionary<string, object>(
+ StringComparer.Ordinal);
+ int cp = NextNonWS(tr, ' ');
+ if (cp == '}') {
+ return r;
+ }
+ for (;;) {
+ if (cp != '"') {
+ throw Unexpected(cp);
+ }
+ string name = ReadString(tr);
+ cp = NextNonWS(tr, ' ');
+ if (cp != ':') {
+ throw Unexpected(cp);
+ }
+ if (r.ContainsKey(name)) {
+ throw new Exception(string.Format(
+ "duplicate key '{0}' in object",
+ name));
+ }
+ object val;
+ cp = NextNonWS(tr, ' ');
+ cp = ReadValue(tr, cp, out val);
+ r[name] = val;
+ cp = NextNonWS(tr, cp);
+ if (cp == '}') {
+ return r;
+ }
+ if (cp != ',') {
+ throw Unexpected(cp);
+ }
+ cp = NextNonWS(tr, ' ');
+ }
+ }
+
+ static object[] ReadArray(TextReader tr)
+ {
+ List<object> r = new List<object>();
+ int cp = NextNonWS(tr, ' ');
+ if (cp == ']') {
+ return r.ToArray();
+ }
+ for (;;) {
+ object val;
+ cp = ReadValue(tr, cp, out val);
+ r.Add(val);
+ cp = NextNonWS(tr, cp);
+ if (cp == ']') {
+ return r.ToArray();
+ }
+ if (cp != ',') {
+ throw Unexpected(cp);
+ }
+ cp = NextNonWS(tr, ' ');
+ }
+ }
+
+ static int ReadNumber(TextReader tr, StringBuilder sb)
+ {
+ int cp;
+ for (;;) {
+ cp = tr.Read();
+ switch (cp) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case '.': case '-': case '+': case 'e': case 'E':
+ sb.Append((char)cp);
+ break;
+ default:
+ return cp;
+ }
+ }
+ }
+
+ static void CheckKeyword(TextReader tr, string str)
+ {
+ int n = str.Length;
+ for (int i = 1; i < n; i ++) {
+ int cp = Next(tr);
+ if (cp != (int)str[i]) {
+ throw Unexpected(cp);
+ }
+ }
+ }
+
+ static bool IsWS(int cp)
+ {
+ return cp == 9 || cp == 10 || cp == 13 || cp == 32;
+ }
+
+ static int Next(TextReader tr)
+ {
+ int cp = tr.Read();
+ if (cp < 0) {
+ throw new EndOfStreamException();
+ }
+ return cp;
+ }
+
+ static int NextNonWS(TextReader tr, int cp)
+ {
+ while (IsWS(cp)) {
+ cp = Next(tr);
+ }
+ return cp;
+ }
+
+ static Exception Unexpected(int cp)
+ {
+ return new Exception(string.Format(
+ "Unexpected character U+{0:X4}", cp));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+/*
+ * This class merges two underlying streams (one for reading, the other
+ * for writing) into a single Stream object. It can also optionally dump
+ * all read and written bytes, in hexadecimal, on an provided text
+ * stream (for debugging purposes).
+ */
+
+internal class MergeStream : Stream {
+
+ Stream subIn, subOut;
+
+ internal TextWriter Debug {
+ get; set;
+ }
+
+ internal MergeStream(Stream subIn, Stream subOut)
+ {
+ this.subIn = subIn;
+ this.subOut = subOut;
+ }
+
+ public override int ReadByte()
+ {
+ int x = subIn.ReadByte();
+ if (Debug != null) {
+ if (x >= 0) {
+ Debug.WriteLine("recv:");
+ Debug.WriteLine(" {0:x2}", x);
+ } else {
+ Debug.WriteLine("recv: EOF");
+ }
+ }
+ return x;
+ }
+
+ public override int Read(byte[] buf, int off, int len)
+ {
+ int rlen = subIn.Read(buf, off, len);
+ if (Debug != null) {
+ if (rlen <= 0) {
+ Debug.WriteLine("recv: EOF");
+ } else {
+ Debug.Write("recv:");
+ for (int i = 0; i < rlen; i ++) {
+ if ((i & 15) == 0) {
+ Debug.WriteLine();
+ Debug.Write(" ");
+ } else if ((i & 7) == 0) {
+ Debug.Write(" ");
+ } else {
+ Debug.Write(" ");
+ }
+ Debug.Write("{0:x2}", buf[i]);
+ }
+ Debug.WriteLine();
+ }
+ }
+ return rlen;
+ }
+
+ public override void WriteByte(byte x)
+ {
+ if (Debug != null) {
+ Debug.WriteLine("send:");
+ Debug.WriteLine(" {0:x2}", x);
+ }
+ subOut.WriteByte(x);
+ }
+
+ public override void Write(byte[] buf, int off, int len)
+ {
+ if (Debug != null) {
+ Debug.Write("send:");
+ for (int i = 0; i < len; i ++) {
+ if ((i & 15) == 0) {
+ Debug.WriteLine();
+ Debug.Write(" ");
+ } else if ((i & 7) == 0) {
+ Debug.Write(" ");
+ } else {
+ Debug.Write(" ");
+ }
+ Debug.Write("{0:x2}", buf[i]);
+ }
+ Debug.WriteLine();
+ }
+ subOut.Write(buf, off, len);
+ }
+
+ public override void Flush()
+ {
+ subOut.Flush();
+ }
+
+ public override void Close()
+ {
+ Exception ex1 = null, ex2 = null;
+ try {
+ subIn.Close();
+ } catch (Exception ex) {
+ ex1 = ex;
+ }
+ try {
+ subOut.Close();
+ } catch (Exception ex) {
+ ex2 = ex;
+ }
+ if (ex2 != null) {
+ throw ex2;
+ } else if (ex1 != null) {
+ throw ex1;
+ }
+ }
+
+ public override long Seek(long off, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long len)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool CanRead {
+ get {
+ return subIn.CanRead;
+ }
+ }
+
+ public override bool CanWrite {
+ get {
+ return subOut.CanWrite;
+ }
+ }
+
+ public override bool CanSeek {
+ get {
+ return false;
+ }
+ }
+
+ public override long Length {
+ get {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override long Position {
+ get {
+ throw new NotSupportedException();
+ }
+ set {
+ throw new NotSupportedException();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+using Asn1;
+using Crypto;
+using SSLTLS;
+using XKeys;
+
+/*
+ * This is the main Twrch class implementation: it provides the entry
+ * point for the command-line application.
+ */
+
+public class Twrch {
+
+ public static void Main(string[] args)
+ {
+ try {
+ new Twrch().Run(args);
+ } catch (Exception e) {
+ Console.WriteLine(e.ToString());
+ Environment.Exit(1);
+ }
+ }
+
+ bool trace;
+ object conf;
+ string commandFile;
+ string commandArgs;
+ bool commandVerbose;
+ string chainRSAFile;
+ byte[][] chainRSA;
+ string skeyRSAFile;
+ IPrivateKey skeyRSA;
+ string chainECFile;
+ byte[][] chainEC;
+ string skeyECFile;
+ IPrivateKey skeyEC;
+ int[] versions;
+ int versionMin;
+ int versionMax;
+ int[] cipherSuites;
+ int[] hashAndSigns;
+ int[] curves;
+ bool noCloseNotify;
+ object[] tests;
+ IDictionary<string, object> testsByName;
+
+ int totalTests;
+ int totalSuccess;
+ int totalFailures;
+
+ void Run(string[] args)
+ {
+ List<string> r = new List<string>();
+ string confName = null;
+ int doEnum = 0;
+ foreach (string a in args) {
+ string b = a.ToLowerInvariant();
+ switch (b) {
+ case "-trace":
+ trace = true;
+ break;
+ case "-enum":
+ doEnum = 1;
+ break;
+ case "-noenum":
+ doEnum = -1;
+ break;
+ case "-cv":
+ commandVerbose = true;
+ break;
+ default:
+ if (confName == null) {
+ confName = a;
+ } else {
+ r.Add(a);
+ }
+ break;
+ }
+ }
+ if (confName == null) {
+ Usage();
+ }
+ string[] testNames = r.ToArray();
+ conf = ReadConfig(confName);
+ if (doEnum == 0) {
+ doEnum = (testNames.Length == 0) ? 1 : -1;
+ }
+ commandFile = JSON.GetString(conf, "commandFile");
+ commandArgs = JSON.GetString(conf, "commandArgs");
+ chainRSAFile = JSON.GetString(conf, "chainRSA");
+ chainECFile = JSON.GetString(conf, "chainEC");
+ skeyRSAFile = JSON.GetString(conf, "skeyRSA");
+ skeyECFile = JSON.GetString(conf, "skeyEC");
+ chainRSA = DecodeChain(chainRSAFile);
+ skeyRSA = DecodePrivateKey(skeyRSAFile);
+ chainEC = DecodeChain(chainECFile);
+ skeyEC = DecodePrivateKey(skeyECFile);
+ versions = GetVersions();
+ if (versions.Length == 0) {
+ throw new Exception("Bad config: no versions");
+ }
+ versionMin = Int32.MaxValue;
+ versionMax = -1;
+ foreach (int v in versions) {
+ versionMin = Math.Min(v, versionMin);
+ versionMax = Math.Max(v, versionMax);
+ }
+ cipherSuites = GetCipherSuites();
+ if (cipherSuites.Length == 0) {
+ throw new Exception("Bad config: no cipher suites");
+ }
+ hashAndSigns = GetHashAndSigns();
+ if (hashAndSigns.Length == 0) {
+ throw new Exception("Bad config: no hash-and-signs");
+ }
+ curves = GetCurves();
+ noCloseNotify = JSON.GetBool(conf, "noCloseNotify");
+ tests = JSON.GetArray(conf, "tests");
+ testsByName = new SortedDictionary<string, object>(
+ StringComparer.Ordinal);
+ foreach (object obj in tests) {
+ string name = JSON.GetString(obj, "name");
+ testsByName[name] = obj;
+ }
+
+ totalTests = 0;
+ totalSuccess = 0;
+ totalFailures = 0;
+ if (doEnum > 0) {
+ totalTests += ComputeTotalEnum();
+ }
+ if (testNames.Length == 0) {
+ foreach (object obj in tests) {
+ totalTests += GetNumTests(obj);
+ }
+ } else {
+ foreach (string name in testNames) {
+ bool client;
+ int version, suite, curve, hs;
+ if (StringToTEnum(name, out client, out version,
+ out suite, out curve, out hs))
+ {
+ totalTests ++;
+ continue;
+ }
+ if (name.EndsWith("_client")
+ || name.EndsWith("_server"))
+ {
+ totalTests ++;
+ } else {
+ totalTests += GetNumTests(
+ testsByName[name]);
+ }
+ }
+ }
+
+ if (doEnum > 0) {
+ RunEnum();
+ }
+ if (testNames.Length == 0) {
+ foreach (object obj in tests) {
+ RunTest(obj);
+ }
+ } else {
+ foreach (string name in testNames) {
+ bool client;
+ int version, suite, curve, hs;
+ if (StringToTEnum(name, out client, out version,
+ out suite, out curve, out hs))
+ {
+ RunEnum(client, version,
+ suite, curve, hs);
+ continue;
+ }
+ if (name.EndsWith("_client")) {
+ client = true;
+ } else if (name.EndsWith("_server")) {
+ client = false;
+ } else {
+ RunTest(testsByName[name]);
+ continue;
+ }
+ string s = name.Substring(0, name.Length - 7);
+ RunTest(client, testsByName[s]);
+ }
+ }
+
+ Console.WriteLine();
+ Console.WriteLine("\rtotal = {0}, failed = {1}",
+ totalTests, totalFailures);
+ }
+
+ static void Usage()
+ {
+ Console.WriteLine(
+"usage: Twrch.exe [ options ] config [ test... ]");
+ Console.WriteLine(
+"options:");
+ Console.WriteLine(
+" -trace enable trace mode (hex dump of all exchanged bytes)");
+ Console.WriteLine(
+" -cv pass the '-v' argument to the test command");
+ Console.WriteLine(
+" -enum perform all version/suite/curve/hash&sign combination tests");
+ Console.WriteLine(
+" -noenum do NOT perform the version/suite/curve/hash&sign tests");
+ Environment.Exit(1);
+ }
+
+ static object ReadConfig(string fname)
+ {
+ using (TextReader r = File.OpenText(fname)) {
+ return JSON.Parse(r);
+ }
+ }
+
+ int[] GetVersions()
+ {
+ string[] r = JSON.GetStringArray(conf, "versions");
+ int[] vv = new int[r.Length];
+ for (int i = 0; i < r.Length; i ++) {
+ vv[i] = GetVersionByName(r[i]);
+ }
+ return vv;
+ }
+
+ internal static int GetVersionByName(string s)
+ {
+ s = s.Replace(" ", "").Replace(".", "").ToUpperInvariant();
+ switch (s) {
+ case "TLS10": return SSL.TLS10;
+ case "TLS11": return SSL.TLS11;
+ case "TLS12": return SSL.TLS12;
+ default:
+ throw new Exception(string.Format(
+ "Unknown version: '{0}'", s));
+ }
+ }
+
+ int[] GetCipherSuites()
+ {
+ return GetSuitesByName(
+ JSON.GetStringArray(conf, "cipherSuites"));
+ }
+
+ internal static int[] GetSuitesByName(string[] ss)
+ {
+ int[] r = new int[ss.Length];
+ for (int i = 0; i < ss.Length; i ++) {
+ r[i] = GetSuiteByName(ss[i]);
+ }
+ return r;
+ }
+
+ internal static int GetSuiteByName(string s)
+ {
+ switch (s) {
+ case "NULL_WITH_NULL_NULL":
+ return SSL.NULL_WITH_NULL_NULL;
+ case "RSA_WITH_NULL_MD5":
+ return SSL.RSA_WITH_NULL_MD5;
+ case "RSA_WITH_NULL_SHA":
+ return SSL.RSA_WITH_NULL_SHA;
+ case "RSA_WITH_NULL_SHA256":
+ return SSL.RSA_WITH_NULL_SHA256;
+ case "RSA_WITH_RC4_128_MD5":
+ return SSL.RSA_WITH_RC4_128_MD5;
+ case "RSA_WITH_RC4_128_SHA":
+ return SSL.RSA_WITH_RC4_128_SHA;
+ case "RSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.RSA_WITH_3DES_EDE_CBC_SHA;
+ case "RSA_WITH_AES_128_CBC_SHA":
+ return SSL.RSA_WITH_AES_128_CBC_SHA;
+ case "RSA_WITH_AES_256_CBC_SHA":
+ return SSL.RSA_WITH_AES_256_CBC_SHA;
+ case "RSA_WITH_AES_128_CBC_SHA256":
+ return SSL.RSA_WITH_AES_128_CBC_SHA256;
+ case "RSA_WITH_AES_256_CBC_SHA256":
+ return SSL.RSA_WITH_AES_256_CBC_SHA256;
+ case "DH_DSS_WITH_3DES_EDE_CBC_SHA":
+ return SSL.DH_DSS_WITH_3DES_EDE_CBC_SHA;
+ case "DH_RSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.DH_RSA_WITH_3DES_EDE_CBC_SHA;
+ case "DHE_DSS_WITH_3DES_EDE_CBC_SHA":
+ return SSL.DHE_DSS_WITH_3DES_EDE_CBC_SHA;
+ case "DHE_RSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.DHE_RSA_WITH_3DES_EDE_CBC_SHA;
+ case "DH_DSS_WITH_AES_128_CBC_SHA":
+ return SSL.DH_DSS_WITH_AES_128_CBC_SHA;
+ case "DH_RSA_WITH_AES_128_CBC_SHA":
+ return SSL.DH_RSA_WITH_AES_128_CBC_SHA;
+ case "DHE_DSS_WITH_AES_128_CBC_SHA":
+ return SSL.DHE_DSS_WITH_AES_128_CBC_SHA;
+ case "DHE_RSA_WITH_AES_128_CBC_SHA":
+ return SSL.DHE_RSA_WITH_AES_128_CBC_SHA;
+ case "DH_DSS_WITH_AES_256_CBC_SHA":
+ return SSL.DH_DSS_WITH_AES_256_CBC_SHA;
+ case "DH_RSA_WITH_AES_256_CBC_SHA":
+ return SSL.DH_RSA_WITH_AES_256_CBC_SHA;
+ case "DHE_DSS_WITH_AES_256_CBC_SHA":
+ return SSL.DHE_DSS_WITH_AES_256_CBC_SHA;
+ case "DHE_RSA_WITH_AES_256_CBC_SHA":
+ return SSL.DHE_RSA_WITH_AES_256_CBC_SHA;
+ case "DH_DSS_WITH_AES_128_CBC_SHA256":
+ return SSL.DH_DSS_WITH_AES_128_CBC_SHA256;
+ case "DH_RSA_WITH_AES_128_CBC_SHA256":
+ return SSL.DH_RSA_WITH_AES_128_CBC_SHA256;
+ case "DHE_DSS_WITH_AES_128_CBC_SHA256":
+ return SSL.DHE_DSS_WITH_AES_128_CBC_SHA256;
+ case "DHE_RSA_WITH_AES_128_CBC_SHA256":
+ return SSL.DHE_RSA_WITH_AES_128_CBC_SHA256;
+ case "DH_DSS_WITH_AES_256_CBC_SHA256":
+ return SSL.DH_DSS_WITH_AES_256_CBC_SHA256;
+ case "DH_RSA_WITH_AES_256_CBC_SHA256":
+ return SSL.DH_RSA_WITH_AES_256_CBC_SHA256;
+ case "DHE_DSS_WITH_AES_256_CBC_SHA256":
+ return SSL.DHE_DSS_WITH_AES_256_CBC_SHA256;
+ case "DHE_RSA_WITH_AES_256_CBC_SHA256":
+ return SSL.DHE_RSA_WITH_AES_256_CBC_SHA256;
+ case "DH_anon_WITH_RC4_128_MD5":
+ return SSL.DH_anon_WITH_RC4_128_MD5;
+ case "DH_anon_WITH_3DES_EDE_CBC_SHA":
+ return SSL.DH_anon_WITH_3DES_EDE_CBC_SHA;
+ case "DH_anon_WITH_AES_128_CBC_SHA":
+ return SSL.DH_anon_WITH_AES_128_CBC_SHA;
+ case "DH_anon_WITH_AES_256_CBC_SHA":
+ return SSL.DH_anon_WITH_AES_256_CBC_SHA;
+ case "DH_anon_WITH_AES_128_CBC_SHA256":
+ return SSL.DH_anon_WITH_AES_128_CBC_SHA256;
+ case "DH_anon_WITH_AES_256_CBC_SHA256":
+ return SSL.DH_anon_WITH_AES_256_CBC_SHA256;
+ case "ECDH_ECDSA_WITH_NULL_SHA":
+ return SSL.ECDH_ECDSA_WITH_NULL_SHA;
+ case "ECDH_ECDSA_WITH_RC4_128_SHA":
+ return SSL.ECDH_ECDSA_WITH_RC4_128_SHA;
+ case "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
+ case "ECDH_ECDSA_WITH_AES_128_CBC_SHA":
+ return SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA;
+ case "ECDH_ECDSA_WITH_AES_256_CBC_SHA":
+ return SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA;
+ case "ECDHE_ECDSA_WITH_NULL_SHA":
+ return SSL.ECDHE_ECDSA_WITH_NULL_SHA;
+ case "ECDHE_ECDSA_WITH_RC4_128_SHA":
+ return SSL.ECDHE_ECDSA_WITH_RC4_128_SHA;
+ case "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
+ case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
+ return SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
+ case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
+ return SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
+ case "ECDH_RSA_WITH_NULL_SHA":
+ return SSL.ECDH_RSA_WITH_NULL_SHA;
+ case "ECDH_RSA_WITH_RC4_128_SHA":
+ return SSL.ECDH_RSA_WITH_RC4_128_SHA;
+ case "ECDH_RSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
+ case "ECDH_RSA_WITH_AES_128_CBC_SHA":
+ return SSL.ECDH_RSA_WITH_AES_128_CBC_SHA;
+ case "ECDH_RSA_WITH_AES_256_CBC_SHA":
+ return SSL.ECDH_RSA_WITH_AES_256_CBC_SHA;
+ case "ECDHE_RSA_WITH_NULL_SHA":
+ return SSL.ECDHE_RSA_WITH_NULL_SHA;
+ case "ECDHE_RSA_WITH_RC4_128_SHA":
+ return SSL.ECDHE_RSA_WITH_RC4_128_SHA;
+ case "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
+ return SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
+ case "ECDHE_RSA_WITH_AES_128_CBC_SHA":
+ return SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA;
+ case "ECDHE_RSA_WITH_AES_256_CBC_SHA":
+ return SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA;
+ case "ECDH_anon_WITH_NULL_SHA":
+ return SSL.ECDH_anon_WITH_NULL_SHA;
+ case "ECDH_anon_WITH_RC4_128_SHA":
+ return SSL.ECDH_anon_WITH_RC4_128_SHA;
+ case "ECDH_anon_WITH_3DES_EDE_CBC_SHA":
+ return SSL.ECDH_anon_WITH_3DES_EDE_CBC_SHA;
+ case "ECDH_anon_WITH_AES_128_CBC_SHA":
+ return SSL.ECDH_anon_WITH_AES_128_CBC_SHA;
+ case "ECDH_anon_WITH_AES_256_CBC_SHA":
+ return SSL.ECDH_anon_WITH_AES_256_CBC_SHA;
+ case "RSA_WITH_AES_128_GCM_SHA256":
+ return SSL.RSA_WITH_AES_128_GCM_SHA256;
+ case "RSA_WITH_AES_256_GCM_SHA384":
+ return SSL.RSA_WITH_AES_256_GCM_SHA384;
+ case "DHE_RSA_WITH_AES_128_GCM_SHA256":
+ return SSL.DHE_RSA_WITH_AES_128_GCM_SHA256;
+ case "DHE_RSA_WITH_AES_256_GCM_SHA384":
+ return SSL.DHE_RSA_WITH_AES_256_GCM_SHA384;
+ case "DH_RSA_WITH_AES_128_GCM_SHA256":
+ return SSL.DH_RSA_WITH_AES_128_GCM_SHA256;
+ case "DH_RSA_WITH_AES_256_GCM_SHA384":
+ return SSL.DH_RSA_WITH_AES_256_GCM_SHA384;
+ case "DHE_DSS_WITH_AES_128_GCM_SHA256":
+ return SSL.DHE_DSS_WITH_AES_128_GCM_SHA256;
+ case "DHE_DSS_WITH_AES_256_GCM_SHA384":
+ return SSL.DHE_DSS_WITH_AES_256_GCM_SHA384;
+ case "DH_DSS_WITH_AES_128_GCM_SHA256":
+ return SSL.DH_DSS_WITH_AES_128_GCM_SHA256;
+ case "DH_DSS_WITH_AES_256_GCM_SHA384":
+ return SSL.DH_DSS_WITH_AES_256_GCM_SHA384;
+ case "DH_anon_WITH_AES_128_GCM_SHA256":
+ return SSL.DH_anon_WITH_AES_128_GCM_SHA256;
+ case "DH_anon_WITH_AES_256_GCM_SHA384":
+ return SSL.DH_anon_WITH_AES_256_GCM_SHA384;
+ case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
+ return SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
+ case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384":
+ return SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
+ case "ECDH_ECDSA_WITH_AES_128_CBC_SHA256":
+ return SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
+ case "ECDH_ECDSA_WITH_AES_256_CBC_SHA384":
+ return SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
+ case "ECDHE_RSA_WITH_AES_128_CBC_SHA256":
+ return SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256;
+ case "ECDHE_RSA_WITH_AES_256_CBC_SHA384":
+ return SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384;
+ case "ECDH_RSA_WITH_AES_128_CBC_SHA256":
+ return SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256;
+ case "ECDH_RSA_WITH_AES_256_CBC_SHA384":
+ return SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384;
+ case "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
+ return SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
+ case "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
+ return SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
+ case "ECDH_ECDSA_WITH_AES_128_GCM_SHA256":
+ return SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
+ case "ECDH_ECDSA_WITH_AES_256_GCM_SHA384":
+ return SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
+ case "ECDHE_RSA_WITH_AES_128_GCM_SHA256":
+ return SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256;
+ case "ECDHE_RSA_WITH_AES_256_GCM_SHA384":
+ return SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384;
+ case "ECDH_RSA_WITH_AES_128_GCM_SHA256":
+ return SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256;
+ case "ECDH_RSA_WITH_AES_256_GCM_SHA384":
+ return SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384;
+ case "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+ case "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
+ case "DHE_RSA_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+ case "PSK_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.PSK_WITH_CHACHA20_POLY1305_SHA256;
+ case "ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256;
+ case "DHE_PSK_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.DHE_PSK_WITH_CHACHA20_POLY1305_SHA256;
+ case "RSA_PSK_WITH_CHACHA20_POLY1305_SHA256":
+ return SSL.RSA_PSK_WITH_CHACHA20_POLY1305_SHA256;
+ default:
+ throw new Exception(string.Format(
+ "Unknown cipher suite: '{0}'", s));
+ }
+ }
+
+ int[] GetHashAndSigns()
+ {
+ return GetHashAndSignsByName(
+ JSON.GetStringArray(conf, "hashAndSigns"));
+ }
+
+ internal static int[] GetHashAndSignsByName(string[] ss)
+ {
+ int[] r = new int[ss.Length];
+ for (int i = 0; i < ss.Length; i ++) {
+ r[i] = GetHashAndSignByName(ss[i]);
+ }
+ return r;
+ }
+
+ internal static int GetHashAndSignByName(string s)
+ {
+ switch (s) {
+ case "RSA_MD5": return SSL.RSA_MD5;
+ case "RSA_SHA1": return SSL.RSA_SHA1;
+ case "RSA_SHA224": return SSL.RSA_SHA224;
+ case "RSA_SHA256": return SSL.RSA_SHA256;
+ case "RSA_SHA384": return SSL.RSA_SHA384;
+ case "RSA_SHA512": return SSL.RSA_SHA512;
+ case "ECDSA_MD5": return SSL.ECDSA_MD5;
+ case "ECDSA_SHA1": return SSL.ECDSA_SHA1;
+ case "ECDSA_SHA224": return SSL.ECDSA_SHA224;
+ case "ECDSA_SHA256": return SSL.ECDSA_SHA256;
+ case "ECDSA_SHA384": return SSL.ECDSA_SHA384;
+ case "ECDSA_SHA512": return SSL.ECDSA_SHA512;
+ default:
+ throw new Exception(string.Format(
+ "Unknown hash-and-sign: '{0}'", s));
+ }
+ }
+
+ int[] GetCurves()
+ {
+ return GetCurvesByName(JSON.GetStringArray(conf, "curves"));
+ }
+
+ internal static int[] GetCurvesByName(string[] ss)
+ {
+ int[] r = new int[ss.Length];
+ for (int i = 0; i < ss.Length; i ++) {
+ r[i] = GetCurveByName(ss[i]);
+ }
+ return r;
+ }
+
+ internal static int GetCurveByName(string s)
+ {
+ switch (s) {
+ case "Curve25519": return SSL.Curve25519;
+ case "NIST_P256": return SSL.NIST_P256;
+ case "NIST_P384": return SSL.NIST_P384;
+ case "NIST_P521": return SSL.NIST_P521;
+ default:
+ throw new Exception(string.Format(
+ "Unknown curve: '{0}'", s));
+ }
+ }
+
+ /*
+ * RunEnum() builds and runs synthetic tests that exercise all
+ * combinations of protocol version, cipher suites, curves
+ * and hash-and-sign. Curves and hash-and-sign are enumerated
+ * only for ECDHE suites.
+ */
+ void RunEnum()
+ {
+ RunEnum(true, true);
+ RunEnum(false, true);
+ }
+
+ int RunEnum(bool cmdClient, bool doit)
+ {
+ int count = 0;
+ foreach (int version in versions) {
+ foreach (int suite in cipherSuites) {
+ if (version < SSL.TLS12 && SSL.IsTLS12(suite)) {
+ continue;
+ }
+ if (!SSL.IsECDHE(suite)) {
+ if (doit) {
+ RunEnum(cmdClient,
+ version, suite, -1, -1);
+ }
+ count ++;
+ continue;
+ }
+ bool needRSA = (version >= SSL.TLS12)
+ && SSL.IsECDHE_RSA(suite);
+ bool needECDSA = (version >= SSL.TLS12)
+ && SSL.IsECDHE_ECDSA(suite);
+ foreach (int hs in hashAndSigns) {
+ int sa = hs & 0xFF;
+ if (needRSA && sa != SSL.RSA) {
+ continue;
+ }
+ if (needECDSA && sa != SSL.ECDSA) {
+ continue;
+ }
+ foreach (int curve in curves) {
+ if (doit) {
+ RunEnum(cmdClient,
+ version,
+ suite,
+ curve, hs);
+ }
+ count ++;
+ }
+ }
+ }
+ }
+ return count;
+ }
+
+ int ComputeTotalEnum()
+ {
+ return RunEnum(true, false) + RunEnum(false, false);
+ }
+
+ static string TEnumToString(
+ int version, int suite, int curve, int hs)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("enum_{0:X4}_{1:X4}", version, suite);
+ if (curve >= 0 && hs >= 0) {
+ sb.AppendFormat("_{0}_{1}", curve, hs);
+ }
+ return sb.ToString();
+ }
+
+ static bool StringToTEnum(string s,
+ out bool cmdClient,
+ out int version, out int suite,
+ out int curve, out int hs)
+ {
+ cmdClient = false;
+ version = -1;
+ suite = -1;
+ curve = -1;
+ hs = -1;
+ s = s.Trim();
+ if (!s.StartsWith("enum_")) {
+ return false;
+ }
+ s = s.Substring(5);
+ if (s.EndsWith("_client")) {
+ cmdClient = true;
+ } else if (s.EndsWith("_server")) {
+ cmdClient = false;
+ } else {
+ return false;
+ }
+ s = s.Substring(0, s.Length - 7);
+ string[] ww = s.Split('_');
+ if (ww.Length != 2 && ww.Length != 4) {
+ return false;
+ }
+ version = ParseHex4(ww[0]);
+ suite = ParseHex4(ww[1]);
+ if (ww.Length == 2) {
+ return version >= 0 && suite >= 0;
+ } else {
+ curve = ParseDec(ww[2]);
+ hs = ParseDec(ww[3]);
+ return version >= 0 && suite >= 0
+ && curve >= 0 && hs >= 0;
+ }
+ }
+
+ static int HexVal(int cp)
+ {
+ if (cp >= '0' && cp <= '9') {
+ return cp - '0';
+ } else if (cp >= 'A' && cp <= 'F') {
+ return cp - ('A' - 10);
+ } else if (cp >= 'a' && cp <= 'f') {
+ return cp - ('a' - 10);
+ } else {
+ return -1;
+ }
+ }
+
+ static int ParseHex4(string s)
+ {
+ if (s.Length != 4) {
+ return -1;
+ }
+ return ParseHex(s);
+ }
+
+ static int ParseHex(string s)
+ {
+ int acc = 0;
+ for (int i = 0; i < 4; i ++) {
+ int v = HexVal(s[i]);
+ if (v < 0) {
+ return -1;
+ }
+ acc = (acc << 4) + v;
+ }
+ return acc;
+ }
+
+ static int ParseDec(string s)
+ {
+ int n = s.Length;
+ int acc = 0;
+ for (int i = 0; i < n; i ++) {
+ int v = HexVal(s[i]);
+ if (v < 0 || v >= 10) {
+ return -1;
+ }
+ acc = (acc * 10) + v;
+ }
+ return acc;
+ }
+
+ void RunEnum(bool cmdClient, int version, int suite, int curve, int hs)
+ {
+ IDictionary<string, object> d =
+ new SortedDictionary<string, object>(
+ StringComparer.Ordinal);
+ d["name"] = TEnumToString(version, suite, curve, hs);
+ d["versionMin"] = SSL.VersionName(version);
+ d["versionMax"] = SSL.VersionName(version);
+ d["cipherSuites"] = new string[] {
+ SSL.CipherSuiteName(suite)
+ };
+ if (curve >= 0) {
+ d["curves"] = new string[] {
+ SSL.CurveName(curve)
+ };
+ d["hashAndSigns"] = new string[] {
+ SSL.HashAndSignName(hs)
+ };
+ } else {
+ d["curves"] = new string[0];
+ d["hashAndSigns"] = new string[0];
+ }
+ if (SSL.IsRSA(suite) || SSL.IsECDHE_RSA(suite)) {
+ d["serverCertType"] = "RSA";
+ }
+ if (SSL.IsECDH(suite) || SSL.IsECDHE_ECDSA(suite)) {
+ d["serverCertType"] = "EC";
+ }
+ RunTest(cmdClient, d);
+ }
+
+ /*
+ * Get certificate type for the provided test (client-side or
+ * server-side certificate, depending on 'client').
+ *
+ * If the test does not contain an explicit indication, then
+ * the certificate type will be "none" for a client, "RSA" for
+ * a server.
+ */
+ string GetCertType(object obj, bool client)
+ {
+ string name = client ? "clientCertType" : "serverCertType";
+ string ct;
+ if (JSON.TryGetString(obj, name, out ct)) {
+ return ct;
+ }
+ return client ? "none" : "RSA";
+ }
+
+ int GetNumTests(object obj)
+ {
+ int num = 0;
+ bool v;
+ if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
+ num ++;
+ }
+ if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
+ num ++;
+ }
+ return num;
+ }
+
+ void RunTest(object obj)
+ {
+ bool v;
+ if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
+ RunTest(true, obj);
+ }
+ if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
+ RunTest(false, obj);
+ }
+ }
+
+ void RunTest(bool cmdClient, object obj)
+ {
+ string name = JSON.GetString(obj, "name")
+ + (cmdClient ? "_client" : "_server");
+ Console.Write("\r({0}/{1})",
+ totalSuccess + totalFailures + 1, totalTests);
+ // Console.Write("{0}:", name);
+
+ /*
+ * Expected command exit code:
+ *
+ * 0 if the command is supposed to exit gracefully
+ * 1 if the command should detect and report an error
+ */
+ int expectedExitCode;
+ JSON.TryGetInt32(obj, "expectedExitCode", out expectedExitCode);
+
+ /*
+ * Expected failure: if defined, then we expect our
+ * library to throw an exception, and the message should
+ * contain that specific string.
+ */
+ string expectedFailure;
+ JSON.TryGetString(obj, "expectedFailure", out expectedFailure);
+
+ /*
+ * Assemble the sub-process command line:
+ *
+ * - Always one of "-client" or "-server"
+ * - For a server command, a certificate and key are
+ * always provided (defaults to RSA); for a client,
+ * only if explicitly asked for.
+ */
+ StringBuilder sb = new StringBuilder();
+ if (cmdClient) {
+ sb.Append("-client");
+ } else {
+ sb.Append("-server");
+ }
+ if (commandVerbose) {
+ sb.Append(" -v");
+ }
+ string certType = GetCertType(obj, cmdClient);
+ switch (certType) {
+ case "RSA":
+ sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
+ chainRSAFile, skeyRSAFile);
+ break;
+ case "EC":
+ sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
+ chainECFile, skeyECFile);
+ break;
+ case "none":
+ break;
+ default:
+ throw new Exception("Unknown certType: " + certType);
+ }
+ string extra;
+ if (JSON.TryGetString(obj, "extraArgs", out extra)) {
+ sb.Append(' ');
+ sb.Append(extra);
+ }
+
+ /*
+ * Run the sub-process.
+ */
+ ProcessStartInfo si = new ProcessStartInfo();
+ si.FileName = commandFile;
+ si.Arguments = string.Format(commandArgs, sb.ToString());
+ si.UseShellExecute = false;
+ si.ErrorDialog = false;
+ si.CreateNoWindow = true;
+ si.RedirectStandardInput = true;
+ si.RedirectStandardOutput = true;
+
+ using (Process pp = new Process()) {
+ pp.StartInfo = si;
+ pp.Start();
+ Exception delayed = null;
+ try {
+ /*
+ * TODO: add a time-out on the streams
+ * so that the test never stalls
+ * indefinitely if the two SSL engines
+ * lose synchronisation.
+ */
+ MergeStream ms = new MergeStream(
+ pp.StandardOutput.BaseStream,
+ pp.StandardInput.BaseStream);
+ if (trace) {
+ ms.Debug = Console.Out;
+ }
+ RunTestInner(cmdClient, obj, ms);
+ } catch (Exception ex) {
+ delayed = ex;
+ }
+
+ /*
+ * Once the test has run, we must make sure that
+ * the sub-processed is finished. It _should_ end
+ * properly by itself for all successful test cases,
+ * so if we have to kill it, then it's a bug.
+ */
+ bool killed = false;
+ if (!pp.WaitForExit(2000)) {
+ try {
+ pp.Kill();
+ } catch {
+ // ignored
+ }
+ pp.WaitForExit();
+ killed = true;
+ }
+ int exc = pp.ExitCode;
+
+ /*
+ * If we had to kill the command, then that is
+ * always a bug. Otherwise, we compare what we
+ * got with the expected outcomes.
+ */
+ List<string> msg = new List<string>();
+ if (killed) {
+ msg.Add("COMMAND KILLED");
+ }
+ if (exc != expectedExitCode) {
+ msg.Add("Wrong exit code: "
+ + exc + " (expected: "
+ + expectedExitCode + ")");
+ }
+ if (delayed == null) {
+ if (expectedFailure != null) {
+ msg.Add("An exception was expected");
+ }
+ } else {
+ if (expectedFailure == null) {
+ msg.Add(delayed.ToString());
+ } else {
+ string s = delayed.Message;
+ if (s == null) {
+ s = "";
+ }
+ if (s.IndexOf(expectedFailure) < 0) {
+ msg.Add(delayed.ToString());
+ }
+ }
+ }
+ if (msg.Count == 0) {
+ totalSuccess ++;
+ } else {
+ Console.WriteLine("{0}: FAIL:", name);
+ foreach (string s in msg) {
+ Console.WriteLine(s);
+ }
+ totalFailures ++;
+ }
+ }
+ }
+
+ void RunTestInner(bool cmdClient, object obj, Stream peer)
+ {
+ /*
+ * Create the SSL engine, and configure it as specified
+ * in the configuration object (with the default
+ * configuration as fallback).
+ */
+
+ SSLEngine eng;
+ byte[][] chain = null;
+ IPrivateKey skey = null;
+ string certType = GetCertType(obj, !cmdClient);
+ switch (certType) {
+ case "RSA":
+ chain = chainRSA;
+ skey = skeyRSA;
+ break;
+ case "EC":
+ chain = chainEC;
+ skey = skeyEC;
+ break;
+ case "none":
+ break;
+ default:
+ throw new Exception("Unknown certType: " + certType);
+ }
+ if (cmdClient) {
+ IServerPolicy spol = new SSLServerPolicyBasic(
+ chain, skey, KeyUsage.EncryptAndSign);
+ SSLServer ss = new SSLServer(peer, spol);
+ ss.SessionCache = new SSLSessionCacheLRU(20);
+ eng = ss;
+ } else {
+ SSLClient sc = new SSLClient(peer);
+ sc.ServerCertValidator =
+ SSLClient.InsecureCertValidator;
+ eng = sc;
+ }
+ eng.NormalizeIOError = true;
+ eng.AutoFlush = false;
+
+ /*
+ * Minimum version.
+ */
+ string svmin;
+ if (JSON.TryGetString(obj, "versionMin", out svmin)) {
+ eng.VersionMin = GetVersionByName(svmin);
+ } else {
+ eng.VersionMin = versionMin;
+ }
+
+ /*
+ * Maximum version.
+ */
+ string svmax;
+ if (JSON.TryGetString(obj, "versionMax", out svmax)) {
+ eng.VersionMax = GetVersionByName(svmax);
+ } else {
+ eng.VersionMax = versionMax;
+ }
+
+ /*
+ * Supported cipher suites.
+ */
+ string[] sccs;
+ if (JSON.TryGetStringArray(obj, "cipherSuites", out sccs)) {
+ eng.SupportedCipherSuites = GetSuitesByName(sccs);
+ } else {
+ eng.SupportedCipherSuites = cipherSuites;
+ }
+
+ /*
+ * Supported hash-and-sign algorithms.
+ */
+ string[] shss;
+ if (JSON.TryGetStringArray(obj, "hashAndSigns", out shss)) {
+ eng.SupportedHashAndSign = GetHashAndSignsByName(shss);
+ } else {
+ eng.SupportedHashAndSign = hashAndSigns;
+ }
+
+ /*
+ * Supported elliptic curves.
+ */
+ string[] secc;
+ if (JSON.TryGetStringArray(obj, "curves", out secc)) {
+ eng.SupportedCurves = GetCurvesByName(secc);
+ } else {
+ eng.SupportedCurves = curves;
+ }
+
+ /*
+ * What to do when there is no close_notify.
+ */
+ bool ncn;
+ if (JSON.TryGetBool(obj, "noCloseNotify", out ncn)) {
+ eng.NoCloseNotify = ncn;
+ } else {
+ eng.NoCloseNotify = noCloseNotify;
+ }
+
+ /*
+ * Quirks.
+ */
+ IDictionary<string, object> qm;
+ if (JSON.TryGetObjectMap(obj, "quirks", out qm)) {
+ SSLQuirks q = new SSLQuirks();
+ foreach (string name in qm.Keys) {
+ q[name] = JSON.GetString(qm, name);
+ }
+ eng.Quirks = q;
+ }
+
+ bool askClose;
+ JSON.TryGetBool(obj, "askClose", out askClose);
+ bool renegotiate, renegotiateAccepted;
+ renegotiate = JSON.TryGetBool(obj, "renegotiate",
+ out renegotiateAccepted);
+ bool askRenegotiate, askRenegotiateAccepted;
+ askRenegotiate = JSON.TryGetBool(obj, "askRenegotiate",
+ out askRenegotiateAccepted);
+
+ bool reconnectSelf = false, reconnectPeer = false;
+ string rcs;
+ if (JSON.TryGetString(obj, "reconnect", out rcs)) {
+ switch (rcs) {
+ case "self": reconnectSelf = true; break;
+ case "peer": reconnectPeer = true; break;
+ default:
+ throw new Exception("Unknown 'reconnect' type: "
+ + rcs);
+ }
+ }
+
+ bool forgetSelf = false, forgetPeer = false;
+ string fgs;
+ if (JSON.TryGetString(obj, "forget", out fgs)) {
+ switch (fgs) {
+ case "self": forgetSelf = true; break;
+ case "peer": forgetPeer = true; break;
+ default:
+ throw new Exception("Unknown 'forget' type: "
+ + fgs);
+ }
+ }
+
+ if (askClose) {
+ SendCommand(eng, 'C');
+ if (eng.ReadByte() != -1) {
+ throw new Exception("Peer did not close");
+ }
+ } else if (renegotiate) {
+ SendMessageNormal(eng, 10);
+ if (eng.Renegotiate()) {
+ if (!renegotiateAccepted) {
+ throw new Exception("Renegotiation"
+ + " should have been rejected");
+ }
+ } else {
+ if (renegotiateAccepted) {
+ throw new Exception("Renegotiation"
+ + " should have been accepted");
+ }
+ }
+ SendMessageNormal(eng, 9);
+ } else if (askRenegotiate) {
+ SendMessageNormal(eng, 10);
+ long rc = eng.HandshakeCount;
+ SendCommand(eng, 'G');
+ string s = ReadLine(eng);
+ switch (s) {
+ case "DENIED":
+ if (askRenegotiateAccepted) {
+ throw new Exception("Renegotiation"
+ + " should have been accepted");
+ }
+ break;
+ case "OK":
+ if (!askRenegotiateAccepted) {
+ throw new Exception("Renegotiation"
+ + " should have been rejected");
+ }
+ long nrc = eng.HandshakeCount;
+ if (nrc != rc + 1) {
+ throw new Exception(string.Format(
+ "Wrong handshake count"
+ + " (old={0}, new={1})",
+ rc, nrc));
+ }
+ break;
+ default:
+ throw new Exception(string.Format(
+ "Unexpected answer string '{0}'", s));
+ }
+ SendMessageNormal(eng, 8);
+ } else if (reconnectSelf || reconnectPeer) {
+ SendMessageNormal(eng, 50);
+ SendMessageNormal(eng, 100);
+ if (forgetPeer) {
+ SendCommand(eng, 'U');
+ string s = ReadLine(eng);
+ if (s != "DONE") {
+ throw new Exception(string.Format(
+ "Unexpected answer '{0}'", s));
+ }
+ }
+ eng.CloseSub = false;
+ if (reconnectPeer) {
+ SendCommand(eng, 'T');
+ if (eng.ReadByte() != -1) {
+ throw new Exception(
+ "Peer did not close");
+ }
+ } else {
+ SendCommand(eng, 'R');
+ string s = ReadLine(eng);
+ if (s != "OK") {
+ throw new Exception(string.Format(
+ "Unexpected answer '{0}'", s));
+ }
+ eng.Close();
+ }
+ SSLEngine eng2;
+ if (cmdClient) {
+ IServerPolicy spol = new SSLServerPolicyBasic(
+ chain, skey, KeyUsage.EncryptAndSign);
+ SSLServer ss = new SSLServer(peer, spol);
+ if (forgetSelf) {
+ ss.SessionCache =
+ new SSLSessionCacheLRU(20);
+ } else {
+ ss.SessionCache =
+ ((SSLServer)eng).SessionCache;
+ }
+ eng2 = ss;
+ } else {
+ SSLSessionParameters sp;
+ if (forgetSelf) {
+ sp = null;
+ } else {
+ sp = eng.SessionParameters;
+ }
+ SSLClient sc = new SSLClient(peer, sp);
+ sc.ServerCertValidator =
+ SSLClient.InsecureCertValidator;
+ eng2 = sc;
+ }
+ eng2.NormalizeIOError = eng.NormalizeIOError;
+ eng2.AutoFlush = eng.AutoFlush;
+ eng2.VersionMin = eng.VersionMin;
+ eng2.VersionMax = eng.VersionMax;
+ eng2.SupportedCipherSuites = eng.SupportedCipherSuites;
+ eng2.SupportedHashAndSign = eng.SupportedHashAndSign;
+ eng2.SupportedCurves = eng.SupportedCurves;
+ eng2.NoCloseNotify = eng.NoCloseNotify;
+ eng2.Quirks = eng.Quirks;
+ eng = eng2;
+ SendMessageNormal(eng, 60);
+ SendMessageNormal(eng, 90);
+ if (forgetSelf || forgetPeer) {
+ if (eng.IsResume) {
+ throw new Exception(
+ "Session was resumed");
+ }
+ } else {
+ if (!eng.IsResume) {
+ throw new Exception(
+ "Session was not resumed");
+ }
+ }
+ } else {
+ for (int i = 0; i <= 38; i ++) {
+ int len;
+ if (i <= 20) {
+ len = i;
+ } else {
+ len = 20 + (1 << (i - 20));
+ }
+ SendMessageNormal(eng, len);
+ }
+ }
+
+ eng.Close();
+ }
+
+ /*
+ * Send a "normal" message to the peer, of the specified
+ * length: this is a sequence of 'len' random bytes, distinct
+ * from 0x0A, followed one 0x0A byte. The peer is supposed to
+ * respond with the SHA-1 hash of the message bytes (excluding
+ * the final 0x0A), encoded in hexadecimal (lowercase) and
+ * followed by a newline (0x0A). An exception is thrown if the
+ * expected value is not obtained.
+ */
+ void SendMessageNormal(SSLEngine eng, int len)
+ {
+ SHA1 sha1 = new SHA1();
+ byte[] buf = new byte[len + 1];
+ RNG.GetBytesNonZero(buf, 0, len);
+ for (int i = 0; i < len; i ++) {
+ buf[i] ^= 0x0A;
+ }
+ buf[len] = 0x0A;
+ if (len == 1) {
+ buf[0] = (byte)('a' + (buf[0] & 0x0F));
+ }
+ StringBuilder sb = new StringBuilder();
+ foreach (byte b in sha1.Hash(buf, 0, len)) {
+ sb.AppendFormat("{0:x2}", b);
+ }
+ sb.Append('\n');
+ eng.Write(buf, 0, buf.Length);
+ eng.Flush();
+ for (int i = 0; i < sb.Length; i ++) {
+ int x = eng.ReadByte();
+ int y = sb[i];
+ if (x != y) {
+ throw new Exception(string.Format(
+ "received {0} (exp: {1})", y, x));
+ }
+ }
+ }
+
+ void SendCommand(SSLEngine eng, char cmd)
+ {
+ eng.WriteByte((byte)cmd);
+ eng.WriteByte(0x0A);
+ eng.Flush();
+ }
+
+ string ReadLine(SSLEngine eng)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (;;) {
+ int c = eng.ReadByte();
+ if (c < 0) {
+ throw new Exception("Unexpected EOF");
+ }
+ if (c == 0x0A) {
+ return sb.ToString();
+ }
+ sb.Append((char)c);
+ }
+ }
+
+ static byte[][] DecodeChain(string fname)
+ {
+ byte[] buf = File.ReadAllBytes(fname);
+ PEMObject[] fpo = AsnIO.DecodePEM(buf);
+ if (fpo.Length == 0) {
+ buf = AsnIO.FindBER(buf);
+ if (buf == null) {
+ throw new Exception(string.Format(
+ "No certificate in file '{0}'", fname));
+ }
+ return new byte[][] { buf };
+ }
+ List<byte[]> r = new List<byte[]>();
+ foreach (PEMObject po in fpo) {
+ string tt = po.type.ToUpperInvariant();
+ if (tt == "CERTIFICATE" || tt == "X509 CERTIFICATE") {
+ r.Add(po.data);
+ }
+ }
+ if (r.Count == 0) {
+ throw new Exception(string.Format(
+ "No certificate in file '{0}'", fname));
+ }
+ return r.ToArray();
+ }
+
+ static IPrivateKey DecodePrivateKey(string fname)
+ {
+ byte[] buf = File.ReadAllBytes(fname);
+ PEMObject[] fpo = AsnIO.DecodePEM(buf);
+ if (fpo.Length == 0) {
+ buf = AsnIO.FindBER(buf);
+ } else {
+ buf = null;
+ foreach (PEMObject po in fpo) {
+ string tt = po.type.ToUpperInvariant();
+ if (tt.IndexOf("PRIVATE KEY") >= 0) {
+ if (buf != null) {
+ throw new Exception(
+ "Multiple keys in '"
+ + fname + "'");
+ }
+ buf = po.data;
+ }
+ }
+ }
+ if (buf == null) {
+ throw new Exception(string.Format(
+ "No private key in file '{0}'", fname));
+ }
+ return KF.DecodePrivateKey(buf);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Asn1;
+
+namespace X500 {
+
+/*
+ * A DNPart instance encodes an X.500 name element: it has a type (an
+ * OID) and a value. The value is an ASN.1 object. If the name type is
+ * one of a list of standard types, then the value is a character
+ * string, and there is a "friendly type" which is a character string
+ * (such as "CN" for the "common name", of OID 2.5.4.3).
+ */
+
+public class DNPart {
+
+ /*
+ * These are the known "friendly types". The values decode as
+ * strings.
+ */
+
+ public const string COMMON_NAME = "CN";
+ public const string LOCALITY = "L";
+ public const string STATE = "ST";
+ public const string ORGANIZATION = "O";
+ public const string ORGANIZATIONAL_UNIT = "OU";
+ public const string COUNTRY = "C";
+ public const string STREET = "STREET";
+ public const string DOMAIN_COMPONENT = "DC";
+ public const string USER_ID = "UID";
+ public const string EMAIL_ADDRESS = "EMAILADDRESS";
+
+ /*
+ * Get the type OID (decimal-dotted string representation).
+ */
+ public string OID {
+ get {
+ return OID_;
+ }
+ }
+ string OID_;
+
+ /*
+ * Get the string value for this element. If the element value
+ * could not be decoded as a string, then this method returns
+ * null.
+ *
+ * (Decoding error for name elements of a standard type trigger
+ * exceptions upon instance creation. Thus, a null value is
+ * possible only for a name element that uses an unknown type.)
+ */
+ public string Value {
+ get {
+ return Value_;
+ }
+ }
+ string Value_;
+
+ /*
+ * Tell whether this element is string based. This property
+ * returns true if and only if Value returns a non-null value.
+ */
+ public bool IsString {
+ get {
+ return Value_ != null;
+ }
+ }
+
+ /*
+ * Get the element value as an ASN.1 structure.
+ */
+ public AsnElt AsnValue {
+ get {
+ return AsnValue_;
+ }
+ }
+ AsnElt AsnValue_;
+
+ /*
+ * Get the "friendly type" for this element. This is the
+ * string mnemonic such as "CN" for "common name". If no
+ * friendly type is known for that element, then the OID
+ * is returned (decimal-dotted representation).
+ */
+ public string FriendlyType {
+ get {
+ return GetFriendlyType(OID);
+ }
+ }
+
+ /*
+ * "Normalized" string value (converted to uppercase then
+ * lowercase, leading and trailing whitespace trimmed, adjacent
+ * spaces coalesced). This should allow for efficient comparison
+ * while still supporting most corner cases.
+ *
+ * This does not implement full RFC 4518 rules, but it should
+ * be good enough for an analysis tool.
+ */
+ string normValue;
+
+ byte[] encodedValue;
+ int hashCode;
+
+ internal DNPart(string oid, AsnElt val)
+ {
+ OID_ = oid;
+ AsnValue_ = val;
+ encodedValue = val.Encode();
+ uint hc = (uint)oid.GetHashCode();
+ try {
+ string s = val.GetString();
+ Value_ = s;
+ s = s.ToUpperInvariant().ToLowerInvariant();
+ StringBuilder sb = new StringBuilder();
+ bool lwws = true;
+ foreach (char c in s.Trim()) {
+ if (IsControl(c)) {
+ continue;
+ }
+ if (IsWS(c)) {
+ if (lwws) {
+ continue;
+ }
+ lwws = true;
+ sb.Append(' ');
+ } else {
+ sb.Append(c);
+ }
+ }
+ int n = sb.Length;
+ if (n > 0 && sb[n - 1] == ' ') {
+ sb.Length = n - 1;
+ }
+ normValue = sb.ToString();
+ hc += (uint)normValue.GetHashCode();
+ } catch {
+ if (OID_TO_FT.ContainsKey(oid)) {
+ throw;
+ }
+ Value_ = null;
+ foreach (byte b in encodedValue) {
+ hc = ((hc << 7) | (hc >> 25)) ^ (uint)b;
+ }
+ }
+ hashCode = (int)hc;
+ }
+
+ static bool MustEscape(int x)
+ {
+ if (x < 0x20 || x >= 0x7F) {
+ return true;
+ }
+ switch (x) {
+ case '"':
+ case '+':
+ case ',':
+ case ';':
+ case '<':
+ case '>':
+ case '\\':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /*
+ * Convert this element to a string. This uses RFC 4514 rules.
+ */
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ string ft;
+ if (OID_TO_FT.TryGetValue(OID, out ft) && IsString) {
+ sb.Append(ft);
+ sb.Append("=");
+ byte[] buf = Encoding.UTF8.GetBytes(Value);
+ for (int i = 0; i < buf.Length; i ++) {
+ byte b = buf[i];
+ if ((i == 0 && (b == ' ' || b == '#'))
+ || (i == buf.Length - 1 && b == ' ')
+ || MustEscape(b))
+ {
+ switch ((char)b) {
+ case ' ':
+ case '"':
+ case '#':
+ case '+':
+ case ',':
+ case ';':
+ case '<':
+ case '=':
+ case '>':
+ case '\\':
+ sb.Append('\\');
+ sb.Append((char)b);
+ break;
+ default:
+ sb.AppendFormat("\\{0:X2}", b);
+ break;
+ }
+ } else {
+ sb.Append((char)b);
+ }
+ }
+ } else {
+ sb.Append(OID);
+ sb.Append("=#");
+ foreach (byte b in AsnValue.Encode()) {
+ sb.AppendFormat("{0:X2}", b);
+ }
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Get the friendly type corresponding to the given OID
+ * (decimal-dotted representation). If no such type is known,
+ * then the OID string is returned.
+ */
+ public static string GetFriendlyType(string oid)
+ {
+ string ft;
+ if (OID_TO_FT.TryGetValue(oid, out ft)) {
+ return ft;
+ }
+ return oid;
+ }
+
+ static int HexVal(char c)
+ {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ return c - ('A' - 10);
+ } else if (c >= 'a' && c <= 'f') {
+ return c - ('a' - 10);
+ } else {
+ return -1;
+ }
+ }
+
+ static int HexValCheck(char c)
+ {
+ int x = HexVal(c);
+ if (x < 0) {
+ throw new AsnException(String.Format(
+ "Not an hex digit: U+{0:X4}", c));
+ }
+ return x;
+ }
+
+ static int HexVal2(string str, int k)
+ {
+ if (k >= str.Length) {
+ throw new AsnException("Missing hex digits");
+ }
+ int x = HexVal(str[k]);
+ if ((k + 1) >= str.Length) {
+ throw new AsnException("Odd number of hex digits");
+ }
+ return (x << 4) + HexVal(str[k + 1]);
+ }
+
+ static int ReadHexEscape(string str, ref int off)
+ {
+ if (off >= str.Length || str[off] != '\\') {
+ return -1;
+ }
+ if ((off + 1) >= str.Length) {
+ throw new AsnException("Truncated escape");
+ }
+ int x = HexVal(str[off + 1]);
+ if (x < 0) {
+ return -1;
+ }
+ if ((off + 2) >= str.Length) {
+ throw new AsnException("Truncated escape");
+ }
+ int y = HexValCheck(str[off + 2]);
+ off += 3;
+ return (x << 4) + y;
+ }
+
+ static int ReadHexUTF(string str, ref int off)
+ {
+ int x = ReadHexEscape(str, ref off);
+ if (x < 0x80 || x >= 0xC0) {
+ throw new AsnException(
+ "Invalid hex escape: not UTF-8");
+ }
+ return x;
+ }
+
+ static string UnEscapeUTF8(string str)
+ {
+ StringBuilder sb = new StringBuilder();
+ int n = str.Length;
+ int k = 0;
+ while (k < n) {
+ char c = str[k];
+ if (c != '\\') {
+ sb.Append(c);
+ k ++;
+ continue;
+ }
+ int x = ReadHexEscape(str, ref k);
+ if (x < 0) {
+ sb.Append(str[k + 1]);
+ k += 2;
+ continue;
+ }
+ if (x < 0x80) {
+ // nothing
+ } else if (x < 0xC0) {
+ throw new AsnException(
+ "Invalid hex escape: not UTF-8");
+ } else if (x < 0xE0) {
+ x &= 0x1F;
+ x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+ } else if (x < 0xF0) {
+ x &= 0x0F;
+ x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+ x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+ } else if (x < 0xF8) {
+ x &= 0x07;
+ x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+ x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+ x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+ if (x > 0x10FFFF) {
+ throw new AsnException("Invalid"
+ + " hex escape: out of range");
+ }
+ } else {
+ throw new AsnException(
+ "Invalid hex escape: not UTF-8");
+ }
+ if (x < 0x10000) {
+ sb.Append((char)x);
+ } else {
+ x -= 0x10000;
+ sb.Append((char)(0xD800 + (x >> 10)));
+ sb.Append((char)(0xDC00 + (x & 0x3FF)));
+ }
+ }
+ return sb.ToString();
+ }
+
+ internal static DNPart Parse(string str)
+ {
+ int j = str.IndexOf('=');
+ if (j < 0) {
+ throw new AsnException("Invalid DN: no '=' sign");
+ }
+ string a = str.Substring(0, j).Trim();
+ string b = str.Substring(j + 1).Trim();
+ string oid;
+ if (!FT_TO_OID.TryGetValue(a, out oid)) {
+ oid = AsnElt.MakeOID(oid).GetOID();
+ }
+ AsnElt aVal;
+ if (b.StartsWith("#")) {
+ MemoryStream ms = new MemoryStream();
+ int n = b.Length;
+ for (int k = 1; k < n; k += 2) {
+ int x = HexValCheck(b[k]);
+ if (k + 1 >= n) {
+ throw new AsnException(
+ "Odd number of hex digits");
+ }
+ x = (x << 4) + HexValCheck(b[k + 1]);
+ ms.WriteByte((byte)x);
+ }
+ try {
+ aVal = AsnElt.Decode(ms.ToArray());
+ } catch (Exception e) {
+ throw new AsnException("Bad DN value: "
+ + e.Message);
+ }
+ } else {
+ b = UnEscapeUTF8(b);
+ int type = AsnElt.PrintableString;
+ foreach (char c in b) {
+ if (!AsnElt.IsPrintable(c)) {
+ type = AsnElt.UTF8String;
+ break;
+ }
+ }
+ aVal = AsnElt.MakeString(type, b);
+ }
+ return new DNPart(oid, aVal);
+ }
+
+ static Dictionary<string, string> OID_TO_FT;
+ static Dictionary<string, string> FT_TO_OID;
+
+ static void AddFT(string oid, string ft)
+ {
+ OID_TO_FT[oid] = ft;
+ FT_TO_OID[ft] = oid;
+ }
+
+ static DNPart()
+ {
+ OID_TO_FT = new Dictionary<string, string>();
+ FT_TO_OID = new Dictionary<string, string>(
+ StringComparer.OrdinalIgnoreCase);
+ AddFT("2.5.4.3", COMMON_NAME);
+ AddFT("2.5.4.7", LOCALITY);
+ AddFT("2.5.4.8", STATE);
+ AddFT("2.5.4.10", ORGANIZATION);
+ AddFT("2.5.4.11", ORGANIZATIONAL_UNIT);
+ AddFT("2.5.4.6", COUNTRY);
+ AddFT("2.5.4.9", STREET);
+ AddFT("0.9.2342.19200300.100.1.25", DOMAIN_COMPONENT);
+ AddFT("0.9.2342.19200300.100.1.1", USER_ID);
+ AddFT("1.2.840.113549.1.9.1", EMAIL_ADDRESS);
+
+ /*
+ * We also accept 'S' as an alias for 'ST' because some
+ * Microsoft software uses it.
+ */
+ FT_TO_OID["S"] = FT_TO_OID["ST"];
+ }
+
+ /*
+ * Tell whether a given character is a "control character" (to
+ * be ignored for DN comparison purposes). This follows RFC 4518
+ * but only for code points in the first plane.
+ */
+ static bool IsControl(char c)
+ {
+ if (c <= 0x0008
+ || (c >= 0x000E && c <= 0x001F)
+ || (c >= 0x007F && c <= 0x0084)
+ || (c >= 0x0086 && c <= 0x009F)
+ || c == 0x06DD
+ || c == 0x070F
+ || c == 0x180E
+ || (c >= 0x200C && c <= 0x200F)
+ || (c >= 0x202A && c <= 0x202E)
+ || (c >= 0x2060 && c <= 0x2063)
+ || (c >= 0x206A && c <= 0x206F)
+ || c == 0xFEFF
+ || (c >= 0xFFF9 && c <= 0xFFFB))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * Tell whether a character is whitespace. This follows
+ * rules of RFC 4518.
+ */
+ static bool IsWS(char c)
+ {
+ if (c == 0x0020
+ || c == 0x00A0
+ || c == 0x1680
+ || (c >= 0x2000 && c <= 0x200A)
+ || c == 0x2028
+ || c == 0x2029
+ || c == 0x202F
+ || c == 0x205F
+ || c == 0x3000)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as DNPart);
+ }
+
+ public bool Equals(DNPart dnp)
+ {
+ if (dnp == null) {
+ return false;
+ }
+ if (OID != dnp.OID) {
+ return false;
+ }
+ if (IsString) {
+ return dnp.IsString
+ && normValue == dnp.normValue;
+ } else if (dnp.IsString) {
+ return false;
+ } else {
+ return Eq(encodedValue, dnp.encodedValue);
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return hashCode;
+ }
+
+ static bool Eq(byte[] a, byte[] b)
+ {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ int n = a.Length;
+ if (n != b.Length) {
+ return false;
+ }
+ for (int i = 0; i < n; i ++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Asn1;
+
+namespace X500 {
+
+/*
+ * An X.500 name is a "distinguished name", which is an ordered sequence
+ * of RDN (relative distinguished names). Each RDN is an unordered set
+ * of name elements. A name element is an arbitrary value with an
+ * identifying OID; some (most) name element values are character
+ * strings.
+ *
+ * X.500 names are used primarily to identify certificate owner entities
+ * (subject and issuer name in a certificate), and to serve as
+ * hierarchical indexing key for values in LDAP.
+ *
+ * An X.500 name is encoded and decoded using ASN.1. They can also be
+ * converted to a string representation. The string representation does
+ * not conserve all encoding details, so encoding+parsing will not
+ * necessarily restore the exact same binary DN.
+ */
+
+public class X500Name {
+
+ /*
+ * Get the individual name elements, in a "flattened" structure
+ * (if a SET in a RDN contains multiple values, then they are
+ * stored consecutively in that array).
+ *
+ * The returned array MUST NOT be modified.
+ */
+ public DNPart[] Parts {
+ get {
+ return Parts_;
+ }
+ }
+ DNPart[] Parts_;
+
+ /*
+ * Get the individual name elements. Each internal array contains
+ * the name elements found in the SET for a specific RDN.
+ */
+ public DNPart[][] PartsGen {
+ get {
+ return PartsGen_;
+ }
+ }
+ DNPart[][] PartsGen_;
+
+ /*
+ * Check whether this DN is empty.
+ */
+ public bool IsEmpty {
+ get {
+ return Parts_.Length == 0;
+ }
+ }
+
+ int hashCode;
+
+ /*
+ * Constructor for parsing.
+ */
+ X500Name(List<List<DNPart>> dn)
+ {
+ Init(dn);
+ }
+
+ void Init(List<List<DNPart>> dn)
+ {
+ int n = dn.Count;
+ List<DNPart> r = new List<DNPart>();
+ PartsGen_ = new DNPart[n][];
+ for (int i = 0; i < n; i ++) {
+ IDictionary<string, DNPart> dd =
+ new SortedDictionary<string, DNPart>(
+ StringComparer.Ordinal);
+ foreach (DNPart dnp in dn[i]) {
+ string nt = dnp.OID;
+ if (dd.ContainsKey(nt)) {
+ throw new AsnException(string.Format(
+ "multiple values of type {0}"
+ + " in RDN", nt));
+ }
+ dd[nt] = dnp;
+ }
+ PartsGen_[i] = new DNPart[dd.Count];
+ int j = 0;
+ foreach (DNPart p in dd.Values) {
+ PartsGen_[i][j ++] = p;
+ r.Add(p);
+ }
+ }
+ Parts_ = r.ToArray();
+
+ uint hc = 0;
+ foreach (DNPart dnp in r) {
+ hc = ((hc << 7) | (hc >> 25)) + (uint)dnp.GetHashCode();
+ }
+ hashCode = (int)hc;
+ }
+
+ /*
+ * Simplified parsing: this constructor checks that every SET
+ * in the sequence of RDN has size exactly 1, and decodes each
+ * name element as a "generic string".
+ *
+ * On decoding error, an AsnException is thrown.
+ */
+ public X500Name(AsnElt aDN) : this(aDN, true)
+ {
+ }
+
+ /*
+ * Generic parsing. If 'strictStrings' is true, then the following
+ * rules are enforced:
+ * -- Every SET in the sequence of RDN must have size 1.
+ * -- Every name element is decoded as a string (by tag).
+ *
+ * If 'strictStrings' is false, then multiple elements may appear
+ * in each SET, and values needs not be decodable as string (values
+ * with a known OID must still be decodable).
+ *
+ * This constructor checks that within a single RDN, no two
+ * attributes may have the same type.
+ *
+ * On decoding error, an AsnException is thrown.
+ */
+ public X500Name(AsnElt aDN, bool strictStrings)
+ {
+ /*
+ * Note: the SEQUENCE tag MUST be present, since the
+ * ASN.1 definition of Name starts with a CHOICE; thus,
+ * any tag override would have to be explicit, not
+ * implicit.
+ */
+ aDN.CheckConstructed();
+ aDN.CheckTag(AsnElt.SEQUENCE);
+ List<List<DNPart>> r = new List<List<DNPart>>();
+ foreach (AsnElt aRDN in aDN.Sub) {
+ aRDN.CheckConstructed();
+ aRDN.CheckTag(AsnElt.SET);
+ aRDN.CheckNumSubMin(1);
+ int n = aRDN.Sub.Length;
+ if (n != 1 && strictStrings) {
+ throw new AsnException(String.Format(
+ "several ({0}) values in RDN", n));
+ }
+ List<DNPart> r2 = new List<DNPart>();
+ r.Add(r2);
+ for (int i = 0; i < n; i ++) {
+ AsnElt aTV = aRDN.Sub[i];
+ aTV.CheckConstructed();
+ aTV.CheckTag(AsnElt.SEQUENCE);
+ aTV.CheckNumSub(2);
+ AsnElt aOID = aTV.GetSub(0);
+ aOID.CheckTag(AsnElt.OBJECT_IDENTIFIER);
+ AsnElt aVal = aTV.GetSub(1);
+ string nt = aOID.GetOID();
+ DNPart dnp = new DNPart(nt, aVal);
+ if (strictStrings && !dnp.IsString) {
+ throw new AsnException(
+ "RDN is not a string");
+ }
+ r2.Add(dnp);
+ }
+ }
+ Init(r);
+ }
+
+ /*
+ * Encode this DN into a string as specified in RFC 4514.
+ */
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = PartsGen_.Length - 1; i >= 0; i --) {
+ DNPart[] dd = PartsGen_[i];
+ for (int j = 0; j < dd.Length; j ++) {
+ if (j > 0) {
+ sb.Append("+");
+ } else if (sb.Length > 0) {
+ sb.Append(",");
+ }
+ sb.Append(dd[j].ToString());
+ }
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Encode back this DN into an ASN.1 structure.
+ */
+ public AsnElt ToAsn1()
+ {
+ AsnElt[] t1 = new AsnElt[PartsGen_.Length];
+ for (int i = 0; i < PartsGen_.Length; i ++) {
+ DNPart[] dp = PartsGen_[i];
+ AsnElt[] t2 = new AsnElt[dp.Length];
+ for (int j = 0; j < dp.Length; j ++) {
+ t2[j] = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeOID(dp[j].OID),
+ dp[j].AsnValue);
+ }
+ t1[i] = AsnElt.MakeSetOf(t2);
+ }
+ return AsnElt.Make(AsnElt.SEQUENCE, t1);
+ }
+
+ /*
+ * Parse a string into a DN. The input is expected to use
+ * RFC 4514 format. Name elements that are provided as
+ * character strings will be mapped to ASN.1 PrintableString
+ * values (if they are compatible with that string type)
+ * or UTF8String values (otherwise).
+ *
+ * On parse error, an AsnException is thrown.
+ */
+ public static X500Name Parse(string str)
+ {
+ int n = str.Length;
+ int p = 0;
+ bool acc = false;
+ List<List<DNPart>> dn = new List<List<DNPart>>();
+ while (p < n) {
+ /*
+ * Find the next unescaped '+' or ',' sign.
+ */
+ bool lcwb = false;
+ int q;
+ for (q = p; q < n; q ++) {
+ if (lcwb) {
+ lcwb = false;
+ continue;
+ }
+ switch (str[q]) {
+ case ',':
+ case '+':
+ goto found;
+ case '\\':
+ lcwb = true;
+ break;
+ }
+ }
+ found:
+
+ /*
+ * Parse DN element.
+ */
+ DNPart dnp = DNPart.Parse(str.Substring(p, q - p));
+ if (acc) {
+ dn[dn.Count - 1].Add(dnp);
+ } else {
+ List<DNPart> r = new List<DNPart>();
+ r.Add(dnp);
+ dn.Add(r);
+ }
+
+ p = q + 1;
+ acc = q < n && str[q] == '+';
+ }
+
+ dn.Reverse();
+ return new X500Name(dn);
+ }
+
+ /*
+ * Compare two DN for equality. "null" is equal to "null" but
+ * to nothing else.
+ */
+ public static bool Equals(X500Name dn1, X500Name dn2)
+ {
+ if (dn1 == null) {
+ return dn2 == null;
+ } else {
+ return dn1.Equals(dn2);
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as X500Name);
+ }
+
+ public bool Equals(X500Name dn)
+ {
+ if (dn == null) {
+ return false;
+ }
+ int n = PartsGen.Length;
+ if (dn.PartsGen.Length != n) {
+ return false;
+ }
+ for (int i = 0; i < n; i ++) {
+ DNPart[] p1 = PartsGen[i];
+ DNPart[] p2 = dn.PartsGen[i];
+ int k = p1.Length;
+ if (k != p2.Length) {
+ return false;
+ }
+ for (int j = 0; j < k; j ++) {
+ if (!p1[j].Equals(p2[j])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ return hashCode;
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+using Asn1;
+
+/*
+ * A wrapper class for an AlgorithmIdentifier (SEQUENCE of an OID
+ * then optional parameters).
+ */
+
+class AlgorithmIdentifier {
+
+ /*
+ * Get the OID that identifies the algorithm.
+ */
+ internal string OID {
+ get {
+ return oid;
+ }
+ }
+
+ /*
+ * Get the algorithm parameters. This may be null if the
+ * structure did not contain parameters.
+ */
+ internal AsnElt Parameters {
+ get {
+ return parameters;
+ }
+ }
+
+ string oid;
+ AsnElt parameters;
+
+ /*
+ * Create an instance over the provided ASN.1 element. The
+ * outer tag will be checked to match the universal tag for
+ * SEQUENCE.
+ */
+ internal AlgorithmIdentifier(AsnElt ai) : this(ai, true)
+ {
+ }
+
+ /*
+ * Create an instance over the provided ASN.1 element. If
+ * 'checkTag' is true, then the outer tag will be checked to
+ * match the universal tag for SEQUENCE. Set 'checkTag' to
+ * false if the tag was already checked, or if it has been
+ * overwritten with an implicit tag.
+ */
+ internal AlgorithmIdentifier(AsnElt ai, bool checkTag)
+ {
+ if (checkTag) {
+ ai.CheckTag(AsnElt.SEQUENCE);
+ }
+ ai.CheckNumSubMin(1);
+ ai.CheckNumSubMax(2);
+ AsnElt ao = ai.GetSub(0);
+ ao.CheckTag(AsnElt.OBJECT_IDENTIFIER);
+ oid = ao.GetOID();
+ if (ai.Sub.Length >= 2) {
+ parameters = ai.GetSub(1);
+ } else {
+ parameters = null;
+ }
+ }
+
+ /*
+ * Create a new instance for a given OID, with no parameters.
+ */
+ internal AlgorithmIdentifier(string oid) : this(oid, null)
+ {
+ }
+
+ /*
+ * Create a new instance for a given OID, with the provided
+ * parameters (which may be null).
+ */
+ internal AlgorithmIdentifier(string oid, AsnElt parameters)
+ {
+ this.oid = oid;
+ this.parameters = parameters;
+ }
+
+ /*
+ * Encode this instance as a new ASN.1 object.
+ */
+ internal AsnElt ToAsn1()
+ {
+ AsnElt ao = AsnElt.MakeOID(oid);
+ if (parameters == null) {
+ return AsnElt.Make(AsnElt.SEQUENCE, ao);
+ } else {
+ return AsnElt.Make(AsnElt.SEQUENCE, ao, parameters);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+using System.Text;
+
+using Asn1;
+using Crypto;
+
+namespace XKeys {
+
+/*
+ * The KF class contains static methods to decode and encode algorithm
+ * parameters, public keys and private keys.
+ */
+
+public class KF {
+
+ const string OID_RSA = "1.2.840.113549.1.1.1";
+ const string OID_RSA_OAEP = "1.2.840.113549.1.1.7";
+ const string OID_RSA_PSS = "1.2.840.113549.1.1.10";
+ const string OID_DSA = "1.2.840.10040.4.1";
+ const string OID_EC = "1.2.840.10045.2.1";
+
+ /*
+ * Encode the private key. If 'pk8' is true, then this uses
+ * PKCS#8 format (unencrypted); otherwise, it uses the
+ * "internal" format that does not specifically identify the key
+ * type.
+ */
+ public static byte[] EncodePrivateKey(IPrivateKey sk, bool pk8)
+ {
+ RSAPrivateKey rk = sk as RSAPrivateKey;
+ /* disabled DSA
+ DSAPrivateKey dk = sk as DSAPrivateKey;
+ */
+ ECPrivateKey ek = sk as ECPrivateKey;
+ if (rk != null) {
+ AsnElt ark = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(0),
+ AsnElt.MakeInteger(rk.N),
+ AsnElt.MakeInteger(rk.E),
+ AsnElt.MakeInteger(rk.D),
+ AsnElt.MakeInteger(rk.P),
+ AsnElt.MakeInteger(rk.Q),
+ AsnElt.MakeInteger(rk.DP),
+ AsnElt.MakeInteger(rk.DQ),
+ AsnElt.MakeInteger(rk.IQ));
+ byte[] enc = ark.Encode();
+ if (pk8) {
+ AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(0),
+ AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeOID(OID_RSA),
+ AsnElt.NULL_V),
+ AsnElt.MakeBlob(enc));
+ enc = apk8.Encode();
+ }
+ return enc;
+ /* disabled DSA
+ } else if (dk != null) {
+ if (pk8) {
+ AsnElt adx = AsnElt.MakeInteger(dk.X);
+ AsnElt adp = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(dk.P),
+ AsnElt.MakeInteger(dk.Q),
+ AsnElt.MakeInteger(dk.G));
+ AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(0),
+ AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeOID(OID_DSA),
+ adp),
+ AsnElt.MakeBlob(adx.Encode()));
+ return apk8.Encode();
+ } else {
+ AsnElt adk = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(0),
+ AsnElt.MakeInteger(dk.P),
+ AsnElt.MakeInteger(dk.Q),
+ AsnElt.MakeInteger(dk.G),
+ AsnElt.MakeInteger(dk.PublicKey.Y),
+ AsnElt.MakeInteger(dk.X));
+ return adk.Encode();
+ }
+ */
+ } else if (ek != null) {
+ /*
+ * The ECPrivateKey class guarantees that the
+ * private key X is already encoded with the same
+ * length as the subgroup order.
+ * The ECPublicKey class provides the public key
+ * as an already encoded point.
+ */
+ AsnElt acc = AsnElt.MakeOID(CurveToOID(ek.Curve));
+ AsnElt apv = AsnElt.MakeExplicit(AsnElt.CONTEXT, 1,
+ AsnElt.MakeBitString(ek.PublicKey.Pub));
+ if (pk8) {
+ AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(1),
+ AsnElt.MakeBlob(ek.X),
+ apv);
+ AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(0),
+ AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeOID(OID_EC),
+ acc),
+ AsnElt.MakeBlob(aek.Encode()));
+ return apk8.Encode();
+ } else {
+ AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(1),
+ AsnElt.MakeBlob(ek.X),
+ AsnElt.MakeExplicit(
+ AsnElt.CONTEXT, 0, acc),
+ apv);
+ return aek.Encode();
+ }
+ } else {
+ if (sk == null) {
+ throw new NullReferenceException();
+ }
+ throw new ArgumentException("Cannot encode "
+ + sk.AlgorithmName + " private key");
+ }
+ }
+
+ /*
+ * Encode the private key into a PEM object. If 'pk8' is true,
+ * then unencrypted PKCS#8 format is used, and the PEM header
+ * is "BEGIN PRIVATE KEY"; otherwise, the "internal" private key
+ * format is used, and the PEM header identifies the key type.
+ *
+ * If 'crlf' is true, then PEM lines end with CR+LF; otherwise,
+ * they end with LF only.
+ */
+ public static string EncodePrivateKeyPEM(IPrivateKey sk,
+ bool pk8, bool crlf)
+ {
+ byte[] enc = EncodePrivateKey(sk, pk8);
+ string objType;
+ if (pk8) {
+ objType = "PRIVATE KEY";
+ } else {
+ objType = sk.AlgorithmName + " PRIVATE KEY";
+ }
+ return ToPEM(objType, enc, crlf ? "\r\n" : "\n");
+ }
+
+ /*
+ * Decode the provided private key. This method accepts both
+ * PKCS#8 and the "internal" format; the source object may be
+ * raw DER, Base64-encoded DER, or PEM. The key type is
+ * automatically detected.
+ */
+ public static IPrivateKey DecodePrivateKey(byte[] enc)
+ {
+ string pemType;
+ enc = AsnIO.FindBER(enc, false, out pemType);
+ if (enc == null) {
+ throw new AsnException("Not an encoded object");
+ }
+ AsnElt ak = AsnElt.Decode(enc);
+ ak.CheckConstructed();
+ if (pemType != null) {
+ switch (pemType) {
+ case "RSA PRIVATE KEY":
+ return DecodePrivateKeyRSA(ak);
+ /* disabled DSA
+ case "DSA PRIVATE KEY":
+ return DecodePrivateKeyDSA(ak);
+ */
+ case "EC PRIVATE KEY":
+ return DecodePrivateKeyEC(ak);
+ case "PRIVATE KEY":
+ return DecodePrivateKeyPKCS8(ak);
+ default:
+ throw new AsnException(
+ "Unknown PEM object: " + pemType);
+ }
+ }
+ if (ak.Sub.Length == 3
+ && ak.GetSub(0).TagValue == AsnElt.INTEGER
+ && ak.GetSub(1).TagValue == AsnElt.SEQUENCE
+ && ak.GetSub(2).TagValue == AsnElt.OCTET_STRING)
+ {
+ return DecodePrivateKeyPKCS8(ak);
+ }
+ if (ak.Sub.Length >= 9) {
+ bool mayBeRSA = true;
+ for (int i = 0; i < 9; i ++) {
+ if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
+ mayBeRSA = false;
+ break;
+ }
+ }
+ if (mayBeRSA) {
+ return DecodePrivateKeyRSA(ak);
+ }
+ }
+ /* disabled DSA
+ if (ak.Sub.Length >= 6) {
+ bool mayBeDSA = true;
+ for (int i = 0; i < 6; i ++) {
+ if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
+ mayBeDSA = false;
+ break;
+ }
+ }
+ if (mayBeDSA) {
+ return DecodePrivateKeyDSA(ak);
+ }
+ }
+ */
+ if (ak.Sub.Length >= 2
+ && ak.GetSub(0).TagValue == AsnElt.INTEGER
+ && ak.GetSub(1).TagValue == AsnElt.OCTET_STRING)
+ {
+ return DecodePrivateKeyEC(ak);
+ }
+ throw new AsnException("Unrecognized private key format");
+ }
+
+ static RSAPrivateKey DecodePrivateKeyRSA(AsnElt ak)
+ {
+ ak.CheckNumSubMin(9);
+ ak.GetSub(0).CheckTag(AsnElt.INTEGER);
+ long kt = ak.GetSub(0).GetInteger();
+ if (kt != 0) {
+ throw new AsnException(
+ "Unsupported RSA key type: " + kt);
+ }
+ ak.CheckNumSub(9);
+ return new RSAPrivateKey(
+ GetPositiveInteger(ak.GetSub(1)),
+ GetPositiveInteger(ak.GetSub(2)),
+ GetPositiveInteger(ak.GetSub(3)),
+ GetPositiveInteger(ak.GetSub(4)),
+ GetPositiveInteger(ak.GetSub(5)),
+ GetPositiveInteger(ak.GetSub(6)),
+ GetPositiveInteger(ak.GetSub(7)),
+ GetPositiveInteger(ak.GetSub(8)));
+ }
+
+ /* disabled DSA
+ static DSAPrivateKey DecodePrivateKeyDSA(AsnElt ak)
+ {
+ ak.CheckNumSubMin(6);
+ for (int i = 0; i < 6; i ++) {
+ ak.GetSub(i).CheckTag(AsnElt.INTEGER);
+ }
+ long kt = ak.GetSub(0).GetInteger();
+ if (kt != 0) {
+ throw new AsnException(
+ "Unsupported DSA key type: " + kt);
+ }
+ DSAPrivateKey dsk = new DSAPrivateKey(
+ GetPositiveInteger(ak.GetSub(1)),
+ GetPositiveInteger(ak.GetSub(2)),
+ GetPositiveInteger(ak.GetSub(3)),
+ GetPositiveInteger(ak.GetSub(5)));
+ DSAPublicKey dpk = dsk.PublicKey;
+ if (BigInt.Compare(dpk.Y,
+ GetPositiveInteger(ak.GetSub(4))) != 0)
+ {
+ throw new CryptoException(
+ "DSA key pair public/private mismatch");
+ }
+ return dsk;
+ }
+ */
+
+ static ECPrivateKey DecodePrivateKeyEC(AsnElt ak)
+ {
+ return DecodePrivateKeyEC(ak, null);
+ }
+
+ static ECPrivateKey DecodePrivateKeyEC(AsnElt ak, ECCurve curve)
+ {
+ ak.CheckNumSubMin(2);
+ ak.GetSub(0).CheckTag(AsnElt.INTEGER);
+ ak.GetSub(1).CheckTag(AsnElt.OCTET_STRING);
+ long kt = ak.GetSub(0).GetInteger();
+ if (kt != 1) {
+ throw new AsnException(
+ "Unsupported EC key type: " + kt);
+ }
+ byte[] x = ak.GetSub(1).CopyValue();
+ byte[] pub = null;
+ int n = ak.Sub.Length;
+ int p = 2;
+ if (p < n) {
+ AsnElt acc = ak.GetSub(p);
+ if (acc.TagClass == AsnElt.CONTEXT
+ && acc.TagValue == 0)
+ {
+ acc.CheckNumSub(1);
+ acc = acc.GetSub(0);
+ ECCurve curve2 = DecodeCurve(acc);
+
+ /*
+ * Here, we support only named curves.
+ */
+ /* obsolete
+ */
+ if (curve == null) {
+ curve = curve2;
+ } else if (!curve.Equals(curve2)) {
+ throw new AsnException(string.Format(
+ "Inconsistent curve"
+ + " specification ({0} / {1})",
+ curve.Name, curve2.Name));
+ }
+
+ p ++;
+ }
+ }
+ if (p < n) {
+ AsnElt acc = ak.GetSub(p);
+ if (acc.TagClass == AsnElt.CONTEXT
+ && acc.TagValue == 1)
+ {
+ acc.CheckNumSub(1);
+ acc = acc.GetSub(0);
+ acc.CheckTag(AsnElt.BIT_STRING);
+ pub = acc.GetBitString();
+ }
+ }
+
+ if (curve == null) {
+ throw new AsnException("No curve specified for EC key");
+ }
+ ECPrivateKey esk = new ECPrivateKey(curve, x);
+ if (pub != null) {
+ ECPublicKey epk = new ECPublicKey(curve, pub);
+ if (!epk.Equals(esk.PublicKey)) {
+ throw new CryptoException(
+ "EC key pair public/private mismatch");
+ }
+ }
+ return esk;
+ }
+
+ static ECCurve DecodeCurve(AsnElt acc)
+ {
+ /*
+ * We support only named curves for now. PKIX does not
+ * want to see any other kind of curve anyway (see RFC
+ * 5480).
+ */
+ acc.CheckTag(AsnElt.OBJECT_IDENTIFIER);
+ string oid = acc.GetOID();
+ return OIDToCurve(oid);
+ }
+
+ static IPrivateKey DecodePrivateKeyPKCS8(AsnElt ak)
+ {
+ ak.CheckNumSub(3);
+ ak.GetSub(0).CheckTag(AsnElt.INTEGER);
+ long v = ak.GetSub(0).GetInteger();
+ if (v != 0) {
+ throw new AsnException(
+ "Unsupported PKCS#8 version: " + v);
+ }
+ AsnElt aai = ak.GetSub(1);
+ aai.CheckTag(AsnElt.SEQUENCE);
+ aai.CheckNumSubMin(1);
+ aai.CheckNumSubMin(2);
+ aai.GetSub(0).CheckTag(AsnElt.OBJECT_IDENTIFIER);
+ string oid = aai.GetSub(0).GetOID();
+ ak.GetSub(2).CheckTag(AsnElt.OCTET_STRING);
+ byte[] rawKey = ak.GetSub(2).CopyValue();
+ AsnElt ark = AsnElt.Decode(rawKey);
+
+ switch (oid) {
+ case OID_RSA:
+ case OID_RSA_OAEP:
+ case OID_RSA_PSS:
+ return DecodePrivateKeyRSA(ark);
+ /* disabled DSA
+ case OID_DSA:
+ return DecodePrivateKeyDSA(ark);
+ */
+ case OID_EC:
+ /*
+ * For elliptic curves, the parameters may
+ * include the curve specification.
+ */
+ ECCurve curve = (aai.Sub.Length == 2)
+ ? DecodeCurve(aai.GetSub(1)) : null;
+ return DecodePrivateKeyEC(ark, curve);
+ default:
+ throw new AsnException(
+ "Unknown PKCS#8 key type: " + oid);
+ }
+ }
+
+ /*
+ * Encode a public key as a SubjectPublicKeyInfo structure.
+ */
+ public static AsnElt EncodePublicKey(IPublicKey pk)
+ {
+ RSAPublicKey rk = pk as RSAPublicKey;
+ /* disabled DSA
+ DSAPublicKey dk = pk as DSAPublicKey;
+ */
+ ECPublicKey ek = pk as ECPublicKey;
+ string oid;
+ AsnElt app;
+ byte[] pkv;
+ if (rk != null) {
+ oid = OID_RSA;
+ app = AsnElt.NULL_V;
+ pkv = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(rk.Modulus),
+ AsnElt.MakeInteger(rk.Exponent)).Encode();
+ /* disabled DSA
+ } else if (dk != null) {
+ oid = OID_DSA;
+ app = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeInteger(dk.P),
+ AsnElt.MakeInteger(dk.Q),
+ AsnElt.MakeInteger(dk.G));
+ pkv = AsnElt.MakeInteger(dk.Y).Encode();
+ */
+ } else if (ek != null) {
+ oid = OID_EC;
+ app = AsnElt.MakeOID(CurveToOID(ek.Curve));
+ pkv = ek.Pub;
+ } else {
+ throw new ArgumentException(
+ "Cannot encode key type: " + pk.AlgorithmName);
+ }
+ AsnElt ai;
+ if (app == null) {
+ ai = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeOID(oid));
+ } else {
+ ai = AsnElt.Make(AsnElt.SEQUENCE,
+ AsnElt.MakeOID(oid),
+ app);
+ }
+ return AsnElt.Make(AsnElt.SEQUENCE,
+ ai,
+ AsnElt.MakeBitString(pkv));
+ }
+
+ /*
+ * Decode a public key (SubjectPublicKeyInfo).
+ */
+ public static IPublicKey DecodePublicKey(byte[] spki)
+ {
+ string pemType = null;
+ spki = AsnIO.FindBER(spki, false, out pemType);
+ if (spki == null) {
+ throw new AsnException("Not an encoded object");
+ }
+ return DecodePublicKey(AsnElt.Decode(spki));
+ }
+
+ /*
+ * Decode a public key (SubjectPublicKeyInfo).
+ */
+ public static IPublicKey DecodePublicKey(AsnElt ak)
+ {
+ ak.CheckNumSub(2);
+ AlgorithmIdentifier ai = new AlgorithmIdentifier(ak.GetSub(0));
+ AsnElt abs = ak.GetSub(1);
+ abs.CheckTag(AsnElt.BIT_STRING);
+ byte[] pub = abs.GetBitString();
+ switch (ai.OID) {
+ case OID_RSA:
+ case OID_RSA_OAEP:
+ case OID_RSA_PSS:
+ return DecodePublicKeyRSA(pub);
+ /* disabled DSA
+ case OID_DSA:
+ return DecodePublicKeyDSA(pub);
+ */
+ case OID_EC:
+ /*
+ * For elliptic curves, the parameters should
+ * include the curve specification.
+ */
+ AsnElt ap = ai.Parameters;
+ if (ap == null) {
+ throw new AsnException("No curve specified"
+ + " for EC public key");
+ }
+ if (ap.TagClass != AsnElt.UNIVERSAL
+ || ap.TagValue != AsnElt.OBJECT_IDENTIFIER)
+ {
+ throw new AsnException("Unsupported type"
+ + " of curve specification");
+ }
+ return new ECPublicKey(OIDToCurve(ap.GetOID()), pub);
+ default:
+ throw new AsnException(
+ "Unknown public key type: " + ai.OID);
+ }
+ }
+
+ static IPublicKey DecodePublicKeyRSA(byte[] pub)
+ {
+ AsnElt ae = AsnElt.Decode(pub);
+ ae.CheckTag(AsnElt.SEQUENCE);
+ ae.CheckNumSub(2);
+ byte[] n = GetPositiveInteger(ae.GetSub(0));
+ byte[] e = GetPositiveInteger(ae.GetSub(1));
+ return new RSAPublicKey(n, e);
+ }
+
+ static string CurveToOID(ECCurve curve)
+ {
+ switch (curve.Name) {
+ case "P-192":
+ return "1.2.840.10045.3.1.1";
+ case "P-224":
+ return "1.3.132.0.33";
+ case "P-256":
+ return "1.2.840.10045.3.1.7";
+ case "P-384":
+ return "1.3.132.0.34";
+ case "P-521":
+ return "1.3.132.0.35";
+ }
+ throw new ArgumentException(string.Format(
+ "No known OID for curve '{0}'", curve.Name));
+ }
+
+ static ECCurve OIDToCurve(string oid)
+ {
+ switch (oid) {
+ /*
+ case "1.2.840.10045.3.1.1":
+ return NIST.P192;
+ case "1.3.132.0.33":
+ return NIST.P224;
+ */
+ case "1.2.840.10045.3.1.7":
+ return NIST.P256;
+ case "1.3.132.0.34":
+ return NIST.P384;
+ case "1.3.132.0.35":
+ return NIST.P521;
+ }
+ throw new ArgumentException(string.Format(
+ "No known curve for OID {0}", oid));
+ }
+
+ static byte[] GetPositiveInteger(AsnElt ae)
+ {
+ ae.CheckTag(AsnElt.INTEGER);
+ byte[] x = ae.CopyValue();
+ if (x.Length == 0) {
+ throw new AsnException("Invalid integer (empty)");
+ }
+ if (x[0] >= 0x80) {
+ throw new AsnException("Invalid integer (negative)");
+ }
+ return x;
+ }
+
+ static int Dec16be(byte[] buf, int off)
+ {
+ return (buf[off] << 8) + buf[off + 1];
+ }
+
+ static int Dec24be(byte[] buf, int off)
+ {
+ return (buf[off] << 16) + (buf[off + 1] << 8) + buf[off + 2];
+ }
+
+ const string B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ public static string ToBase64(byte[] buf, int off, int len)
+ {
+ char[] tc = new char[((len + 2) / 3) << 2];
+ for (int i = 0, j = 0; i < len; i += 3) {
+ if ((i + 3) <= len) {
+ int x = Dec24be(buf, off + i);
+ tc[j ++] = B64[x >> 18];
+ tc[j ++] = B64[(x >> 12) & 0x3F];
+ tc[j ++] = B64[(x >> 6) & 0x3F];
+ tc[j ++] = B64[x & 0x3F];
+ } else if ((i + 2) == len) {
+ int x = Dec16be(buf, off + i);
+ tc[j ++] = B64[x >> 10];
+ tc[j ++] = B64[(x >> 4) & 0x3F];
+ tc[j ++] = B64[(x << 2) & 0x3F];
+ tc[j ++] = '=';
+ } else if ((i + 1) == len) {
+ int x = buf[off + i];
+ tc[j ++] = B64[(x >> 2) & 0x3F];
+ tc[j ++] = B64[(x << 4) & 0x3F];
+ tc[j ++] = '=';
+ tc[j ++] = '=';
+ }
+ }
+ return new string(tc);
+ }
+
+ public static void WritePEM(TextWriter w, string objType, byte[] buf)
+ {
+ w.WriteLine("-----BEGIN {0}-----", objType.ToUpperInvariant());
+ int n = buf.Length;
+ for (int i = 0; i < n; i += 57) {
+ int len = Math.Min(57, n - i);
+ w.WriteLine(ToBase64(buf, i, len));
+ }
+ w.WriteLine("-----END {0}-----", objType.ToUpperInvariant());
+ }
+
+ public static string ToPEM(string objType, byte[] buf)
+ {
+ return ToPEM(objType, buf, "\n");
+ }
+
+ public static string ToPEM(string objType, byte[] buf, string nl)
+ {
+ StringWriter w = new StringWriter();
+ w.NewLine = nl;
+ WritePEM(w, objType, buf);
+ return w.ToString();
+ }
+}
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+/*
+ * A custom "big integer" implementation. It internally uses an array
+ * of 32-bit integers, that encode the integer in little-endian convention,
+ * using two's complement for negative integers.
+ *
+ * Apart from the array, a single 32-bit field is also present, which
+ * encodes the sign. When the value is small (it fits on 32 bits), then
+ * the array pointer is null, and the value is in the 32-bit field.
+ * Since ZInt is a struct, this means that computations using ZInt do
+ * not entail any dynamic (GC-based) memory allocation as long as the
+ * value fits on 32 bits. This makes it substantially faster than usual
+ * "big integer" implementations (including .NET's implementation, since
+ * version 4.0) when values are small.
+ *
+ * Instances are immutable, and thus can be used as if they were
+ * "plain integers".
+ *
+ * None of this code is "constant-time". As such, ZInt should be
+ * considered unsuitable to implementations of cryptographic algorithms.
+ */
+
+public struct ZInt : IComparable, IComparable<ZInt>, IEquatable<ZInt> {
+
+ /*
+ * CONVENTIONS:
+ *
+ * If varray == null, then "small" contains the integer value.
+ *
+ * If varray != null, then it contains the value, in little-endian
+ * convention (least significant word comes first) and of
+ * minimal encoded length (i.e. "trimmed"). Two's complement is
+ * used for negative values. "small" is then -1 or 0, depending
+ * on whether the value is negative or not.
+ *
+ * Note that the trimmed value does not always include the sign
+ * bit.
+ *
+ * If the integer value is in the range of values which can be
+ * represented in an "int", then magn is null. There is no allowed
+ * overlap between the two kinds of encodings.
+ *
+ * Default value thus encodes the integer zero.
+ */
+
+ readonly int small;
+ readonly uint[] varray;
+
+ /*
+ * The value -1.
+ */
+ public static ZInt MinusOne {
+ get { return new ZInt(-1, null); }
+ }
+
+ /*
+ * The value 0.
+ */
+ public static ZInt Zero {
+ get { return new ZInt(0, null); }
+ }
+
+ /*
+ * The value 1.
+ */
+ public static ZInt One {
+ get { return new ZInt(1, null); }
+ }
+
+ /*
+ * The value 2.
+ */
+ public static ZInt Two {
+ get { return new ZInt(2, null); }
+ }
+
+ /*
+ * Internal constructor which assumes that the provided values are
+ * correct and normalized.
+ */
+ private ZInt(int small, uint[] varray)
+ {
+ this.small = small;
+ this.varray = varray;
+#if DEBUG
+ if (varray != null) {
+ if (small != -1 && small != 0) {
+ throw new Exception(
+ "Bad sign encoding: " + small);
+ }
+ if (varray.Length == 0) {
+ throw new Exception("Empty varray");
+ }
+ if (Length(small, varray) != varray.Length) {
+ throw new Exception("Untrimmed varray");
+ }
+ if (varray.Length == 1) {
+ /*
+ * If there was room for a sign bit, then
+ * the "small" encoding should have been used.
+ */
+ if (((varray[0] ^ (uint)small) >> 31) == 0) {
+ throw new Exception(
+ "suboptimal encoding");
+ }
+ }
+ }
+#endif
+ }
+
+ /*
+ * Main internal build method. This method normalizes the encoding
+ * ("small" encoding is used when possible; otherwise, the varray
+ * is trimmed and the sign is normalized to -1 or 0).
+ *
+ * If "varray" is null, then the small value is used. Otherwise,
+ * only the sign bit (most significant bit) of "small" is used,
+ * and that value is normalized; the array is trimmed. If the
+ * small encoding then becomes applicable, then it is used.
+ */
+ private static ZInt Make(int small, uint[] varray)
+ {
+ if (varray == null) {
+ return new ZInt(small, null);
+ }
+ small >>= 31;
+ int n = Length(small, varray);
+ if (n == 1 && (((varray[0] ^ (uint)small) >> 31) == 0)) {
+ small = (int)varray[0];
+ varray = null;
+ } else {
+ /*
+ * Note: if n == 0 then the value is -1 or 0, and
+ * "small" already contains the correct value;
+ * Trim() will then return null, which is appropriate.
+ */
+ varray = Trim(small, varray, n);
+ }
+ return new ZInt(small, varray);
+ }
+
+ /*
+ * Create an instance from a signed 32-bit integer.
+ */
+ public ZInt(int val)
+ {
+ small = val;
+ varray = null;
+ }
+
+ /*
+ * Create an instance from an unsigned 32-bit integer.
+ */
+ public ZInt(uint val)
+ {
+ small = (int)val;
+ if (small < 0) {
+ small = 0;
+ varray = new uint[1] { val };
+ } else {
+ varray = null;
+ }
+ }
+
+ /*
+ * Create an instance from a signed 64-bit integer.
+ */
+ public ZInt(long val)
+ {
+ small = (int)val;
+ if ((long)small == val) {
+ varray = null;
+ } else {
+ ulong uval = (ulong)val;
+ uint w0 = (uint)uval;
+ uint w1 = (uint)(uval >> 32);
+ if (w1 == 0) {
+ small = 0;
+ varray = new uint[1] { w0 };
+ } else if (w1 == 0xFFFFFFFF) {
+ small = -1;
+ varray = new uint[1] { w0 };
+ } else {
+ small = (int)w1 >> 31;
+ varray = new uint[2] { w0, w1 };
+ }
+ }
+ }
+
+ /*
+ * Create an instance from an unsigned 64-bit integer.
+ */
+ public ZInt(ulong val)
+ {
+ if (val <= 0x7FFFFFFF) {
+ small = (int)val;
+ varray = null;
+ } else {
+ small = 0;
+ uint w0 = (uint)val;
+ uint w1 = (uint)(val >> 32);
+ if (w1 == 0) {
+ varray = new uint[1] { w0 };
+ } else {
+ varray = new uint[2] { w0, w1 };
+ }
+ }
+ }
+
+ /*
+ * Create a ZInt instance for an integer expressed as an array
+ * of 32-bit integers (unsigned little-endian convention), with
+ * a specific number of value bits.
+ */
+ public static ZInt Make(uint[] words, int numBits)
+ {
+ if (numBits == 0) {
+ return Zero;
+ }
+ int n = (numBits + 31) >> 5;
+ int kb = numBits & 31;
+ uint[] m = new uint[n];
+ Array.Copy(words, 0, m, 0, n);
+ if (kb > 0) {
+ m[n - 1] &= ~((uint)0xFFFFFFFF << kb);
+ }
+ return Make(0, m);
+ }
+
+ /*
+ * Create a ZInt instance for an integer expressed as an array
+ * of 64-bit integers (unsigned little-endian convention), with
+ * a specific number of value bits.
+ */
+ public static ZInt Make(ulong[] words, int numBits)
+ {
+ if (numBits == 0) {
+ return Zero;
+ }
+ int kw = (numBits + 63) >> 6;
+ int kb = numBits & 63;
+ int n = kw * 2;
+ uint[] m = new uint[n];
+ if (kb != 0) {
+ ulong z = words[kw - 1]
+ & ~((ulong)0xFFFFFFFFFFFFFFFF << kb);
+ m[n - 1] = (uint)(z >> 32);
+ m[n - 2] = (uint)z;
+ kw --;
+ n -= 2;
+ }
+ for (int i = kw - 1, j = n - 2; i >= 0; i --, j -= 2) {
+ ulong z = words[i];
+ m[j + 0] = (uint)z;
+ m[j + 1] = (uint)(z >> 32);
+ }
+ return Make(0, m);
+ }
+
+ /*
+ * Test whether this value is 0.
+ */
+ public bool IsZero {
+ get {
+ return small == 0 && varray == null;
+ }
+ }
+
+ /*
+ * Test whether this value is 1.
+ */
+ public bool IsOne {
+ get {
+ return small == 1;
+ }
+ }
+
+ /*
+ * Test whether this value is even.
+ */
+ public bool IsEven {
+ get {
+ uint w = (varray == null) ? (uint)small : varray[0];
+ return (w & 1) == 0;
+ }
+ }
+
+ /*
+ * Test whether this value is a power of 2. Note that 1 is a power
+ * of 2 (but 0 is not). For negative values, false is returned.
+ */
+ public bool IsPowerOfTwo {
+ get {
+ if (small < 0) {
+ return false;
+ }
+ if (varray == null) {
+ return small != 0 && (small & -small) == small;
+ }
+ int n = varray.Length;
+ int z = (int)varray[n - 1];
+ if ((z & -z) != z) {
+ return false;
+ }
+ for (int i = n - 2; i >= 0; i --) {
+ if (varray[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /*
+ * Get the sign of this value as an integer (-1 for negative
+ * values, 1 for positive, 0 for zero).
+ */
+ public int Sign {
+ get {
+ if (varray == null) {
+ if (small < 0) {
+ return -1;
+ } else if (small == 0) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ return small | 1;
+ }
+ }
+ }
+
+ /*
+ * Test whether this value would fit in an 'int'.
+ */
+ public bool IsInt {
+ get {
+ return varray == null;
+ }
+ }
+
+ /*
+ * Test whether this value would fit in a "long".
+ */
+ public bool IsLong {
+ get {
+ if (varray == null) {
+ return true;
+ }
+ int n = varray.Length;
+ if (n == 1) {
+ return true;
+ } else if (n == 2) {
+ return ((int)varray[1] >> 31) == small;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /*
+ * Get the length of the value in bits. This is the minimal number
+ * of bits of the two's complement representation of the value,
+ * excluding the sign bit; thus, both 0 and -1 have bit length 0.
+ */
+ public int BitLength {
+ get {
+ if (varray == null) {
+ if (small < 0) {
+ return 32 - LeadingZeros(~(uint)small);
+ } else {
+ return 32 - LeadingZeros((uint)small);
+ }
+ } else {
+ int n = varray.Length;
+ int bl = n << 5;
+ uint w = varray[n - 1];
+ if (small < 0) {
+ return bl - LeadingZeros(~w);
+ } else {
+ return bl - LeadingZeros(w);
+ }
+ }
+ }
+ }
+
+ /*
+ * Test whether the specified bit has value 1 or 0 ("true" is
+ * returned if the bit has value 1). Note that for negative values,
+ * two's complement representation is assumed.
+ */
+ public bool TestBit(int n)
+ {
+ if (n < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (varray == null) {
+ if (n >= 32) {
+ return small < 0;
+ } else {
+ return (((uint)small >> n) & (uint)1) != 0;
+ }
+ } else {
+ int nw = n >> 5;
+ if (nw >= varray.Length) {
+ return small != 0;
+ }
+ int nb = n & 31;
+ return ((varray[nw] >> nb) & (uint)1) != 0;
+ }
+ }
+
+ /*
+ * Copy some bits from this instance to the provided array. Bits
+ * are copied in little-endian order. First bit to be copied is
+ * bit at index "off", and exactly "num" bits are copied. This
+ * method modifies only the minimum number of destination words
+ * (i.e. the first "(num+31)/32" words, exactly). Remaining bits
+ * in the last touched word are set to 0.
+ */
+ public void CopyBits(int off, int num, uint[] dest)
+ {
+ CopyBits(off, num, dest, 0);
+ }
+
+ public void CopyBits(int off, int num, uint[] dest, int destOff)
+ {
+ if (off < 0 || num < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (num == 0) {
+ return;
+ }
+ ZInt x = this;
+ if (off > 0) {
+ x >>= off;
+ }
+ int kw = num >> 5;
+ int kb = num & 31;
+ uint hmask = ~((uint)0xFFFFFFFF << kb);
+ if (x.varray == null) {
+ if (kw == 0) {
+ dest[destOff] = (uint)x.small & hmask;
+ } else {
+ uint iw = (uint)(x.small >> 31);
+ dest[destOff] = (uint)x.small;
+ for (int i = 1; i < kw; i ++) {
+ dest[destOff + i] = iw;
+ }
+ if (kb > 0) {
+ dest[destOff + kw] = iw & hmask;
+ }
+ }
+ } else {
+ int n = x.varray.Length;
+ if (kw <= n) {
+ Array.Copy(x.varray, 0, dest, destOff, kw);
+ } else {
+ Array.Copy(x.varray, 0, dest, destOff, n);
+ for (int i = n; i < kw; i ++) {
+ dest[destOff + i] = (uint)x.small;
+ }
+ }
+ if (kb > 0) {
+ uint last;
+ if (kw < n) {
+ last = x.varray[kw] & hmask;
+ } else {
+ last = (uint)x.small & hmask;
+ }
+ dest[destOff + kw] = last;
+ }
+ }
+ }
+
+ /*
+ * Copy some bits from this instance to the provided array. Bits
+ * are copied in little-endian order. First bit to be copied is
+ * bit at index "off", and exactly "num" bits are copied. This
+ * method modifies only the minimum number of destination words
+ * (i.e. the first "(num+63)/64" words, exactly). Remaining bits
+ * in the last touched word are set to 0.
+ */
+ public void CopyBits(int off, int num, ulong[] dest)
+ {
+ CopyBits(off, num, dest, 0);
+ }
+
+ public void CopyBits(int off, int num, ulong[] dest, int destOff)
+ {
+ if (off < 0 || num < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (num == 0) {
+ return;
+ }
+ ZInt x = this;
+ if (off > 0) {
+ x >>= off;
+ }
+ int kw = num >> 6;
+ int kb = num & 63;
+ ulong hmask = ~((ulong)0xFFFFFFFFFFFFFFFF << kb);
+ long xs = (long)x.small;
+ if (x.varray == null) {
+ if (kw == 0) {
+ dest[destOff] = (ulong)xs & hmask;
+ } else {
+ ulong iw = (ulong)(xs >> 31);
+ dest[destOff] = (ulong)xs;
+ for (int i = 1; i < kw; i ++) {
+ dest[destOff + i] = iw;
+ }
+ if (kb > 0) {
+ dest[destOff + kw] = iw & hmask;
+ }
+ }
+ } else {
+ int n = x.varray.Length;
+ uint iw = (uint)x.small;
+ int j = 0;
+ for (int i = 0; i < kw; i ++, j += 2) {
+ uint w0 = (j < n) ? x.varray[j] : iw;
+ uint w1 = ((j + 1) < n) ? x.varray[j + 1] : iw;
+ dest[destOff + i] =
+ (ulong)w0 | ((ulong)w1 << 32);
+ }
+ if (kb > 0) {
+ uint w0 = (j < n) ? x.varray[j] : iw;
+ uint w1 = ((j + 1) < n) ? x.varray[j + 1] : iw;
+ ulong last = (ulong)w0 | ((ulong)w1 << 32);
+ dest[destOff + kw] = last & hmask;
+ }
+ }
+ }
+
+ /*
+ * Extract a 32-bit word at a given offset (counted in bits).
+ * This function is equivalent to right-shifting the value by
+ * "off" bits, then returning the low 32 bits (however, this
+ * function may be more efficient).
+ */
+ public uint GetWord(int off)
+ {
+ if (off < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (varray == null) {
+ int x = small;
+ if (off >= 32) {
+ off = 31;
+ }
+ return (uint)(x >> off);
+ }
+ int n = varray.Length;
+ int kw = off >> 5;
+ if (kw >= n) {
+ return (uint)small;
+ }
+ int kb = off & 31;
+ if (kb == 0) {
+ return varray[kw];
+ } else {
+ uint hi;
+ if (kw == n - 1) {
+ hi = (uint)small;
+ } else {
+ hi = varray[kw + 1];
+ }
+ uint lo = varray[kw];
+ return (lo >> kb) | (hi << (32 - kb));
+ }
+ }
+
+ /*
+ * Extract a 64-bit word at a given offset (counted in bits).
+ * This function is equivalent to right-shifting the value by
+ * "off" bits, then returning the low 64 bits (however, this
+ * function may be more efficient).
+ */
+ public ulong GetWord64(int off)
+ {
+ if (off < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (varray == null) {
+ int x = small;
+ if (off >= 32) {
+ off = 31;
+ }
+ return (ulong)(x >> off);
+ }
+ int n = varray.Length;
+ int kw = off >> 5;
+ if (kw >= n) {
+ return (ulong)small;
+ }
+ int kb = off & 31;
+ if (kb == 0) {
+ if (kw == (n - 1)) {
+ return (ulong)varray[kw]
+ | ((ulong)small << 32);
+ } else {
+ return (ulong)varray[kw]
+ | ((ulong)varray[kw + 1] << 32);
+ }
+ } else {
+ uint v0, v1, v2;
+ if (kw == (n - 1)) {
+ v0 = varray[kw];
+ v1 = (uint)small;
+ v2 = (uint)small;
+ } else if (kw == (n - 2)) {
+ v0 = varray[kw];
+ v1 = varray[kw + 1];
+ v2 = (uint)small;
+ } else {
+ v0 = varray[kw];
+ v1 = varray[kw + 1];
+ v2 = varray[kw + 2];
+ }
+ uint lo = (v0 >> kb) | (v1 << (32 - kb));
+ uint hi = (v1 >> kb) | (v2 << (32 - kb));
+ return (ulong)lo | ((ulong)hi << 32);
+ }
+ }
+
+ /*
+ * Convert this value to an 'int', using silent truncation if
+ * the value does not fit.
+ */
+ public int ToInt {
+ get {
+ return (varray == null) ? small : (int)varray[0];
+ }
+ }
+
+ /*
+ * Convert this value to an 'uint', using silent truncation if
+ * the value does not fit.
+ */
+ public uint ToUInt {
+ get {
+ return (varray == null) ? (uint)small : varray[0];
+ }
+ }
+
+ /*
+ * Convert this value to a 'long', using silent truncation if
+ * the value does not fit.
+ */
+ public long ToLong {
+ get {
+ return (long)ToULong;
+ }
+ }
+
+ /*
+ * Convert this value to an 'ulong', using silent truncation if
+ * the value does not fit.
+ */
+ public ulong ToULong {
+ get {
+ if (varray == null) {
+ return (ulong)small;
+ } else if (varray.Length == 1) {
+ uint iw = (uint)small;
+ return (ulong)varray[0] | ((ulong)iw << 32);
+ } else {
+ return (ulong)varray[0]
+ | ((ulong)varray[1] << 32);
+ }
+ }
+ }
+
+ /*
+ * Get the actual length of a varray encoding: this is the minimal
+ * length, in words, needed to encode the value. The value sign
+ * is provided as a negative or non-negative integer, and the
+ * encoding of minimal length does not necessarily include a
+ * sign bit. The value 0 is returned when the array encodes 0
+ * or -1 (depending on sign).
+ */
+ static int Length(int sign, uint[] m)
+ {
+ if (m == null) {
+ return 0;
+ }
+ uint sw = (uint)(sign >> 31);
+ int n = m.Length;
+ while (n > 0 && m[n - 1] == sw) {
+ n --;
+ }
+ return n;
+ }
+
+ /*
+ * Trim an encoding to its minimal encoded length. If the provided
+ * array is already of minimal length, it is returned unchanged.
+ */
+ static uint[] Trim(int sign, uint[] m)
+ {
+ int n = Length(sign, m);
+ if (n == 0) {
+ return null;
+ } else if (n < m.Length) {
+ uint[] mt = new uint[n];
+ Array.Copy(m, 0, mt, 0, n);
+ return mt;
+ } else {
+ return m;
+ }
+ }
+
+ /*
+ * Trim or extend a value to the provided length. The returned
+ * array will have the length specified as "n" (if n == 0, then
+ * null is returned). If the source array already has the right
+ * size, then it is returned unchanged.
+ */
+ static uint[] Trim(int sign, uint[] m, int n)
+ {
+ if (n == 0) {
+ return null;
+ } else if (m == null) {
+ m = new uint[n];
+ if (sign < 0) {
+ Fill(0xFFFFFFFF, m);
+ }
+ return m;
+ }
+ int ct = m.Length;
+ if (ct < n) {
+ uint[] r = new uint[n];
+ Array.Copy(m, 0, r, 0, ct);
+ return r;
+ } else if (ct == n) {
+ return m;
+ } else {
+ uint[] r = new uint[n];
+ Array.Copy(m, 0, r, 0, n);
+ if (sign < 0) {
+ Fill(0xFFFFFFFF, r, ct, n - ct);
+ }
+ return r;
+ }
+ }
+
+ static void Fill(uint val, uint[] buf)
+ {
+ Fill(val, buf, 0, buf.Length);
+ }
+
+ static void Fill(uint val, uint[] buf, int off, int len)
+ {
+ while (len -- > 0) {
+ buf[off ++] = val;
+ }
+ }
+
+ // =================================================================
+ /*
+ * Utility methods.
+ *
+ * The methods whose name begins with "Mutate" modify the array
+ * they are given as first parameter; the other methods instantiate
+ * a new array.
+ *
+ * As a rule, untrimmed arrays are accepted as input, and output
+ * may be untrimmed as well.
+ */
+
+ /*
+ * Count the number of leading zeros for a 32-bit value (number of
+ * consecutive zeros, starting with the most significant bit). This
+ * value is between 0 (for a value equal to 2^31 or greater) and
+ * 32 (for zero).
+ */
+ static int LeadingZeros(uint v)
+ {
+ if (v == 0) {
+ return 32;
+ }
+ int n = 0;
+ if (v > 0xFFFF) { v >>= 16; } else { n += 16; }
+ if (v > 0x00FF) { v >>= 8; } else { n += 8; }
+ if (v > 0x000F) { v >>= 4; } else { n += 4; }
+ if (v > 0x0003) { v >>= 2; } else { n += 2; }
+ if (v <= 0x0001) { n ++; }
+ return n;
+ }
+
+ /*
+ * Duplicate the provided magnitude array. No attempt is made at
+ * trimming. The source array MUST NOT be null.
+ */
+ static uint[] Dup(uint[] m)
+ {
+ uint[] r = new uint[m.Length];
+ Array.Copy(m, 0, r, 0, m.Length);
+ return r;
+ }
+
+ /*
+ * Increment the provided array. If there is a resulting carry,
+ * then "true" is returned, "false" otherwise. The array MUST
+ * NOT be null.
+ */
+ static bool MutateIncr(uint[] x)
+ {
+ int n = x.Length;
+ for (int i = 0; i < n; i ++) {
+ uint w = x[i] + 1;
+ x[i] = w;
+ if (w != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Decrement the provided array. If there is a resulting carry,
+ * then "true" is returned, "false" otherwise. The array MUST
+ * NOT be null.
+ */
+ static bool MutateDecr(uint[] x)
+ {
+ int n = x.Length;
+ for (int i = 0; i < n; i ++) {
+ uint w = x[i];
+ x[i] = w - 1;
+ if (w != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Multiply a[] with b[] (unsigned multiplication).
+ */
+ static uint[] Mul(uint[] a, uint[] b)
+ {
+ // TODO: use Karatsuba when operands are large.
+ int na = Length(0, a);
+ int nb = Length(0, b);
+ if (na == 0 || nb == 0) {
+ return null;
+ }
+ uint[] r = new uint[na + nb];
+ for (int i = 0; i < na; i ++) {
+ ulong ma = a[i];
+ ulong carry = 0;
+ for (int j = 0; j < nb; j ++) {
+ ulong mb = (ulong)b[j];
+ ulong mr = (ulong)r[i + j];
+ ulong w = ma * mb + mr + carry;
+ r[i + j] = (uint)w;
+ carry = w >> 32;
+ }
+ r[i + nb] = (uint)carry;
+ }
+ return r;
+ }
+
+ /*
+ * Get the sign and magnitude of an integer. The sign is
+ * normalized to -1 (negative) or 0 (positive or zero). The
+ * magnitude is an array of length at least 1, containing the
+ * absolute value of this integer; if possible, the varray
+ * is reused (hence, the magnitude array MUST NOT be altered).
+ */
+ static void ToAbs(ZInt x, out int sign, out uint[] magn)
+ {
+ if (x.small < 0) {
+ sign = -1;
+ x = -x;
+ } else {
+ sign = 0;
+ }
+ magn = x.varray;
+ if (magn == null) {
+ magn = new uint[1] { (uint)x.small };
+ }
+ }
+
+ /*
+ * Compare two integers, yielding -1, 0 or 1.
+ */
+ static int Compare(int a, int b)
+ {
+ if (a < b) {
+ return -1;
+ } else if (a == b) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /*
+ * Compare a[] with b[] (unsigned). Returned value is 1 if a[]
+ * is greater than b[], 0 if they are equal, -1 otherwise.
+ */
+ static int Compare(uint[] a, uint[] b)
+ {
+ int ka = Length(0, a);
+ int kb = Length(0, b);
+ if (ka < kb) {
+ return -1;
+ } else if (ka == kb) {
+ while (ka > 0) {
+ ka --;
+ uint wa = a[ka];
+ uint wb = b[ka];
+ if (wa < wb) {
+ return -1;
+ } else if (wa > wb) {
+ return 1;
+ }
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /*
+ * Add b[] to a[] (unsigned). a[] is modified "in place". Only
+ * n words of a[] are modified. Moreover, the value of
+ * b[] which is added is left-shifted: words b[0]...b[n-1] are
+ * added to a[k]...a[k+n-1]. The final carry is returned ("true"
+ * for 1, "false" for 0). Neither a nor b may be null.
+ */
+ static bool MutateAdd(uint[] a, int n, uint[] b, int k)
+ {
+ bool carry = false;
+ for (int i = 0; i < n; i ++) {
+ uint wa = a[i + k];
+ uint wb = b[i];
+ uint wc = wa + wb;
+ if (carry) {
+ wc ++;
+ carry = wa >= wc;
+ } else {
+ carry = wa > wc;
+ }
+ a[i + k] = wc;
+ }
+ return carry;
+ }
+
+ /*
+ * Substract b[] from a[] (unsigned). a[] is modified "in
+ * place". Only n words of a[] are modified. Words
+ * b[0]...b[n-1] are subtracted from a[k]...a[k+n-1]. The final
+ * carry is returned ("true" for -1, "false" for 0). Neither a
+ * nor b may be null.
+ */
+ static bool MutateSub(uint[] a, int n, uint[] b, int k)
+ {
+ bool carry = false;
+ for (int i = 0; i < n; i ++) {
+ uint wa = a[i + k];
+ uint wb = b[i];
+ uint wc = wa - wb;
+ if (carry) {
+ wc --;
+ carry = wa <= wc;
+ } else {
+ carry = wa < wc;
+ }
+ a[i + k] = wc;
+ }
+ return carry;
+ }
+
+ /*
+ * Get the length (in words) of the result of a left-shift of
+ * the provided integer by k bits. If k < 0, then the value is
+ * computed for a right-shift by -k bits.
+ */
+ static int GetLengthForLeftShift(ZInt x, int k)
+ {
+ if (k < 0) {
+ if (k == Int32.MinValue) {
+ return 0;
+ }
+ return GetLengthForRightShift(x, -k);
+ }
+ uint bl = (uint)x.BitLength + (uint)k;
+ return (int)((bl + 31) >> 5);
+ }
+
+ /*
+ * Get the length (in words) of the result of a right-shift of
+ * the provided integer by k bits. If k < 0, then the value is
+ * computed for a left-shift by -k bits.
+ */
+ static int GetLengthForRightShift(ZInt x, int k)
+ {
+ if (k < 0) {
+ if (k == Int32.MinValue) {
+ throw new OverflowException();
+ }
+ return GetLengthForLeftShift(x, -k);
+ }
+ uint bl = (uint)x.BitLength;
+ if (bl <= (uint)k) {
+ return 0;
+ } else {
+ return (int)((bl - k + 31) >> 5);
+ }
+ }
+
+ /*
+ * Left-shift a[] (unsigned) by k bits. If k < 0, then this becomes
+ * a right-shift.
+ */
+ static uint[] ShiftLeft(uint[] a, int k)
+ {
+ if (k < 0) {
+ return ShiftRight(a, -k);
+ } else if (k == 0) {
+ return a;
+ }
+ int n = Length(0, a);
+ if (n == 0) {
+ return null;
+ }
+
+ /*
+ * Allocate the result array, with the exact proper size.
+ */
+ int bl = ((n << 5) - LeadingZeros(a[n - 1])) + k;
+ uint[] r = new uint[(bl + 31) >> 5];
+
+ int kb = k & 31;
+ int kw = k >> 5;
+
+ /*
+ * Special case: shift by an integral amount of words.
+ */
+ if (kb == 0) {
+ Array.Copy(a, 0, r, kw, n);
+ return r;
+ }
+
+ /*
+ * Copy the bits. This loop handles one source word at
+ * a time, and writes one destination word at a time.
+ * Some unhandled bits may remain at the end.
+ */
+ uint bits = 0;
+ int zkb = 32 - kb;
+ for (int i = 0; i < n; i ++) {
+ uint w = a[i];
+ r[i + kw] = bits | (w << kb);
+ bits = w >> zkb;
+ }
+ if (bits != 0) {
+ r[n + kw] = bits;
+ }
+ return r;
+ }
+
+ /*
+ * Right-shift a[] by k bits. If k < 0, then this becomes
+ * a left-shift.
+ */
+ static uint[] ShiftRight(uint[] a, int k)
+ {
+ if (k < 0) {
+ return ShiftLeft(a, -k);
+ } else if (k == 0) {
+ return a;
+ }
+ int n = Length(0, a);
+ if (n == 0) {
+ return null;
+ }
+ int bl = (n << 5) - LeadingZeros(a[n - 1]) - k;
+ if (bl <= 0) {
+ return null;
+ }
+ uint[] r = new uint[(bl + 31) >> 5];
+
+ int kb = k & 31;
+ int kw = k >> 5;
+
+ /*
+ * Special case: shift by an integral amount of words.
+ */
+ if (kb == 0) {
+ Array.Copy(a, kw, r, 0, r.Length);
+ return r;
+ }
+
+ /*
+ * Copy the bits. This loop handles one source word at
+ * a time, and writes one destination word at a time.
+ * Some unhandled bits may remain at the end.
+ */
+ uint bits = a[kw] >> kb;
+ int zkb = 32 - kb;
+ for (int i = kw + 1; i < n; i ++) {
+ uint w = a[i];
+ r[i - kw - 1] = bits | (w << zkb);
+ bits = w >> kb;
+ }
+ if (bits != 0) {
+ r[n - kw - 1] = bits;
+ }
+ return r;
+ }
+
+ /*
+ * Euclidian division of a[] (unsigned) by b (single word). This
+ * method assumes that b is not 0.
+ */
+ static void DivRem(uint[] a, uint b, out uint[] q, out uint r)
+ {
+ int n = Length(0, a);
+ if (n == 0) {
+ q = null;
+ r = 0;
+ return;
+ }
+ q = new uint[n];
+ ulong carry = 0;
+ for (int i = n - 1; i >= 0; i --) {
+ /*
+ * Performance: we strongly hope that the JIT
+ * compiler will notice that the "/" and "%"
+ * can be combined into a single operation.
+ * TODO: test whether we should replace the
+ * carry computation with:
+ * carry = w - (ulong)q[i] * b;
+ */
+ ulong w = (ulong)a[i] + (carry << 32);
+ q[i] = (uint)(w / b);
+ carry = w % b;
+ }
+ r = (uint)carry;
+ }
+
+ /*
+ * Euclidian division of a[] (unsigned) by b (single word). This
+ * method assumes that b is not 0. a[] is modified in place. The
+ * remainder (in the 0..b-1 range) is returned.
+ */
+ static uint MutateDivRem(uint[] a, uint b)
+ {
+ int n = Length(0, a);
+ if (n == 0) {
+ return 0;
+ }
+ ulong carry = 0;
+ for (int i = n - 1; i >= 0; i --) {
+ /*
+ * Performance: we strongly hope that the JIT
+ * compiler will notice that the "/" and "%"
+ * can be combined into a single operation.
+ * TODO: test whether we should replace the
+ * carry computation with:
+ * carry = w - (ulong)q[i] * b;
+ */
+ ulong w = (ulong)a[i] + (carry << 32);
+ a[i] = (uint)(w / b);
+ carry = w % b;
+ }
+ return (uint)carry;
+ }
+
+ /*
+ * Euclidian division of a[] by b[] (unsigned). This method
+ * assumes that b[] is neither 0 or 1, and a[] is not smaller
+ * than b[] (this implies that the quotient won't be zero).
+ */
+ static void DivRem(uint[] a, uint[] b, out uint[] q, out uint[] r)
+ {
+ int nb = Length(0, b);
+
+ /*
+ * Special case when the divisor fits on one word.
+ */
+ if (nb == 1) {
+ r = new uint[1];
+ DivRem(a, b[0], out q, out r[0]);
+ return;
+ }
+
+ /*
+ * General case.
+ *
+ * We first normalize divisor and dividend such that the
+ * most significant bit of the most significant word of
+ * the divisor is set. We can then compute the quotient
+ * word by word. In details:
+ *
+ * Let:
+ * w = 2^32 (one word)
+ * a = (w*a0 + a1) * w^N + a2
+ * b = b0 * w^N + b2
+ * such that:
+ * 0 <= a0 < w
+ * 0 <= a1 < w
+ * 0 <= a2 < w^N
+ * w/2 <= b0 < w
+ * 0 <= b2 < w^N
+ * a < w*b
+ * In other words, a0 and a1 are the two upper words of a[],
+ * b0 is the upper word of b[] and has length 32 bits exactly,
+ * and the quotient of a by b fits in one word.
+ *
+ * Under these conditions, define q and r such that:
+ * a = b * q + r
+ * q >= 0
+ * 0 <= r < b
+ * We can then compute a value u this way:
+ * if a0 = b0, then let u = w-1
+ * otherwise, let u be such that
+ * (w*a0 + a1) = u*b0 + v, where 0 <= v < b0
+ * It can then be shown that all these inequations hold:
+ * 0 <= u < w
+ * u-2 <= q <= u
+ *
+ * In plain words, this allows us to compute an almost-exact
+ * estimate of the upper word of the quotient, with only
+ * one 64-bit division.
+ */
+
+ /*
+ * Normalize dividend and divisor. The normalized dividend
+ * will become the temporary array for the remainder, and
+ * we will modify it, so we make sure we have a copy.
+ */
+ int norm = LeadingZeros(b[nb - 1]);
+ r = ShiftLeft(a, norm);
+ if (r == a) {
+ r = new uint[a.Length];
+ Array.Copy(a, 0, r, 0, a.Length);
+ }
+ uint[] b2 = ShiftLeft(b, norm);
+ int nr = Length(0, r);
+#if DEBUG
+ if (Length(0, b2) != nb) {
+ throw new Exception("normalize error 1");
+ }
+ if (b2[nb - 1] < 0x80000000) {
+ throw new Exception("normalize error 2");
+ }
+ {
+ uint[] ta = ShiftRight(r, norm);
+ if (Compare(a, ta) != 0) {
+ throw new Exception("normalize error 3");
+ }
+ uint[] tb = ShiftRight(b2, norm);
+ if (Compare(b, tb) != 0) {
+ throw new Exception("normalize error 4");
+ }
+ }
+#endif
+ b = b2;
+
+ /*
+ * Length of the quotient will be (at most) k words. This
+ * is the number of iterations in the loop below.
+ */
+ int k = (nr - nb) + 1;
+#if DEBUG
+ if (k <= 0) {
+ throw new Exception("wrong iteration count: " + k);
+ }
+#endif
+ q = new uint[k];
+
+ /*
+ * The upper word of a[] (the one we modify, i.e. currently
+ * stored in r[]) is in a0; it is carried over from the
+ * previous loop iteration. Initially, it is zero.
+ */
+ uint a0 = 0;
+ uint b0 = b[nb - 1];
+ int j = nr;
+ while (k -- > 0) {
+ uint a1 = r[-- j];
+ uint u;
+ if (a0 == b0) {
+ u = 0xFFFFFFFF;
+ } else {
+ ulong ah = ((ulong)a0 << 32) | (ulong)a1;
+ u = (uint)(ah / b0);
+ }
+
+ /*
+ * Candidate word for the quotient:
+ * -- if u = 0 then qw is necessarily 0, and the
+ * rest of this iteration is trivial;
+ * -- if u = 1 then we try qw = 1;
+ * -- otherwise, we try qw = u-1.
+ */
+ uint qw;
+ if (u == 0) {
+ q[k] = 0;
+ a0 = a1;
+ continue;
+ } else if (u == 1) {
+ qw = 1;
+ } else {
+ qw = u - 1;
+ }
+
+ /*
+ * "Trying" a candidate word means subtracting from
+ * r[] the product qw*b (b[] being shifted by k words).
+ * The result may be negative, in which case we
+ * overestimated qw; it may be greater than b or
+ * equal to b, in which case we underestimated qw;
+ * or it may be just fine.
+ */
+ ulong carry = 0;
+ bool tooBig = true;
+ for (int i = 0; i < nb; i ++) {
+ uint wb = b[i];
+ ulong z = (ulong)wb * (ulong)qw + carry;
+ carry = z >> 32;
+ uint wa = r[i + k];
+ uint wc = wa - (uint)z;
+ if (wc > wa) {
+ carry ++;
+ }
+ r[i + k] = wc;
+ if (wc != wb) {
+ tooBig = wc > wb;
+ }
+ }
+
+ /*
+ * Once we have adjusted everything, the upper word
+ * of r[] will be nullified; wo do it now. Note that
+ * for the first loop iteration, that upper word
+ * may be absent (so already zero, but also "virtual").
+ */
+ if (nb + k < nr) {
+ r[nb + k] = 0;
+ }
+
+ /*
+ * At that point, "carry" should be equal to a0
+ * if we estimated right.
+ */
+ if (carry < (ulong)a0) {
+ /*
+ * Underestimate.
+ */
+ qw ++;
+#if DEBUG
+ if (carry + 1 != (ulong)a0) {
+ throw new Exception("div error 1");
+ }
+ if (!MutateSub(r, nb, b, k)) {
+ throw new Exception("div error 2");
+ }
+#else
+ MutateSub(r, nb, b, k);
+#endif
+ } else if (carry > (ulong)a0) {
+ /*
+ * Overestimate.
+ */
+ qw --;
+#if DEBUG
+ if (carry - 1 != (ulong)a0) {
+ throw new Exception("div error 3");
+ }
+ if (!MutateAdd(r, nb, b, k)) {
+ throw new Exception("div error 4");
+ }
+#else
+ MutateAdd(r, nb, b, k);
+#endif
+ } else if (tooBig) {
+ /*
+ * Underestimate, but no expected carry.
+ */
+ qw ++;
+#if DEBUG
+ if (MutateSub(r, nb, b, k)) {
+ throw new Exception("div error 5");
+ }
+#else
+ MutateSub(r, nb, b, k);
+#endif
+ }
+
+ q[k] = qw;
+ a0 = r[j];
+ }
+
+ /*
+ * At that point, r[] contains the remainder but needs
+ * to be shifted back, to account for the normalization
+ * performed before. q[] is correct (but possibly
+ * untrimmed).
+ */
+ r = ShiftRight(r, norm);
+ }
+
+ // =================================================================
+
+ /*
+ * Conversion to sbyte; an OverflowException is thrown if the
+ * value does not fit. Use ToInt to get a truncating conversion.
+ */
+ public static explicit operator sbyte(ZInt val)
+ {
+ int x = (int)val;
+ if (x < SByte.MinValue || x > SByte.MaxValue) {
+ throw new OverflowException();
+ }
+ return (sbyte)x;
+ }
+
+ /*
+ * Conversion to byte; an OverflowException is thrown if the
+ * value does not fit. Use ToInt to get a truncating conversion.
+ */
+ public static explicit operator byte(ZInt val)
+ {
+ int x = (int)val;
+ if (x > Byte.MaxValue) {
+ throw new OverflowException();
+ }
+ return (byte)x;
+ }
+
+ /*
+ * Conversion to short; an OverflowException is thrown if the
+ * value does not fit. Use ToInt to get a truncating conversion.
+ */
+ public static explicit operator short(ZInt val)
+ {
+ int x = (int)val;
+ if (x < Int16.MinValue || x > Int16.MaxValue) {
+ throw new OverflowException();
+ }
+ return (short)x;
+ }
+
+ /*
+ * Conversion to ushort; an OverflowException is thrown if the
+ * value does not fit. Use ToInt to get a truncating conversion.
+ */
+ public static explicit operator ushort(ZInt val)
+ {
+ int x = (int)val;
+ if (x > UInt16.MaxValue) {
+ throw new OverflowException();
+ }
+ return (ushort)x;
+ }
+
+ /*
+ * Conversion to int; an OverflowException is thrown if the
+ * value does not fit. Use ToInt to get a truncating conversion.
+ */
+ public static explicit operator int(ZInt val)
+ {
+ if (val.varray != null) {
+ throw new OverflowException();
+ }
+ return val.small;
+ }
+
+ /*
+ * Conversion to uint; an OverflowException is thrown if the
+ * value does not fit. Use ToUInt to get a truncating conversion.
+ */
+ public static explicit operator uint(ZInt val)
+ {
+ int s = val.small;
+ if (s < 0) {
+ throw new OverflowException();
+ }
+ uint[] m = val.varray;
+ if (m == null) {
+ return (uint)s;
+ } else if (m.Length > 1) {
+ throw new OverflowException();
+ } else {
+ return m[0];
+ }
+ }
+
+ /*
+ * Conversion to long; an OverflowException is thrown if the
+ * value does not fit. Use ToLong to get a truncating conversion.
+ */
+ public static explicit operator long(ZInt val)
+ {
+ int s = val.small;
+ uint[] m = val.varray;
+ if (m == null) {
+ return (long)s;
+ } else if (m.Length == 1) {
+ return (long)m[0] | ((long)s << 32);
+ } else if (m.Length == 2) {
+ uint w0 = m[0];
+ uint w1 = m[1];
+ if (((w1 ^ (uint)s) >> 31) != 0) {
+ throw new OverflowException();
+ }
+ return (long)w0 | ((long)w1 << 32);
+ } else {
+ throw new OverflowException();
+ }
+ }
+
+ /*
+ * Conversion to ulong; an OverflowException is thrown if the
+ * value does not fit. Use ToULong to get a truncating conversion.
+ */
+ public static explicit operator ulong(ZInt val)
+ {
+ int s = val.small;
+ if (s < 0) {
+ throw new OverflowException();
+ }
+ uint[] m = val.varray;
+ if (m == null) {
+ return (ulong)s;
+ } else if (m.Length == 1) {
+ return (ulong)m[0];
+ } else if (m.Length == 2) {
+ return (ulong)m[0] | ((ulong)m[1] << 32);
+ } else {
+ throw new OverflowException();
+ }
+ }
+
+ /*
+ * By definition, conversion from sbyte conserves the value.
+ */
+ public static implicit operator ZInt(sbyte val)
+ {
+ return new ZInt((int)val);
+ }
+
+ /*
+ * By definition, conversion from byte conserves the value.
+ */
+ public static implicit operator ZInt(byte val)
+ {
+ return new ZInt((uint)val);
+ }
+
+ /*
+ * By definition, conversion from short conserves the value.
+ */
+ public static implicit operator ZInt(short val)
+ {
+ return new ZInt((int)val);
+ }
+
+ /*
+ * By definition, conversion from ushort conserves the value.
+ */
+ public static implicit operator ZInt(ushort val)
+ {
+ return new ZInt((uint)val);
+ }
+
+ /*
+ * By definition, conversion from int conserves the value.
+ */
+ public static implicit operator ZInt(int val)
+ {
+ return new ZInt(val);
+ }
+
+ /*
+ * By definition, conversion from uint conserves the value.
+ */
+ public static implicit operator ZInt(uint val)
+ {
+ return new ZInt(val);
+ }
+
+ /*
+ * By definition, conversion from long conserves the value.
+ */
+ public static implicit operator ZInt(long val)
+ {
+ return new ZInt(val);
+ }
+
+ /*
+ * By definition, conversion from ulong conserves the value.
+ */
+ public static implicit operator ZInt(ulong val)
+ {
+ return new ZInt(val);
+ }
+
+ /*
+ * Unary '+' operator is a no-operation.
+ */
+ public static ZInt operator +(ZInt a)
+ {
+ return a;
+ }
+
+ /*
+ * Unary negation.
+ */
+ public static ZInt operator -(ZInt a)
+ {
+ int s = a.small;
+ uint[] m = a.varray;
+ if (m == null) {
+ if (s == Int32.MinValue) {
+ return new ZInt(0, new uint[1] { 0x80000000 });
+ } else {
+ return new ZInt(-s, null);
+ }
+ }
+
+ /*
+ * Two's complement: invert all bits, then add 1. The
+ * result array will usually have the same size, but may
+ * be one word longer or one word shorter.
+ */
+ int n = Length(s, m);
+ uint[] bm = new uint[n];
+ for (int i = 0; i < n; i ++) {
+ bm[i] = ~m[i];
+ }
+ if (MutateIncr(bm)) {
+ /*
+ * Extra carry. This may happen only if the source
+ * array contained only zeros, which may happen only
+ * for a source value -2^(32*k) for some integer k
+ * (k > 0).
+ */
+ bm = new uint[n + 1];
+ bm[n] = 1;
+ return new ZInt(0, bm);
+ } else {
+ /*
+ * The resulting array might be too big by one word,
+ * so we must not assume that it is trimmed.
+ */
+ return Make((int)~(uint)s, bm);
+ }
+ }
+
+ /*
+ * Addition operator.
+ */
+ public static ZInt operator +(ZInt a, ZInt b)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+ if (ma == null) {
+ if (sa == 0) {
+ return b;
+ }
+ if (mb == null) {
+ if (sb == 0) {
+ return a;
+ }
+ return new ZInt((long)sa + (long)sb);
+ }
+ ma = new uint[1] { (uint)sa };
+ sa >>= 31;
+ } else if (mb == null) {
+ if (sb == 0) {
+ return a;
+ }
+ mb = new uint[1] { (uint)sb };
+ sb >>= 31;
+ }
+ int na = ma.Length;
+ int nb = mb.Length;
+ int n = Math.Max(na, nb) + 1;
+ uint[] mc = new uint[n];
+ ulong carry = 0;
+ for (int i = 0; i < n; i ++) {
+ uint wa = i < na ? ma[i] : (uint)sa;
+ uint wb = i < nb ? mb[i] : (uint)sb;
+ ulong z = (ulong)wa + (ulong)wb + carry;
+ mc[i] = (uint)z;
+ carry = z >> 32;
+ }
+ return Make((-(int)carry) ^ sa ^ sb, mc);
+ }
+
+ /*
+ * Subtraction operator.
+ */
+ public static ZInt operator -(ZInt a, ZInt b)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+ if (ma == null) {
+ if (sa == 0) {
+ return -b;
+ }
+ if (mb == null) {
+ if (sb == 0) {
+ return a;
+ }
+ return new ZInt((long)sa - (long)sb);
+ }
+ ma = new uint[1] { (uint)sa };
+ sa >>= 31;
+ } else if (mb == null) {
+ if (sb == 0) {
+ return a;
+ }
+ mb = new uint[1] { (uint)sb };
+ sb >>= 31;
+ }
+ int na = ma.Length;
+ int nb = mb.Length;
+ int n = Math.Max(na, nb) + 1;
+ uint[] mc = new uint[n];
+ long carry = 0;
+ for (int i = 0; i < n; i ++) {
+ uint wa = i < na ? ma[i] : (uint)sa;
+ uint wb = i < nb ? mb[i] : (uint)sb;
+ long z = (long)wa - (long)wb + carry;
+ mc[i] = (uint)z;
+ carry = z >> 32;
+ }
+ return Make((int)carry ^ sa ^ sb, mc);
+ }
+
+ /*
+ * Increment operator.
+ */
+ public static ZInt operator ++(ZInt a)
+ {
+ int s = a.small;
+ uint[] ma = a.varray;
+ if (ma == null) {
+ return new ZInt((long)s + 1);
+ }
+ uint[] mb = Dup(ma);
+ if (MutateIncr(mb)) {
+ int n = ma.Length;
+ mb = new uint[n + 1];
+ mb[n] = 1;
+ return new ZInt(0, mb);
+ } else {
+ return Make(s, mb);
+ }
+ }
+
+ /*
+ * Decrement operator.
+ */
+ public static ZInt operator --(ZInt a)
+ {
+ int s = a.small;
+ uint[] ma = a.varray;
+ if (ma == null) {
+ return new ZInt((long)s - 1);
+ }
+
+ /*
+ * MutateDecr() will report a carry only if the varray
+ * contained only zeros; since this value was not small,
+ * then it must have been negative.
+ */
+ uint[] mb = Dup(ma);
+ if (MutateDecr(mb)) {
+ int n = mb.Length;
+ uint[] mc = new uint[n + 1];
+ Array.Copy(mb, 0, mc, 0, n);
+ mc[n] = 0xFFFFFFFE;
+ return new ZInt(-1, mc);
+ } else {
+ return Make(s, mb);
+ }
+ }
+
+ /*
+ * Multiplication operator.
+ */
+ public static ZInt operator *(ZInt a, ZInt b)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+
+ /*
+ * Special cases:
+ * -- one of the operands is zero
+ * -- both operands are small
+ */
+ if (ma == null) {
+ if (sa == 0) {
+ return Zero;
+ }
+ if (mb == null) {
+ if (sb == 0) {
+ return Zero;
+ }
+ return new ZInt((long)sa * (long)sb);
+ }
+ } else if (mb == null) {
+ if (sb == 0) {
+ return Zero;
+ }
+ }
+
+ /*
+ * Get both values in sign+magnitude representation.
+ */
+ ToAbs(a, out sa, out ma);
+ ToAbs(b, out sb, out mb);
+
+ /*
+ * Compute the product. Set the sign.
+ */
+ ZInt r = Make(0, Mul(ma, mb));
+ if ((sa ^ sb) < 0) {
+ r = -r;
+ }
+ return r;
+ }
+
+ /*
+ * Integer division: the quotient is returned, and the remainder
+ * is written in 'r'.
+ *
+ * This operation follows the C# rules for integer division:
+ * -- rounding is towards 0
+ * -- quotient is positive if dividend and divisor have the same
+ * sign, negative otherwise
+ * -- remainder has the sign of the dividend
+ *
+ * Attempt at dividing by zero triggers a DivideByZeroException.
+ */
+ public static ZInt DivRem(ZInt a, ZInt b, out ZInt r)
+ {
+ ZInt q;
+ DivRem(a, b, out q, out r);
+ return q;
+ }
+
+ static void DivRem(ZInt a, ZInt b,
+ out ZInt q, out ZInt r)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+
+ /*
+ * Division by zero triggers an exception.
+ */
+ if (mb == null && sb == 0) {
+ throw new DivideByZeroException();
+ }
+
+ /*
+ * If the dividend is zero, then both quotient and
+ * remainder are zero.
+ */
+ if (ma == null && sa == 0) {
+ q = Zero;
+ r = Zero;
+ return;
+ }
+
+ /*
+ * If both dividend and divisor are small, then we
+ * use the native 64-bit integer types. If only the
+ * divisor is small, then we have a special fast case
+ * for division by 1 or -1.
+ */
+ if (ma == null && mb == null) {
+ q = new ZInt((long)sa / (long)sb);
+ r = new ZInt((long)sa % (long)sb);
+ return;
+ }
+ if (mb == null) {
+ if (sb == 1) {
+ q = a;
+ r = Zero;
+ return;
+ } else if (sb == -1) {
+ q = -a;
+ r = Zero;
+ return;
+ }
+ }
+
+ /*
+ * We know that the dividend is not 0, and the divisor
+ * is not -1, 0 or 1. We now want the sign+magnitude
+ * representations of both operands.
+ */
+ ToAbs(a, out sa, out ma);
+ ToAbs(b, out sb, out mb);
+
+ /*
+ * If the divisor is greater (in magnitude) than the
+ * dividend, then the quotient is zero and the remainder
+ * is equal to the dividend. If the divisor and dividend
+ * are equal in magnitude, then the remainder is zero and
+ * the quotient is 1 if divisor and dividend have the same
+ * sign, -1 otherwise.
+ */
+ int cc = Compare(ma, mb);
+ if (cc < 0) {
+ q = Zero;
+ r = a;
+ return;
+ } else if (cc == 0) {
+ q = (sa == sb) ? One : MinusOne;
+ r = Zero;
+ return;
+ }
+
+ /*
+ * At that point, we know that the divisor is not -1, 0
+ * or 1, and that the quotient will not be 0. We perform
+ * the unsigned division (with the magnitudes), then
+ * we adjust the signs.
+ */
+
+ uint[] mq, mr;
+ DivRem(ma, mb, out mq, out mr);
+
+ /*
+ * Quotient is positive if divisor and dividend have the
+ * same sign, negative otherwise. Remainder always has
+ * the sign of the dividend, but it may be zero.
+ */
+ q = Make(0, mq);
+ if (sa != sb) {
+ q = -q;
+ }
+ r = Make(0, mr);
+ if (sa < 0) {
+ r = -r;
+ }
+#if DEBUG
+ if (q * b + r != a) {
+ throw new Exception("division error");
+ }
+#endif
+ }
+
+ /*
+ * Division operator: see DivRem() for details.
+ */
+ public static ZInt operator /(ZInt a, ZInt b)
+ {
+ ZInt q, r;
+ DivRem(a, b, out q, out r);
+ return q;
+ }
+
+ /*
+ * Remainder operator: see DivRem() for details.
+ */
+ public static ZInt operator %(ZInt a, ZInt b)
+ {
+ ZInt q, r;
+ DivRem(a, b, out q, out r);
+ return r;
+ }
+
+ /*
+ * Reduce this value modulo the provided m. This differs from
+ * '%' in that the returned value is always in the 0 to abs(m)-1
+ * range.
+ */
+ public ZInt Mod(ZInt m)
+ {
+ ZInt r = this % m;
+ if (r.small < 0) {
+ if (m.small < 0) {
+ m = -m;
+ }
+ r += m;
+ }
+ return r;
+ }
+
+ /*
+ * Left-shift operator.
+ */
+ public static ZInt operator <<(ZInt a, int n)
+ {
+ if (n < 0) {
+ if (n == Int32.MinValue) {
+ return Zero;
+ }
+ return a >> (-n);
+ }
+ int sa = a.small;
+ uint[] ma = a.varray;
+ if (ma == null) {
+ if (n <= 32) {
+ return new ZInt((long)sa << n);
+ }
+ if (sa == 0) {
+ return Zero;
+ }
+ }
+ uint[] mr = new uint[GetLengthForLeftShift(a, n)];
+
+ /*
+ * Special case when the shift is a multiple of 32.
+ */
+ int kw = n >> 5;
+ int kb = n & 31;
+ if (kb == 0) {
+ if (ma == null) {
+ mr[kw] = (uint)sa;
+ return new ZInt(sa >> 31, mr);
+ } else {
+ Array.Copy(ma, 0, mr, kw, ma.Length);
+ return new ZInt(sa, mr);
+ }
+ }
+
+ /*
+ * At that point, we know that the source integer does
+ * not fit in a signed "int", or is shifted by more than
+ * 32 bits, or both. Either way, the result will not fit
+ * in an "int".
+ *
+ * We process all input words one by one.
+ */
+ uint rem = 0;
+ int ikb = 32 - kb;
+ int j;
+ if (ma == null) {
+ j = 1;
+ uint wa = (uint)sa;
+ mr[kw] = wa << kb;
+ rem = wa >> ikb;
+ } else {
+ j = ma.Length;
+ for (int i = 0; i < j; i ++) {
+ uint wa = ma[i];
+ mr[i + kw] = rem | (wa << kb);
+ rem = wa >> ikb;
+ }
+ }
+ sa >>= 31;
+#if DEBUG
+ if ((j + kw) == mr.Length - 1) {
+ if (rem == ((uint)sa >> ikb)) {
+ throw new Exception(
+ "Wrong left-shift: untrimmed");
+ }
+ } else if ((j + kw) == mr.Length) {
+ if (rem != ((uint)sa >> ikb)) {
+ throw new Exception(
+ "Wrong left-shift: dropped bits");
+ }
+ } else {
+ throw new Exception(
+ "Wrong left-shift: oversized output length");
+ }
+#endif
+ if ((j + kw) < mr.Length) {
+ mr[j + kw] = rem | ((uint)sa << kb);
+ }
+ return new ZInt(sa, mr);
+ }
+
+ /*
+ * Right-shift operator.
+ */
+ public static ZInt operator >>(ZInt a, int n)
+ {
+ if (n < 0) {
+ if (n == Int32.MinValue) {
+ throw new OverflowException();
+ }
+ return a << (-n);
+ }
+ int sa = a.small;
+ uint[] ma = a.varray;
+ if (ma == null) {
+ /*
+ * If right-shifting a "small" value, then we can
+ * do the computation with the native ">>" operator
+ * on "int" values, unless the shift count is 32
+ * or more, in which case we get either 0 or -1,
+ * depending on the source value sign.
+ */
+ if (n < 32) {
+ return new ZInt(sa >> n, null);
+ } else {
+ return new ZInt(sa >> 31, null);
+ }
+ }
+
+ /*
+ * At that point, we know that the source value uses
+ * a non-null varray. We compute the bit length of the
+ * result. If the result would fit in an "int" (bit length
+ * of 31 or less) then we handle it as a special case.
+ */
+ int kw = n >> 5;
+ int kb = n & 31;
+ int bl = a.BitLength - n;
+ if (bl <= 0) {
+ return new ZInt(sa, null);
+ } else if (bl <= 31) {
+ if (kb == 0) {
+ return new ZInt((int)ma[kw], null);
+ } else {
+ int p = ma.Length;
+ uint w0 = ma[kw];
+ uint w1 = (kw + 1) < p ? ma[kw + 1] : (uint)sa;
+ return new ZInt((int)((w0 >> kb)
+ | (w1 << (32 - kb))), null);
+ }
+ }
+
+ /*
+ * Result will require an array. Let's allocate it.
+ */
+ uint[] mr = new uint[(bl + 31) >> 5];
+
+ /*
+ * Special case when the shift is a multiple of 32.
+ */
+ if (kb == 0) {
+#if DEBUG
+ if (mr.Length != (ma.Length - kw)) {
+ throw new Exception(
+ "Wrong right-shift: output length");
+ }
+#endif
+ Array.Copy(ma, kw, mr, 0, ma.Length - kw);
+ return new ZInt(sa, mr);
+ }
+
+ /*
+ * We process all input words one by one.
+ */
+ int ikb = 32 - kb;
+ uint rem = ma[kw] >> kb;
+ int j = ma.Length;
+ for (int i = kw + 1; i < j; i ++) {
+ uint wa = ma[i];
+ mr[i - kw - 1] = rem | (wa << ikb);
+ rem = wa >> kb;
+ }
+#if DEBUG
+ if ((j - kw - 1) == mr.Length - 1) {
+ if (rem == ((uint)sa >> kb)) {
+ throw new Exception(
+ "Wrong right-shift: untrimmed");
+ }
+ } else if ((j - kw - 1) == mr.Length) {
+ if (rem != ((uint)sa >> kb)) {
+ throw new Exception(
+ "Wrong right-shift: dropped bits");
+ }
+ } else {
+ throw new Exception(
+ "Wrong right-shift: oversized output length");
+ }
+#endif
+ if ((j - kw - 1) < mr.Length) {
+ mr[j - kw - 1] = rem | ((uint)sa << ikb);
+ }
+ return new ZInt(sa, mr);
+ }
+
+ /*
+ * NOTES ON BITWISE BOOLEAN OPERATIONS
+ *
+ * When both operands are "small" then the result is necessarily
+ * small: in "small" encoding, all bits beyond bit 31 of the
+ * two's complement encoding are equal to bit 31, so the result
+ * of computing the operation on bits 31 will also be valid for
+ * all subsequent bits. Therefore, when the two operands are
+ * small, we can just do the operation on the "int" values and the
+ * result is guaranteed "small" as well.
+ */
+
+ /*
+ * Compute the bitwise AND between a "big" and a "small" values.
+ */
+ static ZInt AndSmall(int s, uint[] m, int x)
+ {
+ if (x < 0) {
+ uint[] r = Dup(m);
+ r[0] &= (uint)x;
+ return new ZInt(s, r);
+ } else {
+ return new ZInt(x & (int)m[0], null);
+ }
+ }
+
+ /*
+ * Bitwise AND operator.
+ */
+ public static ZInt operator &(ZInt a, ZInt b)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+ if (ma == null) {
+ if (mb == null) {
+ return new ZInt(sa & sb, null);
+ } else {
+ return AndSmall(sb, mb, sa);
+ }
+ } else if (mb == null) {
+ return AndSmall(sa, ma, sb);
+ }
+
+ /*
+ * Both values are big. Since upper zero bits force the
+ * result to contain zeros, the result size is that of the
+ * positive operand (the smallest of the two if both are
+ * positive). If both operands are negative, then the
+ * result magnitude may be as large as the largest of the
+ * two source magnitudes.
+ *
+ * Result is negative if and only if both operands are
+ * negative.
+ */
+ int na = ma.Length;
+ int nb = mb.Length;
+ int nr;
+ if (sa >= 0) {
+ if (sb >= 0) {
+ nr = Math.Min(na, nb);
+ } else {
+ nr = na;
+ }
+ } else {
+ if (sb >= 0) {
+ nr = nb;
+ } else {
+ nr = Math.Max(na, nb);
+ }
+ }
+ uint[] mr = new uint[nr];
+ for (int i = 0; i < nr; i ++) {
+ uint wa = i < na ? ma[i] : (uint)sa;
+ uint wb = i < nb ? mb[i] : (uint)sb;
+ mr[i] = wa & wb;
+ }
+ return Make(sa & sb, mr);
+ }
+
+ /*
+ * Compute the bitwise OR between a "big" value (sign and
+ * magnitude, already normalized/trimmed), and a small value
+ * (which can be positive or negative).
+ */
+ static ZInt OrSmall(int s, uint[] m, int x)
+ {
+ if (x < 0) {
+ return new ZInt(x | (int)m[0], null);
+ } else {
+ uint[] r = Dup(m);
+ r[0] |= (uint)x;
+ return new ZInt(s, r);
+ }
+ }
+
+ /*
+ * Bitwise OR operator.
+ */
+ public static ZInt operator |(ZInt a, ZInt b)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+ if (ma == null) {
+ if (mb == null) {
+ return new ZInt(sa | sb, null);
+ } else {
+ return OrSmall(sb, mb, sa);
+ }
+ } else if (mb == null) {
+ return OrSmall(sa, ma, sb);
+ }
+
+ /*
+ * Both values are big. Since upper one bits force the
+ * result to contain ones, the result size is that of
+ * the negative operand (the greater, i.e. "smallest" of
+ * the two if both are negative). If both operands are
+ * positive, then the result magnitude may be as large
+ * as the largest of the two source magnitudes.
+ *
+ * Result is positive if and only if both operands are
+ * positive.
+ */
+ int na = ma.Length;
+ int nb = mb.Length;
+ int nr;
+ if (sa >= 0) {
+ if (sb >= 0) {
+ nr = Math.Max(na, nb);
+ } else {
+ nr = nb;
+ }
+ } else {
+ if (sb >= 0) {
+ nr = na;
+ } else {
+ nr = Math.Min(na, nb);
+ }
+ }
+ uint[] mr = new uint[nr];
+ for (int i = 0; i < nr; i ++) {
+ uint wa = i < na ? ma[i] : (uint)sa;
+ uint wb = i < nb ? mb[i] : (uint)sb;
+ mr[i] = wa | wb;
+ }
+ return Make(sa | sb, mr);
+ }
+
+ /*
+ * Bitwise XOR operator.
+ */
+ public static ZInt operator ^(ZInt a, ZInt b)
+ {
+ int sa = a.small;
+ int sb = b.small;
+ uint[] ma = a.varray;
+ uint[] mb = b.varray;
+ if (ma == null && mb == null) {
+ return new ZInt(sa ^ sb, null);
+ }
+ if (ma == null) {
+ int st = sa;
+ sa = sb;
+ sb = st;
+ uint[] mt = ma;
+ ma = mb;
+ mb = mt;
+ }
+ if (mb == null) {
+ int nx = ma.Length;
+ uint[] mx = new uint[nx];
+ mx[0] = ma[0] ^ (uint)sb;
+ if (nx > 1) {
+ if (sb < 0) {
+ for (int i = 1; i < nx; i ++) {
+ mx[i] = ~ma[i];
+ }
+ } else {
+ Array.Copy(ma, 1, mx, 1, nx - 1);
+ }
+ }
+ return Make(sa ^ (sb >> 31), mx);
+ }
+
+ /*
+ * Both operands use varrays.
+ * Result can be as big as the bigger of the two operands
+ * (it _will_ be that big, necessarily, if the two operands
+ * have distinct sizes).
+ */
+ int na = ma.Length;
+ int nb = mb.Length;
+ int nr = Math.Max(na, nb);
+ uint[] mr = new uint[nr];
+ for (int i = 0; i < nr; i ++) {
+ uint wa = i < na ? ma[i] : (uint)sa;
+ uint wb = i < nb ? mb[i] : (uint)sb;
+ mr[i] = wa ^ wb;
+ }
+ return Make((sa ^ sb) >> 31, mr);
+ }
+
+ /*
+ * Bitwise inversion operator.
+ */
+ public static ZInt operator ~(ZInt a)
+ {
+ int s = a.small;
+ uint[] m = a.varray;
+ if (m == null) {
+ return new ZInt(~s, null);
+ } else {
+ int n = m.Length;
+ uint[] r = new uint[n];
+ for (int i = 0; i < n; i ++) {
+ r[i] = ~m[i];
+ }
+ return new ZInt(~s, r);
+ }
+ }
+
+ /*
+ * Basic comparison; returned value is -1, 0 or 1 depending on
+ * whether this instance is to be considered lower then, equal
+ * to, or greater than the provided object.
+ *
+ * All ZInt instances are considered greater than 'null', and
+ * lower than any non-null object that is not a ZInt.
+ */
+ public int CompareTo(object obj)
+ {
+ if (obj == null) {
+ return 1;
+ }
+ if (!(obj is ZInt)) {
+ return -1;
+ }
+ return CompareTo((ZInt)obj);
+ }
+
+ /*
+ * Basic comparison; returned value is -1, 0 or 1 depending on
+ * whether this instance is to be considered lower then, equal
+ * to, or greater than the provided value.
+ */
+ public int CompareTo(ZInt v)
+ {
+ int sv = v.small;
+ uint[] mv = v.varray;
+ int sign1 = small >> 31;
+ int sign2 = sv >> 31;
+ if (sign1 != sign2) {
+ /*
+ * One of the sign* values is -1, the other is 0.
+ */
+ return sign1 - sign2;
+ }
+
+ /*
+ * Both values have the same sign. Since the varrays are
+ * trimmed, we can use their presence and length to
+ * quickly resolve most cases.
+ */
+ if (small < 0) {
+ if (varray == null) {
+ if (mv == null) {
+ return Compare(small, sv);
+ } else {
+ return 1;
+ }
+ } else {
+ if (mv == null) {
+ return -1;
+ } else {
+ int n1 = varray.Length;
+ int n2 = mv.Length;
+ if (n1 < n2) {
+ return 1;
+ } else if (n1 == n2) {
+ return Compare(varray, mv);
+ } else {
+ return -1;
+ }
+ }
+ }
+ } else {
+ if (varray == null) {
+ if (mv == null) {
+ return Compare(small, sv);
+ } else {
+ return -1;
+ }
+ } else {
+ if (mv == null) {
+ return 1;
+ } else {
+ return Compare(varray, mv);
+ }
+ }
+ }
+ }
+
+ /*
+ * Equality comparison: a ZInt instance is equal only to another
+ * ZInt instance that encodes the same integer value.
+ */
+ public override bool Equals(object obj)
+ {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj is ZInt)) {
+ return false;
+ }
+ return CompareTo((ZInt)obj) == 0;
+ }
+
+ /*
+ * Equality comparison: a ZInt instance is equal only to another
+ * ZInt instance that encodes the same integer value.
+ */
+ public bool Equals(ZInt v)
+ {
+ return CompareTo(v) == 0;
+ }
+
+ /*
+ * The hash code for a ZInt is equal to its lower 32 bits.
+ */
+ public override int GetHashCode()
+ {
+ if (varray == null) {
+ return small;
+ } else {
+ return (int)varray[0];
+ }
+ }
+
+ /*
+ * Equality operator.
+ */
+ public static bool operator ==(ZInt a, ZInt b)
+ {
+ return a.CompareTo(b) == 0;
+ }
+
+ /*
+ * Inequality operator.
+ */
+ public static bool operator !=(ZInt a, ZInt b)
+ {
+ return a.CompareTo(b) != 0;
+ }
+
+ /*
+ * Lower-than operator.
+ */
+ public static bool operator <(ZInt a, ZInt b)
+ {
+ return a.CompareTo(b) < 0;
+ }
+
+ /*
+ * Lower-or-equal operator.
+ */
+ public static bool operator <=(ZInt a, ZInt b)
+ {
+ return a.CompareTo(b) <= 0;
+ }
+
+ /*
+ * Greater-than operator.
+ */
+ public static bool operator >(ZInt a, ZInt b)
+ {
+ return a.CompareTo(b) > 0;
+ }
+
+ /*
+ * Greater-or-equal operator.
+ */
+ public static bool operator >=(ZInt a, ZInt b)
+ {
+ return a.CompareTo(b) >= 0;
+ }
+
+ /*
+ * Power function: this raises x to the power e. The exponent e
+ * MUST NOT be negative. If x and e are both zero, then 1 is
+ * returned.
+ */
+ public static ZInt Pow(ZInt x, int e)
+ {
+ if (e < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (e == 0) {
+ return One;
+ }
+ if (e == 1 || x.IsZero || x.IsOne) {
+ return x;
+ }
+ bool neg = false;
+ if (x.Sign < 0) {
+ x = -x;
+ neg = (e & 1) != 0;
+ }
+ if (x.IsPowerOfTwo) {
+ int t = x.BitLength - 1;
+ long u = (long)t * (long)e;
+ if (u > (long)Int32.MaxValue) {
+ throw new OverflowException();
+ }
+ x = One << (int)u;
+ } else {
+ ZInt y = One;
+ for (;;) {
+ if ((e & 1) != 0) {
+ y *= x;
+ }
+ e >>= 1;
+ if (e == 0) {
+ break;
+ }
+ x *= x;
+ }
+ x = y;
+ }
+ return neg ? -x : x;
+ }
+
+ /*
+ * Modular exponentation: this function raises v to the power e
+ * modulo m. The returned value is reduced modulo m: it will be
+ * in the 0 to abs(m)-1 range.
+ *
+ * The modulus m must be positive. If m is 1, then the result is
+ * 0 (regardless of the values of v and e).
+ *
+ * The exponent e must be nonnegative (this function does not
+ * compute modular inverses). If e is zero, then the result is 1
+ * (except if m is 1).
+ */
+ public static ZInt ModPow(ZInt v, ZInt e, ZInt m)
+ {
+ int se = e.Sign;
+ if (se < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ int sm = m.Sign;
+ if (sm < 0) {
+ m = -m;
+ } else if (sm == 0) {
+ throw new DivideByZeroException();
+ }
+ if (m.varray == null && m.small == 1) {
+ return Zero;
+ }
+ if (se == 0) {
+ return One;
+ }
+ if (v.IsZero) {
+ return Zero;
+ }
+
+ // TODO: use Montgomery's multiplication when the exponent
+ // is large.
+ ZInt x = v.Mod(m);
+ for (int n = e.BitLength - 2; n >= 0; n --) {
+ x = (x * x).Mod(m);
+ if (e.TestBit(n)) {
+ x = (x * v).Mod(m);
+ }
+ }
+ return x;
+ }
+
+ /*
+ * Get the absolute value of a ZInt.
+ */
+ public static ZInt Abs(ZInt x)
+ {
+ return (x.Sign < 0) ? -x : x;
+ }
+
+ private static void AppendHex(StringBuilder sb, uint w, bool trim)
+ {
+ int i = 28;
+ if (trim) {
+ for (; i >= 0 && (w >> i) == 0; i -= 4);
+ if (i < 0) {
+ sb.Append("0");
+ return;
+ }
+ }
+ for (; i >= 0; i -= 4) {
+ sb.Append("0123456789ABCDEF"[(int)((w >> i) & 0x0F)]);
+ }
+ }
+
+ /*
+ * Convert this value to hexadecimal. If this instance is zero,
+ * then "0" is returned. Otherwise, the number of digits is
+ * minimal (no leading '0'). A leading '-' is used for negative
+ * values. Hexadecimal digits are uppercase.
+ */
+ public string ToHexString()
+ {
+ if (varray == null && small == 0) {
+ return "0";
+ }
+ StringBuilder sb = new StringBuilder();
+ ZInt x = this;
+ if (x.small < 0) {
+ sb.Append('-');
+ x = -x;
+ }
+ if (x.varray == null) {
+ AppendHex(sb, (uint)x.small, true);
+ } else {
+ int n = x.varray.Length;
+ AppendHex(sb, x.varray[n - 1], true);
+ for (int j = n - 2; j >= 0; j --) {
+ AppendHex(sb, x.varray[j], false);
+ }
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Convert this value to decimal. A leading '-' sign is used for
+ * negative value. The number of digits is minimal (no leading '0',
+ * except for zero, which is returned as "0").
+ */
+ public override string ToString()
+ {
+ return ToString(10);
+ }
+
+ private static int[] NDIGITS32 = {
+ 0, 0, 31, 20, 15, 13, 12, 11, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7,
+ 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6
+ };
+
+ private static uint[] RADIXPP32 = {
+ 0, 0, 2147483648, 3486784401, 1073741824, 1220703125,
+ 2176782336, 1977326743, 1073741824, 3486784401, 1000000000,
+ 2357947691, 429981696, 815730721, 1475789056, 2562890625,
+ 268435456, 410338673, 612220032, 893871739, 1280000000,
+ 1801088541, 2494357888, 3404825447, 191102976, 244140625,
+ 308915776, 387420489, 481890304, 594823321, 729000000,
+ 887503681, 1073741824, 1291467969, 1544804416, 1838265625,
+ 2176782336
+ };
+
+ private static char[] DCHAR =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
+
+ static void AppendDigits(StringBuilder sb, uint v, int radix, int num)
+ {
+ while (num -- > 0) {
+ sb.Append(DCHAR[v % (uint)radix]);
+ v = v / (uint)radix;
+ }
+ }
+
+ static void AppendDigits(StringBuilder sb, uint v, int radix)
+ {
+ while (v > 0) {
+ sb.Append(DCHAR[v % (uint)radix]);
+ v = v / (uint)radix;
+ }
+ }
+
+ /*
+ * Convert this value to a string, in the provided radix. The
+ * radix must be in the 2 to 36 range (inclusive); uppercase
+ * letters 'A' to 'Z' are used for digits of value 10 to 35.
+ * If the value is zero, then "0" is returned; otherwise, leading
+ * '0' digits are removed. A leading '-' sign is added for negative
+ * values.
+ */
+ public string ToString(int radix)
+ {
+ if (radix < 2 || radix > 36) {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ /*
+ * Special optimized case for base 16.
+ */
+ if (radix == 16) {
+ return ToHexString();
+ }
+
+ if (IsZero) {
+ return "0";
+ }
+
+ ZInt x = this;
+ if (x.Sign < 0) {
+ x = -x;
+ }
+ StringBuilder sb = new StringBuilder();
+ if (x.varray == null) {
+ AppendDigits(sb, (uint)x.small, radix);
+ } else {
+ uint[] m = new uint[x.varray.Length];
+ Array.Copy(x.varray, 0, m, 0, m.Length);
+ uint prad = RADIXPP32[radix];
+ int dnum = NDIGITS32[radix];
+ for (;;) {
+ uint v = MutateDivRem(m, prad);
+ bool qz = Length(0, m) == 0;
+ if (qz) {
+ AppendDigits(sb, v, radix);
+ break;
+ } else {
+ AppendDigits(sb, v, radix, dnum);
+ }
+ }
+ }
+ if (Sign < 0) {
+ sb.Append('-');
+ }
+ return Reverse(sb);
+ }
+
+ static string Reverse(StringBuilder sb)
+ {
+ int n = sb.Length;
+ char[] tc = new char[n];
+ sb.CopyTo(0, tc, 0, n);
+ for (int i = 0, j = n - 1; i < j; i ++, j --) {
+ char c = tc[i];
+ tc[i] = tc[j];
+ tc[j] = c;
+ }
+ return new string(tc);
+ }
+
+ static uint DigitValue(char c, int radix)
+ {
+ int d;
+ if (c >= '0' && c <= '9') {
+ d = c - '0';
+ } else if (c >= 'a' && c <= 'z') {
+ d = (c - 'a') + 10;
+ } else if (c >= 'A' && c <= 'Z') {
+ d = (c - 'A') + 10;
+ } else {
+ d = -1;
+ }
+ if (d < 0 || d >= radix) {
+ throw new ArgumentException();
+ }
+ return (uint)d;
+ }
+
+ static ZInt ParseUnsigned(string s, int radix)
+ {
+ if (s.Length == 0) {
+ throw new ArgumentException();
+ }
+ ZInt x = Zero;
+ uint acc = 0;
+ int accNum = 0;
+ uint prad = RADIXPP32[radix];
+ int dnum = NDIGITS32[radix];
+ foreach (char c in s) {
+ uint d = DigitValue(c, radix);
+ acc = acc * (uint)radix + d;
+ if (++ accNum == dnum) {
+ x = x * (ZInt)prad + (ZInt)acc;
+ acc = 0;
+ accNum = 0;
+ }
+ }
+ if (accNum > 0) {
+ uint p = 1;
+ while (accNum -- > 0) {
+ p *= (uint)radix;
+ }
+ x = x * (ZInt)p + (ZInt)acc;
+ }
+ return x;
+ }
+
+ /*
+ * Parse a string:
+ * -- A leading '-' is allowed, to denote a negative value.
+ * -- If there is a "0b" or "0B" header (after any '-' sign),
+ * then the value is interpreted in base 2.
+ * -- If there is a "0x" or "0X" header (after any '-' sign),
+ * then the value is interpreted in base 16 (hexadecimal).
+ * Both uppercase and lowercase letters are accepted.
+ * -- If there is no header, then decimal interpretation is used.
+ *
+ * Unexpected characters (including spaces) trigger exceptions.
+ * There must be at least one digit.
+ */
+ public static ZInt Parse(string s)
+ {
+ s = s.Trim();
+ bool neg = false;
+ if (s.StartsWith("-")) {
+ neg = true;
+ s = s.Substring(1);
+ }
+ int radix;
+ if (s.StartsWith("0b") || s.StartsWith("0B")) {
+ radix = 2;
+ s = s.Substring(2);
+ } else if (s.StartsWith("0x") || s.StartsWith("0X")) {
+ radix = 16;
+ s = s.Substring(2);
+ } else {
+ radix = 10;
+ }
+ ZInt x = ParseUnsigned(s, radix);
+ return neg ? -x : x;
+ }
+
+ /*
+ * Parse a string in the specified radix. The radix must be in
+ * the 2 to 36 range (inclusive). Uppercase and lowercase letters
+ * are accepted for digits in the 10 to 35 range.
+ *
+ * A leading '-' sign is allowed, to denote a negative value.
+ * Otherwise, only digits (acceptable with regards to the radix)
+ * may appear. There must be at least one digit.
+ */
+ public static ZInt Parse(string s, int radix)
+ {
+ if (radix < 2 || radix > 36) {
+ throw new ArgumentOutOfRangeException();
+ }
+ s = s.Trim();
+ bool neg = false;
+ if (s.StartsWith("-")) {
+ neg = true;
+ s = s.Substring(1);
+ }
+ ZInt x = ParseUnsigned(s, radix);
+ return neg ? -x : x;
+ }
+
+ static uint DecU32BE(byte[] buf, int off, int len)
+ {
+ switch (len) {
+ case 0:
+ return 0;
+ case 1:
+ return buf[off];
+ case 2:
+ return ((uint)buf[off] << 8)
+ | (uint)buf[off + 1];
+ case 3:
+ return ((uint)buf[off] << 16)
+ | ((uint)buf[off + 1] << 8)
+ | (uint)buf[off + 2];
+ default:
+ return ((uint)buf[off] << 24)
+ | ((uint)buf[off + 1] << 16)
+ | ((uint)buf[off + 2] << 8)
+ | (uint)buf[off + 3];
+ }
+ }
+
+ /*
+ * Decode an integer, assuming unsigned big-endian encoding.
+ * An empty array is decoded as 0.
+ */
+ public static ZInt DecodeUnsignedBE(byte[] buf)
+ {
+ return DecodeUnsignedBE(buf, 0, buf.Length);
+ }
+
+ /*
+ * Decode an integer, assuming unsigned big-endian encoding.
+ * An empty array is decoded as 0.
+ */
+ public static ZInt DecodeUnsignedBE(byte[] buf, int off, int len)
+ {
+ while (len > 0 && buf[off] == 0) {
+ off ++;
+ len --;
+ }
+ if (len == 0) {
+ return Zero;
+ } else if (len <= 4) {
+ return new ZInt(DecU32BE(buf, off, len));
+ }
+ uint[] m = new uint[(len + 3) >> 2];
+ int i = 0;
+ for (int j = len; j > 0; j -= 4) {
+ int k = j - 4;
+ uint w;
+ if (k < 0) {
+ w = DecU32BE(buf, off, j);
+ } else {
+ w = DecU32BE(buf, off + k, 4);
+ }
+ m[i ++] = w;
+ }
+ return new ZInt(0, m);
+ }
+
+ static RNGCryptoServiceProvider RNG = new RNGCryptoServiceProvider();
+
+ /*
+ * Create a random integer of the provided size. Returned value
+ * is in the 0 (inclusive) to 2^size (exclusive) range. A
+ * cryptographically strong RNG is used to ensure uniform selection.
+ */
+ public static ZInt MakeRand(int size)
+ {
+ if (size <= 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ byte[] buf = new byte[(size + 7) >> 3];
+ RNG.GetBytes(buf);
+ int kb = size & 7;
+ if (kb != 0) {
+ buf[0] &= (byte)(0xFF >> (8 - kb));
+ }
+ return DecodeUnsignedBE(buf);
+ }
+
+ /*
+ * Create a random integer in the 0 (inclusive) to max (exclusive)
+ * range. 'max' must be positive. A cryptographically strong RNG
+ * is used to ensure uniform selection.
+ */
+ public static ZInt MakeRand(ZInt max)
+ {
+ if (max.Sign <= 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ int bl = max.BitLength;
+ for (;;) {
+ ZInt x = MakeRand(bl);
+ if (x < max) {
+ return x;
+ }
+ }
+ }
+
+ /*
+ * Create a random integer in the min (inclusive) to max (exclusive)
+ * range. 'max' must be greater than min. A cryptographically
+ * strong RNG is used to ensure uniform selection.
+ */
+ public static ZInt MakeRand(ZInt min, ZInt max)
+ {
+ if (max <= min) {
+ throw new ArgumentOutOfRangeException();
+ }
+ return min + MakeRand(max - min);
+ }
+
+ /*
+ * Check whether this integer is prime. A probabilistic algorithm
+ * is used, that theoretically ensures that a non-prime won't be
+ * declared prime with probability greater than 2^(-100). Note that
+ * this holds regardless of how the integer was generated (this
+ * method does not assume that uniform random selection was used).
+ *
+ * (Realistically, the probability of a computer hardware
+ * malfunction is way greater than 2^(-100), so this property
+ * returns the primality status with as much certainty as can be
+ * achieved with a computer.)
+ */
+ public bool IsPrime {
+ get {
+ return IsProbablePrime(50);
+ }
+ }
+
+ static uint[] PRIMES_BF = new uint[] {
+ 0xA08A28AC, 0x28208A20, 0x02088288, 0x800228A2,
+ 0x20A00A08, 0x80282088, 0x800800A2, 0x08028228,
+ 0x0A20A082, 0x22880020, 0x28020800, 0x88208082,
+ 0x02022020, 0x08828028, 0x8008A202, 0x20880880
+ };
+
+ private bool IsProbablePrime(int rounds)
+ {
+ ZInt x = this;
+ int cc = x.Sign;
+ if (cc == 0) {
+ return false;
+ } else if (cc < 0) {
+ x = -x;
+ }
+ if (x.varray == null) {
+ if (x.small < (PRIMES_BF.Length << 5)) {
+ return (PRIMES_BF[x.small >> 5]
+ & ((uint)1 << (x.small & 31))) != 0;
+ }
+ }
+ if (!x.TestBit(0)) {
+ return false;
+ }
+
+ ZInt xm1 = x;
+ xm1 --;
+ ZInt m = xm1;
+ int a;
+ for (a = 0; !m.TestBit(a); a ++);
+ m >>= a;
+ while (rounds -- > 0) {
+ ZInt b = MakeRand(Two, x);
+ ZInt z = ModPow(b, m, x);
+ for (int j = 0; j < a; j ++) {
+ if (z == One) {
+ if (j > 0) {
+ return false;
+ }
+ break;
+ }
+ if (z == xm1) {
+ break;
+ }
+ if ((j + 1) < a) {
+ z = (z * z) % x;
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Encode this integer as bytes (signed big-endian convention).
+ * Encoding is of minimal length that still contains a sign bit
+ * (compatible with ASN.1 DER encoding).
+ */
+ public byte[] ToBytesBE()
+ {
+ byte[] r = new byte[(BitLength + 8) >> 3];
+ ToBytesBE(r, 0, r.Length);
+ return r;
+ }
+
+ /*
+ * Encode this integer as bytes (signed little-endian convention).
+ * Encoding is of minimal length that still contains a sign bit.
+ */
+ public byte[] ToBytesLE()
+ {
+ byte[] r = new byte[(BitLength + 8) >> 3];
+ ToBytesLE(r, 0, r.Length);
+ return r;
+ }
+
+ /*
+ * Encode this integer as bytes (signed big-endian convention).
+ * Output length is provided; exactly that many bytes will be
+ * written. The value is sign-extended or truncated if needed.
+ */
+ public void ToBytesBE(byte[] buf, int off, int len)
+ {
+ ToBytes(true, buf, off, len);
+ }
+
+ /*
+ * Encode this integer as bytes (signed little-endian convention).
+ * Output length is provided; exactly that many bytes will be
+ * written. The value is sign-extended or truncated if needed.
+ */
+ public void ToBytesLE(byte[] buf, int off, int len)
+ {
+ ToBytes(false, buf, off, len);
+ }
+
+ /*
+ * Encode this integer as bytes (unsigned big-endian convention).
+ * Encoding is of minimal length, possibly without a sign bit. If
+ * this value is zero, then an empty array is returned. If this
+ * value is negative, then an ArgumentOutOfRangeException is thrown.
+ */
+ public byte[] ToBytesUnsignedBE()
+ {
+ if (Sign < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ byte[] r = new byte[(BitLength + 7) >> 3];
+ ToBytesBE(r, 0, r.Length);
+ return r;
+ }
+
+ /*
+ * Encode this integer as bytes (unsigned little-endian convention).
+ * Encoding is of minimal length, possibly without a sign bit. If
+ * this value is zero, then an empty array is returned. If this
+ * value is negative, then an ArgumentOutOfRangeException is thrown.
+ */
+ public byte[] ToBytesUnsignedLE()
+ {
+ if (Sign < 0) {
+ throw new ArgumentOutOfRangeException();
+ }
+ byte[] r = new byte[(BitLength + 7) >> 3];
+ ToBytesLE(r, 0, r.Length);
+ return r;
+ }
+
+ void ToBytes(bool be, byte[] buf, int off, int len)
+ {
+ uint iw = (uint)small >> 31;
+ for (int i = 0; i < len; i ++) {
+ int j = i >> 2;
+ uint w;
+ if (varray == null) {
+ w = (j == 0) ? (uint)small : iw;
+ } else {
+ w = (j < varray.Length) ? varray[j] : iw;
+ }
+ byte v = (byte)(w >> ((i & 3) << 3));
+ if (be) {
+ buf[off + len - 1 - i] = v;
+ } else {
+ buf[off + i] = v;
+ }
+ }
+ }
+}
--- /dev/null
+echo "Twrch..."
+%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:exe /out:Twrch.exe /main:Twrch Asn1\*.cs Crypto\*.cs SSLTLS\*.cs X500\*.cs XKeys\*.cs Twrch\*.cs
+
+echo "TestCrypto..."
+%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:exe /out:TestCrypto.exe /main:TestCrypto Tests\*.cs Asn1\*.cs Crypto\*.cs SSLTLS\*.cs X500\*.cs XKeys\*.cs ZInt\*.cs
--- /dev/null
+#! /bin/sh
+
+CSC=$(which mono-csc || which dmcs || which mcs || echo "none")
+
+if [ $CSC = "none" ]; then
+ echo "Error: please install mono-devel."
+ exit 1
+fi
+
+set -e
+
+echo "TestCrypto..."
+$CSC /out:TestCrypto.exe /main:TestCrypto Tests/*.cs Asn1/*.cs Crypto/*.cs SSLTLS/*.cs X500/*.cs XKeys/*.cs ZInt/*.cs
+
+#echo "Client..."
+#$CSC /out:Client.exe /main:Client Asn1/*.cs Crypto/*.cs SSLTLS/*.cs X500/*.cs XKeys/*.cs Client.cs
+
+#echo "Server..."
+#$CSC /out:Server.exe /main:Server Asn1/*.cs Crypto/*.cs SSLTLS/*.cs X500/*.cs XKeys/*.cs Server.cs
+
+echo "Twrch..."
+$CSC /out:Twrch.exe /main:Twrch Asn1/*.cs Crypto/*.cs SSLTLS/*.cs X500/*.cs XKeys/*.cs Twrch/*.cs
--- /dev/null
+{
+ "commandFile" : "../build/brssl",
+ "commandArgs" : "twrch {0}",
+ "chainRSA" : "conf/rsacert.pem",
+ "skeyRSA" : "conf/rsakey.pem",
+ "chainEC" : "conf/eccert.pem",
+ "skeyEC" : "conf/eckey.pem",
+ "noCloseNotify" : false,
+ "versions" : [
+ "TLS10", "TLS11", "TLS12"
+ ],
+ "cipherSuites" : [
+ "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
+ "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+ "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+ "ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
+ "ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+ "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
+ "ECDHE_RSA_WITH_AES_128_CBC_SHA256",
+ "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
+ "ECDHE_RSA_WITH_AES_256_CBC_SHA384",
+ "ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
+ "ECDHE_RSA_WITH_AES_128_CBC_SHA",
+ "ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
+ "ECDHE_RSA_WITH_AES_256_CBC_SHA",
+
+ "ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ "ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
+ "ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
+ "ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
+ "ECDH_ECDSA_WITH_AES_128_CBC_SHA",
+ "ECDH_ECDSA_WITH_AES_256_CBC_SHA",
+
+ "RSA_WITH_AES_128_GCM_SHA256",
+ "RSA_WITH_AES_256_GCM_SHA384",
+ "RSA_WITH_AES_128_CBC_SHA256",
+ "RSA_WITH_AES_256_CBC_SHA256",
+ "RSA_WITH_AES_128_CBC_SHA",
+ "RSA_WITH_AES_256_CBC_SHA",
+ "RSA_WITH_3DES_EDE_CBC_SHA",
+
+ "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
+ "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
+ "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"
+ ],
+ "hashAndSigns" : [
+ "RSA_SHA256",
+ "RSA_SHA224",
+ "RSA_SHA384",
+ "RSA_SHA512",
+ "RSA_SHA1",
+ "ECDSA_SHA256",
+ "ECDSA_SHA224",
+ "ECDSA_SHA384",
+ "ECDSA_SHA512",
+ "ECDSA_SHA1"
+ ],
+ "curves" : [
+ "Curve25519",
+ "NIST_P256",
+ "NIST_P384",
+ "NIST_P521"
+ ],
+ "tests" : [
+ {
+ "name" : "peerClose",
+ "comment" : "The peer should initiate a clean close",
+ "askClose" : "true"
+ },
+ {
+ "name" : "renegotiateNormal",
+ "comment" : "Normal renegotiation triggered from our side",
+ "renegotiate" : "true"
+ },
+ {
+ "name" : "peerRenegotiateNormal",
+ "comment" : "Normal renegotiation triggered by the peer",
+ "askRenegotiate" : "true"
+ },
+ {
+ "name" : "noSecureReneg",
+ "comment" : "Not sending secure renegotiation; renegotiation attempts should be rejected by the peer.",
+ "renegotiate" : "false",
+ "quirks" : {
+ "noSecureReneg" : "true"
+ }
+ },
+ {
+ "name" : "forceEmptySecureReneg",
+ "comment" : "Forcing empty Secure Renegotiation extension. This should be OK for first handshake, then fail during renegotiation.",
+ "renegotiate" : "true",
+ "expectedExitCode" : 1,
+ "expectedFailure" : "Unexpected transport closure",
+ "quirks" : {
+ "forceEmptySecureReneg" : "true"
+ }
+ },
+ {
+ "name" : "forceNonEmptySecureReneg",
+ "comment" : "A non-empty Secure Renegotiation extension is sent during the first handshake. The peer should call foul play and abort.",
+ "expectedExitCode" : 1,
+ "expectedFailure" : "Unexpected transport closure",
+ "quirks" : {
+ "forceNonEmptySecureReneg" : "true"
+ }
+ },
+ {
+ "name" : "alterNonEmptySecureReneg",
+ "comment" : "The Secure Renegotiation extension contents are altered during second handshake (but the length is preserved). The peer should abort.",
+ "renegotiate" : "true",
+ "expectedExitCode" : 1,
+ "expectedFailure" : "Unexpected transport closure",
+ "quirks" : {
+ "alterNonEmptySecureReneg" : "true"
+ }
+ },
+ {
+ "name" : "oversizedSecureReneg",
+ "comment" : "The Secure Renegotiation extension contents are much bigger than normal. The peer should abort.",
+ "expectedExitCode" : 1,
+ "expectedFailure" : "Unexpected transport closure",
+ "quirks" : {
+ "oversizedSecureReneg" : "true"
+ }
+ },
+ {
+ "name" : "recordSplitHalf",
+ "comment" : "All records of length 2 or more are split into two halves.",
+ "quirks" : {
+ "recordSplitMode" : "half:20,21,22,23"
+ }
+ },
+ {
+ "name" : "recordSplitZeroBefore",
+ "comment" : "All records are preceded with a zero-length record.",
+ "quirks" : {
+ "recordSplitMode" : "zero_before:20,21,22,23"
+ }
+ },
+ {
+ "name" : "recordSplitZeroHalf",
+ "comment" : "All records of length 2 or more are split into two halves, and a zero-length record is inserted between the two halves..",
+ "quirks" : {
+ "recordSplitMode" : "zero_half:20,21,22,23"
+ }
+ },
+ {
+ "name" : "recordSplitOneStart",
+ "comment" : "The first byte of each record of length 2 or more is separated into its own record.",
+ "quirks" : {
+ "recordSplitMode" : "one_start:20,21,22,23"
+ }
+ },
+ {
+ "name" : "recordSplitOneEnd",
+ "comment" : "The last byte of each record of length 2 or more is separated into its own record.",
+ "quirks" : {
+ "recordSplitMode" : "one_end:20,21,22,23"
+ }
+ },
+ {
+ "name" : "recordSplitMultiOne",
+ "comment" : "All records are split into individual records of length 1.",
+ "quirks" : {
+ "recordSplitMode" : "multi_one:20,21,22,23"
+ }
+ },
+ {
+ "name" : "emptyHandshake1",
+ "comment" : "An extra empty handshake message is inserted before the first application data record.",
+ "quirks" : {
+ "thresholdZeroHandshake" : 1
+ }
+ },
+ {
+ "name" : "emptyHandshake2",
+ "comment" : "An extra empty handshake message is inserted before the second application data record.",
+ "quirks" : {
+ "thresholdZeroHandshake" : 2
+ }
+ },
+ {
+ "name" : "emptyAppData1",
+ "comment" : "An extra empty handshake message is inserted before the first handshake record.",
+ "quirks" : {
+ "thresholdZeroAppData" : 1
+ }
+ },
+ {
+ "name" : "emptyAppData2",
+ "comment" : "An extra empty handshake message is inserted before the second handshake record.",
+ "quirks" : {
+ "thresholdZeroAppData" : 2
+ }
+ },
+ {
+ "name" : "extraServerExtension",
+ "comment" : "An extra extension is added in the ServerHello. Client should reject it. BearSSL closes the connection, so the server gets an unexpected transport closure.",
+ "clientOnly" : "true",
+ "expectedExitCode" : 1,
+ "expectedFailure" : "Unexpected transport closure",
+ "quirks" : {
+ "sendExtraExtension" : "0xA7C0"
+ }
+ },
+ {
+ "name" : "extraClientExtension",
+ "comment" : "An extra extension is added in the ClientHello. Server should ignore it.",
+ "serverOnly" : "true",
+ "quirks" : {
+ "sendExtraExtension" : "0xA7C0"
+ }
+ },
+ {
+ "name" : "reconnectSelf",
+ "comment" : "Connection is closed and reconnection is performed; the session should be resumed.",
+ "reconnect" : "self"
+ },
+ {
+ "name" : "reconnectPeer",
+ "comment" : "Peer is tasked with closing then reconnecting; the session should be resumed.",
+ "reconnect" : "peer"
+ },
+ {
+ "name" : "reconnectSelfForgetSelf",
+ "comment" : "Connection is closed and reconnection is performed. Previous session if forgotten on our part.",
+ "reconnect" : "self",
+ "forget" : "self"
+ },
+ {
+ "name" : "reconnectSelfForgetPeer",
+ "comment" : "Peer should forget session. Then we close and reconnect.",
+ "reconnect" : "self",
+ "forget" : "peer"
+ },
+ {
+ "name" : "reconnectPeerForgetSelf",
+ "comment" : "We forget the session. Peer should close and reconnect.",
+ "reconnect" : "peer",
+ "forget" : "self"
+ },
+ {
+ "name" : "reconnectPeerForgetPeer",
+ "comment" : "Peer should forget session. Peer should close and reconnect.",
+ "reconnect" : "peer",
+ "forget" : "peer"
+ }
+ ]
+}
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIBcDCCARWgAwIBAgIJANd5T8SS4w7GMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMMCWxvY2FsaG9z
+dDAeFw0xNzAxMDEwMDAwMDBaFw0zNzEyMzEyMzU5NTlaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDBZ
+MBMGByqGSM49AgEGCCqGSM49AwEHA0IABDQAwvHS1NehjIe51KN7knKtaMhpFRR6WznOQlAVLAfY
+0Ciavy27K/uuQq4EakLKOEeiCj2BM2rcBxk8H49XX9KjUDBOMB0GA1UdDgQWBBSYWUJGpqkjBehD
+G9nG+aPmEIdbezAfBgNVHSMEGDAWgBSYWUJGpqkjBehDG9nG+aPmEIdbezAMBgNVHRMEBTADAQH/
+MAoGCCqGSM49BAMCA0kAMEYCIQCN1Jy39L/q84BGMi4CbhUxZ/0nkDzNmV5qVtXW9AOjuwIhANNA
+HpmN/KwgWM81fNjgdOOxZx+W1ksZsLqeEyCv0MK7
+-----END CERTIFICATE-----
--- /dev/null
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMCRS2RnKIEZdRwPuX+cYCsRestfVps6LEkpPy75xRp7oAoGCCqGSM49
+AwEHoUQDQgAENADC8dLU16GMh7nUo3uScq1oyGkVFHpbOc5CUBUsB9jQKJq/Lbsr
++65CrgRqQso4R6IKPYEzatwHGTwfj1df0g==
+-----END EC PRIVATE KEY-----
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIC+zCCAeOgAwIBAgIJAKVPxGMRfdQpMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCWxvY2Fs
+aG9zdDAeFw0xNzAxMDEwMDAwMDBaFw0zNzEyMzEyMzU5NTlaMBQxEjAQBgNVBAMMCWxvY2FsaG9z
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKgYMcLD/5p+4NpY2BYxH+IiyQ4s9u5y
+fs3FN7bZAqBwiQ5WbDAyCuESqrITtcVvBMmh3J/+yomWp59cVr8HNEGe+OW0NugZtpAre0tJxxAW
+WnZ50UO/V20F/5Y4esy40mSGSOyje8nl00TWk7tlIsBGgxiT9QCFT+XKSmRTwc7kZBoPCeKGzxG0
+hPHU/Vc/tAJJza03VlG0z6FoDC1kHY57rq43jjzyOkKmm6FjrRJdtBfMLgVEcyHgmolIO155cGm7
+g2llp/Ir2UPOP4ktEPLqJnNpLLFZRhb2b6iGzCItEyuzd66EQjz2ddYfE5ZL/FbWPsuXGGFPZAT3
+dvU92lsCAwEAAaNQME4wHQYDVR0OBBYEFEfHRJNlQtw3IIUnNrmyId8RtrWkMB8GA1UdIwQYMBaA
+FEfHRJNlQtw3IIUnNrmyId8RtrWkMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABIk
+UeU6lHgIwwb5qibE9CsIKnjlVR5nLodFQzYBNouB1v81fba/USJfMlHGiqcksL3h9ibzNS/a8PU2
+uSJKKR7Vgz/FN2PgqT4hzXEutKp3VP00+RnLmZDqAVaoSL+3cdMV1dNhwblfGTJEDX6QywUX2hdi
+SJ9muzUCP5oGHVvXjPKjDLYiti2amRJVui+B2cPHjb4l5+gmFwTVSQBuZZr8vFMFNVCIfMX5bR8T
+SRpGc+q1I7bCv3qbSy5xfjbRWHjNvYQ4Vr69PwL48t8To06GIbdvPLuxC1doFKxnUQMuqT+JPuD5
+wcvpmEj2eBvCkhrUP8Hz5z6ptiKrssfletk=
+-----END CERTIFICATE-----
--- /dev/null
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoGDHCw/+afuDa
+WNgWMR/iIskOLPbucn7NxTe22QKgcIkOVmwwMgrhEqqyE7XFbwTJodyf/sqJlqef
+XFa/BzRBnvjltDboGbaQK3tLSccQFlp2edFDv1dtBf+WOHrMuNJkhkjso3vJ5dNE
+1pO7ZSLARoMYk/UAhU/lykpkU8HO5GQaDwnihs8RtITx1P1XP7QCSc2tN1ZRtM+h
+aAwtZB2Oe66uN4488jpCppuhY60SXbQXzC4FRHMh4JqJSDteeXBpu4NpZafyK9lD
+zj+JLRDy6iZzaSyxWUYW9m+ohswiLRMrs3euhEI89nXWHxOWS/xW1j7LlxhhT2QE
+93b1PdpbAgMBAAECggEBAJtjWkSoeNWx6lwN+xtwp/+clm2jRVWhw/SmFl3R+Cqm
+PRxi6boX2JS9c8wQil0Lxso59cB1gXd1LFkVvB71IupycbWuRX+DnY9ikqRDfGAz
+ucaBz+AntkLTY7TTWzl6tQs2U51ld15pNUcScRivYlOKG1ASHk8v7W8H9IMQJj4A
+xbKmhG8azL3Iyyb9rjiQIDwJziqMEJlJxCddKbGAGwVaCItSkxMKQCN016iaf05j
+uNLgOo9Jv6X7844Luw0k+uSRmRFEMoI8jatucJtmpZ2ElMJ54VBIUZG8tA/Pknm6
+8lrGyFAmWczRodN+OknfoXHrCRx/u7Hfct05qWsKR0ECgYEA0oPoOKQ+E2O1ULFP
+j2sk1iz6zWx6zqz0bjMs4IJDhFD3jfHHwocYNydHJ18xVbW4pjKhuHo8y1FjjIwo
+mKWa7/BphFp57CcSVlI8Ef2mlfVj7z1QnldpMgWvW6dQSRDOrbNBC6c7aDouJZYo
+D7sofCgs//rUSMY+aCthGlq+quECgYEAzGnlW60BuPF4GVk3Y9Sb7h0io8LjbzWo
+oXxscfVqFGgIsa+DqJ003KjLq+Uwu0+IVg2o3cp2oxzIlcmBrpen3mpRO6dxanT/
+l45kLG4dL8lXZzjw95xLSNdDXEcBLwCYc5521DPg+240PKwOQ+lcxwjyDoO3VEQg
+yeaBzSqgCLsCgYBRCvMFi0VSlZoh3IDyh58AzQQovVBx7GeVXSIztDJl5/3FqYTr
+wLJz2S0tXRpTEshpQyi7KmPpKgYW/4ZJbce+A2G70FELtub6UGJL0silBnlYitRU
+gPZAiau+ryTbXBsVB+NMpy7ZqzxEwA/gLn8hfR4F1fyPn7I6zChvyuuIQQKBgQCd
+fMjUhMpa7s8U2IOwSlGIdrIFcVVAjRrKr83tTqLX7f8kxpCtC9F6YCHq4b1V0sS7
+Z/K+TgpxSO/RV1quZPFUjpzfVPYwisuQvIe5I20hMAJC6L/eRXBLQm4HXj0vNUo/
+acsrWnzvucxNIlIrSFPOlLnJLPnF1mdcpldC9qAtmQKBgGr1ish0HfAu4B8g29c2
+1p0CPByClSuoqVPVBIQDwHYU05+ehO/wBbHu+/SzIUT7MrWrgYiyXbWosTE9QRsP
+vk9+/MgffqwfBexIxZ39yfeYo2xwnVNzRSzcF3pbxs5Eahtyhu9xDX/wX4jrpYEY
+J4sdGBnEqRp44V6WYtT32+gk
+-----END PRIVATE KEY-----