From: Thomas Pornin Date: Sun, 30 Jul 2017 21:13:10 +0000 (+0200) Subject: Initial commit. X-Git-Url: https://www.bearssl.org/gitweb//home/git/?p=BoarSSL;a=commitdiff_plain;h=0703319f56ad16f1b0e0632842c41b6a8ebc11e7 Initial commit. --- 0703319f56ad16f1b0e0632842c41b6a8ebc11e7 diff --git a/Asn1/AsnElt.cs b/Asn1/AsnElt.cs new file mode 100644 index 0000000..9c6e1ab --- /dev/null +++ b/Asn1/AsnElt.cs @@ -0,0 +1,2315 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 subs = new List(); + 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 d = + new SortedDictionary( + 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 COMPARER_LEXICOGRAPHIC = + new ComparerLexicographic(); + + class ComparerLexicographic : IComparer { + + 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 r = new List(); + 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); + } +} + +} diff --git a/Asn1/AsnException.cs b/Asn1/AsnException.cs new file mode 100644 index 0000000..a2e6555 --- /dev/null +++ b/Asn1/AsnException.cs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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) + { + } +} + +} diff --git a/Asn1/AsnIO.cs b/Asn1/AsnIO.cs new file mode 100644 index 0000000..fb8cd3f --- /dev/null +++ b/Asn1/AsnIO.cs @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 fpo = new List(); + 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; + } +} + +} diff --git a/Asn1/AsnOID.cs b/Asn1/AsnOID.cs new file mode 100644 index 0000000..e794356 --- /dev/null +++ b/Asn1/AsnOID.cs @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 OIDToName = + new Dictionary(); + static Dictionary NameToOID = + new Dictionary(); + + 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; + } +} + +} diff --git a/Asn1/PEMObject.cs b/Asn1/PEMObject.cs new file mode 100644 index 0000000..9be6c1a --- /dev/null +++ b/Asn1/PEMObject.cs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/AES.cs b/Crypto/AES.cs new file mode 100644 index 0000000..1ffd454 --- /dev/null +++ b/Crypto/AES.cs @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } + } +} + +} diff --git a/Crypto/BigInt.cs b/Crypto/BigInt.cs new file mode 100644 index 0000000..893aa7c --- /dev/null +++ b/Crypto/BigInt.cs @@ -0,0 +1,580 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } + } +} + +} diff --git a/Crypto/BlockCipherCore.cs b/Crypto/BlockCipherCore.cs new file mode 100644 index 0000000..2f1b761 --- /dev/null +++ b/Crypto/BlockCipherCore.cs @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); +} + +} diff --git a/Crypto/ChaCha20.cs b/Crypto/ChaCha20.cs new file mode 100644 index 0000000..52e1fd7 --- /dev/null +++ b/Crypto/ChaCha20.cs @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } + } + } + } +} + +} diff --git a/Crypto/CryptoException.cs b/Crypto/CryptoException.cs new file mode 100644 index 0000000..5ce93c6 --- /dev/null +++ b/Crypto/CryptoException.cs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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) + { + } +} + +} diff --git a/Crypto/DES.cs b/Crypto/DES.cs new file mode 100644 index 0000000..3446d58 --- /dev/null +++ b/Crypto/DES.cs @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/DSAUtils.cs b/Crypto/DSAUtils.cs new file mode 100644 index 0000000..c58cf90 --- /dev/null +++ b/Crypto/DSAUtils.cs @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/DigestCore.cs b/Crypto/DigestCore.cs new file mode 100644 index 0000000..49171f0 --- /dev/null +++ b/Crypto/DigestCore.cs @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + } +} + +} diff --git a/Crypto/EC.cs b/Crypto/EC.cs new file mode 100644 index 0000000..959644d --- /dev/null +++ b/Crypto/EC.cs @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + +} + +} diff --git a/Crypto/ECCurve.cs b/Crypto/ECCurve.cs new file mode 100644 index 0000000..c92e7ec --- /dev/null +++ b/Crypto/ECCurve.cs @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/Crypto/ECCurve25519.cs b/Crypto/ECCurve25519.cs new file mode 100644 index 0000000..c31b813 --- /dev/null +++ b/Crypto/ECCurve25519.cs @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/ECCurvePrime.cs b/Crypto/ECCurvePrime.cs new file mode 100644 index 0000000..f2621bb --- /dev/null +++ b/Crypto/ECCurvePrime.cs @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } +} + +} diff --git a/Crypto/ECCurveType.cs b/Crypto/ECCurveType.cs new file mode 100644 index 0000000..c875c0a --- /dev/null +++ b/Crypto/ECCurveType.cs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 +} + +} diff --git a/Crypto/ECDSA.cs b/Crypto/ECDSA.cs new file mode 100644 index 0000000..e10e6d6 --- /dev/null +++ b/Crypto/ECDSA.cs @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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)); + } +} + +} diff --git a/Crypto/ECPrivateKey.cs b/Crypto/ECPrivateKey.cs new file mode 100644 index 0000000..d63692d --- /dev/null +++ b/Crypto/ECPrivateKey.cs @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + } +} + +} diff --git a/Crypto/ECPublicKey.cs b/Crypto/ECPublicKey.cs new file mode 100644 index 0000000..9db1c86 --- /dev/null +++ b/Crypto/ECPublicKey.cs @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/GHASH.cs b/Crypto/GHASH.cs new file mode 100644 index 0000000..7a5fe3c --- /dev/null +++ b/Crypto/GHASH.cs @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } + } +} + +} diff --git a/Crypto/HMAC.cs b/Crypto/HMAC.cs new file mode 100644 index 0000000..fcb1e29 --- /dev/null +++ b/Crypto/HMAC.cs @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } + } +} + +} diff --git a/Crypto/HMAC_DRBG.cs b/Crypto/HMAC_DRBG.cs new file mode 100644 index 0000000..abef663 --- /dev/null +++ b/Crypto/HMAC_DRBG.cs @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } +} + +} diff --git a/Crypto/IBlockCipher.cs b/Crypto/IBlockCipher.cs new file mode 100644 index 0000000..6a51b99 --- /dev/null +++ b/Crypto/IBlockCipher.cs @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); +} + +} diff --git a/Crypto/IDigest.cs b/Crypto/IDigest.cs new file mode 100644 index 0000000..87fb4bf --- /dev/null +++ b/Crypto/IDigest.cs @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/Crypto/IPrivateKey.cs b/Crypto/IPrivateKey.cs new file mode 100644 index 0000000..f29f81d --- /dev/null +++ b/Crypto/IPrivateKey.cs @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); +} + +} diff --git a/Crypto/IPublicKey.cs b/Crypto/IPublicKey.cs new file mode 100644 index 0000000..150e190 --- /dev/null +++ b/Crypto/IPublicKey.cs @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); +} + +} diff --git a/Crypto/MD5.cs b/Crypto/MD5.cs new file mode 100644 index 0000000..7b083f3 --- /dev/null +++ b/Crypto/MD5.cs @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } +} + +} diff --git a/Crypto/ModInt.cs b/Crypto/ModInt.cs new file mode 100644 index 0000000..2aa952a --- /dev/null +++ b/Crypto/ModInt.cs @@ -0,0 +1,1122 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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)); + } +} + +} diff --git a/Crypto/MutableECPoint.cs b/Crypto/MutableECPoint.cs new file mode 100644 index 0000000..ede06df --- /dev/null +++ b/Crypto/MutableECPoint.cs @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/MutableECPointCurve25519.cs b/Crypto/MutableECPointCurve25519.cs new file mode 100644 index 0000000..b3fdf7a --- /dev/null +++ b/Crypto/MutableECPointCurve25519.cs @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/MutableECPointPrime.cs b/Crypto/MutableECPointPrime.cs new file mode 100644 index 0000000..fa4b12c --- /dev/null +++ b/Crypto/MutableECPointPrime.cs @@ -0,0 +1,907 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 compressed, b = lsb of Y + * 0x04 uncompressed + * 0x06+b 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); + } +} + +} diff --git a/Crypto/NIST.cs b/Crypto/NIST.cs new file mode 100644 index 0000000..b21f277 --- /dev/null +++ b/Crypto/NIST.cs @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/Poly1305.cs b/Crypto/Poly1305.cs new file mode 100644 index 0000000..fb63429 --- /dev/null +++ b/Crypto/Poly1305.cs @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } +} + +} diff --git a/Crypto/RFC6979.cs b/Crypto/RFC6979.cs new file mode 100644 index 0000000..03a77b9 --- /dev/null +++ b/Crypto/RFC6979.cs @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } + } + } +} + +} diff --git a/Crypto/RNG.cs b/Crypto/RNG.cs new file mode 100644 index 0000000..ba4d5a9 --- /dev/null +++ b/Crypto/RNG.cs @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } + } + } + } + } +} + +} diff --git a/Crypto/RSA.cs b/Crypto/RSA.cs new file mode 100644 index 0000000..72d95ed --- /dev/null +++ b/Crypto/RSA.cs @@ -0,0 +1,560 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/RSAPrivateKey.cs b/Crypto/RSAPrivateKey.cs new file mode 100644 index 0000000..fea0a1a --- /dev/null +++ b/Crypto/RSAPrivateKey.cs @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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)"); + } + } +} + +} diff --git a/Crypto/RSAPublicKey.cs b/Crypto/RSAPublicKey.cs new file mode 100644 index 0000000..315a7a3 --- /dev/null +++ b/Crypto/RSAPublicKey.cs @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/SHA1.cs b/Crypto/SHA1.cs new file mode 100644 index 0000000..7ba7bce --- /dev/null +++ b/Crypto/SHA1.cs @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/SHA224.cs b/Crypto/SHA224.cs new file mode 100644 index 0000000..236bbd4 --- /dev/null +++ b/Crypto/SHA224.cs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 + }; +} + +} diff --git a/Crypto/SHA256.cs b/Crypto/SHA256.cs new file mode 100644 index 0000000..3328eb5 --- /dev/null +++ b/Crypto/SHA256.cs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 + }; +} + +} diff --git a/Crypto/SHA2Big.cs b/Crypto/SHA2Big.cs new file mode 100644 index 0000000..0e38ac6 --- /dev/null +++ b/Crypto/SHA2Big.cs @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/SHA2Small.cs b/Crypto/SHA2Small.cs new file mode 100644 index 0000000..fed41f4 --- /dev/null +++ b/Crypto/SHA2Small.cs @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/Crypto/SHA384.cs b/Crypto/SHA384.cs new file mode 100644 index 0000000..27eac33 --- /dev/null +++ b/Crypto/SHA384.cs @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 + }; +} + +} diff --git a/Crypto/SHA512.cs b/Crypto/SHA512.cs new file mode 100644 index 0000000..b5fb22b --- /dev/null +++ b/Crypto/SHA512.cs @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 + }; +} + +} diff --git a/SSLTLS/IO.cs b/SSLTLS/IO.cs new file mode 100644 index 0000000..82b8331 --- /dev/null +++ b/SSLTLS/IO.cs @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/IServerChoices.cs b/SSLTLS/IServerChoices.cs new file mode 100644 index 0000000..cf130e7 --- /dev/null +++ b/SSLTLS/IServerChoices.cs @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/SSLTLS/IServerPolicy.cs b/SSLTLS/IServerPolicy.cs new file mode 100644 index 0000000..38f914e --- /dev/null +++ b/SSLTLS/IServerPolicy.cs @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/SSLTLS/ISessionCache.cs b/SSLTLS/ISessionCache.cs new file mode 100644 index 0000000..077e6f6 --- /dev/null +++ b/SSLTLS/ISessionCache.cs @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/SSLTLS/InputRecord.cs b/SSLTLS/InputRecord.cs new file mode 100644 index 0000000..20d3df1 --- /dev/null +++ b/SSLTLS/InputRecord.cs @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/KeyUsage.cs b/SSLTLS/KeyUsage.cs new file mode 100644 index 0000000..4cbd144 --- /dev/null +++ b/SSLTLS/KeyUsage.cs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 +} + +} diff --git a/SSLTLS/OutputRecord.cs b/SSLTLS/OutputRecord.cs new file mode 100644 index 0000000..ebb81c9 --- /dev/null +++ b/SSLTLS/OutputRecord.cs @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + } + } + } +} + +} diff --git a/SSLTLS/PRF.cs b/SSLTLS/PRF.cs new file mode 100644 index 0000000..db8427e --- /dev/null +++ b/SSLTLS/PRF.cs @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } + } + } +} + +} diff --git a/SSLTLS/RecordDecrypt.cs b/SSLTLS/RecordDecrypt.cs new file mode 100644 index 0000000..a1b1008 --- /dev/null +++ b/SSLTLS/RecordDecrypt.cs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/SSLTLS/RecordDecryptCBC.cs b/SSLTLS/RecordDecryptCBC.cs new file mode 100644 index 0000000..347d062 --- /dev/null +++ b/SSLTLS/RecordDecryptCBC.cs @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/RecordDecryptChaPol.cs b/SSLTLS/RecordDecryptChaPol.cs new file mode 100644 index 0000000..13be67e --- /dev/null +++ b/SSLTLS/RecordDecryptChaPol.cs @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/RecordDecryptGCM.cs b/SSLTLS/RecordDecryptGCM.cs new file mode 100644 index 0000000..7dbae8c --- /dev/null +++ b/SSLTLS/RecordDecryptGCM.cs @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/RecordDecryptPlain.cs b/SSLTLS/RecordDecryptPlain.cs new file mode 100644 index 0000000..c20b6e8 --- /dev/null +++ b/SSLTLS/RecordDecryptPlain.cs @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/RecordEncrypt.cs b/SSLTLS/RecordEncrypt.cs new file mode 100644 index 0000000..af6c361 --- /dev/null +++ b/SSLTLS/RecordEncrypt.cs @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); +} + +} diff --git a/SSLTLS/RecordEncryptCBC.cs b/SSLTLS/RecordEncryptCBC.cs new file mode 100644 index 0000000..b0c55fe --- /dev/null +++ b/SSLTLS/RecordEncryptCBC.cs @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/RecordEncryptChaPol.cs b/SSLTLS/RecordEncryptChaPol.cs new file mode 100644 index 0000000..ead61dd --- /dev/null +++ b/SSLTLS/RecordEncryptChaPol.cs @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 ++; + } +} + +} diff --git a/SSLTLS/RecordEncryptGCM.cs b/SSLTLS/RecordEncryptGCM.cs new file mode 100644 index 0000000..2255155 --- /dev/null +++ b/SSLTLS/RecordEncryptGCM.cs @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/RecordEncryptPlain.cs b/SSLTLS/RecordEncryptPlain.cs new file mode 100644 index 0000000..3a87802 --- /dev/null +++ b/SSLTLS/RecordEncryptPlain.cs @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/SSL.cs b/SSLTLS/SSL.cs new file mode 100644 index 0000000..5250e5d --- /dev/null +++ b/SSLTLS/SSL.cs @@ -0,0 +1,859 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } + } +} + +} diff --git a/SSLTLS/SSLClient.cs b/SSLTLS/SSLClient.cs new file mode 100644 index 0000000..2122b94 --- /dev/null +++ b/SSLTLS/SSLClient.cs @@ -0,0 +1,905 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + 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 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 certs = new List(); + 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. + */ + } +} + +} diff --git a/SSLTLS/SSLEngine.cs b/SSLTLS/SSLEngine.cs new file mode 100644 index 0000000..e4ebf95 --- /dev/null +++ b/SSLTLS/SSLEngine.cs @@ -0,0 +1,1688 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } +} + +} diff --git a/SSLTLS/SSLException.cs b/SSLTLS/SSLException.cs new file mode 100644 index 0000000..1c36564 --- /dev/null +++ b/SSLTLS/SSLException.cs @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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)) + { + } +} + +} diff --git a/SSLTLS/SSLQuirks.cs b/SSLTLS/SSLQuirks.cs new file mode 100644 index 0000000..2a025e3 --- /dev/null +++ b/SSLTLS/SSLQuirks.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 d; + + public SSLQuirks() + { + d = new SortedDictionary( + 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; + } + } +} + +} diff --git a/SSLTLS/SSLServer.cs b/SSLTLS/SSLServer.cs new file mode 100644 index 0000000..6f39135 --- /dev/null +++ b/SSLTLS/SSLServer.cs @@ -0,0 +1,1019 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 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 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 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 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 clientSuites = new List(); + 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(); + 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(); + 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(); + List commonSuitesResume = new List(); + 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(); + 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(); + 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 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 FilterList(List v1, int[] v2, bool enforceV2) + { + List r = new List(); + 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; + } +} + +} diff --git a/SSLTLS/SSLServerPolicyBasic.cs b/SSLTLS/SSLServerPolicyBasic.cs new file mode 100644 index 0000000..d4bf1db --- /dev/null +++ b/SSLTLS/SSLServerPolicyBasic.cs @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 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"); + } + } +} + +} diff --git a/SSLTLS/SSLSessionCacheLRU.cs b/SSLTLS/SSLSessionCacheLRU.cs new file mode 100644 index 0000000..916638d --- /dev/null +++ b/SSLTLS/SSLSessionCacheLRU.cs @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 spx; + SSLSessionParameters[] data; + int count, maxCount; + + public SSLSessionCacheLRU(int maxCount) + { + spx = new Dictionary(); + 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(); + } +} + +} diff --git a/SSLTLS/SSLSessionParameters.cs b/SSLTLS/SSLSessionParameters.cs new file mode 100644 index 0000000..a1e0c03 --- /dev/null +++ b/SSLTLS/SSLSessionParameters.cs @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } +} + +} diff --git a/Tests/Poly1305Ref.cs b/Tests/Poly1305Ref.cs new file mode 100644 index 0000000..a1d0420 --- /dev/null +++ b/Tests/Poly1305Ref.cs @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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; + } + } +} diff --git a/Tests/TestCrypto.cs b/Tests/TestCrypto.cs new file mode 100644 index 0000000..cbcff5f --- /dev/null +++ b/Tests/TestCrypto.cs @@ -0,0 +1,3843 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 d = + new SortedDictionary( + 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(" "); + ECPrivateKey sk = new ECPrivateKey(curve, ToBin(ks[1])); + sk.CheckValid(); + Console.Write(" "); + 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(" "); + 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" + }; +} diff --git a/Tests/TestEC.cs b/Tests/TestEC.cs new file mode 100644 index 0000000..6049b07 --- /dev/null +++ b/Tests/TestEC.cs @@ -0,0 +1,553 @@ +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" + }; +} diff --git a/Tests/TestMath.cs b/Tests/TestMath.cs new file mode 100644 index 0000000..8d4c65a --- /dev/null +++ b/Tests/TestMath.cs @@ -0,0 +1,174 @@ +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)); + } + } +} diff --git a/Twrch/JSON.cs b/Twrch/JSON.cs new file mode 100644 index 0000000..9392847 --- /dev/null +++ b/Twrch/JSON.cs @@ -0,0 +1,694 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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, 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 d = + obj as IDictionary; + 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 val) + { + object gv; + if (!TryGet(obj, path, out gv)) { + val = null; + return false; + } + val = gv as IDictionary; + 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 GetObjectMap( + object obj, string path) + { + IDictionary 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) { + tw.Write("{"); + IDictionary d = + (IDictionary)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 ReadObject(TextReader tr) + { + IDictionary r = + new SortedDictionary( + 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 r = new List(); + 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)); + } +} diff --git a/Twrch/MergeStream.cs b/Twrch/MergeStream.cs new file mode 100644 index 0000000..4a5ac29 --- /dev/null +++ b/Twrch/MergeStream.cs @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + } + } +} diff --git a/Twrch/Twrch.cs b/Twrch/Twrch.cs new file mode 100644 index 0000000..6692d53 --- /dev/null +++ b/Twrch/Twrch.cs @@ -0,0 +1,1336 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 testsByName; + + int totalTests; + int totalSuccess; + int totalFailures; + + void Run(string[] args) + { + List r = new List(); + 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( + 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 d = + new SortedDictionary( + 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 msg = new List(); + 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 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 r = new List(); + 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); + } +} diff --git a/X500/DNPart.cs b/X500/DNPart.cs new file mode 100644 index 0000000..57f02a3 --- /dev/null +++ b/X500/DNPart.cs @@ -0,0 +1,550 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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 OID_TO_FT; + static Dictionary 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(); + FT_TO_OID = new Dictionary( + 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; + } +} + +} diff --git a/X500/X500Name.cs b/X500/X500Name.cs new file mode 100644 index 0000000..438faa6 --- /dev/null +++ b/X500/X500Name.cs @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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> dn) + { + Init(dn); + } + + void Init(List> dn) + { + int n = dn.Count; + List r = new List(); + PartsGen_ = new DNPart[n][]; + for (int i = 0; i < n; i ++) { + IDictionary dd = + new SortedDictionary( + 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> r = new List>(); + 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 r2 = new List(); + 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> dn = new List>(); + 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 r = new List(); + 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; + } +} + +} diff --git a/XKeys/AlgorithmIdentifier.cs b/XKeys/AlgorithmIdentifier.cs new file mode 100644 index 0000000..2d76e76 --- /dev/null +++ b/XKeys/AlgorithmIdentifier.cs @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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); + } + } +} diff --git a/XKeys/KF.cs b/XKeys/KF.cs new file mode 100644 index 0000000..2ce1100 --- /dev/null +++ b/XKeys/KF.cs @@ -0,0 +1,650 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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(); + } +} + +} diff --git a/ZInt/ZInt.cs b/ZInt/ZInt.cs new file mode 100644 index 0000000..c8333f4 --- /dev/null +++ b/ZInt/ZInt.cs @@ -0,0 +1,3250 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * 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, IEquatable { + + /* + * 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; + } + } + } +} diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..62d86fb --- /dev/null +++ b/build.cmd @@ -0,0 +1,5 @@ +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 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f54ab99 --- /dev/null +++ b/build.sh @@ -0,0 +1,22 @@ +#! /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 diff --git a/conf/bearssl.json b/conf/bearssl.json new file mode 100644 index 0000000..d46f349 --- /dev/null +++ b/conf/bearssl.json @@ -0,0 +1,250 @@ +{ + "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" + } + ] +} diff --git a/conf/eccert.pem b/conf/eccert.pem new file mode 100644 index 0000000..6de3d2f --- /dev/null +++ b/conf/eccert.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBcDCCARWgAwIBAgIJANd5T8SS4w7GMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMMCWxvY2FsaG9z +dDAeFw0xNzAxMDEwMDAwMDBaFw0zNzEyMzEyMzU5NTlaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABDQAwvHS1NehjIe51KN7knKtaMhpFRR6WznOQlAVLAfY +0Ciavy27K/uuQq4EakLKOEeiCj2BM2rcBxk8H49XX9KjUDBOMB0GA1UdDgQWBBSYWUJGpqkjBehD +G9nG+aPmEIdbezAfBgNVHSMEGDAWgBSYWUJGpqkjBehDG9nG+aPmEIdbezAMBgNVHRMEBTADAQH/ +MAoGCCqGSM49BAMCA0kAMEYCIQCN1Jy39L/q84BGMi4CbhUxZ/0nkDzNmV5qVtXW9AOjuwIhANNA +HpmN/KwgWM81fNjgdOOxZx+W1ksZsLqeEyCv0MK7 +-----END CERTIFICATE----- diff --git a/conf/eckey.pem b/conf/eckey.pem new file mode 100644 index 0000000..524b29c --- /dev/null +++ b/conf/eckey.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMCRS2RnKIEZdRwPuX+cYCsRestfVps6LEkpPy75xRp7oAoGCCqGSM49 +AwEHoUQDQgAENADC8dLU16GMh7nUo3uScq1oyGkVFHpbOc5CUBUsB9jQKJq/Lbsr ++65CrgRqQso4R6IKPYEzatwHGTwfj1df0g== +-----END EC PRIVATE KEY----- diff --git a/conf/rsacert.pem b/conf/rsacert.pem new file mode 100644 index 0000000..b18b3f4 --- /dev/null +++ b/conf/rsacert.pem @@ -0,0 +1,16 @@ +-----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----- diff --git a/conf/rsakey.pem b/conf/rsakey.pem new file mode 100644 index 0000000..8d6301c --- /dev/null +++ b/conf/rsakey.pem @@ -0,0 +1,28 @@ +-----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-----